暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

浅聊Kubernetes的各种认证策略以及适用场景

int32bit 2019-12-17
541

1 Kubernetes认证背景

1.1 为什么Kubernetes没有用户以及用户组?

Kubernetes的RBAC模型授权对象(Subject)是用户(User)或者用户组(Group),即使ServiceAccount也会当作为一个虚拟User。

但是很令人疑惑的是通过Kubernetes找不到真正的用户以及用户组信息,甚至连对应的User Resource以及Group Resource都没有,所以有时候在写rolebingding的时候会觉得很奇怪,Subjects需要填 User
或者 Group
,可是Kubernetes却没有办法列出可信任的User列表以及Group列表。

换句话说Kubernetes并没有提供用户管理和身份认证功能,除了Service Account外,所有的用户信息都依赖外部的用户管理系统来存储,因此通过api-serever根本无法列出User和Group。

这其实挺符合UNIX设计哲学的,即Do One Thing and Do It Well。

Kubernetes只专注于做应用编排,其他的功能则提供接口集成,除了认证和授权,我们发现网络、存储也都如此。

这样做的好处也显而易见,用户账户信息与Kubernetes集群松耦合,便于集成企业已有的身份认证系统,如AD、LADP、Keycloak等。

1.2 关于kubeconfig文件

kubectl是通过读取kubeconfig获取集群信息的,默认路径为 $HOME/.kube/config
,可以通过 KUBECONFIG
环境变量指定多个config文件。

kubeconfig文件主要包含如下三个部分:

  • cluster: 存储api-server的CA根证书、api-server地址、集群名称等。

  • user: 真正配置用户认证时的凭证信息,使用不同的认证策略,包含不同的字段

  • context: 把cluster和user关联起来组成一个集群环境信息,声明通过哪个user连哪个cluster。

手动编辑config文件非常麻烦,kubectl config子命令提供了大部分的参数自动填充kubeconfig文件,分别对应set-cluster、set-credentials、set-context,相对应的有get-clusters、get-contexts以及delete-cluster、delete-context,目前没有对应credential get和delete操作,只能手动编辑kubeconfig文件。

通过use-context切换集群上下文。

1.3 关于Kubernetes认证策略

Kubernetes虽然没有直接实现普通用户的身份认证功能,但是很好的支持集成各种已有的身份认证策略,并且支持同时使用多种认证策略,只要有其中一种认证策略校验通过就算认证通过。

可以说Kubernetes的认证方式五花八门,这些认证方式都有哪些优缺点,适合什么样的场景,这就是本文需要研究的两个问题。

本文接下来主要研究Kubernetes提供的几种认证策略,结合自己在使用过程以及阅读了大量的文献基础上,总结不同策略的优缺点以及适用场景。

1.4 Role以及Rolebingding配置

不仅Kubernetes的身份认证是通过外部系统集成,Kubernetes的授权其实也支持各种插件,本文不会详细讨论关于Kubernetes的授权机制,仅为了验证身份认证,假定授权使用的是RBAC插件。

预先创建了role和rolebingding如下:

  • role:

  1. apiVersion: rbac.authorization.k8s.io/v1

  2. kind: Role

  3. metadata:

  4. name: int32bit-role

  5. namespace: default

  6. rules:

  7. - apiGroups:

  8. - ""

  9. resources:

  10. - pods

  11. - pods/exec

  12. verbs:

  13. - get

  14. - list

  15. - watch

  16. - create

  • rolebingding:

  1. apiVersion: rbac.authorization.k8s.io/v1

  2. kind: RoleBinding

  3. metadata:

  4. name: int32bit-rolebinding

  5. namespace: default

  6. roleRef:

  7. apiGroup: rbac.authorization.k8s.io

  8. kind: Role

  9. name: int32bit-role

  10. subjects:

  11. - apiGroup: rbac.authorization.k8s.io

  12. kind: Group

  13. name: int32bit

  14. - kind: ServiceAccount

  15. name: test-sa

2 静态密码认证

静态密码是最简单的认证方式,只需要在api-server启动时指定使用的密码本路径即可:

  1. kube-apiserver --advertise-address=192.168.193.172 \

  2. # ...省略其他参数... \

  3. --basic-auth-file=/etc/static_secret/passwd

其中密码本格式为csv:

  1. password,user,uid,"group1,group2,group3"

demo如下:

  1. NoMoreSecret,int32bit-1,1000,"int32bit"

此时定义了一个用户 int32bit-1
,静态密码为 NoMoreSecret
,所属Group为 intt32bit

此时可生成凭证config文件:

  1. kubectl config set-credentials int32bit-1 --username=int32bit-1 --password=NoMoreSecret

使用该config文件如下:

int32bit-1由于所属 int32it
group,因此可以读取pod列表,但是无法删除pod。

通过静态密码的唯一优势是简单,其缺点也是非常明显:

  • 静态密码是明文,非常不安全,还有可能被暴力破解。

  • 非常不灵活,增加或者删除用户,必须手动修改静态密码文件并重启所有的api-server服务。

这种方式在实际场景中很少被使用,不建议生产环境使用。

3 x509证书认证

x509认证是默认开启的认证方式,api-server启动时会指定ca证书以及ca私钥,只要是通过ca签发的客户端x509证书,则可认为是可信的客户端。

