36、K8S-安全机制-ServiceAccount(SA)

发布时间 2023-03-30 13:54:30作者: 小粉优化大师

1、基础知识

1.1、场景基础

1.1.1、应用场景

对于任何一种应用场景,其权限的认证管理都是非常重要的,对于linux系统来说,selinux、防火墙、
pam、sudo等等,其核心的目的都是为了保证我们的环境是一个安全的。
对于k8s的这种大型的任务编排系统来说,设计到的认证远远超出了一般平台对认证的技术要求,在k8s平台
中,陆陆续续的产生了一些列对平台的权限认证、对业务的权限认证、对网络的安全认证等等一些了认证体
系。由于篇幅限制,我们这里专门针对平台和应用的权限认证来进行详细的学习。

1.1.2、业务流程

用户访问k8s业务应用的流程:
方法一:无需api_server认证
 用户 -- ingress|service -- pod
方法二:基于api_server认证
管理k8s平台上各种应用对象 对于Kubernetes平台来说,几乎所有的操作基本上都是通过kube
-apiserver这个组件进行的,该组件提供 HTTP RESTful形式的API供集群内外客户端调用,对于kubernetes集群的部署样式主要有两种:http形 式和https形式。我们采用kubeadm部署的形式默认对外是基于https的方式,而内部组件的通信时基于 http方式,而k8s的认证授权机制仅仅存在于https形式的api访问中,也就是说,如果客户端使用HTTP连接 到kube-apiserver,那么是不会进行认证授权的,这样既增加了安全性,也不至于太复杂。

1.2、认证简介

对APIServer的访问一般都要经过的三个步骤,认证(Authn)+授权(Authz)、准入控制(Admission),它
也能在一定程度上提高安全性,不过更多是资源管理方面的作用。
注意:认证和授权功能都是以插件化的方式来实现的,这样可以最大化的用户自定义效果。

1.2.1、认证-(Authn)

对用户身份进行基本的认证,只允许被当前系统许可的人进入集群内部

1.2.2、授权(Authz) 

不同的用户可以获取不同的资源操作权限,比如普通用户、超级用户、等

1.2.3、准入控制(Admission)

类似于审计,主要侧重于 操作动作的校验、语法规范的矫正等写操作场景。

1.3、认证流程

1.3.1、认证流程图

1.3.2、认证流程说明

左侧:
 对于k8s来说,它主要面对两种用户:
 普通人类用户(交互模式) - User Account
 集群内部的pod用户(服务进程) - Service Account
中间: 每个部分都是以插件的方式来整合到 k8s 集群环境中:
- 认证和授权是按照 插件的顺序进行依次性的检测,并且遵循 "短路模型" 的认证方式 所谓的"短路"即,失败的时候,到此结束,后续的规则不会进行。 - 准入控制 由于仅用户写操作场景,所以它不遵循 "短路模型",而是会全部检测,原因在于,它要记录为什么不执行,原因是什么,方便后续审计等动作。

2、原理解析

2.1、认证用户

认证用户,在我们的认证范围中,有一个术语叫Subject,它表示我们在集群中基于某些规则和动作尝试操作
的对象,在k8s集群中定义了两种类型的subject资源:

2.2.1、User Account

这是有外部独立服务进行管理的,对于用户的管理集群内部没有一个关联的资源对
象,所以用户不能通过集群内部的 API 来进行管理。常见的管理方式就是 openssl等

2.2.2、Service Account

通过Kubernetes API 来管理的一些用户帐号,和 namespace 进行关联的,适用于
集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,我们都需要使用到 ServiceAccount

2.2、用户组

在k8s集群中,为了更方便的对某一类用户进行细节化的管理,我们一般会通过用户组的方式来进行管理,常见的用户组有四类

2.2.1、system:unauthenticated

未能通过任何一个授权插件检验的账号的所有未通过认证测试的用户统一隶属的用户组;

2.2.2、system:authenticated

认证成功后的用户自动加入的一个专用组,用于快捷引用所有正常通过认证的用户账号;

2.2.3、system:serviceaccounts

所有名称空间中的所有ServiceAccount对象

2.2.4、system:serviceaccounts: 

特定名称空间内所有的ServiceAccount对象

2.3、插件认证方式

kubernetes提供了多种认证方式,我们可以同时使用一种或多种认证方式,只要通过任何一个都被认作是认证通过。

2.3.1、客户端证书认证

