k8s RBAC访问控制(认证、鉴权、审计)流程原理解析

Kubernetes自身并没有用户管理能力,无法像操作Pod一样,通过API的方式创建/删除一个用户实例,也无法在etcd中找到用户对应的存储对象。

在Kubernetes的访问控制流程中,用户模型是通过请求方的访问控制凭证(如kubectl使用的kube-config中的证书、Pod中引入的ServerAccount)产生的
Kubernetes API的请求从发起到其持久化入库的流程如图:

一、认证阶段(Authentication)

判断用户是否为能够访问集群的合法用户。
apiserver目前提供了9种认证机制。每一种认证机制被实例化后会成为认证器(Authenticator),每一个认证器都被封装在http.Handler请求处理函数中,它们接收组件或客户端的请求并认证请求。
假设所有的认证器都被启用,当客户端发送请求到kube-apiserver服务,该请求会进入Authentication Handler函数(处理认证相关的Handler函数)。在Authentication Handler函数中,会遍历已启用的认证器列表,尝试执行每个认证器,当有一个认证器返回true时,则认证成功,否则继续尝试下一个认证器;如果用户是个非法用户,那apiserver会返回一个401的状态码,并终止该请求。

1、RequestHeader认证

Kubernetes可以设置一个认证代理,客户端发送的认证请求可以通过认证代理将验证信息发送给apiserver
apiserver需要配置:
    --requestheader-username-headers=X-Remote-User
    --requestheader-group-headers=X-Remote-Group
    --requestheader-extra-headers-prefix=X-Remote-Extra-
    --requestheader-client-ca-file:防止头部欺骗
    --requestheader-allowed-names:设置允许的CN列表

2、BasicAuth认证

启动apiserver时通过–basic-auth-file参数启用BasicAuth认证。
AUTH_FILE(Static Password file)是一个CSV文件,文件格式为:
password,user,uid,"group1,group2,group3"
发起请求时在HTTP中添加头即可:
Authorization: Basic BASE64ENCODED(USER:PASSWORD)

3、clientCA认证

X509认证是Kubernetes组件间默认使用的认证方式,同时也是kubectl客户端对应的kube-config中经常使用到的访问凭证。它是一个比较安全的方式。

首先访问者会使用由集群CA签发的,或是添加在apiserver配置中的授信CA签发的客户端证书去访问apiserver。apiserver在接收到请求后,会进行TLS的握手流程。

除了验证证书的合法性,apiserver还会校验客户端证书的请求源地址等信息,开启双向认证。

进行证书签发的步骤:

(1)创建根CA

cat << EOF | tee ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

其中:

  • profiles:指定不同的过期时间、使用场景等参数。文件中可以定义多个,分别后续在签名证书时使用某一个
  • signing:表示该证书可用于签名其它证书,生成的ca.pem证书中CA=TRUE
  • key encipherment:表示密钥用法为密钥加密
  • server auth:表示client可以用该CA 对server提供的证书进行验证
  • client auth:表示server可以用该CA对client提供的证书进行验证
cat << EOF | tee ca-csr.json
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"L": "Shenzhen",
"ST": "Shenzhen",
"O": "k8s",
"OU": "System"
}
]
}
EOF

其中:

  • CN:Common Name,用于从中提取该字段作为请求的用户名
  • C:Country, 国家
  • ST: State,州,省
  • L: Locality,地区,城市
  • O: Organization Name, 用于从中提前该字段作为请求用户所属的组
  • OU: Organization Unit Name,组织单位名称,公司部门
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

执行后生成文件ca.csr、ca-key.pem、ca.pem

(2)签发其它系统组件的证书

Kubernetes集群中所有系统组件与apiserver通讯用到的证书,其实都是由集群根CA来签发的。

①如kube-proxy对应的csr文件

cat << EOF | tee kube-proxy-csr.json
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "L": "Shenzhen",
      "ST": "Shenzhen",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
EOF

使用根CA签署证书:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy

执行后生成文件kube-proxy.csr、kube-proxy-key.pem、kube-proxy.pem

②kubelet启动时实际需要指定两个配置文件

–kubeconfig指定的是kube-config文件,其中内置了集群根CA公钥以及自己作为客户端的公钥和私钥

–config指定的是kubelet的配置,格式如下:

kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: xxx.xxx.xxx.xxx
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS: ["10.0.0.2"]
clusterDomain: cluster.local.
failSwapOn: false
authentication:
    anonymous:
        enabled: true

kubelet组件在工作时,采用主动的查询机制,即定期请求apiserver 获取自己所应当处理的任务,如哪些pod分配到了自己身上,从而去处理这些任务;同时kubelet自己还会暴露出两个本身api的端口,用于将自己本身的私有api暴露出去,这两个端口分别是该配置文件中指定的10250与10255。

对于10250端口,kubelet会在其上采用TLS加密以提供适当的鉴权功能;对于10255端口,kubelet会以只读形式暴露组件本身的私有api,并且不做鉴权处理。

因此,kubelet上实际上有两个地方用到证书,一个是用于与 API server通讯所用到的证书,另一个是该配置文件中设置的kubelet的10250私有api端口需要用到的证书。

(3)签发用户的证书
①首先开发人员需用通过OpenSSL等证书工具生成私钥
openssl genrsa -out test.key 2048

②创建对应的x509 csr请求文件(需要在subj字段中指定user和group)

openssl req -new -key test.key -out test.csr -subj "/CN=xxxx/O=xxxx"

③Kubernetes集群本身就提供了证书签发的API certificates.k8s.io/v1beta1。调用后,api-server会根据请求,以csr资源对象的形式创建对应的签发请求。

例如,在集群的创建过程中,像kubeadm这样的集群安装工具,也会基于不同的csr签发请求调用api-server对应接口,创建不同的csr资源对象。

用户可以通过API创建K8s csr实例并等待管理员审批。

cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: xxxx
spec:
  groups:
  - system:authenticated
  request: $(cat test.csr | base64 | tr -d "\n")
  usages:
  - client auth
EOF

PS:request中是base64编码的csr文件

刚开始创建的签发实例都会处于pending状态:

NAME   AGE   REQUESTOR   CONDITION
xxxx   10s   admin       Pending
直到有权限的管理员进行审批后,这个csr才会处于approved状态,请求对应的证书就会被签发
# kubectl certificate approve john
certificatesigningrequest.certificates.k8s.io/xxxx approved

证书以base64存在csr资源对象的.status.certificate中,配合私钥、CA公钥即可制作kubeconfig文件

集群管理员也可以直接读取集群根CA,并通过x509 csr请求文件签发证书。签发示例如下:

# openssl x509 -req -in test.csr -CA CA_LOCATION/ca.crt -Cakey CA_LOCATION/ca.key -Cacreateserial -out test.crt -days 365

命令中需要指明csr和ca.crt的文件路径,以及签发证书的过期时间信息

④查看kubeconfig配置

# kubectl config view

kubectl默认会从$HOME/.kube目录下查找文件名为config 的文件,也可以通过设置环境变量KUBECONFIG或者通过设置–kubeconfig去指定其它kubeconfig文件。

文件格式为:

{
  "apiVersion": "v1",
  "kind": "Config",
  "preferences": {},
 
  "clusters": [
    {
      "cluster": {
        "certificate-authority": 
        "server": "https://ip:6443"
      },
      "name": {cluster-name}
    }
  ],
  "contexts": [
    {
      "context": {
        "cluster": {cluster-name},
        "user": {user-name}
      },
      "name": {context-name}
    }
  ],
 
  "users": [
    {
      "name": {user-name},
      "user": {
        "client-certificate": 
        "client-key": 
      }
    }
  ]
  "current-context": {context-name},
}

若想要用base64编码数据代替认证文件,需要添加后缀-data,将 certificate-authority、client-certificate、client-key改为certificate-authority-data、client-certificate-data、client-key-data

从config文件还原证书的方法:
# grep 'client-key-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d 
# grep 'client-certificate-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d

⑤下载集群ca公钥文件ca.pem,使用kubectl添加集群连接信息

# kubectl config set-cluster xxx --certificate-authority=ca.pem --embed-certs=true --server=https://ip:6443

⑥使用kubectl设置kubeconfig的users配置段信息,需要将用户秘钥信息加入kubectl配置中

# kubectl config set-credentials {user-name} --client-certificate=test.crt --client-key=test.key --embed-certs=true

⑦添加新的context入口到kubectl配置中

# kubectl config set-context {context-name} --cluster={cluster-name} --user={user-name}

⑧多集群config的合并和切换

# export KUBECONFIG=file1:file2:file3 
# kubectl config view --merge --flatten > ~/.kube/all-config 
# export KUBECONFIG = ~/.kube/all-config

⑨查看和切换上下文

# kubectl config get-contests 
# kubectl config use-context {your-contexts}

4、TokenAuth认证

启动apiserver时通过–token-auth-file参数启用TokenAuth认证。

AUTH_FILE(Static Password file)是一个CSV文件,文件格式为:

token,user,uid,"group1,group2,group3"
发起请求时在HTTP中添加头即可:
Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

5、ServiceAccountAuth认证

serviceaccount是k8s中唯一能够通过API方式管理的apiserver访问凭证,通常用于pod中的业务进程与apiserver的交互

ServiceAccount解决Pod在集群里面的身份认证问题,认证使用的授权信息存在secret里面(由SecretAccount Controller自行创建)。

当一个namespace创建完成后,会同时在该namespace下生成名为default的serviceaccount和对应Secret:

apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2019-11-19T03:07:32Z"
  name: default
  namespace: default
  resourceVersion: "191"
  selfLink: /api/v1/namespaces/default/serviceaccounts/default
  uid: b2322727-08d5-4095-acbe-1afee4fb5e6c
secrets:
- name: default-token-nfdr4

对应的Secret里:

data字段有两块数据:ca.crt用于对服务端的校验,token用于Pod的身份认证,它们都是用base64编码过的。

metadata里annotations字段表明了关联的ServiceAccount信息(被哪个ServiceAccount使用)。

type字段表明了该Secret是service-account-token类型

apiVersion: v1
data:
  ca.crt: LS0tLS1...
  namespace: ZGVmYXVsdA==
  token: ZXlKaG...
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: default
    kubernetes.io/service-account.uid: b2322727-08d5-4095-acbe-1afee4fb5e6c
  creationTimestamp: "2019-11-19T03:07:32Z"
  name: default-token-nfdr4
  namespace: default
  resourceVersion: "190"
  selfLink: /api/v1/namespaces/default/secrets/default-token-nfdr4
  uid: cbb919a4-6309-43c0-ac0b-566e30e9b116
type: kubernetes.io/service-account-token

此外,用户也可以通过api创建其它名称的ServiceAccount,并在该namespace的Pod的.spec.ServiceAccount下指定,默认是default。

Pod创建的时候,Admission Controller会根据指定的ServiceAccount把对应secret的ca.crt和token文件挂载到固定目录/var/run/secrets/kubernetes.io/serviceaccount下。

    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-jbcp7
      readOnly: true

pod要访问集群的时候,默认利用Secret其中的token文件来认证Pod的身份,利用ca.crt校验服务端

默认token的认证信息为:

– Group:system:serviceaccounts:[namespace-name]

– User:system:serviceaccount:[namespace-name]:default

Pod身份被认证合法后,其权限需要通过RBAC来配置,默认只有资源GET权限

 

PS:如果是在Pod创建过程中,发现指定的ServiceAccount不存在,则该Pod创建过程会被终止。

PS:对于已经创建的Pod,不能更新其已经挂载的ServiceAccount内容。

6、Bootstrap Token认证

如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情

TLS bootstrapping的功能就是让kubelet先使用一个预定的低权限用户连接到apiserver,向apiserver申请证书,证书由apiserver动态签署

