Kubernetes mTLS 101

357 阅读4分钟

当客户端发起一个请求访问 K8s 的 kube-apiserver 时,该请求依次经历三个阶段:认证(Authentication,有时简写成 AuthN)、授权(Authorization,有时简写成 AuthZ)和准入控制(Admission Control)。

这三个阶段分别对应身份校验,权限管控,拦截校验与更改。K8s Apiserver 在认证阶段获取用户相关信息,通常由 User 或 Group 定义。但 K8s 中并不直接管理用户信息,而是交由外部系统来管理,这样可以避免重复定义用户模型,方便对接第三方用户权限平台。所以 K8s 并不提供 API 接口进行创建和管理 User 及 Group,相反 k8s 通过以下几种常见用户认证方式进行身份认证:

  • x.509 证书
  • Token
  • HTTP basic auth
  • 身份验证代理

几种比较常见的有 x.509 证书认证(即 mTLS 认证)与 Token 认证。网上绝大部分关于 K8s 认证的文章都是关于创建 ServiceAccount 生成 ServiceAccount Token 进行认证,本文在此不再赘述,下面我们主要介绍如何使用 mTLS 进行身份校验。

什么是 mTLS ?

总所周知,TLS (Transport Layer Security)是一个为 TCP 连接提供安全性的传输层协议,客户端-服务器应用程序使用 TLS 协议以防止窃听和篡改的方式通过网络进行通信。因为 TLS 在传输层工作,所以它可以与任何应用级别的 TCP 协议组合,而不需要执行任何不同的操作(如 HTTPS 就是 HTTP 与 TLS 的组合)。TLS 协议的主要通过使用加密技术(如证书)在两个或多个计算机应用程序之间提供安全性,包括机密性(Confidentiality)、完整性(Integrity)和真实性(Authenticity)。TLS 保证了真实性,但服务器通常不需要知晓客户端的身份(如 Https),所以默认情况下只有客户端单向验证服务器,但服务器不验证客户端。所以 mTLS (Mutual TLS) 使真实性对称,即客户端与服务器相互进行验证。K8s 中的 mTLS 通过解析客户端使用的 x.509 证书,将证书中 subjectCommon Name 字段作为用户名,结合 RBAC 权限验证机制进行客户端请求的认证与授权。

使用 mTLS 结合 RBAC 进行身份验证与授权非常便利,不需要额外的用户模型系统及 API 来维护管理用户及身份,同时应用程序中也不需要侵入任何关于身份验证的代码。并且由于非对称加密的特性,证书中仅包含身份信息和公钥,只有持有私钥的客户端才可以证明自己对应证书中的身份。证书本身需要由 K8s 信任的 CA 签署才可生效。 因此,使用 mTLS 方式与 K8s 通信,客户端只需要配置 x.509 证书,对应私钥及 CA 证书。

使用 mTLS 进行通信

首先创建 service account 及对应 RBAC 权限,此处定义的角色权限为允许访问 default 命名空间下的 Pods,并可以对其进行 get、 list 操作。对应声明 sa.yaml 如下:

kind: ServiceAccount
apiVersion: v1
metadata:
 name: mtls-client
 namespace: default
---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 namespace: default
 name: mtls-client
rules:
- apiGroups: [ "" ]
  resources: [ pods ]
  verbs: [ get,list ]
---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: mtls-client
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: ClusterRole
 name: mtls-client
subjects:
 - kind: ServiceAccount
   name: mtls-client
   namespace: default

在 K8s 中创建相应资源:

kubectl apply -f sa.yaml
  • 接下来创建 x.509 证书,首先创建 rsa 私钥
openssl genrsa -out k8s_mtls.key 4096
  • 生成证书签名请求(CSR, CertificateSigninRequest)

    openssl req -new -key k8s_mtls.key -config k8s_mtls_csr.cnf -out k8s_mtls.csr -nodes
    

    以下为 CSR 配置文件 k8s_mtls_csr.cnf

    [req]
    default_bits = 2048
    default_md = sha256
    distinguished_name = dn
    prompt = no
    
    [dn]
    CN = system:serviceaccount:default:mtls-client
    O = system:serviceaccounts
    
    [v3_ext]
    authorityKeyIdentifier = keyid,issuer:always
    basicConstraints = CA:TRUE
    keyUsage = keyEncipherment,dataEncipherment
    extendedKeyUsage = clientAuth
    
  • 通过以下声明创建 K8s CSR 资源

    apiVersion: certificates.k8s.io/v1
    kind: CertificateSigningRequest
    metadata:
      name: k8s-mtls-csr 
    spec:
      groups:
      - system:authenticated
      request: ${BASE64_CSR}
      signerName: kubernetes.io/kube-apiserver-client
      usages:
      - digital signature
      - key encipherment
      - client auth
    
    

    获取 csr 文件内容并替换 yaml 中的 BASE64_CSR 环境变量

    export BASE64_CSR=$(cat ./k8s_mtls.csr | base64 | tr -d '\n')
    cat k8s_mtls_csr.yaml | envsubst | kubectl apply -f -
    
  • 查看并批准 CSR 请求

    $ kubectl get csr
    NAME           AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
    k8s-mtls-csr   3s    kubernetes.io/kube-apiserver-client   kubernetes-admin   <none>              Pending
    $ kubectl certificate approve k8s-mtls-csr
    certificatesigningrequest.certificates.k8s.io/k8s-mtls-csr approved
    $ kubectl get csr
    NAME           AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
    k8s-mtls-csr   37s   kubernetes.io/kube-apiserver-client   kubernetes-admin   <none>              Approved,Issued
    

    此时我们可以看到 csr 资源的状态已经是 Approved,Issued,所以我们不需要再创建 CA 对证书进行签名,直接使用 K8s 的 CA 证书即可。

  • 下载证书到本地

    k get csr k8s-mtls-csr -o jsonpath='{.status.certificate}' \
       | base64 --decode > k8s_mtls.pem
    

    我们可以进一步查看证书详细信息:

    $ openssl x509 -in k8s_mtls.pem -noout -text
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number:
                b5:38:5a:c9:f7:f...:8d:79:c7:66:5e:84:67
            Signature Algorithm: sha256WithRSAEncryption
            Issuer: CN = kubernetes
            Validity
                Not Before: Jan 27 09:24:32 2023 GMT
                Not After : Jan 27 09:24:32 2024 GMT
            Subject: O = system:serviceaccounts, CN = system:serviceaccount:default:mtls-client
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    RSA Public-Key: (4096 bit)
                    Modulus:
    ...
    

    可以看到 Subject 中的 CN 已经是 service account 账户,接下来我们将 CA 证书也下载到本地:

    kubectl get secrets | grep mtls-client | awk '{system("kubectl get secret -o jsonpath=\"{.items[0].data}\"  $1")}'| jq -r '."ca.crt"' |base64 -d > k8s_mtls_ca.pem
    

    有了 x.509 证书、私钥及 CA 证书后,我们可以通过 curl 验证是否可以使用 mTLS 方式访问 K8s:

    curl -v --cert ./k8s_mtls.pem --key ./k8s_mtls.key --cacert ./k8s_mtls_ca.pem https://<your apiserver address>:<your apiserver port>/api/v1/pods?limit=5
    

    获取到结果即说明客户端认证成功。