K8S集群安全机制

一、访问方式

由于API-Server 是 Kubernetes 集群数据的唯一访问入口,任一 Kubernetes API 的访问都属于以下三种方式之一:

  • 以证书方式访问的普通用户或进程,包括运维人员及 kubectl、 kubelet 等进程

  • 以 Service Account 方式访问的 Kubernetes 的内部服务进程

  • 以匿名方式访问的进程

两种用户账号

  • 集群内部的 Service-Account,但它并不是给 Kubernetes 集群的用户(系统管理员、 运维人员、租户用户等)用的,而是给运行在 Pod 里的进程用的,它为 Pod 里的进程提供了必要的身份证明

  • 外部的用户账号 user-account,给用户使用,具有全局性

1. 证书访问

yaml 复制代码
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTi***
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin    用户
user:
client-certificate-data: LS0tLS1CRUdJ***
client-key-data: LS0tLS1CR*** 

2. ServiceAccount 访问

3. 匿名方式访问

bash 复制代码
➜  ~ curl -k https://localhost:6443/api
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "forbidden: User "system:anonymous" cannot get path "/api"",
  "reason": "Forbidden",
  "details": {},
  "code": 403
}

匿名用户 system:anonymous

sql 复制代码
k get rolebindings.rbac.authorization.k8s.io -owide -A | grep -i system:anonymous
kube-public     kubeadm:bootstrap-signer-clusterinfo                Role/kubeadm:bootstrap-signer-clusterinfo           45d   system:anonymous

k describe role kubeadm:bootstrap-signer-clusterinfo -n kube-public
Name:         kubeadm:bootstrap-signer-clusterinfo
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources   Non-Resource URLs  Resource Names  Verbs
  ---------   -----------------  --------------  -----
  configmaps  []                 [cluster-info]  [get]

匿名账号访问绑定的 role 只能查看 k8s 的 cluster-info

bash 复制代码
curl -k https://localhost:6443/api/v1/namespaces/kube-public/configmaps/cluster-info

二、API-Server 架构分析

API Server 的架构从上到下可以分为以下几层:

1. Client

访问 api-server 的客户端主要分为两类

  • kubectl:使用 kubeconfig 里的信息 (里面保存了客户端访问API-server的密钥相关信息) 来访问
  • Pod: 使用 ServiceAccount 来访问

2. API层

k8s 所有的 API 参看 Github文档

主要以 REST 方式提供各种API接口,除了有 Kubernetes 资源对象的 CRUD 和 Watch 等主要 API,还有健康检查、UI、日志、性能指标等运维监控相关的API

一个 API 对象在 etcd 里面完整的资源路径是由 Group(API组),Version (API版本) 和 Resource (API资源类型) 三个部分组成

bash 复制代码
/apis/batch/v1/jobs 

Group: batch
Version: v1
Resouces: jobs

对于k8s里面的核心API对象,比如Pod、Node等,是不需要Group的(它们的Group是""); 对于这些核心的API对象,k8s会直接在 /api 这个层级下进行下一步的匹配过程

3. 访问控制层

当客户端访问API接口时,访问控制层负责对用户身份鉴权,验明用户身份,核准用户对 Kubernetes 资源对象的访问权限,然后根据配置的各种资源访问许可逻辑(Admission Control),判断是否允许访问

4. 注册表层

Kubernetes 把所有资源对象都保存在注册表(Registry)中,针对注册表中的各种资源对象都定义了:资源对象的类型、如何创建资源对象、如何转换资源的不同版本,以及如何将资源编码和解码为 JSON 或 ProtoBuf 格式进行存储

5. etcd数据库

用于持久化存储 Kubernetes 资源对象的KV数据库。etcd 的 watch API接口对于API Server来说至关重要,因为通过这个接口,API Server 创新性地设计了 List-Watch 这种高性能的资源对象实时同步机制,使 Kubernetes 可以管理超大规模的集群,及时响应和快速处理集群中的各种事件

6. List-Watch 机制

从本质上看,API Server与常见的MIS或ERP系统中的DAO模块类似,可以将主要处理逻辑视作对数据库表的CRUD操作