客户端证书认证叫作TLS双向认证,也就是服务器客户端互相验证证书的正确性,在都
正确的情况下协调通信加密方案。,最常见的方式X509数字证书,证书中的Subject中
的 CommonName 或 Orgnization等信息进行校验
对于这种客户的账号,k8s的平台一般是无法管理的。为了使用这个方案,api-server
需要用--client-ca-file、--tls-private-key-file、--tls-cert-file选项来开启。

2.3.2、令牌认证(Token)

在结点数量非常多的时候,大量手动配置TLS认证比较麻烦,我们可以通过在apiserver开启 experimental-bootstrap-token-auth 特性,通过对客户端的和k8s平台预
先定义的token信息进行匹配,认证通过后,自动为结点颁发证书,可以大大减轻我们
的工作压力,而且应用场景非常广。

常见的令牌
在k8s集群中,为了更方便的对某一类用户进行细节化的管理,我们一般会通过用户组的方式来进行管理,常见的用户组有以下四类:
 kubernetes提供了多种认证方式,我们可以同时使用一种或多种认证方式,只要通过任何一个都被认作是认证通过。常见的认证方式如下:
 
引导令牌:
  kubeadm创建集群环境的时候,自动创建好的令牌,用于其他节点加入到集群环境中
 
静态令牌: 
  存储于API Server进程可直接加载到的文件中保存的令牌,该文件内容会由API Server缓存于内存中
  比如我们在使用kubelet的时候,需要依赖的token文件
 
静态密码:
  存储于API Server进程可直接加载到的文件中保存的账户和密码令牌,该文件内容会由API Server缓存于内存中
  比如我们在使用kubelet的时候,需要依赖的.kube/config文件
SA令牌: SA 就是 ServiceAccount,集群内部账号之间操作相关资源的一些认证方式。 OIDC令牌: OIDC 就是 OpenID Connect,一般应用于集成第三方认证的一种集中式认证方式,一般遵循OAuth 2协议。尤其是第三方云服务商的认证。 Webhook令牌: 常应用于触发第三个动作时候的一些认证机制,主要侧重于http协议场景。

2.3.3、代理认证

一般借助于中间代理的方式来进行统用的认证方式,样式不固定

3、授权

3.1、简介

授权主要是在认证的基础上,用于对集群资源的访问控制权限设置,通过检查请求包含的相关属性值,与相对
应的访问策略相比较,API请求必须满足某些策略才能被处理。

3.2、授权方式

对于k8s集群来说,它会根据实际的业务应用场景,采用不同级别的授权方式,有时候也称为授权策略。这些授权策略很多

3.2.1、Node

主要针对节点的基本通信授权认证,比如kubelet的正常通信

3.2.2、ABAC

(Attribute Based Access Control):基于属性的访问控制,在apiserver本地的某一个文件里写入策略规则,如果满足其中一条,就算授权通过。现阶段如果想新
增规则,那么必须重启apiserver,在生产环境中使用几率较小,但未来可能会使用API动态管理。
更多的关于动态属性控制可以参考 OPA(open policy agent)相关资料。

3.2.3、RBAC

(Role Based Access Control):基于角色的访问控制,这是1.6+版本主推的授权策略,可以使用API自定义角色和集群角色,并将角色和特定的用户,用户组,
Service Account关联起来,可以用来实现多租户隔离功能(基于namespace资源)

3.2.4、Webhook

使用第三方授权组件,以 HTTP Callback 的方式,利用外部授权接口对于已有访问控制组件实现权限控制的无缝衔接

3.2.5、AlwaysDeny

此标志阻止所有请求. 仅使用此标志进行测试

3.2.6、AlwaysAllow

此标志允许所有请求. 只有在您不需要API请求授权的情况下才能使用此标志

3.3.7、默认的认证插件

对于k8s集群来说,默认的认证插件有 Node 和 RBAC,其他的都是使用大量的证书来进行的。
master1 ~]# cat /etc/kubernetes/manifests/kube-apiserver.yaml 
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.10.26:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=192.168.10.26
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --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=10.96.0.0/12
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: registry.aliyuncs.com/google_containers/kube-apiserver:v1.26.0
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 192.168.10.26
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-apiserver
    readinessProbe:
      failureThreshold: 3
      httpGet:
        host: 192.168.10.26
        path: /readyz
        port: 6443
        scheme: HTTPS
      periodSeconds: 1
      timeoutSeconds: 15
    resources:
      requests:
        cpu: 250m
    startupProbe:
      failureThreshold: 24
      httpGet:
        host: 192.168.10.26
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/ssl/certs
      name: ca-certs
      readOnly: true
    - mountPath: /etc/pki
      name: etc-pki
      readOnly: true
    - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true
  hostNetwork: true
  priorityClassName: system-node-critical
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  volumes:
  - hostPath:
      path: /etc/ssl/certs
      type: DirectoryOrCreate
    name: ca-certs
  - hostPath:
      path: /etc/pki
      type: DirectoryOrCreate
    name: etc-pki
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs
status: {}
kube-apiserver.yaml

