Skip to main content

Command Palette

Search for a command to run...

Rego và Open Policy Agent (OPA)

Published
12 min read

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)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 queryevaluation 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:

  • input là dữ liệu OPA nhận vào (ví dụ Kubernetes AdmissionReview)

  • deny là rule output bạn định nghĩa

  • Rule 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ì?

  • allow

  • deny

  • warn

  • patch (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ểmNhượ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 + GatekeeperBest choice cho Admission ControllerRego hơi phức tạp với người mới
KyvernoViết YAML, dễ đọcKhô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
16 views

More from this blog

LOKIAI

143 posts

Code for food