亚马逊AWS官方博客

介绍服务账户的精细 IAM 角色

在 AWS,我们把客户需求摆在首位。在 Amazon EKS 访问控制的背景下,您在我们的公共容器路线图第 23 期中提出了 EKS 中的精细 IAM 角色需求。为解决此需求,社区提出了很多开源解决方案,如 kube2iamkiamZalando 的 IAM 控制器 – 它们是伟大的开发,可使每个人都能更好的理解要求以及不同方法的限制。

现在,是时候开发一款灵活且易于使用的集成式段对端解决方案了。我们的主要目标是提供 pod 级而非节点级的精细角色。而且,我们提出的解决方案也是开源的,在使用 eksctl 预置集群时,您可以将它与 Amazon EKS 结合使用(我们需要在此处进行设置),或者您可以将它与其他 Kubernetes DIY 方法结合使用,如常见的 kops 设置。

访问控制:IAM 和 RBAC

在 AWS 上的 Kubernetes 中,运行着两种互补的访问控制体制。AWS Identity and Access Management (IAM) 可使您将权限分配至 AWS 服务:例如,应用程序可以访问 S3 存储桶。在 Kubernetes 的背景下,定义 Kubernetes 资源权限的补充系统是 Kubernetes 基于角色的访问控制 (RBAC)。完整的端对端示例可能显示如下(我们在前一篇博文使用 Fluent Bit 实现集中式容器日志记录中对此进行了讨论,在该博文中,我们介绍了 Fluent Bit 输出插件):

 

注意 如果您想复习一下知识,请查看我们为此目的放在一起的 IAM 和 RBAC 术语资源页面。

通过 pod-log-reader 角色中的 Kubernetes RBAC 设置,Fluent Bit 插件具有读取 NGINX pod 日志的权限。由于它运行在具有 AWS IAM 角色 eksctl-fluent-bit-demo-nodegroup-ng-2fb6f1a-NodeInstanceRole-P6QXJ5EYS6 的 EC2 实例上,且附加了内联策略,它还可以将日志条目写入 Kinesis Data Firehose 传输系统中。

如上图所示,现在的问题在于,Kubernetes 节点上运行的所有 pod 均共享相同的权限集 – 上述设置违背了最小特权原则并为攻击者提供了远大于其所需的攻击面。

我们能够做到更好? 是的,可以。社区开发了 kiamkube2iam 等工具来解决此问题。然而我们采用的方法服务账户的 IAM 角色 (IRSA) 有很大不同:我们使 pod 成为 IAM 中的一等公民。我们通过对 AWS identity API 进行更改来识别 Kubernetes pod,而不是拦截至 EC2 metadata API 的请求来执行对 STS API 的调用以检索临时凭证。通过结合 OpenID Connect (OIDC) 身份提供者和 Kubernetes 服务账户注释,现在,您可以使用 pod 级 IAM 角色。

进一步深入了解我们的解决方案:OIDC 联合访问 可使您通过安全令牌服务 (STS) 承担 IAM 角色,从而启用 OIDC 提供者的身份验证、接收 JSON Web 令牌 (JWT),然后反过来将该令牌用于承担 IAM 角色。另一方面,Kubernetes 可以签发所谓的预期服务账户令牌,它们恰好是对 pod 有效的 OIDC JWT。我们的设置为每个 pod 配备了加密签署的令牌,该令牌可以通过 STS 针对您选择的 OIDC 提供者进行验证,从而确定 pod 的身份。此外,我们还通过调用 sts:AssumeRoleWithWebIdentity 的新凭证提供者更新了 AWS SDK,从而将 Kubernetes 签发的 OIDC 令牌与 AWS 角色凭证交换。

由此产生的解决方案现在在 EKS 可用,在 EKS,我们可以管理控制平面并运行 Webhook 来负责注入必要的环境变量和预计卷。同时,该解决方案还在 AWS 上的 DIY Kubernetes 设置中可用;有关该选项的更多信息如下所示。

