亚马逊AWS官方博客

EKS认证与授权实践

安全始终是亚马逊云科技的头号工作,也是Aamzon EKS这项托管Kubernetes服务的首要原则。但是很多用户第一次接触EKS时,对EKS的安全体系不了解;另外由于很多workshop材料是基于命令行方式,对于习惯使用控制台的用户来说有一定难度。本文档将针对初学EKS的用户,围绕EKS安全领域最基础的认证与授权问题,指导用户使用控制台进行集群的创建、管理以及维护。

围绕Amazon EKS的安全最佳实践通常涉及到以下几个领域:

在进行系统设计时,通常需要考虑到其中的安全隐患,以及可能对安全态势造成影响的各类实践。例如,您需要控制谁有权对一组资源执行操作。另外,您还需要建立起快速识别安全事件、保护系统与服务免受未授权访问影响,以及通过数据保护维持数据机密性与完整性的能力。此外,建立起一套定义明确且经过预先演练的安全事件应对流程,同样能够极大改善系统的安全状况。这些工具与技术非常重要,对预防财务损失及合规遵从等关键目标有很好的帮助。亚马逊云科技提供丰富的安全服务选项,帮助各类组织实现其安全性与合规性目标。这些服务来自于拥有完善安全意识的客户的经验,具备广泛的适用范围。由此建立起的高安全性基础,将保证客户在安全保护中各类“毫无区别的繁重任务”中耗费更少时间,转而将更多精力投入到实现核心业务目标方面。

由此可见,安全是一项复杂的系统工程。作为安全实践的起始点,我们将围绕Amazon EKS的认证和授权,通过创建一个名为ekssec01的 IAM 用户来验证Kubernetes Role-based Access Control(RBAC),使得该用户经过身份验证可以访问 EKS 集群,但仅被授权(通过 RBAC)列出、获取和监视 ‘ rbac-test’ 命名空间。为此,我们将创建一个 IAM 用户,将该用户映射到 Kubernetes 角色,然后在该用户的上下文中执行 Kubernetes 操作。

一 环境准备

1.1 前置条件:

  • 网络:已经创建了带公有和私有子网的VPC,确保公有子网和私有子网配置都正确,在我的实验环境中事先创建好了vpc:eksgwm
  • 模拟客户端(跳板机):在公有子网创建一台ec2做eks的访问客户端。并安装好客户端工具

安装Kubectl

sudo curl --silent --location -o /usr/local/bin/kubectl \

   https://amazon-eks.s3.us-west-2.amazonaws.com/1.19.6/2021-01-05/bin/linux/amd64/kubectl

sudo chmod +x /usr/local/bin/kubectl

安装awscli

sudo pip install --upgrade awscli && hash -r

安装jq, envsubst

sudo yum -y install jq gettext bash-completion moreutils

安装yq

echo 'yq() {

  docker run --rm -i -v "${PWD}":/workdir mikefarah/yq "$@"

}' | tee -a ~/.bashrc && source ~/.bashrc

Enable kubectl bash_completion

kubectl completion bash >>  ~/.bash_completion

. /etc/profile.d/bash_completion.sh

. ~/.bash_completion

设置AWS Load Balancer Controller 环境变量

echo 'export LBC_VERSION="v2.2.0"' >>  ~/.bash_profile

.  ~/.bash_profile

安装eksctl

curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp

sudo mv -v /tmp/eksctl /usr/local/bin

检查eksctl版本

eksctl version

Enable eksctl bash-completion

eksctl completion bash >> ~/.bash_completion

. /etc/profile.d/bash_completion.sh

. ~/.bash_completion

1.2 用户A创建集群:

用户A登陆亚马逊云科技控制台,进入EKS服务,创建EKS集群,如下图,


用户A访问集群:

用户A的身份标示可以通过AKSK,也可以通过Role 的方式,为了直接理解这个过程,我们用AKSK方式,在实际使用中,不要使用该方式,而应该使用role的方式。保存AKSK到一个隐秘的地方,后面切换用户时,会用到。

设置好AKSK后,尝试访问集群,通过eks客户端将kubeconfig文件下到本地客户端

aws eks update-kubeconfig \

     --region <regjion code> \

     --name <cluster name>

测试配置:

kubectl get svc

1.3 创建托管节点组

通过集群>配置>计算>添加节点组



二 身份与访问管理

