Rails 7.2.3开启public:true后无法写入S3兼容存储桶
Rails 7.2.3 向S3兼容存储桶写入失败的解决方案
问题场景
使用Rails 7.2.3向Linode对象存储(S3兼容)上传文件时,执行以下代码:
Users::User.last.avatar.attach(io: img_io, ...)
触发错误:
(railsapp):4:in '<main>': A header (x-amz-acl) that you provided implies functionality that is not implemented. (Aws::S3::Errors::NotImplemented)
需求:
- 存储桶禁止他人写入/删除,但允许所有人无需签名URL即可读取文件
- 关闭
public: true时可正常写入,但生成的Blob URL是带过期时间的签名URL,不符合公开访问需求
当前配置与存储桶信息
storage.yml配置
bucketname: service: S3 endpoint: https://region.linodeobjects.com access_key_id: access_key secret_access_key: secret_access_key region: default bucket: bucketname public: true
存储桶权限信息(s3cmd输出)
# s3cmd info s3://bucketname: Location: default Payer: none Ownership: none Versioning:none Expiration rule: none Block Public Access: none Policy: { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] }, { "Sid": "auto-1234", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::user/blablah" }, "Action": [ "s3:AbortMultipartUpload", "s3:DeleteBucketWebsite", "s3:DeleteObject", "s3:DeleteObjectTagging", "s3:DeleteObjectVersion", "s3:DeleteObjectVersionTagging", "s3:DeleteReplicationConfiguration", "s3:PutBucketCORS", "s3:PutBucketLogging", "s3:PutBucketNotification", "s3:PutBucketTagging", "s3:PutBucketVersioning", "s3:PutBucketWebsite", "s3:PutLifecycleConfiguration", "s3:PutObject", "s3:PutObjectTagging", "s3:PutObjectAcl", "s3:PutObjectVersionAcl", "s3:PutObjectVersionTagging", "s3:PutReplicationConfiguration", "s3:RestoreObject", "s3:GetBucketAcl", "s3:GetBucketCORS", "s3:GetBucketLocation", "s3:GetBucketLogging", "s3:GetBucketNotification", "s3:GetBucketPolicy", "s3:GetBucketTagging", "s3:GetBucketVersioning", "s3:GetBucketWebsite", "s3:GetLifecycleConfiguration", "s3:GetObjectAcl", "s3:GetObject", "s3:GetObjectTagging", "s3:GetObjectTorrent", "s3:GetReplicationConfiguration", "s3:GetObjectVersionAcl", "s3:GetObjectVersion", "s3:GetObjectVersionTagging", "s3:GetObjectVersionTorrent", "s3:ListBucketMultipartUploads", "s3:ListBucket", "s3:ListBucketVersions", "s3:ListMultipartUploadParts" ], "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] } ] } CORS: none ACL: *anon*: READ ACL: blah: FULL_CONTROL URL: http://bucketname.region.linodeobjects.com/
解决方案
核心原因
Linode对象存储不支持通过x-amz-acl请求头设置对象级ACL(你的存储桶已通过桶策略和ACL全局配置了公开读),但Rails的public: true配置会自动给上传请求添加x-amz-acl: public-read头,触发NotImplemented错误。
修改配置
更新storage.yml,禁用x-amz-acl头发送,同时配置生成无签名的公开URL:
bucketname: service: S3 endpoint: https://region.linodeobjects.com access_key_id: access_key secret_access_key: secret_access_key region: default bucket: bucketname # 移除原有的public: true public: false # 禁止上传时发送x-amz-acl头 upload_options: acl: nil # 配置URL生成时跳过签名,直接生成公开URL url_options: skip_signature: true
验证效果
- 执行上传代码不再触发
Aws::S3::Errors::NotImplemented错误 - 生成的Blob URL为无签名的公开地址(如
http://bucketname.region.linodeobjects.com/xxx/xxx/file.jpg),所有人可直接访问 - 存储桶权限保持原有策略:仅指定IAM用户拥有写/删除权限,其他用户仅能读取文件
内容的提问来源于stack exchange,提问作者Nick M
相关产品推荐
相关产品推荐

