转载

AWS系列:深入了解IAM和访问控制

编者按:本文系InfoQ中文站向陈天的约稿,这是AWS系列文章的第一篇。以后会有更多文章刊出,但并无前后依赖的关系,每篇都自成一体。读者若要跟随文章来学习AWS,应该至少注册了一个AWS账号,事先阅读过当期所介绍服务的简介,并在AWS management console中尝试使用过该服务。否则,阅读的效果不会太好。

写在前面:访问控制,换句话说, 能在 什么 情况下访问 哪些 资源或者操作,是绝大部分应用程序需要仔细斟酌的问题。作为一个志存高远的云服务提供者,AWS自然也在访问控制上下了很大的力气,一步步完善,才有了今日的IAM:Identity and Access Management。如果你要想能够游刃有余地使用AWS的各种服务,在安全上的纰漏尽可能地少,那么,首先需要先深入了解 IAM。

基本概念

按照 AWS 的定义:

IAM enables you to control who can do what in your AWS account.

它提供了用户(users)管理、群组(groups)管理、角色(roles)管理和权限(permissions)管理等供AWS的客户来管理自己账号下面的资源。

1、首先说用户(users)。在AWS里,一个IAM user和unix下的一个用户几乎等价。你可以创建任意数量的用户,为其分配登录AWS management console所需要的密码,以及使用AWS CLI(或其他使用AWS SDK的应用)所需要的密钥。你可以赋予用户管理员的权限,使其能够任意操作AWS的所有服务,也可以依照Principle of least privilege,只授权合适的权限。下面是使用AWS CLI创建一个用户的示例:

saws> aws iam create-user --user-name tyrchen {  "User": {   "CreateDate": "2015-11-03T23:05:05.353Z",   "Arn": "arn:aws:iam::<ACCOUNT-ID>:user/tyrchen",   "UserName": "tyrchen",   "UserId": "AIDAISBVIGXYRRQLDDC3A",   "Path": "/"  } } 

当然,这样创建的用户是没有任何权限的,甚至无法登录,你可以用下面的命令进一步为用户关联群组,设置密码和密钥:

saws> aws iam add-user-to-group --user-name --group-name saws> aws iam create-login-profile --user-name --password saws> aws iam create-access-key --user-name

2、群组(groups)也等同于常见的unix group。将一个用户添加到一个群组里,可以自动获得这个群组所具有的权限。在一家小的创业公司里,其AWS账号下可能会建立这些群组:

  • Admins:拥有全部资源的访问权限
  • Devs:拥有大部分资源的访问权限,但可能不具备一些关键性的权限,如创建用户
  • Ops:拥有部署的权限
  • Stakeholders:拥有只读权限,一般给manager查看信息之用

创建一个群组很简单:

saws> aws iam create-group --group-name stakeholders {  "Group": {   "GroupName": "stakeholders",   "GroupId": "AGPAIVGNNEGMEPLHXY6JU",   "Arn": "arn:aws:iam::<ACCOUNT-ID>:group/stakeholders",   "Path": "/",   "CreateDate": "2015-11-03T23:15:47.021Z"  } } 

然而,这样的群组没有任何权限,我们还需要为其添加policy:

saws> aws iam attach-group-policy --group-name --policy-arn

在前面的例子和这个例子里,我们都看到了ARN这个关键字。ARN是Amazon Resource Names的缩写,在AWS里,创建的任何资源有其全局唯一的ARN。ARN是一个很重要的概念,它是访问控制可以到达的最小粒度。在使用AWS SDK时,我们也需要ARN来操作对应的资源。

policy是描述权限的一段JSON文本,比如AdministratorAccess这个policy,其内容如下:

{   "Version": "2012-10-17",   "Statement": [     {       "Effect": "Allow",       "Action": "*",       "Resource": "*"     }   ] }

用户或者群组只有添加了相关的policy,才会有相应的权限。

3、角色(roles)类似于用户,但没有任何访问凭证(密码或者密钥),它一般被赋予某个资源(包括用户),使其临时具备某些权限。比如说一个EC2实例需要访问DynamoDB,我们可以创建一个具有访问DynamoDB权限的角色,允许其被EC2 Service代入(AssumeRule),然后创建EC2的instance-profile使用这个角色。这样,这个EC2实例就可以访问DynamoDB了。当然,这样的权限控制也可以通过在EC2的文件系统里添加AWS配置文件设置某个用户的密钥(AccessKey)来获得,但使用角色更安全更灵活。角色的密钥是动态创建的,更新和失效都无须特别处理。想象一下如果你有成百上千个EC2实例,如果使用某个用户的密钥来访问AWS SDK,那么,只要某台机器的密钥泄漏,这个用户的密钥就不得不手动更新,进而手动更新所有机器的密钥。这是很多使用AWS多年的老手也会犯下的严重错误。