使用kubeadm部署后的cluster-admin默认就是通过x509证书认证的,node节点和API server通信也是通过证书认证的。

这里需要注意的是,在kubectl config中会有两个证书,分别为 certificate-authority
证书和 client-certificate
证书。 client-certificate
用于客户端认证,这显而易见。

但 certificate-authority
用于做什么呢?答案是为了避免与api server通信时遭受中间人攻击,api server默认使用了https协议,但我们使用kubeadm部署Kubernetes集群时默认使用的是自签证书,为了让客户端信任api server的根证书,需要配置server端的证书。当然直接添加到OS的可信任根证书列表中也是可以的。如果使用权威机构颁发的证书则不需要配置。

综上,kubectl客户端与Kubernetes api-server认证时采用的是双向认证,这里不限于x509认证模式,所有的认证模式都应该配置为双向认证,因此后面将介绍的Token认证模式同样需要server端的根证书。

在使用client-certificate客户端证书认证时,CN(commom Name)对应Kubernetes的User,O(organization)对应Kubernetes的Group。

签发客户端证书有两种方式,一种是基于CA根证书签发证书,另一个种是发起CSR(Certificate Signing Requests)请求。

3.1 使用CA根证书签发客户端证书

使用CA根证书需要CA的私钥,假设要创建一个int32bit用户,所属的组为int32bit,使用openssl签发证书:

  1. openssl genrsa -out int32bit.key 2048 # 生成私钥

  2. openssl req -new -key int32bit.key -out int32bit.csr -subj "/CN=int32bit/O=int32bit" # 发起CSR请求

  3. openssl x509 -req -in int32bit.csr \

  4. -CA $CA_LOCATION/ca.crt \

  5. -CAkey $CA_LOCATION/ca.key \

  6. -CAcreateserial -out int32bit.crt -days 365 # 基于CSR文件签发x509证书

其中 CA_LOCATION
为api server的CA证书路径,使用kubeadm部署一般为 /etc/kubernetes/pki/

最后生成config文件:

  1. kubectl config set-credentials int32bit \

  2. --client-certificate=int32bit.crt \

  3. --client-key=int32bit.key \

  4. --embed-certs=true \

  5. --kubeconfig="int32bit@int32bit-kubernetes.config.config"

注意使用 --embed-certs
参数,这样才会把证书内容填充到kubeconfig文件,否则仅填充证书路径。

3.2 通过CSR签发证书

前面通过CA签发证书需要有CA的私钥,其实Kubernetes可以直接发起CSR请求。

首先创建一个CSR请求,CN为 test-csr
,O为 int32bit
,即User为 test-csr
,Group为 int32bbit

  1. openssl req -new -newkey rsa:4096 \

  2. -nodes -keyout test-csr.key \

  3. -out test-csr.csr -subj "/CN=test-csr/O=int32bit"

声明一个CSR Resource:

  1. apiVersion: certificates.k8s.io/v1beta1

  2. kind: CertificateSigningRequest

  3. metadata:

  4. name: test-csr

  5. spec:

  6. groups:

  7. - int32bit

  8. request: ... # 这里填test-csr.csr内容并转化为base64

  9. usages:

  10. - client auth

创建该资源:

  1. # kubectl apply -f test-csr.yaml

  2. certificatesigningrequest.certificates.k8s.io/test-csr created

  3. # kubectl get csr

  4. NAME AGE REQUESTOR CONDITION

  5. test-csr 4s kubernetes-admin Pending

此时CSR的状态为 Pending
,通过 kubectl certificate approve
命令签发证书:

  1. # kubectl certificate approve test-csr

  2. certificatesigningrequest.certificates.k8s.io/test-csr approved

  3. # kubectl get csr

  4. NAME AGE REQUESTOR CONDITION

  5. test-csr 2m kubernetes-admin Approved,Issued

此时CSR显示已经完成签发,可以读取证书内容:

  1. # kubectl get csr test-csr -o jsonpath='{.status.certificate}' | base64 -d

  2. -----BEGIN CERTIFICATE-----

  3. MIIEDTCCAvWgAwIBAgIUB9dVsj34xnQ8m5KUQwpdblWapNcwDQYJKoZIhvcNAQEL

  4. ...

  5. yvfz8hcwrhQc6APpmZcBnil7iyzia3tnztQjoyaZ0cjC

  6. -----END CERTIFICATE-----

查看证书部分摘要信息:

  1. # kubectl get csr test-csr -o jsonpath='{.status.certificate}' \

  2. | base64 -d \

  3. | openssl x509 -noout \

  4. -subject -issuer

  5. subject=O = int32bit, CN = test-csr

  6. issuer=CN = kubernetes

配置kubeconfig使用证书认证的方式和前面的一样,这里不再赘述。

3.3 使用x509证书认证的问题

使用x509证书相对静态密码来说显然会更安全,只要证书不泄露,可以认为是无懈可击的。但是虽然颁发证书容易,目前却没有很好的方案注销证书。想想如果某个管理员离职,该如何回收他的权限和证书。有人说,证书轮转不就解决了吗?但这也意味着需要重新颁发其他所有证书,非常麻烦。

