FinOps有望降低企业50%+的云成本! 了解详情
写点什么

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

  • 2015-11-08
  • 本文字数:5884 字

    阅读完需:约 19 分钟

编者按:本文系 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

如下图:

什么情况下我们会用到显式 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 文件。

参考文档


感谢魏星对本文的审校。

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

2015-11-08 17:0212731

评论

发布
暂无评论
发现更多内容

如何挑选一份工作

池建强

求职 找工作

重学 Java 设计模式:实战适配器模式

小傅哥

设计模式 小傅哥 重构 代码质量 代码坏味道

游戏夜读 | 如何管理公司?

game1night

奈学教育:Hadoop源码编译全流程分享

奈学教育

入门到放弃:理清前端技术概念

大伟

Java ecmascript 大前端 Node

这场大数据+AI Meetup,一次性安排了大数据当下热门话题

Apache Flink

大数据 flink 流计算 实时计算

原创 | TDD工具集:JUnit、AssertJ和Mockito (十六)编写测试-有条件执行测试

编程道与术

Java 编程 TDD 单元测试 JUnit

什么时候去面试

escray

【Sentry搭建之 docker-compose】

卓丁

DevOps Docker-compose CI/CD sentry

缓存与存储的一致性策略:从 CPU 到分布式系统

伴鱼技术团队

缓存 系统设计 cpu 系统架构 架构模式

程序员都惧怕的故障域

松花皮蛋me

Java 问题处理

Shell 文本处理一则

wong

Shell sed grep

千万别学编译原理

池建强

编译原理

LeetCode | 2. Reverse Integer 整数反转

Puran

Python C# 算法 LeetCode arts

【大厂面试02期】Redis过期key是怎么样清理的?

NotFound9

Java 数据库 redis 架构 后端

Docker 容器优雅终止方案

米开朗基杨

Docker

Flink 1.10 SQL、HiveCatalog 与事件时间整合示例

Apache Flink

大数据 flink 流计算 实时计算

普通二本,毕业三年,北漂之后,我是怎么成为程序猿的。

why技术

个人成长 程序人生 随笔杂谈 北漂

MyBatis之启动分析(一)

ytao

面试 mybatis

微信小程序开发 | 如何在小程序中使用自定义 icon 图标

彭宏豪95

微信小程序 学习 编程 大前端 IT

深入理解JVM内存管理 - 堆和栈

SkyeDance

堆栈 深入理解JVM VM参数

Java 走过的创新25年

田晓旭

Java25周年

吉德热泵烘干机解放阳台,引领生活品质新风尚

infoq小陈

Flink Weekly | 每周社区动态更新-20200520

Apache Flink

大数据 flink 流计算 实时计算

浅谈敏捷开发中的设计

czjczk

敏捷开发

Java 最新的JDK14.0.1调试成功

程李文华

【译】5 个你需要知道的 JavaScript 小技巧

零和幺

Java 大前端 技巧

手撕编译原理:汇编语言不会编

贾献华

工作的创新能力

punkboy

产品 重新理解创业 产品经理 创新突破 创新

centos7分区命令parted的用法(大于2T)

唯爱

ARTS 01 - 技术人的理想主义

jerry.mei

算法 Vue 练习 ARTS 打卡计划 ARTS活动

  • 需要帮助,请添加网站小助手,进入 InfoQ 技术交流群
AWS系列:深入了解IAM和访问控制_安全_陈天_InfoQ精选文章