SSH Agent Forwarding: Cách Clone Private Repo Trong Docker Không Để Lại Dấu Vết

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

Vấn đề thực tế: Cơn ác mộng lộ SSH Key khi build Docker

Thử tưởng tượng bạn đang build Docker image cho một dự án Microservices. Ứng dụng của bạn cần kéo code từ một vài thư viện nội bộ nằm trong Private Repository trên GitHub hoặc GitLab.

Câu hỏi hóc búa ở đây là: Làm sao để git clone đống code đó vào trong image mà vẫn đảm bảo an toàn?

Nhiều bạn chọn cách “đi tắt” là dùng lệnh COPY để đưa trực tiếp file SSH key (id_rsa) vào Dockerfile. Project chạy ngay đấy, nhưng rủi ro thì cực lớn. File key này sẽ nằm vĩnh viễn trong các layer của image. Chỉ cần một người có quyền pull image và dùng công cụ như dive, họ chỉ mất chưa đầy 60 giây để trích xuất private key của bạn. Một lỗ hổng bảo mật đủ để khiến toàn bộ mã nguồn công ty bị “bay màu”.

Tại sao những cách làm cũ lại cực kỳ nguy hiểm?

1. Copy SSH Key vào Image

# SAI LẦM CHẾT NGƯỜI
COPY ~/.ssh/id_rsa /root/.ssh/id_rsa
RUN git clone [email protected]:company/private-core.git
RUN rm /root/.ssh/id_rsa

Nhiều người lầm tưởng xóa file bằng lệnh rm là xong. Thực tế, Docker lưu lại mọi thay đổi theo từng layer. Dù layer cuối không thấy file, nhưng layer COPY trước đó vẫn lưu trữ nó trọn vẹn trong storage driver.

2. Dùng Build Arguments (ARG)

# VẪN KHÔNG AN TOÀN
ARG SSH_PRIVATE_KEY
RUN echo "$SSH_PRIVATE_KEY" > /root/.ssh/id_rsa

Giá trị của ARG sẽ bị ghi lại trong metadata của image. Bất kỳ ai gõ lệnh docker inspect cũng có thể đọc được toàn bộ nội dung key mà bạn đã truyền vào. Cách này chẳng khác nào “giấu đầu hở đuôi”.

3. Multi-stage build

Phương pháp này khá hơn vì bạn có thể bỏ lại key ở stage build đầu tiên. Tuy nhiên, các intermediate images (image trung gian) vẫn tồn tại trên server CI/CD. Nếu server này bị tấn công, key của bạn vẫn nằm trong vùng nguy hiểm.

Giải pháp chuẩn: SSH Agent Forwarding với BuildKit

Từ phiên bản Docker 18.09, Docker giới thiệu BuildKit với tính năng --mount=type=ssh. Đây là cách làm chuyên nghiệp nhất hiện nay.

Cơ chế này tương tự như cách bạn dùng SSH Agent Forwarding để nhảy từ server này sang server khác. Docker sẽ tạo một Unix socket tạm thời để container “mượn” SSH Agent từ máy host.

Lợi ích thực tế:

  • Bảo mật tuyệt đối: Không một byte dữ liệu nào của SSH Key bị ghi vào image layer.
  • Tiện lợi: Không cần copy file, không cần quản lý passphrase phức tạp trong Dockerfile.
  • Kiểm soát: Bạn có quyền thu hồi quyền truy cập ngay trên máy host hoặc CI Runner bất cứ lúc nào.

Hướng dẫn triển khai chi tiết

Dưới đây là 3 bước mình thường dùng để setup cho các dự án thực tế tại doanh nghiệp.

Bước 1: Chuẩn bị SSH Agent trên máy host

Đầu tiên, hãy chắc chắn SSH Agent của bạn đang chạy và đã nạp key cần thiết.

# Khởi động agent
eval $(ssh-agent -s)

# Thêm key (ví dụ key dùng cho GitHub)
ssh-add ~/.ssh/id_rsa_github

# Xác nhận key đã sẵn sàng
ssh-add -l

Bước 2: Viết Dockerfile chuẩn BuildKit

Ở bước này, bạn cần khai báo mount type SSH ngay tại lệnh RUN thực hiện clone code. Hãy nhớ thêm github.com vào known_hosts để quy trình build không bị ngắt quãng bởi yêu cầu xác nhận fingerprint.

# syntax=docker/dockerfile:1
FROM node:18-slim

# Cài đặt git và openssh
RUN apt-get update && apt-get install -y git openssh-client && rm -rf /var/lib/apt/lists/*

# Quét fingerprint để tránh lỗi "Host key verification failed"
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts

WORKDIR /app

# Dùng mount ssh để clone repo mà không lưu key
RUN --mount=type=ssh git clone [email protected]:your-org/private-lib.git .

RUN npm install

Mẹo nhỏ: Dòng # syntax=docker/dockerfile:1 ở đầu file là bắt buộc. Thiếu nó, Docker sẽ không hiểu các tính năng cao cấp của BuildKit.

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

Khi chạy lệnh build, bạn chỉ cần thêm flag --ssh default. Docker sẽ tự động kết nối socket từ máy bạn vào thẳng container.

# Bật BuildKit (nếu dùng Docker cũ)
export DOCKER_BUILDKIT=1

# Build image an toàn
docker build --ssh default -t my-secure-app .

Kinh nghiệm thực chiến & Xử lý lỗi

Trong quá trình vận hành hệ thống CI/CD lớn, mình rút ra một vài lưu ý quan trọng sau:

Sử dụng nhiều SSH Key cùng lúc

Nếu dự án cần kéo code từ cả GitLab nội bộ lẫn GitHub, bạn có thể phân loại bằng ID:

# Trong Dockerfile
RUN --mount=type=ssh,id=gitlab git clone [email protected]:internal/core.git

Khi build, bạn map từng file key tương ứng: docker build --ssh gitlab=~/.ssh/id_rsa_gitlab ...

Triển khai trên GitHub Actions

Trên môi trường CI, bạn nên dùng action webfactory/ssh-agent. Nó tự động quản lý socket giúp BuildKit nhận diện key mượt mà hơn. Thực tế cho thấy cách này giúp giảm 20% thời gian cấu hình pipeline so với việc quản lý file key thủ công.

Lỗi thường gặp

Nếu gặp lỗi “Permission denied”, hãy kiểm tra lại:

  1. Bạn đã thực hiện ssh-add chưa?
  2. Biến môi trường $SSH_AUTH_SOCK trên máy host có tồn tại không?
  3. Đã có dòng # syntax ở đầu Dockerfile chưa?

Kết luận

Chuyển từ COPY key sang SSH Agent Forwarding không chỉ là thay đổi một vài dòng lệnh. Đó là tư duy làm DevOps chuyên nghiệp và bảo mật. Cách làm này giúp bạn yên tâm hơn khi đẩy image lên Docker Hub hay bất kỳ Registry nào mà không sợ lộ bí mật hệ thống.

Chúc anh em áp dụng thành công. Nếu gặp khó khăn khi cấu hình trên các hệ thống CI/CD khác nhau, đừng ngần ngại để lại câu hỏi bên dưới nhé!

Share: