policy-as-code

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Policy as Code

策略即代码(Policy as Code)

Automate policy enforcement through code using OPA/Rego, Kyverno, Checkov, and CI/CD integration to prevent compliance violations before they reach production.
通过集成OPA/Rego、Kyverno、Checkov与CI/CD流程,以代码方式自动化策略执行,在违规内容进入生产环境前阻止合规风险。

When to Use

适用场景

  • Enforcing security and compliance policies on infrastructure-as-code changes
  • Preventing misconfigured Kubernetes workloads from deploying
  • Automating guardrails in CI/CD pipelines for Terraform, CloudFormation, or Helm
  • Implementing organizational standards that must be consistently applied
  • Replacing manual approval gates with automated policy checks
  • 对基础设施即代码(Infrastructure as Code)变更执行安全与合规策略
  • 阻止配置错误的Kubernetes工作负载部署
  • 在Terraform、CloudFormation或Helm的CI/CD流水线中自动化实施管控规则
  • 落实必须统一执行的组织标准
  • 用自动化策略检查替代人工审批环节

Open Policy Agent (OPA) Rego Policies

Open Policy Agent (OPA) Rego Policies

rego
undefined
rego
undefined

deny_public_s3.rego - Deny S3 buckets with public access

deny_public_s3.rego - Deny S3 buckets with public access

package terraform.aws.s3
import rego.v1
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" resource.change.after.acl == "public-read" msg := sprintf( "S3 bucket '%s' has public-read ACL. All buckets must be private. [Policy: no-public-s3]", [resource.address] ) }
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" resource.change.after.acl == "public-read-write" msg := sprintf( "S3 bucket '%s' has public-read-write ACL. This is strictly prohibited. [Policy: no-public-s3]", [resource.address] ) }

```rego
package terraform.aws.s3
import rego.v1
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" resource.change.after.acl == "public-read" msg := sprintf( "S3 bucket '%s' has public-read ACL. All buckets must be private. [Policy: no-public-s3]", [resource.address] ) }
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" resource.change.after.acl == "public-read-write" msg := sprintf( "S3 bucket '%s' has public-read-write ACL. This is strictly prohibited. [Policy: no-public-s3]", [resource.address] ) }

```rego

require_encryption.rego - Require encryption on data stores

require_encryption.rego - Require encryption on data stores

package terraform.aws.encryption
import rego.v1
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_db_instance" not resource.change.after.storage_encrypted msg := sprintf( "RDS instance '%s' does not have storage encryption enabled. [Policy: require-rds-encryption]", [resource.address] ) }
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_ebs_volume" not resource.change.after.encrypted msg := sprintf( "EBS volume '%s' is not encrypted. [Policy: require-ebs-encryption]", [resource.address] ) }
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" not has_encryption(resource) msg := sprintf( "S3 bucket '%s' does not have default encryption configured. [Policy: require-s3-encryption]", [resource.address] ) }
has_encryption(resource) if { resource.change.after.server_side_encryption_configuration[_] }

```rego
package terraform.aws.encryption
import rego.v1
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_db_instance" not resource.change.after.storage_encrypted msg := sprintf( "RDS instance '%s' does not have storage encryption enabled. [Policy: require-rds-encryption]", [resource.address] ) }
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_ebs_volume" not resource.change.after.encrypted msg := sprintf( "EBS volume '%s' is not encrypted. [Policy: require-ebs-encryption]", [resource.address] ) }
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" not has_encryption(resource) msg := sprintf( "S3 bucket '%s' does not have default encryption configured. [Policy: require-s3-encryption]", [resource.address] ) }
has_encryption(resource) if { resource.change.after.server_side_encryption_configuration[_] }

```rego

require_tags.rego - Enforce mandatory tagging

require_tags.rego - Enforce mandatory tagging

package terraform.aws.tags
import rego.v1
required_tags := {"Environment", "Owner", "CostCenter", "DataClassification"}
deny contains msg if { resource := input.resource_changes[_] tags := object.get(resource.change.after, "tags", {}) missing := required_tags - {key | tags[key]} count(missing) > 0 msg := sprintf( "Resource '%s' is missing required tags: %v. [Policy: required-tags]", [resource.address, missing] ) }

