Cấu hình Harbor Admission Controller để chặn image không có chữ ký Notary
Harbor Admission Controller là thành phần chạy trong Kubernetes cluster để kiểm tra image trước khi nó được load vào node. Chúng ta sẽ cấu hình nó để yêu cầu mọi image phải có chữ ký hợp lệ từ Notary trước khi cho phép deploy.
Tại sao: Đây là lớp bảo vệ cuối cùng (defense-in-depth) ngăn chặn việc deploy các image bị giả mạo hoặc không rõ nguồn gốc, ngay cả khi chúng đã được đẩy lên registry.
Kết quả mong đợi: Cluster sẽ từ chối bất kỳ Pod nào cố gắng sử dụng image không có chữ ký Notary hợp lệ, trả về lỗi Admission Denied.
Triển khai Harbor Admission Controller
Đầu tiên, bạn cần deploy Harbor Admission Controller vào Kubernetes cluster. File YAML dưới đây định nghĩa Deployment, ServiceAccount và ClusterRoleBinding cần thiết.
Tạo file /etc/harbor/admission-controller.yaml với nội dung sau:
apiVersion: v1
kind: ServiceAccount
metadata:
name: harbor-admission-controller
namespace: harbor
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: harbor-admission-controller
rules:
- apiGroups: [""]
resources: ["namespaces", "nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["extensions"]
resources: ["deployments", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "replicationcontrollers"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["validatingwebhookconfigurations"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: harbor-admission-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: harbor-admission-controller
subjects:
- kind: ServiceAccount
name: harbor-admission-controller
namespace: harbor
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: harbor-admission-controller
namespace: harbor
spec:
replicas: 2
selector:
matchLabels:
app: harbor-admission-controller
template:
metadata:
labels:
app: harbor-admission-controller
spec:
serviceAccountName: harbor-admission-controller
containers:
- name: admission-controller
image: goharbor/harbor-admission-controller:v2.10.0
imagePullPolicy: Always
env:
- name: HARBOR_URL
value: "https://harbor.your-domain.com"
- name: HARBOR_INSECURE
value: "false"
- name: HARBOR_NOTARY_URL
value: "https://notary.your-domain.com:4443"
- name: HARBOR_NOTARY_INSECURE
value: "false"
- name: HARBOR_PROJECT_WHITELIST
value: "dev-prod,sec-critical"
- name: HARBOR_POLICY_ENFORCEMENT
value: "true"
- name: HARBOR_POLICY_ACTION
value: "deny"
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: harbor-admission-controller
namespace: harbor
spec:
ports:
- port: 443
targetPort: 8080
selector:
app: harbor-admission-controller
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: harbor-admission-controller
webhooks:
- name: harbor-admission-controller.k8s.io
clientConfig:
service:
namespace: harbor
name: harbor-admission-controller
path: "/validate"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
- operations: ["CREATE", "UPDATE"]
apiGroups: ["apps", "extensions"]
apiVersions: ["v1", "v1beta1", "v1beta2"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1", "v1beta1"]
Áp dụng cấu hình vào cluster:
kubectl apply -f /etc/harbor/admission-controller.yaml
Kiểm tra trạng thái Pod:
kubectl get pods -n harbor -l app=harbor-admission-controller
Kết quả mong đợi: 2 Pods có trạng thái Running và READY 2/2.
Thiết lập rule tự động từ chối push image có lỗ hổng Critical/High
Harbor tích hợp sẵn cơ chế Security Scanning. Chúng ta sẽ cấu hình "Update Rule" để tự động từ chối việc push image nếu kết quả quét từ Trivy/Clair trả về mức độ nghiêm trọng Critical hoặc High.
Tại sao: Ngăn chặn ngay tại điểm đẩy (push time) các artifact nguy hiểm vào kho chứa, giảm thiểu rủi ro lan truyền.
Kết quả mong đợi: Khi cố push image có lỗ hổng, Harbor trả về lỗi 403 Forbidden với thông báo về policy violation.
Cấu hình Policy Enforcement trong Harbor UI
Truy cập Harbor UI, đăng nhập với quyền Admin. Chuyển đến mục Administration -> Access Control -> Security Policies.
Tạo một Security Policy mới với cấu hình sau:
apiVersion: harbor.io/v1
kind: SecurityPolicy
metadata:
name: block-critical-high-vulns
project: "*"
spec:
type: vulnerability
severity:
- Critical
- High
action: deny
description: "Auto-reject images with Critical or High vulnerabilities"
Để áp dụng qua API (cho tự động hóa), gửi request POST:
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(harbor-cli login -u admin -p password | grep -oP 'Bearer \K.*)" \
-d '{
"type": "vulnerability",
"severity": ["Critical", "High"],
"action": "deny",
"description": "Block Critical/High vulns"
}' \
https://harbor.your-domain.com/api/v2.0/projects/_system/security/policies
Verify bằng cách cố gắng push một image đã biết có lỗ hổng (ví dụ: alpine:20160906):
docker pull alpine:20160906
docker tag alpine:20160906 harbor.your-domain.com/dev-prod/alpine:20160906
docker push harbor.your-domain.com/dev-prod/alpine:20160906
Kết quả mong đợi: Lệnh push bị dừng và hiện thông báo: "Push denied: Image contains Critical vulnerabilities."
Tích hợp Policy Engine với OPA (Open Policy Agent) để kiểm soát chi tiết hơn
OPA cho phép viết các policy phức tạp bằng ngôn ngữ Rego, vượt ra ngoài các rule đơn giản của Harbor. Chúng ta sẽ deploy OPA Gatekeeper và liên kết nó với Harbor Admission Controller.
Tại sao: Cần kiểm soát logic nghiệp vụ phức tạp như: "Chỉ cho phép image từ registry nội bộ", "Chặn image không có tag version cụ thể", hoặc "Yêu cầu metadata nhất định".
Kết quả mong đợi: OPA Gatekeeper intercept các request từ Admission Controller và enforce các policy Rego tùy chỉnh.
Deploy OPA Gatekeeper và Policy Bundle
Đầu tiên, deploy OPA Gatekeeper vào cluster:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml
Tạo thư mục cấu hình policy cho Harbor:
mkdir -p /etc/harbor/policies/harbor-restrictions
cd /etc/harbor/policies/harbor-restrictions
Tạo file policy Rego /etc/harbor/policies/harbor-restrictions/restrict-internal-images.rego:
package k8s_validation.restrict_internal_images
violation[{
"msg": msg
}] {
input.review.object.kind == "Pod"
not input.review.object.spec.containers[_].image | startswith("harbor.your-domain.com/")
msg := sprintf("Image must be from internal registry: %v", [input.review.object.spec.containers[_].image])
}
violation[{
"msg": msg
}] {
input.review.object.kind == "Deployment"
not input.review.object.spec.template.spec.containers[_].image | startswith("harbor.your-domain.com/")
msg := sprintf("Deployment image must be from internal registry: %v", [input.review.object.spec.template.spec.containers[_].image])
}
Tạo file ConstraintTemplate tương ứng /etc/harbor/policies/harbor-restrictions/constraint-template.yaml:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srestrictinternalimages
spec:
crd:
spec:
names:
kind: K8sRestrictInternalImages
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8s_validation.restrict_internal_images
violation[{
"msg": msg
}] {
input.review.object.kind == "Pod"
not input.review.object.spec.containers[_].image | startswith("harbor.your-domain.com/")
msg := sprintf("Image must be from internal registry: %v", [input.review.object.spec.containers[_].image])
}
Áp dụng ConstraintTemplate và Constraint:
kubectl apply -f /etc/harbor/policies/harbor-restrictions/constraint-template.yaml
kubectl apply -f -
Verify kết quả:
kubectl get constraint K8sRestrictInternalImages
kubectl get ConstraintTemplate
Kết quả mong đợi: Cả Constraint và ConstraintTemplate đều hiển thị trạng thái Valid.
Áp dụng policy cho các namespace và project cụ thể trong tổ chức
Không phải tất cả các project đều cần chính sách bảo mật như nhau. Chúng ta sẽ cấu hình để áp dụng policy khác nhau cho project "prod-critical" so với "dev-sandbox".
Tại sao: Linh hoạt trong quản trị bảo mật, tránh gây khó khăn cho đội phát triển trong môi trường test mà vẫn đảm bảo an toàn cho production.
Kết quả mong đợi: Policy chỉ kích hoạt ở các namespace/project được chỉ định, các project khác vẫn hoạt động bình thường.
Cấu hình Label Selector và Namespace Scope
Sử dụng mechanism "Label Selector" của OPA Gatekeeper để lọc namespace.
Đầu tiên, đánh dấu các namespace cần kiểm soát chặt:
kubectl label namespace prod-critical security-level=high
kubectl label namespace dev-sandbox security-level=low
Tạo một Constraint mới chỉ áp dụng cho namespace có label `security-level=high`:
cat > /tmp/strict-policy.yaml
Đồng thời, cập nhật Harbor Admission Controller để đọc label từ namespace và quyết định hành động. Thêm biến môi trường vào Deployment Harbor Admission Controller (đã có ở phần 1):
kubectl set env deployment/harbor-admission-controller -n harbor HARBOR_NAMESPACE_POLICY_MAP="prod-critical:deny-all,dev-sandbox:warn-only"
Verify bằng cách tạo Pod trong từng namespace:
# Trong prod-critical (sẽ bị chặn nếu dùng image ngoài)
kubectl run test-pod-prod --image=nginx:latest -n prod-critical
# Trong dev-sandbox (có thể cho phép hoặc chỉ cảnh báo tùy config)
kubectl run test-pod-dev --image=nginx:latest -n dev-sandbox
Kiểm tra sự kiện (events) để xem kết quả:
kubectl describe pod test-pod-prod -n prod-critical
kubectl describe pod test-pod-dev -n dev-sandbox
Kết quả mong đợi: Pod trong `prod-critical` bị tạo lỗi "Admission denied" hoặc "Constraint violation", Pod trong `dev-sandbox` có thể chạy (nếu policy chỉ là warn) hoặc bị chặn tùy mức độ cấu hình.
Mô phỏng kịch bản vi phạm policy và xác minh cơ chế chặn
Bước cuối cùng là thực hiện kiểm thử tích hợp (Integration Test) để đảm bảo toàn bộ luồng: Push -> Scan -> Policy Check -> Admission -> Deploy hoạt động chính xác.
Tại sao: Xác minh tính toàn vẹn của hệ thống bảo mật trước khi đưa vào vận hành thực tế.
Kết quả mong đợi: Log chi tiết ghi nhận lý do bị chặn, không có artifact nguy hiểm nào đi qua được hệ thống.
Kịch bản 1: Chặn image không có chữ ký Notary
Tạo một image giả mạo (không ký) và cố gắng deploy nó vào namespace production.
docker pull alpine:latest
docker tag alpine:latest harbor.your-domain.com/prod-critical/alpine:unsigned
docker push harbor.your-domain.com/prod-critical/alpine:unsigned
kubectl run unsigned-pod --image=harbor.your-domain.com/prod-critical/alpine:unsigned -n prod-critical
Kiểm tra log của Harbor Admission Controller:
kubectl logs -n harbor -l app=harbor-admission-controller | grep "unsigned-pod"
Kết quả mong đợi: Log hiển thị "Signature verification failed" hoặc "No valid signature found" và Pod không được tạo.
Kịch bản 2: Chặn image có lỗ hổng Critical
Push image đã biết có lỗ hổng và deploy.
docker pull ubuntu:14.04
docker tag ubuntu:14.04 harbor.your-domain.com/dev-sandbox/ubuntu:vuln
docker push harbor.your-domain.com/dev-sandbox/ubuntu:vuln
kubectl run vuln-pod --image=harbor.your-domain.com/dev-sandbox/ubuntu:vuln -n dev-sandbox
Kiểm tra trạng thái Pod:
kubectl get pod vuln-pod -n dev-sandbox
kubectl describe pod vuln-pod -n dev-sandbox
Kết quả mong đợi: Pod ở trạng thái Pending hoặc Error với lý do "Image rejected due to Critical vulnerability".
Kịch bản 3: Chặn image từ registry bên ngoài
Cố gắng deploy image từ Docker Hub trực tiếp vào namespace có policy strict.
kubectl run external-pod --image=gcr.io/google-containers/pause:latest -n prod-critical
Kiểm tra sự kiện Gatekeeper:
kubectl get events -n prod-critical --sort-by='.lastTimestamp'
Kết quả mong đợi: Sự kiện hiển thị "Constraint violation: Image must be from internal registry".
Để xem log chi tiết của OPA Gatekeeper:
kubectl logs -n gatekeeper-system -l app=gatekeeper -c manager | grep "prod-critical"
Kết quả mong đợi: Log xác nhận rule `K8sRestrictInternalImages` đã kích hoạt và chặn request.
Điều hướng series:
Mục lục: Series: Series: Xây dựng nền tảng Secure Multi-Cloud Container Registry với Harbor, Notary và Policy Engine để kiểm soát phân phối artifact an toàn
« Phần 5: Triển khai Policy Engine (Trivy/Clair) để quét lỗ hổng bảo mật artifact
Phần 7: Tối ưu hóa hiệu năng và bảo mật cho registry trong môi trường Multi-Cloud »