这里解读API Server中资源对象的 List-Watch 机制。以一个完整的Pod调度过程为例,对API-Server 的 List-Watch 机制进行说明

  • 借助 etcd 提供的 Watch API 接口,API Server可以监听(Watch)在 etcd 上发生的数据操作事件,比如Pod创建事件、更新事件、删除事件等,在这些事件发生后,etcd 会及时通知 API-Server

  • 当一个 ReplicaSet 对象被创建并被保存到 etcd 中后,etcd 会立即发送一个对应的 Create 事件给 API-Server(图中的箭头3)

  • 为了让 Kubernetes 中的其他组件在不访问底层 etcd 数据库的情况下,也能及时获取资源对象的变化事件,API Server 模仿 etcd 的 Watch API 接口提供了自己的 Watch 接口,这样一来,这些组件就能近乎实时地获取它们感兴趣的任意资源对象的相关事件通知了,图中的 controller-manager 、scheduler、以及 kubelet 等组件之间的 watch 就表明了这个过程,在监听自己感兴趣的资源的时候,客户端可以增加过滤条件,比如 node1 节点的kubelet 进程只对自己节点上的 pod 事件感兴趣

三、认证(Authentication)

认证即是识别用户身份,认证的方式很多,比如 HTTP base,HTTP token,TLS,Service Account,OpenID Connect等

Kubernetes 集群中所有资源的访问和变更都是通过 Kubernetes API Server 的 REST API 实现的,所以集群安全的关键点就在于如何识别并认证客户端身份 (Authentication),以及随后访问权限的授权 (Authorization) 这两个关键问题

对于第一个认证的问题,Kubernetes 集群提供以下几种级别的客户端身份认证方式

  • 最严格的 HTTPS 证书认证:基于CA根证书签名的双向数字证书认证方式

  • HTTP Token认证:通过一个Token 来识别合法用户

  • OpenID Connect Token 认证 第三方认证:通过第三方OIDC协议进行认证

  • Webhook Token 认证:通过外部 Webhook 服务进行认证

  • Authenticating Proxy:通过代理程序进行认证

1. 双向数字证书认证

单向数字证书验证,如HTTPS TLS 层握手,client 只校验 server 的证书

CA 签发证书的过程,如上图左边部分:

  • 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值
  • 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
  • 最后将 Certificate Signature 添加在文件证书上,形成数字证书;

客户端校验服务端的数字证书的过程,如上图右边部分:

  • 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;

  • 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密 Certificate Signature 内容,得到一个 Hash 值 H2 ;

  • 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信

双向数字证书认证即是 client 校验 server 的证书,server 也校验 client 的证书

1. 使用证书 与 api-server 交互

yaml 复制代码
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJT****

这个 kubernetes-admin 用户没有绑定任何的 role

css 复制代码
➜  ~ k get clusterrolebindings.rbac.authorization.k8s.io -owide | grep -i kubernetes-admin
➜  ~
➜  ~ k get rolebindings.rbac.authorization.k8s.io -A -owide | grep -i kubernetes-admin

所以这个用户肯定是借用的所在的用户组 system:masters 的权限

ini 复制代码
➜  ~ k get clusterrolebindings.rbac.authorization.k8s.io -owide | grep -i system:masters
cluster-admin      ClusterRole/cluster-admin      47d      system:masters

➜  ~ k describe clusterrole cluster-admin
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]
             [*]                []              [*]

所以这个 kubernetes-admin 这个用户能够操作集群内的任何资源

2. 签发证书

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

1. 使用CA根证书签发客户端证书(用的不多)

sql 复制代码
1.生成私钥
openssl genrsa -out cat.key 2048

2.是用于创建证书签名请求(Certificate Signing Request,CSR)
openssl req -new -key cat.key -out cat.csr -subj "/CN=cat"

3.基于CSR文件签发x509证书
openssl x509 -req -in cat.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out cat.crt -days 365 

4.将客户端证书文件 cat.crt 和客户端密钥文件 cat.key 设置为名为 cat 的用户凭证,并将这些证书和密钥嵌入到 kubeconfig 文件中
kubectl config set-credentials cat --client-certificate=cat.crt  --client-key=cat.key  --embed-certs=true

kubectl config view

kubectl config set-context cat@kubernetes --cluster=kubernetes --user=cat

kubectl config use-context cat@kubernetes

配置 userAccount 访问权限

yaml 复制代码
cat <<EOF | k apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-cat-rolebinding
subjects:
  - kind: User
    name: cat
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
EOF

上面方式的缺陷,我们需要在集群的master节点上进行配置,但是生产环境下一般情况下我们都没办法进入到集群的master节点机器

