CKS认证 | Day4 最小化微服务漏洞

发布于:2025-07-09 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、Pod安全上下文

安全上下文(Security Context):K8s对Pod和容器提供的安全机制,可以设置Pod特权和访问控制。
安全上下文限制维度:

  • 自主访问控制(Discretionary Access Control):基于用户ID(UID)和组ID(GID),来判定对对象(例如文件) 的访问权限;
  • 安全性增强的 Linux(SELinux): 为对象赋予安全性标签;
  • privileged:以 特权模式 或者 非特权模式 运行;
  • Linux Capabilities:为进程赋予 root 用户的部分特权而非全部特权;
  • AppArmor:定义Pod使用AppArmor限制容器对资源访问限制;
  • Seccomp:定义Pod使用Seccomp限制容器进程的系统调用;
  • AllowPrivilegeEscalation: 禁止容器中进程(通过 SetUID 或 SetGID 文件模式)获得特权提升。当容器以特权模式 运行或者具有CAP_SYS_ADMIN能力时,AllowPrivilegeEscalation总为True;
  • readOnlyRootFilesystem:以只读方式加载容器的根文件系统;

配置说明:

spec:
  securityContext:            # Pod级别的安全上下文,对内部所有容器均有效
    runAsUser <integer>       # 以指定的用户身份运行容器进程,默认由镜像中的USER指定
    runAsGroup <integer>      # 以指定的用户组运行容器进程,默认使用的组随容器运行时
    fsGroup <integer>         # 数据卷挂载后的目录文件设置为该组
    runAsNonRoot <boolean>    # 是否以非root身份运行
    seLinuxOptions <Object>   # SELinux的相关配置
    sysctls <[]Object>        # 应用到当前Pod上的名称空间级别的sysctl参数设置列表
  containers:
  - name: xxx
    image: xxx
    securityContext:           # 容器级别的安全上下文,仅生效于当前容器
      runAsUser <integer>      # 以指定的用户身份运行容器进程
      runAsGroup <integer>     # 以指定的用户组运行容器进程
      runAsNonRoot <boolean>   # 是否以非root身份运行
      allowPrivilegeEscalation <boolean>     # 是否允许特权升级
      capabilities <Object>    # 于当前容器上添加(add)或删除(drop)的内核能力
        add <[]string>         # 添加由列表定义的各内核能力
        drop <[]string>        # 移除由列表定义的各内核能力
      privileged <boolean>     # 是否运行为特权容器
      readOnlyRootFilesystem <boolean>   # 是否将根文件系统设置为只读模式
      seLinuxOptions <Object>  # SELinux的相关配置

 

1.1 设置容器以普通用户运行

背景:容器中的应用程序默认以root账号运行的,这个root与宿主机root账号是相同的, 拥有大部分对Linux内核的系统调用权限,这样是不安全的,所以我们应该将容器以普 通用户运行,减少应用程序对权限的使用。

可以通过两种方法设置普通用户:
1.Dockerfile里使USER指定运行用户

FROM python

RUN useradd python

RUN mkdir /data/www -p

COPY . /data/www

RUN chown -R python /data

RUN pip install flask -i https://mirrors.aliyun.com/pypi/simple/

WORKDIR /data/www

USER python # 指定运行用户(按照构建顺序,之后的命令都将是以普通python用户运行)

CMD python main.py

2.K8s的yaml里指定spec.securityContext.runAsUser,指定容器默认用户UID

spec:

  securityContext:

    runAsUser: 1000 # 镜像里必须有这个用户UID

    fsGroup: 1000 # 数据卷挂载后的目录属组设置为该组

containers:

  - image: lizhenliang/flask-demo:root

    name: web

    securityContext:

       allowPrivilegeEscalation: false # 不允许提权

1.1.1 Dockerfile里使用USER指定运行用户

实验案例:

1)解压压缩包

[root@k8s-master-1-71 ~]# unzip flask-demo.zip
[root@k8s-master-1-71 ~]# cd flask-demo ; ls
Dockerfile  main.py  templates

 2)查看Dockerfile文件(通过dockerfile文件编写构建web应用实例)

通过Dockerfile实现Python容器化构建镜像的第一步,使用python官方镜像安装一个flask模块,再将当前的网站程序拷贝到镜像里

[root@k8s-master-1-71 flask-demo]# cat Dockerfile
FROM python              # 引用python官方镜像
RUN useradd python       # 使用默认root账户创建普通用户
RUN mkdir /data/www -p   # 创建网站根目录
COPY . /data/www         # 将当前flask-demo目录下的文件拷贝网站根目录
RUN chown -R python /data   # 设置所属组
RUN pip install flask -i https://mirrors.aliyun.com/pypi/simple/   # 安装flask模块
WORKDIR /data/www        # 设置默认的工作目录
USER python              # 指定运行用户(按照构建顺序,之后的命令都将是以普通python用户运行)
CMD python main.py       # 设置CMD启动该服务作为容器第一个进程

3)查看main.py文件(网站主程序)

[root@k8s-master-1-71 flask-demo]# cat main.py
from flask import Flask,render_template      # 导入了flask框架、和渲染模板
的函数

app = Flask(__name__)    # 做一个Flask的web应用实例

@app.route('/')    # 定义路由访问首页,映射到对应Index函数中
def index():
    return render_template("index.html")   # Index函数是以渲染后的HTML模板文件内容进行响应

if __name__ == "__main__":
    app.run(host="0.0.0.0",port=8080)  # 监听所有的IP地址及8080端口通信

4)查看templates/index.html文件(网站模板文件)

[root@k8s-master-1-71 flask-demo]# cat templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>

<h1>Hello Python!</h1>

</body>
</html>

5)构建docker镜像,并允许容器

[root@k8s-master-1-71 flask-demo]# docker build -t flask-demo:v1 .
[root@k8s-master-1-71 flask-demo]# docker run -d --name=demo -p 80:8080 flask-demo:v1

# 测试:容器的程序都是用python用户而非root用户运行
[root@k8s-master-1-71 flask-demo]# docker exec -it demo bash
python@577f69610117:/data/www$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
python        1      0  0 13:08 ?        00:00:00 /bin/sh -c python main.py
python        7      1  2 13:08 ?        00:00:01 python main.py
python        8      0  3 13:08 pts/0    00:00:00 bash
python       14      8  0 13:08 pts/0    00:00:00 ps -ef

# 容器与宿主机共享内核,所以在容器启动后,会有一个进程通过普通用户运行
[root@k8s-master-1-71 ~]# ps -ef |grep python
root        984      1  0 20:10 ?        00:00:02 /usr/bin/python2 -Es /usr/sbin/tuned -l -P
support  104099 104079  0 21:08 ?        00:00:00 /bin/sh -c python main.py
support  104205 104099  0 21:08 ?        00:00:01 python main.py
root     108386 107460  0 21:10 pts/1    00:00:00 grep --color=auto python

1.1.2 K8s里指定spec.securityContext.runAsUser,指定容器默认用户UID

实验案例:

1)配置安全上下文

[root@k8s-master-1-71 ~]# kubectl apply -f flask-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: flack-demo
  name: flack-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flack-demo
  template:
    metadata:
      labels:
        app: flack-demo
    spec:
      securityContext:     # 配置安全上下文
        runAsUser: 1000    # 镜像里必须有这个用户UID
        fsGroup: 1000      # 数据卷挂载后的目录属组设置为该组
      containers:
      - image: lizhenliang/flask-demo:root
        name: flask-demo

# 测试:容器的程序都是用python用户而非root用户运行
[root@k8s-master-1-71 ~]# kubectl get pods
NAME                         READY   STATUS    RESTARTS       AGE
flack-demo-f78575dc8-dlq7b   1/1     Running   0              2m29s

[root@k8s-master-1-71 ~]# kubectl exec -it flack-demo-f78575dc8-dlq7b -- bash
python@flack-demo-f78575dc8-dlq7b:/data/www$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
python        1      0  0 14:00 ?        00:00:00 /bin/sh -c python main.py
python        7      1  0 14:00 ?        00:00:00 python main.py
python        8      0  0 14:01 pts/0    00:00:00 bash
python       14      8  0 14:01 pts/0    00:00:00 ps -ef

注意:如果宿主机已经有1000 用户了,如果构建的docker镜像用的也是1000 可能会有冲突

1.2 k8s里启用特权容器(避免使用)

背景:容器中有些应用程序可能需要访问宿主机设备、修改内核等需求,在默认情况下, 容器没这个有这个能力,因此这时会考虑给容器设置特权模式。

启用特权模式:(只能启用于容器级别)

containers:

  - image: busybox

    command:

      - sleep

      - 24h

    name: web

    securityContext:

       privileged: true

注意:启用特权模式就意味着要为容器提供了访问Linux内核的所有能力,这是很危险的, 为了减少系统调用的供给,可以使用Capabilities为容器赋予仅所需的能力。

案例:

# 非特权模式下挂载目录(普通Pod容器)
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bs-demo
  name: bs-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo
  template:
    metadata:
      labels:
        app: bs-demo
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        
# 测试1:非特权模式下,无法进行mount挂载
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-59d897b956-7nrt4 -- sh
/ # mount -t tmpfs /tmp/ /tmp/
mount: permission denied (are you root?)    # 权限拒绝
# 特权模式下挂载目录
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo-pri.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bs-demo-pri
  name: bs-demo-pri
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo-pri
  template:
    metadata:
      labels:
        app: bs-demo-pri
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        securityContext:        # 添加安全上下文
          privileged: true      # 开启特权模式

# 测试2:特权模式下,可以进行mount挂载
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-pri-747c9976f7-jr9fn -- sh
/ # mount -t tmpfs /tmp/ /tmp/
/ #        # 权限通过

1.3 Linux Capabilities

Capabilities 是一个内核级别的权限,它允许对内核调用权 限进行更细粒度的控制,而不是简单地以 root 身份能力授权。

包括更改文件权限、控制网络子系统和执行系统管理等功能。在 securityContext 中,可以添加或删除 Capabilities,做到容器精细化权限控制。

Capabilities 名称

描述

