k8s 中的用户认证方式

在 k8s 中包含两类用户:

  1. ServiceAccount。又称服务账号,在运行 pod 时必须绑定 ServiceAccount,如果没有指定,则使用当前 namespace 下的 ServiceAccount default。是针对程序而言,用于 pod 中的程序访问 kube-apiserver。
  2. 普通用户。在 k8s 中并没有使用单独的对象来存储,而是通过了分发证书、外部用户认证系统等方式实现,是针对用户而言。

1. X509 证书认证

使用场景:使用 kubectl 访问 k8s 集群即通过 X509 证书认证方式,kubeconfig 本质上是个证书文件。

客户端使用证书中的 Common Name 作为请求的用户名,organization 作为用户组的成员信息。

证书的签发可以使用 openssl、cfssl 等工具来签发,也可以使用 k8s 自带的 CertificateSigningRequest 对象来实现签发。

1.1. CertificateSigningRequest 签发证书

创建私钥信息:

1
2
openssl genrsa -out myuser.key 2048
openssl req -new -key myuser.key -out myuser.csr -subj "/CN=myuser"

创建如下的 CertificateSigningRequest 对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: myuser
spec:
  # value 使用命令 cat myuser.csr | base64 | tr -d "\n" 获取
  request: xxx
  # 固定值
  signerName: kubernetes.io/kube-apiserver-client
  # 过期时间
  expirationSeconds: 86400  # one day
  usages:
  - client auth
EOF

查看 csr 处于 Pending 状态:

1
2
3
$ kubectl get csr
NAME     AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
myuser   12s   kubernetes.io/kube-apiserver-client   kubernetes-admin   24h                 Pending

批准给证书签发请求:

1
kubectl certificate approve myuser

签发完成后的证书会存放到 status.certificate 字段中,至此证书签发完成。

2. ServiceAccount

使用场景:该方式使用较为常见,用于 pod 中访问 k8s apiserver。

原理:pod 可以通过 spec.serviceAccountName 字段来指定要使用的 ServiceAccount,如果没有指定则使用 namespace 下默认的 default ServiceAccount。kube-controller-manager 中的 ServiceAccount 控制器会在拉起的 pod 中自动注入如下的信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spec:
  volumeMounts: 
  - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
    name: kube-api-access-j6vpz
    readOnly: true
  volumes:
  - name: kube-api-access-j6vpz
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

即将信息注入到 pod 的 /var/run/secrets/kubernetes.io/serviceaccount 目录下,目录结构如下:

1
2
3
4
/var/run/secrets/kubernetes.io/serviceaccount
|-- ca.crt -> ..data/ca.crt
|-- namespace -> ..data/namespace
`-- token -> ..data/token

token 为 JWT 认证,对其格式解密后如下:

1
2
3
4
{
  "alg": "RS256",
  "kid": "u7rF5JCtJRNiMzSUOFAYvDpCwPqUII-N-OtxR59cnQ0"
}
1
2
3
4
5
6
7
8
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "default",
  "kubernetes.io/serviceaccount/secret.name": "ingress-token-s9gtm",
  "kubernetes.io/serviceaccount/service-account.name": "ingress",
  "kubernetes.io/serviceaccount/service-account.uid": "19ce4f11-7105-43ce-b189-f3d71a2ffc74",
  "sub": "system:serviceaccount:default:ingress"
}

2.1. Secret 存放 token

在 1.22 版本及之前版本中,token 以 Secret 的形式存在于 pod 所在的 namespace 下,且 token 不会过期。Secret 的名字存在于 ServiceAccount 的 spec 中,格式如下:

1
2
3
4
apiVersion: v1
kind: ServiceAccount
secrets:
- name: nginx-token-scjvn

而 Secret 通过 Annotation kubernetes.io/service-account.name 指定了关联的 ServiceAccount。

在后续版本中,为了兼容当前方案,如果 ServiceAccount 关联了 Secret,则认为仍然使用 Secret 中存放 token 的方式。如果 Secret 已经很长时间没有使用,则自动回收 Secret。

如果要手工创建一个 token Secret,可以创建如下的 Secret,k8s 自动会为 Secret 产生 token:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
  name: build-robot-secret
  annotations:
    # 带有 annotation
    kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token

2.2. TokenRequest API 产生 token

在 1.22 之后的版本中,kubelet 使用 TokenRequest API 获取有时间限制的临时 token,该 token

会在 pod 删除或者 token 生命周期(默认为 1h)结束后失效。

可以使用 kubectl create token default 来为 ServiceAccount default 创建 token,该命令实际上向 kube-apiserver 发送了请求 /api/v1/namespaces/default/serviceaccounts/default/token

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
    "kind": "TokenRequest",
    "apiVersion": "authentication.k8s.io/v1",
    "metadata":
    {
        "creationTimestamp": null
    },
    "spec":
    {
        "audiences": null,
        "expirationSeconds": null,
        "boundObjectRef": null
    },
    "status":
    {
        "token": "",
        "expirationTimestamp": null
    }
}

kube-apiserver 支持如下参数:

  1. –service-account-key-file:用来验证服务账号的 token。
  2. –service-account-issuer:ServiceAccount token 的签发机构。
  3. –service-account-signing-key-file:ServiceAccount token 的签发私钥。

3. 用户伪装

一个用户通过 Http Header Impersonation- 的方式来扮演另外一个用户的身份。

场景:跨 k8s 集群访问的网关服务

支持的 Http Header 如下:

  1. Impersonate-User:要伪装的用户名。
  2. Impersonate-Group:要伪装的组名。该 Header 可以为多个,即支持多个组。

4. bootstrap token

使用 kube-apiserver 参数 --enable-bootstrap-token-auth=true 启用功能,引导 token 以 Secret 的形式存放在 kube-system 下。

该功能仅用于节点初始化时加入到 k8s 集群中。

资料