身份与访问管理(Identity and Access Management ,简称 IAM) 是一项 亚马逊云科技的一个服务,用于实现两项基本功能: 身份验证与授权。身份验证的实质在于考查对方宣称的身份是否属实,而授权则用于控制可对亚马逊云科技资源执行的具体操作。在亚马逊云科技当中,资源可以是另一项服务(例如 EC2),也可以是某种实体(例如 IAM 用户或角色)。这些用于管理获准资源执行操作的规则,即为 IAM 策略。

在K8S集群中,有自己独立的RBAC,其授权访问关系大致如下图,

RBAC 的核心逻辑组件是:

  • 实体(entity)

组、用户或服务帐户(代表想要执行某些操作(动作)并需要权限的应用程序的身份)。

  • 资源(resource)

实体想要使用特定操作访问的 pod、服务或机密。

  • 角色(role)

用于定义实体可以对各种资源采取的操作的规则。

  • 角色绑定(role binding)

这将角色附加(绑定)到实体,说明规则集定义了附加实体在指定资源上允许的操作。

有两种类型的角色(Role、ClusterRole)和各自的绑定(RoleBinding、ClusterRoleBinding)。这些区分命名空间或集群范围内的授权。

  • 命名空间(namespace)

命名空间是创建安全边界的绝佳方式,正如“命名空间”名称所暗示的那样,它们还为对象名称提供了唯一的范围。它们旨在用于多租户环境,以在同一物理集群上创建虚拟 Kubernetes 集群。

在EKS实际访问控制场景中,主要分为两类场景,一类是EKS集群外实体(比如用户)访问集群,一类是k8s集群内的资源(比如pod)访问亚马逊云服务。我们分别就两种情况进行操作演示和讲解。

2.1 EKS外实体访问控制集群

这里主要关注如何使用亚马逊云IAM实体访问EKS集群,Amazon EKS 使用 IAM 向Kubernetes 集群提供身份验证,但实际的认证和授权,还是依赖K8S自身的RBAC。因此当客户端发起请求时分为两个步骤,认证和授权。

  1. 安装16.156版本以上的AWS Cli
  2. 查找本地环境~/.kube目录下的config文件(kubeconfig)
  3. config文件中包含api server的endpoint与CA以及通过何种方式获得token。该实验使用的aws eks get-token
  4. IAM的sts服务根据客户端当前用户或角色返回临时token
  5. 客户端用临时token发出请求到EKS的API server
  6. API通过authenticator server通过webhook方式向IAM获取实体确认
  7. IAM返回用户/角色的ARN
  8. 通过authenticator server在事先定义的configmap中做用户认证
  9. 认证通过后根据事先定义的Role和Role Binding做授权

2.2 IAM用户授权访问eks

2.2.1 安装测试pod

我们创建一个用户ekssec01,提供对在 rbac-test 命名空间中运行的 pod 的有限访问。为此,让我们首先创建 rbac-test 命名空间,然后将 nginx 安装到其中:

kubectl create namespace rbac-test

kubectl create deploy nginx --image=nginx -n rbac-test

要验证测试 Pod 是否已正确安装,请运行:

kubectl get all -n rbac-test

输出应类似于:

2.2.2创建一个用户


登陆console到IAM创建一个新用户ekssec01,并获取其AKSK

2.2.3 绑定IAM用户到K8s

接下来,我们将定义一个名为 ekssecuser01 用户的 k8s 用户,并映射到其 IAM 用户对应方。运行以下文件以获取现有的配置文件,并保存到一个名为 aws-auth.yaml 的文件中:

kubectl get configmap -n kube-system aws-auth -o yaml | grep -v "creationTimestamp\|resourceVersion\|selfLink\|uid" | sed '/^  annotations:/,+2 d' > aws-auth.yaml

接下来将 rbac 用户映射附加到现有配置映射

cat << EoF >> aws-auth.yaml

data:

  mapUsers: |

    - userarn: arn:aws:iam::${ACCOUNT_ID}:user/ekssec01

      username: ekssecuser01

EoF

创建文件时,某些值可能会动态填充。要验证所有填充和创建正确,请运行以下内容:

cat aws-auth.yaml

输出应反映角色和用户群填充,类似于:

将AKSK通过aws configure配置到在1.1中创建的ec2客户端。配置前可以通过aws sts get-caller-identity命令查看当前用户,配置后再输入该命令确认用户已经更改。用户更改后执行如下命令

kubectl get pods -n rbac-test

上面报错说明该用户还没有认证

接下来,我们通过aws configure切换回有权限的用户A,并应用ConfigMap将此映射应用于系统:

kubectl apply -f aws-auth.yaml

2.2.4 测试新用户

到目前为止,作为集群操作员,您一直以管理员用户的身份访问集群。现在,让我们看看当我们将集群访问为新创建的 ekssecu01 用户时会发生什么。