CAP_AUDIT_CONTROL

启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则

CAP_AUDIT_READ

允许通过multicast netlink 套接字读取审计日志

CAP_AUDIT_WRITE

将记录写入内核审计日志

CAP_BLOCK_SUSPEND

使用可以阻止系统挂起的特性

CAP_CHOWN

修改文件所有者的权限

CAP_DAC_OVERRIDE

忽略文件的 DAC访问限制

CAP_DAC_READ_SEARCH

忽略文件读及目录搜索的 DAC 访问限制

CAP_FOWNER

忽略文件属主ID必须和进程用户ID相匹配的限制

CAP_FSETID

允许设置文件的 setuid 位

CAP_IPC_LOCK

允许锁定共享内存片段

CAP_IPC_OWNER

忽略IPC所有权检查

CAP_KILL

允许对不属于自己的进程发送信号

CAP_LEASE

允许修改文件锁的FLLEASE标志

CAP_LINUX_IMMUTABLE

允许修改文件的IMMUTABLE和APPEND 属性标志

CAP_MAC_ADMIN

允许MAC配置或状态更改

CAP_MAC_OVERRIDE

覆盖MAC(Mandatory AccessControl)

CAP_MKNOD

允许使用mknod0)系统调用

CAP_NET_ADMIN

允许执行网络管理任务: 接口、防火墙和路由等

CAP_NET_BIND_SERVICE

允许绑定到小于1024的端口

CAP_NET_BROADCAST

允许网络广播和多播访问

CAP_NET_RAW

允许使用原始套接字

CAP_SETGID

允许改变进程的 GID

CAP SETFCAP

允许为文件设置任意的capabilities

CAP_SETPCAP

允许向其它进程转移能力以及删除其它进程的任意能力(只限init进程)

CAP_SETUID

允许改变进程的UID

CAP_SYS_ADMIN

允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等

CAP_SYS_BOOT

允许重新启动系统

CAP_SYS_CHROOT

允许使用chroot()系统调用

CAP_SYS_MODULE

允许插入和删除内核模块

CAP_SYS_NICE

允许提升优先级及设置其他进程的优先级

CAP_SYS_PACCT

允许执行进程的BSD式审计

CAP_SYS_PTRACE

允许跟踪任何进程

CAP_SYS_RAWIO

允许直接访问/devport/dev/mem 、/dev/kmem及原始块设备

CAP_SYS_RESOURCE

忽略资源限制

CAP_SYS_TIME

允许改变系统时钟

CAP_SYS_TTY_CONFIG

允许配置TTY 设备

CAP_SYSLOG

允许使用 syslog0系统调用

CAP_WAKE_ALARM

允许触发一些能唤醒系统的东西(比如CLOCK BOOTTIME ALARM 计时器)

通过capsh查看已有绑定的Capabilities:capsh --print

# 系统用户root,查看已有绑定的Capabilities
root@flack-demo-7ccb4cb9f6-ghngb:/data/www# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
# 普通用户python,查看已有绑定的Capabilities
python@flack-demo-f78575dc8-dlq7b:/data/www$ capsh --print
Current: =
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=1000(python)
gid=1000(python)
groups=1000(python)

普通用户相比root用户会少一些Capabilities,只会开放一部分基本的权限控制进行使用。

1.3.1 容器默认没有挂载文件系统能力,添加SYS_ADMIN增加这个能力

由于启用特权模式给容器提供访问Linux内核的所有能力, 为了避免危险性,使用Capabilities为容器赋予仅所需的部分root能力(细粒度控制)。

spec:

  containers:

  - image: busybox

    name: test

    command:

    - sleep

    - 24h

    securityContext:

       capabilities:

          add: ["SYS_ADMIN"]

案例:

[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bs-demo
  name: bs-demo-cap
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo
  template:
    metadata:
      labels:
        app: bs-demo
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        securityContext:           # 添加安全上下文
          capabilities:            # 添加capabilities
            add: ["SYS_ADMIN"]     # 添加"SYS_ADMIN"权限


# 测试:# 权限通过,可以进行mount挂载
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-cap-6646c8d695-9879c -- sh
/ # mount -t tmpfs /tmp/ /tmp/

add 是添加权限,drop 是删除权限

1.3.2 只读挂载容器文件系统,防止恶意二进制文件创建

spec:

  containers:

  - image: busybox

    name: test

    command:

    - sleep

    - 24h

    securityContext:

       readOnlyRootFilesystem: true

案例:

[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo-2.yaml
kind: Deployment
metadata:
  labels:
    app: bs-demo
  name: bs-demo-read
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo
  template:
    metadata:
      labels:
        app: bs-demo
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        securityContext:                   # 添加安全上下文
          readOnlyRootFilesystem: true     # 开启只读挂载容器文件系统功能


# 测试:开启只读系统,创建文件失败
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-read-564bb8c497-vhjqh -- sh
/ # touch aa
touch: aa: Read-only file system

二、Pod安全策略(PSP)

PodSecurityPolicy(简称PSP)Pod 安全策略,是集群级别的资源,是Kubernetes中Pod部署时重要的安全校验手段,它首先定义角色,规定了对 Pod 行为的限制,其中包括对特 权容器、主机网络、Capability、加载卷类型等内容进行了限制,然后通过 RBAC 把 SA-Pod-PSP 三者结合起来,完成对 Pod 权限的限制。注意:PSP 在 1.21 废弃,1.25 版本删除。

它能够 有效地约束应用运行时行为安全。 使用PSP对象定义一组Pod在运行时必须遵循的条件及相关字段的默认值,只有Pod满足这 些条件才会被K8s接受。

Pod 安全策略由设置和策略组成,它们能够控制 Pod 访问的安全特征。这些设置分为如下三类:

  • 基于布尔值控制 :这种类型的字段默认为最严格限制的值。
  • 基于被允许的值集合控制 :这种类型的字段会与这组值进行对比,以确认值被允许。
  • 基于策略控制 :设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允 许的这组值中。

Pod安全策略限制维度:

1、RunAsUser

  • 1)MustRunAs - 必须配置一个 range。使用该范围内的第一个值作为默认值。验证是否不在配置 的该范围内。
  • 2)MustRunAsNonRoot - 要求提交的 Pod 具有非零 runAsUser 值,或在镜像中定义 了 USER 环境变量。不提供默认值。
  • 3)RunAsAny - 没有提供默认值。允许指定任何 runAsUser 。