要从新的 IRSA 功能中获益,要执行以下较高级别的必要步骤:

  1. 在启用 eksctl 和 OIDC 提供者设置的情况下创建集群。
  2. 创建一个 IAM 角色,定义至目标 AWS 服务(例如 S3)的访问权,并使用上述 IAM 角色注释服务账户。
  3. 最后,使用上一步中创建的服务账户配置您的 pod 并承担 IAM 角色。

现在,我们来仔细了解这些步骤在 EKS 背景中看起来如何。在这里,我们已处理了所有艰巨的任务,比如启用 IRSA 或将必要的令牌注入 pod 中。

使用 Amazon EKS 和 eksctl 进行设置

所以,如何在 EKS 中使用服务账户的 IAM 角色 (IRSA)? 我们试着尽量简化它,这样您可以一直遵照这个通用演练。再往下,我们使用写入到 S3 的应用程序提供具体的端到端演练。

1.集群和 OIDC ID 提供者创建

首先,使用下面的命令创建新的 v1.13 EKS 集群:

$ eksctl create cluster irptest --approve
[ℹ]  using region us-west-2
...

现在,我们来在 AWS 中设置 OIDC ID 提供者 (IdP):

注:确保使用 eksctl >= 0.5.0 的版本

$ eksctl utils associate-iam-oidc-provider \
               --name irptest \
               --approve
[ℹ]  using region us-west-2
[ℹ]  will create IAM Open ID Connect provider for cluster "irptest" in "us-west-2"
[✔]  created IAM Open ID Connect provider for cluster "irptest" in "us-west-2"

如果您使用 eksctl,您已完成步骤,不需要进一步的步骤。您可以直接进行步骤 2。

或者,若您使用 CloudFormation 进行 EKS 集群预置,您必须自己创建 OIDC IdP。有些系统(如 Terraform)可对此提供一流的支持,否则,您必须手动创建如下 IdP:

首先,通过执行以下步骤获得集群的身份签发者 URL:

$ ISSUER_URL=$(aws eks describe-cluster \
                       --name irptest \
                       --query cluster.identity.oidc.issuer \
                       --output text)

我们在名为 ISSUER_URL 的环境变量中捕获上述 URL,因为在下一个步骤中,我们需要它。现在,创建 OIDC 提供者(我们的 (AWS) OIDC ID 提供者稍后显示),不过,您也可以使用自己的,如下所示:

$ aws iam create-open-id-connect-provider \
          --url $ISSUER_URL \
          --thumbprint-list $ROOT_CA_FINGERPRINT \
          --client-id-list sts.amazonaws.com

如何获取 ROOT_CA_FINGERPRINT 取决于 OIDC 提供者,对于 AWS,请参阅文档中的获取 OpenID Connect 身份提供者的根 CA 指纹

2.Kubernetes 符合账户和 IAM 角色设置

接下来,我们来创建 Kubernetes 服务账户并设置定义至目标服务(如 S3 或 DynamoDB)的访问权的 IAM 角色。为此,我们还需要实施 IAM 信任策略,使指定的 Kubernetes 服务账户承担 IAM 角色。以下命令可以方便地一次完成所有步骤:

$ eksctl create iamserviceaccount \
                --name my-serviceaccount \
                --namespace default \
                --cluster irptest \
                --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
                --approve
[ℹ]  1 task: { 2 sequential sub-tasks: { create addon stack "eksctl-irptest-addon-iamsa-default-my-serviceaccount", create ServiceAccount:default/my-serviceaccount } }
[ℹ]  deploying stack "eksctl-irptest-addon-iamsa-default-my-serviceaccount"
[✔]  创建所有角色和服务账户

功能揭秘,上述命令可执行两项操作:

    1. 它可以创建类似于 eksctl-irptest-addon-iamsa-default-my-serviceaccount-Role1-U1Y90I1RCZWB 形式的 IAM 角色,并将指定策略附加到该角色中,就我们的案例而言为 arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess.
    2. 它可以创建 Kubernetes 服务账户,在此处为 my-serviceaccount,并且可用所述的 IAM 角色注释服务账户。

