一、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规则,任 意一步不满足都会拒绝部署。 因此,需要实施需要有这几步骤:
- 创建SA服务账号
- 该SA需要具备创建对应资源权限,例如创建Pod、Deployment
- SA使用PSP资源权限:创建Role,使用PSP资源权限,再将SA绑定Role
PSP工作流程:
补充:PSP不足与状况
① 将再1.21版本弃用PSP,在1.25版本删除PSP;
② 仅支持Pod策略,不支持Deployment等;
③ 使用复杂,权限模型存在缺陷,控制不明确;
弃用文章:PodSecurityPolicy Deprecation: Past, Present, and Future | Kubernetes
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的策略由两个资源对象组成:
- Template:策略逻辑实现的地方,使用rego语言(具体对资源做什么操作)
- 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无缝集成,很方便使用。
— 容器中应用程序直接与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二进制文件
要手动下载并安装最新版本,请执行以下步骤:
[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工具 检查和调试容器。
准备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:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关笔记、视频,可私信小安,请不要害羞和回避,可以向他人请教,花点时间直到你真正的理解。