背景
一般而言,在使用云厂商的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为例:
大意是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的控制台上确认
同时,IAM也与OIDC Provider建立了信任
2.创建角色并与权限绑定
在AWS IAM控制台上创建角色,并赋予相应的权限,需要注意的是可信实体类型需要选择OIDC Provider
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又是怎么实现的呢?
整体流程如下:
- 首先,当我们创建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
-
其次,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