2、SELinux

  • 1)MustRunAs - 如果没有使用预分配的值,必须配置 seLinuxOptions。默认使 用 seLinuxOptions验证seLinuxOptions。
  • 2)RunAsAny - 没有提供默认值。允许任意指定的 seLinuxOptions ID。

3、SupplementalGroups

  • 1)MustRunAs - 至少需要指定一个范围。默认使用第一个范围的最小值。验证所有范围的值。
  • 2)RunAsAny - 没有提供默认值。允许任意指定的 supplementalGroups ID。

4、FSGroup

  • 1)MustRunAs - 至少需要指定一个范围。默认使用第一个范围的最小值。验证在第一个范围内的 第一个 ID。
  • 2)RunAsAny - 没有提供默认值。允许任意指定的 fsGroup ID。

注意:Pod安全策略实现为一个准入控制器,默认没有启用,当启用后会强制实施 Pod安全策略,没有满足的Pod将无法创建。因此,建议在启用PSP之前先添加 策略并对其授权。

启用Pod安全策略:

vi /etc/kubernetes/manifests/kube-apiserver.yaml
...
- --enable-admission-plugins=NodeRestriction,PodSecurityPolicy
...
systemctl restart kubelet

用户使用SA (ServiceAccount)创建了一个Pod,K8s会先验证这个SA是否 可以访问PSP资源权限,如果可以进一步验证Pod配置是否满足PSP规则,任 意一步不满足都会拒绝部署。 因此,需要实施需要有这几步骤:

  1. 创建SA服务账号
  2. 该SA需要具备创建对应资源权限,例如创建Pod、Deployment
  3. SA使用PSP资源权限:创建Role,使用PSP资源权限,再将SA绑定Role

PSP工作流程:

补充:PSP不足与状况

① 将再1.21版本弃用PSP,在1.25版本删除PSP;

② 仅支持Pod策略,不支持Deployment等;

③ 使用复杂,权限模型存在缺陷,控制不明确;

弃用文章:PodSecurityPolicy Deprecation: Past, Present, and Future | Kubernetes

替代提案:https://github.com/kubernetes/enhancements/issues/2579

2.1 示例1:禁止创建特权模式的Pod

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp-example
spec:
  privileged: false    # 不允许特权Pod
  # 下面是一些必要的字段
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny 
  fsGroup:
    rule: RunAsAny
  volumes:
  - '*'
# 创建SA
kubectl create serviceaccount aliang

# 将SA绑定到系统内置Role
kubectl create rolebinding aliang --clusterrole=edit --serviceaccount=default:aliang

# 创建使用PSP权限的Role
kubectl create role psp:unprivileged --verb=use --resource=podsecuritypolicy --resource-name=psp-example

# 将SA绑定到Role
kubectl create rolebinding aliang:psp:unprivileged --role=psp:unprivileged --serviceaccount=default:aliang

2.2 示例2:禁止没指定普通用户运行的容器(runAsUser)

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp-example
spec:
  privileged: false   # 不允许特权Pod
  # 下面是一些必要的字段
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: MustRunAsNonRoot     //是否以非root身份运行
  fsGroup:
    rule: RunAsAny
  volumes:
  - '*'

三、OPA Gatekeeper

OPA(Open Policy Agent):是一个开源的、通用策略引擎,可以将策略编写为代码。提供一种高级声明性语言-Rego来编写策略,并把决策这一步骤从复杂的业务逻辑中解耦出来。

OPA可以用来做什么?
- 拒绝不符合条件的YAML部署
- 允许使用哪些仓库中的镜像
- 允许在哪个时间段访问系统
- 等...

Gatekeeper 是基于 OPA的一个 Kubernetes 策略解决方案,可替代PSP或者部分RBAC功能。

• OPA官网:Open Policy Agent - Homepage | Open Policy Agent

