{"record":{"id":"1d71d1a148d33f82","repo":"kubernetes/kops","slug":"error-writing-s-with-acl-q-v","errorCode":null,"errorMessage":"error writing %s (with ACL=%q): %v","messagePattern":"error writing (.+?) \\(with ACL=%q\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/pkg/vfs/s3fs.go","lineNumber":352,"sourceCode":"\tvar sseLog string\n\trequest.ServerSideEncryption, sseLog, _ = p.getServerSideEncryption(ctx)\n\n\tacl, err := p.getRequestACL(aclObj)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif acl != nil {\n\t\trequest.ACL = *acl\n\t}\n\n\t// We don't need Content-MD5: https://github.com/aws/aws-sdk-go/issues/208\n\n\tklog.V(8).Infof(\"Calling S3 PutObject Bucket=%q Key=%q SSE=%q ACL=%q\", p.bucket, p.key, sseLog, request.ACL)\n\n\t_, err = client.PutObject(ctx, request)\n\tif err != nil {\n\t\tif len(request.ACL) > 0 {\n\t\t\treturn fmt.Errorf(\"error writing %s (with ACL=%q): %v\", p, request.ACL, err)\n\t\t}\n\t\treturn fmt.Errorf(\"error writing %s: %v\", p, err)\n\t}\n\n\treturn nil\n}\n\n// To prevent concurrent creates on the same file while maintaining atomicity of writes,\n// we take a process-wide lock during the operation.\n// Not a great approach, but fine for a single process (with low concurrency)\n// TODO: should we enable versioning?\nvar createFileLockS3 sync.Mutex\n\nfunc (p *S3Path) CreateFile(ctx context.Context, data io.ReadSeeker, acl ACL) error {\n\tcreateFileLockS3.Lock()\n\tdefer createFileLockS3.Unlock()\n\n\t// Check if exists","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/util/pkg/vfs/s3fs.go#L334-L370","documentation":"WriteFile uploads the object with PutObject; when an ACL was attached to the request and the upload fails, the error is wrapped as \"error writing <path> (with ACL=<acl>)\" so the failing ACL is visible. The %v carries the underlying AWS error (most commonly AccessDenied).","triggerScenarios":"Calling WriteFile/CreateFile on an S3Path with an ACL set when PutObject fails: the ACL string is invalid/unrecognized, or the principal is not allowed to PutObject with that canned ACL (s3:PutObject with s3:x-amz-acl condition).","commonSituations":"KOPS_STATE_S3_ACL set to a value the bucket policy forbids (e.g. bucket-owner-full-control without cross-account permission); typo'd canned ACL names; cross-account state stores missing bucket-owner ACL grants.","solutions":["Read the wrapped AWS error: AccessDenied with ACL usually means the bucket policy's s3:x-amz-acl condition rejected the request","Set KOPS_STATE_S3_ACL to a valid canned ACL the policy allows (e.g. bucket-owner-full-control for cross-account state stores)","Verify the canned ACL spelling (private, public-read, bucket-owner-full-control, ...)","For cross-account buckets, ensure the bucket policy grants the kOps principal PutObject with the required ACL condition"],"exampleFix":"// before\nexport KOPS_STATE_S3_ACL=bucket-owner-read\n// after (allowed by the bucket policy)\nexport KOPS_STATE_S3_ACL=bucket-owner-full-control","handlingStrategy":"try-catch","validationCode":"// Confirm the canned ACL will be accepted: check bucket policy condition\npol, _ := s3Client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{Bucket: bucket})\nallowed := strings.Contains(aws.ToString(pol.Policy), \"s3:x-amz-acl\") // inspect conditions","typeGuard":"func validCannedACL(s string) bool {\n\tswitch types.ObjectCannedACL(s) {\n\tcase types.ObjectCannedACLPrivate, types.ObjectCannedACLPublicRead,\n\t\ttypes.ObjectCannedACLBucketOwnerFullControl, types.ObjectCannedACLBucketOwnerRead:\n\t\treturn true\n\t}\n\treturn false\n}","tryCatchPattern":"if err := p.WriteFile(ctx, data, meta, acl); err != nil {\n\tif AWSErrorCode(err) == \"AccessDenied\" {\n\t\treturn fmt.Errorf(\"PutObject rejected with ACL %q — align KOPS_STATE_S3_ACL with bucket policy: %w\", acl, err)\n\t}\n\treturn err\n}","preventionTips":["Set KOPS_STATE_S3_ACL=bucket-owner-full-control for cross-account state stores","Validate canned ACL names against the S3 enum before writes","Keep bucket policy s3:x-amz-acl conditions in sync with the configured ACL"],"tags":["aws","s3","write","acl","iam"],"backgroundTag":"s3-putobject-access-denied","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}