亚马逊AWS官方博客

全方位保护您在Amazon S3的数据资产系列I-访问控制详解

2006年,Amazon S3作为AWS发布的第一款公有云服务面世,如今,成千上万的AWS客户在利用S3创造各类激动人心的应用。从企业数据湖、机器学习存储,到HPC、渲染场景中的高性能共享存储,再到广泛的web应用中存储静态资源、日志、文件,Amazon S3以其高扩展性、高持久性、低成本的特性,在云原生架构中发挥了至关重要的作用。

大量的企业的数字资产存储在Amazon S3上,随之而来的问题是我们如何做好S3数据的安全保护?

Amazon S3本身提供了丰富的安全功能,作为企业的安全团队及IT研发、运维团队,应在自上而下的安全策略、基线、操作制定(Due Care)的基础上,积极并持续地评估Amazon S3以及其他AWS服务所提供的各项安全功能,进行自下而上的安全防护措施的设计和落地(Due Diligence)。

本篇博客将为读者总结Amazon S3相关的访问控制功能,给出通用最佳实践供读者参考,并尝试在S3访问控制的几项主要机制做深入的探讨。

 

1.Amazon S3的访问控制功能一览

Amazon S3提供了大量访问控制相关的功能,包括但不限于,

  1. Bucket ACL/Object ACL
  2. Bucket policy
  3. IAM policy (principle policy)
  4. Access Points
  5. Block Public Access
  6. Access Analyzer for S3

持续的安全功能发布有时会让客户感到疑惑,这么多的访问控制功能,是否属于过度设计,从易用性上是否打了折扣?

Amazon所践行的十四条领导力准则提到了‘客户至尚’和‘创新简化’,同时Amazon的产品开发方法论也提倡‘Working Backwards’。遵照这些理念,AWS始终站在客户的角度开发客户所需要的服务和功能,也保证了新发布的每个功能都对应着一套原本难以解决的客户挑战。在我们的现实世界中有大量的业务场景,对S3对象存储的访问控制提出了多角度要求,比如如何确保访问者和资源拥有者都能分别从自身需求角度管理访问权限,如何管理多账号甚至跨供应商的S3访问,如何在一个复杂组织中方便而精准地部署细粒度访问控制,如何在强制实施安全策略的同时给到业务、研发部门最大化的灵活度… 正是由于现实世界的需求驱动,才有了当前丰富的访问控制功能,同时每种新功能的发布都会确保对尚未探索该功能的客户的兼容和不受影响。

以下表格尝试将与S3访问控制相关的AWS功能映射到CIA安全模型:

Confidentiality Integrity Availability
IAM Policy IAM Policy 3AZ Availability (99.99%)
Bucket Policy Bucket Policy 11 9’s Durability (99.999999999%)
ACL ACL MFA Delete
Block Public Access Block Public Access Versioning
Access Point Access Analyzer Cross Region Replicate
Access Analyzer Guardduty Access Point
Server Side Encryption Object Lock
Enforced HTTPS Versioning
Macie VPC Endpoint
MFA Delete

除上述功能,AWS还提供大量服务和功能从监控、审计角度对S3的安全和访问控制方面进行了加强,包括但不限于 Amazon Cloudtrail, Amazon Trusted Advisor, Amazon S3 Access Log, Amazon CloudWatch, Amazon Config等等。

 

2. 功能对比及详解

由于篇幅所限,我将展开对几大主要的S3访问控制功能的侧重点、实际场景及其之间的关系进行梳理,而不再做各功能的基础性介绍,如需了解更多基础信息,请参考链接:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/s3-access-control.html

2.1 IAM Policy与Bucket Policy

Amazon IAM是AWS云服务的身份和访问管理的基石,任何对AWS服务的API调用,都会涉及到IAM身份和权限的验证过程。

对于S3的访问来说,对于通常访问控制的场景,详细剖析如下,

  • 使用某个IAM实体(通常是IAM user)的Access Key和Secret Key(AK/SK)直接访问某个存储桶/某个文件。

