文章目录
- 一、AppArmor限制容器对资源访问
- 1.1 实现步骤
- 1.1.1 定义策略
- 1.1.2 加载策略
- 1.1.3 引用策略
- 2.2 案例
- 二、Seccomp 限制容器进程系统调用
- 案例一:使用自定义策略
- 案例二:使用容器运行时默认策略
- 三、动态准入控制Webhook
- 3.1 ImagePolicyWebhook控制器
- 3.1.1 apiserver服务器准备操作
- 3.1.2 部署webhook服务器
- 3.1.3 测试验证
一、AppArmor限制容器对资源访问
概念:
- AppArmor(Application Armor) 是一个 Linux 内核安全模块,用于限制主机操作系统上运行的进程。每个进程都可以拥有自己的安全配置文件,安全配置文件用来允许或禁止特定功能,例如网络访问、文件读/写/执行权限等。
- 是Linux发行版的内置功能,比如Ubuntu、Debian、SUSE,而CentOS和redhat系统没有这个AppArmor,其功能相当于selinux,但selinux安全程度不高,也不方便使用,所以一般人不用。
- Ubuntu系统携带了许多没有合并到上游 Linux 内核中的 AppArmor 补丁, 包括添加附加钩子和特性的补丁。Kubernetes 只在上游版本中测试过,不承诺支持其他特性。
工作模式:
- Enforcement(强制模式) :在这种模式下,配置文件里列出的限制条件都会得到执行,并且对于违反这些限制条件的程序会进行日志记录,相当于selinux的enforcing模式。
- Complain(投诉模式):在这种模式下,配置文件里的限制条件不会得到执行,Apparmor只是对程序的行为进行记录,一般用于调试,相当于selinux的permissive模式。
K8s使用AppArmor的先决条件:
- K8s版本v1.4+,检查是否支持:kubectl describe node |grep AppArmor。
- Linux内核已启用AppArmor,查看 cat /sys/module/apparmor/parameters/enabled。
- 容器运行时需要支持AppArmor,docker、CRI-O 、containerd都支持。
- 比如以下ubuntu系统核内核是支持的,但在centos系统是看不到apparmor进程的。
工作流程图:
1.1 实现步骤
****:
- 将自定义策略配置文件保存到默认目录/etc/apparmor.d/下。
- 加载配置文件到内核。
- 定义pod.yaml文件时,在注解参数下指定策略名。因为AppArmor目前还处于测试阶段,因此以注解形式设定,日后进入正式阶段就可以使用安全上下文格式来配置了。
1.1.1 定义策略
配置策略:
vim /etc/apparmor.d/k8s-deny-write ##文件名称自定义。 #include <tunables/global> ##默认格式,导入依赖,遵循C语言约定。 profile k8s-deny-write flags=(attach_disconnected) { ##指定策略名,也就是命令能查到显示出来的策略名称。 #include <abstractions/base> ##定义策略。 file, ##允许所有文件读写。 deny /bin/** w, ##递归匹配,拒绝/bin目录下的所有目录和文件写权限。 deny /data/www/** w, ##递归匹配,拒绝/data/www目录下的所有目录和文件写权限。 }
1.1.2 加载策略
- 使用以下命令将策略加载到内核。
常用命令 | 释义 |
---|---|
apparmor_status | 查看AppArmor配置文件的当前状态的。 |
apparmor_parser | 将AppArmor配置文件加载到内核中。 |
apparmor_parser < profile > | 加载到内核中。 |
apparmor_parser -r < profile > | 重新加载配置。 |
apparmor_parser -R < profile > | 删除配置。 |
aa-complain | 将AppArmor配置文件设置为投诉模式,需要安装apparmor-utils软件包。 |
aa-enforce | 将AppArmor配置文件设置为强制模式,需要安装apparmor-utils软件包。 |
1.1.3 引用策略
- 定义pod.yaml时添加注解加以引用。
- 注意,引用的策略文件必须是要pod所在的宿主机上,实际生产中是需要运维来维护的,可以写脚本把策略文件给K8s集群每个节点分配上去并加载到内核。
apiVersion: v1 kind: Pod metadata: name: hello-apparmor annotations: container.apparmor.security.beta.kubernetes.io/<container_name>: localhost/<profile_ref> ...... ##< container_name >: Pod中容器名称。 ##< profile_ref >: Pod所在宿主机上策略名,默认目录/etc/apparmor.d。
访问文件权限模式字符 | 描述 |
---|---|
r | 读 |
w | 写 |
a | 追加 |
k | 文件锁定 |
l | 链接 |
x | 可执行 |
通配符 | 描述 | 示例 |
---|---|---|
* | 在目录级别匹配零个或多个字符。 | /dir/* :匹配目录中的任何文件。 /dir/a* :匹配目录中以a开头的任意文件。 /dir/.png: 匹配目录中以.png结尾的任意文件。 /dir/a/ :匹配/dir里面以a开头的目录。 /dir/*a/ :匹配/dir里面以a结尾的目录。 |
** | 在多个目录级别匹配零个或多个字符。 | /dir/** : 匹配/dir目录或者/dir目录下任何文件和目录。 /dir/**/ : 匹配/dir或者/dir下面任何目录。 |
[] [^] | 字符串,匹配其中任意字符。 | /dir/[^.]* :匹配/dir目录中以.之外的任何文件。 /dir/**[^/] :匹配/dir目录或者/dir下面的任何目录中的任何文件。 |
pod.yaml字段 | 释义 |
---|---|
<container_name> | pod.yaml文件里所针对的容器名称。 |
<profile_def> | Pod所在宿主机上策略名,引用刚刚定义的配置文件。 |
<profile_ref> | 可以是以下取值之一: runtime/default:应用运行时的默认配置。 localhost/<profile_name>:应用在主机上加载的名为 <profile_name> 的配置文件。 unconfined:表示不加载配置文件。 |
2.2 案例
案例:容器文件系统访问限制。
1.先查看默认生效的策略名称,我们定义的策略生效后就会显示在该列表中。
2.查看策略文件所在目录。
3.在K8s每个节点的/etc/apparmor.d目录下生成策略文件。策略是禁止在/bin目录下写,然后将策略文件加载到内核,查看结果。因为在编写pod.yaml里指定策略地址时,是使用的localhost读取本地。
4.先测试下使用我们生成策略之前效果,进入测试容器,可以在/bin目录下正常创建文件。
5.创建一个pod,其yaml文件里指定策略名称。
6.进入该容器创建目录测试效果。
7.若要修改策略,则修改后需要重新加载,并且需要重建pod容器。
apparmor parser -r k8s-deny-write
二、Seccomp 限制容器进程系统调用
前提了解:
- 在Linux上, 我们操作的所有跟资源相关的命令都是通过系统调用来完成的。
- 系统调用实现技术层次上解耦,内核只关心系统调用API的实现,而不必关心谁调用的。
调用关系图:
基本概念:
- Seccomp(Secure computing mode) 是一个 Linux 内核安全模块,可用于应用进程允许使用的系统调用。容器实际上是在宿主机上运行的一个进程,共享宿主机内核,如果所有容器都具有任何系统调用的能力,那么容器如果被入侵,就很轻松绕过容器隔离更改宿主机系统权限或者进入宿主机。这就可以使用Seccomp机制限制容器系统调用,有效减少攻击面。
- Linux发行版内置:CentOS、Ubuntu。
Type类型:
- pod.yaml中会定义Type字段,表示将应用哪种类型的seccomp配置文件,具有如下选项:
- Localhost:应该使用在节点上的文件中定义的配置文件。
- RuntimeDefault:应该使用容器运行时默认配置文件。
- Unconfined:无限制,不使用策略。
注意事项:
- 引用的策略文件必须是要pod所在的宿主机上,实际生产中是需要运维来维护的,可以写脚本把策略文件给K8s集群每个节点分配上去并加载到内核。
- Seccomp在Kubernetes 1.3版本引入,在1.19版本成为GA版本,因此K8s中使用Seccomp可以通过以下两种方式:
- 1.19版本之前使用注解:
annotations: seccomp.security.alpha.kubernetes.io/pod: "localhost/<profile>"
- 1.19版本+配置安全上下文:
apiVersion: v1 kind: Pod metadata: name: hello-seccomp spec: securityContext: seccompProfile: type: Localhost localhostProfile: <profile> # Pod所在宿主机上策略文件名,默认目录:/var/lib/kubelet/seccomp containers: ...
案例一:使用自定义策略
- 需求:禁止容器使用chmod。
1.创建默认文件路径。
[root@k8s-master1 ~]# mkdir /var/lib/kubelet/seccomp
2.在默认路径下生成限制策略。
[root@k8s-master1 seccomp]# cat chmod.json
{
"defaultAction": "SCMP_ACT_ALLOW", ##在syscalls部分未定义的任何系统调用默认动作为允许。
"syscalls": [
{
"names": [ ##系统调用名称,可以换行写多个。
"chmod"
],
"action": "SCMP_ACT_ERRNO" ## 阻止自定义的系统调用。
}
]
}
3.创建pod时,引用该策略。
[root@k8s-master1 ResourceQuota]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: baimu
spec:
nodeName: k8s-master1 ##需要执行该pod被分配的机器上是有限制策略的。
securityContext: ##添加此参数。
seccompProfile:
type: Localhost ##指定类型,调用本地自定义策略文件。
localhostProfile: chmod.json ##使用相对路径,指定应用策略名称。
containers:
- image: busybox
name: test
command:
- sleep
- 24h
4.进入普通容器测试可以修改权限。
5.进入指定限制策略的容器修改权限失败,限制成功。
6.此时需要再限制创建目录权限,就直接在chmod.json文件里加。
7.限制策略文件内容更改后,指定该策略的pod需要重新创建,先删除再创建容器,最后进入容器测试。
案例二:使用容器运行时默认策略
大多数容器运行时都提供一组允许或不允许的默认系统调用。
通过使用 runtime/default 注释 或将Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault,可以轻松地在 Kubernetes中应用默认值。
docker默认配置说明
1.指定docker默认策略创建pod。
[root@k8s-master1 ResourceQuota]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: baimu
spec:
nodeName: k8s-master1
securityContext:
seccompProfile:
type: RuntimeDefault ##修改类型,使用容器运行时默认策略。
# localhostProfile: chmod.json ##注释掉。
containers:
- image: busybox
name: test
command:
- sleep
- 24h
2.查看系统调用。查看操作命令用到哪些系统调用可以使用strace命令查看,一个命令通常会使用多个系统调用,若是在实际生产中使用需要挨个调试。
[root@k8s-master1 seccomp]# yum -y install strace
三、动态准入控制Webhook
基本概念:
- 前面讲过的准入控制是通过添加K8s内置的准入插件来实现,当然也可以作为扩展独立开发,并以运行时所配置的 Webhook 的形式运行。
- 准入控制器Webhook是准入控制插件的一种,是一种用于接收准入请求并对其进行处理的 HTTP 回调机制 ,常用于拦截所有向apiserver发送的请求,并且可以修改请求或拒绝请求。
- Admission webhook为开发者提供了非常灵活的插件模式,在kubernetes资源持久化之前,管理员通过程序可以对指定资源做校验、修改等操作。例如为资源自动打标签、pod设置默认SA,自动注入sidecar容器等。
Webhook准入控制器分类:
- Mutating Admission Webhook:先调用,修改资源,理论上可以监听并修改任何经过ApiServer处理的请求。
- Validating Admission Webhook:后调用,验证资源。
- ImagePolicyWebhook:镜像策略,主要验证镜像字段是否满足条件,允许使用后端 Webhook 做出准入决策。
工作流程图:
3.1 ImagePolicyWebhook控制器
工作流程图:
玩法思路:
- 签发证书。镜像策略服务器是以https启用,需要给它自签证书,访问它时也要携带根证书或者客户端证书。
- 准备imgaepolicywebhook配置文件,指定镜像策略服务器配置文件存放目录。
- 准备镜像策略服务器配置文件,指定连接服务器IP地址、端口、API、根证书、客户端证书。
- 启用准入控制插件。
3.1.1 apiserver服务器准备操作
1.定义镜像策略控制器配置文件,指定配置文件存放路径。
##镜像策略控制器配置文件,指定连接镜像策略服务器配置文件地址,连接服务器通用策略。
[root@k8s-master1 imagepolicywebhook]# cat admission_configuration.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
configuration:
imagePolicy:
kubeConfigFile: /etc/kubernetes/image-policy/connect_webhook.yaml # 指定镜像策略服务器配置文件。
allowTTL: 50 # 控制批准请求的缓存时间,单位秒
denyTTL: 50 # 控制批准请求的缓存时间,单位秒
retryBackoff: 500 # 控制重试间隔,单位毫秒
defaultAllow: true # 确定webhook后端失效的行为
2.定义镜像策略服务器连接配置文件,指定它的根证书、连接它的客户端证书、连接地址、端口和API。
[root@k8s-master1 imagepolicywebhook]# cat connect_webhook.yaml
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/image-policy/webhook.pem # 数字证书,用于验证远程服务
server: https://192.168.130.146:8080/image_policy # 镜像策略服务器地址,必须是https
name: webhook
contexts:
- context:
cluster: webhook
user: apiserver
name: webhook
current-context: webhook
preferences: {}
users:
- name: apiserver
user:
client-certificate: /etc/kubernetes/image-policy/apiserver-client.pem # webhook准入控制器使用的证书
client-key: /etc/kubernetes/image-policy/apiserver-client-key.pem # 对应私钥证书
3.统一两文件存放目录。
[root@k8s-master1 imagepolicywebhook]# mkdir -p /etc/kubernetes/image-policy
[root@k8s-master1 imagepolicywebhook]# mv admission_configuration.yaml connect_webhook.yaml /etc/kubernetes/image-policy/
4.签发证书。
[root@k8s-master1 ssl]# cat image-policy-certs.sh
cat > ca-config.json <<EOF ##k8s根证书。
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"expiry": "87600h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
EOF
cat > ca-csr.json <<EOF
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"L": "Beijing",
"ST": "Beijing"
}
]
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
cat > webhook-csr.json <<EOF ##镜像策略服务器根证书。
{
"CN": "webhook",
"hosts": [
"192.168.130.146" ##镜像策略服务器地址。
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"L": "BeiJing",
"ST": "BeiJing"
}
]
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes webhook-csr.json | cfssljson -bare webhook
cat > apiserver-client-csr.json <<EOF ##连接镜像策略服务器的客户端证书。
{
"CN": "apiserver",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"L": "BeiJing",
"ST": "BeiJing"
}
]
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes apiserver-client-csr.json | cfssljson -bare apiserver-client
5.将证书放入前面两文件目录里,文件里有指定读取这两个证书。
[root@k8s-master1 ssl]# mv apiserver-client*.pem webhook.pem /etc/kubernetes/image-policy/
6.修改apiserver配置文件,开启ImagePolicyWebhook插件,指定imagepolicywebhook配置文件位置,并挂载到容器里面。
- --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
- --admission-control-config-file=/etc/kubernetes/image-policy/admission_configuration.yaml
3.1.2 部署webhook服务器
1.部署webhook站点服务器,我这里是写了一个python程序来实现对部署时的镜像没有指定标签(即latest)进行拒绝。
[root@k8s-master2 imagepolicywebhook]# cat main.py
from flask import Flask,request
import json
app = Flask(__name__)
@app.route('/image_policy',methods=["POST"]) ##处理发送过来的post请求。
def image_policy():
post_data = request.get_data().decode() ##接受发送过来的post请求。
#print("POST数据: %s" %post_data) ##将post请求打印出来。
data = json.loads(post_data) ##解析这个post请求。
for c in data['spec']['containers']: ##过滤这个post请求中的spec.containers字段下的内容。
if ":" not in c['image'] or ":latest" in c['image']: ##如果镜像里不带冒号或者带:latest说明是镜像使用latest标签。
allowed, reason = False, "检查镜像失败!镜像标签不允许使用latest!"
break
else:
allowed, reason = True, "检查镜像通过."
print("检查结果: %s" %reason)
result = {"apiVersion": "imagepolicy.k8s.io/v1alpha1","kind": "ImageReview", ##返回个apiserver的api,固定格式。
"status": {"allowed": allowed,"reason": reason}}
return json.dumps(result,ensure_ascii=False)
if __name__ == "__main__": ##监听服务端口8999,需要把证书传到该服务器,后面会挂载到容器里。
app.run(host="0.0.0.0",port=8080,ssl_context=('/data/www/webhook.pem','/data/www/webhook-key.pem'))
2.构建镜像。
[root@k8s-master2 imagepolicywebhook]# cat Dockerfile
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
CMD python main.py
[root@k8s-master2 imagepolicywebhook]# docker build -t qingjun:v1 .
3.准备好之前在apiserver服务器上创建的证书,并运行一个容器进入查看,容器日志会显示python程序检查apiserver发过来的请求结果。
docker run -d -u root --name=baimu1 \
-v $PWD/webhook.pem:/data/www/webhook.pem \
-v $PWD/webhook-key.pem:/data/www/webhook-key.pem \
-e PYTHONUNBUFFERED=1 -p 8080:8080 \
qingjun:v1
3.1.3 测试验证
1.创建pod容器时,使用不带标签的镜像,则返回false。
2.创建pod容器时,使用带latest标签的镜像,返回false。
3.创建pod容器时,使用带标签的镜像,返回true。