diff --git a/weed/filer/filechunk_manifest_resolve_test.go b/weed/filer/filechunk_manifest_resolve_test.go index 0daf1f274..8b1902f8b 100644 --- a/weed/filer/filechunk_manifest_resolve_test.go +++ b/weed/filer/filechunk_manifest_resolve_test.go @@ -402,6 +402,11 @@ func TestResolveChunkManifestKeepsRealErrorAfterInternalCancellation(t *testing. <-ctx.Done() return nil, earlyErr case "late": + // The assertion is specifically about preserving the earlier input's + // real error after a later input cancels the batch. Do not let the + // later lookup cancel the batch before the earlier lookup has entered; + // that scheduling race is especially visible on slower 32-bit CI. + <-earlyStarted close(lateStarted) return nil, lateErr default: diff --git a/weed/iam/helpers.go b/weed/iam/helpers.go index c335f8759..0e8016433 100644 --- a/weed/iam/helpers.go +++ b/weed/iam/helpers.go @@ -137,7 +137,7 @@ var baseS3ActionMap = map[string]string{ "GetBucketNotification": s3_constants.ACTION_READ, "PutBucketNotification": s3_constants.ACTION_WRITE, "GetBucketObjectLockConfiguration": s3_constants.ACTION_READ, - "PutBucketObjectLockConfiguration": s3_constants.ACTION_WRITE, + "PutBucketObjectLockConfiguration": s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG, // Multipart upload operations "CreateMultipartUpload": s3_constants.ACTION_WRITE, "UploadPart": s3_constants.ACTION_WRITE, @@ -191,6 +191,8 @@ func MapToIdentitiesAction(action string) string { return StatementActionTagging case s3_constants.ACTION_DELETE_BUCKET: return StatementActionDelete + case s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG: + return "PutBucketObjectLockConfiguration" case s3_constants.ACTION_PUT_BUCKET_POLICY: return "PutBucketPolicy" case s3_constants.ACTION_DELETE_BUCKET_POLICY: diff --git a/weed/s3api/s3api_object_lock_authz_test.go b/weed/s3api/s3api_object_lock_authz_test.go new file mode 100644 index 000000000..1f287b051 --- /dev/null +++ b/weed/s3api/s3api_object_lock_authz_test.go @@ -0,0 +1,57 @@ +package s3api + +import ( + "testing" + + iamlib "github.com/seaweedfs/seaweedfs/weed/iam" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" +) + +func TestPutObjectLockConfigurationUsesDedicatedAction(t *testing.T) { + bindings := handlerActionBindings(t) + if got := bindings["PutObjectLockConfigurationHandler"]; got != "ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG" { + t.Fatalf("PutObjectLockConfigurationHandler is gated on %s, want ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG", got) + } +} + +func TestObjectWriteDoesNotAllowPutObjectLockConfiguration(t *testing.T) { + objectWriter := &Identity{ + Name: "object-writer", + Actions: []Action{Action(s3_constants.ACTION_WRITE + ":test-bucket")}, + } + + if !objectWriter.CanDo(s3_constants.ACTION_WRITE, "test-bucket", "some/key") { + t.Fatal("precondition failed: the identity should be able to write objects") + } + if objectWriter.CanDo(s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG, "test-bucket", "") { + t.Fatal("an object writer was allowed to change the bucket Object Lock configuration") + } + + lockManager := &Identity{ + Name: "lock-manager", + Actions: []Action{ + Action(s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG + ":test-bucket"), + }, + } + if !lockManager.CanDo(s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG, "test-bucket", "") { + t.Fatal("an explicitly delegated identity was denied permission to change the bucket Object Lock configuration") + } +} + +func TestPutObjectLockConfigurationActionRoundTrips(t *testing.T) { + for _, policyAction := range []string{ + "s3:PutBucketObjectLockConfiguration", + "PutBucketObjectLockConfiguration", + } { + if got := iamlib.MapToStatementAction(policyAction); got != s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG { + t.Errorf("MapToStatementAction(%q) = %q, want %q", policyAction, got, s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG) + } + } + + if got := iamlib.MapToIdentitiesAction(s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG); got != "PutBucketObjectLockConfiguration" { + t.Errorf("MapToIdentitiesAction(%q) = %q, want PutBucketObjectLockConfiguration", s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG, got) + } + if got := mapBaseActionToS3Format(s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG); got != s3_constants.S3_ACTION_PUT_BUCKET_OBJECT_LOCK { + t.Errorf("mapBaseActionToS3Format(%q) = %q, want %q", s3_constants.ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG, got, s3_constants.S3_ACTION_PUT_BUCKET_OBJECT_LOCK) + } +} diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go index b93c7004d..c8c3cd328 100644 --- a/weed/s3api/s3api_server.go +++ b/weed/s3api/s3api_server.go @@ -965,7 +965,7 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) { // GetObjectLockConfiguration / PutObjectLockConfiguration (bucket-level operations) bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectLockConfigurationHandler, ACTION_READ)), "GET")).Queries("object-lock", "") - bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectLockConfigurationHandler, ACTION_WRITE)), "PUT")).Queries("object-lock", "") + bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectLockConfigurationHandler, ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG)), "PUT")).Queries("object-lock", "") // GetBucketTagging bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketTaggingHandler, ACTION_TAGGING)), "GET")).Queries("tagging", "")