最基础的场景是,无论您使用AWS控制台,AWS命令行工具或者利用AWS SDK编程来访问S3存储(如读取某个文件),您的客户端底层都实现了利用您当前的IAM user对应的AK/SK来签发您的此次S3 API请求(如GetObject API),使得云上的IAM服务接到该请求后可以对您进行身份验证和授权。在身份验证过程中,IAM服务将通过验证签名来确认该请求发起的主体是您所宣称的主体(如IAM user bob),授权的过程则会通过仔细考察您的请求携带的上下文信息(访问主体,客体,源IP,请求协议,tag信息等等),结合您发起请求的IAM user所带有的IAM policy(一组AWS API权限的集合)信息,以及可能涉及到的资源端policy(如S3 bucket policy),最终确定您是否有权限发起该请求。

关于利用AK/SK进行请求签名的详细过程可参考以下SigV4的实现机制,您也可以参考此机制自己来做AWS请求的底层实现: https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4_signing.html

  • 使用与AWS服务集成的IAM role来访问。

由于您的AK/SK就像用户名密码一样可以完全代表您的身份,对他们的保护显得尤为重要。例如在一段需要读写S3 文件的程序实现中,最佳实践是不要将AK/SK明文地写入到您的代码中。基于您的代码所运行的计算服务的不同场景,有不同的方案来帮助您避免在代码中明文写入AK/SK。

如果您的程序部署在EC2上,可采用Instance profile功能来将某个IAM role绑定到该EC2实例上,IAM role的policy对应您希望程序拥有的IAM权限(如读写某个S3存储桶的某个prefix)。请参考https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html

如果您的程序部署在EKS容器集群,可利用EKS built-in的IRSA机制,通过在部署yaml文件中指定service account的方式来指定您希望程序拥有的IAM权限。请参考https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/

如果您的程序部署在Lambda函数中,可利用Lambda Execution Role来对应您希望程序拥有的IAM权限。请参考https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/lambda-intro-execution-role.html

在AWS的很多托管服务中也和Lambda类似,可通过为该服务设置某个IAM role来赋予该服务访问您的S3存储桶或者其他AWS服务的权限。在此不做赘述。

值得注意的是,通过IAM role方式为应用赋权的做法,其底层仍然是基于AK/SK签发API请求的做法,只不过不再需要用户来处理明文AK/SK,而是隐式地由AWS SDK底层去调用STS服务来获取临时AK/SK并完成请求签名的过程。用户和应用代码本身则不再需要接触到永久性的、明文的AK/SK,减少了密钥泄露的风险。

  • 使用您现有的身份和访问管理系统与IAM服务集成从而访问S3。

很多客户在自建应用系统为最终用户提供服务时,希望通过AD域或者现有的SSO系统来提供最终用户登录,并能够让最终用户获取到定制化的IAM权限,比如仅允许应用用户bob读写’S3://some-application-bucket/bob/*’存储桶资源等等。一种推荐的实现思路是,应用系统的登录服务将负责处理与SSO系统的集成,在确认用户成功登录后查询该用户的预设权限范围(一套权限集合对应一个IAM role),并负责调用AWS STS相关API来生成临时的IAM权限,并返回临时AK/SK给客户端,同时在客户端后续的访问中负责维护和更新该临时权限。通过这种设计,使得最终用户可以使用熟悉的登录模块来完成登录,并可以通过客户端程序使用IAM临时权限来直接请求S3等AWS资源来完成业务操作,这里的数据交互过程无需经过应用服务器,在减小应用服务器性能压力的同时,实现了扩展性、性能、安全性兼顾的云原生架构。

  • 与AWS提供的其他企业级IAM权限管理功能集成。

AWS提供诸如Amazon Organizations SCP, Permissions Boundary等企业级IAM权限管理功能,使得一个复杂的组织能够更灵活地集中管控企业多账号环境的权限边界,实现多层级的安全管控。对于即将或者正在探索这些功能的企业来说,理解这些功能之间的关系是规划设计的前提。对于扁平化、暂无集中化权限管理需求的企业来说,可暂时忽略这部分内容。

