Rego và Open Policy Agent (OPA)
Trong bối cảnh Cloud-native, Kubernetes, Microservice, và Zero Trust, việc kiểm soát chính sách bảo mật không chỉ nằm ở firewall hay IAM nữa. Chúng ta cần một công cụ linh hoạt, có khả năng nhúng vào mọi tầng của hệ thống — từ Kubernetes Admission Controller, API Gateway, CI/CD Pipeline, Terraform, cho đến ứng dụng backend. Đây chính là lý do OPA (Open Policy Agent) và Rego trở thành vũ khí mạnh mẽ cho DevSecOps.
Trong bài viết này, tôi – với vai trò DevSecOps – sẽ chia sẻ cách hiểu thực tế và dễ áp dụng nhất về OPA và Rego.
1. OPA là gì?
OPA (Open Policy Agent) là một công cụ Policy-as-Code, cho phép bạn tách logic chính sách ra khỏi code ứng dụng. Thay vì hardcode quyền truy cập, rule bảo mật, hay rule compliance trong hệ thống, bạn đưa nó vào một policy engine độc lập.
🧩 OPA có thể dùng ở đâu?
✔ Trong Kubernetes Admission Controller để kiểm tra YAML trước khi apply
✔ Trong API Gateway như Envoy, Istio để quyết định request có được phép đi qua
✔ Trong CI/CD (GitHub Actions, GitLab CI, ArgoCD) để kiểm tra compliance trước khi deploy
✔ Trong Terraform, kiểm tra resource có tuân thủ security baseline
✔ Trong backend microservice, quyết định access control theo ABAC/RBAC
Điểm mạnh: OPA không áp đặt mô hình chính sách cụ thể — bạn định nghĩa tất cả bằng Rego.
2. Rego là gì?
Rego là ngôn ngữ khai báo (declarative language) được dùng để viết policy cho OPA.
Đặc điểm của Rego:
Không phải là ngôn ngữ lập trình tổng quát
Không có loop, không có variable mutation
Chạy theo mô hình logic programming
Tập trung vào query và evaluation dữ liệu đầu vào
Nói ngắn gọn: bạn chỉ mô tả logic mong muốn, OPA sẽ tự tìm cách evaluate.
3. Cấu trúc Policy Rego
Một file Rego cơ bản có dạng:
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Deployment"
not input.request.object.spec.template.spec.securityContext.runAsNonRoot
msg := "Containers must run as non-root user"
}
🔍 Giải thích:
inputlà dữ liệu OPA nhận vào (ví dụ Kubernetes AdmissionReview)denylà rule output bạn định nghĩaRule trả về danh sách lỗi nếu điều kiện đúng
4. Tư duy viết Rego cho DevSecOps
Khi viết Rego, hãy luôn xác định:
(1) Policy muốn bảo vệ điều gì?
Ví dụ:
Container không được chạy root
Terraform không được tạo S3 bucket public
API chỉ cho phép user role "admin"
(2) Input data sẽ là gì?
OPA không đoán dữ liệu, bạn phải hiểu input structure.
Ví dụ AdmissionReview:
{
"request": {
"kind": { "kind": "Deployment" },
"object": { ... }
}
}
(3) Kết quả trả về cho hệ thống là gì?
allowdenywarnpatch(Gatekeeper)
5. Ví dụ thực tế: Policy cấm container dùng image không có tag
🎯 Business rule
Không cho phép deploy image dạng latest, phải pin version.
🧠 Policy Rego
package kubernetes.admission
deny[msg] {
image := input.request.object.spec.template.spec.containers[_].image
endswith(image, ":latest")
msg := sprintf("Image %s dùng tag 'latest' – không được phép!", [image])
}
✔ Kết quả khi vi phạm
OPA sẽ trả về deny với message để Kubernetes từ chối YAML.
6. Ví dụ: Policy kiểm tra Terraform resource
Bạn muốn ngăn việc tạo S3 bucket dạng public-read.
package terraform.s3
deny[msg] {
resource := input.resource.aws_s3_bucket[_]
resource.acl == "public-read"
msg := "S3 bucket không được phép public"
}
7. Rego trong CI/CD – DevSecOps thực chiến
Trong pipeline GitHub Actions:
- name: OPA Test
run: opa eval -i deployment.yaml -d policy.rego "data.kubernetes.admission.deny"
CI sẽ fail nếu tactic violate security baseline.
🎯 Lợi ích:
Chặn vi phạm ngay từ pipeline
Không cần đợi đến khi apply vào cluster
Dev tự sửa, đỡ tốn thời gian Ops/Platform
8. Best Practices khi viết policy Rego
✔ Tách policy theo domain
kubernetes/
terraform/
api-authz/
cicd/
✔ Viết test bằng Rego
test_run_as_nonroot_violation {
input := {"request": {"object": {"spec": {"template": {"spec": {"securityContext": {}}}}}}}
result := deny[input]
count(result) > 0
}
✔ Tránh rule quá lớn
Thay vào đó, chia thành rule nhỏ rồi combine.
✔ Logically pure – không phụ thuộc môi trường
Policy chạy tốt từ laptop → CI → production.
9. Rego vs Gatekeeper vs Kyverno
| Công cụ | Ưu điểm | Nhược điểm |
| OPA (standalone) | Nhúng vào API gateway, CI/CD, backend, Terraform… | Không có CRD, không quản lý policy trong K8s |
| OPA + Gatekeeper | Best choice cho Admission Controller | Rego hơi phức tạp với người mới |
| Kyverno | Viết YAML, dễ đọc | Không xài được ngoài Kubernetes |
Nếu bạn muốn xây kiến trúc bảo mật unified nhiều hệ thống → OPA là lựa chọn số 1.
10. Ví Dụ
package kubernetes.admission
import future.keywords.if
import future.keywords.in
import future.keywords.contains
# Danh sách các labels bắt buộc
required_labels := ["env", "org"]
# ============================================
# 1. KIỂM TRA NAMESPACE LABELS
# ============================================
# Deny nếu namespace backend thiếu labels bắt buộc
deny contains msg if {
input.request.kind.kind == "Namespace"
input.request.object.metadata.name == "backend"
# Lấy labels hiện tại (hoặc empty object nếu không có)
labels := object.get(input.request.object.metadata, "labels", {})
# Kiểm tra từng label bắt buộc
some required_label in required_labels
not labels[required_label]
msg := sprintf("Namespace 'backend' thiếu label bắt buộc: '%s'", [required_label])
}
# Deny nếu labels bắt buộc có giá trị rỗng
deny contains msg if {
input.request.kind.kind == "Namespace"
input.request.object.metadata.name == "backend"
labels := input.request.object.metadata.labels
some required_label in required_labels
labels[required_label] == ""
msg := sprintf("Label '%s' trong namespace 'backend' không được để trống", [required_label])
}
# ============================================
# 2. KIỂM TRA RESOURCE LIMITS CHO POD
# ============================================
# Deny nếu Pod trong namespace backend không có resource limits
deny contains msg if {
# Kiểm tra các loại workload
input.request.kind.kind in ["Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob"]
input.request.namespace == "backend"
# Lấy containers từ các loại workload khác nhau
containers := get_containers(input.request)
# Kiểm tra từng container
some container in containers
not has_resource_limits(container)
msg := sprintf(
"Container '%s' trong namespace 'backend' phải khai báo resource limits (cpu và memory)",
[container.name]
)
}
# Deny nếu thiếu CPU limit
deny contains msg if {
input.request.kind.kind in ["Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob"]
input.request.namespace == "backend"
containers := get_containers(input.request)
some container in containers
# Có resources nhưng thiếu CPU limit
container.resources.limits
not container.resources.limits.cpu
msg := sprintf(
"Container '%s' trong namespace 'backend' thiếu CPU limit",
[container.name]
)
}
# Deny nếu thiếu Memory limit
deny contains msg if {
input.request.kind.kind in ["Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob"]
input.request.namespace == "backend"
containers := get_containers(input.request)
some container in containers
# Có resources nhưng thiếu Memory limit
container.resources.limits
not container.resources.limits.memory
msg := sprintf(
"Container '%s' trong namespace 'backend' thiếu Memory limit",
[container.name]
)
}
# ============================================
# 3. KIỂM TRA RESOURCE REQUESTS (Khuyến nghị)
# ============================================
# Warning nếu không có resource requests
warn contains msg if {
input.request.kind.kind in ["Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob"]
input.request.namespace == "backend"
containers := get_containers(input.request)
some container in containers
not container.resources.requests
msg := sprintf(
"Khuyến nghị: Container '%s' nên có resource requests để tối ưu scheduling",
[container.name]
)
}
# ============================================
# HELPER FUNCTIONS
# ============================================
# Lấy containers từ các loại workload khác nhau
get_containers(request) := containers if {
request.kind.kind == "Pod"
containers := request.object.spec.containers
}
get_containers(request) := containers if {
request.kind.kind in ["Deployment", "StatefulSet", "DaemonSet"]
containers := request.object.spec.template.spec.containers
}
get_containers(request) := containers if {
request.kind.kind == "Job"
containers := request.object.spec.template.spec.containers
}
get_containers(request) := containers if {
request.kind.kind == "CronJob"
containers := request.object.spec.jobTemplate.spec.template.spec.containers
}
# Kiểm tra container có đầy đủ resource limits
has_resource_limits(container) if {
container.resources.limits
container.resources.limits.cpu
container.resources.limits.memory
}
# ============================================
# 4. VALIDATION RULES BỔ SUNG
# ============================================
# Deny nếu giá trị CPU limit quá nhỏ (dưới 100m)
deny contains msg if {
input.request.namespace == "backend"
containers := get_containers(input.request)
some container in containers
cpu_limit := container.resources.limits.cpu
cpu_value := parse_cpu(cpu_limit)
cpu_value < 100
msg := sprintf(
"Container '%s' có CPU limit quá thấp (%s), tối thiểu 100m",
[container.name, cpu_limit]
)
}
# Deny nếu giá trị Memory limit quá nhỏ (dưới 64Mi)
deny contains msg if {
input.request.namespace == "backend"
containers := get_containers(input.request)
some container in containers
memory_limit := container.resources.limits.memory
memory_value := parse_memory(memory_limit)
memory_value < 67108864 # 64Mi in bytes
msg := sprintf(
"Container '%s' có Memory limit quá thấp (%s), tối thiểu 64Mi",
[container.name, memory_limit]
)
}
# Parse CPU string to milicores
parse_cpu(cpu) := value if {
endswith(cpu, "m")
value := to_number(trim_suffix(cpu, "m"))
}
parse_cpu(cpu) := value if {
not endswith(cpu, "m")
value := to_number(cpu) * 1000
}
# Parse Memory string to bytes
parse_memory(memory) := value if {
endswith(memory, "Mi")
value := to_number(trim_suffix(memory, "Mi")) * 1024 * 1024
}
parse_memory(memory) := value if {
endswith(memory, "Gi")
value := to_number(trim_suffix(memory, "Gi")) * 1024 * 1024 * 1024
}
parse_memory(memory) := value if {
endswith(memory, "M")
value := to_number(trim_suffix(memory, "M")) * 1000 * 1000
}
parse_memory(memory) := value if {
endswith(memory, "G")
value := to_number(trim_suffix(memory, "G")) * 1000 * 1000 * 1000
}
parse_memory(memory) := value if {
not contains(memory, "i")
not contains(memory, "M")
not contains(memory, "G")
value := to_number(memory)
}
package kubernetes.admission
import future.keywords.if
# ============================================
# TEST CASES CHO NAMESPACE LABELS
# ============================================
test_namespace_with_all_labels if {
count(deny) == 0 with input as {
"request": {
"kind": {"kind": "Namespace"},
"object": {
"metadata": {
"name": "backend",
"labels": {
"env": "production",
"org": "engineering"
}
}
}
}
}
}
test_namespace_missing_env_label if {
deny["Namespace 'backend' thiếu label bắt buộc: 'env'"] with input as {
"request": {
"kind": {"kind": "Namespace"},
"object": {
"metadata": {
"name": "backend",
"labels": {
"org": "engineering"
}
}
}
}
}
}
test_namespace_missing_org_label if {
deny["Namespace 'backend' thiếu label bắt buộc: 'org'"] with input as {
"request": {
"kind": {"kind": "Namespace"},
"object": {
"metadata": {
"name": "backend",
"labels": {
"env": "production"
}
}
}
}
}
}
test_namespace_empty_label_value if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Namespace"},
"object": {
"metadata": {
"name": "backend",
"labels": {
"env": "",
"org": "engineering"
}
}
}
}
}
}
test_namespace_no_labels if {
count(deny) == 2 with input as {
"request": {
"kind": {"kind": "Namespace"},
"object": {
"metadata": {
"name": "backend"
}
}
}
}
}
# ============================================
# TEST CASES CHO POD RESOURCES
# ============================================
test_pod_with_proper_resources if {
count(deny) == 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "backend",
"object": {
"spec": {
"containers": [{
"name": "app",
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "250m",
"memory": "256Mi"
}
}
}]
}
}
}
}
}
test_pod_without_resources if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "backend",
"object": {
"spec": {
"containers": [{
"name": "app"
}]
}
}
}
}
}
test_pod_missing_cpu_limit if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "backend",
"object": {
"spec": {
"containers": [{
"name": "app",
"resources": {
"limits": {
"memory": "512Mi"
}
}
}]
}
}
}
}
}
test_pod_missing_memory_limit if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "backend",
"object": {
"spec": {
"containers": [{
"name": "app",
"resources": {
"limits": {
"cpu": "500m"
}
}
}]
}
}
}
}
}
# ============================================
# TEST CASES CHO DEPLOYMENT
# ============================================
test_deployment_with_proper_resources if {
count(deny) == 0 with input as {
"request": {
"kind": {"kind": "Deployment"},
"namespace": "backend",
"object": {
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "app",
"resources": {
"limits": {
"cpu": "1000m",
"memory": "1Gi"
},
"requests": {
"cpu": "500m",
"memory": "512Mi"
}
}
},
{
"name": "sidecar",
"resources": {
"limits": {
"cpu": "200m",
"memory": "256Mi"
}
}
}
]
}
}
}
}
}
}
}
test_deployment_one_container_missing_resources if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Deployment"},
"namespace": "backend",
"object": {
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "app",
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
}
}
},
{
"name": "sidecar"
}
]
}
}
}
}
}
}
}
# ============================================
# TEST CASES CHO RESOURCE MINIMUMS
# ============================================
test_cpu_limit_too_low if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "backend",
"object": {
"spec": {
"containers": [{
"name": "app",
"resources": {
"limits": {
"cpu": "50m",
"memory": "512Mi"
}
}
}]
}
}
}
}
}
test_memory_limit_too_low if {
count(deny) > 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "backend",
"object": {
"spec": {
"containers": [{
"name": "app",
"resources": {
"limits": {
"cpu": "500m",
"memory": "32Mi"
}
}
}]
}
}
}
}
}
# ============================================
# TEST CASES CHO NAMESPACE KHÁC
# ============================================
test_other_namespace_not_affected if {
count(deny) == 0 with input as {
"request": {
"kind": {"kind": "Pod"},
"namespace": "frontend",
"object": {
"spec": {
"containers": [{
"name": "app"
}]
}
}
}
}
}
# ============================================
# VÍ DỤ 1: Namespace ĐÚNG - Có đủ labels
# ============================================
apiVersion: v1
kind: Namespace
metadata:
name: backend
labels:
env: production
org: engineering
team: platform # Label thêm (không bắt buộc)
---
# ============================================
# VÍ DỤ 2: Namespace SAI - Thiếu label 'env'
# ============================================
apiVersion: v1
kind: Namespace
metadata:
name: backend
labels:
org: engineering
---
# ============================================
# VÍ DỤ 3: Pod ĐÚNG - Có đủ resource limits
# ============================================
apiVersion: v1
kind: Pod
metadata:
name: backend-app
namespace: backend
spec:
containers:
- name: app
image: nginx:1.21
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
---
# ============================================
# VÍ DỤ 4: Pod SAI - Không có resources
# ============================================
apiVersion: v1
kind: Pod
metadata:
name: backend-app-no-resources
namespace: backend
spec:
containers:
- name: app
image: nginx:1.21
---
# ============================================
# VÍ DỤ 5: Deployment ĐÚNG - Multi containers
# ============================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api
namespace: backend
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapp/api:v1.0
resources:
limits:
cpu: "1000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
- name: sidecar-proxy
image: envoy:v1.20
resources:
limits:
cpu: "200m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
---
# ============================================
# VÍ DỤ 6: Deployment SAI - Thiếu CPU limit
# ============================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api-missing-cpu
namespace: backend
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapp/api:v1.0
resources:
limits:
memory: "1Gi" # Thiếu CPU
requests:
cpu: "500m"
memory: "512Mi"
---
# ============================================
# VÍ DỤ 7: StatefulSet ĐÚNG
# ============================================
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: backend-db
namespace: backend
spec:
serviceName: "db"
replicas: 3
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
spec:
containers:
- name: postgres
image: postgres:14
resources:
limits:
cpu: "2000m"
memory: "4Gi"
requests:
cpu: "1000m"
memory: "2Gi"
---
# ============================================
# VÍ DỤ 8: CronJob ĐÚNG
# ============================================
apiVersion: batch/v1
kind: CronJob
metadata:
name: backend-cleanup
namespace: backend
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cleanup
image: busybox:1.35
resources:
limits:
cpu: "200m"
memory: "128Mi"
requests:
cpu: "100m"
memory: "64Mi"
restartPolicy: OnFailure
---
# ============================================
# VÍ DỤ 9: Pod SAI - CPU quá thấp
# ============================================
apiVersion: v1
kind: Pod
metadata:
name: backend-low-cpu
namespace: backend
spec:
containers:
- name: app
image: nginx:1.21
resources:
limits:
cpu: "50m" # Quá thấp, tối thiểu 100m
memory: "512Mi"
---
# ============================================
# VÍ DỤ 10: Pod SAI - Memory quá thấp
# ============================================
apiVersion: v1
kind: Pod
metadata:
name: backend-low-memory
namespace: backend
spec:
containers:
- name: app
image: nginx:1.21
resources:
limits:
cpu: "500m"
memory: "32Mi" # Quá thấp, tối thiểu 64Mi
---
# ============================================
# VÍ DỤ 11: Pod trong namespace khác (KHÔNG bị kiểm tra)
# ============================================
apiVersion: v1
kind: Pod
metadata:
name: frontend-app
namespace: frontend
spec:
containers:
- name: app
image: nginx:1.21
# Không cần resources vì không phải namespace backend