4、准入控制

4.1、简介

准入控制(Admission Control),实际上是一个准入控制器(Admission Controller)插件列表,发送
到APIServer的请求都需要经过这个列表中的每个准入控制器插件的检查,如果某一个控制器插件准入失败,
就准入失败。
它主要涉及到pod和容器对特定用户和特定权限之间的关联关系。

对于k8s来说,他支持的准入控制器有数十种,分别应用于不同的场景功能。一般情况下,k8s的不同版本的
默认准入控制器相差不大。但是其他的功能场景的准入控制器相差还是比较大的,尤其是最近两三年版本中所
支持的准入控制器。

到目前位置,k8s由于其灵活性和云原生的特性,它在安全层面做的不是太完善,每个版本更新的时候,都会
在安全层面上面发现新的问题并进行解决。希望以后会好一点

4.2、常见控制器

4.2.1、LimitRanger

对一些没有做资源限制的pod,赋予一些默认的资源配置属性,主要针对的是pod个体。

4.2.2、ResourceQuota

对一些没有做资源限额的pod,赋予一些默认的资源限额,主要针对的是ns的范围限制。

4.2.3、PSP

PodSecurityPolicy,在集群层面来限制,pod内部使用的某些用户特权级别资源。

5、SA认证-解析

5.1、SA简介

K8S自动为每个Pod注入一个 ServiceAccount 及 配套的令牌
在每个名称空间中,会自动存在(由ServiceAccount准入控制器负责)一个ServiceAccount,将被该空间下的每个Pod共享使用。
认证令牌保存于该空间下的一个Secret对象中,该对象中共有三个信息:namespace、ca.crt、token

5.2、认证示例

5.2.1、样式1-Secret

Volumes:
  ...
  default-token-9vs4d:
    Type:       Secret (a volume populated by a Secret)
    SecretName: default-token-9vs4d
    Optional:   false
这里的SecretName就是一个认证信息,来源于我们创建k8s的时候,默认创建的一个secret
]# kubectl get secrets NAME                   TYPE                                 DATA   AGE default
-token-9vs4d   kubernetes.io/service-account-token   3     3d # 这是k8s提供的一种认证模式,只不过默认的认证权限不是太大。         token是有限期限的,如果过期的话,token就没有了,那么就会出现第二种方式。

5.2.2、样式2-Projected

Volumes:
  kube-api-access-hvv4j:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true

5.3、资源属性

apiVersion: v1       # ServiceAccount所属的API群组及版本
kind: ServiceAccount # 资源类型标识
metadata:
  name <string>                        # 资源名称
  namespace <string>                   # ServiceAccount是名称空间级别的资源
automountServiceAccountToken <boolean> # 是否让Pod自动挂载API令牌
secrets <[]Object>          # 以该SA运行的Pod所要使用的Secret对象组成的列表
  apiVersion <string>       # 引用的Secret对象所属的API群组及版本,可省略
  kind <string>             # 引用的资源的类型,这里是指Secret,可省略
  name <string>             # 引用的Secret对象的名称,通常仅给出该字段即可
  namespace <string>        # 引用的Secret对象所属的名称空间
  uid <string>              # 引用的Secret对象的标识符;
imagePullSecrets <[]Object> # 引用的用于下载Pod中容器镜像的Secret对象列表
  name <string>             # docker-registry类型的Secret资源的名称

6、ServiceAccount-实践

6.1、查看空间的信息

6.1.1、查看普通用户空间的信息

master1 ]# kubectl get sa
NAME      SECRETS   AGE
default   0         13d

master1 ]# kubectl get serviceaccounts 
NAME      SECRETS   AGE
default   0         13d

注意:
    每个命名空间都会有一个默认的default的sa账号名称

6.1.2、查看kube-system的sa信息

master1 ~]# kubectl -n kube-system get sa | grep -v SECRETS | wc -l
36

可以看到:在kube-system的namespace中sa的条目多大31条

6.1.3、sa属性查看

master1 ~]# kubectl get sa default -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2023-03-16T12:07:15Z"
  name: default
  namespace: default
  resourceVersion: "304"
  uid: ee1771e2-a8ba-42f9-82eb-dd35c78e52cf