• Gatekeeper项目:https://github.com/open-policy-agent/gatekeeper

• Gatekeeper文档:How to use Gatekeeper | Gatekeeper

当在集群中部署了Gatekeeper组件,APIServer所有的创建、更新或者删除操作都会触发 Gatekeeper来处理,如果不满足策略则拒绝。

工作流程图:

部署Gatekeeper:kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

# 查看相关信息
[root@k8s-master-1-71 ~]# kubectl get pods -n gatekeeper-system
NAME                                             READY   STATUS    RESTARTS       AGE
gatekeeper-audit-7fb8f4c95c-fvmwc                1/1     Running   2 (2m6s ago)   6m8s
gatekeeper-controller-manager-7db564f9f4-j49g5   1/1     Running   0              6m8s
gatekeeper-controller-manager-7db564f9f4-rmq9d   1/1     Running   0              6m8s
gatekeeper-controller-manager-7db564f9f4-sxmzz   1/1     Running   0              6m8s

[root@k8s-master-1-71 ~]# kubectl get svc -n gatekeeper-system
NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
gatekeeper-webhook-service   ClusterIP   10.111.124.211   <none>        443/TCP   5m33s

[root@k8s-master-1-71 ~]# kubectl get sa -n gatekeeper-system
NAME               SECRETS   AGE
default            0         5m52s
gatekeeper-admin   0         5m51s

Gatekeeper的策略由两个资源对象组成:

  1. Template:策略逻辑实现的地方,使用rego语言(具体对资源做什么操作)
  2. Contsraint:负责Kubernetes资源对象的过滤或者为Template提供输入参数(过滤哪些资源)

补充:先部署模板,再部署约束

3.1 案例1:禁止容器启用特权

1)模板:

查看资源命令 :kubectl get ConstraintTemplate

[root@k8s-master-1-71 ~]# kubectl apply -f test-ConstraintTemplate.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: privileged 
spec:
  crd:
    spec:
      names:
        kind: privileged
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package admission    # 导入准入控制包
        violation[{"msg": msg}] {     # 如果violation为true(表达式通过)说明违反约束
          containers = input.review.object.spec.template.spec.containers
          c_name := containers[0].name    # 获取第一个容器的名称
          containers[0].securityContext.privileged      # 检查是否有启用特权模式。如果返回true,说明违反约束
          msg := sprintf("提示:'%v'容器禁止启用特权!",[c_name])
        }

解释:violation为回调函数,当violation表达式返回值为true,说明违反约束;相反,当violation表达式返回值为false,说明符合约束;

判断表达式:检查是否有启用特权模式。如果返回true,说明违反约束

2)约束:

查看资源 命令 :kubectl get constraints

[root@k8s-master-1-71 ~]# kubectl apply -f test-constraints.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: privileged
metadata:
  name: privileged
spec:
  match:   # 匹配的资源
    kinds:
      - apiGroups: ["apps"]
        kinds:
        - "Deployment"
        - "DaemonSet"
        - "StatefulSet"

测试:

# 查看 ConstraintTemplate 模板
[root@k8s-master-1-71 ~]# kubectl get ConstraintTemplate
NAME         AGE
privileged   103s
# 查看 constraints 约束
[root@k8s-master-1-71 ~]# kubectl get constraints
NAME         ENFORCEMENT-ACTION   TOTAL-VIOLATIONS
privileged                        2

--------------------------------------------------------------------------------
# 部署启用特权模式的Pod
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo-pri.yaml
Error from server (Forbidden): error when creating "bs-demo-pri.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [privileged] 提示:'busybox'容器禁止启用特权!

删除模板/约束命令:

  • kubectl delete constraints <约束名>
  • kubectl delete ConstraintTemplate <模板名>

 

3.2 案例2:只允许使用特定的镜像仓库

1)模板:

[root@k8s-master-1-71 ~]# kubectl apply -f test1-ConstraintTemplate.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: image-check
spec:
  crd:
    spec:
      names:
        kind: image-check
      validation:
        openAPIV3Schema: 
          properties:     # 需要满足条件的参数
            prefix:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package image
        violation[{"msg": msg}] { 
          containers = input.review.object.spec.template.spec.containers
          image := containers[0].image
          not startswith(image, input.parameters.prefix)     # 镜像地址开头不匹配并取反则为true,说明违反约束
          msg := sprintf("提示:'%v'镜像地址不在可信任仓库!", [image])
        }

解释:violation为回调函数,当violation表达式返回值为true,说明违反约束;相反,当violation表达式返回值为false,说明符合约束

判断表达式:镜像地址不匹配以“lizhenliang/”开头的镜像,返回true,说明违反约束

2)约束:

[root@k8s-master-1-71 ~]# kubectl apply -f test1-constraints.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: image-check
metadata:
  name: image-check
spec:
  match:
    kinds:
      - apiGroups: ["apps"] 
        kinds:
        - "Deployment"
        - "DaemonSet"
        - "StatefulSet"
  parameters:          # 传递给opa的参数(允许的镜像仓库前缀)
    prefix: "lizhenliang/"

测试:

