Kubernetes 安全折腾手记
Kubernetes 安全一旦进项目,好看的架构图就没那么管用了。
这次做 Kubernetes 安全加固,从 RBAC 到安全策略,。
基础安全
集群访问控制
# API Server 认证配置
apiVersion: v1
kind: Config
clusters:
- cluster:
server: https://kubernetes-api-server
certificate-authority-data: <ca-cert>
name: kubernetes
users:
- name: admin
user:
client-certificate-data: <client-cert>
client-key-data: <client-key>
contexts:
- context:
cluster: kubernetes
user: admin
name: admin-context
current-context: admin-context
RBAC 基础
# 创建 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: default
---
# 创建 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: myapp-role
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update", "delete"]
---
# 创建 RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: myapp-rolebinding
namespace: default
subjects:
- kind: ServiceAccount
name: myapp-sa
namespace: default
roleRef:
kind: Role
name: myapp-role
apiGroup: rbac.authorization.k8s.io
Pod 安全
安全上下文
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: myapp
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Pod 安全策略
# 创建 PodSecurityPolicy
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted-psp
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
readOnlyRootFilesystem: false
---
# 创建 ClusterRole for PSP
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: psp-restricted-role
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['restricted-psp']
---
# 创建 RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: psp-restricted-binding
namespace: default
subjects:
- kind: ServiceAccount
name: myapp-sa
namespace: default
roleRef:
kind: ClusterRole
name: psp-restricted-role
apiGroup: rbac.authorization.k8s.io
网络策略
# 拒绝所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 允许特定流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-specific
namespace: default
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
ports:
- protocol: TCP
port: 80
egress:
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
Secret 管理
Kubernetes Secret
# 创建 Secret
apiVersion: v1
kind: Secret
metadata:
name: myapp-secret
type: Opaque
stringData:
database-url: "postgres://user:password@db:5432/myapp"
api-key: "your-api-key"
---
# 使用 Secret
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secret
key: database-url
- name: API_KEY
valueFrom:
secretKeyRef:
name: myapp-secret
key: api-key
External Secrets
# 使用 External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: myapp-secret
creationPolicy: Owner
data:
- secretKey: database-url
remoteRef:
key: myapp/credentials
property: database_url
- secretKey: api-key
remoteRef:
key: myapp/credentials
property: api_key
Vault 集成
# 使用 Vault Agent Injector
apiVersion: v1
kind: Pod
metadata:
name: myapp
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp-role"
vault.hashicorp.com/agent-inject-secret-database-url: "secret/data/myapp#database_url"
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: DATABASE_URL
value: "vault:secret/data/myapp#database_url"
镜像安全
镜像扫描
# 使用 Trivy 扫描镜像
trivy image myapp:latest
# 使用 Clair 扫描镜像
clairctl scan myapp:latest
# 使用 Grype 扫描镜像
grype myapp:latest
镜像签名验证
# 使用 Notary 签名镜像
# 1. 生成密钥对
notary key generate myapp
# 2. 签名镜像
notary sign myapp:latest
# 3. 验证签名
notary verify myapp:latest
# 在 Kubernetes 中配置镜像策略
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageTagDig
metadata:
name: image-tag-policy
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["default"]
parameters:
tags:
- digest
审计日志
审计配置
# 审计策略
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
namespaces: ["default"]
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
verbs: ["create", "update", "delete"]
- level: Request
users: ["system:anonymous"]
verbs: ["get", "list", "watch"]
---
# 启用审计日志
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
volumeMounts:
- name: audit-config
mountPath: /etc/kubernetes/audit-policy.yaml
- name: audit-log
mountPath: /var/log/kubernetes
审计日志分析
# 使用 kubectl audit
kubectl audit logs /var/log/kubernetes/audit.log
# 使用 Falco 监控安全事件
falco --config /etc/falco/falco.yaml
踩过的坑
坑一:权限过大
给 ServiceAccount 分配了过大权限。
解决:遵循最小权限原则。
# 错误:给 ServiceAccount 所有权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: too-permissive
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# 正确:只给必要的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: minimal-permissions
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
坑二:镜像漏洞
使用了有漏洞的镜像。
解决:定期扫描镜像,及时更新。
# CI/CD 中集成镜像扫描
trivy image --severity HIGH,CRITICAL myapp:latest
# 或者在 Kubernetes 中使用准入控制器
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/trivy-operator/main/deploy/static/trivy-operator.yaml
坑三:网络策略配置错误
网络策略配置错误,导致服务无法通信。
解决:仔细测试网络策略,逐步放开。
# 先测试默认拒绝策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
# 然后逐步添加允许策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-traffic
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- ports:
- protocol: TCP
port: 80
安全检查清单
集群安全
- RBAC 配置正确
- 网络策略配置
- 镜像安全扫描
- 审计日志启用
Pod 安全
- SecurityContext 配置
- 只读根文件系统
- 非 root 用户
- 资源限制
Secret 安全
- Secret 加密存储
- 不使用明文 Secret
- 定期轮换密钥
网络安全
- 网络策略配置
- 入口/出口控制
- TLS 加密
写在最后
Kubernetes 安全这东西,不只是技术,是运维文化和流程。
解决了:
- 集群安全
- 容器安全
- 数据安全
带来了:
- 复杂度增加
- 运维成本
- 性能影响
安全加固之前先评估:
- 安全要求
- 团队能力
- 预算
- 性能要求
不是所有集群都需要严格的安全配置,但基本安全不能少。
这次 Kubernetes 安全加固花了一个月,从 RBAC 到安全策略。加固完成后,安全事件减少了 80%,合规性检查通过率从 60% 提升到 95%。
版权声明: 本文首发于 指尖魔法屋-Kubernetes 安全折腾手记(https://blog.thinkmoon.cn/post/92-kubernetes-security-rbac-security-policies-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。