介绍一个小工具:Security Profiles Operator
在云原生安全方面,Kubernetes 在不同维度提供了很多的不同内容,例如 RBAC、Networkpolicy、SecurityContext 等等,种种措施中,像我这样基础不牢的 YAML 工程师最头大的可能就要数 SecurityContext 里面的 SELinux、Seccomp 和 AppArmor 三大块了。Security Profiles Operator 项目为此而来,希望能够降低在 Kubernetes 集群中使用这些安全技术的难度。在项目网页上转了转,发现他所说的简化,除了定义几个 CRD 封装这样的 Operator 传统技能之外;还有一个使用 CRD 在节点间传输 Security Profile 的能力;最后也是最重要的,提供了很方便的录制功能,这倒是真的戳中了痛点——手写 Profile 固然酷炫,录制生成才是生产力啊。目前支持的功能矩阵如下:
功能 | Seccomp | SELinux | AppArmor |
---|---|---|---|
Profile CRD | Yes | Yes | Yes |
ProfileBinding | Yes | No | No |
Deploy profiles into nodes | Yes | Yes | WIP |
Remove profiles no longer in use | Yes | Yes | WIP |
Profile Auto-generation (logs) | Yes | WIP | No |
Profile Auto-generation (ebpf) | Yes | No | No |
Audit log enrichment | Yes | WIP | Yes |
部署
如果目标环境不是 Openshift,首先需要安装 Cert Manager
:
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
...
接下来安装 SPO:
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/security-profiles-operator/master/deploy/operator.yaml
customresourcedefinition.apiextensions.k8s.io/profilebindings.security-profiles-operator.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/profilerecordings.security-profiles-operator.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/seccompprofiles.security-profiles-operator.x-k8s.io created
查看生成的 CRD,大致功能如下
全名 | 缩写 | 命名空间级 | 功能 |
---|---|---|---|
AppArmorProfile | aa | true | 用于保存 AppArmor Profile |
ProfileBinding | N/A | true | 把 Profile 绑定到 Pod 上 |
ProfileRecording | N/A | true | 用录制的方式生成 Profile |
RawSelinuxProfile | N/A | true | |
SeccompProfile | sp | true | 用于保存 Seccomp Profile |
SecurityProfileNodeStatus | spns | true | |
SecurityProfilesOperatorDaemon | spod | true | |
SelinuxProfile | N/A | true | 用于保存 Selinux Profile |
最后创建一个命名空间 spo
,并以此作为缺省命名空间,进行后续的试用过程。
借助 SPO 传递 Seccomp
创建一个 Seccomp Profile,其中加入了对系统调用的审计日志:
apiVersion: security-profiles-operator.x-k8s.io/v1beta1
kind: SeccompProfile
metadata:
namespace: spo
name: seccomp-profile-sample
spec:
defaultAction: SCMP_ACT_LOG
Apply 之后 kubectl get sp seccomp-profile-sample -o yaml
,会发现状态如下:
metadata:
...
finalizers:
- gke-gcp-vlab-k8s-default-pool-7c61250b-x3h1-delete
- gke-gcp-vlab-k8s-default-pool-7c61250b-n9l3-delete
- gke-gcp-vlab-k8s-default-pool-7c61250b-86wz-delete
...
status:
...
localhostProfile: operator/spo/seccomp-profile-sample.json
status: Installed
表明 Profile 已经部署到三个节点,状态为 Installed
。
这样一来就可以创建一个 Pod,引用这个 Profile:
apiVersion: v1
kind: Pod
metadata:
name: sleep-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: operator/spo/seccomp-profile-sample.json
containers:
- name: sleep
image: dustise/sleep:v0.9.6
volumeMounts:
- name: kubelet
mountPath: /data/kubelet
volumes:
- name: kubelet
hostPath:
path: /var/lib/kubelet/seccomp/operator/spo
type: Directory
Pod 创建之后,进入 Pod Shell,可以看到我们生成的 Profile:
$ kubectl exec -it sleep-pod -- bash
bash-5.0# cd /data/kubelet
bash-5.0# ls
seccomp-profile-sample.json
bash-5.0# cat seccomp-profile-sample.json
{"defaultAction":"SCMP_ACT_LOG"}
接下来执行一下 CURL 点什么,登录节点看看日志
$ sudo journalctl -xe | grep -i seccomp | grep curl
Mar 21 10:12:44 gke-gcp-vlab-k8s-default-pool-d97cb436-mdgb audit[180209]: SECCOMP auid=4294967295 uid=0 gid=0 ses=4294967295 subj==docker-default (enforce) pi
d=180209 comm="curl" exe="/usr/bin/curl" sig=0 arch=c000003e syscall=231 compat=0 ip=0x7f6d3b8d76f9 code=0x7ffc0000
删掉 Pod。便于进行后续步骤
如果我不想修改 Pod,可以用 ProfileBinding 把 Seccomp Profile 和镜像绑定到一起,如下配置:
apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
kind: ProfileBinding
metadata:
name: sample-binding
spec:
profileRef:
kind: SeccompProfile
name: seccomp-profile-sample
image: dustise/sleep:v0.9.6
看看重建一个没有 securityPod
的 Pod 会有什么变化:
apiVersion: v1
kind: Pod
metadata:
name: sleep-pod
spec:
containers:
- name: sleep
image: dustise/sleep:v0.9.6
volumeMounts:
- name: kubelet
mountPath: /data/kubelet
volumes:
- name: kubelet
hostPath:
path: /var/lib/kubelet/seccomp/operator/spo
type: Directory
创建之后,我们看看线上的 YAML:
$ kubectl get po sleep-pod -o yaml
apiVersion: v1
kind: Pod
...
securityContext:
seccompProfile:
localhostProfile: operator/spo/seccomp-profile-sample.json
type: Localhost
果然这里被修改了。有兴趣还可以查查,这里用到的 Mutating Webhook。
录制 Profile
SPO 支持三种录制模式,分别是 hook
、log
或 eBPF
。
hook
指的是 OCI hooks
,但是目前 containerd 还不支持。
log
则是使用基于日志的录制方式。
eBPF
自然就是最新的基于 eBPF 的录制了。
缺省情况下 spod
没有开启 eBPF 录制功能,需要把开关 spec.enableBpfRecorder
设置为 true
:
$ kubectl patch spod spod -n security-profiles-operator --type=merge -p '{"spec":{"enableBpfRecorder":true}}'
securityprofilesoperatordaemon.security-profiles-operator.x-k8s.io/spod patched
接下来创建一个录制对象:
apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
kind: ProfileRecording
metadata:
name: sample-recording
spec:
kind: SeccompProfile
recorder: bpf
podSelector:
matchLabels:
app: sleep
提交之后,我们给上一个 Pod 加上标签 app=sleep
,创建出来,然后随便执行点什么:
$ kubectl apply -f recording.yaml
profilerecording.security-profiles-operator.x-k8s.io/sample-recording created
$ kubectl apply -f plain-pod.yaml
pod/sleep-pod created
$ kubectl exec -it sleep-pod -- curl http://jd.com
...
$ kubectl exec -it sleep-pod -- mkdir 123
...
$ kubectl exec -it sleep-pod -- ping baidu.com
...
删除 Pod,查看 SeccompProfile:
$ kubectl delete -f plain-pod.yaml
pod "sleep-pod" deleted
$ kubectl get sp
NAME STATUS AGE
sample-recording-sleep Installed 9s
$ kubectl get sp sample-recording-sleep -o yaml
kind: SeccompProfile
metadata:
creationTimestamp: "2022-03-21T14:07:23Z"
finalizers:
- gke-gcp-vlab-k8s-default-pool-d97cb436-5tct-delete
- gke-gcp-vlab-k8s-default-pool-d97cb436-mdgb-delete
- gke-gcp-vlab-k8s-default-pool-d97cb436-d8d1-delete
generation: 1
labels:
spo.x-k8s.io/profile-id: SeccompProfile-sample-recording-sleep
...
spec:
architectures:
- SCMP_ARCH_X86_64
defaultAction: SCMP_ACT_ERRNO
syscalls:
- action: SCMP_ACT_ALLOW
names:
- arch_prctl
- bind
...
这里看到,删除 Pod 之后,录制过程自动生成了新的 SeccompProfile,其中包含了 Pod 工作过程中使用的配置,并且已经被安装到了各个节点之上。
牢骚
Kubernetes 普及之后,新方向层出不穷,正如杨蒙恩说的——“遍地是大王,短暂又辉煌”,不过安全可能是目前确定性最高的一块内容,决不短暂。所谓安全无小事,没有网格、没有 Serverless 甚至没有多集群、经济性、混布都可以,没有安全可能就全盘皆输了;也不要总想着新瓶装旧酒,挑挑节点、固定一下 IP 就完事了,BMW 装上马鞍之后,丢的不只是风阻和车顶,至少车友群是不太容易混了。