4、最后是权限(permissions)。AWS下的权限都通过policy document描述,就是上面我们给出的那个例子。policy是IAM的核心内容,我们稍后详细介绍。

每年的 AWS re:invent 大会,都会有一个session:Top 10 AWS IAM Best Practices,感兴趣的读者可以去 YouTube搜索 。 2015年的top 10(top 11) 如下:

  1. users: create individual users
  2. permissions: Grant least priviledge
  3. groups: manage permissions with groups
  4. conditions: restrict priviledged access further with conditions
  5. auditing: enable cloudTrail to get logs of API calls
  6. password: configure a strong password policy
  7. rotate: rotate security credentials regularly.
  8. MFA: enable MFA (Multi-Factor Authentication) for priviledged users
  9. sharing: use IAM roles to share access
  10. roles: use IAM roles for EC2 instances
  11. root: reduce or remove use of root

这11条best practices很清晰,我就不详细解释了。

按照上面的原则,如果一个用户只需要访问AWS management console,那么不要为其创建密钥;反之,如果一个用户只使用AWS CLI,则不要为其创建密码。一切不必要的,都是多余的——这就是安全之道。

使用 policy 做访问控制

上述内容你若理顺,IAM 就算入了门。但真要把握好IAM的精髓,需要深入了解policy,以及如何撰写policy。

前面我们看到,policy是用JSON来描述的,主要包含Statement,也就是这个policy拥有的权限的陈述,一言以蔽之,即: 在什么 条件 下能对哪些 资源 的哪些 操作 进行 处理 。也就是所谓的撰写policy的 PARCE 原则:

  • Principal:谁
  • Action:哪些操作
  • Resource:哪些资源
  • Condition:什么条件
  • Effect:怎么处理(Allow/Deny)

我们看一个允许对S3进行只读操作的policy:

{   "Version": "2012-10-17",   "Statement": [     {       "Effect": "Allow",       "Action": [         "s3:Get*",         "s3:List*"       ],       "Resource": "*"     }   ] }

其中,Effect是Allow,允许policy中所有列出的权限,Resource是 * ,代表任意S3的资源,Action有两个: s3:Get*s3:List* ,允许列出S3下的资源目录,及获取某个具体的S3 Object。

在这个policy里,Principal和Condition都没有出现。如果对资源的访问没有任何附加条件,是不需要Condition的;而这条policy的使用者是用户相关的principal(users, groups, roles),当其被添加到某个用户身上时,自然获得了principal的属性,所以这里不必指明,也不能指明。

所有的IAM managed policy是不需要指明Principal的。这种policy可以单独创建,在需要的时候可以被添加到用户、群组或者角色身上。另一大类policy是Resource policy,它们不能单独创建,只能依附在某个资源之上(所以也叫inline policy),这时候,需要指明Principal。比如,当我希望对一个S3 bucket使能Web hosting时,这个bucket里面的对象自然是要允许外界访问的,所以需要如下的inline policy:

{  "Version": "2012-10-17",  "Statement": [   {    "Effect": "Allow",    "Principal": "*",    "Action": "s3:GetObject",    "Resource": "arn:aws:s3:::corp-fs-web-bucket/*"   }  ] } 

