Kubernetesマニフェストを静的解析するツールのkubesecを試してみたメモ。
kubesecというツールは2つある。一つはSecretを暗号化するツールで、こちらのイメージが強かった。
もう一つがKubernetesマニフェストを静的解析するツール。
このツールは、セキュリティベストプラクティスに基づく固定のルールで、マニフェストをチェックできる。トップページでデモを触ってみるとツールの意味がわかりやすい。
- バイナリ
- Dockerコンテナ
- kubectlプラグイン
- Admission Controller (kubesec-webhook)
Dockerコンテナでの実行
テスト用のyamlを作成する。
k run nginx --image=nginx -o yaml --dry-run=client > pod.yaml
apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: nginx name: nginx spec: containers: - image: nginx name: nginx resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {}
スキャンを実行する。Passしているが0点。
root@cks-master:~# docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin < pod.yaml Unable to find image 'kubesec/kubesec:512c5e0' locally 512c5e0: Pulling from kubesec/kubesec c87736221ed0: Pull complete 5dfbfe40753f: Pull complete 0ab7f5410346: Pull complete b91424b4f19c: Pull complete 0cff159cca1a: Pull complete 32836ab12770: Pull complete Digest: sha256:8b1e0856fc64cabb1cf91fea6609748d3b3ef204a42e98d0e20ebadb9131bcb7 Status: Downloaded newer image for kubesec/kubesec:512c5e0 [ { "object": "Pod/nginx.default", "valid": true, "message": "Passed with a score of 0 points", "score": 0, "scoring": { "advise": [ { "selector": "containers[] .securityContext .readOnlyRootFilesystem == true", "reason": "An immutable root filesystem can prevent malicious binaries being added to PATH and increase attack cost" }, { "selector": "containers[] .securityContext .runAsNonRoot == true", "reason": "Force the running image to run as a non-root user to ensure least privilege" }, { "selector": "containers[] .securityContext .runAsUser -gt 10000", "reason": "Run as a high-UID user to avoid conflicts with the host's user table" }, { "selector": "containers[] .securityContext .capabilities .drop", "reason": "Reducing kernel capabilities available to a container limits its attack surface" }, { "selector": "containers[] .securityContext .capabilities .drop | index(\"ALL\")", "reason": "Drop all capabilities and add only those required to reduce syscall attack surface" }, { "selector": ".spec .serviceAccountName", "reason": "Service accounts restrict Kubernetes API access and should be configured with least privilege" }, { "selector": "containers[] .resources .requests .cpu", "reason": "Enforcing CPU requests aids a fair balancing of resources across the cluster" }, { "selector": "containers[] .resources .limits .cpu", "reason": "Enforcing CPU limits prevents DOS via resource exhaustion" }, { "selector": "containers[] .resources .requests .memory", "reason": "Enforcing memory requests aids a fair balancing of resources across the cluster" }, { "selector": "containers[] .resources .limits .memory", "reason": "Enforcing memory limits prevents DOS via resource exhaustion" }, { "selector": ".metadata .annotations .\"container.seccomp.security.alpha.kubernetes.io/pod\"", "reason": "Seccomp profiles set minimum privilege and secure against unknown threats" }, { "selector": ".metadata .annotations .\"container.apparmor.security.beta.kubernetes.io/nginx\"", "reason": "Well defined AppArmor policies may provide greater protection from unknown threats. WARNING: NOT PRODUCTION READY" } ] } } ]
apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: nginx name: nginx spec: containers: - image: nginx name: nginx resources: {} securityContext: runAsNonRoot: true dnsPolicy: ClusterFirst restartPolicy: Always status: {}
スキャンを再実行する。1点獲得。
root@cks-master:~# docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin < pod.yaml [ { "object": "Pod/nginx.default", "valid": true, "message": "Passed with a score of 1 points", "score": 1, "scoring": { "advise": [ { "selector": "containers[] .securityContext .readOnlyRootFilesystem == true", "reason": "An immutable root filesystem can prevent malicious binaries being added to PATH and increase attack cost" }, { "selector": "containers[] .securityContext .runAsUser -gt 10000", "reason": "Run as a high-UID user to avoid conflicts with the host's user table" }, { "selector": "containers[] .securityContext .capabilities .drop", "reason": "Reducing kernel capabilities available to a container limits its attack surface" }, { "selector": "containers[] .securityContext .capabilities .drop | index(\"ALL\")", "reason": "Drop all capabilities and add only those required to reduce syscall attack surface" }, { "selector": ".spec .serviceAccountName", "reason": "Service accounts restrict Kubernetes API access and should be configured with least privilege" }, { "selector": "containers[] .resources .requests .cpu", "reason": "Enforcing CPU requests aids a fair balancing of resources across the cluster" }, { "selector": "containers[] .resources .limits .cpu", "reason": "Enforcing CPU limits prevents DOS via resource exhaustion" }, { "selector": "containers[] .resources .requests .memory", "reason": "Enforcing memory requests aids a fair balancing of resources across the cluster" }, { "selector": "containers[] .resources .limits .memory", "reason": "Enforcing memory limits prevents DOS via resource exhaustion" }, { "selector": ".metadata .annotations .\"container.seccomp.security.alpha.kubernetes.io/pod\"", "reason": "Seccomp profiles set minimum privilege and secure against unknown threats" }, { "selector": ".metadata .annotations .\"container.apparmor.security.beta.kubernetes.io/nginx\"", "reason": "Well defined AppArmor policies may provide greater protection from unknown threats. WARNING: NOT PRODUCTION READY" } ] } } ]
OPAで自分で書かなくても、固定のルールがあるのですぐ使えて楽。CIに組み込み、何点以上でないとコミットできないようにするといった使い方をすることは可能そう。OPAで自分でルールを書くときに参考としても使えそう。