# 查看 ConstraintTemplate 模板
[root@k8s-master-1-71 ~]# kubectl get ConstraintTemplate
NAME          AGE
image-check   5m26s
# 查看 constraints 约束
[root@k8s-master-1-71 ~]# kubectl get constraints
NAME          ENFORCEMENT-ACTION   TOTAL-VIOLATIONS
image-check

--------------------------------------------------------------------------------
# 部署Pod指定非lizhenliang/开头的镜像,报错:镜像地址不在可信任仓库!
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=nginx
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [image-check] 提示:'nginx'镜像地址不在可信任仓库!
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=192.168.1.10/nginx:v1
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [image-check] 提示:'192.168.1.10/nginx:v1'镜像地址不在可信任仓库!

# 部署Pod指定lizhenliang/开头的镜像,成功创建
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=lizhenliang/java-demo
deployment.apps/web created

四、Secret存储敏感数据

Secret是一个用于存储敏感数据的资源,所有的数据要经过base64编码,数据实际会存储在K8s中Etcd, 然后通过创建Pod时引用该数据。应用场景:凭据

1、Pod使用secret数据有两种方式:

  • 变量注入
  • 数据卷挂载

2、kubectl create secret 支持三种数据类型:

1)docker-registry:存储镜像仓库认证信息(镜像仓库需要账户密码认证)

2)generic:存储用户名、密码:

  • 例如:kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
  • 例如:kubectl create secret generic my-secret --from-literal=username=produser --from-literal=password=123456

3)tls:存储证书:kubectl create secret tls --cert=path/to/tls.cert --key=path/to/tls.key

  • 例如  使用ingress - tls 部署https

3、相关命令:

查看Secret:kubectl get secret
相关base64命令:

# echo -n 'admin' | base64           //加密(YWRtaW4=)
# echo YWRtaW4= | base64 -d   //解密(admin)

补充: 在Secret 配置文件中未作显式设定时,默认的 Secret 类型是 Opaque

命令示例:

[root@k8s-master ~]# kubectl create secret generic mysql-secret --from-literal=username=admin --from-literal=password=123456

示例:将Mysql用户密码保存到Secret中存储

TYPE类型参考:Secrets | Kubernetes

[root@k8s-master-1-71 ~]# kubectl apply -f mysql-secret-pod.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque    # 默认的 Secret 类型是 Opaque
data:
  mysql-root-password: "MTIzNDU2"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: db
        image: mysql:5.7.30
        env:
        - name: MYSQL_ROOT_PASSWORD     # 为Mysql预先定义变量
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: mysql-root-password

测试:

[root@k8s-master-1-71 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS      AGE
mysql-f958d8ddb-k69cp       1/1     Running   0             74s

[root@k8s-master-1-71 ~]# kubectl exec -it mysql-f958d8ddb-k69cp -- bash
root@mysql-f958d8ddb-k69cp:/# env |grep PASSWORD
MYSQL_ROOT_PASSWORD=123456

补充:容器镜像对变量的使用方式:

① 容器里的程序读取系统变量(例如Python的OS模块)

② 容器启动后脚本(entrypoint)获取变量

当构建docker镜像时,配置entrypoint之后,CMD将作为这个执行脚本的参数传递给它,没有配置entrypoint则默认允许CMD执行的程序

# docker run -d nginx ls

五、安全沙箱运行容器

所知,容器的应用程序可以直接访问Linux内核的系统调用,容器在安全隔离上还是比较弱,虽然 内核在不断地增强自身的安全特性,但由于内核自身代码极端复杂,CVE 漏洞层出不穷。 所以要想减少这方面安全风险,就是做好安全隔离,阻断容器内程序对物理机内核的依赖。

Google开源的一种gVisor容器沙箱技术就是采用这种思路,gVisor隔离容器内应用和内核之间访 问,提供了大部分Linux内核的系统调用,巧妙的将容器内进程的系统调用转化为对gVisor的访问。gVisor兼容OCI,与Docker和K8s无缝集成,很方便使用。

项目地址:https://github.com/google/gvisor

— 容器中应用程序直接与Linux内核交互(未使用gVisor)

— 容器中应用程序需要通过gVisor才能使用资源(使用gVisor)

补充:类似安全防火墙,做一层过滤,对不安全的系统调用进行隔离

缺点:安全性提升,但性能性下降5%左右

gVisor架构图:

gVisor 由 3 个组件构成:

- Runsc 是一种 Runtime 引擎,负责容器的创建与销毁;
- Sentry 负责容器内程序的系统调用处理;
- Gofer 负责文件系统的操作代理,IO 请求都会由它转接 到 Host 上;

5.2 gVisor与Docker集成(在k8s-node2测试)

gVisor内核要求:Linux 3.17+

注意:如果用的是CentOS7则需要升级内核,Ubuntu不需要。

已测试过可兼容的应用和工具:https://gvisor.dev/docs/user_guide/compatibility/

# 查看有哪些内核版本可供安装
yum  --disablerepo="*"  --enablerepo="elrepo-kernel"  list  available

# 载入ELRepo仓库的公共密钥
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
# 安装ELRepo仓库的yum源
rpm -Uvh http://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm

# 长期维护版本kernel-lt  如需更新最新稳定版选择kernel-ml(较为激进)
yum --enablerepo=elrepo-kernel install kernel-ml-devel kernel-ml –y

# 查看可用内核版本及启动顺序
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg

# 设置内核默认启动顺序
grub2-set-default 0

# 运行grub2-mkconfig命令来重新创建内核配置
grub2-mkconfig -o /boot/grub2/grub.cfg

# 重启并查看内核版本
reboot
uname -r

# 查看系统中已安装的内核
rpm -qa | grep kernel

1)准备gVisor二进制文件