下图展示了从IAM主体角度的权限决策链,

 

一个IAM主体最终拥有的IAM permission,会经过图中不同权限管理功能的层层规范。举例来说,决定一个IAM user的最终权限,可以从该user的IAM policy所定义的权限出发,去掉Organizations SCP未赋予该user所在的AWS账号的那部分权限,去掉不在Permissions Boundary赋予该user的权限范围的权限,最终得到的即是该user真正拥有的IAM权限。在此图中,‘Session Principal’指的是由该user调用STS API(如assumerole)获取到另一个IAM role的临时权限,则assumed role(被调用的角色)的权限也会纳入最终考量。在此不做展开。

  • 利用Bucket Policy实现资源角度的权限管控。

在上述四种场景中描述的是不同场景下发起API请求的IAM实体,以及如何判断其最终所具有的IAM权限。而决定一个发往S3的API请求是否最终得到授权,IAM服务除了会检查IAM实体所拥有的权限,还会检查Bucket Policy所允许的权限。

Bucket Policy与IAM Policy的区别在于,IAM Policy从主体的角度规定了某个IAM实体(user, role)所拥有的权限,而Bucket Policy则从客体的角度规范某个S3存储桶所允许和/或禁止的来自某些主体的访问权限。换言之,IAM Policy是跟着IAM实体走,是访问方账号的权限管控意志的体现,而Bucket Policy则是跟着资源(S3存储桶)走的,是资源方账号的权限管控意志的体现。

IAM服务决定某一个针对S3的API请求最终是否成功时,会同时考察IAM Policy和Bucket Policy。这里的考察机制看似复杂,可能也让许多用户感到困惑。总体来说可以参考下图:

在本图中,User context对应的是发起请求的IAM user所在的AWS账号层面;Bucket context对应的是所请求的存储桶层面;Object context对应的是所请求的文件层面。对于某个文件的访问请求,将在这三个层面来考察该请求是否被允许。

为方便读者理解,也为了更贴近多数现实场景,这里做一下场景的简化,假设Bucket owner和Object owner是同一AWS账号,根据该AWS账号与IAM实体所在的AWS账号的异同,这里分几种情况做场景展开:

    1. 在IAM实体和S3存储桶同账号的场景下,Bucket Policy与IAM Policy中只要有一方允许某权限,结果即为允许。比如:IAM user bob的IAM policy具有访问s3://bucket-abc/prefix1/*的read权限,而bucket-abc存储桶的bucket policy中未含有allow IAM user bob的任何权限,甚至bucket policy为空。此时bob发起的针对s3://bucket-abc/prefix1/file1的读取请求将被允许。反之,如果bob的IAM policy中无S3权限,而他访问的存储桶的bucket policy允许了IAM user bob具有访问s3://bucket-abc/prefix1/*的read权限,则bob针对该prefix下的文件读取请求也将被允许。
    2. 在IAM实体和S3存储桶不同账号的场景下,Bucket Policy与IAM Policy均需要显式允许该操作,结果才为允许。所以在上一场景中提到的两种情况,Bucket Policy与IAM Policy都只有一方显式的允许,那么在这一场景中的访问结果都将是拒绝访问。
    3. 在任何情况下(无论该访问是来自同账号还是跨账号),Bucket Policy与IAM Policy中只要有一方显式的deny某实体具有该object访问的权限,则最终结果即为deny(拒绝访问)。

2.2 Object ACL与Bucket Policy

Amazon S3 ACL(Bucket ACL/Object ACL)属于旧有的Amazon S3访问控制机制,随着Amazon S3服务的诞生而诞生。在Amazon IAM服务发布后,S3 ACL权限管理机制逐渐与IAM服务进行了映射和融合,形成了今天的两种访问控制功能并行的状态。有关ACL的基本概念请参考https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/acl-overview.html

其中,Bucket ACL功能适用的场景很少,在此不做赘述。典型场景比如为本账号中负责存储S3 Server Access Log的S3存储桶设置Bucket ACL以允许S3 LogDelivery服务来写入日志。

