前阵子安全合规部门突然甩过来一份安全审计报告,指着我们好几个线上核心业务的 Docker 镜像说里面堆满了高危 CVE 漏洞。我点进去一看瞬间心肺停止:很多开发为了图省事,基础镜像直接用 node:latest 或者 ubuntu:latest。里面的软件包动辄两三年没更新,甚至还有人用 root 权限去跑 Node 应用。更有甚者,直接把数据库的配置密码用 ENV 形式写死在 Dockerfile 里,这要是镜像泄漏出去,简直就是把内网大门敞开给人进。
过去我们对 Docker 镜像的管控基本全靠人工 Review,效率低不说,漏网之鱼更是一抓一大把。把安全责任全押在开发个人的“心智负担”上是最不靠谱的。我们必须干脆利落地把镜像审计和元数据安全扫描全部塞进 CI/CD 流程里,不合规的构建直接在 Pipeline 里拉闸打回。
要达成这个目标,我们需要两件武器:一个是针对 Dockerfile 语法与规范进行静态分析的 Hadolint,另一个是专门扫描镜像层 CVE 漏洞的 Trivy。
我们在 GitLab CI/CD 中实现了全自动的双重审计卡点。首先,在代码提交阶段,Hadolint 会作为第一道门槛,拒绝任何写得不规范的 Dockerfile。比如基础镜像没有指定具体版本(用了 latest)、没有清理 apt-get 缓存或者漏写了 USER 指令。第二道关卡则是等镜像构建完成后,由 Trivy 对镜像进行全量 CVE 漏洞扫描,一旦发现 CRITICAL 级别的系统漏洞,直接阻断部署。
这是我们调优出来的完整 .gitlab-ci.yml 流水线配置:
stages:
- lint
- build
- scan
variables:
IMAGE_NAME: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
lint-dockerfile:
stage: lint
image: hadolint/hadolint:latest-debian
script:
- mkdir -p reports
- hadolint --format codeclimate Dockerfile > reports/hadolint-json.json
- hadolint Dockerfile
artifacts:
name: "hadolint-report"
when: always
paths:
- reports/
reports:
codequality: reports/hadolint-json.json
build-image:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
- docker build --pull -t "$IMAGE_NAME" .
- docker push "$IMAGE_NAME"
scan-image:
stage: scan
image:
name: aquasec/trivy:latest
entrypoint: [""]
variables:
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
script:
- trivy image --clear-cache
- trivy image --download-db-only
# 第一次扫描:生成报告供 GitLab UI 展示,但不让 Pipeline 崩溃
- trivy image --exit-code 0 --severity HIGH,CRITICAL --format template --template "@/contrib/gitlab.tpl" --output reports/gl-container-scanning-report.json "$IMAGE_NAME"
# 第二次扫描:如果发现 Critical(致命)级漏洞,直接返回 exit code 1 挂掉流水线
- trivy image --exit-code 1 --severity CRITICAL "$IMAGE_NAME"
cache:
key: trivy-cache
paths:
- .trivycache/
artifacts:
reports:
container_scanning: reports/gl-container-scanning-report.json
这里面有几个踩坑后总结出来的细节需要特别注意:
第一,Hadolint 我们启用了 codeclimate 格式输出,并挂载到了 GitLab 的 reports: codequality 里。这样一旦有违规写法,开发在 Merge Request 页面能直接看到高亮的具体报错行,而不需要去翻看冗长的 CI 日志。
第二,Trivy 扫描时,我们做了两次。第一次是输出 JSON 报告并挂载到 GitLab 的 container_scanning 安全仪表盘上;第二次才是核心防御,设置 --exit-code 1。只要扫出 CRITICAL 漏洞,流水线当场中断,彻底拦截了带病上线的镜像。
第三,缓存策略。Trivy 每次运行都要拉取庞大的漏洞库元数据,如果不做 TRIVY_CACHE_DIR 缓存,每次执行都要耗费数分钟。把缓存文件夹加入到 CI 缓存中后,速度能提升 5 倍以上。
自从这套卡点上线以后,我们后台构建 Dockerfile 的质量直线上升。开发在本地开发阶段,也开始自发安装 Hadolint 插件在 IDE 里做实时检查,免得代码提交后又被 CI 打回来重新折腾。
你们团队目前是如何保证容器镜像安全的?是完全依赖云厂商提供的后端扫描,还是也把安全卡点提前放到了 CI/CD 的最前端?