但是一般情况我们都能拿到对应的一个kubeconfig文件用来访问k8s集群,通过 kubeconfig 文件我们也可以进行证书的批准和授权

2. 通过 CSR 签发证书(推荐)

  1. 前面通过CA签发证书需要有CA的私钥,其实 Kubernetes 可以直接发起 CSR 请求
csharp 复制代码
生成私钥和CSR文件
openssl genrsa -out cat.key 2048
  1. 生成一个CSR文件
vbnet 复制代码
openssl req -new -key cat.key -out cat.csr

这里一定注意:生成的CSR文件里面的用户名一定要写你下面利用 kubeconfig 文件操作 k8s 集群的用户名

  1. 通过 kubectl 创建一个 CertificateSigningRequest 并将其提交到 Kubernetes 集群
yaml 复制代码
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: cat
spec:
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0****
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
EOF
  • usage 字段必须是 'client auth',代表密钥的用途
  • expirationSeconds 证书过期时间(1.22版本之前没有这个字段,默认的过期时间是十年左右)

以下是我在 k8s 1.20 版本上申请的证书的默认生效时间范围

  • request 字段是 CSR 文件内容的 base64 编码值。 要得到该值,可以执行命令

cat cat.csr | base64 | tr -d "\n"

  1. 批准 CertificateSigningRequest
bash 复制代码
kubectl certificate approve cat

注意:下面的 csr 代表的是 certificatesigningrequests(是k8s里面的一种资源),注意与上面的 certificate 区分开来

  1. 从 CertificateSigningRequest 导出颁发的证书
bash 复制代码
kubectl get csr cat -o jsonpath='{.status.certificate}'| base64 -d > cat.crt
  1. 配置到对应的 kubeconfig 上下文中
matlab 复制代码
将客户端证书文件 cat.crt 和客户端密钥文件 cat.key 设置为名为 cat 的用户凭证,并将这些证书和密钥嵌入到 kubeconfig 文件中
kubectl config set-credentials cat --client-certificate=cat.crt  --client-key=cat.key  --embed-certs=true

kubectl config view

kubectl config set-context cat@kubernetes --cluster=kubernetes --user=cat
  1. 配置 userAccount 访问权限

参看 Github文档 创建k8s的所有clusterrole的相关权限

yaml 复制代码
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-cat-rolebinding
subjects:
  - kind: User
    name: cat
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole           # 不一定要配置集群管理员权限
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
  1. 切换上下文为当前新创建的用户
perl 复制代码
kubectl config use-context cat@kubernetes

2. HTTP Token认证

HTTP Token 的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串一Token 来表明客户身份的一种方式。在通常情况下,Token 是一个很复杂的字符串,比如我们用私钥签名一个字符串后的数据就可以当作一个Token。此外,每个Token 对应一个用户名,存储在 API Server 能访问的一个文件中

当客户端发起API调用请求时 ,需要在 HTTP Header 里放入 Token , 这样 API-Server 就能识别合法用户和非法用户

1. 静态令牌(Token)

自己机器上的 kube-apiserver 的启动参数如下

/etc/kubernetes/manifests/kube-apiserver.yaml

kube-apiserver 启动参数参考官网

ini 复制代码
- command:
- kube-apiserver
- --advertise-address=10.37.18.97
- --allow-privileged=true
- --authorization-mode=Node,RBAC        启动 RBAC 和 Node 授权模式
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --secure-port=6443
- --service-account-issuer=https://kubernetes.default.svc.cluster.local
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
- --service-cluster-ip-range=172.16.9.0/24
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
- --token-auth-file=/etc/kubernetes/pki/token.csv             启用HTTP-Token认证
  • client-ca-file 指定CA根证书文件,内置CA公钥用于验证某证书是否是CA签发的证书

  • tls-cert-file 指定Api-Server证书文件

  • tls-private-key-file 指定Api-Server私钥文件

生成静态 token,格式如下 token,user,uid,"group1,group2,group3"

bash 复制代码
export BOOTSTRAP_TOKEN=$(head -c 16 /dev/urandom | od -An -t x | tr -d ' ')
cat > token.csv <<EOF
${BOOTSTRAP_TOKEN},kubelet-bootstrap,10001,"system:kubelet-bootstrap"
EOF

请求Api时只要在Authorization头中加入Bearer Token即可:

bash 复制代码
curl -k --header "Authorization: Bearer d6fe3b32dac12ca0e55f8a0114eec2d0" https://localhost:6443/api