结果显示:
对于一个serviceaccount来说,其属性信息在metadata部分,在这里比较重要的是对于sa的信息交流还是需要通过secrets的认证信息进行通信,
而这个认证信息它是自动帮我们附加的,我们无需关心。

6.1.4、secrets信息查看

master1 ~]# kubectl get secrets k8s-auth -o yaml
apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyIxOTIuMTY4LjEwLjIyOjgwIjp7InVzZXJuYW1lIjoiY3ljIiwicGFzc3dvcmQiOiJBYTEyMzQ1NiIsImF1dGgiOiJZM2xqT2tGaE1USXpORFUyIn19fQ==
kind: Secret
metadata:
  creationTimestamp: "2023-03-16T16:47:27Z"
  name: k8s-auth
  namespace: default
  resourceVersion: "32313"
  uid: d7640f43-1f8c-4f67-9fcd-82f56ecc7203
type: kubernetes.io/dockerconfigjson

这个secrets是一个dockerconfigjson类型的

6.2、创建sa账号

6.2.1、创建sa命令

命令格式:
    kubectl create serviceaccount NAME [--dry-run] [options]

作用:创建一个"服务账号"
参数详解
    --dry-run=false 模拟创建模式
    --generator='serviceaccount/v1' 设定api版本信息
    -o, --output='' 设定输出信息格式,常见的有:json|yaml|name|template|...
    --save-config=false 保存配置信息
    --template='' 设定配置模板文件

6.2.2、打印出创建sa yaml格式

master1 ~]# kubectl create serviceaccount mysa --dry-run=client -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: null
  name: mysa

6.2.3、创建一个sa并且关联token

# 创建sa帐号admin
master1 ~]# kubectl create serviceaccount admin
serviceaccount/admin created

master1 ~]# kubectl get sa
NAME      SECRETS   AGE
admin     0         3s      # 生成了一个sa账号
default   0         13d

# 创建toke,给sa关联token
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: build-admin-secret
  annotations:
    kubernetes.io/service-account.name: admin
type: kubernetes.io/service-account-token
EOF

master1 ]# kubectl describe sa admin 
Name:                admin
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>              # 下载镜像时候的认证信息
Mountable secrets:   <none>              # 挂载数据时候的认证信息
Tokens:              build-admin-secret  # 原始的账户认证信息
Events:              <none>

6.3、给sa增加 Image pull secrets

6.3.1、创建docker登陆的帐号信息

# 创建docker登陆的帐号信息
kubectl create secret docker-registry myregistrykey --docker-server=192.168.10.88:80 \
--docker-username=cyc \
--docker-password=12345678 \
--docker-email=test@qq.com

6.3.2、将docker登陆的帐号信息增加至sa

kubectl patch serviceaccount admin -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'

6.3.3、检查是否增加成功

master1 ~]# kubectl describe serviceaccounts admin 
Name:                admin
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  myregistrykey
Mountable secrets:   <none>
Tokens:              build-admin-secret
Events:              <none>

6.4、将pod关联创建的sa帐号-实践

6.4.1、定义资源配置清单

cat > security-sa-admin.yml <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin
---
apiVersion: v1
kind: Secret
metadata:
  name: build-admin-secret
  annotations:
    kubernetes.io/service-account.name: admin
type: kubernetes.io/service-account-token
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-sa-admin
spec:
  containers:
  - name: pod-sa-admin
    image: 192.168.10.33:80/k8s/my_nginx:v1
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /var/run/secrets/tokens
      name: vol-admin-secret
  serviceAccountName: admin
  volumes:
  - name: vol-admin-secret
    secret:
      secretName: build-admin-secret
EOF

6.4.2、应用资源配置清单

kubectl apply -f security-sa-admin.yml

master1 ]# kubectl get secrets 
NAME                 TYPE                                  DATA   AGE
build-admin-secret   kubernetes.io/service-account-token   3      8m22s
myregistrykey        kubernetes.io/dockerconfigjson        1      25m

master1 ]# kubectl get sa
NAME      SECRETS   AGE
admin     0         8m26s

master1 ]# kubectl describe secrets build-admin-secret 
...
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6I...

6.4.3、检查pod里面secrets挂载是否正常

master1 ~]# kubectl exec -it pod-sa-admin --  cat /var/run/secrets/tokens/token
eyJhbGciOiJSUzI1NiIsImtpZCI6I...

# token一致,表示挂载成功