参考:https://gvisor.dev/docs/user_guide/install/

要手动下载并安装最新版本,请执行以下步骤:

[root@k8s-node2-1-73 ~]# (
  set -e
  ARCH=$(uname -m)
  URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
  wget ${URL}/runsc ${URL}/runsc.sha512 \
    ${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
  sha512sum -c runsc.sha512 \
    -c containerd-shim-runsc-v1.sha512
  rm -f *.sha512
  chmod a+rx runsc containerd-shim-runsc-v1
  sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)

注意runsc:复制到所有用户都可读和可执行的位置很重要,因为runsc它会以用户身份执行nobody,以避免不必要的特权。该/usr/local/bin目录是放置runsc二进制文件的好地方。

2)Docker配置使用gVisor

[root@k8s-node2-1-73 ~]#  /usr/local/bin/runsc install
2023/06/02 22:08:08 Clobber is set. Overwriting runtime runsc not found: adding
2023/06/02 22:08:08 Successfully updated config.

# 重启docker生效
[root@k8s-node2-1-73 ~]# systemctl restart docker

#
 查看docker信息
[root@k8s-node2-1-73 ~]# docker info
...
 Runtimes: io.containerd.runc.v2 runc runsc    # 以安装runsc,但默认运行时是runc
 Default Runtime: runc
...

3)使用runsc运行容器:

[root@k8s-node2-1-73 ~]# docker run -d --runtime=runsc nginx

验证1:dmesg

# 有gVisor接管的容器,可用dmesg查看
[root@k8s-node2-1-73 ~]# docker exec -it e59e011d4cc9 bash
root@e59e011d4cc9:/# dmesg
[    0.000000] Starting gVisor...
[    0.584074] Committing treasure map to memory...
[    0.754707] Creating bureaucratic processes...
[    1.175149] Reading process obituaries...
[    1.291416] Granting licence to kill(2)...
[    1.701557] Constructing home...
[    1.831904] Verifying that no non-zero bytes made their way into /dev/zero...
[    2.264950] Searching for needles in stacks...
[    2.715440] Searching for socket adapter...
[    3.169122] Letting the watchdogs out...
[    3.480816] Segmenting fault lines...
[    3.506517] Setting up VFS...
[    3.783351] Setting up FUSE...
[    3.900146] Ready!

# 没有gVisor接管的容器,dmesg查看
[root@k8s-node2-1-73 ~]# docker exec -it ac1662642021a bash
root@ac1662642021:/# dmesg
dmesg: read kernel buffer failed: Operation not permitted

验证2:uname -r

root@e59e011d4cc9:/# uname -r
4.4.0

root@ac1662642021:/# uname -r
6.3.5-1.el7.elrepo.x86_64

由于容器共享内核,所以查看内核的版本不一样,而使用gVisor接管的容器,所有的系统调用都需要它进行过滤,所以不是宿主机的内核版本

5.3 gVisor与Containerd集成

切换Containerd容器引擎

1)准备配置

cat > /etc/sysctl.d/99-kubernetes-cri.conf << EOF
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl -system

2)安装 containerd(可忽略,安装docker既包含了containerd)

cd /etc/yum.repos.d
wget http://mirrors.aliyun.com/dockerce/linux/centos/docker-ce.repo
yum install -y containerd.io

3)修改配置文件

  • pause镜像地址
  • Cgroup驱动改为systemd
  • 增加 runsc容器运行时
  • 配置 docker镜像加速器
# 如果是没有安装docker,自行安装的containerd,需手动创建并修改配置文件
mkdir -p /etc/containerd    # 创建containerd目录(没有则创建)
# 生成并覆盖原有的配置文件
containerd config default > /etc/containerd/config.toml

# 修改配置文件
vi /etc/containerd/config.toml
disabled_plugins = []    # 注释掉,containerd可以作为独立的容器运行;

...
  [plugins."io.containerd.grpc.v1.cri"] 
   sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.2"   # 修改pause镜像地址



   ...
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
        ...
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            ...
            SystemdCgroup = true   # Cgroup驱动改为systemd


        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]  # 添加.containerd.runtimes.runsc
          runtime_type = "io.containerd.runsc.v1"
        ...
    [plugins."io.containerd.grpc.v1.cri".registry]
    ...
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]    # 配置docker镜像加速器
        endpoint = ["https://b9pmyelo.mirror.aliyuncs.com"]

    ...

# 重启容器
systemctl restart containerd

disabled_plugins = ["cri"] //运行模式,如果是禁用containerd的cri,就相当于以docker集成的方式工作,如果注释掉,containerd可以作为独立的容器运行;

