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流程,以代码方式自动化策略执行,在违规内容进入生产环境前阻止合规风险。
- 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
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]
)
}
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]
)
}
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[_]
}
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[_]
}
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]
)
}
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]
)
}
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]
)
}
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]
)
}
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
conftest test tfplan.json --policy policies/ --output table
Kyverno Kubernetes Policies
Kyverno Kubernetes Policies
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}}"
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}}"
Checkov Custom Checks
Checkov Custom Checks
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()
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()
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()
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()
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
checkov -d ./terraform
--skip-check CKV_AWS_145
--skip-check CKV_AWS_79
CI/CD Pipeline Integration
CI/CD Pipeline Integration
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/
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/
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
Test OPA policies with mock input
Test OPA policies with mock input
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
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
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
- 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)
- 监控策略违规趋势,识别系统性问题
- 提供清晰、可操作的错误信息,说明如何修复违规问题
- 逐步推广新策略:通知相关团队,给予整改窗口期,再执行强制管控