Xử lý lỗi phổ biến khi ký image hoặc xác minh chữ ký trong CI/CD
Trong môi trường CI/CD, quy trình ký (signing) và xác minh (verification) thường thất bại do cấu hình môi trường hoặc lỗi mạng. Dưới đây là các bước khắc phục sự cố cụ thể cho Cosign.
Khắc phục lỗi "tuf: repository not initialized" hoặc lỗi TUF
Lỗi này xảy ra khi Cosign không thể tải hoặc xác thực TUF (The Update Framework) metadata từ Fulcio hoặc Rekor, thường do firewall chặn hoặc DNS không giải quyết được tên miền.
Tại sao: Cosign yêu cầu kết nối với TUF repository để xác minh tính toàn vẹn của dữ liệu trước khi thực hiện ký hoặc verify. Nếu không có kết nối, pipeline sẽ bị treo hoặc báo lỗi.
Kết quả mong đợi: Cosign có thể tải metadata TUF thành công và thực hiện ký image.
Thực hiện lệnh kiểm tra kết nối và cấu hình biến môi trường để chỉ định TUF repository rõ ràng:
export COSIGN_TUF_ROOT=https://tuf-repo.sigstore.dev
export COSIGN_REKOR_URL=https://rekor.sigstore.dev
export COSIGN_FULCIO_URL=https://fulcio.sigstore.dev
Chạy lệnh test kết nối trực tiếp:
curl -v https://tuf-repo.sigstore.dev/tuf.json | head -n 5
Nếu curl trả về JSON metadata thành công, vấn đề là do firewall hoặc proxy. Nếu vẫn lỗi, cần cấu hình proxy trong biến môi trường hoặc firewall rules.
Để verify kết quả, chạy lệnh ký giả lập:
cosign sign --key $COSIGN_KEY_PATH ghcr.io/my-registry/my-app:v1.0.0
Chờ thông báo "Successfully signed" xuất hiện.
Xử lý lỗi "certificate expired" hoặc lỗi thời gian máy chủ
Lỗi này xuất hiện khi chứng thư (certificate) từ Fulcio đã hết hạn hoặc đồng hồ thời gian (system clock) trên node CI/CD bị lệch so với thời gian chuẩn (UTC).
Tại sao: Chứng thư bảo mật có thời gian sống ngắn (thường là 1-24h). Nếu thời gian trên server CI/CD lệch, Cosign sẽ coi chứng thư hợp lệ là đã hết hạn hoặc chưa có hiệu lực.
Kết quả mong đợi: Đồng hồ hệ thống được đồng bộ chính xác và chứng thư được chấp nhận.
Trước tiên, kiểm tra thời gian trên máy chủ CI/CD hoặc container runner:
date -u
So sánh với thời gian chuẩn. Nếu lệch, đồng bộ lại bằng NTP (Chrony hoặc NTPd):
chronyc sources -v
chronyc makestep
Nếu đang chạy trong container Docker/Kubernetes, đảm bảo container được chạy với quyền truy cập vào host time:
docker run --privileged --name time-fix alpine ntpdate pool.ntp.org
Đối với Kubernetes, đảm bảo node có cấu hình Chrony service đang chạy:
kubectl describe node | grep -i chrony
Verify kết quả bằng cách kiểm tra lại lỗi sau khi đồng bộ:
cosign verify-attestation --type slsaprovenance --key $COSIGN_KEY_PATH ghcr.io/my-registry/my-app:v1.0.0
Thông báo "Verified OK" xác nhận lỗi thời gian đã được khắc phục.
Khắc phục lỗi "key not found" hoặc lỗi định dạng Key
Lỗi này thường xảy ra khi biến môi trường chứa đường dẫn key bị sai hoặc key file không có quyền đọc (read permission) cho user chạy pipeline.
Tại sao: Cosign cần quyền đọc file key để giải mã và ký. Nếu pipeline chạy dưới user `nobody` hoặc `jenkins` mà file key thuộc quyền root, quá trình sẽ thất bại.
Kết quả mong đợi: Cosign đọc được key file và thực hiện ký thành công.
Kiểm tra quyền truy cập của file key:
ls -l /path/to/cosign.key
Đảm bảo file có quyền đọc (400 hoặc 600) và thuộc về user chạy pipeline:
chmod 400 /path/to/cosign.key
chown jenkins:jenkins /path/to/cosign.key
Đối với Tekton/Pipeline, đảm bảo Secret được mount đúng path:
kubectl get secret cosign-secret -o jsonpath='{.data.cos\.key}' | base64 --decode > /tmp/cosign.key
chmod 400 /tmp/cosign.key
Verify kết quả bằng cách hiển thị thông tin key (không tiết lộ nội dung):
cosign public-key --key /tmp/cosign.key
Đầu ra phải hiển thị chuỗi public key bắt đầu bằng "-----BEGIN PUBLIC KEY-----".
Khắc phục sự cố OPA Gatekeeper không chặn được image vi phạm
Gatekeeper có thể bị "lờ đi" nếu cấu hình ConstraintTemplate sai, Constraint chưa được áp dụng, hoặc webhook bị lỗi thời gian chờ (timeout).
Debug ConstraintTemplate và Constraint chưa khớp (Match Failure)
Khi image vi phạm chính sách nhưng vẫn được deploy, nguyên nhân thường là Rego policy trong ConstraintTemplate không khớp với label của namespace hoặc resource type.
Tại sao: Gatekeeper sử dụng cơ chế "match" để lọc resource. Nếu `match` không đúng, policy sẽ không được áp dụng cho resource đó, dẫn đến việc không chặn.
Kết quả mong đợi: ConstraintTemplate và Constraint được tạo đúng, và log hiển thị việc vi phạm chính sách.
Kiểm tra trạng thái của Constraint bằng lệnh:
kubectl get constraint -o yaml | grep -A 10 "status:"
Quan sát trường `enforcementActions` và `violations`. Nếu không có vi phạm, kiểm tra lại định nghĩa `match` trong file template.
Đảm bảo file ConstraintTemplate `/etc/gatekeeper/templates/k8simageallow.yaml` (ví dụ) có cấu trúc `match` chính xác:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8simageallow
spec:
crd:
spec:
names:
kind: K8sImageAllow
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8simageallow
violation[{
"msg": msg,
"details": {"allowed_images": allowed}
}] {
input := input.review.object
image := input.spec.containers[_].image
not allowed_image(image)
msg := sprintf("Image %v is not allowed. Allowed images: %v", [image, allowed])
}
allowed_image(img) {
allowed := input.parameters.allowed_images[_]
img == allowed
}
default allowed_images[_] := []
Tạo Constraint tương ứng với đúng namespace:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageAllow
metadata:
name: k8s-allowlist
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Deployment", "Pod"]
namespaces:
- production
parameters:
allowed_images:
- "gcr.io/my-project/*"
- "docker.io/my-registry/*"
Apply config và verify bằng cách deploy một Pod vi phạm:
kubectl apply -f /etc/gatekeeper/constraints/k8s-allowlist.yaml
kubectl run test-pod --image=nginx:latest -n production
Kết quả mong đợi: Lệnh deploy bị từ chối với lỗi "K8sImageAllow.k8s-allowlist: Image nginx:latest is not allowed...".
Khắc phục lỗi Gatekeeper Webhook Timeout
Khi Gatekeeper không trả về phản hồi trong thời gian quy định (thường 10s), Kubernetes API Server sẽ mặc định cho phép request (fail-open) hoặc từ chối tùy cấu hình.
Tại sao: Nếu quá nhiều Constraint hoặc policy quá phức tạp, thời gian đánh giá (evaluation time) vượt quá threshold, webhook bị kill.
Kết quả mong đợi: Webhook phản hồi nhanh, không bị timeout, và chính sách được thực thi đúng.
Kiểm tra log của Gatekeeper controller để tìm lỗi timeout:
kubectl logs -n gatekeeper-system deployment/gatekeeper-controller-manager | grep -i "timeout"
Giảm độ phức tạp của Rego hoặc tăng timeout threshold trong ConfigMap của Gatekeeper. Đường dẫn: `/etc/gatekeeper/config/config.yaml`:
apiVersion: v1
kind: ConfigMap
metadata:
name: config
namespace: gatekeeper-system
data:
config: |
validation:
timeoutSeconds: 10
excludeGroups: ["system:serviceaccounts"]
Apply lại config để Gatekeeper reload:
kubectl apply -f /etc/gatekeeper/config/config.yaml
kubectl rollout restart deployment/gatekeeper-controller-manager -n gatekeeper-system
Verify kết quả bằng cách tạo Pod và kiểm tra thời gian phản hồi:
time kubectl run test-pod --image=gcr.io/my-project/valid-app:v1 -n production
Thời gian thực thi phải nhỏ hơn 10s và Pod được tạo thành công (nếu hợp lệ) hoặc bị chặn ngay lập tức.
Xử lý lỗi "Constraint not found" khi đã tạo
Đôi khi Constraint đã được tạo nhưng Gatekeeper chưa đồng bộ (sync) kịp thời.
Tại sao: Gatekeeper có cơ chế sync period (mặc định 30s). Nếu bạn tạo Constraint và ngay lập tức deploy Pod, Gatekeeper có thể chưa "nhìn thấy" nó.
Kết quả mong đợi: Constraint được đồng bộ ngay lập tức và áp dụng chính sách.
Kiểm tra trạng thái đồng bộ trong status của Constraint:
kubectl get constraint k8s-allowlist -o jsonpath='{.status.violations}'
Nếu muốn buộc đồng bộ ngay, tạo một event giả hoặc restart controller. Cách nhanh nhất là cập nhật annotation `gatekeeper.io/force-sync`:
kubectl annotate constraint k8s-allowlist gatekeeper.io/force-sync=$(date +%s) --overwrite
Verify kết quả bằng cách deploy Pod vi phạm ngay sau đó:
kubectl run test-pod --image=nginx:latest -n production
Lần này, lỗi "Image not allowed" phải xuất hiện ngay lập tức.
Mẹo tối ưu hiệu năng khi xử lý lượng image lớn trong pipeline
Khi số lượng image tăng lên hàng trăm trong một lần build, việc ký và xác minh từng image riêng lẻ sẽ làm pipeline chậm đáng kể.
Sử dụng Batch Signing với Cosign
Thay vì chạy lệnh `cosign sign` cho từng image, hãy sử dụng cơ chế batch để ký nhiều image cùng lúc hoặc ký một "bundle" (tệp tin chứa danh sách image).
Tại sao: Mỗi lần gọi API Fulcio/Rekor đều có overhead về handshake TLS và mạng. Batch giảm số lần gọi API xuống còn 1-2 lần cho toàn bộ batch.
Kết quả mong đợi: Thời gian ký giảm từ 5 phút xuống còn 1 phút cho 50 image.
Tạo file danh sách image (image-list.txt) trong pipeline:
echo "ghcr.io/my-registry/app-frontend:v1.0.0
ghcr.io/my-registry/app-backend:v1.0.0
ghcr.io/my-registry/app-worker:v1.0.0" > image-list.txt
Sử dụng lệnh `cosign sign` với nhiều argument hoặc pipe từ file:
xargs -a image-list.txt -n1 cosign sign --key $COSIGN_KEY_PATH --yes
Lưu ý: Nếu dùng Tekton, hãy cấu hình Task chạy song song (parallel) cho từng image nếu không dùng batch, hoặc dùng một Task duy nhất chạy script batch ở trên.
Verify kết quả bằng cách kiểm tra số lượng chữ ký trên Rekor:
cosign search --key $COSIGN_KEY_PATH ghcr.io/my-registry --type attestation | wc -l
Số lượng dòng trả về phải bằng số lượng image đã ký.
Tối ưu Cache Layer cho Image Verification
Trong pipeline, việc xác minh (verify) image có thể lặp đi lặp lại nếu image đã được ký và đẩy lên registry.
Tại sao: Xác minh yêu cầu tải lại chữ ký từ Rekor và kiểm tra lại hash. Nếu image không đổi, không cần verify lại toàn bộ.
Kết quả mong đợi: Giảm thời gian pipeline bằng cách bỏ qua bước verify cho image đã được cache.
Trong Tekton, sử dụng `workspaces` hoặc `volumes` để lưu cache kết quả verify (checksum của image digest).
Tạo script `verify-with-cache.sh`:
#!/bin/bash
IMAGE=$1
CACHE_DIR=/workspace/cache
DIGEST=$(echo $IMAGE | cut -d: -f2)
CACHE_FILE=${CACHE_DIR}/${DIGEST}.verified
if [ -f "$CACHE_FILE" ]; then
echo "Cache hit for $IMAGE, skipping verification."
exit 0
fi
echo "Verifying $IMAGE..."
if cosign verify --key $COSIGN_KEY_PATH $IMAGE; then
touch $CACHE_FILE
echo "Verification successful, cached."
else
echo "Verification failed!"
exit 1
fi
Mount volume cache trong Tekton Task:
workspaces:
- name: cache
subPath: sigstore-cache
Verify kết quả bằng cách chạy pipeline 2 lần liên tiếp. Lần thứ 2 phải có log "Cache hit" và thời gian thực thi ngắn hơn nhiều.
Tối ưu cấu hình OPA Gatekeeper cho hiệu năng
Khi có hàng nghìn resource, Gatekeeper có thể làm chậm API Server nếu không được cấu hình đúng.
Tại sao: Gatekeeper đánh giá policy cho mỗi request. Nếu policy quá phức tạp hoặc số lượng constraint quá lớn, thời gian đánh giá tăng theo cấp số nhân.
Kết quả mong đợi: API Server phản hồi nhanh, không bị tắc nghẽn khi deploy hàng loạt Pod.
Áp dụng cấu hình `excludeNamespaces` trong ConfigMap để loại trừ các namespace không cần bảo mật cao (ví dụ: dev, staging, kube-system).
File config `/etc/gatekeeper/config/config.yaml`:
apiVersion: v1
kind: ConfigMap
metadata:
name: config
namespace: gatekeeper-system
data:
config: |
validation:
excludeNamespaces: ["kube-system", "gatekeeper-system", "dev", "staging"]
excludeGroups: ["system:serviceaccounts"]
Apply lại config:
kubectl apply -f /etc/gatekeeper/config/config.yaml
Verify hiệu năng bằng cách đo thời gian deploy 100 Pod:
time for i in {1..100}; do kubectl run pod-$i --image=nginx -n production & done
So sánh thời gian với và không có cấu hình exclude. Thời gian phải giảm đáng kể do Gatekeeper bỏ qua các request không cần thiết.
Điều hướng series:
Mục lục: Series: Series: Xây dựng nền tảng Secure Software Supply Chain với Sigstore, Cosign, Tekton và OPA Gatekeeper trên Kubernetes để đảm bảo an toàn vòng đời phần mềm
« Phần 7: Tích hợp end-to-end: Từ code đến production với chuỗi cung ứng an toàn