+86 13541016684Mon. - Fri. 10:00-22:00

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

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

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

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

如下图:

1105000

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

参考文档