基于 S3 存储桶的 AWS Cloudfront 分布,跨账户对象被拒绝访问

AWS Cloudfront distribution based on S3 bucket with cross-account objects getting Access denied

我有两个帐户(acc-1acc-2)。
acc-1 托管一个 API 来处理文件上传到 acc-1 的存储桶(我们称之为 upload)。上传会触发 SNS 以转换图像或转码视频。生成的文件被放置在 acc-1 (output) 中的另一个存储桶中,这再次触发了 SNS。然后我将文件(作为用户 api 来自 acc-1)复制到他们在 acc-2 (content) 中的最终存储桶。

content acc-2

中的存储桶策略
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<ACC_1_ID>:user/api"
      },
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::content/*"
    }
  ]
}

api acc-1

中的用户策略
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::upload/*",
        "arn:aws:s3:::output/*",
        "arn:aws:s3:::content/*"
      ]
    }
  ]
}

我使用 aws-sdk for nodejs 复制文件并将 ACL 设置为 bucket-owner-full-control,这样来自 acc-2 的用户可以访问 content 中复制的文件,尽管 api 来自 acc-1 的用户仍然是文件的所有者。

一切正常 - 文件存储在 content 存储桶中,存储桶所有者和 api 用户可以访问。

content 存储桶中的文件对其他所有人都是私有的,应该通过 Cloudfront 分发提供。

我为 Web 创建了一个新的 Cloudfront 发行版并使用了以下设置:

源域名:content
原始路径:/folder1
限制存储桶访问:yes
来源访问身份:create new identity
授予对存储桶的读取权限:yes, update bucket policy

这创建了一个新的源访问身份并将存储桶策略更改为:

content 之后的存储桶策略

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<ACC_1_ID>:user/api"
      },
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::content/*"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <OAI_ID>"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::content/*"
    }
  ]
}

但是,当我使用 Cloudfront URL:

时,尝试从 folder1 文件夹内的 content 存储桶访问文件不起作用
❌ https://abcdef12345.cloudfront.net/test1.jpg

这个returns一个403'Access denied'.

如果我将文件 (test2.jpg) 从 acc-2 直接上传到 content/folder1 并尝试访问它,它有效...!?

✅ https://abcdef12345.cloudfront.net/test2.jpg

除了所有者不同,test1.jpgtest2.jpg 看起来完全相同。

我做错了什么?

不幸的是,这是预期的行为。 OAI 无法访问由不同账户拥有(创建)的对象,因为 bucket-owner-full-control 使用了 "full" 的不寻常定义,该定义排除了对您自己的 AWS 账户之外的委托人的存储桶策略授权——并且 OAI 的规范用户是,从技术上讲,在您的 AWS 账户之外。

If another AWS account uploads files to your bucket, that account is the owner of those files. Bucket policies only apply to files that the bucket owner owns. This means that if another account uploads files to your bucket, the bucket policy that you created for your OAI will not be evaluated for those files.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-granting-permissions-to-oai

正如@Michael - sqlbot 在他的回答中指出的那样,这是预期的行为。

一个可能的解决方案是使用 acc-2 帐户的凭据执行到最终存储桶的复制,因此对象的所有者将始终是 acc-2。至少有两种选择:

1) 使用临时凭证和 AssumeRole AWS STS API:您在 acc-2 中创建了一个具有足够权限的 IAM 角色来执行复制到content 存储桶(PutObjectPutObjectAcl),然后从 acc-1 API 调用 AWS STS AssumeRole 以通过承担 IAM 角色获取临时凭证,并执行使用这些临时访问密钥的副本。 这是最安全的方法。

2) 使用访问密钥:您可以在 acc-2 中创建一个 IAM 用户,为其生成常规访问密钥,并将这些密钥处理到 acc-1,因此 acc-1使用那些 "permanent" 凭据来执行复制。 从安全的角度来看,跨 AWS 账户分发访问密钥并不是一个好主意,AWS 不鼓励您这样做,但这确实是可能的。此外,从可维护性的角度来看,这也是一个问题——因为 acc-1 应该以非常安全的方式存储访问密钥,而 acc-2 应该稍微频繁地轮换访问密钥。

解决这个问题有两个步骤。

  1. 运行 下面的命令使用源帐户凭据
aws s3api put-object-acl --bucket bucket_name --key object_name --acl bucket-owner-full-control
  1. 运行 下面的命令使用目标帐户凭据
aws s3 cp s3://object_path  s3://object_path  --metadata-directive COPY

我的解决方案是使用 s3 putobject 事件和 lambda。 在 acc-1 的 putobject 上,发出 s3 putobject 事件,并由 acc-2 的 lambda 覆盖对象。 这是我的程序(Python3)。

import boto3
from urllib.parse import unquote_plus


s3_client = boto3.client('s3')

def lambda_handler(event, context):
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = unquote_plus(record['s3']['object']['key'])
        filename = '/tmp/tmpfile'
        s3_client.download_file(bucket, key, filename)
        s3_client.upload_file(filename, bucket, key)