Object ACL与Bucket Policy功能似乎类似,都可以实现对S3 文件的权限管控。那么两者有什么不同,又应该如何选择?

对于绝大多数场景来说,S3存储桶的拥有者(Bucket Owner)也是存储桶里文件的拥有者(Object Owner)。在这种情况下,推荐采用Bucket Policy来管理访问权限, 而无需考虑ACL设置。主要的原因是Object ACL所赋予权限的对象是AWS-Canonical-ID,该ID对应的是某个AWS账号,可以理解为Object ACL所赋权的主体是单个AWS账号;而Bucket Policy采用的是IAM授权机制,赋权的主体可以细化到单个AWS账号的单个IAM实体,且支持大量针对请求上下文信息的权限管控(source IP,tag,访问协议等),相比Object ACL来说,Bucket Policy实现的访问控制场景更灵活。不过,Object ACL可以针对单个文件设置访问权限,对于某些罕见的场景下,比如存在大量跨账号访问的权限要求都不一样的文件,使用Bucket Policy对各个文件单独赋权变得难以操作(Bucket Policy会冗长而难以管理),则可以考虑使用Object ACL。

值得再次强调的是,我们在谈到Bucket Owner和Object Owner时,两者都指的是AWS账号,而不是IAM实体,这一点对于理解ACL的概念很重要。可以简单的认为,ACL机制所规范的主体对象是AWS账号,而IAM机制所规范的主体对象是IAM实体(user, role等)。Amazon S3在设计上是允许Bucket Owner和 Object Owner是不同的AWS账号,这也是自2006年以来S3沿用至今的一种机制。Bucket Owner可以赋予Object Owner写入、读取桶的权限(无论是通过早期的ACL机制,还是通过现在的IAM跨账号赋权机制)。在默认情况下,Object Owner上传文件后,Bucket Owner对该文件是无读写权限的,除非Object Owner在上传时明确赋予了Bucket Owner相应的权限。

以下的例子说明了一种真实场景:公司A提供某种平台服务,允许业内众多公司采用自己的AWS账号上传文件到公司A的S3存储桶中,同时上传后的文件的权限仍由上传者账号来管理。在这种情况下,某个上传文件的公司B使用Object ACL来管理文件权限是比较恰当的选择。而从公司A的角度,采用Bucket Policy由于只能管控到自己所拥有的文件,默认是管不到桶里由其他AWS账号Own的文件权限的(这里的‘管不到’更多指的是读写权限,其实Bucket Owner可以编写Bucket Policy来拒绝外部对存储桶中任何对象的访问,也可以删除存储桶内的任何对象,无论这些对象是否属于他。这里的设计逻辑是,由于账单由存储桶拥有者来支付,为了保障自己的利益,存储桶拥有者需要有这样的权限)。当然,公司B也可以选择将full control权限赋予Bucket Owner(公司A),使得公司A不仅能够访问文件,而且能够管理文件的权限。具体来说,公司B可在调用S3 PutObject API写入文件时指定‘x-amz-acl’字段为‘bucket-owner-full-control’的方式来操作。参考:https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html

而如果站在bucket owner的角度,希望强制Object Owner在上传文件时赋予自己完全控制权限,否则不允许文件上传,则可以通过以下方式来实现:利用Bucket Policy中的Condition字段来给某个文件上传者设置相应的限制条件,

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "statement1",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AccountB-ID:user/bob"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::awsexamplebucket1/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-grant-full-control": "id=AccountA-CanonicalUserID"
        }
      }
    }
  ]
}