所以使用x509证书认证适用于Kubernetes内部组件之间认证,普通用户认证并不推荐通过证书的形式进行认证,参考Kubernetes – Don’t Use Certificates for Authentication。

4 Bearer Token认证

4.1 静态token认证

静态token认证和静态密码原理几乎完全一样,唯一不同的是静态token通过token-auth-file指定token文件,认证时头部格式为 Authorization:Bearer${Token}
,而静态密码通过basic-auth-file指定密码文件,认证头部为 Basicbase64encode(${username}:${password})
,本质都是一样的。

因此其优点和缺点也和静态密码完全一样,这里不再赘述。

4.2 Bootstrap Token认证

前面提到的静态token在运行时是固定不变的,并且不会在Kubernetes集群中存储,意味着除非修改静态文件并重启服务,token不会改变。

而bootstrap token则是由Kubernetes动态生成的,通过Secret形式存储,并且具有一定的生命周期,一旦过期就会失效。

为了简便我们使用kubeadm生成一个token:

  1. # kubeadm token list

  2. TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS

  3. bpjp71.6ckt2g3o3hso3gn4 23h 2019-12-15T11:58:13Z authentication,signing <none> system:bootstrappers:kubeadm:default-node-token

Token有两个部分组成,由 .
分割,前面部分为Token ID bpjp71
,后面部分为Token Secret 6ckt2g3o3hso3gn4
。Token默认TTL为一天,对应的group为 system:bootstrappers:kubeadm:default-node-token
,对应User为 system:bootstrap:${TokenID}

kubeadm创建一个Token会对应在Kubernetes的kube-system namespace创建一个secret,secret名为 bootstrap-token-${TOKEN_ID}
,这里为 bootstrap-token-bpjp71

  1. # kubectl get secret bootstrap-token-bpjp71 -n kube-system -o yaml --export

  2. apiVersion: v1

  3. data:

  4. auth-extra-groups: c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=

  5. expiration: MjAxOS0xMi0xNVQxMTo1ODoxM1o=

  6. token-id: YnBqcDcx

  7. token-secret: NmNrdDJnM28zaHNvM2duNA==

  8. usage-bootstrap-authentication: dHJ1ZQ==

  9. usage-bootstrap-signing: dHJ1ZQ==

  10. kind: Secret

  11. metadata:

  12. name: bootstrap-token-bpjp71

  13. type: bootstrap.kubernetes.io/token

此时可以通过如下命令生成config:

  1. kubectl config set-credentials bootstrap \

  2. --user bootstrap \

  3. --token bpjp71.6ckt2g3o3hso3gn4

为了验证boostrap token,我们把用户添加到 int32bit-role
中,注意对应的虚拟User名。

  1. # kubectl describe rolebindings int32bit-rolebinding

  2. Name: int32bit-rolebinding

  3. Labels: <none>

  4. Annotations: <none>

  5. Role:

  6. Kind: Role

  7. Name: int32bit-role

  8. Subjects:

  9. Kind Name Namespace

  10. ---- ---- ---------

  11. Group int32bit

  12. User system:bootstrap:bpjp71

这种token主要用于临时授权使用,比如kubeadm初始化集群时会生成一个bootstrap token,这个token具有创建certificatesigningrequests权限,从而新Node能够发起CSR请求,请求客户端证书。

4.3 service account token认证

service account是Kubernetes唯一由自己管理的账号实体,意味着service account可以通过Kubernetes创建,不过这里的service account并不是直接和User关联的,service account是namespace作用域,而User是全cluster唯一。service account会对应一个虚拟User,User名为 system:serviceaccount:${namespace}:${sa_name}
,比如在default namespace的test service account,则对应的虚拟User为 system:serviceaccount:default:test

和前面的bootstrap一样,service account也是使用Bearer Token认证的,不过和前面的Token不一样的是service account是基于JWT(JSON Web Token)认证机制,JWT原理和x509证书认证其实有点类似,都是通过CA根证书进行签名和校验,只是格式不一样而已,JWT由三个部分组成,每个部分由 .
分割,三个部分依次如下:

  • Header(头部): Token的元数据,如alg表示签名算法,typ表示令牌类型,一般为 JWT
    ,kid表示Token ID等。

  • Payload(负载): 实际存放的用户凭证数据,如iss表示签发人,sub签发对象,exp过期时间等。

  • Signature(签名):基于alg指定的算法生成的数字签名,为了避免被篡改和伪造。

为了便于HTTP传输,JWT Token在传递过程中会转成Base64URL编码,其中Base64URL相对我们常用的Base64编码不同的是 =
被省略、 +
替换成 -
, /
替换成 _
,这么做的原因是因为这些字符在URL里面有特殊含义,更多关于JWT的介绍可参考阮一峰的JSON Web Token 入门教程。

