深入了解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 条评论
登录 后参与评论

相关文章

来自专栏数据之美

迷之 crontab 异常:不运行、不报错、无日志

1、背景 前几天新同学入职,一不小心将跳板机上的 crontab 清空了,导致凌晨一大批任务异常,同事问了运维同学也没有备份,这一百多个任务要是恢复起来可不是件...

3526
来自专栏繁花云

关于电脑无法开机或无法启动的几种可能和解决方案

(转载自http://www.bios.net.cn/e/DoPrint/?classid=34&id=650 )

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

今天碰到的几个问题20151023(r6笔记第97天)

每天工作中会碰到一些问题,圈内朋友也会有各种问题,解决问题总是能够带来很大的成就感,有时候感觉在做两份工作。:) 帮助别人的意义就在于别人碰到的坑,你可能也会碰...

2714
来自专栏刘望舒

Android Apk安装过程解析

静默安装 apk安装流程简析 installd进程意义 最近工作上遇到静默安装相关的内容,顺便学习一下apk安装的知识

766
来自专栏xiaoheike

Eclipse下maven使用嵌入式(Embedded)Neo4j创建Hello World项目

代码可以在github上看到:https://github.com/neo4j/neo4j/blob/2.3.3/community/embedded-exam...

582
来自专栏腾讯云容器服务团队的专栏

Deployment vs ReplicationController in Kubernetes

此文主要选择了两个最常用的 controller : Deployment 和 ReplicationController ,从各自功能,优缺点方面进行对比,...

2.2K0
来自专栏智能大石头

开发板通用刷机教程

    这是STM通用的刷固件方法,不仅仅适用于.Net Micro Framework,支持我们当前所有板子,包括阿波罗、探索者和雅典娜三个系列。     ...

2386
来自专栏青青天空树

nodejs构建多房间简易聊天室

  本服务器需要提供两个功能:http服务和websocket服务,由于node的事件驱动机制,可将两种服务搭建在同一个端口下。

291
来自专栏Albert陈凯

常见编程语言对REPL支持情况小结

最近跟一个朋友聊起编程语言的一些特性,他有个言论让我略有所思:“不能REPL的都是渣”。当然这个观点有点偏激,但我们可以探究一下,我们常用的编程语言里面,哪些支...

2974
来自专栏IT派

Pip10已正式发布

我代表Python包管理局(Python Packaging Authority),很高兴在此宣布:pip10刚刚正式发布了。这个版本是几个月以来社区工作的结晶...

740

扫描关注云+社区