Kubernetes安全实战:从RBAC到NetworkPolicy 引言 Kubernetes已成为云原生应用的标准部署平台,但安全问题也随之而来。从集群配置到应用部署,从网络隔离到密钥管理,安全需要贯穿全生命周期。本文将深入讲解K8s安全的最佳实践和实施方案。 一、集群安全基础 1.1 API Server安全 加固API Server: 1.2 审计日志 1.3 加密etcd数据 二、RBAC权限控制 2.1 Role和RoleBinding 2.2 ClusterRole和ClusterRoleBinding 2.3 服务账户(ServiceAccount) 使用服务账户: 三、Pod安全 3.1 SecurityContext 3.
Kubernetes已成为云原生应用的标准部署平台,但安全问题也随之而来。从集群配置到应用部署,从网络隔离到密钥管理,安全需要贯穿全生命周期。本文将深入讲解K8s安全的最佳实践和实施方案。
加固API Server:
# /etc/kubernetes/manifests/kube-apiserver.yaml apiVersion: v1 kind: Pod metadata: name: kube-apiserver namespace: kube-system spec: containers: - name: kube-apiserver command: - kube-apiserver # 启用RBAC - --authorization-mode=Node,RBAC # 启用Node授权 - --enable-bootstrap-token-auth=true # 限制匿名访问 - --anonymous-auth=false # 启用审计日志 - --audit-log-path=/var/log/kubernetes/audit.log - --audit-policy-file=/etc/kubernetes/audit-policy.yaml # 准入控制 - --enable-admission-plugins=NodeRestriction,PodSecurityPolicy # 安全端口 - --secure-port=6443 # TLS证书 - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key # 服务账户密钥 - --service-account-key-file=/etc/kubernetes/pki/sa.pub # 客户端CA - --client-ca-file=/etc/kubernetes/pki/ca.crt
# /etc/kubernetes/audit-policy.yaml apiVersion: audit.k8s.io/v1 kind: Policy rules: # 记录所有修改操作 - level: RequestResponse verbs: ["create", "update", "patch", "delete"] resources: - group: "" resources: ["pods", "deployments", "services", "configmaps", "secrets"] # 记录Secret访问 - level: Request verbs: ["get", "list"] resources: - group: "" resources: ["secrets"] # 记录认证失败 - level: Metadata userGroups: ["system:authenticated"] verbs: ["create", "update", "patch", "delete"] # 不记录configmap的读取操作 - level: None verbs: ["get", "list"] resources: - group: "" resources: ["configmaps"]
# /etc/kubernetes/manifests/kube-apiserver.yaml - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml # /etc/kubernetes/encryption-config.yaml apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: <base64-encoded-key> - identity: {} # 回退到无加密
# 定义命名空间级别的角色 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: pod-reader rules: - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["pods/exec"] verbs: ["create"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-pods namespace: default subjects: - kind: User name: jane@example.com apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io
# 集群级别的角色 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cluster-admin rules: - apiGroups: ["*"] resources: ["*"] verbs: ["*"] --- # 只读集群角色 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: view-only rules: - apiGroups: [""] resources: ["*"] verbs: ["get", "list", "watch"] - nonResourceURLs: ["/metrics", "/healthz"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: read-only-everywhere subjects: - kind: Group name: readonly@example.com apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: view-only apiGroup: rbac.authorization.k8s.io
# 创建服务账户 apiVersion: v1 kind: ServiceAccount metadata: name: cicd-bot namespace: default --- # 授予权限 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: deployer namespace: default rules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "create", "update", "patch", "delete"] - apiGroups: [""] resources: ["services"] verbs: ["get", "create", "update"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: deployer-binding namespace: default subjects: - kind: ServiceAccount name: cicd-bot namespace: default roleRef: kind: Role name: deployer apiGroup: rbac.authorization.k8s.io
使用服务账户:
apiVersion: v1 kind: Pod metadata: name: app spec: serviceAccountName: cicd-bot # 指定服务账户 containers: - name: app image: myapp:latest
apiVersion: v1 kind: Pod metadata: name: secure-pod spec: securityContext: runAsNonRoot: true # 不以root运行 runAsUser: 1000 # 指定用户ID runAsGroup: 3000 # 指定组ID fsGroup: 2000 # 文件系统组ID seccompProfile: type: RuntimeDefault # 使用默认seccomp配置 containers: - name: app image: nginx:latest securityContext: allowPrivilegeEscalation: false # 禁止权限提升 capabilities: drop: - ALL # 删除所有能力 add: - NET_BIND_SERVICE # 只添加必要的 readOnlyRootFilesystem: true # 只读根文件系统 runAsNonRoot: true volumes: - name: cache emptyDir: {} # 需要写的目录挂载emptyDir
Pod Security Standards(v1.25+):
# namespace级别的安全标准 apiVersion: v1 kind: Namespace metadata: name: secure-namespace labels: pod-security.kubernetes.io/enforce: restricted # 强制执行 pod-security.kubernetes.io/enforce-version: latest pod-security.kubernetes.io/audit: restricted # 审计违规 pod-security.kubernetes.io/warn: baseline # 警告
三种安全级别:
apiVersion: v1 kind: Pod metadata: name: resource-limited-pod spec: containers: - name: app image: myapp:latest resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" # 优雅关闭 lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 10; nginx -s quit"] # 健康检查 livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5
# 默认拒绝所有入站流量 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress namespace: default spec: podSelector: {} policyTypes: - Ingress --- # 允许特定Pod间通信 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-from-frontend namespace: default spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080 --- # 允许出站流量 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns namespace: default spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: name: kube-system ports: - protocol: UDP port: 53 - to: - namespaceSelector: {} podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53
# 启用mTLS自动加固 apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: default spec: mtls: mode: STRICT # 强制mTLS --- # 授权策略 apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: allow-get-products namespace: default spec: selector: matchLabels: app: productpage action: ALLOW rules: - to: - operation: methods: ["GET"] paths: ["/api/v1/products/*"] from: - source: principals: ["cluster.local/ns/default/sa/frontend"] when: - key: source.ip values: ["10.0.0.0/8"]
# 隔离敏感工作负载 apiVersion: v1 kind: Namespace metadata: name: production labels: name: production environment: prod --- # 生产环境默认拒绝 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: prod-default-deny namespace: production spec: podSelector: {} policyTypes: - Ingress - Egress --- # 只允许特定入口 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-ingress namespace: production spec: podSelector: matchLabels: tier: backend policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: ingress-nginx ports: - protocol: TCP port: 8080 - Egress egress: - to: - namespaceSelector: matchLabels: name: kube-system ports: - protocol: UDP port: 53 - protocol: TCP port: 443
# 创建Secret apiVersion: v1 kind: Secret metadata: name: db-credentials namespace: default type: Opaque data: username: YWRtaW4= # base64编码 password: cGFzc3dvcmQ= --- # 使用Secret apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: myapp:latest env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-credentials key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-credentials key: password volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true volumes: - name: secret-volume secret: secretName: db-credentials
apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: aws-secrets-manager namespace: default spec: provider: aws: service: SecretsManager region: us-west-2 auth: jwt: serviceAccountRef: name: external-secrets-sa --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-credentials namespace: default spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: db-credentials creationPolicy: Owner data: - secretKey: username remoteRef: key: prod/db/username - secretKey: password remoteRef: key: prod/db/password
# Vault Agent注入 apiVersion: v1 kind: Pod metadata: name: app-with-vault annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "myapp" vault.hashicorp.com/agent-inject-secret-database: "secret/data/myapp/config" vault.hashicorp.com/agent-inject-template-database: | {{- with secret "secret/data/myapp/config" -}} DB_HOST="{{ .Data.data.host }}" DB_PASSWORD="{{ .Data.data.password }}" {{- end }} spec: serviceAccountName: vault-auth containers: - name: app image: myapp:latest
# 使用Trivy扫描镜像 trivy image myapp:latest # 集成到CI/CD cat > .github/workflows/scan.yaml <<EOF name: Scan Image on: [push] jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Scan image uses: aquasecurity/trivy-action@master with: image-ref: myapp:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' - name: Upload results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' EOF
# 使用Cosign签名镜像 cosign sign --key cosign.key myapp:latest # 验证签名 cosign verify --key cosign.pub myapp:latest # K8s准入控制验证签名 kubectl apply -f - <<EOF apiVersion: v1 kind: ValidatingWebhookConfiguration metadata: name: cosign-admission webhooks: - name: cosign-admission rules: - operations: ["CREATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] clientConfig: service: namespace: cosign-system name: cosign-webhook path: /validate admissionReviewVersions: ["v1"] sideEffects: None EOF
# 多阶段构建 FROM golang:1.21 as builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -o myapp # 最小镜像 FROM scratch COPY --from=builder /app/myapp /myapp COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ ENTRYPOINT ["/myapp"] # 或者使用distroless FROM gcr.io/distroless/static-debian11 COPY --from=builder /app/myapp /myapp ENTRYPOINT ["/myapp"]
# Falco规则示例 - rule: Detect root shell desc: Detect shell spawned by root condition: > spawned_process and shell_procs and user.uid = 0 output: > Root shell spawned (user=%user.name container=%container.id shell=%proc.name) priority: CRITICAL --- - rule: Sensitive file access desc: Detect access to sensitive files condition: > open_read and sensitive_files and not proc.name in (node, sshd, vault) output: > Sensitive file opened (user=%user.name file=%fd.name) priority: WARNING
# 运行CIS基准检查 kube-bench --benchmark cis-1.23 # 输出JSON格式 kube-bench --json > report.json # 定期检查 kubectl create job kube-bench --from=cronjob/kube-bench-check
K8s安全核心要点: