Object locking need to persist the tags and set the headers (#6994)

* fix object locking read and write

No logic to include object lock metadata in HEAD/GET response headers
No logic to extract object lock metadata from PUT request headers

* add tests for object locking

* Update weed/s3api/s3api_object_handlers_put.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update weed/s3api/s3api_object_handlers.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor

* add unit tests

* sync versions

* Update s3_worm_integration_test.go

* fix legal hold values

* lint

* fix tests

* racing condition when enable versioning

* fix tests

* validate put object lock header

* allow check lock permissions for PUT

* default to OFF legal hold

* only set object lock headers for objects that are actually from object lock-enabled buckets

fix     --- FAIL: TestAddObjectLockHeadersToResponse/Handle_entry_with_no_object_lock_metadata (0.00s)

* address comments

* fix tests

* purge

* fix

* refactoring

* address comment

* address comment

* Update weed/s3api/s3api_object_handlers_put.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update weed/s3api/s3api_object_handlers_put.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update weed/s3api/s3api_object_handlers.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* avoid nil

* ensure locked objects cannot be overwritten

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Chris Lu
2025-07-16 23:00:25 -07:00
committed by GitHub
parent 89706d36dc
commit a524b4f485
19 changed files with 1397 additions and 255 deletions

View File

@@ -1,36 +0,0 @@
module github.com/seaweedfs/seaweedfs/test/s3/cors
go 1.19
require (
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.42
github.com/aws/aws-sdk-go-v2/credentials v1.13.40
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0
github.com/k0kubun/pp v3.0.1+incompatible
github.com/stretchr/testify v1.8.4
)
require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,63 +0,0 @@
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM=
github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8=
github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI=
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og=
github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 h1:6lJvvkQ9HmbHZ4h/IEwclwv2mrTW8Uq1SOB/kXy0mfw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4/go.mod h1:1PrKYwxTM+zjpw9Y41KFtoJCQrJ34Z47Y4VgVbfndjo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 h1:eev2yZX7esGRjqRbnVk1UxMLw4CyVZDpZXRCcy75oQk=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36/go.mod h1:lGnOkH9NJATw0XEPcAknFBj3zzNTEGRHtSw+CwC1YTg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 h1:v0jkRigbSD6uOdwcaUQmgEwG1BkPfAPDqaeNt/29ghg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4/go.mod h1:LhTyt8J04LL+9cIt7pYJ5lbS/U98ZmXovLOR/4LUsk8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 h1:wl5dxN1NONhTDQD9uaEvNsDRX29cBmGED/nl0jkWlt4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0/go.mod h1:rDGMZA7f4pbmTtPOk5v5UM2lmX6UAbRnMDJeDvnH7AM=
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64=
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -29,7 +29,7 @@ func TestCORSPreflightRequest(t *testing.T) {
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag", "Content-Length"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -81,7 +81,7 @@ func TestCORSActualRequest(t *testing.T) {
AllowedMethods: []string{"GET", "PUT"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag", "Content-Length"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -175,7 +175,7 @@ func TestCORSOriginMatching(t *testing.T) {
AllowedMethods: []string{"GET"},
AllowedOrigins: tc.allowedOrigins,
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -268,7 +268,7 @@ func TestCORSHeaderMatching(t *testing.T) {
AllowedMethods: []string{"GET", "POST"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -349,7 +349,7 @@ func TestCORSMethodMatching(t *testing.T) {
AllowedMethods: []string{"GET", "POST"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -413,14 +413,14 @@ func TestCORSMultipleRulesMatching(t *testing.T) {
AllowedMethods: []string{"GET"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
{
AllowedHeaders: []string{"Authorization"},
AllowedMethods: []string{"POST", "PUT"},
AllowedOrigins: []string{"https://api.example.com"},
ExposeHeaders: []string{"Content-Length"},
MaxAgeSeconds: 7200,
MaxAgeSeconds: aws.Int32(7200),
},
},
}

View File

@@ -128,7 +128,7 @@ func TestCORSConfigurationManagement(t *testing.T) {
AllowedMethods: []string{"GET", "POST", "PUT"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -152,7 +152,7 @@ func TestCORSConfigurationManagement(t *testing.T) {
assert.Equal(t, []string{"GET", "POST", "PUT"}, rule.AllowedMethods, "Allowed methods should match")
assert.Equal(t, []string{"https://example.com"}, rule.AllowedOrigins, "Allowed origins should match")
assert.Equal(t, []string{"ETag"}, rule.ExposeHeaders, "Expose headers should match")
assert.Equal(t, int32(3600), rule.MaxAgeSeconds, "Max age should match")
assert.Equal(t, aws.Int32(3600), rule.MaxAgeSeconds, "Max age should match")
// Test 4: Update CORS configuration
updatedCorsConfig := &types.CORSConfiguration{
@@ -162,7 +162,7 @@ func TestCORSConfigurationManagement(t *testing.T) {
AllowedMethods: []string{"GET", "POST"},
AllowedOrigins: []string{"https://example.com", "https://another.com"},
ExposeHeaders: []string{"ETag", "Content-Length"},
MaxAgeSeconds: 7200,
MaxAgeSeconds: aws.Int32(7200),
},
},
}
@@ -209,21 +209,21 @@ func TestCORSMultipleRules(t *testing.T) {
AllowedMethods: []string{"GET", "HEAD"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
{
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowedMethods: []string{"POST", "PUT", "DELETE"},
AllowedOrigins: []string{"https://app.example.com"},
ExposeHeaders: []string{"ETag", "Content-Length"},
MaxAgeSeconds: 7200,
MaxAgeSeconds: aws.Int32(7200),
},
{
AllowedHeaders: []string{"*"},
AllowedMethods: []string{"GET"},
AllowedOrigins: []string{"*"},
ExposeHeaders: []string{"ETag"},
MaxAgeSeconds: 1800,
MaxAgeSeconds: aws.Int32(1800),
},
},
}
@@ -307,7 +307,7 @@ func TestCORSValidation(t *testing.T) {
AllowedHeaders: []string{"*"},
AllowedMethods: []string{"GET"},
AllowedOrigins: []string{"https://example.com"},
MaxAgeSeconds: -1,
MaxAgeSeconds: aws.Int32(-1),
},
},
}
@@ -333,7 +333,7 @@ func TestCORSWithWildcards(t *testing.T) {
AllowedMethods: []string{"GET", "POST"},
AllowedOrigins: []string{"https://*.example.com"},
ExposeHeaders: []string{"*"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -370,7 +370,7 @@ func TestCORSRuleLimit(t *testing.T) {
AllowedHeaders: []string{"*"},
AllowedMethods: []string{"GET"},
AllowedOrigins: []string{fmt.Sprintf("https://example%d.com", i)},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
}
}
@@ -389,7 +389,7 @@ func TestCORSRuleLimit(t *testing.T) {
AllowedHeaders: []string{"*"},
AllowedMethods: []string{"GET"},
AllowedOrigins: []string{"https://example101.com"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
})
corsConfig.CORSRules = rules
@@ -450,7 +450,7 @@ func TestCORSObjectOperations(t *testing.T) {
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedOrigins: []string{"https://example.com"},
ExposeHeaders: []string{"ETag", "Content-Length"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -501,7 +501,7 @@ func TestCORSCaching(t *testing.T) {
AllowedHeaders: []string{"*"},
AllowedMethods: []string{"GET"},
AllowedOrigins: []string{"https://example.com"},
MaxAgeSeconds: 3600,
MaxAgeSeconds: aws.Int32(3600),
},
},
}
@@ -526,7 +526,7 @@ func TestCORSCaching(t *testing.T) {
AllowedHeaders: []string{"Content-Type"},
AllowedMethods: []string{"GET", "POST"},
AllowedOrigins: []string{"https://example.com", "https://another.com"},
MaxAgeSeconds: 7200,
MaxAgeSeconds: aws.Int32(7200),
},
},
}
@@ -548,7 +548,7 @@ func TestCORSCaching(t *testing.T) {
assert.Equal(t, []string{"Content-Type"}, rule.AllowedHeaders, "Should have updated headers")
assert.Equal(t, []string{"GET", "POST"}, rule.AllowedMethods, "Should have updated methods")
assert.Equal(t, []string{"https://example.com", "https://another.com"}, rule.AllowedOrigins, "Should have updated origins")
assert.Equal(t, int32(7200), rule.MaxAgeSeconds, "Should have updated max age")
assert.Equal(t, aws.Int32(7200), rule.MaxAgeSeconds, "Should have updated max age")
}
// TestCORSErrorHandling tests various error conditions