以下 CLI 命令顺序等效于 eksctl create iamserviceaccount 为您执行的步骤:

# 步骤 1:创建 IAM 角色并附加目标策略:
$ ISSUER_HOSTPATH=$(echo $ISSUER_URL | cut -f 3- -d'/')
$ ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
$ PROVIDER_ARN="arn:aws:iam::$ACCOUNT_ID:oidc-provider/$ISSUER_HOSTPATH"
$ cat > irp-trust-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "$PROVIDER_ARN"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${ISSUER_HOSTPATH}:sub": "system:serviceaccount:default:my-serviceaccount"
        }
      }
    }
  ]
}
EOF
$ ROLE_NAME=s3-reader
$ aws iam create-role \
          --role-name $ROLE_NAME 
          --assume-role-policy-document file://irp-trust-policy.json
$ aws iam update-assume-role-policy \
          --role-name $ROLE_NAME \
          --policy-document file://irp-trust-policy.json
$ aws iam attach-role-policy \
          --role-name $ROLE_NAME \
          --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
$ S3_ROLE_ARN=$(aws iam get-role \
                        --role-name $ROLE_NAME \
                        --query Role.Arn --output text)

# 步骤 2:创建 Kubernetes 服务账户并用 IAM 角色注释该服务:
$ kubectl create sa my-serviceaccount
$ kubectl annotate sa my-serviceaccount eks.amazonaws.com/role-arn=$S3_ROLE_ARN

现在,我们已讨论了身份方面的内容并且已设置服务账户和 IAM 角色,接下来可以在 pod 上下文中继续使用此设置。

3.Pod 设置

请记住,服务账户是您的应用程序在 Kubernetes API 服务器上的身份,且托管您的应用程序的 pod 使用上述服务账户。

在上一步中,我们创建了称为 my-serviceaccount 的服务账户,接下来我们在 pod 规范中使用该账户。服务账户应显示如下(为实现可读性而编辑):

$ kubectl get sa my-serviceaccount -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-irptest-addon-iamsa-default-my-serviceaccount-Role1-UCGG6NDYZ3UE
  name: my-serviceaccount
  namespace: default
secrets:
- name: my-serviceaccount-token-m5msn

通过使用部署清单中的 serviceAccountName: my-serviceaccount,现在我们可以使该账户监督的 pod 使用我们在上面定义的服务账户。查看部署时,您应该会发现如下内容(为实现可读性而编辑):

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: myapp
  name: myapp
spec:
  ...
  template:
    ...
    spec:
      serviceAccountName: my-serviceaccount
      containers:
      - image: myapp:1.2
        name: myapp
        ...

现在,我们终于可以使用 kubectl apply 来创建部署,且由此产生的 pod 应显示如下(再次为实现可读性而编辑):

apiVersion: apps/v1
kind: Pod
metadata:
  name: myapp
spec:
  serviceAccountName: my-serviceaccount
  containers:
  - name: myapp
    image: myapp:1.2
    env:
    - name: AWS_ROLE_ARN
      value: arn:aws:iam::123456789012:role/eksctl-irptest-addon-iamsa-default-my-serviceaccount-Role1-UCGG6NDYZ3UE
    - name: AWS_WEB_IDENTITY_TOKEN_FILE
      value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    volumeMounts:
    - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
        name: aws-iam-token
        readOnly: true
  volumes:
  - name: aws-iam-token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          audience: sts.amazonaws.com
          expirationSeconds: 86400
          path: token

从上面,您可以看到,我们在 EKS 中(通过 Webhook)运行的转变准入控制器自动注入环境变量 AWS_IAM_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE 以及 aws-iam-token 卷。您只需要注释服务账户 my-serviceaccount。而且,您还可以看到,STS 临时凭证的默认有效期为 86400 秒(即 24 小时)。

如果您不希望准入控制器修改您的 pod,您可以使用服务账户令牌预计位置的值和要承担的角色手动添加变量 AWS_WEB_IDENTITY_TOKEN_FILEAWS_ROLE_ARN。此外,您还需要使用服务账户预计令牌将 volumevolumeMounts 参数添加到 pod 中,请参见 Kubernetes 文档获得参考。