请运行以下命令:aws sts get-caller-identity,我们现在以 ekssec01用户的身份调用 API:

现在,我们根据 ekssec01 用户的上下文进行调用,请让我们快速发起request,获取所有pod:

kubectl get pods -n rbac-test

您应该得到类似于:

说明该用户已经认证但未授权

2.2.5 创建角色绑定

如前所述,我们有我们的新用户 rbac 用户,但它尚未约束任何角色。为此,我们需要切换回默认管理员用户A。

要再次验证我们是管理员用户,并且不再是 ekssec01 用户,请发布以下命令:

aws sts get-caller-identity,输出应显示用户不再是 ekssec用户

现在,我们再次成为管理员用户A,我们将创建一个名为 pod-reader的角色,该角色提供list, get, watch access和部署的访问权限,但仅用于 rbac 测试命名空间。运行以下创建此角色:

cat << EoF > rbacuser-role.yaml

kind: Role

apiVersion: rbac.authorization.k8s.io/v1

metadata:

  namespace: rbac-test

  name: pod-reader

rules:

- apiGroups: [""] # "" indicates the core API group

  resources: ["pods"]

  verbs: ["list","get","watch"]

- apiGroups: ["extensions","apps"]

  resources: ["deployments"]

  verbs: ["get", "list", "watch"]

EoF

我们有用户,我们有角色,现在我们把他们与角色绑定资源捆绑在一起。运行以下创建此角色绑定

cat << EoF > rbacuser-role-binding.yaml

kind: RoleBinding

apiVersion: rbac.authorization.k8s.io/v1

metadata:

  name: read-pods

  namespace: rbac-test

subjects:

- kind: User

  name: ekssecuser01

  apiGroup: rbac.authorization.k8s.io

roleRef:

  kind: Role

  name: pod-reader

  apiGroup: rbac.authorization.k8s.io

EoF

接下来,我们应用我们创建的角色和角色绑定:

kubectl apply -f rbacuser-role.yaml

kubectl apply -f rbacuser-role-binding.yaml

2.2.6 验证角色和绑定

现在,用户、角色和 RoleBinding 已定义,请允许切换回 ekssec01 用户并进行测试。

aws sts get-caller-identity

您应该看到反映您已登录为 ekssec01 用户的输出。

kubectl get pods -n rbac-test

输出类似于:

再次尝试运行相同的命令,但在 rbac 测试命名空间之外:

kubectl get pods -n kube-system

您应该遇到类似于:

因为您必须扮演的角色不会让您访问 rbac 测试以外的任何命名空间。

三 Pod安全

对于某些运行在 Kubernetes 集群内的应用程序来说,必须具备调用 Kubernetes API 的权限才能保持正常运作。 例如,ALB(应用程序负载均衡器)入口控制器需要能够列出当前服务的各端点,并调用 AWS API 以实现对 ALB 的设定与配置。我们探讨如何在 Pod 权限分配方面遵循最佳实践。

3.1 服务账户

服务账户是一种特殊的对象类型,允许您将 Kubernetes RBAC 角色分配给 Pod。集群内的各个命名空间(Namespace)会自动创建一个与之对应的默认服务账户。当我们将 Pod 部署至命名空间之内,且未引用特定服务账户时,该命名空间会将该默认服务账户分配给当前 Pod 与 Secret。这里的 Secret,即该服务账户的 JWT 令牌,将以卷的形式被安装在 Pod 上的 /var/run/secrets/kubernetes.io/serviceaccount 位置。

当 Pod 中运行的应用程序调用 Kubernetes API 时,我们需要为 Pod 分配一个服务账户,由该账户明确授权 Pod 调用这些 API 的权限。与用户访问规则相似,绑定至服务账户的 Role 或 ClusterRole 也应明确指定该应用程序运行所需要的 API 资源及方法,除此之外再不设其他权限。要使用非默认服务账户,你需要将 Pod 中 的 spec.serviceAccountName 字段设置为需要使用的服务账户名称。

3.2 用于服务账号的IAM角色(IRSA)

IRSA 是一项功能,允许您将 IAM 角色分配给 Kubernetes 服务账户。其基本原理,是利用服务账户令卷投影(Service Account Token Volume Projection)这一Kubernetes 功能,保证引用 IAM 角色的服务账户Pod 在启动时调用 AWS IAM 的公共 OIDC 发现端点。该端点负责对 Kubernetes 发布的 OIDC Token进行密码签名,此Token将最终允许目标 Pod 调用与 AWS API 相关的 IAM 角色。