我写了如下脚本实现Kubernetes Service Account的Token解码:

  1. #!/bin/bash


  2. # base64url解码

  3. decode_base64_url() {

  4. LEN=$((${#1} % 4))

  5. RESULT="$1"

  6. if [ $LEN -eq 2 ]; then

  7. RESULT+='=='

  8. elif [ $LEN -eq 3 ]; then

  9. RESULT+='='

  10. fi

  11. echo "$RESULT" | tr '_-' '/+' | base64 -d

  12. }


  13. # 解码JWT

  14. decode_jwt()

  15. {

  16. JWT_RAW=$1

  17. for line in $(echo "$JWT_RAW" | awk -F '.' '{print $1,$2}'); do

  18. RESULT=$(decode_base64_url "$line")

  19. echo "$RESULT" | python -m json.tool

  20. done

  21. }


  22. # 获取k8s sa token

  23. get_k8s_sa_token()

  24. {

  25. NAME=$1

  26. TOKEN_NAME=$(kubectl get sa "$NAME" -o jsonpath='{.secrets[0].name}')

  27. kubectl get secret "${TOKEN_NAME}" -o jsonpath='{.data.token}' | base64 -d

  28. }


  29. main()

  30. {

  31. NAME=$1

  32. if [[ -z $NAME ]]; then

  33. echo "Usage: $0 <secret_name>"

  34. exit 1

  35. fi

  36. TOKEN=$(get_k8s_sa_token "$NAME")

  37. decode_jwt "$TOKEN"

  38. }


  39. main "$@"

从解码的数据可见,JWT Token的颁发机构为 kubernetes/serviceaccount
,颁发对象为SA对应的虚拟用户 system:serviceaccount:default:test
,除此之外还存储着其他的SA信息,如SA name、namespace、uuid等。这里需要注意的是我们发现JWT Token中没有exp字段,即意味着只要这个SA存在,这个Token就是永久有效的。

通过如下方式配置kubeconfig:

  1. TOKEN_NAME=$(kubectl get serviceaccounts ${SA_NAME} -o jsonpath={.secrets[0].name})

  2. TOKEN=$(kubectl get secret "${TOKEN_NAME}" -o jsonpath={.data.token} | base64 -d)

  3. kubectl config set-credentials "${USERNAME}" --token="$TOKEN"

为了验证test-sa,在刚刚创建的 int32bit-rolebinding
的subjects增加了ServiceAccount test-sa

验证test-sa是否可以读取pod列表以及删除pod:

和预期一样,test-sa能够读取pod列表但没有删除pod权限。

service account除了可以用于集群外认证外,其还有一个最大的特点是可以通过 Pod.spec.serviceAccountName
把token attach到Pod中,这类似于AWS IAM把Role attach关联到EC2实例上。

此时Kubernetes会自动把SA的Token通过volume的形式挂载到 /run/secrets/kubernetes.io/serviceaccount
目录上,从而Pod可以读取token调用Kubernetes API.

针对一些需要和Kubernetes API交互的应用非常有用,比如coredns就需要监控endpoints、services的变化,因此关联了coredns SA,coredns又关联了 system:coredns
 clusterrole。flannel需要监控pods以及nodes变化同样关联了flannel SA。

到这里为止,service account可能是Kubernetes目前最完美的认证方案了,既能支持集群外的客户端认证,又支持集群内的Pod关联授权。

但事实上,service account并不是设计用来给普通user认证的,而是给集群内部服务使用的。目前虽然token是永久有效的,但未来会改成使用动态token的方式,参考官方设计设计文档Bound Service Account Tokens,此时如果kubectl客户端认证则需要频繁更新token。

除此之外,SA虽然能够对应一个虚拟User,但不支持自定义Group,在授权体系中不够灵活。另外也不支持客户端高级认证功能,比如MFA、SSO等。

5 集成外部认证系统

前面已经介绍过Kubernetes集成简单的静态用户文件以及x509证书认证,Kubernetes最强大的功能是支持集成第三方Id Provider(IdP),主流的如AD、LADP以及OpenStack Keystone等,毕竟专业的人做专业的事。

本文接下来主要介绍集成OpenID Connect以及通过webhook集成Keystone。

5.1 通过OpenID Connect集成keycloak认证系统

当前支持OpenID Connect的产品有很多,如:

  • Keycloak

  • UAA

  • Dex

  • OpenUnison

这里以Keycloak为例,这里仅为了实现测试,因此部署standalone模式,安装非常简单,可参考官方文档Getting Started Guide,这里不再赘述。

5.1.1 keycloak配置

由于Kubernetes要求必须是https,测试环境需要签发自己的CA,参考为Kubernetes 搭建支持 OpenId Connect 的身份认证系统:

  1. #!/bin/bash

  2. mkdir -p ssl

  3. cat << EOF > ssl/ca.cnf

  4. [req]

  5. req_extensions = v3_req

  6. distinguished_name = req_distinguished_name


  7. [req_distinguished_name]


  8. [ v3_req ]

  9. basicConstraints = CA:TRUE

  10. EOF

  11. cat << EOF > ssl/req.cnf

  12. [req]

  13. req_extensions = v3_req

  14. distinguished_name = req_distinguished_name


  15. [req_distinguished_name]


  16. [ v3_req ]

  17. basicConstraints = CA:FALSE

  18. keyUsage = nonRepudiation, digitalSignature, keyEncipherment

  19. subjectAltName = @alt_names


  20. [alt_names]

  21. IP.1 = 192.168.193.172

  22. EOF


  23. openssl genrsa -out ssl/ca-key.pem 2048

  24. openssl req -x509 -new -nodes -key ssl/ca-key.pem -days 365 -out ssl/ca.pem -subj "/CN=keycloak-ca" -extensions v3_req -config ssl/ca.cnf


  25. openssl genrsa -out ssl/keycloak.pem 2048

  26. openssl req -new -key ssl/keycloak.pem -out ssl/keycloak-csr.pem -subj "/CN=keycloak" -config ssl/req.cnf

  27. openssl x509 -req -in ssl/keycloak-csr.pem -CA ssl/ca.pem -CAkey ssl/ca-key.pem -CAcreateserial -out ssl/keycloak.crt -days 365 -extensions v3_req -extfile ssl/req.cnf


  28. # 生成 keystore 并导入 keypair

  29. openssl pkcs12 -export -out ssl/keycloak.p12 -inkey ssl/keycloak.pem -in ssl/keycloak.crt -certfile ssl/ca.pem

  30. keytool -importkeystore -deststorepass 'noMoreSecret' -destkeystore ssl/keycloak.jks -srckeystore ssl/keycloak.p12 -srcstoretype PKCS12

由于没有配置固定域名,因此添加了 alt_names
并指定IP。

最后复制 ssl/keycloak.p12
到如下两个路径:

  1. cp ssl/keycloak.p12 keycloak-8.0.1/standalone/configuration/keycloak.jks

  2. cp ssl/keycloak.p12 /etc/kubernetes/pki/

5.1.2 keycloak认证信息配置

登录keycloak管理页面创建一个realm以及client,名称都为 int32bit-kubernetes
。其中realm类似namespace概念,实现了多租户模型,client对应一个认证主体,所有使用keycloak认证的都需要创建一个对应的client。

每个client会对应有一个secret,这二者关系就是access key和access secret关系:

接下来通过Web管理页面执行如下操作:

  • 在Roles中创建两个role分别为 int32bit-kubernetes-cluster-admin
    、 int32bit-kubernetes-readonly

  • 在Users中创建两个用户 k8s-admin
    、 k8s-readonlly

  • k8s-admin
    关联 int32bit-kubernetes-cluster-admin
     role, k8s-readonlly
    关联 int32bit-kubernetes-readonly
     role。

注: 管理员可以在User的Credentials面板中设置用户密码。

通过curl检查是否可认证获取token:

  1. curl -sSLk \

  2. -d "client_id=int32bit-kubernetes" \

  3. -d "client_secret=700eeab2-2f85-45a1-9904-297a0be4d4fd" \

  4. -d "response_type=code token" \

  5. -d "grant_type=password" \

  6. -d "username=k8s-admin" \

  7. -d "password=noMoreSecret" \

  8. -d "scope=openid" \

  9. https://192.168.193.172:8443/auth/realms/int32bit-kubernetes/protocol/openid-connect/token

其中返回的 id_token
,在后面Kubernetes对接中非常重要,它也是一个JWT Token,解码后的内容如下:

  1. {

  2. "alg": "RS256",

  3. "typ": "JWT",

  4. "kid": "95WIuxaLj99XHhmytuQm4POZztFxYCaw3Pd-KyBGVVQ"

  5. }

  6. {

  7. "jti": "39b94185-045a-4cee-b025-d0e0909d6bfd",

  8. "exp": 1576466962,

  9. "nbf": 0,

  10. "iat": 1576466662,

  11. "iss": "https://192.168.193.172:8443/auth/realms/int32bit-kubernetes",

  12. "aud": "int32bit-kubernetes",

  13. "sub": "95374c54-c47f-42a5-9bb2-2e0e417a9ff2",

  14. "typ": "ID",

  15. "azp": "int32bit-kubernetes",

  16. "auth_time": 0,

  17. "session_state": "0eafa8ba-6536-4f6a-989f-177e19e4882a",

  18. "acr": "1",

  19. "email_verified": false,

  20. "preferred_username": "k8s-admin"

  21. }

我们发现 id_token
默认没有groups信息,为了支持Kubernetes的Group认证,需要在client中添加mappers字段groups。

这里之所以映射 UserRealmRole
,而不是 GroupMemberShip
,是因为Group会在id_token中添加前缀 /
,如 /test-group1,/test-group2
,这个暂时没想到怎么处理,或许有更好的办法。

再次生成token_id就会有groups信息了:

  1. {

  2. "jti": "39b94185-045a-4cee-b025-d0e0909d6bfd",

  3. "exp": 1576466962,

  4. "nbf": 0,

  5. "iat": 1576466662,

  6. "iss": "https://192.168.193.172:8443/auth/realms/int32bit-kubernetes",

  7. "aud": "int32bit-kubernetes",

  8. "sub": "95374c54-c47f-42a5-9bb2-2e0e417a9ff2",

  9. "typ": "ID",

  10. "azp": "int32bit-kubernetes",

  11. "auth_time": 0,

  12. "session_state": "0eafa8ba-6536-4f6a-989f-177e19e4882a",

  13. "acr": "1",

  14. "email_verified": false,

  15. "groups": [

  16. "int32bit-kubernetes-cluster-admin"

  17. ],

  18. "preferred_username": "k8s-admin"

  19. }

5.1.3 Kubernetes集成keycloak认证

在api-server中增加如下命令行启动参数:

  1. - --oidc-issuer-url=https://192.168.193.172:8443/auth/realms/int32bit-kubernetes

  2. - --oidc-client-id=int32bit-kubernetes

  3. - --oidc-username-claim=preferred_username

  4. - --oidc-username-prefix=-

  5. - --oidc-groups-claim=groups

  6. - --oidc-ca-file=/etc/kubernetes/pki/keycloak.crt

  • --oidc-issuer-url
    路径需要具体到realm,这里为 int32bit-kubernetes

  • --oidc-client-id
    对应client id,前面我们已经创建。

  • --oidc-username-claim
    、 --oidc-groups-claim
    告诉Kubernetes如何从 id_token
    中读取username和groups,根据前面解码后的 id_token
    ,我们不难选择。

  • --oidc-username-prefix
    告诉Kubernetes针对这个odic的用户需要添加什么前缀,如果集群同时有多个认证系统,建议添加个前缀加以区分,如指定前缀为 odic:
    ,则Kubernetes对应的User为 odic:preferred_username

  • --oidc-ca-file
    指定keycloak的根证书,因为不是权威证书,不指定则不会信任该证书。

前面创建了两个用户,与Role的关联关系如下:

  • k8s-admin: int32bit-kubernetes-cluster-admin

  • k8s-readonly: int32bit-kubernetes-readonly

相对应的在Kubernetes创建两个clusterrolebinging:

cluster-admin:

  1. apiVersion: rbac.authorization.k8s.io/v1

  2. kind: ClusterRoleBinding

  3. metadata:

  4. annotations:

  5. rbac.authorization.kubernetes.io/autoupdate: "true"

  6. labels:

  7. kubernetes.io/bootstrapping: rbac-defaults

  8. name: cluster-admin

  9. roleRef:

  10. apiGroup: rbac.authorization.k8s.io

  11. kind: ClusterRole

  12. name: cluster-admin

  13. subjects:

  14. - apiGroup: rbac.authorization.k8s.io

  15. kind: Group

  16. name: system:masters

  17. - apiGroup: rbac.authorization.k8s.io

  18. kind: Group

  19. name: int32bit-kubernetes-cluster-admin

cluster-readonly:

  1. apiVersion: rbac.authorization.k8s.io/v1

  2. kind: ClusterRoleBinding

  3. metadata:

  4. annotations:

  5. rbac.authorization.kubernetes.io/autoupdate: "true"

  6. labels:

  7. kubernetes.io/bootstrapping: rbac-defaults

  8. name: cluster-readonly

  9. roleRef:

  10. apiGroup: rbac.authorization.k8s.io

  11. kind: ClusterRole

  12. name: view

  13. subjects:

  14. - apiGroup: rbac.authorization.k8s.io

  15. kind: Group

  16. name: int32bit-kubernetes-readonly

5.1.4 使用OpenId Connect认证

生成config文件:

  1. kubectl config set-credentials oidc \

  2. --auth-provider=oidc \

  3. --auth-provider-arg=idp-issuer-url=https://192.168.193.172:8443/auth/realms/int32bit-kubernetes \

  4. --auth-provider-arg=client-id=int32bit-kubernetes \

  5. --auth-provider-arg=client-secret=700eeab2-2f85-45a1-9904-297a0be4d4fd

为了便于登录,下载kube-login插件:

  1. kubectl krew install oidc-login

此时可以直接通过如下命令进行登录:

  1. kubectl oidc-login --username username --password passwor

如果是图形界面,不指定参数直接使用kubectl oidc-login可自动打开浏览器进行登录校验。

可见使用k8s-admin具有所有权限,而k8s-readonly只有list的权限。

5.2 通过webhook集成OpenStack Keystone

webhook和odic一样也是集成外部认证系统的一种方式,当client发起api-server请求时会触发webhook服务TokenReview调用,webhook会检查用户的凭证信息,如果是合法则返回 authenticated": true
等信息。api-server会等待webhook服务返回,如果返回的 authenticated
结果为true,则表明认证成功,否则拒绝访问。

5.2.1 OpenStack Keystone配置

为了后续测试,我们在Keystone创建如下资源:

  1. #!/bin/bash

  2. openstack project create int32bit-kubernetes

  3. USERS_AND_ROLES=(k8s-admin k8s-viewer)

  4. for i in "${USERS_AND_ROLES[@]}"; do

  5. openstack user create --project int32bit-kubernetes --password noMoreSecret "$i"

  6. openstack role create "$i"

  7. openstack role add --user "$i" --project int32bit-kubernetes "$i"

  8. done

其中k8s-admin user关联k8s-admin role,k8s-viewer user关联k8s-viewer role,我们根据不同role角色设置不同的权限:

  1. apiVersion: v1

  2. kind: ConfigMap

  3. metadata:

  4. name: k8s-auth-policy

  5. namespace: kube-system

  6. data:

  7. policies: |

  8. [

  9. {

  10. "resource": {

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

  12. "resources": ["*"],

  13. "version": "*",

  14. "namespace": "default"

  15. },

  16. "match": [

  17. {

  18. "type": "role",

  19. "values": ["k8s-viewer", "k8s-admin"]

  20. },

  21. {

  22. "type": "project",

  23. "values": ["int32bit-kubernetes"]

  24. }

  25. ]

  26. },

  27. {

  28. "resource": {

  29. "verbs": ["create", "update", "delete"],

  30. "resources": ["*"],

  31. "version": "*",

  32. "namespace": "default"

  33. },

  34. "match": [

  35. {

  36. "type": "role",

  37. "values": ["k8s-admin"]

  38. },

  39. {

  40. "type": "project",

  41. "values": ["int32bit-kubernetes"]

  42. }

  43. ]

  44. }

  45. ]

如上policy配置中,k8s-viewer只允许读namesapce default的资源,而k8s-admin允许create、update以及delete等所有权限。

创建如上configmap:

  1. kubectl apply -f keystone-auth.yaml

5.2.2 配置k8s-keystone-auth webhook插件

安装和配置k8s-keystone-auth可参考官方文档k8s-keystone-auth

安装完后验证webhook认证结果:

  1. #!/bin/bash

  2. keystone_auth_service_addr=$(kubectl get svc keystone-auth -o jsonpath={.spec.clusterIP})

  3. token=$(openstack token issue -f shell | awk -F '=' '/^id=.*/{print $2}' | tr -d '"')

  4. cat <<EOF | curl -ks -XPOST -d @- https://${keystone_auth_service_addr}:8443/webhook | python -mjson.tool

  5. {

  6. "apiVersion": "authentication.k8s.io/v1beta1",

  7. "kind": "TokenReview",

  8. "metadata": {

  9. "creationTimestamp": null

  10. },

  11. "spec": {

  12. "token": "$token"

  13. }

  14. }

  15. EOF

输出如果 authenticated": true
则说明认证成功。

当然也可以验证webhook的授权,如验证k8s-viewer是否具有list pods权限:

  1. keystone_auth_service_addr=$(kubectl get svc keystone-auth -o jsonpath={.spec.clusterIP})

  2. cat <<EOF | curl -ks -XPOST -d @- https://${keystone_auth_service_addr}:8443/webhook | python -mjson.tool

  3. {

  4. "apiVersion": "authorization.k8s.io/v1beta1",

  5. "kind": "SubjectAccessReview",

  6. "spec": {

  7. "resourceAttributes": {

  8. "namespace": "default",

  9. "verb": "list",

  10. "group": "",

  11. "resource": "pods"

  12. },

  13. "user": "k8s-viewer",

  14. "group": ["423d41d3a02f4b77b4a9bbfbc3a1b3c6"],

  15. "extra": {

  16. "alpha.kubernetes.io/identity/project/id": ["7c266ba4f14d4a64bda0b6b562f2cd60"],

  17. "alpha.kubernetes.io/identity/project/name": ["int32bit-kubernetes"],

  18. "alpha.kubernetes.io/identity/roles": ["k8s-viewer"]

  19. }

  20. }

  21. }

  22. EOF

如果输出 "allowed":true
,则说明具有list pods权限。

5.2.3 配置Kubernetes使用keystone webhook

按照官方文档,创建webhook conf文件:

  1. keystone_auth_service_addr=$(kubectl get svc keystone-auth -o jsonpath={.spec.clusterIP})

  2. cat <<EOF > /etc/kubernetes/pki/webhookconfig.yaml

  3. ---

  4. apiVersion: v1

  5. kind: Config

  6. preferences: {}

  7. clusters:

  8. - cluster:

  9. insecure-skip-tls-verify: true

  10. server: https://${keystone_auth_service_addr}:8443/webhook

  11. name: webhook

  12. users:

  13. - name: webhook

  14. contexts:

  15. - context:

  16. cluster: webhook

  17. user: webhook

  18. name: webhook

  19. current-context: webhook

  20. EOF

修改api-server配置文件:

  1. sed -i '/image:/ i \ \ \ \ - --authentication-token-webhook-config-file=/etc/kubernetes/pki/webhookconfig.yaml' /etc/kubernetes/manifests/kube-apiserver.yaml

  2. sed -i '/image:/ i \ \ \ \ - --authorization-webhook-config-file=/etc/kubernetes/pki/webhookconfig.yaml' /etc/kubernetes/manifests/kube-apiserver.yaml

  3. sed -i "/authorization-mode/c \ \ \ \ - --authorization-mode=Node,Webhook,RBAC" /etc/kubernetes/manifests/kube-apiserver.yaml

如上开启了基于Webhook授权功能,如果仅使用Keystone认证而不使用Keystone授权,可以不开启。

5.2.4 使用Keystone认证

下载webhook插件,用于请求认证时获取keystone token:

  1. curl -sSL https://api.nz-por-1.catalystcloud.io:8443/v1/AUTH_b23a5e41d1af4c20974bf58b4dff8e5a/lingxian-public/client-keystone-auth -o ~/keystone/client-keystone-auth

  2. chmod +x ~/keystone/client-keystone-auth

通过如下脚本生成kubeconfig文件:

  1. #!/bin/bash

  2. kubectl config set-cluster "int32bit-kubernetes" \

  3. --certificate-authority="/etc/kubernetes/pki/ca.crt" \

  4. --server https://192.168.193.172:6443 \

  5. --embed-certs=true \

  6. --kubeconfig=keystone@int32bit-kubernetes.config


  7. kubectl config set-credentials keystone \

  8. --kubeconfig keystone@int32bit-kubernetes.config

  9. sed -i '/user: {}/ d' keystone@int32bit-kubernetes.config

  10. cat <<EOF >> keystone@int32bit-kubernetes.config

  11. user:

  12. exec:

  13. command: "/root/keystone/client-keystone-auth"

  14. apiVersion: "client.authentication.k8s.io/v1beta1"

  15. EOF

  16. kubectl config set-context \

  17. --cluster=int32bit-kubernetes \

  18. --user=keystone keystone@int32bit-kubernetes \

  19. --namespace=default --kubeconfig keystone@int32bit-kubernetes.config

  20. cp keystone@int32bit-kubernetes.config \

  21. ~/users-credentials/credentials/

配置完后就可以通过Keystone实现认证了。

  1. # cat k8s_viewere_openrc

  2. export OS_DOMAIN_NAME=Default

  3. export OS_USERNAME=k8s-viewer

  4. export OS_PASSWORD=noMoreSecret

  5. export OS_PROJECT_NAME=int32bit-kubernetes

  6. export OS_USER_DOMAIN_NAME=Default

  7. export OS_PROJECT_DOMAIN_NAME=Default

  8. export OS_AUTH_URL=http://192.168.193.77:5000/v3

  9. export OS_IDENTITY_API_VERSION=3

  10. # source k8s_viewere_openrc

  11. # kubectl get pod nginx-7cfc94d94-w8tpl

  12. NAME READY STATUS RESTARTS AGE

  13. nginx-7cfc94d94-w8tpl 1/1 Running 0 13d

  14. # kubectl delete pod nginx-7cfc94d94-w8tpl

  15. Error from server (Forbidden): pods "nginx-7cfc94d94-w8tpl" is forbidden: User "k8s-viewer" cannot delete resource "pods" in API group "" in the namespace "default"

可见k8s-viewer用户可以查看pod但没有删除pod的权限。

  1. # cat k8s_admin_openrc

  2. export OS_DOMAIN_NAME=Default

  3. export OS_USERNAME=k8s-admin

  4. export OS_PASSWORD=noMoreSecret

  5. export OS_PROJECT_NAME=int32bit-kubernetes

  6. export OS_USER_DOMAIN_NAME=Default

  7. export OS_PROJECT_DOMAIN_NAME=Default

  8. export OS_AUTH_URL=http://192.168.193.77:5000/v3

  9. export OS_IDENTITY_API_VERSION=3

  10. # source k8s_admin_openrc

  11. # kubectl get pod nginx-7cfc94d94-w8tpl

  12. NAME READY STATUS RESTARTS AGE

  13. nginx-7cfc94d94-w8tpl 1/1 Running 0 13d

  14. # kubectl delete pod nginx-7cfc94d94-w8tpl

  15. pod "nginx-7cfc94d94-w8tpl" deleted

k8s-admin用户既可以查看pod,也可以删除pod。

我们可以通过kubectl-access_matrix插件查看权限矩阵:

进一步说明k8s-admin用户具有default namespace的所有权限,而k8s-viewer只具有可读权限。

如果企业已经部署OpenStack,Kubernetes运行在OpenStack平台之上,或者通过Magnum部署,集成Keystone实现Kubernetes认证和授权非常方便,很好地把Kubernetes的认证和授权与OpenStack的认证授权统一管理整合在一块。

5.3 使用外部认证系统的优势

对比前面的认证方式,使用OpenID Connect认证以及基于Webhook的认证方式优势显而易见:

  • 安全。基于JWT Token交换认证,JWT具有数字签名,可避免伪造。并且相对Service Account JWT,OpenID Connect认证的JWT具有有效期。

  • 灵活。身份认证和集群本身是松耦合的,通过IDP配置账户信息不需要Kubernetes干预。

  • 认证功能丰富。可使用企业身份系统的MFA、SSO等功能实现更完善更安全的认证策略。

6 总结

本文介绍了Kubernetes认证的几种策略,其中:

  • 静态密码和静态token认证策略的优点是非常简单,缺点是非常不安全和不灵活,不推荐使用。

  • x509证书认证本身的安全性保障没有问题,最大的问题是不支持证书回收,意味着一旦证书颁发出去就很难在回收过来。这种认证策略适合集群内部组件之间的认证通信。

  • bootstrap token适合需要临时授权的场景,如集群初始化。

  • service account基于JWT认证,JWT包含的字段比较简单,没有有效期和aud字段,存在安全隐患,不适用于普通用户认证,适用于集群内的Pod向api-server认证,如kube-proxy和flannel需要调用api-server监控service和pod的状态变化。

  • OpenID Connect(oidc)以及webhook可集成企业已有的身份认证系统,如AD、LDAP,其特点是安全、灵活、功能全面,并且身份认证与Kubernetes集群解耦合,非常适用于普通用户的认证,推荐使用。

参考

  • https://stackoverflow.com/questions/36919323/how-to-revoke-signed-certificate-in-kubernetes-cluster

  • https://www.tremolosecurity.com/kubernetes-dont-use-certificates-for-authentication/

  • Support client certificate revocation

  • applying-best-practice-security-controls-to-a-kubernetes-cluster.

  • https://www.openlogic.com/blog/granting-user-access-your-kubernetes-cluster

  • https://thenewstack.io/no-more-forever-tokens-changes-in-identity-management-for-kubernetes/

图片看不清?点击原文查看高清动图!

文章转载自int32bit,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论