从镜像中移除构建密钥

DockerBeginner
立即练习

介绍

当 Dockerfile 复制了过多的构建上下文时,构建密钥可能会泄露到镜像中。一个常见的错误是使用 COPY . /app/:虽然这很方便,但它可能会将令牌(token)、凭据、本地笔记和临时文件带入最终镜像。一旦密钥进入镜像层,运行时控制手段就无法可靠地消除这种供应链错误。

在本实验中,你将构建一个故意存在泄露的本地镜像,证明其中存在构建阶段的令牌,然后通过缩小 Dockerfile 的复制边界并添加 .dockerignore 文件来修复构建过程。最后,你将部署这个干净的镜像,并验证运行时容器不再包含该构建密钥。

检查不安全的构建上下文

在此步骤中,你将检查构建上下文和不安全的 Dockerfile。构建上下文是发送给镜像构建器的目录。如果 Dockerfile 复制了整个上下文,那么该目录中的每个文件都可能成为最终镜像的一部分。

在实验工作区中开始:

cd /home/labex/project/build-secret-image

阅读说明:

sed -n '1,120p' BUILD_SECRET_NOTES.md

列出当前存在于构建上下文中的文件:

find . -maxdepth 1 -type f -printf '%f\n' | sort | tee evidence/initial-context-files.txt

查看不安全的 Dockerfile:

sed -n '1,120p' Dockerfile.insecure | tee evidence/insecure-dockerfile-review.txt

风险点在于 COPY . /app/ 这一行。它不仅复制了 app.sh,还复制了 build-token.txt。在下一步中,你将构建此镜像并从镜像运行时内部证明泄露的存在。

构建泄露镜像并证明密钥存在

在此步骤中,你将在 labex-v134 minikube 配置中构建不安全的镜像。在配置中构建镜像可以使 Kubernetes 节点运行时能够识别该镜像。

将不安全的 Dockerfile 复制为当前使用的 Dockerfile 并构建泄露镜像:

cp Dockerfile.insecure Dockerfile
minikube -p labex-v134 image build -t labex/leaky-build:1.0 .

在 minikube Docker 运行时中运行该镜像并检查令牌文件:

minikube -p labex-v134 ssh -- 'docker run --rm --entrypoint /bin/sh labex/leaky-build:1.0 -c "ls -l /app && cat /app/build-token.txt"' | tee evidence/leaky-image-contents.txt

你应该会看到 build-token=labex-temporary-secret。这就是泄露的内容。镜像虽然可以运行,但它包含了一个本应保留在最终运行时镜像之外的文件。

通过缩小复制边界构建干净的镜像

在此步骤中,你将修复镜像构建过程。有两个关键点:.dockerignore 可以防止令牌被发送到构建器,而 Dockerfile 只复制 app.sh 而不是整个目录。

创建 .dockerignore:

cat > .dockerignore <<'EOF'
build-token.txt
evidence/
*.txt
EOF

创建干净的 Dockerfile:

cat > Dockerfile <<'EOF'
FROM busybox:1.36

WORKDIR /app
COPY app.sh /app/app.sh
RUN chmod 0555 /app/app.sh

USER 65534:65534
ENTRYPOINT ["/app/app.sh"]
EOF

构建干净的镜像:

minikube -p labex-v134 image build -t labex/clean-build:1.0 .

从 minikube Docker 运行时内部检查干净的镜像:

minikube -p labex-v134 ssh -- 'docker run --rm --entrypoint /bin/sh labex/clean-build:1.0 -c "ls -l /app; if [ -f /app/build-token.txt ]; then echo BUILD_SECRET_PRESENT; else echo NO_BUILD_SECRET; fi; id -u"' | tee evidence/clean-image-contents.txt

输出应显示 NO_BUILD_SECRET 和 UID 65534。

部署干净的镜像

在此步骤中,你将部署干净的镜像,并验证运行中的容器报告不存在构建密钥。这使得最终验证更贴近镜像实际运行的 Kubernetes 环境。

创建并应用 Pod 清单:

cat > clean-build-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: clean-build-app
  namespace: cks-prep-build-secret
  labels:
    app: clean-build-app
spec:
  restartPolicy: Never
  containers:
    - name: app
      image: labex/clean-build:1.0
      imagePullPolicy: Never
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        runAsGroup: 65534
        allowPrivilegeEscalation: false
EOF
kubectl apply -f clean-build-pod.yaml

等待 Pod 就绪并读取其日志:

kubectl -n cks-prep-build-secret wait --for=condition=Ready pod/clean-build-app --timeout=120s
kubectl -n cks-prep-build-secret logs clean-build-app | tee evidence/clean-pod-logs.txt

日志应包含:

clean build image ready
NO_BUILD_SECRET

总结

你检查了一个不安全的镜像构建上下文,构建了一个存在泄露的镜像,证明了构建令牌的存在,然后通过 .dockerignore 和精确的 Dockerfile COPY 指令修复了构建过程。你构建并部署了一个以非 root 用户身份运行且报告 NO_BUILD_SECRET 的干净镜像。通过将镜像构建规范与 Kubernetes 运行时验证相结合,你完成了供应链安全阶段的实验。