这里,我们对于 arn:aws:s3:::corp-fs-web-bucket/* 这个资源的 s3:GetObject ,允许任何人访问( Principal: * )。

有时候,我们希望能更加精细地控制用户究竟能访问资源下的哪些数据,这个时候,可以使用Condition。比如对一个叫tyrchen的用户,只允许他访问personal-files这个S3 bucket下和他有关的目录:

{  "Version": "2012-10-17",  "Statement": [   {    "Action": [     "s3:ListBucket"    ],    "Effect": "Allow",    "Resource": [     "arn:aws:s3:::personal-files"    ],    "Condition": {     "StringLike": {      "s3:prefix": [       "tyrchen/*"      ]     }    }   },   {    "Action": [     "s3:GetObject",     "s3:PutObject"    ],    "Effect": "Allow",    "Resource": [     "arn:aws:s3:::personal-files/tyrchen/*"    ]   }  ] } 

这里我们用到了 StringLike 这个Condition,只有当访问的 s3:prefix 满足 tyrchen/* 时,才为真。我们还可以使用非常复杂的Condition:

"Condition": {     "IPAddress": {"aws:SourceIP": ["10.0.0.0/8", "4.4.4.4/32"]},     "StringEquals": {"ec2:ResourceTag/department": "dev"} }

在一条Condition下并列的若干个条件间是and的关系,这里IPAddress和StringEquals这两个条件必须同时成立;在某个条件内部则是or的关系,这里10.0.0.0/8和4.4.4.4/32任意一个源IP都可以访问。这个条件最终的意思是:对于一个EC2实例,如果其department标签是dev,且访问的源IP是10网段的内网地址或者4.4.4.4/32这个外网地址,则Condition成立。

讲完了Condition,我们再回到之前的policy。

这条policy里有两个statement,前一个允许列出 arn:aws:s3:::personal-files 下prefix是 tyrchen/* 里的任何对象;后一个允许读写 arn:aws:s3:::personal-files/tyrchen/* 里的对象。注意这个policy不能写成:

{  "Version": "2012-10-17",  "Statement": [   {    "Action": [     "s3:ListObject",     "s3:GetObject",     "s3:PutObject"    ],    "Effect": "Allow",    "Resource": [     "arn:aws:s3:::personal-files/tyrchen/*"    ]   }  ] } 

因为这样的话,用户在AWS management console连在personal-files下列出 /tyrchen 这个根目录的机会都没有了,自然也无法进行后续的操作。这样的policy其权限是不完备的。

上面的policy可以被添加到用户tyrchen身上,这样他就可以访问自己的私人目录;如果我们创建一个新用户叫lindsey,也想做类似处理,则需要再创建一个几乎一样的policy,非常不符合DRY(Don’t Repeat Yourself)原则。好在AWS也考虑到了这一点,它支持policy variable,允许用户在policy里使用AWS预置的一些变量,比如 ${aws:username} 。上述的policy里,把 tyrchen 替换为 ${aws:username} ,就变得通用多了。

以上是policy的一些基础用法,下面讲讲policy的执行规则,它也是几乎所有访问控制方案的通用规则:

  • 默认情况下,一切资源的一切行为的访问都是Deny
  • 如果在policy里显式Deny,则最终的结果是Deny
  • 否则,如果在policy里是Allow,则最终结果是Allow
  • 否则,最终结果是Deny

如下图:

AWS系列:深入了解IAM和访问控制

什么情况下我们会用到显式Deny呢?请看下面的例子:

{   "Version": "2012-10-17",   "Statement": [     {       "Effect": "Allow",       "Action": [         "dynamodb:*",         "s3:*"       ],       "Resource": [         "arn:aws:dynamodb:AWS-REGION-IDENTIFIER:ACCOUNT-ID-WITHOUT-HYPHENS:table/EXAMPLE-TABLE-NAME",         "arn:aws:s3:::EXAMPLE-BUCKET-NAME",         "arn:aws:s3:::EXAMPLE-BUCKET-NAME/*"       ]     },     {       "Effect": "Deny",       "NotAction": [         "dynamodb:*",         "s3:*"       ],       "NotResource": [         "arn:aws:dynamodb:AWS-REGION-IDENTIFIER:ACCOUNT-ID-WITHOUT-HYPHENS:table/EXAMPLE-TABLE-NAME",         "arn:aws:s3:::EXAMPLE-BUCKET-NAME",         "arn:aws:s3:::EXAMPLE-BUCKET-NAME/*"       ]     }   ] }

在这个例子里,我们只允许用户访问DynamoDB和S3中的特定资源,除此之外一律不允许访问。我们知道一个用户可以有多重权限,属于多个群组。所以上述policy里的第一个statement虽然规定了用户只能访问的资源,但别的policy可能赋予用户其他资源的访问权限。所以,我们需要第二条statement封死其他的可能。按照之前的policy enforcement的规则,只要看见Deny,就是最终结果,不会考虑其他policy是否有Allow,这样杜绝了一些隐性的后门,符合Principle of least privilege。

我们再看一个生产环境中可能用得着的例子,来证明IAM不仅「攘内」,还能「安外」。假设我们是一个手游公司,使用AWS Cognito来管理游戏用户。每个游戏用户的私人数据放置于S3之中。我们希望congito user只能访问他们各自的目录,IAM policy可以定义如下:

{   "Version": "2012-10-17",   "Statement": [     {       "Effect": "Allow",       "Action": ["s3:ListBucket"],       "Resource": ["arn:aws:s3:::awesome-game"],       "Condition": {"StringLike": {"s3:prefix": ["cognito/*"]}}     },     {       "Effect": "Allow",       "Action": [         "s3:GetObject",         "s3:PutObject",         "s3:DeleteObject"       ],       "Resource": [         "arn:aws:s3:::awesome-game/cognito/${cognito-identity.amazonaws.com:sub}",         "arn:aws:s3:::awesome-gamecognito/${cognito-identity.amazonaws.com:sub}/*"       ]     }   ] }

最后,讲一下如何创建policy。很简单:

$ aws iam create-policy --policy-name --policy-document

其中policy-document就是如上所示的一个个JSON文件。

参考文档

  • IAM policy overview
  • policy variables
  • policy evaluation logic

感谢魏星对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群 AWS系列:深入了解IAM和访问控制 )。

正文到此结束
Loading...