Kubernetes 认证安全专家(CKS)学习指南(二)
原文:zh.annas-archive.org/md5/525e77013d4f84e5576c98a6b161f26a译者:飞龙
协议:CC BY-NC-SA 4.0
第五章:最小化微服务漏洞
在 Kubernetes 集群中操纵的应用步调堆栈通常遵循微服务架构。“最小化微服务漏洞”领域涵盖了在 Pod 级别上实施安全设置的治理和强制执行。我们将介绍 Kubernetes 核心功能以及外部工具,帮助减少安全漏洞。此外,我们还将讨论运行微服务的 Pod 之间的加密网络通信。
在高层次上,本章涵盖以下概念:
[*] 设置恰当的操纵体系级安全域与安全上下文,Pod 安全审计(PSA)和 Open Policy Agent Gatekeeper
[*] 管理秘密
[*] 利用容器运行时沙盒,如 gVisor 和 Kata 容器
[*] 通过双向传输层安全协议(TLS)实现 Pod 之间的通信加密
设置恰当的操纵体系级安全域
Kubernetes 核心和 Kubernetes 生态体系均提供了界说、实施和管理 Pod 和容器级别安全设置的解决方案。本节将讨论安全上下文、Pod 安全审计和 Open Policy Agent Gatekeeper。您将学习怎样通过示例应用每个功能和工具,展示它们在安全性方面的紧张性。让我们从设置一个场景开始。
景象:攻击者滥用 root 用户容器访问权限
默认情况下,容器以 root 权限运行。应用步调中的漏洞大概会授予攻击者root访问容器的权限。容器的 root 用户与主机上的 root 用户相同。攻击者不仅可以检查或修改应用步调,还大概安装额外的工具,使攻击者能够打破容器并以root权限进入主机命名空间。攻击者还可以将主机文件体系的敏感数据复制到容器中。图 5-1 阐明了该场景。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0501.png
图 5-1. 攻击者滥用 root 用户容器访问权限
因此,默认情况下以 root 用户运行容器是一个不好的选择。接下来的章节将解释如作甚容器声明一个安全上下文,强制利用非 root 用户或特定用户和/或组标识符。我们还将讨论其他与从容器到主机的访问隔离相关的安全上下文设置。
理解安全上下文
作为容器编排引擎的 Kubernetes 可以应用额外的设置来增强容器安全性。通过界说安全上下文来实现。安全上下文界说了 Pod 或容器的特权和访问控制设置。以下列表提供了一些示例:
[*] 应用步调应利用的用户 ID 来运行 Pod 和/或容器
[*] 文件体系访问应利用的组 ID
[*] 授予容器内运行的历程部分 root 用户权限但不是全部权限
安全上下文不是 Kubernetes 的原语。它被建模为 Pod 规范内的一组属性,在指令 securityContext 下。在 Pod 级别界说的安全设置适用于在 Pod 中运行的所有容器;但容器级别的设置优先。有关 Pod 级别安全属性的更多信息,请参阅 PodSecurityContext API。容器级别的安全属性可以在 SecurityContext API 中找到。
强制利用非 root 用户
我们将看一个利用案例,使功能更透明。某些镜像,如用于开源反向代理服务器 nginx 的镜像,必须以 root 用户运行。假设你想强制容器不能作为 root 用户运行,以支持更公道的安全计谋。文件 container-non-root-user-error.yaml 中的 YAML 清单文件(示例 5-1)界说了特定容器的安全设置。此安全上下文仅适用于此容器,而不适用于其他容器(假如你要界说更多的话)。
示例 5-1. 强制在需要以 root 用户运行的镜像上利用非 root 用户
apiVersion: v1
kind: Pod
metadata:
name: non-root-error
spec:
containers:
- image: nginx:1.23.1
name: nginx
securityContext:
runAsNonRoot: true
容器在启动过程中失败,状态为 CreateContainerConfigError。查看 Pod 的事故日志显示,镜像实验以 root 用户运行。设置的安全上下文不允许这样做:
$ kubectl apply -f container-non-root-user-error.yaml
pod/non-root-error created
$ kubectl get pod non-root-error
NAME READY STATUS RESTARTS AGE
non-root-error 0/1 CreateContainerConfigError 0 9s
$ kubectl describe pod non-root-error
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled24s default-schedulerSuccessfully \
assigned default/non-root to minikube
Normal Pulling 24s kubelet Pulling image \
"nginx:1.23.1"
Normal Pulled 16s kubelet Successfully \
pulled image "nginx:1.23.1" in 7.775950615s
WarningFailed 4s (x3 over 16s)kubelet Error: container \
has runAsNonRoot and image will run as root (pod: "non-root-error_default \
(6ed9ed71-1002-4dc2-8cb1-3423f86bd144)", container: secured-container)
Normal Pulled 4s (x2 over 16s)kubelet Container image \
"nginx:1.23.1" already present on machine
有可用的替代 nginx 容器镜像,不需要以 root 用户运行。比方 bitnami/nginx。示例 5-2 展示了文件 container-non-root-user-success.yaml 的内容。此文件的主要变更是为 spec.containers[].image 属性分配的值。
示例 5-2. 强制在支持利用用户 ID 运行的镜像上利用非 root 用户
apiVersion: v1
kind: Pod
metadata:
name: non-root-success
spec:
containers:
- image: bitnami/nginx:1.23.1
name: nginx
securityContext:
runAsNonRoot: true
利用 runAsNonRoot 指令启动容器将正常工作。容器转换为“运行”状态:
$ kubectl apply -f container-non-root-user-success.yaml
pod/non-root-success created
$ kubectl get pod non-root-success
NAME READY STATUS RESTARTS AGE
non-root-success 1/1 Running 0 7s
让我们快速检查容器利用哪个用户 ID。进入容器并运行 id 下令。输出显示用户 ID、组 ID 和附加组的 ID。镜像 bitnami/nginx 在构建容器镜像时通过指令将用户 ID 设置为 1001:
$ kubectl exec non-root-success -it -- /bin/sh
$ id
uid=1001 gid=0(root) groups=0(root)
$ exit
设置特定的用户和组 ID
很多容器镜像未设置显式的用户 ID 或组 ID。倒霉用 root 默认用户运行,可以设置所需的用户 ID 和组 ID,以最小化埋伏的安全风险。文件 container-user-id.yaml 中存储的 YAML 清单文件(示例 5-3)将用户 ID 设置为 1000,组 ID 设置为 3000。
示例 5-3. 利用特定用户和组 ID 运行容器
apiVersion: v1
kind: Pod
metadata:
name: user-id
spec:
containers:
- image: busybox:1.35.0
name: busybox
command: ["sh", "-c", "sleep 1h"]
securityContext:
runAsUser: 1000
runAsGroup: 3000
创建 Pod 将无问题。容器转换为“运行”状态:
$ kubectl apply -f container-user-id.yaml
pod/user-id created
$ kubectl get pods user-id
NAME READY STATUS RESTARTS AGE
user-id 1/1 Running 0 6s
进入容器后,您可以检查用户 ID 和组 ID。当前用户无权在/目录中创建文件。在/tmp目录中创建文件将起作用,因为大多数用户都有写入权限:
$ kubectl exec user-id -it -- /bin/sh
/ $ id
uid=1000 gid=3000 groups=3000
/ $ touch test.txt
touch: test.txt: Permission denied
/ $ touch /tmp/test.txt
/ $ exit
避免利用特权容器
Kubernetes 在历程、网络、挂载、用户 ID 等方面在容器命名空间和主机命名空间之间创建了明白的分离。您可以设置容器的安全上下文以获取对主机命名空间某些方面的权限。利用特权容器时,请思量以下影响:
[*] 容器内的历程险些具有与主机上历程相同的权限。
[*] 容器具有访问主机上所有设备的权限。
[*] 容器中的root用户具有与主机上的root用户类似的权限。
[*] 可以在容器中挂载主机文件体系上的所有目录。
[*] 可以通过利用sysctl下令更改内核设置。
利用特权模式的容器
设置容器以利用特权模式应该是一个罕见的情况。大多数运行在容器中的应用步调和历程不需要超出容器命名空间之外的提拔权限。假如遇到设置为利用特权模式的 Pod,请接洽负责的团队或开发者举行澄清,因为这将为攻击者打开入侵主机体系的漏洞。
让我们比力设置为非特权容器和设置为特权模式运行的容器的行为。首先,我们将设置一个常规的 Pod,如示例 5-4 所示。在 Pod 或容器级别未设置安全上下文。
示例 5-4. 利用非特权模式的容器中的 Pod
apiVersion: v1
kind: Pod
metadata:
name: non-privileged
spec:
containers:
- image: busybox:1.35.0
name: busybox
command: ["sh", "-c", "sleep 1h"]
创建 Pod 并确保其正确启动:
$ kubectl apply -f non-privileged.yaml
pod/non-privileged created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
non-privileged 1/1 Running 0 6s
为了演示容器命名空间与主机命名空间之间的隔离,我们将实验利用sysctl来更改主机名。正如您在下令输出中所看到的那样,容器明白执行受限权限:
$ kubectl exec non-privileged -it -- /bin/sh
/ # sysctl kernel.hostname=test
sysctl: error setting key 'kernel.hostname': Read-only file system
/ # exit
要使容器具有特权,请简单地将值true分配给安全上下文属性privileged。在示例 5-5 中的 YAML 清单中显示了一个示例。
示例 5-5. 设置为运行在特权模式中的容器的 Pod
apiVersion: v1
kind: Pod
metadata:
name: privileged
spec:
containers:
- image: busybox:1.35.0
name: busybox
command: ["sh", "-c", "sleep 1h"]
securityContext:
privileged: true
如常创建 Pod。Pod 应该转换为“Running”状态:
$ kubectl apply -f privileged.yaml
pod/privileged created
$ kubectl get pod privileged
NAME READY STATUS RESTARTS AGE
privileged 1/1 Running 0 6s
现在您可以看到,相同的sysctl将允许您更改主机名:
$ kubectl exec privileged -it -- /bin/sh
/ # sysctl kernel.hostname=test
kernel.hostname = test
/ # exit
与特权模式相关的容器安全上下文设置是属性allowPrivilegeEscalation。此属性将允许运行容器的历程获得比父历程更多的权限。该属性的默认值为false,但假如看到将属性设置为true,请对其利用举行严格检察。在大多数情况下,您不需要此功能。
景象:开发者不遵循 Pod 安全最佳实践
假设开发人员在 Kubernetes 功能方面,特别是适用于安全最佳实践的方面,没有广泛的知识是不公平的。在前一节中,我们了解了安全上下文以及要避免利用的设置。开发人员大概不知道这些最佳实践,没有持续的教育,因此大概会创建利用问题安全设置或根本倒霉用安全设置的 Pod。Figure 5-2 显示了一名开发人员利用从互联网上找到的复制清单在启用特权模式下创建 Pod。攻击者将乐意利用这种设置获取优势。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0502.png
图 5-2. 开发人员创建启用特权模式的 Pod
作为 Kubernetes 安全专家,您就是这里的关键人物。Kubernetes 生态体系提供了核心功能和外部工具,用于强制执行 Pod 的安全标准,以便没有正确设置的对象将被拒绝或至少会举行审计。下一节将探究名为 Pod 安全入场的 Kubernetes 核心功能。
理解 Pod 安全入场(PSA)
Kubernetes 的旧版本附带了名为 Pod 安全计谋(PSP)的功能。Pod 安全计谋是一个概念,帮助强制执行 Pod 对象的安全标准。Kubernetes 1.21 弃用了 Pod 安全计谋,并引入了其替代功能 Pod 安全入场。PSA 决定要遵循哪个 Pod 安全标准(PSS)。PSS 界说了从高度限制到高度宽松的安全计谋范围。
然而,Kubernetes 1.25 完全删除了 Pod 安全计谋。您大概仍然会在旧版本的 CKS 课程中看到此功能。我们在本书中将仅关注 Pod 安全入场。PSA 在 Kubernetes 1.23 中默认启用;但是,您需要声明哪些 Pod 应遵循安全标准。要选择 PSA 功能,您只需在命名空间中添加特定格式的标签。该命名空间中的所有 Pod 将必须遵循声明的标准。
标签包罗三个部分:前缀、模式和级别。前缀 总是利用硬编码值 pod-security.kubernetes.io,背面跟着一个斜线。模式 决定了违规处理方式。最后,级别 规定了遵循的安全标准的程度。这样的标签示例大概如下所示:
metadata:
labels:
pod-security.kubernetes.io/enforce: restricted
模式允许设置三种差别的选项,如 Table 5-1 所示。
Table 5-1. Pod 安全入场模式
模式行为enforce违规将导致 Pod 被拒绝。audit允许创建 Pod。违规将被记载到审计日志中。warn允许创建 Pod。违规将在控制台上显示。 Table 5-2 阐明了由 PSA 设置的级别确定的安全计谋。
Table 5-2. Pod 安全入场级别
级别行为privileged完全无限制的计谋。baseline最低限度的限制性计谋,覆盖了关键标准。restricted遵循从安全角度加固 Pod 的最佳实践的严格限制性计谋。 详细了解 PSA 的内容,请参阅 Kubernetes 文档。
为命名空间执行 Pod 安全标准
在 psa 命名空间中将 PSA 应用于一个 Pod。示例 5-6 展示了命名空间的界说及相关标签的声明。该标签将强制执行最高级别的安全标准。
Example 5-6. 强制执行最高级别安全标准的命名空间
apiVersion: v1
kind: Namespace
metadata:
name: psa
labels:
pod-security.kubernetes.io/enforce: restricted
确保 Pod 在 psa 命名空间中创建。示例 5-7 展示了运行 busybox 镜像的简单 Pod 的 YAML 清单。
Example 5-7. 违背 PSA 限制的 Pod
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: psa
spec:
containers:
- image: busybox:1.35.0
name: busybox
command: ["sh", "-c", "sleep 1h"]
在运行创建 Pod 的下令时,违规将在控制台中呈现。如下所示,该 Pod 无法被创建:
$ kubectl create -f psa-namespace.yaml
namespace/psa created
$ kubectl apply -f psa-violating-pod.yaml
Error from server (Forbidden): error when creating "psa-pod.yaml": pods \
"busybox" is forbidden: violates PodSecurity "restricted:latest": \
allowPrivilegeEscalation != false (container "busybox" must set \
securityContext.allowPrivilegeEscalation=false), unrestricted \
capabilities (container "busybox" must set securityContext. \
capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container \
"busybox" must set securityContext.runAsNonRoot=true), seccompProfile \
(pod or container "busybox" must set securityContext.seccompProfile. \
type to "RuntimeDefault" or "Localhost")
$ kubectl get pod -n psa
No resources found in psa namespace.
您需要设置 Pod 的安全上下文设置,以遵循非常严格的标准。示例 5-8 展示了一个示范 Pod 界说,它不违背 Pod 安全标准。
Example 5-8. 跟随 PSS 的 Pod
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: psa
spec:
containers:
- image: busybox:1.35.0
name: busybox
command: ["sh", "-c", "sleep 1h"]
securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] runAsNonRoot: true runAsUser: 2000 runAsGroup: 3000 seccompProfile: type: RuntimeDefault 现在创建 Pod 对象按预期运行:
$ kubectl apply -f psa-non-violating-pod.yaml
pod/busybox created
$ kubectl get pod busybox -n psa
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 10s
PSA 是 Kubernetes 1.23 版本或更高版本中默认启用的内置功能。它易于采用,允许选择合适的计谋标准,并可设置为强制执行或仅记载违规行为。
不幸的是,PSA 仅适用于具有预界说计谋集的 Pod。您无法编写自界说规则、更改消息传递或变异 Pod 对象,假如不符合 PSS 的话。在下一节中,我们将研究超越 PSA 功能的工具。
了解开放计谋代理(OPA)和 Gatekeeper
Open Policy Agent (OPA) 是一款开源的通用计谋引擎,用于强制执行规则。OPA 不特定于 Kubernetes,可以在其他技术堆栈中利用。其好处之一是能够以非常机动的方式界说计谋。您可以利用名为 Rego 的查询语言编写自己的规则。在 Rego 中编写的验证逻辑确定是否继承或拒绝对象。
Gatekeeper 是 Kubernetes 的一个扩展,利用 OPA。Gatekeeper 允许为任何范例的 Kubernetes API 原语界说和强制执行自界说计谋。因此,它比 PSA 更加机动,但需要更复杂的知识来订定这些规则。Gatekeeper 参与了讨论的 准入控制 阶段,详见 “处理请求”。以下计谋列表试图给您展示 Gatekeeper 的大概性:
[*] 确保所有 Service 对象都需要界说一个带有键 team 的标签。
[*] 确保 Pods 界说的所有容器镜像都从公司内部注册表中拉取。
[*] 确保 Deployments 需要至少控制三个副本。
在撰写本文时,Gatekeeper 允许通过拒绝对象创建来强制执行计谋,假如未满足要求。Gatekeeper 的将来版本还大概提供一种在创建时改变对象的机制。比方,您大概希望为创建的任何对象添加特定的标签键值对。该变动将自动处理添加这些标签。
安装 Gatekeeper
安装 Gatekeeper 相对简单。您只需从 Gatekeeper 项目提供的 YAML 清单创建一堆 Kubernetes 对象即可。您需要有集群管理员权限来正确安装 Gatekeeper。以下下令展示了应用最新发布版的 kubectl 下令。更多信息,请参阅 安装手册。
$ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/\
gatekeeper/master/deploy/gatekeeper.yaml
Gatekeeper 对象已安装在命名空间 gatekeeper-system 中。确保在实验利用 Gatekeeper 之前,命名空间中的所有 Pods 都转入“运行”状态:
$ kubectl get namespaces
NAME STATUS AGE
default Active 29h
gatekeeper-system Active 4s
...
实施 OPA 计谋。
我们将利用一个具体的用例作为示例,演示界说自界说 OPA 计谋所需的各部分。“利用网络计谋限制 Pod 间通信” 解释了如作甚命名空间分配标签,以便从网络计谋中选择。在其核心,我们的自界说 OPA 计谋将确定命名空间需要界说至少一个带有键 app 的标签,以表示命名空间托管的应用步调。
Gatekeeper 要求我们为自界说计谋实现两个组件,约束模板 和 约束。简言之,约束模板利用 Rego 界说规则,并形貌约束的模式。示例 5-9 展示了用于强制执行标签分配的约束模板界说。
示例 5-9. 利用 OPA 约束模板要求至少界说一个单一标签
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
validation:
openAPIV3Schema: <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: | <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels}
required := {label | label := input.parameters.labels}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", )
}
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO1-1
声明用于约束的范例。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO1-2
指定约束的验证模式。在这种情况下,我们允许传递名为 labels 的属性,其中捕获所需的标签键。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO1-3
利用 Rego 检查标签的存在并将其与所需键的列表举行比力。
约束本质上是约束模板的实现。它利用约束模板界说的种类,并填充终端用户提供的数据。在 示例 5-10 中,种类是 K8sRequiredLabels,我们在约束模板中界说了它。我们正在匹配命名空间,并期望它们界说具有键 app 的标签。
示例 5-10. 界说计谋“数据”的 OPA 约束
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
metadata:
name: ns-must-have-app-label-key
spec:
match: <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters: <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
labels: ["app"]
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO2-1
利用约束模板界说的种类。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO2-2
界说约束模板应用的 API 资源。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO2-3
声明 labels 属性期望存在键 app。
有了相关的 YAML 清单,让我们为约束模板和约束创建对象。假设约束模板写入文件 constraint-template-labels.yaml,约束写入文件 constraint-ns-labels.yaml:
$ kubectl apply -f constraint-template-labels.yaml
constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created
$ kubectl apply -f constraint-ns-labels.yaml
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-app-label-key created
您可以通过快速运行的下令验证验证行为。以下下令实验创建一个没有标签分配的新命名空间。 Gatekeeper 将呈现错误消息并阻止对象的创建:
$ kubectl create ns governed-ns
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" \
denied the request: you must provide labels: {"app"}
让我们确保我们现实上可以创建具有预期标签分配的命名空间。示例 5-11 展示了这样一个命名空间的 YAML 清单。
示例 5-11. 具有标签分配的命名空间的 YAML 清单
apiVersion: v1
kind: Namespace
metadata:
labels:
app: orion
name: governed-ns
以下下令从名为 namespace-app-label.yaml 的 YAML 清单文件创建对象:
$ kubectl apply -f namespace-app-label.yaml
namespace/governed-ns created
这个简单的例子演示了 OPA Gatekeeper 的利用。您可以在 OPA Gatekeeper Library 中找到很多其他示例。只管 CKS 课程没有明白阐明,您大概还想查看最近在 Kubernetes 社区中获得了很多关注的项目 Kyverno。
管理秘密
Kubernetes 安全功能讨论不完整,没有提到 Secrets。我假设你已经非常认识 API 原语 Secret 用于界说敏感数据以及在 Pod 中消费的差别选项。思量到这个主题已经是 CKA 考试的一部分,我在这里不再赘述。更多信息,请参阅《Certified Kubernetes Administrator (CKA) Study Guide》中的相关章节或者Kubernetes 文档。我在“Configuring a Container with a ConfigMap or Secret”章节中讨论在容器中消费 ConfigMaps 和 Secrets 时的安全方面。
CKS 考试更增强调 Secret 管理的更专业方面。其中一个场景是处理可以分配给服务账户的 Secret,我们已经提到过。回顾“Creating a Secret for a service account”以刷新你对该主题的影象。由于我们不会在这里讨论所有内置的 Secret 范例,请阅读相关章节了解它们的用途和创建方式在Kubernetes 文档中。
存储 Secrets 键值对的中心位置是 etcd。让我们来看看,假如攻击者能够访问 Kubernetes 后端存储集群数据,大概会出现的埋伏问题。
场景:攻击者获得访问运行 etcd 的节点
etcd 运行的位置取决于你的 Kubernetes 集群的拓扑结构。为了这个场景,我们假设 etcd 运行在控制平面节点上。存储在 etcd 中的任何数据都以未加密情势存在,因此访问控制平面节点允许以明文情势读取 Secrets。图 5-3 展示了攻击者访问控制平面节点和因此在 etcd 中的未加密 Secrets。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0503.png
图 5-3. 攻击者获得访问 etcd 并读取 Secrets
缓解这种情况的一种方法是加密存储在 etcd 中的数据。无论是利用 etcdctl 访问 etcd 照旧从文件体系读取 etcd 数据,都不会再暴暴露人类可读的敏感信息。
访问 etcd 数据
我们将首先展示,攻击者在能够登录控制平面节点后怎样读取 etcd 数据。首先,我们需要创建一个 Secret 对象以存储在 etcd 中。利用以下下令来创建一个条目:
$ kubectl create secret generic app-config --from-literal=password=passwd123
secret/app-config created
我们创建了一个带有键值对 password=passwd123 的 Secret。利用 SSH 登录控制平面节点。你可以轻松利用 etcd 客户端工具 etcdctl 从 etcd 中读取一个条目。
利用 etcd 客户端工具 etcdctl
很大概您尚未在控制平面节点上安装etcdctl。请按照安装手册安装工具。在 Debian Linux 上,可以利用sudo apt install etcd-client举行安装。要对 etcd 举行身份验证,您需要提供必需的下令行选项--cacert、--cert和--key。您可以在通常位于/etc/kubernetes/manifests/kube-apiserver.yaml的 API 服务器设置文件中找到相应的值。参数需要以--etcd前缀开头。
以下下令利用必需的 CLI 选项从名为app-config的秘密对象中读取内容。以下输出以十六进制格式显示文件内容。固然不是 100%显着,但您仍然可以从输出中识别出明文的键值对:
$ sudo ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/\
etcd/server.key get /registry/secrets/default/app-config | hexdump -C
000000002f 72 65 67 69 73 74 7279 2f 73 65 63 72 65 74|/registry/secret|
0000001073 2f 64 65 66 61 75 6c74 2f 61 70 70 2d 63 6f|s/default/app-co|
000000206e 66 69 67 0a 6b 38 7300 0a 0c 0a 02 76 31 12|nfig.k8s.....v1.|
0000003006 53 65 63 72 65 74 12d9 01 0a b7 01 0a 0a 61|.Secret........a|
0000004070 70 2d 63 6f 6e 66 6967 12 00 1a 07 64 65 66|pp-config....def|
0000005061 75 6c 74 22 00 2a 2436 38 64 65 65 34 34 38|ault".*$68dee448|
000000602d 34 39 62 37 2d 34 3432 66 2d 39 62 32 66 2d|-49b7-442f-9b2f-|
0000007033 66 39 62 39 62 32 6166 66 36 64 32 00 38 00|3f9b9b2aff6d2.8.|
0000008042 08 08 97 f8 a4 9b 0610 00 7a 00 8a 01 65 0a|B.........z...e.|
000000900e 6b 75 62 65 63 74 6c2d 63 72 65 61 74 65 12|.kubectl-create.|
000000a006 55 70 64 61 74 65 1a02 76 31 22 08 08 97 f8|.Update..v1"....|
000000b0a4 9b 06 10 00 32 08 4669 65 6c 64 73 56 31 3a|.....2.FieldsV1:|
000000c031 0a 2f 7b 22 66 3a 6461 74 61 22 3a 7b 22 2e|1./{"f:data":{".|
000000d022 3a 7b 7d 2c 22 66 3a70 61 73 73 77 6f 72 64|":{},"f:password|
000000e022 3a 7b 7d 7d 2c 22 663a 74 79 70 65 22 3a 7b|":{}},"f:type":{|
000000f07d 7d 42 00 12 15 0a 0870 61 73 73 77 6f 72 64|}}B.....password|
0000010012 09 70 61 73 73 77 6431 32 33 1a 06 4f 70 61|..passwd123..Opa|
0000011071 75 65 1a 00 22 00 0a |que.."..|
接下来,我们将加密存储在 etcd 中的秘密,然后利用相同的下令验证现有条目。
加密 etcd 数据
您可以利用下令行选项--encryption-provider-config控制 API 数据在 etcd 中怎样加密,该选项提供给 API 服务器历程。分配给参数的值需要指向一个界说了EncryptionConfiguration对象的设置文件。我们将首先创建设置文件,然后设置 API 服务器历程来消耗它。
生成一个 32 字节的随机密钥并对其举行 base64 编码。该值用于设置所谓的加密设置中的提供步调:
$ head -c 32 /dev/urandom | base64
W68xlPT/VXcOSEZJvWeIvkGJnGfQNFpvZYfT9e+ZYuY=
接下来,我们将利用 base64 编码的密钥并将其分配给加密设置中的提供步调,如示例 5-12 所示。将内容生存在文件/etc/kubernetes/enc/enc.yaml中。
示例 5-12. 用于加密设置的 YAML 清单
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
providers:
- aescbc:
keys:
- name: key1
secret: W68xlPT/VXcOSEZJvWeIvkGJnGfQNFpvZYfT9e+ZYuY= <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
- identity: {}
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO3-1
界说要在 etcd 中加密的 API 资源。我们这里只加密秘密数据。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_minimizing_microservice_vulnerabilities_CO3-2
分配给 AES-CBC 加密提供步调的 base64 编码密钥。
编辑位于/etc/kubernetes/manifests/kube-apiserver.yaml的清单,这是界说怎样在 Pod 中运行 API 服务器的 YAML 清单。添加参数--encryption-provider-config,并界说设置文件的 Volume 及其挂载路径,如下所示:
$ sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: \
192.168.56.10:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
volumeMounts:
...
- name: enc
mountPath: /etc/kubernetes/enc
readonly: true
volumes:
...
- name: enc
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
...
运行 API 服务器的 Pod 应该会自动重新启动。这个过程大概需要几分钟时间。一旦完全重启,您应该能够查询它:
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
...
kube-apiserver-control-plane 1/1 Running 0 69s
新的秘密将自动加密。现有的秘密需要更新。您可以运行以下下令来跨所有命名空间执行秘密的更新。这包罗default命名空间中名为app-config的秘密:
$ kubectl get secrets --all-namespaces -o json | kubectl replace -f -
...
secret/app-config replaced
运行我们之前利用的etcdctl下令将显示aescbc提供步调已用于加密数据。暗码值不再能以明文读取:
$ sudo ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/\
etcd/server.key get /registry/secrets/default/app-config | hexdump -C
000000002f 72 65 67 69 73 74 7279 2f 73 65 63 72 65 74|/registry/secret|
0000001073 2f 64 65 66 61 75 6c74 2f 61 70 70 2d 63 6f|s/default/app-co|
000000206e 66 69 67 0a 6b 38 733a 65 6e 63 3a 61 65 73|nfig.k8s:enc:aes|
0000003063 62 63 3a 76 31 3a 6b65 79 31 3a ae 26 e9 c2|cbc:v1:key1:.&..|
000000407b fd a2 74 30 24 85 613c 18 1e 56 00 a1 24 65|{..t0$.a<..V..$e|
0000005052 3c 3f f1 24 43 9f 6dde 5f b0 84 32 18 84 47|R<?.$C.m._..2..G|
00000060d5 30 e9 64 84 22 f5 d00b 6f 02 af db 1d 51 34|.0.d."...o....Q4|
00000070db 57 c8 17 93 ed 9e 00ea 9a 7b ec 0e 75 0c 49|.W........{..u.I|
000000806a e9 97 cd 54 d4 ae 6bb6 cb 65 8a 5d 4c 3c 9c|j...T..k..e.]L<.|
00000090db 9b ed bc ce bf 3c eff6 2e cb 6d a2 53 25 49|......<....m.S%I|
000000a0d4 26 c5 4c 18 f3 65 bba8 4c 0f 8d 6e be 7b d3|.&.L..e..L..n.{.|
000000b024 9b a8 09 9c bb a3 f953 49 78 86 f5 24 e7 10|$.......SIx..$..|
000000c0ad 05 45 b8 cb 31 bd 38b6 5c 00 02 b2 a4 62 13|..E..1.8.\....b.|
000000d0d5 82 6b 73 79 97 7e fa2f 5d 3b 91 a0 21 50 9d|..ksy.~./];..!P.|
000000e077 1a 32 44 e1 93 9b 9cbe bf 49 d2 f9 dc 56 23|w.2D......I...V#|
000000f007 a8 ca a5 e3 e7 d1 ae9c 22 1f 98 b1 63 b8 73|........."...c.s|
0000010066 3f 9f a5 6a 45 60 a781 eb 32 e5 42 4d 2b fd|f?..jE`...2.BM+.|
0000011065 6c c2 c7 74 9f 1d 6a1c 24 32 0e 7a 94 a2 60|el..t..j.$2.z..`|
0000012022 77 58 c9 69 c3 55 72e8 fb 0b 63 9d 7f 04 31|"wX.i.Ur...c...1|
0000013000 a2 07 76 af 95 4e 030a 92 10 b8 bb 1e 89 94|...v..N.........|
0000014045 60 01 45 bf d7 95 dfff 2e 9e 31 0a |E`.E.......1.|
0000014d
要了解有关加密 etcd 数据的更多详细信息,请参阅Kubernetes 文档。在那里,您将找到关于其他加密提供者的额外信息,怎样轮换解密密钥以及思量用于高可用 (HA) 集群设置的过程。
理解容器运行时沙盒
容器在与主机环境隔离的容器运行时中运行。运行在容器中的历程或应用步调可以通过 syscalls 与内核交互。现在,我们可以在单个 Kubernetes 集群节点上运行多个容器(由 Pod 控制),因此利用相同的内核。在某些条件下,漏洞大概导致在容器运行的历程“突破”其隔离环境,并访问在同一主机上运行的另一个容器。容器运行时沙盒与常规容器运行时并行运行,但通过增强历程隔离添加了额外的安全层。
有几种情况下利用容器运行时沙盒大概是故意义的。比方,您的 Kubernetes 集群利用相同的基础办法处理差别客户的工作负载,这种称为多租户环境。另一个希望依赖更强容器隔离性的缘故起因是,您大概不信托从公共注册表拉取的容器镜像中运行的历程或应用步调,特别是当您无法验证创建者或其运行时行为时。
景象:攻击者获取对另一个容器的访问权限
在这种情况下,我们面对的是开发者从公共注册表拉取容器镜像,如 Pod 所引用。该容器未经安全漏洞扫描。攻击者可以推送容器镜像的新标签来执行恶意代码。在实例化从该镜像运行的容器之后,运行在容器 1 的内核组中的恶意代码可以访问运行在容器 2 中的历程。正如您在图 5-4 中所看到的,两个容器利用同一主机体系的内核。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0504.png
图 5-4. 攻击者获取对另一个容器的访问权限
一般来说,盲目信托公共容器镜像并不是一个好主意。确保这样的容器镜像以更高的隔离运行的一种方法是容器运行时沙盒。下一节将向您介绍两种实现方式,这两种方式在课程中都有明白提到。
可用的容器运行时沙盒实现
在本书中,我们只讨论两种容器运行时沙箱实现,Kata Containers和gVisor。
安装和设置 gVisor
示例 5-14. 利用运行时类的 Pod 的 YAML 清单
创建和利用运行时类
接下来,设置用于签署归档和存储库的密钥。正如您在以下下令中所见,gVisor 托管在 Google 存储中:
$ sudo apt-get update && \
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg
Kata containers 通过在轻量级捏造机中运行来实现容器隔离。gVisor 接纳了差别的方法。它有用地实现了在主机体系上运行的 Linux 内核。因此,主机体系上的所有容器不再共享体系调用。
$ curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/\
keyrings/gvisor-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/\
gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases \
release main" | sudo tee /etc/apt/sources.list.d/gvisor.list > /dev/null
最后,重新启动 containerd 以使更改生效:
$ sudo apt-get update && sudo apt-get install -y runsc
现在我们可以在 Pod 的设置中引用运行时类名gvisor。Example 5-14 展示了一个分配了spec.runtimeClassName属性的 Pod 界说,指定了运行时类。
$ cat <<EOF | sudo tee /etc/containerd/config.toml
version = 2
shim_debug = true
runtime_type = "io.containerd.runc.v2"
runtime_type = "io.containerd.runsc.v1"
EOF
利用apply下令创建运行时类和 Pod 对象:
$ sudo systemctl restart containerd
示例 5-13. 利用 runsc 处理步调界说运行时类的 YAML 清单
gVisor 包罗一个名为 runsc 的 Open Container Initiative (OCI)运行时。runsc 运行时与 Docker 和 Kubernetes 等工具集成,用于运行容器运行时沙箱。以下下令从存储库安装可执行文件:
假设我们正在利用 containerd 作为容器运行时。您需要向 containerd 添加一些设置以使其意识到 runsc。您可以在 gVisor 文档中找到其他容器运行时的类似阐明:
利用容器运行时沙箱在 Pod 中是一个两步调的过程。首先,您需要创建一个运行时类。RuntimeClass 是一个 Kubernetes API 资源,用于界说容器运行时的设置。Example 5-13 展示了一个利用 runsc 处理步调的容器运行时的 YAML 清单。
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
以下instructions形貌了利用apt软件包管理器在 Linux 上安装 gVisor 所需的步调。您需要在所有被声明为工作节点的主机机器上重复这些步调。在考试中,您不需要安装 gVisor 或 Kata Containers。您可以假设容器运行时沙箱已经安装并设置好了。
从以下下令开始安装 gVisor 的依赖项:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
runtimeClassName: gvisor
containers:
- name: nginx
image: nginx:1.23.2
对于这些容器运行时沙箱实现的特性集或特定用例的深入讨论超出了本书的范围。我们将简单地学习怎样利用一个解决方案作为示例,即 gVisor,并怎样将其与 Kubernetes 集成。详细比力,请查看讲座“Kata Containers 和 gVisor:定量比力”。
$ kubectl apply -f runtimeclass.yaml
runtimeclass.node.k8s.io/gvisor created
$ kubectl apply -f pod.yaml
pod/nginx created
您可以通过设置容器运行时沙箱来验证容器是否正在运行。只需执行 dmesg 下令来检查内核环形缓冲区。下令的输出应该提到 gVisor,如下所示:
$ kubectl exec nginx -- dmesg
[ 0.000000] Starting gVisor...
[ 0.123202] Preparing for the zombie uprising...
[ 0.415862] Rewriting operating system in Javascript...
[ 0.593368] Reading process obituaries...
[ 0.741642] Segmenting fault lines...
[ 0.797360] Daemonizing children...
[ 0.831010] Creating bureaucratic processes...
[ 1.313731] Searching for needles in stacks...
[ 1.455084] Constructing home...
[ 1.834278] Gathering forks...
[ 1.928142] Mounting deweydecimalfs...
[ 2.109973] Setting up VFS...
[ 2.157224] Ready!
理解 Pod 与 Pod 之间的 mTLS 加密
在 “利用网络计谋限制 Pod 与 Pod 通信” 中,我们谈论了 Pod 与 Pod 之间的通信。一个紧张的要点是,除非你订定更严格的网络计谋,否则每个 Pod 都可以通过定位其捏造 IP 地点与任何其他 Pod 通信。默认情况下,两个 Pod 之间的通信是未加密的。
TLS 提供网络通信的加密,通常与 HTTP 协议一起利用。当我们谈论从浏览器对 Web 页面的调用时,就会涉及利用 HTTPS 协议。作为认证过程的一部分,客户端向服务器提供其客户端证书以证实其身份。但服务器不对客户端举行认证。
在加载 Web 页面时,客户端(即浏览器)的身份通常并不紧张。紧张的是 Web 页面证实其身份。双向 TLS(mTLS)类似于 TLS,但两边都必须举行身份验证。这种方法有以下几个好处。首先,通过加密实现安全通信。其次,可以验证客户端身份。攻击者不能轻易假冒另一个 Pod。
景象:攻击者监听两个 Pod 之间的通信
攻击者可以利用默认的未加密 Pod 与 Pod 之间的网络通信行为来加以利用。如你在 图 5-5 中所见,攻击者甚至不需要侵入 Pod。他们可以简单地通过假冒发送端或接收端来监听 Pod 与 Pod 的通信,提取敏感信息,然后用于更高级的攻击向量。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0505.png
图 5-5. 攻击者监听 Pod 与 Pod 之间的通信
通过设置 mTLS,你可以缓解这种情况。下一节将简要介绍实现这一目标的选项。
在 Kubernetes 中采用 mTLS
在 Kubernetes 集群中实现 mTLS 的棘手部分是证书的管理。正如你可以想象的,当实施微服务架构时,我们将不得不处理大量的证书。这些证书通常由官方证书颁发机构(CA)生成,以确保它们可以信托。请求证书涉及向 CA 发送证书署名请求(CSR)。假如 CA 批准请求,它将创建证书,然后署名并返回。建议在证书到期之前分配较短的生命周期,然后重新发行。这个过程称为证书轮换。
CKS 考试需要您了解 mTLS 的详细程度尚不太清晰。请求和批准证书的一般过程在Kubernetes 文档中有形貌。
在大多数情况下,Kubernetes 管理员依赖于 Kubernetes 服务网格来实现 mTLS,而不是手动实现。Kubernetes 服务网格(如 Linkerd 或 Istio)是一种工具,用于为集群添加横切功能,如可观察性和安全性。
另一个选项是利用透明加密来确保流量不会在传输中未加密。一些盛行的 CNI 插件,如Calico和Cilium,已经增加了对WireGuard的支持。WireGuard 是一种开源、轻量级和安全的捏造专用网络(VPN)解决方案,无需设置或管理加密密钥或证书。很多团队更喜欢 WireGuard 而不是服务网格,因为它更易于管理。
考试不涵盖服务网格和 WireGuard。
总结
对于 Pod 来说,执行安全最佳实践非常紧张。在本章中,我们检察了差别的选项。我们关注安全上下文以及怎样在 Pod 和容器级别界说它们。比方,我们可以为容器设置一个安全上下文,使其以非 root 用户运行并防止利用特权模式。通常由开发人员负责界说这些设置。Pod 安全入场是 Kubernetes 的一个功能,可以进一步设置 Pod 安全设置。设置的安全标准可以强制执行、审计或仅记载到标准输出。Gatekeeper 是一个开源项目,实现了用于 Kubernetes 的 Open Policy Agent 的功能。不仅可以管理 Pod 对象的设置,还可以在创建时对其他范例的对象应用计谋。
由 Secrets 界说的键值对以明文存储在 etcd 中。您应该设置 etcd 的加密,以确保攻击者无法从中读取敏感数据。要启用加密,创建一个 EncryptionConfiguration 的 YAML 清单,然后利用下令行选项--encryption-provider-config将其传递给 API 服务器历程。
容器运行时沙盒比常规容器运行时更有用地隔离历程和应用步调。项目 Kata Containers 和 gVisor 是此类容器运行时沙盒的实现,可以安装并设置以与 Kubernetes 一起工作。我们实验了 gVisor。安装和设置 gVisor 后,您需要创建一个指向 runsc 的 RuntimeClass 对象。在 Pod 设置中,通过名称指向 RuntimeClass 对象。
默认情况下,Pod 到 Pod 的通信是未加密且未认证的。双向 TLS 可以使这个过程更安全。互相通信的 Pods 需要提供证书来证实它们的身份。为具有数百个微服务的集群实现 mTLS 是一个繁琐的任务。每个运行微服务的 Pod 需要利用来自客户机机构的批准证书。服务网格可以作为 Kubernetes 集群中添加 mTLS 功能的一种方式。
考试要点
练习利用核心 Kubernetes 功能和外部工具来管理安全设置。
在本章的过程中,我们探究了操纵体系级别的安全设置及怎样通过差别的核心功能和外部工具举行管理。您需要了解差别选项、它们的优势和范围性,并能够根据情境要求应用它们。练习利用安全上下文、Pod 安全考核和 Open Policy Agent Gatekeeper。Kubernetes 生态体系在这方面提供了更多的工具。可以自行探索以扩展视野。
了解 etcd 怎样管理 Secrets 数据。
CKA 考试已经涵盖了利用 Secrets 将敏感设置数据注入到 Pods 的工作流程。我假设您已经知道怎样操纵。每个 Secret 的键值对都存储在 etcd 中。通过学习怎样加密 etcd 来扩展您对 Secret 管理的知识,这样即使攻击者能访问运行 etcd 的主机,也无法以明文读取信息。
知道怎样设置容器运行时沙盒的利用。
容器运行时沙盒有助于为容器增加更严格的隔离。您不需要安装容器运行时沙盒,如 Kata Containers 或 gVisor。但您需要了解怎样通过 RuntimeClass 对象设置容器运行时沙盒的过程,并将 RuntimeClass 分配给 Pod 名称。
了解 mTLS 的紧张性。
为所有在 Pod 中运行的微服务设置 mTLS 大概因证书管理而非常繁琐。在考试中,了解希望为 Pod 到 Pod 通信设置 mTLS 的一般用例。只管您大概不需要手动实现它,但生产 Kubernetes 集群利用服务网格提供 mTLS 作为一种功能。
样例练习
这些练习的解决方案可以在 附录 中找到。
[*] 创建一个名为 busybox-security-context 的 Pod,利用容器镜像 busybox:1.28,运行下令 sh -c sleep 1h。添加一个范例为 emptydir 的 Volume,并将其挂载到路径 /data/test。设置安全上下文,包罗以部属性:runAsUser: 1000,runAsGroup: 3000,fsGroup: 2000。此外,将属性 allowPrivilegeEscalation 设置为 false。
登入容器,导航到目录 /data/test,并创建名为 hello.txt 的文件。检查文件分配的群组。数值是多少?退出容器。
[*] 创建一个 Pod 安全性考核(PSA)规则。在名为 audited 的命名空间中,创建一个 Pod 安全性标准(PSS),其级别为 baseline,该信息应该渲染到控制台。
实验在违背 PSS 的命名空间中创建一个 Pod,并在控制台日志中输出消息。您可以提供任何名称、容器映像和安全设置。Pod 是否被创建?需要设置哪些 PSA 级别来防止创建该 Pod?
[*] 在集群上安装 Gatekeeper。创建一个 Gatekeeper ConstraintTemplate 对象,界说一个副本集所控制的副本数的最小和最大值。实例化一个利用该 ConstraintTemplate 的约束对象。设置最小副本数为 3,最大副本数为 10。
创建 创建一个部署对象,将副本数设置为 15。门禁不允许创建该部署、副本集和 Pod,应该渲染一个错误消息。实验再次创建副本数为 7 的部署对象,验证是否乐成创建了所有对象。
[*] 利用 aescbc 提供者为 etcd 设置加密。创建一个范例为 Opaque 的密钥对象。提供键值对 api-key=YZvkiWUkycvspyGHk3fQRAkt。利用 etcdctl 查询该 Secret 的值,查看加密值是什么。
[*] 导航到从 GitHub 仓库bmuschko/cks-study-guidecheckout 的目录 app-a/ch05/gvisor,启动运行集群的捏造机利用下令 vagrant up。该集群包罗一个名为 kube-control-plane 的单个控制平面节点和一个名为 kube-worker-1 的工作节点。完成后,利用 vagrant destroy -f 关闭集群。
gVisor 已经安装在捏造机 kube-worker-1 上。进入该捏造机并创建一个名为 container-runtime-sandbox 的 RuntimeClass 对象,利用 runsc 作为处理步调。然后创建一个名为 nginx 的 Pod,并利用容器映像 nginx:1.23.2 分配该 RuntimeClass。
先决条件:此练习需要安装工具 Vagrant 和 VirtualBox。
第六章:供应链安全
较早的章节主要集中在保护 Kubernetes 集群及其组件、用于运行集群节点的操纵体系基础办法以及在现有容器镜像上运行工作负载的操纵方面。本章退一步,深入探究了操持、构建和优化容器镜像的过程、最佳实践和工具。
偶尔,你大概不想创建自己的容器镜像,而是消耗由其他团队或公司生产的现有镜像。在利用它们运行工作负载之前,手动或自动扫描已知漏洞的容器镜像应该成为你的检察过程的一部分。我们将讨论与 CKS 考试相关的一些选项,用于识别、分析和减轻预构建容器镜像的安全风险。
在高层次上,本章涵盖以下概念:
[*] 减小基础镜像的占用空间
[*] 保护供应链
[*] 利用用户工作负载的静态分析
[*] 扫描已知漏洞的镜像
减小基础镜像的占用空间
表面上看,构建容器镜像的过程似乎很简单;然而,妖怪每每隐蔽在细节中。对于新手来说,要避免构建过大、充满漏洞而且不优化容器层缓存的容器镜像大概并不显着。在本章的课程中,我们将在 Docker 容器引擎的帮助下解决所有这些问题。
场景:攻击者利用容器漏洞
在界说 Dockerfile 时,你需要做的第一个决定之一是选择一个基础镜像。基础镜像提供操纵体系和额外的依赖项,并大概暴露 shell 访问权限。
在像 Docker Hub 这样的公共注册表上,你可以选择的一些基础镜像体积巨大,而且大概包罗你不肯定需要在其中运行应用步调的功能。操纵体系自己以及基础镜像提供的任何依赖项都大概存在漏洞。
在 图 6-1 中,攻击者能够通过访问容器获取关于其详细信息的信息。这些漏洞现在可以被用作更高级攻击的跳板。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0601.png
图 6-1. 攻击者利用容器镜像漏洞
建议利用一个功能和依赖项最少的基础镜像。接下来的几节将解释创建更优化的基础镜像的方法,这将使构建速率更快,从容器注册表下载速率更快,而且通过减少痴肥的镜像来减小攻击面。接下来的几节将涉及最紧张的技术。你可以在Docker 文档中找到更详细的 Dockerfile 写作最佳实践列表。
选择尺寸小的基础镜像
一些容器镜像大概有千兆字节甚至更多的大小。您真的需要这种容器镜像捆绑的所有功能吗?大概性不大。荣幸的是,很多容器生产商为同一个版本上传了各种变体的容器镜像。其中一个变体是alpine镜像,这是一个小巧、轻量且较少漏洞的 Linux 发行版。正如您在下面的输出中所看到的,下载的带有标签3.17.0的alpine容器镜像只有 7.05MB 的大小:
$ docker pull alpine:3.17.0
...
$ docker image ls alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine 3.17.0 49176f190c7e 3 weeks ago 7.05MB
alpine容器镜像自带一个sh shell,您可以用来排查容器内运行的历程。您可以利用以下下令在一个新的容器中打开交互式 shell:
$ docker run -it alpine:3.17.0 /bin/sh
/ # exit
固然运行时的故障排除功能大概很有用,但将 shell 作为容器镜像的一部分会增加其大小,并大概为攻击者打开大门。此外,容器镜像内的软件越多,其漏洞也会越多。
您可以通过利用由 Google 提供的distroless 镜像进一步减小容器镜像的大小和攻击面。以下下令下载gcr.io/distroless/static-debian11容器镜像的最新标签,并显示其详情。容器镜像的大小仅为 2.34MB:
$ docker pull gcr.io/distroless/static-debian11
...
$ docker image ls gcr.io/distroless/static-debian11:latest
REPOSITORY TAG IMAGE ID CREATED \
SIZE
gcr.io/distroless/static-debian11 latest 901590160d4d 53 years ago \
2.34MB
Distroless 容器镜像不包罗任何 shell,您可以通过运行以下下令观察到这一点:
$ docker run -it gcr.io/distroless/static-debian11:latest /bin/sh
docker: Error response from daemon: failed to create shim task: OCI runtime \
create failed: runc create failed: unable to start container process: exec: \
"/bin/sh": stat /bin/sh: no such file or directory: unknown.
Kubernetes 提供了用于故障排除 distroless 容器的临时容器的概念。这些容器被操持为可抛弃的,可以用于排查通常不允许打开 shell 的最小化容器。讨论临时容器超出了本书的范围,但您可以在Kubernetes 文档中找到更多信息。
利用多阶段方法构建容器镜像
作为开发者,您可以决定将应用步调代码作为 Dockerfile 指令的一部分举行构建。这个过程大概包罗编译代码和构建一个应该成为容器镜像入口点的二进制文件。拥有实施这一过程所需的所有工具和依赖项将自动增加容器镜像的大小,而且您在运行时也不再需要这些依赖项。
在 Docker 中利用多阶段构建的头脑是将构建阶段与运行阶段分离。因此,构建阶段中需要的所有依赖项在完成历程后都会被抛弃,因此不会出现在终极的容器镜像中。这种方法通过去除所有不必要的内容,大大减小了容器镜像的大小。
只管我们不会详细介绍制作和完全理解多阶段 Dockerfile 的细节,但我想在高层次上展示一下它们的区别。我们将首先展示一个 Dockerfile,它利用编程语言 Go 构建和测试一个简单的步调,如示例 6-1 所示。实质上,我们利用了一个包罗 Go 1.19.4 的基础镜像。Go 运行时提供了go下令,我们将调用它来执行测试并构建应用步调的二进制文件。
示例 6-1. 利用 Go 基础镜像构建和测试 Go 步调
FROM golang:1.19.4-alpine <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go test -v <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
RUN go build -o /go-sample-app . <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
CMD ["/go-sample-app"]
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO1-1
利用了一个 Go 基础镜像
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO1-2
执行针对应用代码的测试。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO1-3
构建 Go 应用步调的二进制文件。
您可以利用docker build下令生成镜像,如下所示:
$ docker build . -t go-sample-app:0.0.1
...
效果容器镜像的大小相当大,为 348MB,这其中有很好的理由。它包罗了 Go 运行时,只管在启动容器时现实上不再需要它。go build下令生成的二进制文件将作为容器启动时的下令运行:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
go-sample-app 0.0.1 88175f3ab0d3 44 seconds ago 358MB
接下来,我们将看看多阶段方法。在多阶段的 Dockerfile 中,您至少界说了两个阶段。在示例 6-2 中,我们指定了一个别名为build的阶段来运行测试并构建二进制文件,类似于之前的操纵。第二个阶段将由阶段build生成的二进制文件复制到一个专用目录中;然而,它利用alpine基础镜像来运行它。在运行docker build下令时,阶段build将不再包罗在终极的容器镜像中。
示例 6-2. 作为多阶段 Dockerfile 的一部分构建和测试 Go 步调
FROM golang:1.19.4-alpine AS build <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
RUN apk add --no-cache git
WORKDIR /tmp/go-sample-app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go test -v <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
RUN go build -o ./out/go-sample-app . <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
FROM alpine:3.17.0 <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/4.png>
RUN apk add ca-certificates
COPY --from=build /tmp/go-sample-app/out/go-sample-app /app/go-sample-app <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
CMD ["/app/go-sample-app"]
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO2-1
在名为build的阶段中,利用了一个 Go 基础镜像来构建和测试步调。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO2-2
执行针对应用代码的测试。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO2-3
构建 Go 应用步调的二进制文件。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO2-4
利用一个更小的基础镜像来运行容器中的应用步调。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO2-5
复制在build阶段生成的应用步调二进制文件,并将其用作启动容器时运行的下令。
利用 alpine 基础镜像时,生成的容器镜像大小显著较小,仅为 12MB。您可以通过再次运行 docker build 下令并列出容器镜像大小来验证效果:
$ docker build . -t go-sample-app:0.0.1
...
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEgo-sample-app 0.0.1 88175f3ab0d3 44 seconds ago 12MB 我们不仅减少了大小,还通过包罗更少的依赖项减少了攻击面。通过利用 distroless 基础镜像而不是 alpine 基础镜像,你可以进一步减小容器镜像的大小。
减少层数
每个 Dockerfile 都由一系列有序的指令组成。只有一些指令会在终极容器镜像中创建只读层。这些指令是 FROM、COPY、RUN 和 CMD。所有其他指令不会创建层,因为它们会创建临时中间镜像。向容器镜像添加的层数越多,构建执行时间大概会越慢,容器镜像的大小大概会越大。因此,需要审慎选择 Dockerfile 中的指令,以只管减小容器镜像的占用空间。
通常会连续执行多个下令。您可以利用单独的 RUN 指令列表来界说这些下令,如 示例 6-3 所示。
示例 6-3. 指定多个 RUN 指令的 Dockerfile
FROM ubuntu:22.10
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install -y curl
每个 RUN 指令将创建一个层,大概会增加容器镜像的大小。利用 && 将它们串联在一起更有用,确保只生成单个层。示例 6-4 展示了这种优化技术的示例。
示例 6-4. 指定多个 RUN 指令的 Dockerfile
FROM ubuntu:22.10
RUN apt-get update -y && apt-get upgrade -y && apt-get install -y curl
利用容器镜像优化工具
很轻易忘记之前提到的优化实践。开源社区开发了一些工具,可以帮助检查生成的容器镜像。它们的功能提供了有用的提示,可以减少不必要的层、文件和文件夹:
DockerSlim
DockerSlim 将通太过析应用步调及其依赖项来优化和保护您的容器镜像。您可以在该工具的 GitHub 仓库 中获取更多信息。
Dive
Dive 是一个用于探索嵌入容器镜像中层的工具。它可以帮助您轻松识别不必要的层,进而举行进一步优化。Dive 的代码和文档可在 GitHub 仓库 中找到。
这只是容器镜像优化工具的简短列表。在 “工作负载的静态分析” 中,我们将看看其他专注于 Dockerfile 和 Kubernetes 清单的静态分析工具。
保护供应链
供应链自动化生产容器镜像并在运行时环境中操纵的过程,在这种情况下是 Kubernetes。我们已经提到了一些可以集成到供应链中以支持容器镜像优化方面的工具。在本节中,我们将扩展到针对安全方面的实践。参考册本 Container Security 作者 Liz Rice(O’Reilly)以获取更多信息。
签署容器镜像
在将容器镜像推送到容器注册表之前,您可以对其举行署名。署名可以通过docker trust sign下令实现,该下令会向容器镜像添加一个署名,即所谓的图像择要。图像择要是根据容器镜像的内容派生的,通常以 SHA256 的情势表示。在消费容器镜像时,Kubernetes 可以将图像择要与镜像内容举行比力,以确保其未被篡改。
场景:攻击者将恶意代码注入容器镜像
验证图像择要的 Kubernetes 组件是 kubelet。假如您将 image pull policy 设置为 Always,即使 kubelet 在之前已经下载和验证过容器镜像,它也会从容器注册表中查询图像择要。
攻击者可以实验修改现有容器镜像的内容,注入恶意代码,并将其上传到具有相同标签的容器注册表中,如 图 6-2 所示。然后,运行在容器中的恶意代码大概会将敏感信息发送到攻击者可访问的第三方服务器。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0602.png
图 6-2. 攻击者将恶意代码注入容器镜像
容器镜像的校验和验证不是自动举行的
图像择要验证在 Kubernetes 中是一种选择性功能。在界说 Pod 时,请确保明白指定所有容器镜像的图像择要。
验证容器镜像
在 Kubernetes 中,您可以将 SHA256 图像择要附加到容器的规范中。比方,可以通过属性spec.containers[].image来实现。图像择要通常可以在容器注册表中找到。比方,图 6-3 显示了 Docker Hub 上 alpine:3.17.0 容器镜像的图像择要(https://oreil.ly/ZAV_H)。在此示例中,图像择要为 sha256:c0d488a800e4127c334ad20d61d7bc21b4097540327217dfab52262adc02380c。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0603.png
图 6-3. Docker Hub 上 alpine:3.17.0 容器镜像的图像择要
让我们看看图像择要的作用。而不是利用标签,示例 6-5 通过附加图像择要来指定容器镜像。
示例 6-5. 利用有用容器镜像择要的 Pod
apiVersion: v1
kind: Pod
metadata:
name: alpine-valid
spec:
containers:
- name: alpine
image: alpine@sha256:c0d488a800e4127c334ad20d61d7bc21b40 \
97540327217dfab52262adc02380c
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10; done"]
创建 Pod 将按预期工作。将验证镜像择要,并将容器转换为“运行”状态:
$ kubectl apply -f pod-valid-image-digest.yaml
pod/alpine-valid created
$ kubectl get pod alpine-valid
NAME READY STATUS RESTARTS AGE
alpine-valid 1/1 Running 0 6s
示例 6-6 显示了相同的 Pod 界说;但是,已更改了镜像择要,因此它与容器镜像的内容不匹配。
示例 6-6. 利用无效容器镜像择要的 Pod
apiVersion: v1
kind: Pod
metadata:
name: alpine-invalid
spec:
containers:
- name: alpine
image: alpine@sha256:d006a643bccb6e9adbabaae668533c7f2e5 \
111572fffb5c61cb7fcba7ef4150b
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10; done"]
您会发现 Kubernetes 仍然可以创建 Pod 对象,但无法正确验证容器镜像的哈希值。这将导致状态为“ErrImagePull”。正如事故日志中所示,甚至无法从容器注册表中拉取容器镜像:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
alpine-invalid 0/1 ErrImagePull 0 29s
$ kubectl describe pod alpine-invalid
...
Events:
Type Reason Age From Message
---- ------ -------- -------
Normal Scheduled13s default-schedulerSuccessfully assigned default \
/alpine-invalid to minikube
Normal Pulling 13s kubelet Pulling image "alpine@sha256: \
d006a643bccb6e9adbabaae668533c7f2e5111572fffb5c61cb7fcba7ef4150b"
WarningFailed 11s kubelet Failed to pull image \
"alpine@sha256:d006a643bccb6e9adbabaae668533c7f2e5111572fffb5c61cb7fcba7ef4 \
150b": rpc error: code = Unknown desc = Error response from daemon: manifest \
for alpine@sha256:d006a643bccb6e9adbabaae668533c7f2e5111572fffb5c61cb7fcba7e \
f4150b not found: manifest unknown: manifest unknown
WarningFailed 11s kubelet Error: ErrImagePull
Normal BackOff 11s kubelet Back-off pulling image \
"alpine@sha256:d006a643bccb6e9adbabaae668533c7f2e5111572fffb5c61cb7fcba7ef415 \
0b"
WarningFailed 11s kubelet Error: ImagePullBackOff
利用公共镜像注册表
每当创建一个 Pod 时,容器运行时引擎将从容器注册表下载声明的容器镜像(假如本地尚不可用)。可以利用镜像拉取计谋来进一步调整此运行时行为。
图像名称中的前缀声明了注册表的域名;比方,gcr.io/google-containers/debian-base:v1.0.1 指的是 google-containers/debian-base:v1.0.1 在Google Cloud 容器注册表中,由 gcr.io 表示。假如在容器镜像声明中不指定域名,则容器运行时将实验从 docker.io,即Docker Hub 容器注册表 剖析它。
景象:攻击者上传恶意容器镜像
固然从公共容器注册表剖析容器镜像很方便,但也陪同着风险。任何拥有这些容器注册表登录凭据的人都可以上传新的镜像。通常,利用容器镜像并不需要账户。
如图 6-4 所示(#公共注册表攻击者),攻击者可以利用窃取的凭据上传包罗恶意代码的容器镜像到公共注册表。任何引用该注册表中容器镜像的容器都将自动运行恶意代码。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0604.png
图 6-4. 攻击者上传恶意容器镜像
在企业级别上,您需要控制信托的容器注册表。建议在公司网络内设置自己的容器注册表,可以完全控制和管理。或者,您可以在云提供商环境中设置一个私有容器注册表,不允许任何其他人访问。
您可以选择其中一个主要的二进制仓库管理器,比方JFrog Artifactory。该产物完全支持存储、扫描和提供容器镜像。任何利用容器镜像的消费者应仅允许从您的白名单容器注册表中拉取镜像。所有其他容器注册表都应该被拒绝。
利用 OPA GateKeeper 白名单允许的镜像注册表
通过 OPA Gatekeeper 控制容器注册表的利用方式。我们讨论了 “理解开放计谋代理(OPA)和 Gatekeeper” 的安装过程和功能。在这里,我们将触及约束模板和允许信托容器注册表的约束条件。
示例 6-7 展示了我直接从 OPA Gatekeeper 库获取的 约束模板。作为输入属性,约束模板界说了一个字符串数组,表示容器注册表的前缀。Rego 规则不仅验证了属性 spec.containers[] 的分配容器镜像,还验证了 spec.initContainers[] 和 spec.ephemeralContainers[]。
示例 6-7. 用于执行容器注册表强制约束的 OPA Gatekeeper 约束模板。
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
annotations:
metadata.gatekeeper.sh/title: "Allowed Repositories"
metadata.gatekeeper.sh/version: 1.0.0
description: >-
Requires container images to begin with a string from the specified list.
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
description: The list of prefixes a container image is allowed to have.
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers
satisfied := ; \
good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("container <%v> has an invalid image repo <%v>, allowed \
repos are %v", )
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers
satisfied := ; \
good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("initContainer <%v> has an invalid image repo <%v>, \
allowed repos are %v", [container.name, container.image, \
input.parameters.repos])
}
violation[{"msg": msg}] {
container := input.review.object.spec.ephemeralContainers
satisfied := ; \
good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("ephemeralContainer <%v> has an invalid image repo <%v>, \
allowed repos are %v", [container.name, container.image, \
input.parameters.repos])
}
在 示例 6-8 中显示的约束负责界说我们想要允许的容器注册表。通常会选择公司网络中托管的服务器的域名。在这里,我们将利用 gcr.io/ 作为演示目标。
示例 6-8. OPA Gatekeeper 约束,将 Google Cloud 注册表分配为受信托的存储库。
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: repo-is-gcr
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
repos:
- "gcr.io/"
让我们利用 apply 下令创建这两个对象:
$ kubectl apply -f allowed-repos-constraint-template.yaml
constrainttemplate.templates.gatekeeper.sh/k8sallowedrepos created
$ kubectl apply -f gcr-allowed-repos-constraint.yaml
k8sallowedrepos.constraints.gatekeeper.sh/repo-is-gcr created
在约束中,我们没有指定规则应适用于的命名空间。因此,它们将适用于集群中的所有命名空间。以下下令验证白名单规则是否按预期工作。以下下令实验利用来自 Docker Hub 的 nginx 容器镜像创建一个 Pod。创建 Pod 被拒绝,并显示恰当的错误消息:
$ kubectl run nginx --image=nginx:1.23.3
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" \
denied the request: container <nginx> has an invalid image \
repo <nginx:1.23.3>, allowed repos are ["gcr.io/"]
下一个下令创建一个 Pod,其容器镜像来自 Google Cloud 容器注册表。操纵被允许,而且创建了 Pod 对象:
$ kubectl run busybox --image=gcr.io/google-containers/busybox:1.27.2
pod/busybox created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
busybox 0/1 Completed 1 (2s ago) 3s
利用 ImagePolicyWebhook Admission Controller 插件为允许的镜像注册表设置白名单。
另一种验证允许利用的镜像注册表的方法是拦截在创建 Pod 时向 API 服务器发出的调用。通过启用 Admission Controller 插件可以实现此机制。设置后,当 API 服务器接收请求时,会自动调用 Admission Controller 插件的逻辑,但在验证调用者身份后。我们已经讨论了 API 调用必须颠末的阶段,见 “处理请求”。
Admission Controller 提供了在请求生效之前批准、拒绝或变更请求的方式。比方,我们可以检查与创建 Pod 的 API 请求一起发送的数据,迭代分配的容器镜像,并执行自界说逻辑以验证容器镜像的表示法。假如容器镜像不符合预期的约定,我们可以拒绝创建 Pod。图 6-5 展示了高级别的工作流程。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0605.png
图 6-5. 拦截特定 Pod API 调用并利用 Webhook 处理它
ImagePolicyWebhook 准入控制器插件是我们可以设置用于拦截 Kubernetes API 调用的插件之一。 您可以从其名称推断出插件的功能。 它为 Pod 中界说的所有容器镜像界说计谋。 为了与界说的计谋比力容器镜像,插件通过 HTTPS 调用到一个服务外部于 Kubernetes 的 webhook。 外部服务然后决定怎样验证数据。 在准入控制器插件的上下文中,外部服务也被称为后端。
实施后端应用
后端应用可以利用您选择的编程语言实现。 它必须满足以下三个要求:
[*] 这是一个可以处理 HTTPS 请求的 Web 应用步调。
[*] 它可以继承和剖析 JSON 请求有用载荷。
[*] 它可以发送一个 JSON 响应有用载荷。
实施后端应用不是 CKS 考试的一部分,但您可以在本书的GitHub 仓库中找到一个基于 Go 的示例实现。 请注意,该代码不被视为生产就绪。
以下下令演示了应用步调在https://localhost:8080/validate上的运行时行为。 您可以在Kubernetes 文档中找到示例请求和响应 JSON 主体。
以下 curl 下令调用验证逻辑以验证容器镜像 nginx:1.19.0。 如 JSON 响应所示,镜像被拒绝:
$ curl -X POST -H "Content-Type: application/json" -k -d \'{"apiVersion": \
"imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", "spec": \
{"containers": [{"image": "nginx:1.19.0"}]}}' https://localhost:8080/validate
{"apiVersion": "imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", \
"status": {"allowed": false, "reason": "Denied request: [container 1 \
has an invalid image repo nginx:1.19.0, allowed repos are ]"}}
以下 curl 下令调用验证逻辑以验证容器镜像 gcr.io/nginx:1.19.0。 如 JSON 响应所示,镜像被允许:
$ curl -X POST -H "Content-Type: application/json"-k -d '{"apiVersion": \
"imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", "spec": {"containers": \
[{"image": "gcr.io/nginx:1.19.0"}]}}' https://localhost:8080/validate
{"apiVersion": "imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", \
"status": {"allowed": true, "reason": ""}}
设置 ImagePolicyWebhook 准入控制器插件
考试中,您需要理解怎样“毗连”ImagePolicyWebhook 准入控制器插件。 本节将引导您完成此过程。 首先,您需要为准入控制器创建一个设置文件,以便它知道要利用哪些插件以及运行时应怎样行为。 创建文件/etc/kubernetes/admission-control/image-policy-webhook-admission-config.yaml并填入示例 6-9 中显示的内容。
示例 6-9. 准入控制器设置文件
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
configuration:
imagePolicy:
kubeConfigFile: /etc/kubernetes/admission-control/ \
imagepolicywebhook.kubeconfig <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
allowTTL: 50
denyTTL: 50
retryBackoff: 500
defaultAllow: false <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO3-1
提供了 ImagePolicyWebhook 插件的设置。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO3-2
指向用于设置后端的设置文件。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO3-3
假如无法访问后端,则拒绝 API 请求。 默认值为 true,但将其设置为 false 更为明智。
接下来,创建由 plugins[].configuration.imagePolicy.kubeConfigFile 属性引用的文件。该文件的内容指向外部服务的 HTTPS URL。它还界说了磁盘上的客户端证书和密钥文件,以及 CA 文件。示例 6-10 显示了设置文件的内容。
示例 6-10. 镜像计谋设置文件
apiVersion: v1
kind: Config
preferences: {}
clusters:
- name: image-validation-webhook
cluster:
certificate-authority: /etc/kubernetes/admission-control/ca.crt
server: https://image-validation-webhook:8080/validate <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
contexts:
- context:
cluster: image-validation-webhook
user: api-server-client
name: image-validation-webhook
current-context: image-validation-webhook
users:
- name: api-server-client
user:
client-certificate: /etc/kubernetes/admission-control/api-server-client.crt
client-key: /etc/kubernetes/admission-control/api-server-client.key
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_supply_chain_security_CO4-1
后端服务的 URL。必须利用 HTTPS 协议。
启用 API 服务器的 ImagePolicyWebhook 准入控制器插件,并将准入控制器指向设置文件。为实现此目标,请编辑 API 服务器的设置文件,通常位于 /etc/kubernetes/manifests/kube-apiserver.yaml。
查找下令行选项 --enable-admission-plugins,并在现有插件列表中追加值 ImagePolicyWebhook,以逗号分隔。假如尚不存在,请提供下令行选项 --admission-control-config-file,并将值设置为 /etc/kubernetes/admission-control/image-policy-webhook-admission-configuration.yaml。由于设置文件位于新目录中,您将需要将其界说为 Volume 并将其挂载到容器中。示例 6-11 显示了 API 服务器容器的相关选项。
示例 6-11. API 服务器设置文件
...
spec:
containers:
- command:
- kube-apiserver
- --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
- --admission-control-config-file=/etc/kubernetes/admission-control/ \
image-policy-webhook-admission-configuration.yaml
...
volumeMounts:
...
- name: admission-control
mountPath: /etc/kubernetes/admission-control
readonly: true
volumes:
...
- name: admission-control
hostPath:
path: /etc/kubernetes/admission-control
type: DirectoryOrCreate
...
运行 API 服务器的 Pod 应自动重启。此过程大概需要几分钟时间。假如 API 服务器未能自行启动,请利用下令 sudo systemctl restart kubelet 重新启动 kubelet 服务。一旦完全重启,您应该能够查询它:
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
...
kube-apiserver-control-plane 1/1 Running 0 69s
现在,任何请求创建 Pod 的 API 调用将被路由到后端。根据验证效果,将允许或拒绝对象的创建。
工作负载的静态分析
在本书中,我们讨论了 Dockerfile 和 Kubernetes 清单的最佳实践。您可以手动检查这些文件,查找不良设置,并手动修复它们。这种方法需要大量复杂的知识,而且非常繁琐且易出错。通过恰当的工具以自动化方式分析工作负载文件,会更加方便和高效。贸易和开源静态分析工具的列表很长。在本节中,我们将介绍两个选项的功能,即 Haskell Dockerfile Linter 和 Kubesec。
在企业级别上,当您需要处理数百甚至数千个工作负载文件时,您可以借助持续交付管道的帮助来完成,如 图 6-6 所示。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0606.png
图 6-6. Kubernetes 的示例持续交付管道
相关的静态分析工具可以作为管道早期阶段的质量门控调用,甚至在构建容器镜像、推送到注册表或部署到 Kubernetes 集群之前。有关持续交付原则和实践的深入了解,请参阅 Jez Humble 和 David Farley 的优秀著作 Continuous Delivery(Addison-Wesley Professional)。
利用 Hadolint 分析 Dockerfile
Haskell Dockerfile Linter,也称为 hadolint,是 Dockerfile 的一个代码检查工具。该工具根据 Docker 文档页面上列出的 最佳实践 检查 Dockerfile。示例 6-12 展示了用于构建运行基于 Go 的应用步调的容器镜像的未优化 Dockerfile。
示例 6-12. 一个未优化的 Dockerfile
FROM golang
COPY main.go .
RUN go build main.go
CMD ["./main"]
Haskell Dockerfile Linter 支持一种操纵模式,允许您将 hadolint 可执行文件指向磁盘上的 Dockerfile。您可以看到随后的下令执行,包罗分析产生的发现的告诫消息:
$ hadolint Dockerfile
Dockerfile:1 DL3006 warning: Always tag the version of an image explicitly
Dockerfile:2 DL3045 warning: `COPY` to a relative destination without \
`WORKDIR` set.
下令的输出建议您为基础镜像分配一个标签。现有的 Dockerfile 将拉取 latest 标签,该标签将剖析为最新的 Go 容器镜像。这种做法大概导致 Go 运行时版本与应用步调代码不兼容。界说用于复制资源的工作目录有助于将操纵体系特定的目录和文件与应用步调特定的目录和文件分开。示例 6-13 修复了 Haskell Dockerfile Linter 发现的告诫消息。
示例 6-13. 一个优化后的 Dockerfile
FROM golang:1.19.4-alpine
WORKDIR /app
COPY main.go .
RUN go build main.go
CMD ["./main"]
对修改后的 Dockerfile 的另一次执行将导致乐成的退出代码,而且不会呈现任何额外的告诫消息:
$ hadolint Dockerfile
Dockerfile 现在遵循 hadolint 感知到的最佳实践。
利用 Kubesec 分析 Kubernetes 清单
Kubesec 是分析 Kubernetes 清单的工具。它可以作为二进制文件、Docker 容器、准入控制器插件甚至是 kubectl 插件执行。为了演示其用法,我们将设置一个 YAML 清单文件 pod-initial-kubesec-test.yaml,如 示例 6-14 所示,作为出发点。
示例 6-14. 利用初始安全设置的 Pod YAML 清单
apiVersion: v1
kind: Pod
metadata:
name: kubesec-demo
spec:
containers:
- name: kubesec-demo
image: gcr.io/google-samples/node-hello:1.0
securityContext:
readOnlyRootFilesystem: true
在检查 Pod 设置时,您大概已经根据前几章的内容有一些改进建议。让我们看看 Kubesec 将会保举什么。
通过在 Docker 容器中运行逻辑的最简单方法来调用 Kubesec。以下下令将 YAML 清单的内容传送到标准输入流中。格式化为 JSON 的效果呈现评分,并提供效果的人类可读消息,并建议举行更改:
$ docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin \
< pod-initial-kubesec-test.yaml
[
{
"object": "Pod/kubesec-demo.default",
"valid": true,
"message": "Passed with a score of 1 points",
"score": 1,
"scoring": {
"advise": [
{
"selector": ".spec .serviceAccountName",
"reason": "Service accounts restrict Kubernetes API access and \
should be configured with least privilege"
},
{
"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"
},
{
"selector": "containers[] .resources .requests .cpu",
"reason": "Enforcing CPU requests aids a fair balancing of \
resources across the cluster"
},
{
"selector": ".metadata .annotations .\"container.seccomp.security. \
alpha.kubernetes.io/pod\"",
"reason": "Seccomp profiles set minimum privilege and secure against \
unknown threats"
},
{
"selector": "containers[] .resources .limits .memory",
"reason": "Enforcing memory limits prevents DOS via resource \
exhaustion"
},
{
"selector": "containers[] .resources .limits .cpu",
"reason": "Enforcing CPU limits prevents DOS via resource exhaustion"
},
{
"selector": "containers[] .securityContext .runAsNonRoot == true",
"reason": "Force the running image to run as a non-root user to \
ensure least privilege"
},
{
"selector": "containers[] .resources .requests .memory",
"reason": "Enforcing memory requests aids a fair balancing of \
resources across the cluster"
},
{
"selector": "containers[] .securityContext .capabilities .drop",
"reason": "Reducing kernel capabilities available to a container \
limits its attack surface"
},
{
"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 | \
index(\"ALL\")",
"reason": "Drop all capabilities and add only those required to \
reduce syscall attack surface"
}
]
}
}
]
示例 6-15 中可以找到原始 YAML 清单的修订版本。在这里,我们整合了 Kubesec 建议的一些保举更改。
示例 6-15. 利用改进的安全设置的 Pod YAML 清单
apiVersion: v1
kind: Pod
metadata:
name: kubesec-demo
spec:
containers:
- name: kubesec-demo
image: gcr.io/google-samples/node-hello:1.0
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 20000
capabilities:
drop: ["ALL"]
对改变的 Pod YAML 清单执行相同的 Docker 下令将会呈现出一个改进的分数,并减少建议消息的数量:
$ docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin \
< pod-improved-kubesec-test.yaml
[
{
"object": "Pod/kubesec-demo.default",
"valid": true,
"message": "Passed with a score of 9 points",
"score": 9,
"scoring": {
"advise": [
{
"selector": ".metadata .annotations .\"container.seccomp.security. \
alpha.kubernetes.io/pod\"",
"reason": "Seccomp profiles set minimum privilege and secure against \
unknown threats"
},
{
"selector": ".spec .serviceAccountName",
"reason": "Service accounts restrict Kubernetes API access and should \
be configured with least privilege"
},
{
"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"
}
]
}
}
]
扫描已知漏洞的镜像
记载和发现安全漏洞的主要来源之一是CVE 详情。该页面列出并按分数排名已知的漏洞(CVE)。自动化工具可以识别嵌入容器镜像中的软件组件,将其与中央漏洞数据库举行比力,并通过其严重性标记问题。
在 CKS 课程中明白提到的具备此能力的开源工具之一是Trivy。Trivy 可以以差别的操纵模式运行:作为下令行工具、在容器中、作为 GitHub Action 设置在持续集成工作流中、作为 IDE VSCode 的插件以及作为 Kubernetes 操纵者。关于可用安装选项和流程的概述,请参阅Trivy 文档。在考试期间,您无需安装此工具,它已经预先设置好。您只需要理解怎样运行它、怎样解释和修复找到的漏洞即可。
假设您已安装了 Trivy 的下令行实现。您可以利用以下下令检查 Trivy 的版本:
$ trivy -v
Version: 0.36.1
Vulnerability DB:
Version: 2
UpdatedAt: 2022-12-13 12:07:14.884952254 +0000 UTC
NextUpdate: 2022-12-13 18:07:14.884951854 +0000 UTC
DownloadedAt: 2022-12-13 17:09:28.866739 +0000 UTC
如您在图 6-7 中所见,Trivy 指示了从中央数据库下载已知漏洞副本的时间戳。Trivy 可以以各种情势扫描容器镜像。子下令image期望您简单地拼写出镜像名称和标签,在本例中为python:3.4-alpine。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0607.png
图 6-7. 利用 Trivy 扫描容器镜像生成的报告
输出中最紧张的信息包罗包罗特定漏洞的库、其严重性以及修复问题所需的最低版本。应思量修复任何具有高或关键严重性的发现漏洞。假如您无法控制容器镜像自己,则可以实验升级到更新版本。
总结
Kubernetes 的主要目标是以可伸缩和安全的方式运行应用步调容器。在本章中,我们探究了确保生成的容器镜像尺寸小且具有尽大概少的已知安全漏洞的过程、最佳实践和工具。
我们回顾了将容器映像占用空间减少到最小的一些最有用技术。首先选择一个小的基础映像来开始。甚至可以走极端,完全不提供 shell 以获得额外的大小减少。假如您正在利用 Docker,则可以利用多阶段方法,在容器内构建应用步调,而无需捆绑编译器、运行时和构建工具以实现目标。
在 Pod 中消费容器映像时,请确保仅从受信托的注册表中拉取容器映像。建议设置内部容器注册表以提供容器映像,从而消除对公共、可通过互联网访问的注册表的依赖,以消除埋伏的安全风险。您可以通过 OPA Gatekeeper 帮助执行受信托容器注册表列表的利用。另一种安全步伐是利用容器映像的 SHA256 哈希来验证其预期内容。
构建和扫描容器映像的过程可以整合到 CI/CD 流水线中。第三方工具可以在构建之前剖析和分析可部署工件的资源文件。我们查看了用于 Dockerfile 的 Haskell Dockerfile Linter 和用于 Kubernetes 清单的 Kubesec。另一个需要在安全方面涵盖的用例是作为开发人员或您公司外部实体构建的现有容器映像的消费。在运行 Kubernetes Pod 中的容器映像之前,请确保扫描其内容以查找漏洞。Trivy 是可以识别并报告容器映像中漏洞的工具之一,以帮助您了解在操持在容器中操纵时大概面对的安全风险。
考试要点
认识有助于减少容器映像占用空间的技术。
在本节中,我们形貌了在构建时减小容器映像大小的一些技术。我建议您阅读 Docker 网页上提到的最佳实践,并实验将它们应用于样本容器映像。比力应用技术前后生成的容器映像的大小。您可以实验通过避免丢失关键功能的同时将容器映像减小到最小尺寸,寻衅自己。
行走通过治理过程,其中容器映像可以剖析。
OPA Gatekeeper 提供了界说允许用户剖析容器映像的注册表的方法。设置约束模板和约束对象,并查看规则是否适用于界说主应用步调容器、初始化容器和临时容器的 Pod。为了扩展您的曝光度,还可以查看 Kubernetes 领域中提供类似功能的其他产物。其中一个产物是 Kyverno。
对一个容器镜像举行署名,并利用哈希值验证。
构建容器镜像后,请确保也为其创建择要。将容器镜像及其择要发布到注册表。练习怎样在 Pod 界说中利用择要,并验证 Kubernetes 在拉取容器镜像时的行为。
理解怎样设置 ImagePolicyWebhook 准入控制器插件。
你不需要为 ImagePolicyWebhook 编写后端。这超出了考试范围,并需要把握一门编程语言。但你确实需要了解怎样在 API 服务器设置中启用该插件。即使没有运行的后端应用步调,我建议你练习这个工作流程。
知道怎样修复静态分析工具产生的告诫。
CKS 课程不指定用于分析 Dockerfile 和 Kubernetes 清单的特定工具。在考试期间,您大概会被要求运行一个特定的下令,该下令将生成一系列错误和/或告诫消息。理解怎样解释这些消息,并在相关资源文件中修复它们是很紧张的。
练习利用 Trivy 来识别和修复安全漏洞。
CKS 的常见问题解答中列出了 Trivy 的文档页面。因此,可以公道地假设 Trivy 大概会在其中的一个问题中出现。您需要了解调用 Trivy 扫描容器镜像的差别方式。生成的报告将清晰地指示需要修复的内容及找到的漏洞的严重程度。思量到您不能轻易修改容器镜像,您大概会被要求标记运行具有已知漏洞的容器镜像的 Pod。
示例练习
这些练习的解决方案可以在附录中找到。
[*] 看一下以下的 Dockerfile。你能找出减少生成的容器镜像占用空间的大概性吗?
FROM node:latest
ENV NODE_ENV development
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3001
CMD ["node", "app.js"]
将 Dockerfile 最佳实践应用于优化容器镜像占用空间。在举行优化之前和之后运行docker build下令。容器镜像的终极大小应更小,但应用步调仍应正常运行。
[*] 在你的 Kubernetes 集群中安装 Kyverno。您可以在文档中找到安装阐明。
应用形貌在文档页面上的“限制镜像注册表”计谋。唯一允许的注册表应为gcr.io。克制利用任何其他注册表。
创建一个界说了容器镜像gcr.io/google-containers/busybox:1.27.2的 Pod。创建 Pod 应该失败。利用容器镜像busybox:1.27.2创建另一个 Pod。Kyverno 应该允许创建该 Pod。
[*] 在 YAML 清单 pod-validate-image.yaml 中利用容器镜像 nginx:1.23.3-alpine 界说一个 Pod。从 Docker Hub 检索容器镜像的择要。利用 SHA256 哈希验证容器镜像内容。创建 Pod。Kubernetes 应能够乐成拉取容器镜像。
[*] 利用 Kubesec 分析 YAML 清单文件 pod.yaml 中的以下内容:
apiVersion: v1
kind: Pod
metadata:
name: hello-world
spec:
securityContext:
runAsUser: 0
containers:
- name: linux
image: hello-world:linux
检查 Kubesec 生成的建议。忽略关于 seccomp 和 AppArmor 的建议。修复所有消息的根本缘故起因,以确保再次执行工具时不会报告任何额外的建议。
[*] 转到已检出的 GitHub 仓库 bmuschko/cks-study-guide 的目录 app-a/ch06/trivy。执行下令 kubectl apply -f setup.yaml。
该下令在命名空间 r61 中创建了三个差别的 Pod。从下令行执行 Trivy 对这些 Pod 利用的容器镜像举行检查。删除所有具有“CRITICAL”漏洞的 Pod。哪些 Pod 仍在运行?
第七章:监控、日志和运行时安全
课程的最后一个领域主要涉及在 Kubernetes 集群中检测主机和容器级别的可疑活动。我们首先界说 行为分析 这个术语,并阐明它怎样应用于 Kubernetes 领域。理论澄清后,我们将介绍一个名为 Falco 的工具,它可以检测入侵场景。
一旦容器启动,其运行环境仍然可以修改。比方,作为操纵员,您可以决定进入容器以手动安装额外工具或向容器的临时文件体系写入文件。在容器启动后修改容器大概会带来安全风险。您应该思量创建 不可变容器,即启动后无法修改的容器。我们将学习怎样设置 Pod 的正确设置,使其容器变为不可变。
最后,我们将讨论捕获在 Kubernetes 集群中发生事故的日志。这些日志可用于集群级别的故障排除,以重修集群设置何时以及怎样发生变化,导致不希望或破损的运行时行为。日志条目还可用于跟踪正在发生的攻击,作为实施对策的手段。
在高层次上,本章涵盖以下概念:
[*] 举行行为分析以检测恶意活动
[*] 举行深入的分析调查和识别
[*] 在运行时确保容器的不可变性
[*] 利用审计日志监控访问
举行行为分析
除了管理和升级 Kubernetes 集群外,管理员还负责监视埋伏的恶意活动。固然您可以通过登录到集群节点并观察主机和容器级别的历程来手动执行此任务,但这是一项效率极低的工作。
行为分析 是观察集群节点是否存在异常活动的过程。自动化过程有助于过滤、记载和警报特定感兴趣的事故。
景象:Kubernetes 管理员可以观察攻击者接纳的行动
攻击者通过在工作节点上运行的 shell 打开容器获得访问权,以在整个 Kubernetes 集群中启动额外的攻击。管理员不能轻易地通过手动检查每个容器来检测此事故。图 7-1 形貌了这种情况。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0701.png
图 7-1。行为分析工具记载的恶意事故
管理员决定自行安装行为分析工具。该工具将持续监视特定事故并险些即时记载它们。管理员现在拥有有用的机制来检测入侵并接纳行动。
与考试相关的行为分析工具中包罗Falco,Tracee和Tetragon。在本书中,我们将只关注 Falco,因为它是考试期间可用文档页面链接之一。
理解 Falco
Falco 通过观察主机和容器级活动来帮助检测威胁。以下是 Falco 大概监控的一些事故示例:
[*] 在文件体系中特定位置读取或写入文件
[*] 打开容器的 shell 二进制文件,比方/bin/bash打开 bash shell
[*] 实验向不良 URL 举行网络调用
Falco 部署了一组传感器来监听设置的事故和条件。每个传感器由一组规则组成,将事故映射到数据源。当规则匹配特定事故时会产生警报。警报将发送到输出通道以记载事故,比方标准输出、文件或 HTTPS 端点。Falco 允许同时启用多个输出通道。图 7-2 展示了 Falco 的高级架构。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0702.png
图 7-2. Falco 的高级架构
Falco 是一个功能丰富且有多种设置选项的工具。在本书中我们无法讨论所有功能,但我建议您花些时间了解 Falco 的高级概念。
关于 Falco 的另一个很好的学习资源可以在 Sysdig 培训流派网页上找到。“Falco 101”是一个免费的视频课程,详细解释了产物的所有细节。要开始学习,您只需注册一个账户。此外,我建议您阅读Practical Cloud Native Security with Falco,这本由 Loris Degioanni 和 Leonardo Grasso(O’Reilly 出书)合著的册本采用了面向初学者的友爱方法来学习 Falco。
安装 Falco
Falco 可以作为主机体系上的二进制文件或 Kubernetes 中的 DaemonSet 对象安装。您可以放心地假设 Falco 已经预安装在考试环境中。有关安装过程的更多信息,请查看Falco 文档的相关部分。以下步调简要阐明了在 Ubuntu 机器上安装二进制文件的过程。Falco 需要安装在 Kubernetes 集群的所有工作节点上。请注意,这些阐明大概会随着 Falco 的将来版本而更改。
首先,您需要信托 Falco 的 GPG 密钥,设置特定于 Falco 的 apt 存储库,并更新软件包列表:
$ curl -s https://falco.org/repo/falcosecurity-packages.asc | apt-key add -
$ echo "deb https://download.falco.org/packages/deb stable main" | tee -a \
/etc/apt/sources.list.d/falcosecurity.list
$ apt-get update -y
然后,您可以利用以下下令安装内核头文件:
$ apt-get -y install linux-headers-$(uname -r)
最后,您需要安装版本为 0.33.1 的 Falco apt 包:
$ apt-get install -y falco=0.33.1
Falco 已乐成安装,并作为一个后台的 systemd 服务运行。运行以下下令检查 Falco 服务的状态:
$ sudo systemctl status falco
● falco.service - Falco: Container Native Runtime Security
Loaded: loaded (/lib/systemd/system/falco.service; enabled; vendor preset: \
enabled)
Active: active (running) since Tue 2023-01-24 15:42:31 UTC; 43min ago
Docs: https://falco.org/docs/
Main PID: 8718 (falco)
Tasks: 12 (limit: 1131)
Memory: 30.2M
CGroup: /system.slice/falco.service
└─8718 /usr/bin/falco --pidfile=/var/run/falco.pid
Falco 服务应该处于“active”状态。它已经在监视体系中的事故。
设置 Falco
Falco 服务提供了一个操纵环境,用于监视体系并带有一组默认规则。这些规则存储在 /etc/falco 目录中的 YAML 文件中。/etc/falco 中的文件和子目录列表如下:
$ tree /etc/falco
/etc/falco
├── aws_cloudtrail_rules.yaml
├── falco.yaml
├── falco_rules.local.yaml
├── falco_rules.yaml
├── k8s_audit_rules.yaml
├── rules.available
│ └── application_rules.yaml
└── rules.d
我想形貌其中最紧张文件的高级目标。
Falco 设置文件
名为 falco.yaml 的文件是 Falco 历程的设置文件。它控制在发生警报时将通知的通道。通道可以是标准输出或文件。此外,该文件界说了在日志中包罗的警报的最低日志级别,以及怎样设置用于实现 Falco 历程健康检查的嵌入式 Web 服务器。请参考 “Falco 设置选项” 获取所有可用选项的完整参考。
默认规则
名为 falco_rules.yaml 的文件界说了一组预安装规则。Falco 以为这些规则是默认应用的。其中包罗检查当打开容器的 shell 或执行写操纵到体系目录时创建警报的规则。您可以在 “规则示例”页面 上找到其他示例及其相应的规则界说。
自界说规则
名为 falco_rules.local.yaml 的文件允许您界说自界说规则并覆盖默认规则。在安装 Falco 时,该文件仅包罗解释掉的规则,为您提供添加自己规则的出发点。您可以在 Falco 文档中找到 编写自界说规则的引导。
Kubernetes 特定规则
名为 k8s_audit_rules.yaml 的文件界说了 Kubernetes 特定规则,除了记载体系调用事故。典型示例是“当命名空间被删除时记载事故”或“当创建 Role 或 ClusterRole 对象时记载事故”。
应用设置更改
修改设置文件的内容不会自动流传到 Falco 历程。您需要重新启动 Falco 服务,如下所示:
$ sudo systemctl restart falco
接下来,我们将触发 Falco 默认规则涵盖的一些事故。每个事故都会创建一个写入设置通道的警报。Falco 的初始设置将消息路由到标准输出。
生成事故并检查 Falco 日志
让我们看看 Falco 警报是怎样工作的。Falco 默认规则之一会在用户实验打开容器的 shell 时创建一个警报。我们需要执行这个活动来查看其日志条目。为了实现这一点,创建一个名为 nginx 的新 Pod,打开容器的 bash shell,然退却出容器:
$ kubectl run nginx --image=nginx:1.23.3
pod/nginx created
$ kubectl exec -it nginx -- bash
root@nginx:/# exit
通过检查其运行时详细信息来确定 Pod 运行在哪个集群节点上:
$ kubectl get pod nginx -o jsonpath='{.spec.nodeName}'
kube-worker-1
此 Pod 正在名为kube-worker-1的集群节点上运行。您需要检查该机器上的 Falco 日志以找到相关的日志条目。您可以直接在kube-worker-1集群节点上利用journalctl下令检查记载的事故。以下下令搜索包罗关键字falco的条目:
$ sudo journalctl -fu falco
...
Jan 24 18:03:37 kube-worker-1 falco: 18:03:14.632368639: Notice A shell \
was spawned in a container with an attached terminal (user=root user_loginuid=0 \
nginx (id=18b247adb3ca) shell=bash parent=runc cmdline=bash pid=47773 \
terminal=34816 container_id=18b247adb3ca image=docker.io/library/nginx)
假如您实验修改容器状态,将会发现会引入额外的规则。比如说,您在nginx容器中利用apt安装了 Git 包:
root@nginx:/# apt update
root@nginx:/# apt install git
Falco 为这些体系级操纵添加了日志条目。以下输出呈现了警报:
$ sudo journalctl -fu falco
Jan 24 18:55:48 ubuntu-focal falco: 18:55:05.173895727: Error Package \
management process launched in container (user=root user_loginuid=0 \
command=apt update pid=60538 container_id=18b247adb3ca container_name=nginx \
image=docker.io/library/nginx:1.23.3)
Jan 24 18:55:48 ubuntu-focal falco: 18:55:11.050925982: Error Package \
management process launched in container (user=root user_loginuid=0 \
command=apt install git-all pid=60823 container_id=18b247adb3ca \
container_name=nginx image=docker.io/library/nginx:1.23.3)
...
在下一节中,我们将检查触发警报创建的 Falco 规则及其语法。
理解 Falco 规则文件基础知识
Falco 规则文件通常由 YAML 中界说的三个元素组成:规则、宏和列表。您需要在高层次上理解这些元素,并知道怎样修改它们以达到特定的目标。
编写您自己的 Falco 规则
考试期间,您大概不需要自己编写 Falco 规则。您将获得一组现有的规则。假如您想更深入地了解 Falco 规则,请查看相关的文档页面。
规则
规则是生成警报的条件。它还界说了警报的输出消息。输出消息可以包罗硬编码的消息并结合内置变量。警报记载在WARNING日志级别上。示例 7-1 展示了一个规则的 YAML,监听试图从除了传统视频集会软件(如 Skype 或 Webex)之外的历程访问机器摄像头的事故。
示例 7-1. 监控摄像头访问的 Falco 规则
- rule: access_camera
desc: a process other than skype/webex tries to access the camera
condition: evt.type = open and fd.name = /dev/video0 and not proc.name in \
(skype, webex)
output: Unexpected process opening camera video device (command=%proc.cmdline)
priority: WARNING
宏
宏是可重复利用的规则条件,有助于避免在多个规则之间复制粘贴类似的逻辑。假如规则文件变得很长,而且您希望提高可维护性,宏非常有用。
假设您正在简化一个规则文件的过程中。您注意到多个规则利用相同的条件监听摄像头访问。示例 7-2 展示了怎样将逻辑分解为宏。
示例 7-2. 界说可重复条件的 Falco 宏
- macro: camera_process_access
condition: evt.type = open and fd.name = /dev/video0 and not proc.name in \
(skype, webex)
现在可以通过名称在规则界说中引用宏,如示例 7-3 所示。
示例 7-3. 包罗宏的 Falco 规则
- rule: access_camera
desc: a process other than skype/webex tries to access the camera
condition: camera_process_access
output: Unexpected process opening camera video device (command=%proc.cmdline)
priority: WARNING
列表
列表是可以包罗在规则、宏和其他列表中的项目集合。将列表视为传统编程语言中的数组。示例 7-4 创建了一个与视频集会软件关联的历程名称列表。
示例 7-4. Falco 列表
- list: video_conferencing_software
items:
示例 7-5 展示了怎样在宏中利用列表。
示例 7-5. 利用列表的 Falco 宏
- macro: camera_process_access
condition: evt.type = open and fd.name = /dev/video0 and not proc.name in \
(video_conferencing_software)
剖析现有规则
Falco 预装默认规则的缘故起因是为了缩短生产集群启动时间。与其自己界说规则和正确的语法,您可以简单地安装 Falco,并从一开始就受益于最佳实践。
让我们回到我们在 “生成事故并检查 Falco 日志” 中触发的事故之一。撰写时,我正在利用 Falco 版本 0.33.1。随附的规则文件 /etc/falco/falco_rules.yaml 包罗一个名为 “Terminal shell in container” 的规则。它观察打开容器的 shell 事故。您可以通过搜索日志消息的一部分,比方 “在容器中生成了一个 shell” 来轻松找到该规则。示例 7-6 展示了规则界说的语法,以及 YAML 解释部分。
示例 7-6。监视 shell 活动到容器的 Falco 规则
- macro: spawned_process <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
condition: (evt.type in (execve, execveat) and evt.dir=<)
- macro: container <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
condition: (container.id != host)
- macro: container_entrypoint <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
condition: (not proc.pname exists or proc.pname in (runc:, \
runc:, runc, docker-runc, exe, docker-runc-cur))
- macro: user_expected_terminal_shell_in_container_conditions <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
condition: (never_true)
- rule: Terminal shell in container <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
desc: A shell was used as the entrypoint/exec point into a container with an \
attached terminal.
condition: > <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
and not user_expected_terminal_shell_in_container_conditions
output: > <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/4.png>
A shell was spawned in a container with an attached terminal (user=%user.name \
user_loginuid=%user.loginuid %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline pid=%proc.pid \
terminal=%proc.tty container_id=%container.id image=%container.image.repository)
priority: NOTICE <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
tags: <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/6.png>
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO1-1
界说宏,一种可在多个规则中重复利用的条件,通过名称引用。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO1-5
指定规则的名称。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO1-6
由多个宏组成的聚合条件。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO1-7
假如发生事故,警报消息应该。消息大概利用内置字段引用运行时值。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO1-8
指示规则违背的严重程度。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO1-9
将规则集分为相关规则组,以便管理。
偶尔候您大概希望更改现有的规则。下一节将解释怎样最好地覆盖默认规则。
覆盖现有规则
不要直接修改 /etc/falco/falco_rules.yaml 中的规则界说,我建议您在 /etc/falco/falco_rules.local.yaml 中重新界说规则。这样做可以帮助您在想要消除修改或在过程中出现任何错误时回退到原始规则界说。规则界说需要在集群的所有工作节点上举行更改。
在 示例 7-7 中展示的规则界说通过将优先级更改为 ALERT 并将输特别式更改为通过 内置字段 来包罗新格式来覆盖名为 “Terminal shell in container” 的规则。
示例 7-7。修改后的监视 shell 活动到容器的规则
- rule: Terminal shell in container
desc: A shell was used as the entrypoint/exec point into a container with an \
attached terminal.
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
and not user_expected_terminal_shell_in_container_conditions
output: >
Opened shell: %evt.time,%user.name,%container.name <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
priority: ALERT <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
tags:
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO2-1
简化违规时生成的日志输出。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO2-2
以 ALERT 优先级处理违背规则。
在向 falco_rules.local.yaml 添加规则后,我们需要让 Falco 获取这些更改。请利用以下下令重启 Falco 服务:
$ sudo systemctl restart falco
因此,任何实验 shell 进入容器的行为都将以差别的输特别式和优先级记载,如下所示:
$ sudo journalctl -fu falco
...
Jan 24 21:19:13 kube-worker-1 falco: 21:19:13.961970887: Alert Opened \
shell: 21:19:13.961970887,<NA>,nginx
除了覆盖现有的 Falco 规则,您还可以在 /etc/falco/falco_rules.local.yaml 中界说自己的自界说规则。编写自界说规则不在本书的讨论范围内,但您可以在 Falco 文档中找到大量相关信息。
确保容器的不可变性
容器默认是可变的。在容器启动后,您可以打开一个 shell 毗连到它,安装现有软件的补丁,修改文件或更改其设置。可变的容器允许攻击者通过下载或安装恶意软件来获取对容器的访问权限。
要对抗这种情况,请确保容器处于不可变状态。这意味着防止对容器文件体系的写操纵,甚至克制对容器举行 shell 访问。假如需要对容器举行任何巨大更改,比如更新依赖项或集成新的应用功能,应该发布容器镜像的新标签,而不是手动修改容器自己。然后,您可以将容器镜像的新标签分配给 Pod,而无需直接修改其内部。
景象:攻击者安装恶意软件
图 7-3 形貌了一个景象,攻击者利用窃取的凭据访问容器。攻击者下载并安装了恶意软件,因为容器允许对根文件体系举行写操纵。恶意软件继承监视容器中的活动,比方可以剖析应用步调日志以获取敏感信息,然后将信息发送到 Kubernetes 集群外的服务器。因此,数据被用作登录体系其他部分的手段。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0703.png
图 7-3. 攻击者 shell 进入容器并安装恶意软件
在下一节中,您将学习怎样通过利用 Distroless 容器镜像、通过 ConfigMap 或 Secret 注入设置数据以及设置只读容器文件体系来防止这种情况的发生。这些设置将使容器真正成为不可变的。
利用 Distroless 容器镜像
Distroless 容器镜像在容器的天下中越来越受欢迎。它们仅捆绑了您的应用步调及其运行时依赖项,同时尽大概地去除操纵体系的大部分内容,比方 shell、软件包管理器和库。Distroless 容器镜像不仅尺寸更小,而且更安全。攻击者无法进入容器,因此文件体系无法被滥用安装恶意软件。利用 Distroless 容器镜像是创建不可变容器的第一防线。我们已经在“选择尺寸小的基础镜像”中介绍了 Distroless 容器镜像。更多信息请参阅该部分。
设置容器利用 ConfigMap 或 Secret
最佳实践是在差别的部署环境中利用相同的容器镜像,即使它们的运行时设置大概差别。任何特定于环境的设置,如凭据和体系其他部分的毗连 URL,都应该是外部化的。在 Kubernetes 中,您可以通过 ConfigMap 或 Secret 将设置数据作为环境变量或通过 Volumes 挂载为文件注入到容器中。图 7-4 展示了在开发和生产集群中设置 Pod 时重用相同的容器镜像。环境特定的设置数据由 ConfigMap 提供。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0704.png
图 7-4. 在多个环境中利用相同的容器镜像
避免创建特定于环境的容器镜像简化了创建过程,减少了引入意外安全风险的风险,并使功能测试更加轻易。在容器启动后注入运行时值无需更改容器,因此是使其不可变的关键。
当在容器中将 Secret 作为环境变量利用时,请确保避免意外将值记载到标准输出,比方在编写日志消息时作为明文值。任何可以访问容器日志的人都可以剖析其中的 Secret 值。作为抽查,识别应用步调代码中利用 Secret 的位置,并评估其暴露风险。
要了解创建、设置和利用 ConfigMaps 和 Secrets 的最新信息,请查阅Kubernetes 文档。
设置只读容器根文件体系
容器不可变性的另一个方面是防止对容器文件体系的写访问。您可以通过将值true分配给属性spec.containers[].securityContext.readOnlyRootFilesystem来设置此运行时行为。
仍然有一些应用步调需要写入权限以满足其功能需求。比方,nginx 需要写入到 /var/run、/var/cache/nginx 和 /usr/local/nginx 目录。与设置 readOnlyRootFilesystem 为 true 结合利用,您可以声明卷使这些目录可写。Example 7-8 展示了运行 nginx 的不可变容器的 YAML 清单。
Example 7-8. 一个克制对根文件体系举行写入访问的容器
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.21.6
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: nginx-run
mountPath: /var/run
- name: nginx-cache
mountPath: /var/cache/nginx
- name: nginx-data
mountPath: /usr/local/nginx
volumes:
- name: nginx-run
emptyDir: {}
- name: nginx-data
emptyDir: {}
- name: nginx-cache
emptyDir: {}
在创建 Pod 之前,确定应用步调的文件体系读/写需求。利用卷来设置写入挂载路径。任何其他文件体系路径应设为只读。
利用审计日志监控访问
对于 Kubernetes 管理员来说,记载集群中发生的事故好坏常紧张的。这些记载可以帮助实时检测入侵,或者用于跟踪设置更改以举行故障排除。审计日志 提供了 API 服务器接收到的事故的时间顺序视图。
景象:管理员可以实时监控恶意事故
Figure 7-5 展示了监控 Kubernetes API 事故的好处。在这种情况下,攻击者试图调用 API 服务器。审计日志机制已捕获到感兴趣的事故。管理员可以随时查看这些事故,识别入侵实验,并接纳对策。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0705.png
图 7-5. 通过观察审计日志监控的攻击者
我们只在这里回顾了一个用例,即适用于安全问题的用例。不能低估跟踪公司内部 API 请求的能力。通过检察审计日志,管理员可以为试图创建 Kubernetes 对象的应用步调开发人员提供引导,或者重修大概导致集群行为故障的设置更改。
理解审计日志
Kubernetes 可以存储由终极用户触发的对 API 服务器的任何请求或由控制平面自己发出的事故的记载。审计日志中的条目以 JSON Lines 格式存在,大概包罗但不限于以下信息:
[*] 发生了什么事故?
[*] 是谁触发了这个事故?
[*] 它是什么时间触发的?
[*] 哪个 Kubernetes 组件处理了该请求?
事故范例及其对应的请求数据由 审计计谋 界说。审计计谋是一个指定这些规则的 YAML 清单,而且必须提供给 API 服务器历程。
审计后端 负责存储根据审计计谋界说的记载的审计事故。对于后端,您有两个可设置的选项:
[*] 一个日志后端,将事故写入文件。
[*] Webhook 后端通过 HTTP(S) 将事故发送到外部服务,比方集中式日志记载和监控体系集成的目标。 这样的后端可以帮助调试运行时问题,比方应用步调崩溃。
图 7-6 汇总了设置审计日志所需的所有要素。 下面的章节将解释设置它们的详细信息。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0706.png
图 7-6. 高级审计日志架构
让我们深入了解审计计谋文件及其设置选项。
创建审计计谋文件
审计计谋文件现实上是用于 Policy 资源的 YAML 清单。 API 服务器接收到的任何事故都会按照计谋文件中界说的顺序匹配规则。 假如能找到匹配的规则,则记载事故及其声明的审计级别。 表 7-1 列出了所有可用的审计级别。
表 7-1. 审计级别
LevelEffectNone不记载与此规则匹配的事故。Metadata仅记载事故的请求元数据。Request记载事故的元数据和请求体。RequestResponse记载事故的元数据、请求和响应体。 示例 7-9 展示了一个示例审计计谋。 规则被指定为具有名为 rules 的属性的项目数组。 每个规则声明一个级别,适用于的资源范例和 API 组,以及一个可选的命名空间。
示例 7-9. 审计计谋文件的内容
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived" <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
rules:
- level: RequestResponse <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
resources:
- group: ""
resources: ["pods"]
- level: Metadata <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
resources:
- group: ""
resources: ["pods/log", "pods/status"]
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO3-1
阻止在 RequestReceived 阶段为所有请求生成日志
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO3-2
记载 RequestResponse 级别的 Pod 变更
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO3-3
记载特定的 Pod 事故,比方日志和状态请求,在 Metadata 级别
先前的审计计谋并不是很详细,但应该让您对其格式有所了解。 有关更多示例和详细信息,请参阅 Kubernetes 文档。
一旦创建了审计计谋文件,它就可以被 API 服务器历程消费。 在文件 /etc/kubernetes/manifests/kube-apiserver.yaml 中的 API 服务器历程中添加标记 --audit-policy-file。 参数分配的值是审计计谋文件的完全限定路径。
接下来,我们将详细介绍设置 API 服务器的审计日志记载所需的设置,包罗文件日志后端和 Webhook 后端。
设置日志后端
要设置基于文件的日志后端,您需要向文件 /etc/kubernetes/manifests/kube-apiserver.yaml 添加三个设置项。 以下列表总结了设置内容:
[*] 向 API 服务器历程提供两个标记:标记--audit-policy-file指向审计计谋文件;标记--audit-log-path指向日志输出文件。
[*] 为审计日志计谋文件和日志输出目录添加卷挂载路径。
[*] 为审计日志计谋文件和日志输出目录在主机路径上添加卷界说。
示例 7-10 显示了 API 服务器设置文件的修改内容。
示例 7-10。设置审计计谋文件和审计日志文件
...
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
- --audit-log-path=/var/log/kubernetes/audit/audit.log <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/1.png>
...
volumeMounts:
- mountPath: /etc/kubernetes/audit-policy.yaml <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
name: audit
readOnly: true
- mountPath: /var/log/kubernetes/audit/ <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/2.png>
name: audit-log
readOnly: false
...
volumes:
- name: audit <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
hostPath:
path: /etc/kubernetes/audit-policy.yaml
type: File
- name: audit-log <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/3.png>
hostPath:
path: /var/log/kubernetes/audit/
type: DirectoryOrCreate
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO4-1
将计谋文件和日志文件的位置提供给 API 服务器历程。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO4-3
将计谋文件和审计日志目录挂载到给定路径。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/#co_monitoring__logging__and_runtime_security_CO4-5
界说计谋文件和审计日志目录的卷。
通过向 API 服务器历程传递附加标记,可以进一步自界说日志后端的运行时行为。比方,您可以通过提供标记--audit-log-maxage指定生存旧审计日志文件的最大天数。请参考Kubernetes 文档查看完整的标记列表。
现在是产生一些日志条目标时间了。以下kubectl下令向 API 服务器发送一个请求,以创建名为nginx的 Pod:
$ kubectl run nginx --image=nginx:1.21.6
pod/nginx created
在上一步中,我们设置了审计日志文件位于/var/log/kubernetes/audit/audit.log。根据审计计谋中的规则,条目数量大概会非常巨大,这使得查找特定事故变得困难。过滤已设置事故的简单方法是搜索分配给apiVersion属性的值audit.k8s.io/v1。以下下令查找相关的日志条目,一个是RequestResponse级别的,另一个是Metadata级别的:
$ sudo grep 'audit.k8s.io/v1' /var/log/kubernetes/audit/audit.log
...
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse", \
"auditID":"285f4b99-951e-405b-b5de-6b66295074f4","stage":"ResponseComplete", \
"requestURI":"/api/v1/namespaces/default/pods/nginx","verb":"get", \
"user":{"username":"system:node:node01","groups":["system:nodes", \
"system:authenticated"]},"sourceIPs":["172.28.116.6"],"userAgent": \
"kubelet/v1.26.0 (linux/amd64) kubernetes/b46a3f8","objectRef": \
{"resource":"pods","namespace":"default","name":"nginx","apiVersion":"v1"}, \
"responseStatus":{"metadata":{},"code":200},"responseObject":{"kind":"Pod", \
"apiVersion":"v1","metadata":{"name":"nginx","namespace":"default", \
...
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID": \
"5c8e5ecc-0ce0-49e0-8ab2-368284f2f785","stage":"ResponseComplete", \
"requestURI":"/api/v1/namespaces/default/pods/nginx/status","verb":"patch", \
"user":{"username":"system:node:node01","groups":["system:nodes", \
"system:authenticated"]},"sourceIPs":["172.28.116.6"],"userAgent": \
"kubelet/v1.26.0 (linux/amd64) kubernetes/b46a3f8","objectRef": \
{"resource":"pods","namespace":"default","name":"nginx","apiVersion":"v1", \
"subresource":"status"},"responseStatus":{"metadata":{},"code":200}, \
...
设置 Webhook 后端
设置 Webhook 后端与设置日志后端有所差别。我们需要告诉 API 服务器向外部服务发送 HTTP(S)请求,而不是文件。与我们在“设置 ImagePolicyWebhook Admission Controller 插件”中所做的类似,将设置到外部服务的设置、Webhook 和用于认证的凭据界说在一个kubeconfig 文件中。
在文件/etc/kubernetes/manifests/kube-apiserver.yaml中向 API 服务器历程添加标记--audit-webhook-config-file,并指向 kubeconfig 文件的位置。标记--audit-webhook-initial-backoff界说了在初始请求后等待外部服务重试之前的时间。您仍然需要分配标记--audit-policy-file来指向审计计谋文件。
总结
在 Kubernetes 集群中监视和记载事故是每个管理员的紧张职责。我们利用 Falco 来识别和过滤与安全相关的事故。您了解了差别设置文件的目标和语法,以及怎样在日志中找到相关警报。
除了利用行为分析工具外,您还需要为到达 API 服务器的请求设置审计日志记载。审计日志记载设置的事故到后端,可以是控制平面节点上的文件,也可以通过 HTTP(S)调用发送到外部服务。我们已经完成了为 API 服务器历程启用审计日志记载的过程。
朝向更安全容器的明智步调是使其不可变。不可变容器仅支持只读文件体系,因此埋伏攻击者无法安装恶意软件。假如容器内运行的应用步调需要写入数据,则挂载一个卷。利用 distroless 容器镜像阻止攻击者能够进入容器的 shell。
考试要点
练习怎样设置和操纵 Falco。
Falco 肯定会成为考试中的一个主题。您需要了解怎样读取和修改设置文件中的规则。我建议您详细浏览语法和选项,以防需要自己编写规则。运行 Falco 的主要入口点是下令行工具。可以公道地假设它已经预装在考试环境中。
知道怎样识别不可变容器。
不可变容器是本考试领域的核心主题。了解如作甚 Pod 设置spec.containers[].securityContext.readOnlyRootFilesystem属性,以及怎样挂载卷到特定路径,以防容器历程需要写操纵。
深入了解审计日志设置选项。
设置审计日志记载包罗两个步调。首先,您需要了解审计计谋文件的语法和结构。另一个方面是怎样设置 API 服务器以消耗审计计谋文件,提供到后端的引用,并挂载相关的文件体系卷。确保举行所有这些方面的现实操纵练习。
示例练习
[*] 导航到已检出的 GitHub 存储库bmuschko/cks-study-guide的app-a/ch07/falco目录。利用下令vagrant up启动运行集群的捏造机(VMs)。该集群包罗一个名为kube-control-plane的单个控制平面节点和一个名为kube-worker-1的工作节点。完成后,利用vagrant destroy -f关闭集群。Falco 已作为一个 systemd 服务正在运行。
检查运行在名为malicious的现有 Pod 中的历程。查看 Falco 日志,看看是否有规则为该历程创建了日志。
通过更改输出为<timestamp>,<username>,<container-id>来重新设置创建事故日志的现有规则。在 Falco 日志中找到已更改的日志条目。
重新设置 Falco,使其将日志写入到文件 /var/logs/falco.log。禁用标准输出通道。确保 Falco 将新消息追加到日志文件中。
先决条件: 这项练习需要安装工具 Vagrant 和 VirtualBox。
[*] 转到已检出的 GitHub 仓库 bmuschko/cks-study-guide 的目录 app-a/ch07/immutable-container。利用下令 kubectl apply -f setup.yaml 执行操纵。
检查 YAML 清单在 default 命名空间中创建的 Pod。对 Pod 举行相关更改,以便其容器被视为不可变。
[*] 转到已检出的 GitHub 仓库 bmuschko/cks-study-guide 的目录 app-a/ch07/audit-log。利用下令 vagrant up 启动运行集群的捏造机。该集群包罗一个名为 kube-control-plane 的单控制平面节点和一个名为 kube-worker-1 的工作节点。完成后,利用 vagrant destroy -f 停止集群。
编辑位于 /etc/kubernetes/audit/rules/audit-policy.yaml 的现有审计计谋文件。添加一个规则,以 Metadata 级别记载 ConfigMaps 和 Secrets 的事故。添加另一个规则,以 Request 级别记载 Services 的事故。
设置 API 服务器以消耗审计计谋文件。日志应写入到文件 /var/log/kubernetes/audit/logs/apiserver.log。界说最多生存审计日志文件五天。
确保已创建日志文件并包罗至少一个与设置的事故匹配的条目。
先决条件: 这项练习需要安装工具 Vagrant 和 VirtualBox。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]