在现代架构设计中,往往由Bucket Owner统一管理权限的设计更常见,典型场景比如企业数据湖核心的S3权限管理。在数据湖架构中,来自于不同AWS账号、不同应用、不同形态的生产数据会通过不同的数据注入技术传入S3存储桶,在数据注入的过程中,其权限控制往往交由数据湖账号来统一管理,以便最终结合Bucket Policy、IAM Policy、Lakeformation服务、应用层认证授权等机制形成多层级的数据湖访问控制,实现企业对数据保护和访问控制的需求。如本节上面内容所揭示的,在跨账号的数据注入过程中有两种方式可以实现S3数据湖账号对数据文件拥有完全控制权限:

  1. 使用跨账号的IAM实体来写入文件,文件写入者需要在请求中指定‘x-amz-acl’字段为‘bucket-owner-full-control’。同时存储桶的S3 Object Ownership需设置为‘Bucket Owner Preferred’。当然,为了让跨账号IAM实体具有写入权限,需要在IAM Policy及S3 Bucket Policy层面同时确保显式的允许。
  2. 跨账号IAM实体可通过assumerole的方式获取存储桶所在账号下的某个role的权限(具备写入本账号下存储桶的权限),利用该role的权限完成数据的写入。同样,这个操作的前提是两方分别给予了对方assumerole/被对方assume的权限。关于这个场景的详细配置,我将在第二篇博客中为读者提供示例。

2.3 S3 接入点(S3 Access Point)

随着Amazon S3服务的日益普及和用户使用上的深入,很多客户发现,对于某些共享访问的S3存储桶,单纯使用Bucket Policy来进行资源侧的权限控制变得越来越难。例如某一个S3存储桶存有企业各部门的共享数据,前端十几个应用都需要访问该存储桶的数据,需要确保每个应用具有不同prefix不同条件的读写权限,如果仅采用一套冗长的Bucket Policy来管理,一旦新增应用或者涉及到现有应用权限的变更,很难保证Bucket Policy能被正确的配置,以及确认更改后的策略是否对其他应用造成影响。而且存储桶策略也面临总大小不能超过20KB的限制等。

在这种情况下,引入S3 接入点可以为客户提供很好的权限控制结构,就像将原来粗放式的大水管改造成一个个水龙头,每个水龙头对应一家用户(需要访问该S3桶的应用),可以对每个水龙头施加不一样的管控策略,而不影响到其他水龙头。类似的,您可以针对每个S3接入点设置不同的IAM Policy(甚至限制请求来自于哪个网络),当用户、应用通过该接入点访问S3存储桶的数据时,会受到接入点的IAM policy的管控,同时也会受到S3存储桶的bucket policy的管控。所以应用S3接入点的一种最佳实践是,在Bucket Policy层面,对所有IAM实体放开权限,仅仅实现‘必须通过S3接入点的请求才被允许’,而将具体的权限管控策略部署在每个接入点上。以下展示了一个示例Bucket Policy的写法,可以实现上述这一点:

{
    "Version": "2012-10-17",
    "Statement" : [
    {
        "Effect": "Allow",
        "Principal" : { "AWS": "*" },
        "Action" : "*",
        "Resource" : [ "arn:aws-cn:s3:::somebucket", " arn:aws-cn:s3:::somebucket/*"],
        "Condition": {
            "StringEquals" : { "s3:DataAccessPointAccount" : "123123123123" }
        }
    }
  ]
}

相应地,在某个接入点侧可以附加如下示例的IAM Policy做实际的权限控制:

{
    "Version":"2012-10-17",
    "Statement": [
    {
        "Effect": "Allow",
        "Principal": {
            "AWS": "arn:aws:iam::123123123123:user/Bob"
        },
        "Action": ["s3:GetObject", "s3:PutObject"],
        "Resource": "arn:aws-cn:s3:cn-north-1: 123123123123:accesspoint/my-access-point/Bob/*"
    }
  ]
}

这样最终实现的效果是,IAM user Bob只能通过访问my-access-point的访问点才能读取和写入自己的prefix下的文件,直接访问存储桶的请求则会被Bucket Policy拒绝。

2.4 Block Public Access(BPA)

Amazon S3提供了Block Public Access功能,该功能是方便AWS客户集中管理S3存储桶的公共权限的很实用的功能。

