【问题标题】:AWS Cloudfront distribution based on S3 bucket with cross-account objects getting Access denied基于 S3 存储桶的 AWS Cloudfront 分发,跨账户对象被拒绝访问
【发布时间】:2020-09-23 09:54:51
【问题描述】:

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

contentacc-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/*"
    }
  ]
}

acc-1 中的api 用户策略

{
  "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/*"
      ]
    }
  ]
}

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

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

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

我为网络创建了一个新的 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

这将返回 403“拒绝访问”。

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

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

除了拥有不同的所有者之外,test1.jpgtest2.jpg 似乎完全相同。

我做错了什么?

【问题讨论】:

    标签: amazon-web-services amazon-s3 amazon-cloudfront


    【解决方案1】:

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

    如果另一个 AWS 账户将文件上传到您的存储桶,则该账户是这些文件的所有者。存储桶策略仅适用于存储桶所有者拥有的文件。这意味着,如果其他账户将文件上传到您的存储桶,则不会针对这些文件评估您为 OAI 创建的存储桶策略。

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

    【讨论】:

    • 感谢您的回复 - 但有什么解决方案呢?以不同的方式复制这些文件?复制后更改所有者? ...
    • 我有同样的问题,但没有安全的解决方法。我正在考虑将我的存储桶公开,因为无论如何它都被用作公共网站 - 我只是想强制用户通过云端强制使用 HTTPS。
    • 也许在复制时将 ACL 设置为 bucket-owner-full-control 可以解决问题
    • 在使用源帐户的身份复制对象时仅将 ACL 设置为 bucket-owner-full-control 无济于事,因为这仍然会设置错误的对象所有权。 CF 在这里的挑剔之处在于它要求 OAI 访问的对象属于同一帐户。
    【解决方案2】:

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

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

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

    2) 使用访问密钥:您可以在 acc-2 中创建一个 IAM 用户,为其生成常规访问密钥,并将这些密钥处理到 acc-1,因此 acc-1 使用这些“永久”凭证来执行副本。 从安全角度来看,跨 AWS 账户分发访问密钥并不是一个好主意,AWS 不鼓励您这样做,但它肯定是可能的。此外,从可维护性的角度来看,这也可能是一个问题 - 因为acc-1 应该以非常安全的方式存储访问密钥,而acc-2 应该经常轮换访问密钥。

    【讨论】:

      【解决方案3】:

      解决方案分两步。

      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
      

      【讨论】:

        【解决方案4】:

        我的解决方案是使用 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)
        

        【讨论】:

          猜你喜欢
          • 2017-07-04
          • 2018-05-19
          • 2019-02-03
          • 1970-01-01
          • 2021-12-02
          • 1970-01-01
          • 2021-02-21
          • 2021-06-07
          • 2019-04-23
          相关资源
          最近更新 更多