4)配置kubelet使用containerd

将 unix:///var/run/cri-dockerd.sock 修改成 unix:///run/containerd/containerd.sock

修改位置:/etc/sysconfig/kubelet  和 /var/lib/kubelet/kubeadm-flags.env

# 方式1:(推荐)
vi /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime-endpoint=unix:///run/containerd/containerd.sock --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9"

# 方式2:
vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --cgroup-driver=systemd"

# 重启kubelet服务
systemctl restart kubelet

5)验证

# 重启服务前
[root@k8s-node2-1-73 ~]# kubectl get node -o wide
NAME              STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                                                                                                                              KERNEL-VERSION              CONTAINER-RUNTIME
k8s-master-1-71   Ready    control-plane   106d   v1.26.3   192.168.1.71   <none>        CentOS Linux                                                                                                               7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node1-1-72    Ready    <none>          106d   v1.26.3   192.168.1.72   <none>        CentOS Linux                                                                                                               7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node2-1-73    Ready    <none>          106d   v1.26.3   192.168.1.73   <none>        CentOS Linux                                                                                                               7 (Core)   6.3.5-1.el7.elrepo.x86_64   docker://23.0.1

# 重启服务后
[root@k8s-node2-1-73 ~]# kubectl get node -o wide
NAME              STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION              CONTAINER-RUNTIME
k8s-master-1-71   Ready    control-plane   106d   v1.26.3   192.168.1.71   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node1-1-72    Ready    <none>          106d   v1.26.3   192.168.1.72   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node2-1-73    Ready    <none>          106d   v1.26.3   192.168.1.73   <none>        CentOS Linux 7 (Core)   6.3.5-1.el7.elrepo.x86_64   containerd://1.6.16
    # 镜像为containerd

containerd 也有 ctr 管理工具,但功能比较简单,一般使用 crictl工具 检查和调试容器。

项目地址:https://github.com/kubernetes-sigs/cri-tools/

准备crictl 连接containerd 配置文件:

cat > /etc/crictl.yaml << EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF

下面是docker与crictl命令对照表:

 

5.4 K8s使用gVisor运行容器

RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用 于运行 Pod 中的容器

1)创建RuntimeClass:

查看RuntimeClass:kubectl get runtimeclass

[root@k8s-master-1-71 ~]# kubectl apply -f test-runtimeclass.yaml
apiVersion: node.k8s.io/v1   # RuntimeClass 定义于 node.k8s.io API 组
kind: RuntimeClass
metadata:
  name: gvisor   # 用来引用 RuntimeClass 的名字
handler: runsc   # 对应的 CRI 配置的名称

--------------------------------------------------------------------------------
[root@k8s-master-1-71 ~]# kubectl get runtimeclass
NAME     HANDLER   AGE
gvisor   runsc     9s

注意:对应的 CRI 配置的名称,必须是/etc/containerd/config.toml配置文件中定义容器运行时添加的runsc对应,[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]

2)创建Pod指定runtimeclass类

补充:由于只在node2切换了containerd,所以需要将Pod部署在node2节点上

[root@k8s-master-1-71 ~]# kubectl apply -f test-rclass-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-gvisor
spec:
  runtimeClassName: gvisor
  containers:
  - name: nginx
    image: nginx

3)测试gVisor

[root@k8s-master-1-71 ~]# kubectl get pod nginx-gvisor -o wide
NAME           READY   STATUS    RESTARTS   AGE    IP              NODE             NOMINATED NODE   READINESS GATES
nginx-gvisor   1/1     Running   0          110s   10.244.114.38   k8s-node2-1-73   <none>           <none>

[root@k8s-master-1-71 ~]# kubectl exec -it nginx-gvisor -- dmesg
[    0.000000] Starting gVisor...
[    0.139631] Checking naughty and nice process list...
[    0.438644] Recruiting cron-ies...
[    0.852565] Committing treasure map to memory...
[    0.940108] Daemonizing children...
[    1.112831] Reading process obituaries...
[    1.572130] Mounting deweydecimalfs...
[    1.585922] Waiting for children...
[    1.784469] Digging up root...
[    1.904385] Reticulating splines...
[    2.203931] Letting the watchdogs out...
[    2.347858] Setting up VFS...
[    2.657248] Setting up FUSE...
[    3.124126] Ready!

总结:

  • 追求安全性,可以以gVisor启动Pod
  • 追求性能,建议还是默认启动Pod

课后作业

1、 使用containerd作为容器运行时,准备好gVisor,创建一个RuntimeClass, 创建一个Pod在gVisor上运行

小结

本篇为 【Kubernetes CKS认证 DAY4】的开篇学习笔记,希望这篇笔记可以让您初步了解到 如何设置容器以普通用户运行、Pod安全策略(PSP)、OPA Gatekeeper、Secret存储敏感数据、安全沙箱运行容器,不妨跟着我的笔记步伐亲自实践一下吧!


Tip:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关笔记、视频,可私信小安,请不要害羞和回避,可以向他人请教,花点时间直到你真正的理解。