最后一个必要步骤是 pod 通过其服务账户承担 IAM 角色。其作用方式如下:用户通过 OIDC 联合使用安全令牌服务 (STS) 承担 IAM 角色,以此通过可用于承担 OIDC 提供者 IAM 角色的 OAuth2 流有效接收 JSON Web Token (JWT)。然后,我们在 Kubernetes 中使用服务账户的预计令牌,即有效的 OIDC JWT,为每个 pod 提供加密签署的令牌,可以通过 STS 针对 OIDC 提供者验证此令牌,以确定身份。AWS SDK 已通过调用 sts:AssumeRoleWithWebIdentity 的新凭证提供者进行了更新,从而将 Kubernetes 签发的 OIDC 令牌与 AWS 角色凭证交换。为了使此功能正常运行,您需要使用大于或等于下列值的 SDK 版本:

如果您还没有使用上述 SDK 版本之一,或者尚未准备好迁移,您可以使用以下配方使应用程序感知 IRSA(在 pod 中)。作为前提条件,您需要安装 AWS CLIjq,例如下面这样:

$ JQ=/usr/bin/jq && curl https://stedolan.github.io/jq/download/linux64/jq > $JQ && chmod +x $JQ
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python get-pip.py
$ pip install awscli --upgrade

现在,您可以手动调用 sts:AssumeRoleWithWebIdentity

$ aws sts assume-role-with-web-identity \
 --role-arn $AWS_ROLE_ARN \
 --role-session-name mh9test \
 --web-identity-token file://$AWS_WEB_IDENTITY_TOKEN_FILE \
 --duration-seconds 1000 > /tmp/irp-cred.txt
$ export AWS_ACCESS_KEY_ID="$(cat /tmp/irp-cred.txt | jq -r ".Credentials.AccessKeyId")"
$ export AWS_SECRET_ACCESS_KEY="$(cat /tmp/irp-cred.txt | jq -r ".Credentials.SecretAccessKey")"
$ export AWS_SESSION_TOKEN="$(cat /tmp/irp-cred.txt | jq -r ".Credentials.SessionToken")"
$ rm /tmp/irp-cred.txt

在上述情况下,临时 STS 凭证的有效期为 1000 秒,通过 --duration-seconds 进行进行指定,您需要自行刷新该凭证。另外,请注意,会话名称是任意确定的,每个会话均是无状态且独立的;也就是说:令牌包含所有的相关数据。

完成通用设置之后,我们来看看具体的端到端示例,以显示操作中的 IRSA。

使用演练示例

在此演练中,我们向您端到端的演示如何将 IRSA(服务账户的 IAM 角色)用于从 stdin 中获取输入并将数据写入 S3 存储桶的应用程序(按创建事件键入)。

为了使 S3 Echoer 演示应用程序在 EKS 中运行,我们必须在 nutshell 中设置启用了 IRSA 的集群,为应用程序运行所在的 pod 创建 S3 存储桶并启用 IRSA,然后才能启动写入到 S3 存储桶的 pod。

首先,我们将演示应用程序存储库克隆到本地目录中:

$ git clone https://github.com/mhausenblas/s3-echoer.git && cd s3-echoer

接下来,创建 EKS 集群并在该集群中启用 IRSA:

$ eksctl create cluster --approve

$ eksctl utils associate-iam-oidc-provider --name s3echotest --approve

现在,我们通过创建 IAM 角色并使用该角色注释 pod 将使用的服务账户来定义应用程序的必要权限:

$ eksctl create iamserviceaccount \
                --name s3-echoer \
                --cluster s3echotest \
                --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess 
                --approve

现在,我们已做好全部准备工作:现在我们创建要写入内容的目标存储桶并像 Kubernetes 作业一样一次性启动 S3 Echoer 应用程序:

$ TARGET_BUCKET=irp-test-2019

