容器安全扫描与加固
大约 17 分钟约 5208 字
容器安全扫描与加固
简介
容器安全是云原生安全的重中之重。从镜像构建到运行时,容器面临供应链攻击、漏洞利用、配置错误、密钥泄露等多重威胁。本文系统讲解容器安全的全生命周期管理,包括镜像扫描(Trivy)、镜像签名(cosign)、SBOM 生成、运行时安全(Falco)、Dockerfile 安全清单、网络策略、Pod 安全标准和 CI/CD 安全门禁。
特点
容器安全生命周期
安全分层模型
┌─────────────────────────────────────────────────────┐
│ CI/CD 安全线 │
│ 镜像扫描 → 签名验证 → 合规检查 → 准入控制 │
├─────────────────────────────────────────────────────┤
│ 运行时安全 │
│ Falco 检测 → 网络策略 → Pod 安全标准 → Seccomp │
├─────────────────────────────────────────────────────┤
│ 镜像安全 │
│ 基础镜像选择 → Dockerfile 加固 → 密钥管理 → SBOM │
├─────────────────────────────────────────────────────┤
│ 主机安全 │
│ 内核加固 → 容器运行时 → 节点更新 → 权限隔离 │
└─────────────────────────────────────────────────────┘Trivy 镜像扫描
安装和基本使用
# 安装 Trivy
# Ubuntu/Debian
sudo apt-get install wget apt-transport-https gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
# macOS
brew install trivy
# Docker 方式使用
docker run --rm -v trivy-cache:/root/.cache/ aquasec/trivy image nginx:latest镜像扫描实战
# 扫描镜像漏洞
trivy image nginx:1.25
# 扫描指定严重级别的漏洞
trivy image --severity HIGH,CRITICAL nginx:1.25
# 输出 JSON 格式
trivy image --format json --output report.json nginx:1.25
# 输出表格格式(默认)
trivy image --format table nginx:1.25
# 只显示已修复的漏洞
trivy image --ignore-unfixed nginx:1.25
# 忽略特定漏洞(使用 .trivyignore 文件)
cat > .trivyignore << 'EOF'
# CVE-2023-XXXXX - 暂无修复版本
CVE-2023-44487
# CVE-2024-XXXXX - 已评估风险可接受
CVE-2024-12345
EOF
trivy image --ignorefile .trivyignore nginx:1.25
# 扫描本地的 Dockerfile
trivy config Dockerfile
# 扫描文件系统(项目依赖漏洞)
trivy fs --scanners vuln,secret,misconfig /path/to/project
# 扫描 Kubernetes 集群
trivy k8s --report summary cluster
# 扫描特定 Namespace
trivy k8s -n default --report summary all
# 扫描 Ingress/IAM 等云资源配置
trivy config ./k8s-manifests/Trivy 配置文件
# trivy.yaml — Trivy 配置文件
version: 2
severity:
- CRITICAL
- HIGH
scan:
scanners:
- vuln
- secret
- misconfig
vulnerability:
type:
- os
- library
ignore-unfixed: true
# 跳过特定 CVE
skip-update: false
# 离线模式(使用本地数据库)
offline-scan: false
secret:
# 自定义密钥检测规则
config-data: {}
misconfig:
# 包含的检查器
include:
- dockerfile
- kubernetes
# 跳过的检查
exclude:
- AKS
- EKS
format: table
exit-code: 1
# 发现 CRITICAL 漏洞时返回非零退出码
# 适用于 CI/CD 门禁SBOM 生成
# 生成 SPDX 格式的 SBOM
trivy image --format spdx-json --output sbom.json nginx:1.25
# 生成 CycloneDX 格式的 SBOM
trivy image --format cyclonedx --output sbom.xml nginx:1.25
# 基于 SBOM 扫描漏洞
trivy sbom --severity HIGH,CRITICAL sbom.json
# 使用 syft 生成 SBOM(另一个流行工具)
# 安装 syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# 生成 SBOM
syft nginx:1.25 -o spdx-json > sbom.json
syft nginx:1.25 -o cyclonedx-json > sbom-cdx.json
# 使用 grype 基于 SBOM 扫描漏洞
grype sbom:./sbom.jsonTrivy Server 模式
# trivy-server.yaml — Trivy 服务端部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: trivy-server
namespace: security
spec:
replicas: 1
selector:
matchLabels:
app: trivy-server
template:
metadata:
labels:
app: trivy-server
spec:
containers:
- name: trivy
image: aquasec/trivy:0.50.0
args:
- "server"
- "--listen"
- "0.0.0.0:4954"
- "--cache-dir"
- "/tmp/trivy-cache"
ports:
- containerPort: 4954
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
volumeMounts:
- name: cache
mountPath: /tmp/trivy-cache
volumes:
- name: cache
emptyDir:
sizeLimit: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: trivy-server
namespace: security
spec:
type: ClusterIP
ports:
- port: 4954
targetPort: 4954
selector:
app: trivy-server# 客户端连接 Trivy Server
trivy image --server http://trivy-server.security:4954 nginx:1.25镜像签名
Cosign 签名
# 安装 cosign
curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 -o /usr/local/bin/cosign
chmod +x /usr/local/bin/cosign
# 生成密钥对
cosign generate-key-pair
# 输出:cosign.key(私钥)和 cosign.pub(公钥)
# 使用私钥签名镜像
cosign sign --key cosign.key myregistry.com/myapp:v1.0.0
# 验证签名
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0
# 使用 Keyless 模式(推荐,无需管理密钥)
cosign sign myregistry.com/myapp:v1.0.0
# Keyless 验证
cosign verify myregistry.com/myapp:v1.0.0
# 签名时附加签名信息(提交 SHA、构建信息等)
cosign sign \
--key cosign.key \
-a commit=$(git rev-parse HEAD) \
-a builder=github-actions \
-a build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
myregistry.com/myapp:v1.0.0
# 验证时检查签名信息
cosign verify \
--key cosign.pub \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity-regexp="^https://github.com/myorg/.+" \
myregistry.com/myapp:v1.0.0Notation 签名(替代方案)
# 安装 notation
curl -Lo /tmp/notation.tar.gz https://github.com/notaryproject/notation/releases/latest/download/notation_1.1.0-linux_amd64.tar.gz
tar -xzf /tmp/notation.tar.gz -C /usr/local/bin notation
# 生成签名证书
notation cert generate-test --default "my-cert"
# 查看证书
notation cert list
# 签名镜像
notation sign myregistry.com/myapp:v1.0.0
# 验证签名
notation verify myregistry.com/myapp:v1.0.0
# 配置信任策略
cat > ~/.config/notation/trustpolicy.json << 'EOF'
{
"version": "1.0",
"trustPolicies": [
{
"name": "my-images",
"registryScopes": ["myregistry.com/myorg"],
"signatureVerification": {
"level": "strict"
},
"trustStores": ["ca:my-cert"],
"trustedIdentities": ["*"]
}
]
}
EOFKyverno 准入控制(验证签名)
# security/kyverno-verify-signature.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
annotations:
policies.kyverno.io/title: 验证镜像签名
policies.kyverno.io/description: 要求所有镜像必须经过 cosign 签名
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.com/myorg/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
attestations:
- type: https://example.com/vuln
conditions:
- all:
- key: "{{ criticals }}"
operator: Equals
value: 0Dockerfile 安全清单
安全的 Dockerfile
# Dockerfile.secure — 安全加固的 Dockerfile
# 1. 使用特定版本的基础镜像(不用 latest)
# 2. 使用 distroless 或 alpine 减小攻击面
FROM node:20.11-alpine AS builder
# 3. 设置非 root 用户
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
# 4. 设置工作目录
WORKDIR /app
# 5. 先复制依赖文件(利用缓存)
COPY package.json pnpm-lock.yaml ./
# 6. 安装依赖(生产依赖)
RUN corepack enable pnpm && pnpm install --frozen-lockfile --prod
# 7. 复制应用代码
COPY . .
# 8. 构建应用
RUN pnpm build
# 9. 生产阶段(多阶段构建)
FROM node:20.11-alpine AS production
# 10. 安装安全更新
RUN apk update && apk upgrade --no-cache && \
apk add --no-cache dumb-init
# 11. 创建非 root 用户
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
# 12. 设置安全相关的环境变量
ENV NODE_ENV=production \
NPM_CONFIG_PRODUCTION=true \
HELLO_UNSAFE_PERM=true
WORKDIR /app
# 13. 只复制构建产物
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
# 14. 切换到非 root 用户
USER appuser
# 15. 只暴露必要的端口
EXPOSE 3000
# 16. 使用 dumb-init 作为 PID 1(正确处理信号)
ENTRYPOINT ["dumb-init", "--"]
# 17. 健康检查
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# 18. 启动命令
CMD ["node", "dist/main.js"]Dockerfile 安全检查清单
## Dockerfile 安全检查清单
### 基础镜像
- [ ] 使用特定版本标签(不用 latest)
- [ ] 使用最小化基础镜像(distroless/alpine)
- [ ] 定期更新基础镜像版本
- [ ] 使用官方镜像或已验证的镜像源
### 用户权限
- [ ] 创建非 root 用户运行应用
- [ ] 使用 USER 指令切换用户
- [ ] 确保文件所有权正确(chown)
### 构建安全
- [ ] 使用多阶段构建(减小最终镜像大小)
- [ ] 不在镜像中包含构建工具
- [ ] 不在镜像中包含源代码
- [ ] 不在镜像中包含 .git 目录
### 密钥管理
- [ ] 不在 Dockerfile 中硬编码密码/Token
- [ ] 不使用 ARG 传递密钥(会留在镜像层中)
- [ ] 使用 Docker secrets 或环境变量
- [ ] 添加 .dockerignore 排除敏感文件
### 网络
- [ ] 只暴露必要的端口
- [ ] 不暴露调试端口
### 健康检查
- [ ] 配置 HEALTHCHECK 指令
- [ ] 健康检查端点不需要认证
### 其他
- [ ] 删除不必要的包和缓存
- [ ] 设置合适的文件权限
- [ ] 使用 dumb-init 或 tini 处理信号.dockerignore 配置
# .dockerignore
.git
.github
.gitignore
node_modules
npm-debug.log
dist
coverage
.env
.env.*
*.key
*.pem
*.p12
credentials.json
service-account.json
.vscode
.idea
*.md
!README.md
docker-compose*.yml
Dockerfile*
.dockerignore运行时安全 (Falco)
Falco 安装
# 使用 Helm 安装 Falco
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
# 安装 Falco
helm install falco falcosecurity/falco \
--namespace security \
--create-namespace \
--set driver.kind=ebpf \
--set falco.jsonOutput=true \
--set falco.httpOutput.enabled=true \
--set falco.httpOutput.url=http://falcosidekick.security:2801
# 验证安装
kubectl get pods -n security -l app=falcoFalco 自定义规则
# falco-rules.yaml — 自定义安全规则
# 通过 ConfigMap 挂载到 Falco
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-custom-rules
namespace: security
data:
custom-rules.yaml: |
# 规则:检测容器中运行 shell
- rule: Terminal Shell in Container
desc: 检测容器中打开的交互式 shell
condition: >
spawned_process and container and
proc.name in (bash, sh, zsh, fish) and
not proc.pname in (docker-entrypoint, entrypoint.sh)
output: >
在容器中检测到 Shell
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline image=%container.image.repository)
priority: WARNING
tags: [container, shell]
# 规则:检测敏感文件读取
- rule: Read Sensitive File
desc: 检测读取敏感文件的尝试
condition: >
open_read and container and
fd.name in (/etc/shadow, /etc/passwd, /etc/ssh/sshd_config) and
not proc.name in (passwd, chpasswd, useradd)
output: >
检测到读取敏感文件
(user=%user.name file=%fd.name container=%container.name
image=%container.image.repository)
priority: CRITICAL
tags: [filesystem, sensitive]
# 规则:检测未经授权的网络连接
- rule: Unauthorized Network Connection
desc: 检测容器到外部的异常连接
condition: >
outbound and container and
not fd.sip in (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and
not container.image.repository in (myregistry.com/trusted/*)
output: >
检测到异常外联
(user=%user.name container=%container.name
connection=%fd.name remote_ip=%fd.sip remote_port=%fd.sport
image=%container.image.repository)
priority: WARNING
tags: [network, unauthorized]
# 规则:检测特权容器操作
- rule: Privileged Container Activity
desc: 检测特权容器中的敏感操作
condition: >
container and container.privileged and
(open_write or spawned_process) and
not proc.name in (pause, kube-proxy)
output: >
特权容器中的可疑操作
(user=%user.name container=%container.name
operation=%evt.type file=%fd.name
image=%container.image.repository)
priority: CRITICAL
tags: [container, privileged]
# 规则:检测容器逃逸尝试
- rule: Container Escape Attempt
desc: 检测潜在的容器逃逸行为
condition: >
container and
(open_write and fd.name startswith /proc/sys) or
(open_write and fd.name startswith /sys/fs/cgroup) or
(open_write and fd.name contains docker.sock) or
(open_write and fd.name contains containerd.sock)
output: >
疑似容器逃逸行为
(user=%user.name container=%container.name
file=%fd.name image=%container.image.repository)
priority: CRITICAL
tags: [container, escape]
# 规则:检测加密货币挖矿
- rule: Detect Crypto Mining
desc: 检测潜在的加密货币挖矿进程
condition: >
spawned_process and container and
(proc.name in (xmrig, minerd, cgminer, bfgminer) or
proc.cmdline contains "stratum+tcp" or
proc.cmdline contains "pool.minexmr")
output: >
检测到疑似挖矿进程
(user=%user.name container=%container.name
process=%proc.name cmdline=%proc.cmdline
image=%container.image.repository)
priority: CRITICAL
tags: [malware, mining]Falcosidekick 告警通知
# falcosidekick.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: falcosidekick
namespace: security
spec:
replicas: 1
selector:
matchLabels:
app: falcosidekick
template:
metadata:
labels:
app: falcosidekick
spec:
containers:
- name: falcosidekick
image: falcosecurity/falcosidekick:2.28
env:
# Webhook 告警
- name: WEBHOOK_ADDRESS
value: "https://hooks.slack.com/services/xxx"
# Slack 告警
- name: SLACK_WEBHOOKURL
valueFrom:
secretKeyRef:
name: falco-secrets
key: slack-webhook
- name: SLACK_MINIMUMPRIORITY
value: "warning"
# 邮件告警
- name: SMTP_HOST
value: "smtp.example.com"
- name: SMTP_PORT
value: "587"
- name: SMTP_USER
valueFrom:
secretKeyRef:
name: falco-secrets
key: smtp-user
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: falco-secrets
key: smtp-password
- name: SMTP_SENDER
value: "falco@example.com"
- name: SMTP_RECEIVER
value: "security-team@example.com"
ports:
- containerPort: 2801
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi网络策略
NetworkPolicy 配置
# security/network-policies.yaml
# 默认拒绝所有入站和出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 允许 DNS 解析(所有 Pod 都需要)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# 允许前端访问后端 API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: default
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
---
# 允许 API 访问数据库
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-database
namespace: default
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
---
# 允许 Ingress Controller 访问前端
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-frontend
namespace: default
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 80
---
# 限制出站流量到外部(只允许 HTTPS)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-https-egress
namespace: default
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- ports:
- protocol: TCP
port: 443Pod 安全标准
Pod Security Standards 配置
# security/pod-security-standards.yaml
# 在命名空间级别 enforcing Pod 安全标准
# 三种级别:privileged(无限制)、baseline(最小限制)、restricted(严格限制)
# 严格限制的命名空间标签
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
---
# 安全的 Pod 配置示例
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
# 自动挂载 Service Account Token(不需要时关闭)
automountServiceAccountToken: false
securityContext:
# Pod 级别安全上下文
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myregistry.com/myapp:v1.0.0
securityContext:
# 容器级别安全上下文
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir:
sizeLimit: 100Mi密钥管理
使用 Sealed Secrets
# 安装 Sealed Secrets Controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n security --create-namespace
# 安装 kubeseal CLI
curl -sSfL https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/kubeseal-linux-amd64 -o /usr/local/bin/kubeseal
chmod +x /usr/local/bin/kubeseal
# 从 Secret 创建 SealedSecret
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password='S3cur3P@ss!' \
--dry-run=client -o yaml | kubeseal \
-o yaml > sealed-secret.yaml
# sealed-secret.yaml 可以安全提交到 Git
# 部署后 Controller 会自动解密为普通 Secret
kubectl apply -f sealed-secret.yaml使用 External Secrets Operator
# external-secrets.yaml
# 从 AWS Secrets Manager 同步密钥
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database/credentials
property: username
- secretKey: password
remoteRef:
key: prod/database/credentials
property: passwordCI/CD 安全门禁
GitHub Actions 安全扫描
# .github/workflows/security-scan.yml
name: Container Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
IMAGE_NAME: myregistry.com/myorg/myapp
jobs:
# 阶段一:代码安全扫描
code-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner in fs mode
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
exit-code: '1'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Secret scanning
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
# 阶段二:构建和扫描镜像
build-and-scan:
needs: code-scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: myregistry.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build Image
uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: ${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Run Trivy Image Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.IMAGE_NAME }}:${{ github.sha }}'
severity: 'CRITICAL,HIGH'
exit-code: '1'
format: 'json'
output: 'image-scan-results.json'
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: '${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'cyclonedx-json'
output-file: 'sbom.json'
- name: Sign Image with Cosign
uses: sigstore/cosign-installer@v3
- run: |
cosign sign --yes \
-a commit=${{ github.sha }} \
-a builder=github-actions \
-a build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Push Image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.IMAGE_NAME }}:latest
# 阶段三:合规检查
compliance-check:
needs: build-and-scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run kubeaudit
uses: controlplaneio/kubeaudit-action@v0.1.0
with:
manifests: ./k8s/
- name: Check Container Image is not root
run: |
# 检查 Dockerfile 中是否使用 USER 指令
if ! grep -q "^USER" Dockerfile; then
echo "ERROR: Dockerfile 缺少 USER 指令"
exit 1
fi
- name: Verify no latest tag in manifests
run: |
if grep -r ":latest" ./k8s/; then
echo "ERROR: K8s manifests 中使用了 :latest 标签"
exit 1
fi合规扫描 (OPA/Kyverno)
# security/kyverno-policies.yaml
# 禁止特权容器
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged
spec:
validationFailureAction: Enforce
rules:
- name: validate-privileged
match:
any:
- resources:
kinds:
- Pod
validate:
message: "不允许使用特权容器"
pattern:
spec:
containers:
- securityContext:
privileged: false
---
# 要求资源限制
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resources
spec:
validationFailureAction: Enforce
rules:
- name: validate-resources
match:
any:
- resources:
kinds:
- Pod
validate:
message: "必须设置 CPU 和 Memory 的 requests 和 limits"
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"
---
# 禁止 latest 标签
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: validate-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "不允许使用 :latest 标签"
pattern:
spec:
containers:
- image: "!*:latest"
---
# 要求只读根文件系统
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-readonly-rootfs
spec:
validationFailureAction: Audit
rules:
- name: validate-readonly-rootfs
match:
any:
- resources:
kinds:
- Pod
validate:
message: "推荐使用只读根文件系统"
pattern:
spec:
containers:
- securityContext:
readOnlyRootFilesystem: true应急响应
安全事件响应流程
#!/bin/bash
# security-incident-response.sh — 容器安全事件响应脚本
NAMESPACE="${1:-default}"
POD_NAME="${2}"
echo "=== 安全事件响应 ==="
echo "时间: $(date -u)"
echo "命名空间: ${NAMESPACE}"
echo "Pod: ${POD_NAME}"
echo ""
# 1. 隔离 Pod(添加 NetworkPolicy 阻止流量)
echo "--- 步骤 1: 隔离 Pod ---"
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: quarantine-${POD_NAME}
namespace: ${NAMESPACE}
spec:
podSelector:
matchLabels:
pod-template-hash: $(kubectl get pod ${POD_NAME} -n ${NAMESPACE} -o jsonpath='{.metadata.labels.pod-template-hash}')
policyTypes:
- Ingress
- Egress
EOF
# 2. 保存现场证据
echo "--- 步骤 2: 保存证据 ---"
EVIDENCE_DIR="/tmp/security-evidence-$(date +%Y%m%d-%H%M%S)"
mkdir -p ${EVIDENCE_DIR}
kubectl describe pod ${POD_NAME} -n ${NAMESPACE} > ${EVIDENCE_DIR}/pod-describe.txt
kubectl logs ${POD_NAME} -n ${NAMESPACE} --all-containers > ${EVIDENCE_DIR}/pod-logs.txt
kubectl get pod ${POD_NAME} -n ${NAMESPACE} -o yaml > ${EVIDENCE_DIR}/pod-yaml.yaml
# 3. 检查容器内进程
echo "--- 步骤 3: 检查异常进程 ---"
kubectl exec ${POD_NAME} -n ${NAMESPACE} -- ps aux > ${EVIDENCE_DIR}/processes.txt
kubectl exec ${POD_NAME} -n ${NAMESPACE} -- netstat -tlnp > ${EVIDENCE_DIR}/network.txt 2>/dev/null || true
# 4. 检查镜像信息
echo "--- 步骤 4: 镜像信息 ---"
IMAGE=$(kubectl get pod ${POD_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.containers[0].image}')
echo "镜像: ${IMAGE}" > ${EVIDENCE_DIR}/image-info.txt
trivy image --severity HIGH,CRITICAL --format json ${IMAGE} > ${EVIDENCE_DIR}/trivy-scan.json 2>/dev/null || true
# 5. 导出事件日志
echo "--- 步骤 5: 导出事件 ---"
kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' > ${EVIDENCE_DIR}/events.txt
echo ""
echo "证据已保存到: ${EVIDENCE_DIR}"
echo ""
echo "后续操作:"
echo "1. 检查 ${EVIDENCE_DIR} 中的证据文件"
echo "2. 分析异常进程和网络连接"
echo "3. 如果确认安全事件,删除被入侵的 Pod"
echo "4. 重建 Pod 使用干净的镜像"
echo "5. 通知安全团队并记录事件"优点
- 全生命周期覆盖:从构建到运行的完整安全链条
- 自动化扫描:CI/CD 中自动检测漏洞和配置问题
- 实时检测:Falco 运行时监控异常行为
- 镜像签名:防止供应链攻击
- 零信任网络:NetworkPolicy 限制 Pod 通信
- 合规保障:Kyverno 策略自动检查合规性
缺点
- 安全工具链复杂:需要维护 Trivy、Falco、Kyverno 等多个工具
- 运维成本高:安全工具本身也需要维护和升级
- 误报处理:漏洞扫描和异常检测可能产生大量误报
- 性能开销:Falco 和安全扫描会消耗计算资源
- 学习曲线:容器安全涉及多个领域的专业知识
性能注意事项
- Trivy 扫描优化:使用 Server 模式共享漏洞数据库,减少下载时间
- Falco eBPF 驱动:相比内核模块,eBPF 驱动性能开销更低
- 扫描时间控制:CI 中设置扫描超时,避免流水线阻塞
- 漏扫缓存:Trivy 缓存漏洞数据库,定期更新即可
- Falco 规则优化:避免过于宽泛的规则,减少误报和性能消耗
- Kyverno 策略精简:只启用必要的策略,减少准入控制器延迟
总结
容器安全是一个系统工程,需要从镜像构建(安全 Dockerfile、漏洞扫描、镜像签名)、部署准入(Kyverno 策略、签名验证)、运行时保护(Falco 检测、NetworkPolicy 隔离、Pod 安全标准)三个层面协同工作。通过 CI/CD 安全门禁实现自动化检查,将安全左移,在开发阶段就发现和修复安全问题。
关键知识点
- Trivy 支持镜像漏洞扫描、密钥检测、配置审计和 SBOM 生成
- Cosign/Sigstore 实现镜像签名和验证,防止供应链攻击
- Falco 基于 syscalls/eBPF 检测容器运行时异常行为
- Pod 安全标准分三个级别:privileged、baseline、restricted
- NetworkPolicy 实现零信任网络,默认拒绝所有流量
- SBOM(软件物料清单)记录镜像中所有组件和依赖
常见误区
- 基础镜像用 latest — 应使用固定版本,避免不可预期的变更
- 容器以 root 运行 — 即使在容器内也应使用非 root 用户
- 扫描一次就够 — 新漏洞不断被发现,需要定期重新扫描
- 忽略运行时安全 — 构建时安全不能替代运行时安全
- NetworkPolicy 不需要 — 默认 Kubernetes 网络是完全开放的
- 密钥放在环境变量就安全 — 环境变量对所有进程可见,应使用 Secret 挂载
进阶路线
- 入门:Dockerfile 安全编写、Trivy 基础扫描
- 进阶:CI/CD 安全门禁、镜像签名、SBOM 管理
- 高级:Falco 运行时检测、NetworkPolicy 设计、Kyverno 策略
- 专家:eBPF 安全编程、供应链安全框架、合规自动化
- 架构:零信任容器平台、安全运营中心 (SOC) 集成
适用场景
- 所有使用容器的生产环境
- 对安全合规有要求的行业(金融、医疗、政务)
- 微服务架构中的服务隔离
- 多租户 Kubernetes 集群
- CI/CD 流水线安全加固
落地建议
- 先扫描后加固:先用 Trivy 扫描现有镜像,了解安全现状
- CI/CD 集成:在流水线中加入安全扫描和签名步骤
- 制定安全基线:参考 CIS Benchmarks 制定 Docker 和 K8s 安全基线
- 分层防御:构建时扫描 + 准入控制 + 运行时检测
- 定期演练:模拟安全事件,验证响应流程
- 建立漏洞管理流程:漏洞评估、修复优先级、修复时间线
排错清单
复盘问题
- 为什么容器内使用非 root 用户比以 root 运行更安全?
- SBOM 在供应链安全中的角色是什么?如何利用 SBOM 做漏洞管理?
- cosign 的 Keyless 模式是如何保证签名安全的?
- Falco 的 eBPF 驱动和内核模块驱动各有什么优缺点?
- 如何平衡安全扫描的严格程度和 CI/CD 流水线的速度?
- 在多租户 Kubernetes 集群中,如何实现租户间的安全隔离?