2. ServiceAccount Token

服务账号(Service Account)是一种自动被启用的用户认证机制,使用经过签名的持有者令牌来验证请求

每个 Service-Account 都对应一个 Secret 对象,在这个 Secret 对象中有一个加密的 secret 字段,这个 Token 字段就是 Bearer Token

这个 token 使用哪个公钥加密的,取决于 API-Server 的启动参数 --service-account-key-file 设置的文件

Service Account 的正常工作离不开以下三个控制器:

Service Account Controller、Token Controller、Admission Controller

Service Account Controller

Service Account Controller 的工作相对简单,它会监听 Service Account 和 Namespace 这两种资源对象的事件,如果在一个 Namespace 中没有默认的 Service Account,那么它会为该 Namespace 创建一个默认的 ServiceAccount 对象,这就是在每个 Namespace 下都有一个名为 default 的 Service Account 的原因

Token Controller

Token Controller 也监听 Service Account 的事件,如果发现在新建的 Service Account 里没有对应的 Service Account Secret,则会用 APIServer 私钥(--service-account-private-key-file 指定的文件)创建一个 Token,并用该 Token、api-server 的 CA 证书等三个信息产生一个新的 Secret 对象,然后放入刚才的 Service Account 中。如果监听到的事件是 Service Account 删除事件,则自动删除与该 Service Account 相关的所有 Secret。此外,Token Controller 对象也会同时监听 Secret 的创建和删除事件,确保与对应的 Service Account 的关联关系正确

Admission Controller

Admission Controller 会验证 Pod 里的 Service Account 是否合法,当我们在 api-server 的准入控制链中启用了 Service Account 类型的准入控制器时(这也是默认的设置),则针对 Pod 新增或修改的请求,会做出如下控制操作:

  1. 如果 spec.serviceAccount 域没有被设置,则 Kubernetes 默认为其指定名称为 default 的 ServiceAccount

  2. 如果 Pod 的 spec.serviceAccount 域指定了不存在的 Service Account,则该 Pod 操作会被拒绝。

  3. 如果在 Pod 中没有指定 ImagePullSecrets,那么这个 spec.serviceAccount 域指定的 ServiceAccount 的 ImagePullSecrets 会被加入该 Pod 中。

  4. 给 Pod 添加一个特殊的 volumeSource,在该 Volume 中包含 ServiceAccountSecret 中的 Token

  5. 给 Pod 里的每个容器都增加对应的 VolumeSource,将包含 Secret 的 Volume 挂载到 Pod 中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount)

一旦 Secret 被创建,就可以通过下面三种方式使用它 :

  • 创建 Pod 时,通过为 Pod 指定 ServiceAccount 来自动使用该 Secret

  • 通过挂载该 Secret 到 Pod 来使用它

  • 在 Docker 镜像下载时使用,通过指定 Pod 的 spc.ImagePullSecrets 来使用它

pod 中如果不手动指定service-account,则默认的secret会被自动创建,且对应的卷会被挂载到每个pod上

在Pod内使用ServiceAccount的token和证书

go 复制代码
func InClusterConfig() (*Config, error) {
    const (
tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
    )
    host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
    if len(host) == 0 || len(port) == 0 {
        return nil, ErrNotInCluster
    }

    token, err := ioutil.ReadFile(tokenFile)
    if err != nil {
        return nil, err
    }

    tlsClientConfig := TLSClientConfig{}

    if _, err := certutil.NewPool(rootCAFile); err != nil {
        klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
    } else {
        tlsClientConfig.CAFile = rootCAFile
    }

    return &Config{
        // TODO: switch to using cluster DNS.
        Host:            "https://" + net.JoinHostPort(host, port),
        TLSClientConfig: tlsClientConfig,
        BearerToken:     string(token),
        BearerTokenFile: tokenFile,
    }, nil
}

Service-account token 是 JWT(Json web token)

eyJhbGciOiJSUzI1NiIsImtpZCI6Ik91a1NaajM2UWd6SlU0cE5wRlZ5amdLZTlTd0FSMkVpdlhkYXJqMnhud3MifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbG54ZHciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjgzM2UyYTc0LTMzNTQtNDlkMS1iMzZhLTY2YTEyOGM4MGYyMCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.TYFnZOt6S-o1DqTfhuDt0Tz25u0jhEVKn_G7wUMG8sanwuQaJEJJ8hGZMwLNDVZQrNpsmg3B591YEUhOx8m1G9zMzqt948plgtsW4p878UwzwjuTgk9JDPcYgT5O2CRY_tE6cN4-FPPj3S7_JD6Pto1n55Bx-6yvts8epu4fG7gpTWePMjdOXKC2tz6xawNGvo92RnI4FEVpozDWKJn5p8azrfudqRQhSbs4Oj_haSnV4Rn677gfBLMinoNC_GHd5EWLRQCrHUcErH42gkF66qxVpp19Rh_csZNwQ4fgajbyQQBEuYQ7CJenv73vTbfPifeVCun1Vy2mGjx6hPeN6A

一个JWT实际上就是一个字符串,由三部分组成分别是:

  • header(头部)
json 复制代码
{
    "alg": "RS256",
    "kid": "OukSZj36QgzJU4pNpFVyjgKe9SwAR2EivXdarj2xnws"
}
  • payload (载荷)

payload(载荷)信息存放的是Claims声明信息。载荷其实就是自定义的数据,一般存储用户Id,过期时间等信息。也就是JWT的核心所在,因为这些数据就是使后端知道此token是哪个用户已经登录的凭证。而且这些数据是存在token里面的,由前端携带,所以后端几乎不需要保存任何数据

json 复制代码
{
    "iss": "kubernetes/serviceaccount",
    "kubernetes.io/serviceaccount/namespace": "default",
    "kubernetes.io/serviceaccount/secret.name": "default-token-lnxdw",
    "kubernetes.io/serviceaccount/service-account.name": "default",
    "kubernetes.io/serviceaccount/service-account.uid": "833e2a74-3354-49d1-b36a-66a128c80f20",
    "sub": "system:serviceaccount:default:default"
}
  • signature(签名)

signature(签名)需要使用编码后的header和payload以及一个秘钥,使用header中声明的编码方式进行加盐secret组合加密,然后就构成了jwt的第三部分

最后将 token = header + '.'+ payload +'.'+ signature,生成token

Token 验证流程

  1. 客户端提交账号和密码等信息到服务端

  2. 通过登录验证后,服务端响应由 JWT 生成的 Token 令牌

  3. 客户端要访问服务器中其他资源,会在请求中带着 Token 到服务端

  4. 服务端接收到请求之后,从 Token 中拿出 header 和 payload ,然后通过HS256算法将 header 和 payload 和 "盐" 值 进行计算得出内容,将计算出的内容与Token中的第三部分,也就是 Signature 去比较,如果一致则验证通过,反之则失败

3. Bootstrap Token

API 服务器上设置 --enable-bootstrap-token-auth 标志来启用基于启动引导令牌的身份认证组件

lua 复制代码
kubeadm token create --kubeconfig ...

在 kube-system namespace 下

  1. 创建Token
  1. 查看Token

四、授权/鉴权(Authorization)

授权是识别是否有相应操作权利

授权者,通过组合属性(用户属性,资源属性,实体)的策略向用户授予访问权限。授权的方式也有很多,比如:AlwaysDeny,AlwaysAllow,ABAC,RBAC,Node 等,高版本的 kubernetes 默认的授权方式是 RBAC 和 Node

通过 api-server 的 ****authorization-mode 来开启授权

ini 复制代码
- --authorization-mode=Node,RBAC        启动 RBAC 和 Node 授权模式

1. 授权方式--Node

要启用节点鉴权器,使用 --authorization-mode=Node 启动 API 服务器

根据调度到 kubelet 上运行的 Pod 为 kubelet 授予权限,专门对 kubelet 发出的 API 请求进行授权

节点鉴权器允许 kubelet 执行 API 操作。包括:

读取操作:

  • services、endpoints、nodes、pods、与绑定到 kubelet 节点的 Pod 相关的 Secret、ConfigMap、PersistentVolumeClaim 和持久卷

写入操作:

  • 节点和节点状态(启用 NodeRestriction 准入插件以限制 kubelet 只能修改自己的节点)
  • Pod 和 Pod 状态 (启用 NodeRestriction 准入插件以限制 kubelet 只能修改绑定到自身的 Pod)
  • Event

身份认证与鉴权相关的操作:

  • 对于基于 TLS 的启动引导过程时使用的 certificationsigningrequests API 的读/写权限
  • 为委派的身份验证/鉴权检查创建 TokenReview 和 SubjectAccessReview 的能力