$ aws s3api create-bucket \
            --bucket $TARGET_BUCKET \
            --create-bucket-configuration LocationConstraint=$(aws configure get region) \
            --region $(aws configure get region)

$ sed -e "s/TARGET_BUCKET/${TARGET_BUCKET}/g" s3-echoer-job.yaml.template > s3-echoer-job.yaml

$ kubectl apply -f s3-echoer-job.yaml

确保您使用与此处所示值不同的 $TARGET_BUCKET 值,因为 S3 存储桶名称必须是全局唯一的。

最后,为了验证写入存储桶是否成功,请执行以下操作:

$ aws s3api list-objects \
            --bucket $TARGET_BUCKET \
            --query 'Contents[].{Key: Key, Size: Size}'
[
    {
        "Key": "s3echoer-1565024447",
        "Size": 27
    }
]

下面演示的是 AWS IAM 和 Kubernetes 的不同部分如何共同作用在 EKS 中实现 IRSA(虚线表示操作,实线表示属性或关系):

上图中有很多需要完成的操作,我们来逐步执行:

  1. 当您使用 kubectl apply -f s3-echoer-job.yaml 启动 S3 Echoer 应用程序时,YAML 清单会在配置了 Amazon EKS Pod Identity Webhook 的情况下提交至 API 服务器,该操作在转变准入步骤中调用。
  2. Kubernetes 作业使用服务账户 s3-echoer,通过 serviceAccountName 进行设置。
  3. 由于服务账户具有 eks.amazonaws.com/role-arn 注释,Webhook 会注入必要的环境变量(AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE)并在作业监督的 pod 中设置 aws-iam-token 预计卷。
  4. 当 S3 Echoer 应用程序调用 S3 以尝试将数据写入存储桶时,我们在此使用的启用了 IRSA 的 Go SDK 将执行 sts:assume-role-with-web-identity 调用,以承担附加了 arn:aws:iam::aws:policy/AmazonS3FullAccess 托管策略的 IAM 角色。它将接收完成 S3 写入操作要使用的临时凭证。

如果您希望自己探索访问控制空间以了解 IAM 角色、服务账户等是如何连接的,您可以使用 rbIAM,它是我们专为以统一方式探索 IAM/RBAC 空间而编写的工具。例如,对于 S3 Echoer 演示,操作中的 rbIAM 的摘录如下:

就这么简单! 使用 S3 Echoer 应用程序,我们演示了如何在 EKS 中使用 IRSA,并显示了 IAM 和 Kubernetes 中的不同实体如何共同实现 IRSA。不要忘记使用 kubectl delete job/s3-echoer 进行清理。

开源即是双赢:在 AWS 上与 DIY Kubernetes 一起使用

现在您已了解如何在 EKS 中使用 IRSA,您可能想知道您是否能将它用于 AWS 上的 DIY Kubernetes,例如,是否能使用 kops 管理您的 Kubernetes 集群。我们已对我们的解决方案进行开源,因此,除了使用 EKS 的托管解决方案之外,您还可以在自己的设置中使用它:请查看 aws/amazon-eks-pod-identity-webhook,即 API 服务器在转变准入期间调用的 Amazon EKS Pod Identity Webhook。

从 GitHub 存储库中,您可以了解如何在您自己的环境中设置和配置它:

要开始从您自己的 Kubernetes 设置中获益于 IRSA,请遵照 Amazon EKS Pod Identity Webhook GitHub 存储库中的说明设置 Webhook 并通过问题告知我们进展。

后续步骤

考虑到需求并基于我们已对必要组件进行开源的事实,我们很高兴能与您分享此解决方案,让您在自己的集群上使用它。我们将继续改进 IRSA,解决社区提出的常见问题,包括(但不限于)支持跨账户角色、支持多个配置文件及使用令牌与其他系统(即非 AWS 服务)通信,举例来说,当您希望访问在 EKS 中运行的 Jenkins 或 Vault 时。

如果有些功能未按您的预期运行,请务必告诉我们,并且在此留下反馈、评论,或在 AWS Containers Roadmap on GitHub 上新建问题。