在配合RBAC授权模型下的工作流程大致如下所示:

TLS bootstrapping下kubelet发起的CSR请求大致分为以下三种:

nodeclient:kubelet以O=system:nodes、CN=system:node:(node name)形式发起的CSR请求(仅在第一次启动时产生)

selfnodeclient:kubelet发起的更新自己的作为client的证书的CSR请求(与上一个证书有相同的O、CN)

selfnodeserver:kubelet发起的更新自己的作为server的证书(即kubelet 10250 api端口证书)的CSR请求

①使用TLS Bootstrapping Token时的配置流程:

(1)创建TLS Bootstrapping Token

# head -c 16 /dev/urandom | od -An -t x | tr -d ' '
8f01b7072246e0f3409d54e379c8699f

(2)修改用户的描述文件token.csv(相当于预设的用户配置),基本格式为Token,user,uid,group:

8f01b7072246e0f3409d54e379c8699f,kubelet-bootstrap,10001,"system:kubelet-bootstrap"

(3)在apiserver配置中添加–enable-bootstrap-token-auth开启TLS bootstrapping功能,通过–token-auth-file参数指定token.csv文件,apiserver启动时会将其加载,相当于在集群内创建了这个用户。

(4)kubelet-bootstrap用户没有任何权限(包括创建CSR请求),需要创建一个ClusterRoleBinding,将预设用户kubelet-bootstrap用户与内置的ClusterRole system:node-bootstrapper绑定到一起,使其能够发起 CSR 请求

# kubectl create clusterrolebinding kubelet-bootstrap \ 
  --clusterrole=system:node-bootstrapper \ 
  --user=kubelet-bootstrap

否则kubelet会报401无权访问apiserver的错误

(4)创建kubelet的配置文件bootstrapping.kubeconfig

BOOTSTRAP_TOKEN=01f6717d648e3e7e71282a9632dd99ab
KUBE_APISERVER="https://132.132.11.12:6443"

执行命令:

# kubectl config set-cluster kubernetes \
  --certificate-authority=./ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-credentials kubelet-bootstrap \
  --token=${BOOTSTRAP_TOKEN} \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-context default \
  --cluster=kubernetes \
  --user=kubelet-bootstrap \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

kubelet-bootstrap用户的Token和apiserver的使用的CA证书被写入了该配置文件中

首次请求时,kubelet使用配置文件中的apiserver CA证书与apiserver建立TLS通讯,使用配置文件中的用户Token向apiserver声明自己的RBAC授权身份。

(5)启动kubelet时需要指定–kubeconfig和–bootstrap-kubeconfig(两种身份),指定–cert-dir用来存放所有证书

(6)在kubelet首次启动后,如果用户Token没问题,并且RBAC也做了相应的设置,那么此时在集群内应该能看到kubelet发起的CSR 请求。出现CSR请求后,可以使用kubectl手动签发kubelet的证书

(7)当成功签发证书后,目标节点的 kubelet 会将证书写入到–cert-dir选项指定的目录中

kubelet-client.crt、kubelet-client.key:kubelet与apiserver通讯所使用的证书

kubelet.crt、kubelet.key:用于kubelet的10250端口做鉴权使用(这个证书是个独立于apiserver CA的自签CA,并且删除后kubelet会重新生成它)

 

配置controller manager自动签署证书

kubelet发起的CSR请求都是由controller manager来做实际签署的

(1)kubelet启动时增加–feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true参数,则证书即将到期时会自动发起一个renew自己证书的CSR请求

配置了–feature-gates=RotateKubeletClientCertificate=true后,kubelet首次启动时仍会使用kubelet-client.crt证书与apiserver通信。续期请求被批准后会生成一个kubelet-client-时间戳.pemkubelet-client-current.pem文件则始终软连接到最新的真实证书文件

配置了–feature-gates=RotateKubeletServerCertificate=true后不再生成kubelet.crt,改为生成kubelet-server-时间戳.pem,kubelet-server-current.pem文件则始终软连接到最新的真实证书文件

 