在调用 AWS API 时,AWS SDK 会调用 sts:AssumeRoleWithWebIdentity 并自动将 Kubernetes 发行的令牌交换为 AWS 角色凭证。作为 EKS 控制平面中组成部分运行的 Webhook 变种,将把 AWS Role ARN 与 Web Identity Token文件的路径以环境变量的形式注入 Pod。当然,您也可以手动提供这些值。

3.3 如何使用IRSA

在 Kubernetes 1.12 版中,添加了对新 ProjectedServiceAccountToken 功能的支持,这是一个 OIDC JSON Web 令牌,它也包含服务帐户身份,并支持可配置的受众。Amazon EKS 现在为每个集群托管一个公共 OIDC 发现终端节点,其中包含 ProjectedServiceAccountToken JWT的签名密钥,因此外部系统(如 IAM)可以验证和接受 Kubernetes 颁发的 OIDC Token。

OIDC 联合访问允许您通过安全令牌服务 (STS) 承担 IAM 角色,启用与 OIDC 提供商的身份验证,接收 JSON Web 令牌 (JWT),而后者又可用于承担 IAM 角色。另一方面,Kubernetes 可以发布所谓的projected service account tokens,这恰好是 Pod 的有效 OIDC JWT。我们的设置为每个 Pod 配备了一个加密签名的令牌,STS 可以针对您选择的 OIDC 提供商进行验证,以建立 Pod 的身份。

3.3.1 创建OIDC供应商

登陆控制台,并获得OpenID Connect 提供商 URL:

要为集群中的服务账户使用 IAM角色,您必须在 IAM控制台中创建 OIDC 身份提供商,

3.3.2 创建IRSA

eksctl create iamserviceaccount \

    --name iam-test \

    --namespace default \

    --cluster ekssecurity \

    --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \

    --approve \

    --override-existing-serviceaccounts

3.3.3 为服务账户指定 IAM 角色

在上一步中,我们创建了与集群中名为 iam-test 的服务账户关联的 IAM 角色,用以下命令确认该service account已经创建好

kubectl get sa iam-test

kubectl describe sa iam-test,查看创建的角色

3.3.4 部署Pod

现在我们已经完成了所有必要的配置,我们将使用新创建的 IAM 角色运行两个 kubernetes 作业:

  • job-s3.yaml: 这将输出命令 aws s3 ls 的结果(此作业应该会成功)。
  • job-ec2.yaml: 这将输出命令 aws ec2 describe-instances –region ${AWS_REGION} 的结果(此作业应该失败)

mkdir ~/irsa

cat <<EoF> ~/irsa/job-s3.yaml

apiVersion: batch/v1

kind: Job

metadata:

  name: eks-iam-test-s3

spec:

  template:

    metadata:

      labels:

        app: eks-iam-test-s3

    spec:

      serviceAccountName: iam-test

      containers:

      - name: eks-iam-test

        image: amazon/aws-cli:latest

        args: ["s3", "ls"]

      restartPolicy: Never

EoF

kubectl apply -f ~/irsa/job-s3.yaml

等作业执行完成

kubectl get job -l app=eks-iam-test-s3

检查作业日志

kubectl logs -l app=eks-iam-test-s3

我们再尝试执行一个作业去describe ec2实例

cat <<EoF> ~/irsa/job-ec2.yaml

apiVersion: batch/v1

kind: Job

metadata:

  name: eks-iam-test-ec2

spec:

  template:

    metadata:

      labels:

        app: eks-iam-test-ec2

    spec:

      serviceAccountName: iam-test

      containers:

      - name: eks-iam-test

        image: amazon/aws-cli:latest

        args: ["ec2", "describe-instances", "--region", "${AWS_REGION}"]

      restartPolicy: Never

  backoffLimit: 0

EoF

kubectl apply -f ~/irsa/job-ec2.yaml

检查作业执行状态,应该一直没有执行完,因为权限不够

kubectl get job -l app=eks-iam-test-ec2

总结

本文介绍了Amazon EKS的认证与授权过程,同时对IAM新建用户如何访问EKS以及EKS中的Pod如何授权访问亚马逊云服务中的资源进行了说明。对于刚接触EKS的用户,您可以通过控制台完成EKS的创建和资源管理,同时结合命令行方式管理EKS集群

本篇作者

柏燕峥

AWS 解决方案架构师。十余年企业软件架构和咨询经验,专注企业私有云、混合云、云管理平台、云安全、DevOps。著有大学教材中的大数据与人工智能技术丛书中的《云计算导论》