
Policy As Code
- 86 installs
- 44 repo stars
- Updated May 22, 2026
- bagelhole/devops-security-agent-skills
Policy as Code is a Claude Code skill that enforces security and compliance policies on infrastructure-as-code using OPA/Rego, Kyverno, and Checkov in CI/CD pipelines.
About
This skill implements policy-as-code with Open Policy Agent (Rego), Kyverno, and Checkov to enforce compliance on infrastructure-as-code changes. A developer uses it to block misconfigured Kubernetes workloads and Terraform resources in CI/CD before they deploy. It replaces manual approval gates with automated policy checks.
- OPA/Rego, Kyverno, and Checkov policies for Terraform and Kubernetes
- Runs as automated guardrails in CI/CD via conftest and opa eval
- Denies public S3, unencrypted stores, missing tags, and privileged containers
Policy As Code by the numbers
- 86 all-time installs (skills.sh)
- Ranked #574 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
policy-as-code capabilities & compatibility
- Capabilities
- security audit · ci cd · devops
- Works with
- terraform · kubernetes · aws · github
- Use cases
- security audit · ci cd · devops
- Pricing
- Free
What policy-as-code says it does
Automate policy enforcement through code using OPA/Rego, Kyverno, Checkov, and CI/CD integration to prevent compliance violations before they reach production.
Preventing misconfigured Kubernetes workloads from deploying
Replacing manual approval gates with automated policy checks
npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill policy-as-codeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 86 |
|---|---|
| repo stars | ★ 44 |
| Last updated | May 22, 2026 |
| Repository | bagelhole/devops-security-agent-skills ↗ |
What it does
Enforce security and compliance policies on Terraform and Kubernetes changes automatically inside a CI/CD pipeline.
Who is it for?
Teams enforcing organizational infrastructure standards through automated policy checks in CI/CD.
Skip if: Runtime application-layer security or scanning source code for code vulnerabilities.
When should I use this skill?
You need to prevent non-compliant Terraform or Kubernetes changes from deploying.
What you get
Non-compliant infrastructure changes are denied automatically before deploy.
- Rego deny policies
- Kyverno ClusterPolicy manifests
- CI/CD policy-check step
By the numbers
- 4 approved AWS regions allow-listed in the sample restrict_regions policy
- 4 mandatory tags enforced (Environment, Owner, CostCenter, DataClassification)
Files
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.
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
Open Policy Agent (OPA) Rego Policies
# 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]
)
}# 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[_]
}# 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]
)
}# 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]
)
}# Evaluate OPA policies against Terraform plan
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# Run OPA evaluation
opa eval \
--data policies/ \
--input tfplan.json \
"data.terraform.aws.s3.deny" \
--format pretty
# Use conftest for easier CI integration
conftest test tfplan.json --policy policies/ --output tableKyverno Kubernetes Policies
# 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: "?*"
---
# 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"
---
# 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: "?*"
---
# 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 & *:*"
---
# 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/*"
---
# 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}}"Checkov Custom Checks
# 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()# 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()# Run Checkov with custom checks
checkov -d ./terraform \
--framework terraform \
--external-checks-dir ./custom_checks \
--output cli \
--compact
# Run specific check IDs
checkov -d ./terraform \
--check CUSTOM_S3_001,CUSTOM_RDS_001,CKV_AWS_18,CKV_AWS_19
# Generate SARIF output for GitHub Advanced Security integration
checkov -d ./terraform \
--framework terraform \
--output sarif \
--output-file checkov-results.sarif
# Skip specific checks with documented justification
checkov -d ./terraform \
--skip-check CKV_AWS_145 \
--skip-check CKV_AWS_79CI/CD Pipeline Integration
# 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/Policy Exception Management
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 metricPolicy Testing
# Test OPA policies with mock input
mkdir -p policies/tests
# 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
# Run test
opa eval --data policies/ --input policies/tests/public_bucket_test.json \
"data.terraform.aws.s3.deny" --format pretty
# Should output the deny message
# 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/ -vBest 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
Related skills
FAQ
Which policy engines does this skill cover?
Open Policy Agent (Rego), Kyverno for Kubernetes, and Checkov, evaluated via opa eval and conftest.
How does it run in CI?
It converts a Terraform plan to JSON and runs opa eval or conftest test against a policies directory.