(2)controller manager启动时增加–feature-gates=RotateKubeletServerCertificate=true参数,则会在kubelet发起证书请求的时候自动帮助其签署证书

 

PS:如果不配置该参数,则即使配置了相关的RBAC规则,也只会自动批准kubelet client的更新证书请求

此外,还需要配置RBAC 规则,保证 controller manager只对kubelet发起的特定CSR请求自动批准

(3)针对kubelet发起的3种CSR请求创建3种对应的ClusterRole:

# A ClusterRole which instructs the CSR approver to approve a user requesting node client credentials.
kind:ClusterRole
apiVersion:rbac.authorization.k8s.io/v1
metadata:
name:approve-node-client-csr
rules:
-apiGroups:["certificates.k8s.io"]
resources:["certificatesigningrequests/nodeclient"]
verbs:["create"]
 
---
 
# A ClusterRole which instructs the CSR approver to approve a node renewing its own client credentials.
kind:ClusterRole
apiVersion:rbac.authorization.k8s.io/v1
metadata:
name:approve-node-client-renewal-csr
rules:
-apiGroups:["certificates.k8s.io"]
resources:["certificatesigningrequests/selfnodeclient"]
verbs:["create"]
 
---
 
# A ClusterRole which instructs the CSR approver to approve a node requesting a serving cert matching its client cert.
kind:ClusterRole
apiVersion:rbac.authorization.k8s.io/v1
metadata:
name:approve-node-server-renewal-csr
rules:
-apiGroups:["certificates.k8s.io"]
resources:["certificatesigningrequests/selfnodeserver"]
verbs:["create"]

PS:1.8后的apiserver自动创建了前两条ClusterRole

(4)将适当的ClusterRole绑定到 kubelet 自动续期时所所采用的用户或者用户组身上

自动批准kubelet的首次CSR请求(用于与apiserver通信的证书):

# kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=approve-node-client-csr --group=system:bootstrappers

自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求:

# kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=approve-node-client-renewal-csr --group=system:nodes

自动批准 kubelet 发起的用于 10250 端口鉴权证书的 CSR 请求(包括后续 renew):

# kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes

PS:在 1.8 后kubelet需要增加–rotate-certificates参数,kubelet 才会自动重载新证书

PS:在 1.7 版本以后kube-controller-manager可以通过–experimental-cluster-signing-duration参数来设置签署的证书有效时间,默认为8760h0m0s(1年)。

 

②使用TLS Bootstrapping Token Secret时的配置流程:

(1)生成token

echo "$(head -c 6 /dev/urandom | md5sum | head -c 6)"."$(head -c 16 /dev/urandom | md5sum | head -c 16)”
47f392.d22d04e89a65eb22

Token 必须满足 [a-z0-9]{6}\.[a-z0-9]{16} 格式;以 . 分割,前面的部分被称作Token ID(可以暴露出去),后面的部分称为Token Secret(需要保密)

(2)创建Bootstrap Token Secret

apiVersion: v1
kind: Secret
metadata:
  name: bootstrap-token-07401b
  namespace: kube-system
type: bootstrap.kubernetes.io/token
stringData:
  description: "The default bootstrap token generated by 'kubeadm init'."
  token-id: 47f392
  token-secret: d22d04e89a65eb22
  expiration: 2018-09-10T00:00:11Z
  usage-bootstrap-authentication: "true"
  usage-bootstrap-signing: "true"
  auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress

type 必须为 bootstrap.kubernetes.io/token

  1. name 必须为 bootstrap-token-<token id>
  2. usage-bootstrap-authentication、usage-bootstrap-signing 必须设置为 true
  3. expiration 字段是可选的,如果设置则到期后将由 Controller Manager 中的 tokencleaner 自动清理
  4. auth-extra-groups 也是可选的,令牌的扩展认证组,组必须以system:bootstrappers:开头

 

(3)引导时,kubelet使用Token发起的请求其用户名为system:bootstrap:<token id>,用户组为system:bootstrappers

创建ClusterRoleBinding时要绑定到这个用户或者组上

允许 system:bootstrappers 组用户创建 CSR 请求:

# kubectl create clusterrolebinding kubelet-bootstrap \
  --clusterrole=system:node-bootstrapper \
  --group=system:bootstrappers

自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求:

# kubectl create clusterrolebinding node-client-auto-approve-csr \
  --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient \
  --group=system:bootstrappers

自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求:

# kubectl create clusterrolebinding node-client-auto-renew-crt \
  --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient \
  --group=system:nodes

自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求:

# kubectl create clusterrolebinding node-server-auto-renew-crt \
    --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeserver \
    --group=system:nodes

(4)Controller Manager需要添加参数–controllers=*,bootstrapsigner,tokencleaner以启用 tokencleaner 和 bootstrapsigner

(5)kubelet使用的kubeconfig文件也要相应变化:

# kubectl config set-cluster kubernetes \
  --certificate-authority=/etc/kubernetes/ssl/k8s-root-ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-credentials system:bootstrap:47f392 \
  --token=47f392.d22d04e89a65eb22 \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-context default \
  --cluster=kubernetes \
  --user=system:bootstrap:47f392 \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

7、OIDC认证

所谓OIDC(OpenID Connect),就是先向identity provider获取服务器签名的JSON Web Token (JWT)

identity provider会提供access_token、id_token、refresh_token

使用kubectl时通过–token参数添加id_token,或者直接把它添加到kubeconfig文件中,kubectl会把id_token添加到http头里

apiserver会通过证书确认JWT是否有效、确认JWT是否过期、身份是否合法等

 

使用OIDC认证,apiserver需要配置:
    --oidc-issuer-url:identity provider的地址
    --oidc-client-id:client id,一般配成kubernetes
    --oidc-username-claim,如sub
    --oidc-groups-claim,如groups
    --oidc-ca-file:为identity provider签名的CA公钥

 

8、Webhook TokenAuth认证

当客户端发送的认证请求到达apiserver时,apiserver回调钩子方法,将验证信息发送给远程的Webhook服务器进行认证,然后根据Webhook服务器返回的状态码来判断是否认证成功

通过指定如下参数启用WebhookTokenAuth认证:

    --authentication-token-webhook-config-file:Webhook配置文件描述了如何访问远程Webhook服务
    --authentication-token-webhook-cache-ttl:缓存认证时间,默认值为2分钟

9、Anonymous认证

未被其他认证器拒绝的请求都可视为匿名请求,匿名用户权值很低

apiserver通过指定–anonymous-auth参数启用Anonymous认证,默认该参数值为true

 

认证流程之后,api-server会将请求中凭证中的用户身份转化为对应的User和Groups。在随后的鉴权操作和审计操作流程中,api-server都会使用该用户模型实例。

二、鉴权阶段(Authorization)

采用RBAC判断用户是否有权限进行请求中的操作。如果无权进行操作,api-server会返回403的状态码,并终止该操作

RBAC包含三个要素:

  • Subjects:可以是开发人员、集群管理员这样的自然人,也可以是系统组件进程、Pod中的业务进程;
  • API Resource:也就是请求对应的访问目标,在Kubernetes集群中指各类资源对象;
  • Verbs:对应为请求对象资源可以进行哪些操作,如list、get、watch等。

 

部分常用操作需要的权限如下:

 

Role:定义了用户在指定的Kubernetes namespace上可以进行哪些操作

 

通过RoleBinding进行role和Subject的绑定:

 

除了定义指定namespace中的权限模型,也可以通过ClusterRole定义一个集群维度的权限模型。以定义集群维度的权限(如PV、Nodes等namespace中不可见的资源)

ClusterRole编排文件几乎和Role一样,删除指定namespace的那行即可。

通过ClusterRoleBinding进行ClusterRole和Subject的绑定。

 

系统预置的ClusterRole:

system:basic-user:system:unauthenticated组(未认证用户组)默认绑定Role,无任何操作权限

cluster-admin:system:masters组默认绑定的ClusterRole,有集群管理员权限

系统组件(kube-controller-manager、kube-scheduler、kube-proxy……)都绑定了默认的ClusterRole

 

三、审计阶段(AdmissionControl)

Admission Controller(准入控制器)是一个拦截器,被编译进API Server的可执行文件内部

它以插件的形式运行在apiserver进程中,会在鉴权阶段之后、对象被持久化到etcd之前,拦截apiserver的请求,对请求的资源对象执行自定义(校验、修改或拒绝等)操作。

AC有几十种,大体上分为3类:

  1.     validating(验证型)用于验证k8s的资源定义是否符合规则
  2.     mutating(修改型)用于修改k8s的资源定义,如添加label,一般运行在validating之前
  3.     既是验证型又是修改型

只要有一个准入控制器拒绝了该请求,则整个请求被拒绝(HTTP 403Forbidden)并返回一个错误给客户端。

可通过–enable-admission-plugins参数指定启用的准入控制器列表,通过–disable-admission-plugins参数指定禁用的准入控制器列表

查看打开的AC:

# kube-apiserver -h | grep enable-admission-plugins
      --admission-control strings              Admission is divided into two phases. In the first phase, only mutating admission plugins run. In the second phase, only validating admission plugins run. The names in the below list may represent a validating plugin, a mutating plugin, or both. The order of plugins in which they are passed to this flag does not matter. Comma-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DefaultStorageClass, DefaultTolerationSeconds, DenyEscalatingExec, DenyExecOnPrivileged, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodPreset, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. (DEPRECATED: Use --enable-admission-plugins or --disable-admission-plugins instead. Will be removed in a future version.)
      --enable-admission-plugins strings       admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DefaultStorageClass, DefaultTolerationSeconds, DenyEscalatingExec, DenyExecOnPrivileged, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodPreset, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.
可见,AC一共有几十种,下面介绍一些常用的:

1、ResourceQuota和LimitRanger

ResourceQuota可以限制namespace资源用量

apiVersion: v1
kind: ResourceQuota
metadata:
  name: ns-quota-cns-test
  namespace: cns-test
spec:
  hard:
    pods: "4"
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "26"
    limits.memory: 2Gi
  scopeSelector:
    matchExpressions: 
    - operator: Exists
      scopeName: NotBestEffort

spec.hard:除了基础的资源,还可以可以限制Pod数量

spec.scopeSelector:定义更丰富的索引能力,包括Terminating/Not Terminating、BestEffort/NotBestEffort、PriorityClass。

创建ResourceQuota后,如果用户用超了资源,在提交Pod时,会收到一个forbidden的403错误,提示exceeded quota。但假如用户提交没有包含在这个ResourceQuota方案里面的资源,还是能成功的。

 

在某个namespace下创建LimitRange,则会自动为该namespace下的容器添加request和limit

例如创建该LimitRange:

apiVersion: v1 
kind: LimitRange 
metadata: 
    name: mem-limit-range 
spec: 
    limits:     
    - default: 
        memory: 512Mi 
      defaultRequest:     
        memory: 256Mi 
        type: Container

会自动为所在命名空间下的容器添加256M的内存request和512M的内存limit

2、SecurityContextDeny

SecurityContext用于限制容器的一个行为,保证系统和其他容器的安全。从粒度上又分为以下两种:

Container-level Security Context:仅应用到指定的容器

Pod-level Security Context:应用到Pod内所有容器以及Volume

该能力不是Kubernetes或者容器runtime本身的能力,而是kubernetes和runtime通过用户的配置,最后下传到内核里,再通过内核的机制让其生效。

Pod级别和容器级别配置SecurityContext的例子:

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false

SecurityContext设置项主要包括:

(1)通过用户ID和组ID来控制文件访问权限;

(2)SELinux:通过策略配置来控制用户或者进程对文件的访问控制;

(3)特权容器;

(4)Capabilities:给特定进程来配置一个privileged能力;

(5)AppArmor:通过一些配置文件来控制可执行文件的一个访问控制权限(比如说一些端口的读写);