为了获得节点鉴权器的授权,kubelet 必须使用一个凭证以表示它在 system:nodes 组中,用户名为 system:node:<nodeName>,并且该组名和用户名的格式需要与 kublet TLS 启动过程中为 kubelet 创建的标识匹配

yaml 复制代码
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS**
    server: https://10.37.18.97:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:node:n37-018-097
  name: system:node:n37-018-097@kubernetes
current-context: system:node:n37-018-097@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:n37-018-097
  user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem    kubelet证书
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem            kubelet私钥
vbnet 复制代码
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text
  • CN (Common Name):通常是证书持有者的名称或标识符,例如域名、个人姓名等
  • O (Organization):证书持有者所属的组织或机构名称

观察图可知,这个用户组就是system:nodes,用户名是 system:node:n37-018-097,授权中心是 kubernetes

sql 复制代码
➜  ~ k get clusterrolebindings.rbac.authorization.k8s.io -owide | grep -i system:nodes

➜  ~ k describe clusterrolebindings.rbac.authorization.k8s.io kubeadm:node-autoapprove-certificate-rotation
Name:         kubeadm:node-autoapprove-certificate-rotation
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
Subjects:
  Kind   Name          Namespace
  ----   ----          ---------
  Group  system:nodes

2. 授权方式--RBAC

RBAC 是一种基于角色的授权方式,角色是对用户拥有权利的抽象,角色绑定是将角色绑定到用户(user,group或者service account)

RBAC API 声明了四种 Kubernetes 对象:RoleClusterRoleRoleBindingClusterRoleBinding

RBAC 的三个基本概念

  • Subject:被作用者,它表示k8s中的三类主体, user, group, serviceAccount
  • Role:角色,它其实是一组规则,定义了一组对 Kubernetes API 对象的操作权限
  • RoleBinding:定义了"被作用者"和"角色"的绑定关系

Q: 为什么需要 ClusterRole 和 ClusterRoleBinding ?

A:两个理由

  1. 一个常规的角色只允许访问和角色在同一命名空间中的资源。 如果你希望允许跨不同命名空间访问资源,就必须要在每个命名空间中创建一个 Role 和 RoleBinding。 如果你想将这种行为扩展到所有的命名空间(集群管理员可能需要 ), 需要在每个命名空间中创建相同的 Role和 RoleBinding。当创建一个新的命名空间时, 必须记住也要在新的命名空间中创建这两个资源

比如我上面 bar 命名空间的服务账号想要访问 foo 命名空间的 services 相关信息,就必须要在 foo 命令空间内创建对应的 Role 和 RoleBinding(注意RoleBinding绑定对应的服务账号是可以指定namespace的)

  1. 一些特定的资源完全不在命名空间中 (包括 Node、 PersistentVolume、 Namespace,等等)。我们也提到过 API 服务器对外暴露了 一些不表示资源的 UI 路径(例如/ healthz)。 常规角色不能对这些资源或非资源型的 URL 进行授权,但是 ClusterRole 可以

另外,Kubernetes 还提供了四个预先定义好的 ClusterRole 来供用户直接使用,它们是:

  • cluster-admin:超管

  • admin:普通管理权限

  • edit:修改权限

  • view:只读权限

3. webhook

WebHook 是一种 HTTP 回调:某些条件下触发的 HTTP POST 请求;通过 HTTP POST 发送的简单事件通知。一个基于 web 应用实现的 WebHook 会在特定事件发生时把消息发送给特定的 URL

具体来说,当在判断用户权限时,Webhook 模式会使 Kubernetes 查询外部的 REST 服务

Webhook 模式需要一个 HTTP 配置文件,通过

--authorization-webhook-config-file=SOME_FILENAME 的参数声明

配置文件的格式使用 kubeconfig。 在该文件中,"users" 代表着 API 服务器的 webhook,而 "cluster" 代表着远程服务

使用 HTTPS 客户端认证的配置例子

yaml 复制代码
# Kubernetes API 版本
apiVersion: v1
# API 对象种类
kind: Config
# clusters 代表远程服务。
clusters:
  - name: name-of-remote-authz-service
    cluster:
      # 对远程服务进行身份认证的 CA
      certificate-authority: /path/to/ca.pem
      # 远程服务的查询 URL。必须使用 'https'。
      server: https://authz.example.com/authorize

