DigitalOcean Kubernetes安全实战:最小权限与零信任落地指南
1. 这不是“加个防火墙”就能搞定的安全——初创团队在 DigitalOcean Kubernetes 上的真实安全水位线
你刚用
kubekey
在 DigitalOcean 上三分钟拉起一个三节点集群,
kubectl get nodes
全绿,服务跑起来了,前端能访问,后端日志在刷,团队群里一片“成了!”。这时候如果有人问:“安全吗?”——90% 的初创技术负责人会下意识回一句:“应该……没问题吧?我们没开什么高危端口。”
这不是敷衍,是真实困境。DigitalOcean 的 Kubernetes 服务(DOKS)把底层基础设施的复杂度削平了,但
安全责任的边界反而更模糊了
。它不替你管 Pod 里跑的是不是带漏洞的旧版 Nginx,不检查你的 Helm Chart 里有没有硬编码的数据库密码,更不会阻止开发人员用
--privileged=true
启动一个调试容器。它只保证:你的控制平面是隔离的、API Server 的 TLS 是有效的、节点 OS 的基础补丁是打的。剩下的,全是你的战场。
我见过太多案例:一家做 SaaS 工具的团队,用 DOKS 托管核心 API,上线三个月后被扫描出
/etc/passwd
可读漏洞——根源不是 DigitalOcean 的问题,而是他们用了一个社区 Helm Chart,Chart 里默认把 ConfigMap 挂载进了容器的
/
根目录,而 ConfigMap 里恰好存了测试环境的数据库连接串。另一个团队更典型:为快速验证功能,直接在生产命名空间里部署了一个带
hostNetwork: true
和
hostPID: true
的临时诊断 Pod,结果这个 Pod 被上游供应链里的一个恶意依赖反向 SSH 出去,成了整个集群的跳板。
这些都不是理论风险。它们发生在真实的、资源紧张、节奏飞快的初创场景里。而“Zero Trust”和“Least Privilege”这两个词,在 DigitalOcean 的文档里可能只占一页纸,但在你每天写的 YAML 文件里,它们得落实成每一行
securityContext
、每一个
RoleBinding
、每一次
kubectl auth can-i
的验证。这篇内容不讲大道理,不堆概念,只拆解你在 DOKS 上真正要动手改、要写、要反复验证的六个关键动作。它来自我们帮 17 家不同阶段的初创公司做 Kubernetes 安全加固的实操记录,每一步都标好了“为什么必须做”和“不做会怎样”。
2. 控制平面不是黑箱:理解 DOKS 安全模型的“已托管”与“自担责”分界点
很多团队对 DOKS 的安全认知存在一个根本性错觉:以为“托管 Kubernetes”等于“托管全部安全”。这就像租了一套精装修公寓,物业负责楼体结构、电梯和消防系统,但你家里的门锁、保险柜、甚至是否给保姆留备用钥匙,物业概不负责。DOKS 的安全责任模型,正是如此清晰的划分。
2.1 DigitalOcean 明确托管的部分:你无需操心的“基座”
DigitalOcean 对 DOKS 控制平面(Control Plane)承担 100% 的安全责任。这包括:
-
API Server 的 TLS 终止与证书管理 :每个新集群创建时,DOKS 自动为你签发并轮换一套由 Let’s Encrypt 或其私有 CA 签发的证书。你不需要生成
ca.crt、server.key,也不需要配置--tls-cert-file。所有kubectl请求都通过 HTTPS 加密通道抵达 API Server,且证书链是可信的。这是最基础也是最关键的加密层,DOKS 做得很稳。 -
etcd 数据库的静态加密(At-Rest Encryption) :DOKS 默认启用 AES-256-GCM 加密算法,对存储在 DigitalOcean 块存储(Block Storage)上的 etcd 数据进行加密。这意味着即使物理磁盘被非法获取,没有密钥也无法解密其中的 Secret、ConfigMap 等敏感数据。这个密钥由 DigitalOcean 的密钥管理系统(KMS)托管,你无法访问或导出它——这恰恰是好事,避免了密钥管理的额外负担。
-
控制平面组件的高可用与补丁更新 :DOKS 将 API Server、etcd、Controller Manager、Scheduler 等组件以多副本形式部署在 DigitalOcean 的专用、隔离网络中。当上游 Kubernetes 社区发布 CVE 补丁(例如 CVE-2023-2431,一个影响 kube-apiserver 的拒绝服务漏洞),DigitalOcean 会在 72 小时内完成热补丁或滚动升级,你无需手动干预。这是托管服务的核心价值,省去了你维护一个高可用、合规的控制平面的巨大成本。
提示:你可以通过
doctl kubernetes cluster get <cluster-name>命令查看集群的kubernetes_version和updated_at字段,确认当前版本及上次更新时间。不要试图自己去 SSH 到 master 节点——DOKS 不提供对控制平面节点的 SSH 访问权限,这是设计使然,也是安全边界。
2.2 你必须 100% 自行负责的部分:安全的“最后一公里”
一旦请求穿过 API Server,进入你的工作负载(Workload)层面,安全责任就完全移交给你。DOKS 不会、也不能替你做以下任何事:
-
Pod 内部的安全上下文(Security Context) :
runAsUser、runAsGroup、fsGroup、readOnlyRootFilesystem、allowPrivilegeEscalation这些字段,全靠你在 Deployment 或 Pod 的 YAML 中显式声明。DOKS 不会帮你把runAsUser: 0(即 root)自动降权为1001。如果你的镜像默认以 root 运行,且你没覆盖它,那它就是以 root 运行。 -
网络策略(NetworkPolicy)的定义与执行 :DOKS 集群默认启用
calicoCNI 插件,它完全支持 Kubernetes NetworkPolicy API。但 DOKS 不会为你生成一条deny-all的默认策略,也不会自动限制frontend命名空间只能访问backend命名空间的8080端口。这一切,从策略编写、到kubectl apply -f network-policy.yaml,再到验证其生效,都是你的任务。 -
Secret 的生命周期管理 :DOKS 提供
kubectl create secret命令,但它只是把 Base64 编码后的字符串存进 etcd。它不检查你存的是不是明文密码,不阻止你把admin_password和db_password存在同一个 Secret 里,更不会在 Secret 被挂载进 Pod 后,自动清理 Pod 容器内的内存副本。Secret 的安全性,完全取决于你如何创建、如何使用、以及你的应用是否会在日志中无意打印它。 -
节点(Worker Node)的操作系统与运行时安全 :DOKS 为你预装了 Ubuntu 22.04 LTS 或 Debian 11,并定期推送安全更新(如
apt update && apt upgrade)。但 DOKS 不会替你禁用root用户的 SSH 登录,不会帮你卸载集群里根本用不到的dockerCLI(因为 DOKS 使用 containerd),更不会阻止你在节点上手动安装一个未经审计的监控代理。节点是你自己的计算资源,它的操作系统安全,是你自己的责任。
2.3 一个被严重低估的“灰色地带”:Ingress Controller 与 Load Balancer
这里藏着一个初创团队最容易栽跟头的坑。DOKS 提供了官方的
digitalocean-cloud-controller-manager
,它能自动为你创建 DigitalOcean 的 Load Balancer(DO LB)来暴露 Ingress。但 DO LB 本身是一个独立的、位于 Kubernetes 集群之外的网络设备。它的安全配置,
既不属于 DOKS 托管的控制平面,也不属于你管理的 Worker Node
。
-
SSL/TLS 终止位置 :你可以选择让 DO LB 终止 TLS(即把证书上传到 DO LB),然后以 HTTP 流量转发给你的 Ingress Controller(如 Nginx Ingress)。这很常见,但意味着从 DO LB 到 Ingress Controller 的这段流量是明文的。如果攻击者已经渗透进你的 VPC(例如通过一个被攻破的 Pod),他就能嗅探这段流量。更安全的做法是,让 DO LB 透传 TLS(TCP 模式),由 Ingress Controller 自己处理证书。但这要求你把证书和私钥作为 Kubernetes Secret 存入集群,并在 Ingress 资源中引用。DOKS 不会替你做这个决策,也不会帮你生成那个 Secret。
-
WAF(Web 应用防火墙)集成 :DigitalOcean 的 Load Balancer 目前不原生集成 WAF 功能。如果你需要防 SQL 注入、XSS 攻击,你有两个选择:一是在集群内部署一个开源 WAF(如 ModSecurity + Nginx),这增加了你的运维复杂度;二是购买第三方 WAF 服务(如 Cloudflare),并将 DO LB 的后端指向它。无论哪种,配置、规则调优、日志分析,全是你的活。
理解这个分界点,是制定有效安全策略的第一步。它决定了你的精力应该投向哪里:把时间花在研究如何自动化轮换 etcd 加密密钥上,是徒劳的;而花时间写一个脚本,每天扫描所有 Pod 的
securityContext
并报告
allowPrivilegeEscalation: true
的实例,则是刀刀见血的务实之举。
3. 从“能跑”到“可信”的第一步:强制实施最小权限原则(Least Privilege)的落地清单
在 DOKS 上,“最小权限”不是一句口号,它是一张必须逐项打钩的、覆盖整个请求生命周期的检查清单。这张清单的起点,不是你的应用代码,而是
kubectl
这个命令行工具本身。因为它是所有操作的源头。
3.1 Kubectl 的身份:别再用
doctl kubernetes cluster kubeconfig save
生成的“上帝令牌”
当你执行
doctl kubernetes cluster kubeconfig save --cluster-name my-cluster
时,DigitalOcean 会为你生成一个
kubeconfig
文件。这个文件里包含的
token
,是一个拥有
cluster-admin
角色的 ServiceAccount Token。它等同于 Linux 系统里的
root
用户,可以
get
、
list
、
create
、
delete
、
watch
集群内的一切资源,包括
secrets
、
nodes
、
clusterroles
。
初创团队早期图省事,把这个
kubeconfig
文件直接交给所有开发、测试、甚至产品经理,让他们自己
kubectl port-forward
查看日志。这无异于把公司保险柜的万能钥匙,发给了前台接待员。一次误操作
kubectl delete namespace production
,或者一个被钓鱼邮件诱导执行的恶意脚本,后果不堪设想。
正确做法:为每个角色创建专属的、受限的 ServiceAccount
我们为一家客户设计了一套基于角色的访问控制(RBAC)体系,仅用了 4 个 YAML 文件,就将权限收束得非常干净:
# 1. dev-sa.yaml: 开发人员 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: dev-sa
namespace: default
---
# 2. dev-role.yaml: 开发人员 Role (命名空间级别)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dev-role
namespace: default
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/exec", "services", "endpoints"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch", "patch"] # patch 允许更新镜像 tag,但不允许 delete 或 create
---
# 3. dev-rolebinding.yaml: 将 Role 绑定到 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-rolebinding
namespace: default
subjects:
- kind: ServiceAccount
name: dev-sa
namespace: default
roleRef:
kind: Role
name: dev-role
apiGroup: rbac.authorization.k8s.io
---
# 4. dev-kubeconfig.yaml: 为 dev-sa 生成专属 kubeconfig
# 此文件需通过安全渠道(如 HashiCorp Vault)分发给开发者
# 它只包含 dev-sa 的 token 和指向集群的 server 地址,不含任何其他信息
这个方案的关键在于:
权限是按“动词”(verbs)而非“名词”(resources)授予的
。
dev-role
允许
patch
Deployment,但不允许
create
或
delete
。这意味着开发者可以执行
kubectl set image deployment/my-app container-name=new-image:v1.2
来更新镜像,但无法执行
kubectl delete deployment my-app
来删除整个应用。这是一个微小但至关重要的区别,它把“变更”和“摧毁”彻底隔离开。
注意:
kubectl auth can-i --list --as=system:serviceaccount:default:dev-sa是你的最佳朋友。在部署任何 RBAC 配置后,务必用这条命令验证该账号的实际权限。它会列出所有yes和no的结果,比凭空想象可靠一万倍。
3.2 Pod 的“身份证”:SecurityContext 的七项硬性约束
一个 Pod 的
securityContext
,就是它的“宪法”。它规定了这个进程在操作系统层面能做什么。在 DOKS 上,我们强制所有生产环境的 Deployment 必须包含以下七项约束,缺一不可:
-
runAsNonRoot: true:这是底线。它强制 Pod 内的主进程不能以 UID 0(root)启动。如果镜像的Dockerfile里写了USER root,这个 Pod 就会启动失败。这迫使你去修改镜像,用USER 1001这样的非特权用户。这是防御“容器逃逸”(Container Escape)的第一道物理屏障。 -
runAsUser: 1001:明确指定 UID。不能只写runAsNonRoot: true就完事。因为有些镜像会以 UID 65534(nobody)启动,而nobody在某些发行版里可能被赋予了意外的组权限。固定一个 UID(如 1001),并在你的应用 Dockerfile 中useradd -u 1001 appuser,能确保行为可预测。 -
runAsGroup: 1001:同理,固定 GID。这能防止应用因组权限混乱而无法读写挂载的卷(Volume)。 -
fsGroup: 1001:这个字段常被忽略,但它至关重要。它告诉 kubelet,当一个PersistentVolumeClaim(PVC)被挂载进 Pod 时,kubelet 应该递归地将该卷内所有文件的group ID修改为1001,并赋予g+rwx权限。这样,你的应用进程(UID 1001, GID 1001)就能顺利读写卷里的文件,而无需在容器启动脚本里写chown -R 1001:1001 /data这种危险操作。 -
readOnlyRootFilesystem: true:将容器的根文件系统设为只读。这意味着/bin、/usr、/lib等目录无法被写入。这能有效阻止恶意软件在运行时向/bin/sh注入后门,或篡改系统二进制文件。当然,你需要确保应用的所有写操作都指向/tmp或一个显式声明的emptyDir或volumeMount。 -
allowPrivilegeEscalation: false:这是runAsNonRoot的加强版。它禁止进程通过setuid或setgid二进制文件(如/usr/bin/ping)来提升自身权限。即使你的镜像里不小心包含了ping,这个设置也能让它失效。 -
capabilities.drop: ["ALL"]:Linux Capabilities 是对 root 权限的精细化拆分。ALL表示丢弃所有能力。一个典型的、安全的最小集合是:capabilities: drop: - ALL add: - NET_BIND_SERVICE # 如果你的应用需要监听 80/443 端口
这七项加起来,就是一个 Pod 的“安全基线”。我们把它封装成一个
kustomize
的
bases
目录,所有新项目都
kustomize build
这个基线,再叠加自己的业务配置。这确保了安全不是靠人肉记忆,而是靠工程化流程。
3.3 服务间通信:NetworkPolicy 的“默认拒绝”哲学
在 DOKS 上,
calico
默认是启用的,但它的默认行为是“允许所有”。这就像一栋大楼,所有房间的门都是敞开的。
NetworkPolicy
就是你的门禁系统。
我们的实践是:
每个命名空间(Namespace)创建之初,第一件事就是部署一条
default-deny
策略
。
# default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: my-app
spec:
podSelector: {} # 选择该命名空间下的所有 Pod
policyTypes:
- Ingress
- Egress
这条策略本身不定义任何允许规则,它只做一件事: 拒绝所有入站(Ingress)和出站(Egress)流量 。它像一张白纸,你必须在上面亲手画出每一条“允许”的线。
然后,你才开始添加具体的策略。例如,
frontend
命名空间的 Pod 只能访问
backend
命名空间的
8080
端口:
# frontend-to-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-to-backend
namespace: frontend
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: backend
podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 8080
这个过程强迫你去思考:“我的服务到底需要和谁通信?需要什么协议?需要哪个端口?”而不是“反正都开着,有需要再说”。我们曾帮一家客户审计,发现他们的
monitoring
命名空间里的 Prometheus,竟然能
egress
到互联网上的任意 IP 和端口。这完全是不必要的风险,一条
NetworkPolicy
就能把它锁死在
metrics
命名空间内部。
4. 零信任(Zero Trust)不是玄学:在 DOKS 上构建“永不信任,始终验证”的数据流
“Zero Trust”这个词被用滥了,但在 Kubernetes 的语境下,它有一个极其具体、可落地的含义: 任何两个 Pod 之间的通信,都不能基于“它们在同一个集群里”这个事实而被默认信任。每一次连接,都必须经过显式的、可审计的身份认证和授权。 在 DOKS 上,这主要通过 Service Mesh(服务网格)来实现,而 Istio 是目前最成熟的选择。
4.1 为什么是 Istio?为什么不是 Linkerd 或 Consul?
初创团队常纠结于选型。Linkerd 更轻量,Consul 更通用。但我们坚定推荐 Istio,原因有三:
-
与 DOKS 的兼容性经过充分验证 :DigitalOcean 官方文档和 GitHub 示例仓库中,Istio 是唯一被完整演示的 Service Mesh。它的
istioctl install命令能完美适配 DOKS 的containerd运行时和calicoCNI,几乎零配置即可完成安装。而 Linkerd 的linkerd check在某些 DOKS 版本上会报CNI plugin not found的警告,需要额外调试。 -
mTLS(双向 TLS)是开箱即用的默认行为 :Istio 的
istiod控制平面,会自动为每个注入了 Sidecar(istio-proxy)的 Pod 生成短期证书(默认 24 小时有效期),并强制所有 Pod 间的通信使用 mTLS 加密。这意味着,frontendPod 发送给backendPod 的 HTTP 请求,在网络层面上是被加密的 TLS 流量,中间人(Man-in-the-Middle)无法窃听或篡改。这解决了“服务间通信明文传输”的老大难问题。 -
细粒度的授权策略(AuthorizationPolicy) :Istio 的
AuthorizationPolicy,比原生 Kubernetes 的NetworkPolicy更进一步。NetworkPolicy只能控制“IP+端口”,而AuthorizationPolicy可以控制“HTTP 方法+路径+Header+JWT Claim”。例如,你可以写一条策略:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: api-read-only namespace: backend spec: selector: matchLabels: app: api-server rules: - from: - source: principals: ["cluster.local/ns/frontend/sa/frontend-sa"] to: - operation: methods: ["GET", "HEAD"] paths: ["/v1/users/*"]这条策略的意思是:“只有
frontend命名空间里,名为frontend-sa的 ServiceAccount 启动的 Pod,才能以GET或HEAD方法,访问/v1/users/*这个路径”。它把权限控制从网络层,精准地下沉到了应用层。
4.2 Istio 的“渐进式”落地:从自动注入到全链路追踪
在资源紧张的初创团队,我们反对一次性全量部署 Istio。我们采用三步走的渐进策略:
第一步:启用 Sidecar 自动注入(Auto-Injection)
在目标命名空间(如
production
)上打一个标签:
kubectl label namespace production istio-injection=enabled
然后,所有新创建的 Pod,都会被
istiod
自动注入一个
istio-proxy
容器。这个
istio-proxy
是一个轻量级的 Envoy 代理,它会劫持 Pod 的所有入站(Inbound)和出站(Outbound)流量。此时,mTLS 已经在后台静默工作,但你的应用代码完全无感,零改造。
第二步:定义
PeerAuthentication
策略,强制 mTLS
创建一个
PeerAuthentication
资源,作用域为整个集群:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT # 强制所有流量必须使用 mTLS
这条策略生效后,任何未注入 Sidecar 的 Pod,将无法与注入了 Sidecar 的 Pod 通信。这会立刻暴露出那些“裸奔”的、未被网格化的服务,逼着你去处理它们。
第三步:引入
RequestAuthentication
与
AuthorizationPolicy
当 mTLS 稳定运行一周后,再引入应用层的认证与授权。
RequestAuthentication
用于验证 JWT(JSON Web Token)令牌,通常由你的 API Gateway(如 Kong 或 Traefik)在入口处签发。
AuthorizationPolicy
则基于 JWT 中的
sub
(Subject)或
groups
字段,决定该请求是否有权访问后端服务。
这个过程会产生大量可观测性数据。
istioctl dashboard kiali
会为你展示一张实时的服务拓扑图,上面清晰地标出了每个服务的请求成功率、延迟、错误率。你会发现,一个看似健康的
GET /healthz
接口,其成功率可能只有 99.2%,背后是 0.8% 的超时——这往往是网络抖动或下游服务过载的早期信号。这种“全链路追踪”能力,是 Zero Trust 的眼睛和耳朵。
提示:Istio 的资源对象(
VirtualService,DestinationRule,Gateway)本身也是 Kubernetes 的 CRD(Custom Resource Definition)。这意味着,你可以像管理 Deployment 一样,用 GitOps(如 Argo CD)来管理它们。把所有的 Istio 配置都放进 Git 仓库,每次kubectl apply都是一次可追溯、可回滚的变更。这是 Zero Trust 文化在工程实践上的终极体现:一切皆代码,一切皆可审计。
5. 秘密(Secret)管理:告别
kubectl create secret generic
的原始时代
在 DOKS 上,
kubectl create secret generic my-db-secret --from-literal=username=admin --from-literal=password=123456
是最便捷的方式,也是最危险的习惯。它把明文密码塞进了 etcd,而 etcd 的加密密钥由 DigitalOcean 托管,你无法控制其轮换周期。更糟的是,这个 Secret 一旦被创建,就永远躺在那里,直到你手动
delete
它。而现实中,密码轮换是常态。
5.1 Secret 的生命周期:从“创建”到“销毁”的四个阶段
一个安全的 Secret 管理流程,必须覆盖其完整的生命周期:
-
生成(Generation) :密码不应由人手动生成。应使用
openssl rand -base64 32或 HashiCorp Vault 的kv引擎来生成高强度、随机的密钥。DOKS 本身不提供密钥生成服务,所以你需要一个外部的、可信的密钥管理服务(KMS)。 -
存储(Storage) :生成的密钥,不应以明文形式存入 Git 仓库。应将其存入 Vault 的
kv-v2路径下,例如secret/data/myapp/db-credentials。Vault 会对它进行二次加密,并记录每一次读取的日志。 -
注入(Injection) :当你的应用 Pod 启动时,它需要从 Vault 获取密钥。这里有两种主流模式:
-
Sidecar 模式(Vault Agent Injector)
:在 Pod 的 YAML 中,添加一个
vault.hashicorp.com/agent-inject: 'true'的 annotation。Vault Agent Injector 会自动为 Pod 注入一个vault-agent容器,并将密钥以文件形式挂载到/vault/secrets/下。你的应用只需读取这个文件。 -
Init Container 模式(Vault Agent)
:在 Pod 的
initContainers中,启动一个vault-agent,让它先从 Vault 拉取密钥,写入一个emptyDir卷,然后主容器再从这个卷里读取。这种方式更轻量,但需要你管理 Init Container 的镜像和配置。
-
Sidecar 模式(Vault Agent Injector)
:在 Pod 的 YAML 中,添加一个
-
轮换(Rotation) :这是最常被忽视的一环。Vault 的
kv-v2引擎支持版本化。当你在 Vault 中更新secret/data/myapp/db-credentials时,它会创建一个新版本(v2),而旧版本(v1)依然存在。你的应用 Pod 不会自动感知到新版本。你需要触发一次滚动更新(Rolling Update),让新的 Pod 启动时拉取 v2 的密钥。这可以通过一个简单的kubectl rollout restart deployment/my-app来完成,前提是你的 Deployment 的spec.template.spec.containers中,env或volumeMounts的配置,是动态绑定到 Vault 的路径的。
5.2 一个可落地的 Vault + DOKS 集成方案
我们为一家客户搭建的方案,只用了 3 个核心组件:
-
HashiCorp Vault(托管在 DigitalOcean Droplet 上) :我们选择在一台独立的、最小规格($5/month)的 Ubuntu Droplet 上安装 Vault。它与你的 DOKS 集群在同一个 VPC 内,通过内网通信,延迟极低。我们禁用了 Vault 的 UI,只通过
vaultCLI 和 API 进行管理。 -
Vault Agent Injector(作为 Kubernetes DaemonSet 部署) :这个组件监听 Kubernetes API,当它发现一个带有
vault.hashicorp.com/agent-inject: 'true'annotation 的 Pod 创建请求时,它会拦截该请求,修改 Pod 的 YAML,注入vault-agent容器和相应的volumeMounts。 -
一个标准化的 Helm Chart(
myapp-chart) :这个 Chart 的values.yaml中,有一个vault区块:vault: enabled: true address: "http://vault.vault.svc.cluster.local:8200" # Vault Service 的 ClusterIP role: "myapp-role" # Vault 中为该应用定义的 Role secrets: - path: "secret/data/myapp/db-credentials" type: "kv-v2" keys: ["username", "password"]当你执行
helm install myapp ./myapp-chart --set vault.enabled=true时,Helm 会根据values.yaml,自动生成带有正确 annotation 和 volumeMount 的 Deployment。
这个方案的好处是:
密钥的生命周期完全脱离了 Kubernetes 的
Secret
对象
。它不再是一个静态的、可能被
kubectl get secret -o yaml
导出的资源,而是一个动态的、按需拉取的、有审计日志的、可版本化的凭证。它把“秘密管理”从一个运维操作,变成了一个标准的、可复用的、可测试的软件交付环节。
6. 持续验证:建立你的 DOKS 安全健康度仪表盘
安全不是一次性的“加固项目”,而是一个持续的、数据驱动的“健康监测”过程。在 DOKS 上,你需要一个仪表盘,它能告诉你:今天,我的集群比昨天更安全,还是更脆弱?
6.1 三个必建的、自动化的安全检查脚本
我们为所有客户部署了三个核心脚本,它们每天凌晨 2 点,通过 CronJob 在集群内自动运行,并将结果推送到 Slack 频道:
脚本一:
check-pod-security.sh
—— 扫描所有 Pod 的 SecurityContext
#!/bin/bash
# 这个脚本会遍历所有命名空间下的所有 Pod
# 报告任何违反“七项硬性约束”的实例
echo "=== POD SECURITY CHECK ==="
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
echo "Checking namespace: $ns"
# 检查 runAsNonRoot
kubectl get pods -n $ns -o json | jq -r '.items[] | select(.spec.securityContext.runAsNonRoot != true) | .metadata.name' | while read pod; do
echo "❌ [${ns}] Pod ${pod} missing runAsNonRoot: true"
done
# 检查 allowPrivilegeEscalation
kubectl get pods -n $ns -o json | jq -r '.items[] | select(.spec.securityContext.allowPrivilegeEscalation == true) | .metadata.name' | while read pod; do
echo "❌ [${ns}] Pod ${pod} has allowPrivilegeEscalation: true"
done
done
脚本二:
check-network-policy.sh
—— 验证 NetworkPolicy 的覆盖率
#!/bin/bash
echo "=== NETWORK POLICY COVERAGE CHECK ==="
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
# 检查该命名空间下是否存在 default-deny 策略
if ! kubectl get networkpolicy -n $ns default-deny >/dev/null 2>&1; then
echo "⚠️ [${ns}] Missing default-deny NetworkPolicy!"
fi
# 检查该命名空间下是否有 Pod 没有被任何 NetworkPolicy 选中
# (即,没有任何 NetworkPolicy 的 podSelector 能匹配到它)
for pod in $(kubectl get pods -n $ns -o jsonpath='{.items[*].metadata.name}'); do
# 这里用一个简化的逻辑:如果该 Pod 有 label,且没有 NetworkPolicy 的 podSelector 匹配它,则报警
# 实际生产中,我们会用更精确的匹配逻辑
labels=$(kubectl get pod -n $ns $pod -o jsonpath='{.metadata.labels}')
if [[ -z "$labels" ]]; then
echo "⚠️ [${ns}] Pod ${pod} has no labels, cannot be targeted by NetworkPolicy"
fi
done
done
脚本三:
check-secret-rotation.sh
—— 审计 Secret 的年龄
#!/bin/bash
echo "=== SECRET ROTATION AUDIT ==="
# 列出所有 Secret,并计算其创建时间距今多少天
kubectl get secrets --all-namespaces -o json | \
jq -r '.items[] | select(.metadata.creationTimestamp != null) |
"\(.metadata.namespace)/\(.metadata.name) \(.metadata.creationTimestamp)"' | \
while read line; do
ns_name=$(echo $line | awk '{print $1}')
created=$(echo $line | awk '{print $2}')
# 将 ISO8601 时间戳转换为 Unix 时间戳,并计算天数差
age_days=$(( ($(date -d "$created" +%s 2>/dev/null) - $(date -d "now" +%s)) / 86400 ))
if [[ $age_days -gt 90 ]]; then
echo "❌ [${ns_name}] Secret is ${age_days} days old (>90 days)"
fi
done
这三个脚本,构成了你的安全基线。它们不追求“100% 安全”,而是追求“100% 可见”。当某天 Slack 里弹出一条
❌ [production] Pod api-server-7b8c9d0e-fgh12 has allowPrivilegeEscalation: true
的消息时,它不是一个故障告警,而是一个明确的、可立即行动的工单。它告诉你,某个新上线的、未经安全审查的 Helm Chart,绕过了你们的 CI/CD 流水线中的安全检查。
6.2 将安全指标融入你的 SLO(服务等级目标)
最后,把安全从一个“后台运维事项”,提升为一个“前台业务指标”。我们建议,将以下两个指标,写入你的工程团队的季度 OKR(Objectives and Key Results)中:
-
**Key Result 1:所有生产命名空间的
default-denyNetworkPolicy 覆盖
更多推荐
所有评论(0)