```rego
package terraform.aws.tags
import rego.v1
required_tags := {"Environment", "Owner", "CostCenter", "DataClassification"}
deny contains msg if { resource := input.resource_changes[_] tags := object.get(resource.change.after, "tags", {}) missing := required_tags - {key | tags[key]} count(missing) > 0 msg := sprintf( "Resource '%s' is missing required tags: %v. [Policy: required-tags]", [resource.address, missing] ) }

```rego

restrict_regions.rego - Limit resource deployment to approved regions

restrict_regions.rego - Limit resource deployment to approved regions

package terraform.aws.regions
import rego.v1
approved_regions := {"us-east-1", "us-west-2", "eu-west-1"}
deny contains msg if { resource := input.resource_changes[_] provider_config := input.configuration.provider_config.aws region := provider_config.expressions.region.constant_value not region in approved_regions msg := sprintf( "Resource '%s' is in region '%s'. Approved regions: %v. [Policy: approved-regions]", [resource.address, region, approved_regions] ) }

```bash
package terraform.aws.regions
import rego.v1
approved_regions := {"us-east-1", "us-west-2", "eu-west-1"}
deny contains msg if { resource := input.resource_changes[_] provider_config := input.configuration.provider_config.aws region := provider_config.expressions.region.constant_value not region in approved_regions msg := sprintf( "Resource '%s' is in region '%s'. Approved regions: %v. [Policy: approved-regions]", [resource.address, region, approved_regions] ) }

```bash

Evaluate OPA policies against Terraform plan

Evaluate OPA policies against Terraform plan

terraform plan -out=tfplan terraform show -json tfplan > tfplan.json
terraform plan -out=tfplan terraform show -json tfplan > tfplan.json

Run OPA evaluation

Run OPA evaluation

opa eval
--data policies/
--input tfplan.json
"data.terraform.aws.s3.deny"
--format pretty
opa eval
--data policies/
--input tfplan.json
"data.terraform.aws.s3.deny"
--format pretty

Use conftest for easier CI integration

Use conftest for easier CI integration

conftest test tfplan.json --policy policies/ --output table
undefined
conftest test tfplan.json --policy policies/ --output table
undefined

Kyverno Kubernetes Policies

Kyverno Kubernetes Policies

yaml
undefined
yaml
undefined

require-labels.yaml - Enforce required labels on all pods

require-labels.yaml - Enforce required labels on all pods

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-labels annotations: policies.kyverno.io/title: Require Labels policies.kyverno.io/category: Best Practices policies.kyverno.io/severity: medium spec: validationFailureAction: Enforce background: true rules:
  • name: check-required-labels match: any:
    • resources: kinds:
      • Pod validate: message: >- Labels 'app.kubernetes.io/name', 'app.kubernetes.io/version', and 'team' are required on all Pods. pattern: metadata: labels: app.kubernetes.io/name: "?" app.kubernetes.io/version: "?" team: "?*"

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-labels annotations: policies.kyverno.io/title: Require Labels policies.kyverno.io/category: Best Practices policies.kyverno.io/severity: medium spec: validationFailureAction: Enforce background: true rules:
  • name: check-required-labels match: any:
    • resources: kinds:
      • Pod validate: message: >- Labels 'app.kubernetes.io/name', 'app.kubernetes.io/version', and 'team' are required on all Pods. pattern: metadata: labels: app.kubernetes.io/name: "?" app.kubernetes.io/version: "?" team: "?*"

disallow-privileged.yaml - Block privileged containers

disallow-privileged.yaml - Block privileged containers

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-privileged-containers annotations: policies.kyverno.io/title: Disallow Privileged Containers policies.kyverno.io/category: Pod Security policies.kyverno.io/severity: high spec: validationFailureAction: Enforce background: true rules:
  • name: deny-privileged match: any:
    • resources: kinds:
      • Pod validate: message: "Privileged containers are not allowed." pattern: spec: containers:
      • securityContext: privileged: "false" =(initContainers):
      • securityContext: privileged: "false"

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-privileged-containers annotations: policies.kyverno.io/title: Disallow Privileged Containers policies.kyverno.io/category: Pod Security policies.kyverno.io/severity: high spec: validationFailureAction: Enforce background: true rules:
  • name: deny-privileged match: any:
    • resources: kinds:
      • Pod validate: message: "Privileged containers are not allowed." pattern: spec: containers:
      • securityContext: privileged: "false" =(initContainers):
      • securityContext: privileged: "false"

require-resource-limits.yaml - Enforce resource limits

require-resource-limits.yaml - Enforce resource limits

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-resource-limits annotations: policies.kyverno.io/title: Require Resource Limits policies.kyverno.io/severity: medium spec: validationFailureAction: Enforce background: true rules:
  • name: check-resource-limits match: any:
    • resources: kinds:
      • Pod validate: message: "All containers must have CPU and memory limits defined." pattern: spec: containers:
      • resources: limits: memory: "?" cpu: "?"

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-resource-limits annotations: policies.kyverno.io/title: Require Resource Limits policies.kyverno.io/severity: medium spec: validationFailureAction: Enforce background: true rules:
  • name: check-resource-limits match: any:
    • resources: kinds:
      • Pod validate: message: "All containers must have CPU and memory limits defined." pattern: spec: containers:
      • resources: limits: memory: "?" cpu: "?"

disallow-latest-tag.yaml - Block usage of 'latest' image tag

disallow-latest-tag.yaml - Block usage of 'latest' image tag

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-latest-tag annotations: policies.kyverno.io/title: Disallow Latest Tag policies.kyverno.io/severity: medium spec: validationFailureAction: Enforce background: true rules:
  • name: validate-image-tag match: any:
    • resources: kinds:
      • Pod validate: message: "Images must use a specific tag, not 'latest'." pattern: spec: containers:
      • image: "!*:latest & :"

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-latest-tag annotations: policies.kyverno.io/title: Disallow Latest Tag policies.kyverno.io/severity: medium spec: validationFailureAction: Enforce background: true rules:
  • name: validate-image-tag match: any:
    • resources: kinds:
      • Pod validate: message: "Images must use a specific tag, not 'latest'." pattern: spec: containers:
      • image: "!*:latest & :"

restrict-image-registries.yaml - Allow only approved registries

restrict-image-registries.yaml - Allow only approved registries

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: restrict-image-registries annotations: policies.kyverno.io/title: Restrict Image Registries policies.kyverno.io/severity: high spec: validationFailureAction: Enforce background: true rules:
  • name: validate-registries match: any:
    • resources: kinds:
      • Pod validate: message: >- Images must come from approved registries: 123456789012.dkr.ecr.us-east-1.amazonaws.com or ghcr.io/your-org. pattern: spec: containers:
      • image: "123456789012.dkr.ecr..amazonaws.com/ | ghcr.io/your-org/*"

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: restrict-image-registries annotations: policies.kyverno.io/title: Restrict Image Registries policies.kyverno.io/severity: high spec: validationFailureAction: Enforce background: true rules:
  • name: validate-registries match: any:
    • resources: kinds:
      • Pod validate: message: >- Images must come from approved registries: 123456789012.dkr.ecr.us-east-1.amazonaws.com or ghcr.io/your-org. pattern: spec: containers:
      • image: "123456789012.dkr.ecr..amazonaws.com/ | ghcr.io/your-org/*"

require-networkpolicy.yaml - Ensure namespaces have NetworkPolicies

require-networkpolicy.yaml - Ensure namespaces have NetworkPolicies

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-networkpolicy annotations: policies.kyverno.io/title: Require Network Policy policies.kyverno.io/severity: high spec: validationFailureAction: Audit background: true rules:
  • name: check-networkpolicy match: any:
    • resources: kinds:
      • Deployment preconditions: all:
    • key: "{{request.object.metadata.namespace}}" operator: NotIn value: ["kube-system", "kube-public"] validate: message: "A NetworkPolicy must exist in namespace '{{request.object.metadata.namespace}}' before deploying workloads." deny: conditions: all:
      • key: "{{request.object.metadata.namespace}}" operator: AnyNotIn value: "{{request.object.metadata.namespace}}"