# users 代表 API 服务器的 webhook 配置
users:
  - name: name-of-api-server
    user:
      client-certificate: /path/to/cert.pem # webhook plugin 使用 cert
      client-key: /path/to/key.pem          # cert 所对应的 key

# kubeconfig 文件必须有 context。需要提供一个给 API 服务器。
current-context: webhook
contexts:
- context:
    cluster: name-of-remote-authz-service
    user: name-of-api-server
  name: webhook

五、准入(Admisson control)

判断你的操作是否符合集群的要求,是一种更灵活的管控机制,用户还可以根据自己的需求定义准入插件来管理集群

kubernetes 中将准入模块分为三种,validating(验证型),mutating(修改型)以及两者兼有,准入的默认配置是NodeRestriction

默认的准入控制器

sql 复制代码
查看哪些插件被默认启动
kube-apiserver -h | grep enable-admission-plugins

 --enable-admission-plugins strings       admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.

NamespaceLifecycle

类别:验证型

该准入控制器禁止在一个正在被终止的 Namespace 中创建新对象,并确保针对不存在的 Namespace 的请求被拒绝。该准入控制器还会禁止删除三个系统保留的名字空间,即 defaultkube-systemkube-public

Namespace 的删除操作会触发一系列删除该名字空间中所有对象(Pod、Service 等)的操作。 为了确保这个过程的完整性,我们强烈建议启用这个准入控制器。

DefaultTolerationSeconds

类别:修改型

此准入控制器基于 k8s-apiserver 的输入参数 default-not-ready-toleration-secondsdefault-unreachable-toleration-seconds 为 Pod 设置默认的容忍度,以容忍 notready:NoExecuteunreachable:NoExecute 污点 (如果 Pod 尚未容忍 node.kubernetes.io/not-ready:NoExecutenode.kubernetes.io/unreachable:NoExecute 污点的话)。 default-not-ready-toleration-secondsdefault-unreachable-toleration-seconds 的默认值是 5 分钟。

ServiceAccount

类别:变更和验证

此准入控制器实现了 ServiceAccount 的自动化

额外的准入控制器

也可以用 --enable-admission-plugins 开启额外的"准入控制器"

NodeRestriction

类别:验证型

这个插件的作用是限制每一个kubelet只能操作自己node的资源

Node 授权模式针对的 Subject 是 Node, 不是 user 或者应用的 Service Account, 是专门对 kubelet 发起的 API 请求进行授权的管理模式

Node 授权者 (node authorizer) 允许 kubelet 发起 API 操作的资源对象如下

  1. 读取操作: Service、 Endpoint、 Node、 Pod、 Secret、 ConfigMap、 PVC, 以及绑定到 Node 的与 Pod 相关的持久卷
  2. 写入操作:
  • Node 和 Node Status (启用 NodeRestriction 准入控制器,以限制 kubelet 只能修改自己节点的信息)
  • Pod 和 Pod Status (启用 NodeRestriction 准入控制器,以限制 kubelet 只能修改绑 定到本节点的 Pod 信息 )
  • Event
  1. 授权相关操作:
  • 基于 TLS 启动引导过程中使用的 certificationsigningrequest 资源对象的读写操作;
  • 在代理鉴权或授权检查过程中创建 tokenreview 和 subjectaccessreview 资源对象

为了获取 Node 授权者的授权, kubelet 需要使用一个凭据,以标识它在 system:nodes 组内,用户名为 system:node:,并且该组名和用户名的格式需要与 kubeletTLS 启动过程中为 kubelet 创建的标识匹配

参考文章

K8S Api Server认证

Linux企业运维------Kubernetes(十三)访问控制

API Server简介

Kubernetes--API Server

kubernetes.feisky.xyz/extension/a...

kubernetes.io/zh-cn/docs/...

小林Coding

相关推荐
幼儿园老大*10 分钟前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
YCyjs3 小时前
K8S群集调度二
云原生·容器·kubernetes
Hoxy.R3 小时前
K8s小白入门
云原生·容器·kubernetes
童先生7 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*9 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
景天科技苑13 小时前
【云原生开发】K8S多集群资源管理平台架构设计
云原生·容器·kubernetes·k8s·云原生开发·k8s管理系统
架构师那点事儿14 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
wclass-zhengge14 小时前
K8S篇(基本介绍)
云原生·容器·kubernetes
颜淡慕潇14 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
昌sit!1 天前
K8S node节点没有相应的pod镜像运行故障处理办法
云原生·容器·kubernetes