深入了解IAM和访问控制

本文为InfoQ中文站特供稿件,首发地址为:http://www.infoq.com/cn/articles/aws-iam-dive-in。

访问控制,换句话说,谁 能在 什么 情况下访问 哪些 资源或者操作,是绝大部分应用程序需要仔细斟酌的问题。作为一个志存高远的云服务提供者,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 的客户来管理自己账号下面的资源。

首先说用户(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

群组(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,才会有相应的权限。

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

最后是权限(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 里面的 objects 自然是要允许外界访问的,所以需要如下的 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 instance,如果其 department 标签是 dev,且访问的源 IP 是10网段的内网地址或者 4.4.4.4/32 这个外网地址,则 Condition 成立。

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

这条 policy 里有两个 statement,前一个允许列出 arn:aws:s3:::personal-files 下 prefix 是 tyrchen/*里的任何 object;后一个允许读写 arn:aws:s3:::personal-files/tyrchen/* 里的 object。注意这个 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 文件。

原文发布于微信公众号 - 程序人生(programmer_life)

原文发表时间:2015-11-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏杨建荣的学习笔记

Oracle 12c中DBCA搭建备库体验(r11笔记第92天)

Oracle 12c中DBCA有一个特性看起来蛮有意思,就是直接通过DBCA来搭建Data Guard,当然这么说也有点噱头,我们来实际看看。 Ora...

3346
来自专栏别先生

摸索出来的chrom调试前后台数据(Java&&Ajax)交互的方法分享一下咯!!!

1:开始没想分享的,后来看到有大佬分享如何使用Chrom的工具进行调试,哈哈哈哼,我就借着他的博客写一下我摸索的如何进行前后台数据交互吧(注:反正是自己瞎  捣...

33210
来自专栏安恒网络空间安全讲武堂

2018红帽杯线下攻防赛Web总结

2013
来自专栏架构之路

超清晰的makefile解释、编写与示例

Makefile范例教学 Makefile和GNU make可能是linux世界里最重要的档案跟指令了。编译一个小程式,可以用简单的command来进行编译;稍...

3308
来自专栏Bug生活2048

告别单调工作系列——利用python再次拯救漂亮妹子

记得帮妹子搞定自动提交表单之后的第三天,妹子端着奶茶乐呵呵的来找我,和我一番畅谈理想,又指点江山之后,终于切入了正题。

682
来自专栏花叔的专栏

第一个摇一摇小程序?在Nodes里摇一摇

话说,小程序也能做摇一摇功能,但好像没怎么看到过有什么小程序做了这个?(大家如果看到,可以留言推荐一下),按耐不住心中好奇心的花叔这两天研究了一下。 在说这之前...

3147
来自专栏Android相关

X86处理器架构--Nehalem

最开始的处理器比较简单,8086处理器是评估当前的指令指针(CS:IP)指向的指令,然后再执行解码、执行、退出,并移动指令指针到下一个位置,每一个新的芯片都做了...

1004
来自专栏腾讯云数据库团队的专栏

GreenPlum 简单性能测试与分析(续)

作者介绍:黄辉,16年毕业于电子科技大学并加入腾讯。目前在腾讯云存储产品团队从事云数据库开发工作,喜欢研究分布式数据库相关技术(如:分布式事务,高可用性等)。 ...

8056
来自专栏FreeBuf

使用Digispark和Duck2Spark打造一个廉价USB橡皮鸭

如今市面上出现了许多优秀的硬件黑客工具,但缺点是这些工具的价格往往非常的高昂。因此,许多黑客更愿意自己动手打造更为廉价的专属版本。本文我将教大家使用Digisp...

514
来自专栏杨建荣的学习笔记

物化视图刷新结合ADG的尝试 (r8笔记第47天)

最近开发提了几个需求,需要把几个线上的分布式的表整合到统计系统中方便统计,看来分久必合,合久必分,当时的分开考虑,肯定没有想到以后会整合起来,这 可对我们是一些...

35210

扫码关注云+社区