适用场景 :生产容器环境安全加固、CI/CD 漏洞自动化检测、镜像供应链安全、运行时攻击防御。 前置条件 :
组件 | RHEL 8/9 | Ubuntu 20.04/22.04 | 版本要求 | 最小规格 |
---|---|---|---|---|
OS 内核 | 4.18+ | 5.4+ | 支持 seccomp-bpf | - |
Docker | 20.10.23+ | 24.0+ | 含 BuildKit | 2C/4G/50G |
containerd | 1.6.20+ | 1.7+ | 需 runc 1.1+ | 2C/4G |
Kubernetes | 1.25-1.30 | 1.25-1.30 | 含 PSA 特性 | Master: 4C/8G |
Trivy | 0.48+ | 0.48+ | DB 自动更新 | 1C/2G/10G(DB) |
Grype | 0.74+ | 0.74+ | Syft 0.100+ | 1C/2G |
Cosign | 2.0+ | 2.0+ | 需 Rekor 服务 | 1C/1G |
Falco | 0.36+(可选) | 0.36+ | 内核模块/eBPforming | 2C/4G |
Harbor | 2.8+ | 2.8+ | 集成 Trivy | 4C/8G/100G |
RHEL/CentOS:
# 安装 Trivysudo rpm -ivh https://github.com/aquasecurity/trivy/releases/download/v0.48.3/trivy_0.48.3_Linux-64bit.rpm# 验证安装trivy --version# 更新漏洞数据库trivy image --download-db-only# 检查数据库路径ls -lh ~/.cache/trivy/db/
Ubuntu/Debian:
# 安装 Trivywget https://github.com/aquasecurity/trivy/releases/download/v0.48.3/trivy_0.48.3_Linux-64bit.debsudo dpkg -i trivy_0.48.3_Linux-64bit.deb# 离线环境配置(可选)trivy image --download-db-only --cache-dir /opt/trivy-dbexport TRIVY_CACHE_DIR=/opt/trivy-db
关键参数:
--severity CRITICAL,HIGH
:仅扫描高危漏洞--exit-code 1
:发现漏洞时返回非零退出码(用于 CI 阻断)--ignore-unfixed
:忽略暂无修复版本的 CVE验证扫描功能:
# 扫描官方 Nginx 镜像trivy image nginx:latest# 仅输出 CRITICAL 和 HIGH 级别trivy image --severity CRITICAL,HIGH nginx:latest# JSON 格式输出trivy image -f json -o nginx-scan.json nginx:latest# 检查输出jq '.Results[0].Vulnerabilities | length' nginx-scan.json
预期输出示例:
nginx:latest (debian 12.2)Total: 87 (CRITICAL: 2, HIGH: 15, MEDIUM: 70)┌────────────┬───────────────┬──────────┬──────────────────┬───────────────┐│ Library │ Vulnerability │ Severity │ Installed Version│ Fixed Version │├────────────┼───────────────┼──────────┼──────────────────┼───────────────┤│ openssl │ CVE-2023-5678 │ CRITICAL │ 3.0.9-1 │ 3.0.11-1 │└────────────┴───────────────┴──────────┴──────────────────┴───────────────┘
多阶段构建 + 最小镜像 + 非 root 运行:
# 构建阶段:使用完整镜像FROM golang:1.21-alpine AS builderWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o myapp# 运行阶段:使用 distrolessFROM gcr.io/distroless/static-debian12:nonroot# 使用非特权用户(UID 65532)USER nonroot:nonrootWORKDIR /app# 只读根文件系统(需要在 /tmp 挂载可写卷)COPY --from=builder --chown=nonroot:nonroot /app/myapp .EXPOSE8080ENTRYPOINT ["/app/myapp"]
关键安全实践:
扫描验证:
# 构建镜像docker build -t myapp:secure .# 扫描对比trivy image --severity HIGH,CRITICAL myapp:secure# 检查用户配置docker inspect myapp:secure | jq '.[0].Config.User'# 预期输出:"nonroot"
Alpine 基础镜像替代方案:
FROM alpine:3.19RUN apk add --no-cache ca-certificates && \ addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserCOPY --chown=appuser:appgroup myapp /app/ENTRYPOINT ["/app/myapp"]
.gitlab-ci.yml
配置:
stages:-build-scan-sign-deployvariables:IMAGE_NAME:myappIMAGE_TAG:$CI_COMMIT_SHORT_SHAREGISTRY:registry.example.combuild:stage:buildimage:docker:24-dindscript:-dockerbuild-t$REGISTRY/$IMAGE_NAME:$IMAGE_TAG.-dockerpush$REGISTRY/$IMAGE_NAME:$IMAGE_TAGsecurity-scan:stage:scanimage:aquasec/trivy:latestscript:-trivyimage--exit-code1--severityCRITICAL$REGISTRY/$IMAGE_NAME:$IMAGE_TAG-trivyimage--severityHIGH,CRITICAL--formatjson--outputscan-report.json$REGISTRY/$IMAGE_NAME:$IMAGE_TAGartifacts:reports:container_scanning:scan-report.jsonexpire_in:30daysallow_failure:false# 发现 CRITICAL 漏洞时阻断流水线sign-image:stage:signimage:gcr.io/projectsigstore/cosign:v2.2script:-cosignsign--keycosign.key$REGISTRY/$IMAGE_NAME:$IMAGE_TAGonly:-main
关键配置:
--exit-code 1
:CRITICAL 漏洞阻断部署allow_failure: false
:强制修复后才能继续GitHub Actions 等效配置:
-name:ScanwithTrivyuses:aquasecurity/trivy-action@masterwith:image-ref:${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{github.sha}}severity:'CRITICAL,HIGH'exit-code:1
生成签名密钥:
# 生成 Cosign 密钥对cosign generate-key-pair# 输出:cosign.key(私钥)+ cosign.pub(公钥)# 私钥存储到 GitLab/GitHub Secrets# 签名镜像cosign sign --key cosign.key registry.example.com/myapp:v1.0# 验证签名cosign verify --key cosign.pub registry.example.com/myapp:v1.0
Kubernetes 准入控制(OPA Gatekeeper + Cosign):
# 安装 Gatekeeperkubectlapply-fhttps://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml# 创建签名验证策略apiVersion:templates.gatekeeper.sh/v1kind:ConstraintTemplatemetadata:name:cosignsignedimagesspec:crd:spec:names:kind:CosignSignedImagestargets:-target:admission.k8s.gatekeeper.shrego:| package cosignsignedimages violation[{"msg": msg}] { input.review.object.kind == "Pod" image := input.review.object.spec.containers[_].image not cosign_verify(image) msg := sprintf("Image %v is not signed", [image]) } cosign_verify(image) { # 调用外部 Cosign 验证服务 response := http.send({ "method": "GET", "url": sprintf("http://cosign-verifier.default.svc/verify?image=%v", [image]) }) response.status_code == 200 }
部署验证服务(简化示例):
# 使用 Policy Controller(Sigstore 官方)kubectl apply -f https://github.com/sigstore/policy-controller/releases/download/v0.8.0/release.yaml# 配置全局签名验证策略kubectl apply -f - <<EOFapiVersion: policy.sigstore.dev/v1beta1kind: ClusterImagePolicymetadata: name: require-signed-imagesspec: images: - glob: "registry.example.com/**" authorities: - key: data: | $(cat cosign.pub)EOF
验证阻断效果:
# 尝试部署未签名镜像kubectl run test --image=nginx:latest# 预期错误:# Error: admission webhook denied the request:# no matching signatures found for image nginx:latest
Pod 安全上下文模板:
apiVersion:v1kind:Podmetadata:name:secure-appspec:securityContext:runAsNonRoot:truerunAsUser:10000fsGroup:10000seccompProfile:type:RuntimeDefaultcontainers:-name:appimage:registry.example.com/myapp:v1.0securityContext:allowPrivilegeEscalation:falsereadOnlyRootFilesystem:truecapabilities:drop:-ALLadd:-NET_BIND_SERVICE# 仅允许绑定 1024 以下端口runAsNonRoot:truevolumeMounts:-name:tmpmountPath:/tmp-name:cachemountPath:/app/cachevolumes:-name:tmpemptyDir: {}-name:cacheemptyDir: {}
关键参数解释:
readOnlyRootFilesystem: true
:防止运行时篡改文件系统capabilities.drop: ALL
:移除所有 Linux CapabilitiesseccompProfile: RuntimeDefault
:启用 seccomp 系统调用过滤验证安全配置:
# 检查 Pod 运行用户kubectl exec secure-app -- id# 预期输出:uid=10000 gid=10000# 尝试写入根目录(应失败)kubectl exec secure-app -- touch /test.txt# 预期错误:touch: /test.txt: Read-only file system# 检查 Capabilitieskubectl exec secure-app -- capsh --print# 预期输出:Current: cap_net_bind_service=ep
默认拒绝策略:
apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:default-deny-allnamespace:productionspec:podSelector: {}policyTypes:-Ingress-Egress
精细化白名单策略:
apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-app-egressnamespace:productionspec:podSelector:matchLabels:app:myapppolicyTypes:-Egressegress:# 允许访问内部数据库-to:-podSelector:matchLabels:app:mysqlports:-protocol:TCPport:3306# 允许 DNS 查询-to:-namespaceSelector:matchLabels:name:kube-system-podSelector:matchLabels:k8s-app:kube-dnsports:-protocol:UDPport:53# 允许访问外部 API(CIDR 限定)-to:-ipBlock:cidr:203.0.113.0/24ports:-protocol:TCPport:443
验证网络隔离:
# 测试内部连接(应成功)kubectl exec -n production myapp-pod -- nc -zv mysql-service 3306# 测试未授权连接(应超时)kubectl exec -n production myapp-pod -- nc -zv redis-service 6379# 预期输出:Connection timed out# 检查策略生效kubectl get netpol -n productionkubectl describe netpol allow-app-egress -n production
启用 PSA(K8s 1.25+ 默认启用):
# 查看当前策略kubectl get ns production -o yaml | grep pod-security# 配置 Namespace 级别策略kubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/audit=restricted \ pod-security.kubernetes.io/warn=restricted
Restricted 策略限制内容:
privileged: true
)测试策略阻断:
# 尝试部署特权容器cat <<EOF | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: privileged-test namespace: productionspec: containers: - name: test image: nginx securityContext: privileged: trueEOF# 预期错误:# Error: pods "privileged-test" is forbidden:# violates PodSecurity "restricted:latest": privileged
豁免特定工作负载(谨慎使用):
apiVersion:v1kind:Namespacemetadata:name:monitoringlabels:pod-security.kubernetes.io/enforce:baselinepod-security.kubernetes.io/audit:restrictedpod-security.kubernetes.io/warn:restricted# 豁免特定用户/ServiceAccountpod-security.kubernetes.io/exempt:prometheus-sa
安装 Falco(Helm):
# 添加 Helm 仓库helm repo add falcosecurity https://falcosecurity.github.io/chartshelm repo update# 安装 Falco(eBPF 模式)helm install falco falcosecurity/falco \ --namespace falco --create-namespace \ --set driver.kind=ebpf \ --set falcosidekick.enabled=true \ --set falcosidekick.webui.enabled=true# 验证 DaemonSet 运行kubectl get pods -n falco
自定义安全规则(/etc/falco/rules.d/custom.yaml
):
-rule:UnauthorizedProcessinContainerdesc:Detectshellorpackagemanagerexecutioninproductioncontainerscondition:> spawned_process and container and (proc.name in (sh, bash, ash, z