# Kubernetes 中的 Pod 安全策略

很多人分不清 `SecurityContext` 和 `PodSecurityPolicy` 这两个关键字的差别，其实很简单：

* SecurityContext 是 Pod 中的一个字段，而 PSP 是一个独立的资源类型。
    
* SecurityContext 是 Pod 自身对安全上下文的声明；而 PSP 则是强制实施的——不合规矩的 Pod 无法创建。
    

PSP 的用法和 RBAC 是紧密相关的，换句话说，应用 PSP 的基础要求是：

* 不同运维人员的操作账号需要互相隔离并进行单独授权。
    
* 不同命名空间，不同 ServiceAccount 也同样要纳入管理流程。
    

PSP 环境下，运维人员或者新应用要接入集群，除了 RBAC 设置之外，还需要声明其工作范围所需的安全策略，并进行绑定，才能完成工作。

[PSP 的官方文档](https://kubernetes.io/docs/concepts/policy/pod-security-policy/)中提到，PSP 是通过 [`Admission Controller`](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podsecuritypolicy) 启用的，并且注明了：启用 PSP 是一个有风险的工作，未经合理授权，可能导致 Pod 无法创建。

开始之前，首先设置一个别名，在 `default` 命名空间新建 ServiceAccount 来模拟一个有权创建 Pod 的用户：

```bash
$ kubectl create sa common
serviceaccount/common created

$ kubectl create rolebinding common --clusterrole=edit --serviceaccount=default:common
rolebinding.rbac.authorization.k8s.io/common created

$ alias kube-common='kubectl --as=system:serviceaccount:default:common'
```

## 第一个 PSP

我们首先创建一个不允许创建特权 Pod 的策略：

```yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: noprivileged
spec:
  privileged: false
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  volumes:
  - '*'
```

保存为 `psp.noprivileged.yaml` 并提交给集群。

接下来创建两个 Pod：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: noprivileged
spec:
  containers:
  - name: pause
    image: k8s.gcr.io/pause
---
apiVersion: v1
kind: Pod
metadata:
  name: privileged
spec:
  containers:
  - name: pause
    image: k8s.gcr.io/pause
    securityContext:
      privileged: true
```

用普通用户创建这个 Pod：

```bash
$ kube-common apply -f pod.yaml && kube-common delete -f pod.yaml
pod/noprivileged created
pod/privileged created
pod "noprivileged" deleted
pod "privileged" deleted
```

可以看到，在不允许创建特权容器的规则之中，我们的用户还是能够创建特权容器，这是因为还没启用 PSP，接下来在集群设置中启动 PSP，各种环境的启用方式不同，例如在 GKE 环境：

```bash
$ gcloud beta container clusters update gcp-k8s --enable-pod-security-policy --zone=asia-east1-a
Updating gcp-vlab-k8s...done.
```

删除重建 Pod：

```bash
$ kube-common apply -f pod.yaml && kube-common delete -f pod.yaml
Error from server (Forbidden): error when creating "pod.yaml": pods "noprivileged" is forbidden: unable to validate against any pod security policy: []
Error from server (Forbidden): error when creating "pod.yaml": pods "privileged" is forbidden: unable to validate against any pod security policy: []
```

可以看到，Pod 的新建请求被拒绝了——**然而使用集群管理员身份还是能成功创建的**：

```bash
$ kubectl apply -f pod.yaml && kubectl delete -f pod.yaml
pod/noprivileged created
pod/privileged created
pod "noprivileged" deleted
pod "privileged" deleted
```

> 全员 admin 是万恶之源。

用 RBAC 进行授权：

```bash
$ kubectl create role psp:noprivileged \
    --verb=use \
    --resource=podsecuritypolicy \
    --resource-name=noprivileged
role.rbac.authorization.k8s.io/psp:noprivileged created

$ kubectl create rolebinding common:psp:noprivileged \
    --role=psp:noprivileged \
    --serviceaccount=default:common
rolebinding.rbac.authorization.k8s.io/common:psp:noprivileged created
```

再试试普通用户的能力：

```bash
$ kube-common apply -f pod.yaml ; kube-common delete -f pod.yaml
pod/noprivileged created
Error from server (Forbidden): error when creating "pod.yaml": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
pod "noprivileged" deleted
Error from server (NotFound): error when deleting "pod.yaml": pods "privileged" not found
```

非特权 Pod 才能够成功创建，这符合我们的预期。

## 副作用

Pod 成功创建了之后，顺理成章，做个 Deployment 看看：

```yaml
kind: Deployment
metadata:
  name: privileged
spec:
  replicas: 1  
  template:
    metadata:
      labels:
        app: pause
        version: v1
    spec:
      containers:
      - name: sleep
        image: k8s.gcr.io/pause
        securityContext:
          privileged: true
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: noprivileged
spec:
  replicas: 1
  template:
    serviceAccount: common
    metadata:
      labels:
        app: pause
        version: v1
    spec:
      containers:
      - name: sleep
        image: k8s.gcr.io/pause
```

我们会发现，Deployment 无法正常工作：

```bash
$ kubectl get pods
kuNo resources found in default namespace.
$ kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
noprivileged   0/1     0            0           15m
privileged     0/1     0            0           15m
```

查看一下事件：

```bash
$ kubectl get events | grep policy
8m38s       Warning   FailedCreate        replicaset/noprivileged-6f94f9c9b8                 Error creating: pods "noprivileged-6f94f9c9b8-" is forbidden: unable to validate against any pod security policy: []
8m38s       Warning   FailedCreate        replicaset/privileged-6d78d5458                    Error creating: pods "privileged-6d78d5458-" is forbidden: unable to validate against any pod security policy: []
```

这次的 Pod 不是由我们授权的 `common` 用户创建的，而是由 RS Controller 启动的，因此会失败，加入一个 Service Account：

```yaml
...
    spec:
      serviceAccount: common
      containers:
...
    spec:
      serviceAccount: common
      containers:
...
```

提交变更，会发现非特权 Pod 开始创建：

```bash
$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
noprivileged-6cf595c5bd-rc8cx   1/1     Running   0          4s
```

## 系统 Pod 怎么办

这时候我想到个问题，其它 Pod 会不会受到影响？我删除了 `kube-system` 下面的一个 `kube-proxy` 的 Pod，发现这个 Pod 自动重建了，没有受到 PSP 的影响，查看一下 RBAC 相关配置，会发现 GCP 在更新集群的过程中已经为系统服务进行了预设：

```bash
$ kubectl get rolebinding
...
gce:podsecuritypolicy:kube-proxy                    80m
gce:podsecuritypolicy:metadata-agent                80m
gce:podsecuritypolicy:metadata-proxy                80m
gce:podsecuritypolicy:nodes                         80m
...
```

追查下去：

```bash
$ kubectl get rolebinding gce:podsecuritypolicy:metadata-proxy -o yaml
...
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: gce:podsecuritypolicy:privileged
subjects:
- kind: ServiceAccount
  name: metadata-proxy
  namespace: kube-system
```

如果追查其中涉及到的 ClusterRole，会发现它指向一个 PSP：

```bash
$ kubectl get clusterrole gce:podsecuritypolicy:privileged -o yaml
...
rules:
- apiGroups:
  - policy
  resourceNames:
  - gce.privileged
  resources:
  - podsecuritypolicies
  verbs:
  - use
```

看看这个 PSP 的内容：

```bash
$ kubectl get psp  gce.privileged -o yaml
...
    privileged: true
...
```

的确包含了特权 Pod 的内容。

最后看看负责创建这个特权 Pod 的 Daemonset：

```bash
$  kubectl get daemonset  metadata-proxy-v0.1 -o yaml
...
      serviceAccount: metadata-proxy
      serviceAccountName: metadata-proxy
...
```

## PSP 的限制能力

分为以下几个大方面：

* 特权容器
    
* 主机命名空间：例如 HostPID、HostNetwork 等。
    
* 卷和文件系统：例如 PVC、configMap、emptyDir 等卷类型，以及 fsGroup、AllowedHostPaths 等加载能力。
    
* 用户和组：运行身份
    
* 提权：是否允许
    
* Capability 和 sysctl
    
* SeLinux、AppArmor 等。
    

## 马后炮

kubectl 的 `advise-psp` 插件，能够根据当前运行的 Pod，提取出所需的 PSP 信息。

## 参考链接

* `https://kubernetes.io/docs/concepts/policy/pod-security-policy/`