在默认情况下,新存储桶、访问点和对象不允许公有访问(公有访问即未经IAM SigV4签名的请求,比如通过浏览器直接访问某个S3文件的URL)。但是,用户可以修改存储桶策略、访问点策略或对象权限以允许公有访问。对于企业的安全部门来讲,限制甚至禁止普通用户有意或无意地将生产环境的S3存储桶对公暴露是常见的需求,也符合AWS安全最佳实践。通过BPA功能的应用,可以使得BPA设置覆盖这些用户设置的策略和权限,以便于统一地限制这些资源的公有访问。

BPA的配置方式很直观,可参考文档:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/configuring-block-public-access-account.html

2.5 S3访问控制案例:两组织间的S3文件拷贝

场景:A公司提供行业机器学习解决方案,B公司作为数据采集公司为A公司提供原始数据,以便A公司做后续的处理、模型训练等。B公司需要一次性将自己S3存储桶bucket-b里的海量数据安全高效地同步到A公司的存储桶bucket-a。

解决方案:B公司获取A公司的IAM role信息,双方配置信任关系,最终由A公司的IAM user以assumerole的方式获取B公司role的权限并发起S3同步操作。

配置步骤:

  • A公司创建名为’s3-ingest’的IAM role, 附上以下IAM Policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "List",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": [
                "arn:aws-cn:s3:::bucket-a",
                "arn:aws-cn:s3:::bucket-b"
            ]
        },
        {
            "Sid": "Get",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws-cn:s3:::bucket-b/*"
        },
        {
            "Sid": "Put",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws-cn:s3:::bucket-a/*"
        }
    ]
}
  • 添加’s3-ingest’ role的trust relationship规则如下
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": " 456456456456 "
      },
      "Action": "sts:AssumeRole"
      "Condition": {"StringEquals": {"sts:ExternalId": "COMPANYB666"}}
    }
  ]
}

该信任规则允许该role被B公司账号(456456456456)的IAM用户来assume。且通过指定External ID条件,强制要求B公司相关团队在上传过程中指定External ID,防止了Confused Deputy攻击,进一步增强了安全性。

  • A公司将IAM role ARN、External ID等信息告知B公司相关团队;
  • B公司配置bucket-b存储桶的Bucket Policy以允许A公司的role访问,
{
    "Version": "2008-10-17",
    "Id": "SourceBucket",
    "Statement": [
        {
            "Sid": "Read",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws-cn:iam::123123123123:role/s3-ingest"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws-cn:s3:::bucket-b/*",
                "arn:aws-cn:s3:::bucket-b"
            ]
        }
    ]	    
}   
  • B公司配置本地工作环境。在AWS命令行client端通过编辑config配置文件新配置一个profile,

#vi ~/.aws/config

在文本文件下方加入以下内容:

[profile new]

role_arn = arn:aws-cn:iam::123123123123:role/s3-ingest

external_id = COMPANYB666

source_profile = default

region = cn-north-1

  • B公司采用AWS命令行工具发起文件拷贝,

#aws s3 cp s3://bucket-b/ s3://bucket-a/ --profile new

  • 如配置正常,此时已发起了从源桶bucket-b往目标桶bucket-a的高速数据拷贝。等待拷贝结束后,B公司即可通知A公司拷贝任务完成。

 

总结

Amazon S3作为云原生架构的核心组件,提供了大量的访问控制功能,这要求我们AWS用户的安全团队、云团队对这些功能保持持续的了解,以便当工作中遇到S3访问控制相关的场景,无论是新应用新项目的架构设计、安全评估、安全测试,还是从企业/部门角度进行统一的安全规划,威胁建模,风险分析等,都可以做到游刃有余。

 

本篇作者

李萌

AWS资深解决方案架构师,负责跨国企业客户的解决方案咨询,应用架构设计优化,同时致力于AWS云存储及安全类服务的应用和推广,对云安全/高可用及容灾系统/区块链等技术有深入的研究和热情。加入AWS之前曾服务于多家跨国电信运营商及IT解决方案供应商,积累了丰富的电信/零售快消行业IT系统建设项目经验。