(6)对系统调用的控制;

(7)对子进程能否获取比父亲更多的权限的一个限制

最后其实都是落到内核来控制它的一些权限。

3、PodSecurityPolicy

Pod Security Policies(PSP)是应用到集群内部所有Pod以及Volume的安全策略

PSP的使用:

(1)通过在apiserver的admission-plugin参数中添加PodSecurityPolicy开启:

(2)在集群中创建PSP策略实例,支持的控制项包括:

 

(3)配置策略与身份的RBAC策略绑定(大多数pod中使用的身份都是Serviceaccount)
(4)启动PSP后admission会强制要求pod在鉴权后找到至少一个对应的策略实例,因此最好设置一个集群维度的全局策略,同时针对指定namespace配置细化策略
(5)如果同时有多个PSP满足权限绑定关系,优先从非mutating(不改变Pod模型的策略)的满足策略中按照实例name名字母排序选择第一个

4、ValidatingAdmissionWebhook和MutatingAdmissionWebhook

webhook就是一个HTTP回调,接收API Server发送的admissionReview请求,处理(验证或修改)并返回admissionResponse。

使用步骤:

(1)创建ValidatingWebhookConfiguration文件和MutatingWebhookConfiguration文件:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: validation-kube-webhook-cfg
  namespace: paas
  labels:
    app: paas-webhook
webhooks:
  - name: nodeport.kube-webhook.cn
    clientConfig:
      service:
        name: paas-webhook-svc
        namespace: paas
        path: "/validating"
      caBundle: LS0tLS1...
    rules:
      - operations: [ "CREATE" ]
        apiGroups: ["apps", "extensions", ""]
        apiVersions: ["v1", "v1beta1"]
        resources: ["services"]
    namespaceSelector:
      matchLabels:
        paas-webhook: enabled

其中rules定义了匹配规则,当发给apiserver的请求满足该规则的时候,apiserver就会给clientConfig中配置的service发送Admission请求。

(2)开发的webhook程序需要实现IWebHookServer接口:

type IWebHookServer interface {
    mutating(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
    validating(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
    Start()
    Stop()
}

例如,定义一个结构体:

type webHookServer struct {
    server *http.Server
}

 

实现这四个方法:

①开始

func (ws *webHookServer) Start() {
   ws.server.ListenAndServeTLS("", "")
}
②结束
func (ws *webHookServer) Stop() {
   glog.Infof("Got OS shutdown signal, shutting down wenhook server gracefully...")
   ws.server.Shutdown(context.Background())
}

在main函数中调用go ws.Start()后,可以以如下方式阻塞,等待退出信号后再调用ws.Stop()

signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan
 
ws.Stop()

③mutating负责修改

④validating负责审核

可以用一个req := ar.Request接下请求

req.Kind是metav1.GroupVersionKind(GVK结构体)

例如req.Kind.Kind是Service时的反序列化:

var service corev1.Service
json.Unmarshal(req.Object.Raw, &service)
resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta

最后返回结果的数据结构:

type AdmissionResponse struct {
   UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
   Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"`
   Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
   Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"`
   PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"`
   AuditAnnotations map[string]string `json:"auditAnnotations,omitempty" protobuf:"bytes,6,opt,name=auditAnnotations"`
}

返回示例:

allowed := true
result = &metav1.Status{
   Reason: "Unauthorized nodeport",
}
return &v1beta1.AdmissionResponse{
   Allowed: allowed,
   Result:  result,
}

webhook可以做到很多事情,例如限制每个namespace使用的端口号、为每个Pod插入sidecar容器等。

参考资料:

[1] https://kubernetes.io/docs/home/

[2] https://edu.aliyun.com/roadmap/cloudnative

[3] https://mritd.me/2018/08/28/kubernetes-tls-bootstrapping-with-bootstrap-token/

[4] https://mritd.me/2018/01/07/kubernetes-tls-bootstrapping-note/

[5] 郑东旭《Kubernetes源码剖析》

发表评论