Docker BuildKit Secrets: Đừng để lộ API Key trong Image Layers nữa!

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Sai lầm phổ biến: Biến Docker Image thành “miếng mồi” cho hacker

Bạn đã bao giờ thử chạy lệnh docker history trên image của mình và giật mình thấy cái NPM_TOKEN hiện ra lù lù chưa? Mình từng mắc lỗi này khi mới tập Dockerize ứng dụng Node.js. Lúc đó, mình dùng ARG để truyền token tải package nội bộ và đinh ninh rằng: “Build xong thì token cũng mất”.

Mọi chuyện không đơn giản như vậy. Bất kỳ ai có quyền docker pull image đều có thể soi ra toàn bộ giá trị ARG. Thậm chí, nếu dùng ENV, token đó còn nằm chễm chệ trong container khi chạy. Sau một lần bị bộ phận Security cảnh báo vì lộ AWS Access Key trên Docker Hub, mình mới hiểu tầm quan trọng của việc quản lý secret khi build.

Tại sao các cách truyền Secret cũ lại nguy hiểm?

Hãy nhìn lại 3 phương pháp “truyền thống” mà giới DevOps hiện nay khuyên nên tránh xa:

1. Sử dụng Docker ARG

Đây là sai lầm kinh điển nhất. Các giá trị qua --build-arg sẽ lưu vĩnh viễn trong metadata của image layer. Dù bạn có unset biến đó ở layer sau, nó vẫn nằm lại ở layer trước đó trong lịch sử build.

2. Copy file secret rồi xóa đi (Anti-pattern)

COPY .npmrc .npmrc
RUN npm install
RUN rm .npmrc

Cách này cực kỳ tai hại. Docker hoạt động theo cơ chế layer-stacking. File .npmrc nằm ở layer COPY. Lệnh RUN rm chỉ tạo thêm một layer mới đánh dấu là file đã bị xóa. Dữ liệu thực tế vẫn nằm nguyên ở layer cũ, chỉ cần một vài kỹ thuật giải nén layer là lấy được ngay.

3. Multi-stage build

Phương pháp này khá hơn vì bạn dùng stage đầu để build và stage sau để copy artifact. Tuy nhiên, nếu bạn vô tình đẩy cả build-stage lên registry hoặc lưu cache không cẩn thận, rủi ro rò rỉ vẫn hiện hữu.

BuildKit Secrets: Tiêu chuẩn bảo mật mới cho Production

Từ bản Docker 18.09, BuildKit đã thay đổi cuộc chơi với tính năng --secret. Cơ chế của nó rất thông minh: Secret được mount dưới dạng một file tạm thời (tmpfs) trong quá trình build. Nó không bao giờ được ghi vào bất kỳ layer nào.

Khi xử lý các file cấu hình phức tạp, mình thường cần kiểm tra lại định dạng JSON sau khi inject secret. Những lúc đó, mình hay dùng toolcraft.app/vi/tools/developer/json-formatter để format lại cho dễ nhìn. Cách này nhanh hơn việc cài extension hay gõ lệnh jq loằng ngoằng trong terminal.

Hướng dẫn triển khai Docker BuildKit Secrets

Bước 1: Kích hoạt BuildKit

Docker Desktop hiện nay thường bật sẵn BuildKit. Nếu bạn dùng Linux bản cũ, hãy chạy lệnh này trước khi build:

export DOCKER_BUILDKIT=1

Bước 2: Tạo file Secret mẫu

Giả sử chúng ta cần bảo mật một API Key trong file my_token.txt:

echo "super-secret-api-key-2024" > my_token.txt

Bước 3: Cấu hình Dockerfile với –mount=type=secret

Đây là điểm mấu chốt. Bạn phải khai báo quyền truy cập secret ngay tại lệnh RUN cần sử dụng nó:

# syntax=docker/dockerfile:1
FROM alpine

# Mount secret vào đường dẫn mặc định /run/secrets/my_token
RUN --mount=type=secret,id=my_token \
    TOKEN=$(cat /run/secrets/my_token) && \
    echo "Đang dùng token để fetch dữ liệu từ API..." && \
    curl -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/setup

# Kết thúc lệnh RUN, file secret sẽ tự động bị unmount

Quan trọng: Đừng quên dòng # syntax=docker/dockerfile:1 ở đầu file. Thiếu nó, Docker sẽ không hiểu cú pháp mount nâng cao này.

Bước 4: Thực hiện Build

Sử dụng flag --secret để map file từ máy host vào id trong Dockerfile:

docker build --no-cache --secret id=my_token,src=my_token.txt -t secure-app:v1 .

Hai tình huống thực tế hay dùng nhất

1. Cài đặt Private NPM/Python Packages

Thay vì copy file cấu hình, hãy mount trực tiếp vào thư mục home của root:

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm install

2. Clone Git Private Repo qua SSH

BuildKit hỗ trợ SSH cực tốt. Bạn không cần truyền key thủ công vào image:

# Trong Dockerfile
RUN --mount=type=ssh git clone [email protected]:org/private-core.git

Khi build, chỉ cần thêm flag: docker build --ssh default .

Đánh giá nhanh: Ưu và Nhược điểm

Ưu điểm:

  • An toàn tuyệt đối: Secret chỉ tồn tại trong bộ nhớ tạm khi build.
  • Sạch sẽ: Không cần lệnh rm -rf để dọn dẹp layer.

Nhược điểm:

  • Cú pháp: Hơi lạ lẫm với những người đã quen kiểu ARG/ENV truyền thống.
  • Hệ thống CI: Một số bản Jenkins hoặc GitLab Runner cũ cần cấu hình thêm để hỗ trợ BuildKit.

Bài học thực chiến sau 1 năm áp dụng

  1. Kiểm tra .dockerignore: Luôn đưa file secret vào .dockerignore. Nếu bạn lỡ tay COPY . ., mọi nỗ lực bảo mật bằng BuildKit sẽ vô nghĩa.
  2. Phân loại dữ liệu: Chỉ dùng --secret cho thông tin nhạy cảm. Với các cấu hình như APP_VERSION, dùng ARG vẫn tiện lợi và nhanh hơn.
  3. Verify bằng tool: Sau khi build, hãy dùng tool dive để soi từng layer. Nếu không thấy file secret nào xuất hiện, nghĩa là bạn đã thành công.

Bảo mật Docker không chỉ là việc chặn port hay quét lỗ hổng. Nó bắt đầu ngay từ cách bạn xây dựng image. Hy vọng bài viết này giúp quy trình CI/CD của team bạn an toàn hơn!

Share: