还在写死Access Key?IRSA可以用起来了

728 阅读3分钟

 背景

一般而言,在使用云厂商的SDK开发时我们都会用到一对secret key作为credentials,在代码里或者配置中心写死secret key是很常见的做法,但是这样做法却存在泄露的隐患:总有一些疏忽大意的使用人员有意无意的将包含secretKey的代码上传到公网上,一旦secretKey落入到别有用心的人手中,就只能"任人宰割"了。

因此,云厂商提供了一种更加安全和优雅的做法:角色绑定。角色跟权限绑定,机器再跟角色绑定,那么运行在EC2上的应用程序就能自动的继承角色拥有的权限来调用API,而无需在代码中写死secretKey

其背后的原理是: 当角色与ec2绑定后,ec2的metadata中会生成临时凭证,默认情况下sdk会自动从ec2的metadata中拿取临时凭证访问相应的资源

EC2 metadata,即EC2元数据,涵盖了ec2自身所有属性,当然也包括与之绑定的角色和临时凭证。

至于SDK是如何实现这样的“骚操作",我们可以看下文档,以Python SDK boto3为例:

image.png 大意是boto3为了查找credetials,会遍历搜索路径,而Instance metadata在搜索路径的末尾,也就是说,boto3的兜底方案是从metadata中拿取凭证

所以,你会发现,在角色与机器绑定后,代码中即使不用指定secret key,也能成功的调用AWS API。

然而,在k8s中,这种角色与机器绑定的做法却会产生"权限溢出"问题: 一台机器往往会运行多个pod,那么意味着一旦你将worker节点与角色绑定,那么所有运行在上面的pod拿到的都是同样的角色和权限,显然这和预期是不相符的。为此,AWS推出了IRSA

什么是IRSA

IAM Roles for Service Accounts 简称IRSA,就是利用OpenID Connect (OIDC)将Kubernetes 的 service account和 IAM 角色绑定,实现pod级别的IAM角色,简而言之,就是将AWS的IAM角色绑定到pod上,实现pod级别的角色

那么如何使用IRSA呢?只需四步:

1.安装OIDC Provider

在EKS上可以很方便的通过命令安装,要求Kubernetes版本>= 1.16

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

安装成功后,我们可以在EKS的控制台上确认 image.png 同时,IAM也与OIDC Provider建立了信任

image.png

2.创建角色并与权限绑定

在AWS IAM控制台上创建角色,并赋予相应的权限,需要注意的是可信实体类型需要选择OIDC Provider

image.png

3.在k8s中创建Service Account

service account的注解必须指明AWS角色的ARN地址

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::88888888:role/nginx-role
  labels:
    app: nginx
  name: nginx-sa

或者我们可以走捷径一步到位,直接用eksctl一行命令创建第2、3步

eksctl create iamserviceaccount --cluster=<clusterName> --name=<serviceAccountName> --role-name role-name --attach-policy-arn=policy-arn

iamserviceaccount 会同时创建Serivce Account 和 IAM Role

4.service account和pod绑定

apiVersion: v1 
kind: Pod 
metadata: 
  name: nginx
  spec: 
    serviceAccountName: nginx-sa
    containers: 
      - name: nginx
        image: nginx:latest 
     

在一番操作之后,我们就成功的启用了IRSA,实现了pod基本的角色

原理

那么IRSA又是怎么实现的呢?

整体流程如下: image.png

  1. 首先,当我们创建Service Account并与pod绑定后,k8会做两件事情:
  • 自动创建与Service Account相对应的Service Account token,Service Account token 以Json web token的形式存在,并且是通过EKS预先安装的adminission webhook amazon-eks-pod-identity-webhook签发的,签发的issuer正是OICD Provider。与此同时,这个Service Account token 会以volume mount的方式被挂载到pod中

我们可以看下pod挂载的Service Account Token:

kubectl describe pod nginx
...
Mounts:
...
/var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
...
Volumes:

aws-iam-token:

Type: Projected (a volume that contains injected data from multiple sources)

TokenExpirationSeconds: 86400
...

  • EKS中的amazon-eks-pod-identity-webhook会自动为pod注入两个环境变量

    AWS_ROLE_ARN: role arn地址 从service account的annotations获取到

    AWS_WEB_IDENTITY_TOKEN_FILE: service account token 挂载路径

 $ kubectl describe pod nginx
...
Environment:
...
AWS_ROLE_ARN: arn:aws:iam::88888888:role/nginx-role

AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
  1. 其次,AWS SDK会自动的通过环境变量获取role arn和 service account token;

    文章开头提到boto3获取credential的搜索路径包含Assume Role Provider,而boto3恰好在这里做了伏笔,由一个专门的类AssumeRoleWithWebIdentityProvider,去拿取环境变量

...
class AssumeRoleWithWebIdentityProvider(CredentialProvider):
    METHOD = 'assume-role-with-web-identity'
    CANONICAL_NAME = None
    _CONFIG_TO_ENV_VAR = {
        'web_identity_token_file': 'AWS_WEB_IDENTITY_TOKEN_FILE',
        'role_session_name': 'AWS_ROLE_SESSION_NAME',
        'role_arn': 'AWS_ROLE_ARN',
    }
 ...

3.紧接着,SDK向AWS STS service发起请求,请求携带着service account的JWT token和Role Arn。由于IAM已经与OIDC Provider已经建立了信任关系,因此,STS会将service account token交给OIDC Provider做验证,验证通过之后,STS就会生成临时凭证颁发给Pod,Pod就获得了相应权限去调用AWS API

image.png