undefined
apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-networkpolicy annotations: policies.kyverno.io/title: Require Network Policy policies.kyverno.io/severity: high spec: validationFailureAction: Audit background: true rules:
  • name: check-networkpolicy match: any:
    • resources: kinds:
      • Deployment preconditions: all:
    • key: "{{request.object.metadata.namespace}}" operator: NotIn value: ["kube-system", "kube-public"] validate: message: "A NetworkPolicy must exist in namespace '{{request.object.metadata.namespace}}' before deploying workloads." deny: conditions: all:
      • key: "{{request.object.metadata.namespace}}" operator: AnyNotIn value: "{{request.object.metadata.namespace}}"
undefined

Checkov Custom Checks

Checkov Custom Checks

python
undefined
python
undefined

custom_checks/require_s3_versioning.py

custom_checks/require_s3_versioning.py

from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck from checkov.common.models.enums import CheckResult, CheckCategories
class S3Versioning(BaseResourceCheck): def init(self): name = "Ensure S3 bucket has versioning enabled" id = "CUSTOM_S3_001" supported_resources = ["aws_s3_bucket_versioning"] categories = [CheckCategories.BACKUP_AND_RECOVERY] super().init(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
    versioning = conf.get("versioning_configuration", [{}])
    if isinstance(versioning, list):
        versioning = versioning[0] if versioning else {}
    status = versioning.get("status", ["Disabled"])
    if isinstance(status, list):
        status = status[0]
    return CheckResult.PASSED if status == "Enabled" else CheckResult.FAILED
check = S3Versioning()

```python
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck from checkov.common.models.enums import CheckResult, CheckCategories
class S3Versioning(BaseResourceCheck): def init(self): name = "Ensure S3 bucket has versioning enabled" id = "CUSTOM_S3_001" supported_resources = ["aws_s3_bucket_versioning"] categories = [CheckCategories.BACKUP_AND_RECOVERY] super().init(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
    versioning = conf.get("versioning_configuration", [{}])
    if isinstance(versioning, list):
        versioning = versioning[0] if versioning else {}
    status = versioning.get("status", ["Disabled"])
    if isinstance(status, list):
        status = status[0]
    return CheckResult.PASSED if status == "Enabled" else CheckResult.FAILED
check = S3Versioning()

```python

custom_checks/require_rds_backup.py

custom_checks/require_rds_backup.py

from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck from checkov.common.models.enums import CheckResult, CheckCategories
class RDSBackupRetention(BaseResourceCheck): def init(self): name = "Ensure RDS has backup retention of at least 7 days" id = "CUSTOM_RDS_001" supported_resources = ["aws_db_instance"] categories = [CheckCategories.BACKUP_AND_RECOVERY] super().init(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
    retention = conf.get("backup_retention_period", [0])
    if isinstance(retention, list):
        retention = retention[0]
    return CheckResult.PASSED if int(retention) >= 7 else CheckResult.FAILED
check = RDSBackupRetention()

```bash
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck from checkov.common.models.enums import CheckResult, CheckCategories
class RDSBackupRetention(BaseResourceCheck): def init(self): name = "Ensure RDS has backup retention of at least 7 days" id = "CUSTOM_RDS_001" supported_resources = ["aws_db_instance"] categories = [CheckCategories.BACKUP_AND_RECOVERY] super().init(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
    retention = conf.get("backup_retention_period", [0])
    if isinstance(retention, list):
        retention = retention[0]
    return CheckResult.PASSED if int(retention) >= 7 else CheckResult.FAILED
check = RDSBackupRetention()

```bash

Run Checkov with custom checks

Run Checkov with custom checks

checkov -d ./terraform
--framework terraform
--external-checks-dir ./custom_checks
--output cli
--compact
checkov -d ./terraform
--framework terraform
--external-checks-dir ./custom_checks
--output cli
--compact

Run specific check IDs

Run specific check IDs

checkov -d ./terraform
--check CUSTOM_S3_001,CUSTOM_RDS_001,CKV_AWS_18,CKV_AWS_19
checkov -d ./terraform
--check CUSTOM_S3_001,CUSTOM_RDS_001,CKV_AWS_18,CKV_AWS_19

Generate SARIF output for GitHub Advanced Security integration

Generate SARIF output for GitHub Advanced Security integration

checkov -d ./terraform
--framework terraform
--output sarif
--output-file checkov-results.sarif
checkov -d ./terraform
--framework terraform
--output sarif
--output-file checkov-results.sarif

Skip specific checks with documented justification

Skip specific checks with documented justification

checkov -d ./terraform
--skip-check CKV_AWS_145
--skip-check CKV_AWS_79
undefined
checkov -d ./terraform
--skip-check CKV_AWS_145
--skip-check CKV_AWS_79
undefined

CI/CD Pipeline Integration

CI/CD Pipeline Integration

yaml
undefined
yaml
undefined

GitHub Actions - Policy enforcement in PR workflow

GitHub Actions - Policy enforcement in PR workflow

name: Policy Checks on: pull_request: paths: - 'terraform/' - 'kubernetes/'
jobs: terraform-policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Setup Terraform
    uses: hashicorp/setup-terraform@v3

  - name: Terraform Init and Plan
    working-directory: terraform/
    run: |
      terraform init -backend=false
      terraform plan -out=tfplan
      terraform show -json tfplan > tfplan.json

  - name: OPA Policy Check
    uses: open-policy-agent/setup-opa@v2
    with:
      version: latest
  - run: |
      RESULTS=$(opa eval \
        --data policies/ \
        --input terraform/tfplan.json \
        --format json \
        "data.terraform" | jq '.result[0].expressions[0].value')
      DENY_COUNT=$(echo "$RESULTS" | jq '[.. | .deny? // empty | .[] ] | length')
      if [ "$DENY_COUNT" -gt 0 ]; then
        echo "::error::Policy violations found:"
        echo "$RESULTS" | jq '.. | .deny? // empty | .[]'
        exit 1
      fi

  - name: Checkov Scan
    uses: bridgecrewio/checkov-action@v12
    with:
      directory: terraform/
      framework: terraform
      output_format: cli,sarif
      output_file_path: console,checkov-results.sarif
      soft_fail: false
      external_checks_dirs: custom_checks/

  - name: Upload SARIF
    if: always()
    uses: github/codeql-action/upload-sarif@v3
    with:
      sarif_file: checkov-results.sarif
kubernetes-policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Install Kyverno CLI
    run: |
      curl -LO https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_amd64.tar.gz
      tar -xzf kyverno-cli_linux_amd64.tar.gz
      sudo mv kyverno /usr/local/bin/

  - name: Test Kyverno Policies
    run: |
      kyverno apply policies/kyverno/ \
        --resource kubernetes/manifests/ \
        --detailed-results \
        --output-format table

  - name: Conftest Kubernetes Manifests
    uses: open-policy-agent/conftest-action@v2
    with:
      files: kubernetes/manifests/
      policy: policies/kubernetes/
undefined
name: Policy Checks on: pull_request: paths: - 'terraform/' - 'kubernetes/'
jobs: terraform-policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Setup Terraform
    uses: hashicorp/setup-terraform@v3

  - name: Terraform Init and Plan
    working-directory: terraform/
    run: |
      terraform init -backend=false
      terraform plan -out=tfplan
      terraform show -json tfplan > tfplan.json

  - name: OPA Policy Check
    uses: open-policy-agent/setup-opa@v2
    with:
      version: latest
  - run: |
      RESULTS=$(opa eval \
        --data policies/ \
        --input terraform/tfplan.json \
        --format json \
        "data.terraform" | jq '.result[0].expressions[0].value')
      DENY_COUNT=$(echo "$RESULTS" | jq '[.. | .deny? // empty | .[] ] | length')
      if [ "$DENY_COUNT" -gt 0 ]; then
        echo "::error::Policy violations found:"
        echo "$RESULTS" | jq '.. | .deny? // empty | .[]'
        exit 1
      fi

  - name: Checkov Scan
    uses: bridgecrewio/checkov-action@v12
    with:
      directory: terraform/
      framework: terraform
      output_format: cli,sarif
      output_file_path: console,checkov-results.sarif
      soft_fail: false
      external_checks_dirs: custom_checks/

  - name: Upload SARIF
    if: always()
    uses: github/codeql-action/upload-sarif@v3
    with:
      sarif_file: checkov-results.sarif
kubernetes-policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Install Kyverno CLI
    run: |
      curl -LO https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_amd64.tar.gz
      tar -xzf kyverno-cli_linux_amd64.tar.gz
      sudo mv kyverno /usr/local/bin/

  - name: Test Kyverno Policies
    run: |
      kyverno apply policies/kyverno/ \
        --resource kubernetes/manifests/ \
        --detailed-results \
        --output-format table

  - name: Conftest Kubernetes Manifests
    uses: open-policy-agent/conftest-action@v2
    with:
      files: kubernetes/manifests/
      policy: policies/kubernetes/
undefined

Policy Exception Management

Policy Exception Management

yaml
exception_workflow:
  request:
    fields:
      - policy_id: "Which policy needs an exception"
      - resource: "Specific resource requiring exception"
      - justification: "Business reason for the exception"
      - compensating_controls: "Alternative mitigations in place"
      - duration: "Temporary (with expiry) or permanent"
      - requestor: "Person requesting"
      - approver: "Security team member who approved"

  approval_process:
    1: "Requestor submits exception with justification"
    2: "Security team reviews and assesses risk"
    3: "Compensating controls verified"
    4: "Exception approved or denied with rationale"
    5: "Exception documented in registry"
    6: "Automated enforcement updated to allow exception"

  enforcement:
    opa: |
      # Exception list loaded as data
      # policies/exceptions.json
      # {"exceptions": [{"resource": "aws_s3_bucket.public_website", "policy": "no-public-s3", "expires": "2025-06-01"}]}
    kyverno: |
      # Use Kyverno PolicyException resource
      apiVersion: kyverno.io/v2beta1
      kind: PolicyException
      metadata:
        name: allow-public-website
        namespace: web
      spec:
        exceptions:
        - policyName: disallow-privileged-containers
          ruleNames:
          - deny-privileged
        match:
          any:
          - resources:
              kinds:
              - Pod
              names:
              - legacy-app-*

  review_schedule:
    - Review all active exceptions quarterly
    - Expire temporary exceptions automatically
    - Re-justify permanent exceptions annually
    - Track exception count trends as a security metric
yaml
exception_workflow:
  request:
    fields:
      - policy_id: "Which policy needs an exception"
      - resource: "Specific resource requiring exception"
      - justification: "Business reason for the exception"
      - compensating_controls: "Alternative mitigations in place"
      - duration: "Temporary (with expiry) or permanent"
      - requestor: "Person requesting"
      - approver: "Security team member who approved"

  approval_process:
    1: "Requestor submits exception with justification"
    2: "Security team reviews and assesses risk"
    3: "Compensating controls verified"
    4: "Exception approved or denied with rationale"
    5: "Exception documented in registry"
    6: "Automated enforcement updated to allow exception"

  enforcement:
    opa: |
      # Exception list loaded as data
      # policies/exceptions.json
      # {"exceptions": [{"resource": "aws_s3_bucket.public_website", "policy": "no-public-s3", "expires": "2025-06-01"}]}
    kyverno: |
      # Use Kyverno PolicyException resource
      apiVersion: kyverno.io/v2beta1
      kind: PolicyException
      metadata:
        name: allow-public-website
        namespace: web
      spec:
        exceptions:
        - policyName: disallow-privileged-containers
          ruleNames:
          - deny-privileged
        match:
          any:
          - resources:
              kinds:
              - Pod
              names:
              - legacy-app-*

  review_schedule:
    - Review all active exceptions quarterly
    - Expire temporary exceptions automatically
    - Re-justify permanent exceptions annually
    - Track exception count trends as a security metric

Policy Testing

Policy Testing

bash
undefined
bash
undefined

Test OPA policies with mock input

Test OPA policies with mock input

mkdir -p policies/tests
mkdir -p policies/tests

Create test input

Create test input

cat > policies/tests/public_bucket_test.json <<'EOF' { "resource_changes": [{ "address": "aws_s3_bucket.test", "type": "aws_s3_bucket", "change": { "after": {"acl": "public-read"} } }] } EOF
cat > policies/tests/public_bucket_test.json <<'EOF' { "resource_changes": [{ "address": "aws_s3_bucket.test", "type": "aws_s3_bucket", "change": { "after": {"acl": "public-read"} } }] } EOF

Run test

Run test

opa eval --data policies/ --input policies/tests/public_bucket_test.json
"data.terraform.aws.s3.deny" --format pretty
opa eval --data policies/ --input policies/tests/public_bucket_test.json
"data.terraform.aws.s3.deny" --format pretty

Should output the deny message

Should output the deny message

OPA unit tests

OPA unit tests

cat > policies/tests/s3_test.rego <<'EOF' package terraform.aws.s3_test
import rego.v1 import data.terraform.aws.s3
test_deny_public_bucket if { result := s3.deny with input as {"resource_changes": [{"address": "test", "type": "aws_s3_bucket", "change": {"after": {"acl": "public-read"}}}]} count(result) > 0 }
test_allow_private_bucket if { result := s3.deny with input as {"resource_changes": [{"address": "test", "type": "aws_s3_bucket", "change": {"after": {"acl": "private"}}}]} count(result) == 0 } EOF
opa test policies/ -v
undefined
cat > policies/tests/s3_test.rego <<'EOF' package terraform.aws.s3_test
import rego.v1 import data.terraform.aws.s3
test_deny_public_bucket if { result := s3.deny with input as {"resource_changes": [{"address": "test", "type": "aws_s3_bucket", "change": {"after": {"acl": "public-read"}}}]} count(result) > 0 }
test_allow_private_bucket if { result := s3.deny with input as {"resource_changes": [{"address": "test", "type": "aws_s3_bucket", "change": {"after": {"acl": "private"}}}]} count(result) == 0 } EOF
opa test policies/ -v
undefined

Best Practices

最佳实践

  • Version control all policies alongside the infrastructure code they govern
  • Start in audit/warn mode and transition to enforce after verifying no false positives
  • Write unit tests for every policy to catch regressions and verify intended behavior
  • Implement a formal exception process: never disable policies to bypass legitimate checks
  • Use policy results as PR status checks to block non-compliant merges
  • Layer policies: Checkov for static analysis, OPA for Terraform plan evaluation, Kyverno for runtime
  • Tag policies with compliance framework references (e.g., SOC 2 CC6.1, PCI Req 2.2)
  • Monitor policy violation trends over time to identify systemic issues
  • Provide clear, actionable error messages that explain how to fix violations
  • Roll out new policies gradually: inform teams, give a remediation window, then enforce
  • 将所有策略与它们管控的基础设施代码一同纳入版本控制
  • 先以审计/警告模式运行,确认无误报后再切换至强制模式
  • 为每个策略编写单元测试,以捕获回归问题并验证预期行为
  • 实施正式的例外流程:切勿为绕过合理检查而禁用策略
  • 将策略检查结果作为PR状态检查,阻止不合规的代码合并
  • 分层部署策略:使用Checkov进行静态分析,OPA评估Terraform计划,Kyverno负责运行时管控
  • 为策略添加合规框架关联标签(如SOC 2 CC6.1、PCI Req 2.2)
  • 监控策略违规趋势,识别系统性问题
  • 提供清晰、可操作的错误信息,说明如何修复违规问题
  • 逐步推广新策略:通知相关团队,给予整改窗口期,再执行强制管控