Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:02 +08:00
commit ff1f4bd119
252 changed files with 72682 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
# Assets Directory
Place files that will be used in the output Claude produces:
- Templates
- Configuration files
- Images/logos
- Boilerplate code
These files are NOT loaded into context but copied/modified in output.

View File

@@ -0,0 +1,234 @@
# GitHub Actions CI/CD Pipeline with OPA Policy Validation
name: OPA Policy Validation
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
# Test OPA policies with unit tests
test-policies:
name: Test OPA Policies
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
with:
version: latest
- name: Run Policy Tests
run: |
opa test policies/ --verbose --coverage
opa test policies/ --coverage --format=json > coverage.json
- name: Check Coverage Threshold
run: |
COVERAGE=$(jq -r '.coverage' coverage.json | awk '{print int($1)}')
if [ "$COVERAGE" -lt 80 ]; then
echo "Coverage $COVERAGE% is below threshold 80%"
exit 1
fi
echo "Coverage: $COVERAGE%"
# Validate Kubernetes manifests
validate-kubernetes:
name: Validate Kubernetes Configs
runs-on: ubuntu-latest
needs: test-policies
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
- name: Validate Kubernetes Manifests
run: |
for file in k8s/**/*.yaml; do
echo "Validating $file"
opa eval --data policies/ --input "$file" \
--format pretty 'data.kubernetes.admission.deny' \
> violations.txt
if [ -s violations.txt ]; then
echo "Policy violations found in $file:"
cat violations.txt
exit 1
fi
done
- name: Generate Validation Report
if: always()
run: |
./scripts/generate_report.py \
--policy policies/ \
--audit-logs violations.json \
--format html \
--output validation-report.html
- name: Upload Report
if: always()
uses: actions/upload-artifact@v3
with:
name: validation-report
path: validation-report.html
# Validate Terraform configurations
validate-terraform:
name: Validate Terraform Configs
runs-on: ubuntu-latest
needs: test-policies
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
- name: Terraform Init
run: terraform init
- name: Generate Terraform Plan
run: |
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
- name: Validate with OPA
run: |
opa eval --data policies/terraform/ --input tfplan.json \
--format pretty 'data.terraform.security.deny' \
> terraform-violations.json
if [ -s terraform-violations.json ]; then
echo "Terraform policy violations detected:"
cat terraform-violations.json
exit 1
fi
# Compliance validation for production
compliance-check:
name: Compliance Validation
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: [validate-kubernetes, validate-terraform]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
- name: SOC2 Compliance Check
run: |
opa eval --data policies/compliance/soc2-compliance.rego \
--input deployments/ \
--format json 'data.compliance.soc2.deny' \
> soc2-violations.json
- name: PCI-DSS Compliance Check
run: |
opa eval --data policies/compliance/pci-dss-compliance.rego \
--input deployments/ \
--format json 'data.compliance.pci.deny' \
> pci-violations.json
- name: GDPR Compliance Check
run: |
opa eval --data policies/compliance/gdpr-compliance.rego \
--input deployments/ \
--format json 'data.compliance.gdpr.deny' \
> gdpr-violations.json
- name: Generate Compliance Report
run: |
./scripts/generate_report.py \
--policy policies/compliance/ \
--audit-logs soc2-violations.json \
--format html \
--output compliance-report.html
- name: Upload Compliance Report
uses: actions/upload-artifact@v3
with:
name: compliance-report
path: compliance-report.html
- name: Fail on Violations
run: |
TOTAL_VIOLATIONS=$(cat *-violations.json | jq -s 'map(length) | add')
if [ "$TOTAL_VIOLATIONS" -gt 0 ]; then
echo "Found $TOTAL_VIOLATIONS compliance violations"
exit 1
fi
---
# GitLab CI/CD Pipeline Example
# .gitlab-ci.yml
stages:
- test
- validate
- compliance
variables:
OPA_VERSION: "latest"
test-policies:
stage: test
image: openpolicyagent/opa:${OPA_VERSION}
script:
- opa test policies/ --verbose --coverage
- opa test policies/ --format=json --coverage > coverage.json
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.json
validate-kubernetes:
stage: validate
image: openpolicyagent/opa:${OPA_VERSION}
script:
- |
for file in k8s/**/*.yaml; do
opa eval --data policies/ --input "$file" \
'data.kubernetes.admission.deny' || exit 1
done
only:
- merge_requests
- main
validate-terraform:
stage: validate
image: hashicorp/terraform:latest
before_script:
- apk add --no-cache curl jq
- curl -L -o /usr/local/bin/opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
- chmod +x /usr/local/bin/opa
script:
- terraform init
- terraform plan -out=tfplan.binary
- terraform show -json tfplan.binary > tfplan.json
- opa eval --data policies/terraform/ --input tfplan.json 'data.terraform.security.deny'
only:
- merge_requests
- main
compliance-check:
stage: compliance
image: openpolicyagent/opa:${OPA_VERSION}
script:
- opa eval --data policies/compliance/ --input deployments/ 'data.compliance'
artifacts:
reports:
junit: compliance-report.xml
only:
- main

View File

@@ -0,0 +1,159 @@
package compliance.gdpr
import future.keywords.if
# GDPR Article 25: Data Protection by Design and by Default
# Require data classification labels
deny[msg] {
input.kind == "Deployment"
processes_personal_data(input)
not input.metadata.labels["data-classification"]
msg := {
"control": "GDPR Article 25",
"severity": "high",
"violation": sprintf("Deployment processing personal data requires classification: %v", [input.metadata.name]),
"remediation": "Add label: data-classification=personal|sensitive|public",
}
}
# Data minimization - limit replicas for personal data
deny[msg] {
input.kind == "Deployment"
input.metadata.labels["data-type"] == "personal"
input.spec.replicas > 3
not input.metadata.annotations["gdpr.justification"]
msg := {
"control": "GDPR Article 25",
"severity": "medium",
"violation": sprintf("Excessive replicas for personal data: %v", [input.metadata.name]),
"remediation": "Reduce replicas or add justification annotation",
}
}
# Require purpose limitation annotation
deny[msg] {
input.kind == "Deployment"
processes_personal_data(input)
not input.metadata.annotations["data-purpose"]
msg := {
"control": "GDPR Article 25",
"severity": "medium",
"violation": sprintf("Personal data deployment requires purpose annotation: %v", [input.metadata.name]),
"remediation": "Add annotation: data-purpose=<specific purpose>",
}
}
processes_personal_data(resource) {
resource.metadata.labels["data-type"] == "personal"
}
processes_personal_data(resource) {
resource.metadata.labels["data-type"] == "pii"
}
processes_personal_data(resource) {
contains(lower(resource.metadata.name), "user")
}
# GDPR Article 32: Security of Processing
# Require encryption for personal data volumes
deny[msg] {
input.kind == "PersistentVolumeClaim"
input.metadata.labels["data-type"] == "personal"
not input.metadata.annotations["volume.encryption.enabled"] == "true"
msg := {
"control": "GDPR Article 32",
"severity": "high",
"violation": sprintf("Personal data volume requires encryption: %v", [input.metadata.name]),
"remediation": "Enable volume encryption",
}
}
# Require TLS for personal data services
deny[msg] {
input.kind == "Service"
input.metadata.labels["data-type"] == "personal"
not input.metadata.annotations["tls.enabled"] == "true"
msg := {
"control": "GDPR Article 32",
"severity": "high",
"violation": sprintf("Personal data service requires TLS: %v", [input.metadata.name]),
"remediation": "Enable TLS encryption",
}
}
# Require pseudonymization or anonymization
deny[msg] {
input.kind == "Deployment"
processes_personal_data(input)
not input.metadata.annotations["data-protection.method"]
msg := {
"control": "GDPR Article 32",
"severity": "medium",
"violation": sprintf("Personal data deployment requires protection method: %v", [input.metadata.name]),
"remediation": "Add annotation: data-protection.method=pseudonymization|anonymization|encryption",
}
}
# GDPR Article 33: Breach Notification
# Require incident response plan
deny[msg] {
input.kind == "Deployment"
processes_personal_data(input)
input.metadata.namespace == "production"
not input.metadata.annotations["incident-response.plan"]
msg := {
"control": "GDPR Article 33",
"severity": "medium",
"violation": sprintf("Production personal data deployment requires incident response plan: %v", [input.metadata.name]),
"remediation": "Add annotation: incident-response.plan=<plan-id>",
}
}
# GDPR Article 30: Records of Processing Activities
# Require data processing record
deny[msg] {
input.kind == "Deployment"
processes_personal_data(input)
not input.metadata.annotations["dpa.record-id"]
msg := {
"control": "GDPR Article 30",
"severity": "medium",
"violation": sprintf("Personal data deployment requires processing record: %v", [input.metadata.name]),
"remediation": "Add annotation: dpa.record-id=<record-id>",
}
}
# GDPR Article 35: Data Protection Impact Assessment (DPIA)
# Require DPIA for high-risk processing
deny[msg] {
input.kind == "Deployment"
input.metadata.labels["data-type"] == "sensitive"
not input.metadata.annotations["dpia.reference"]
msg := {
"control": "GDPR Article 35",
"severity": "high",
"violation": sprintf("Sensitive data deployment requires DPIA: %v", [input.metadata.name]),
"remediation": "Conduct DPIA and add annotation: dpia.reference=<dpia-id>",
}
}
# GDPR Article 17: Right to Erasure (Right to be Forgotten)
# Require data retention policy
deny[msg] {
input.kind == "PersistentVolumeClaim"
input.metadata.labels["data-type"] == "personal"
not input.metadata.annotations["data-retention.days"]
msg := {
"control": "GDPR Article 17",
"severity": "medium",
"violation": sprintf("Personal data volume requires retention policy: %v", [input.metadata.name]),
"remediation": "Add annotation: data-retention.days=<number>",
}
}

View File

@@ -0,0 +1,87 @@
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8spodsecurity
annotations:
description: "Enforces pod security standards including privileged containers, host namespaces, and capabilities"
spec:
crd:
spec:
names:
kind: K8sPodSecurity
validation:
openAPIV3Schema:
type: object
properties:
allowPrivileged:
type: boolean
description: "Allow privileged containers"
allowHostNamespace:
type: boolean
description: "Allow host namespace usage"
allowedCapabilities:
type: array
description: "List of allowed capabilities"
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spodsecurity
import future.keywords.contains
import future.keywords.if
violation[{"msg": msg}] {
not input.parameters.allowPrivileged
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged container not allowed: %v", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container must run as non-root: %v", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.readOnlyRootFilesystem
msg := sprintf("Container must use read-only root filesystem: %v", [container.name])
}
violation[{"msg": msg}] {
not input.parameters.allowHostNamespace
input.review.object.spec.hostPID == true
msg := "Host PID namespace not allowed"
}
violation[{"msg": msg}] {
not input.parameters.allowHostNamespace
input.review.object.spec.hostIPC == true
msg := "Host IPC namespace not allowed"
}
violation[{"msg": msg}] {
not input.parameters.allowHostNamespace
input.review.object.spec.hostNetwork == true
msg := "Host network namespace not allowed"
}
violation[{"msg": msg}] {
volume := input.review.object.spec.volumes[_]
volume.hostPath
msg := sprintf("hostPath volume not allowed: %v", [volume.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
capability := container.securityContext.capabilities.add[_]
not is_allowed_capability(capability)
msg := sprintf("Capability %v not allowed for container: %v", [capability, container.name])
}
is_allowed_capability(capability) {
input.parameters.allowedCapabilities[_] == capability
}

View File

@@ -0,0 +1,20 @@
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPodSecurity
metadata:
name: pod-security-policy
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
- "staging"
excludedNamespaces:
- "kube-system"
- "gatekeeper-system"
parameters:
allowPrivileged: false
allowHostNamespace: false
allowedCapabilities:
- "NET_BIND_SERVICE" # Allow binding to privileged ports

View File

@@ -0,0 +1,90 @@
package kubernetes.admission
import future.keywords.contains
import future.keywords.if
# Deny privileged containers
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged container is not allowed: %v", [container.name])
}
# Enforce non-root user
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container must run as non-root user: %v", [container.name])
}
# Require read-only root filesystem
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.securityContext.readOnlyRootFilesystem
msg := sprintf("Container must use read-only root filesystem: %v", [container.name])
}
# Deny host namespaces
deny[msg] {
input.request.kind.kind == "Pod"
input.request.object.spec.hostPID == true
msg := "Sharing the host PID namespace is not allowed"
}
deny[msg] {
input.request.kind.kind == "Pod"
input.request.object.spec.hostIPC == true
msg := "Sharing the host IPC namespace is not allowed"
}
deny[msg] {
input.request.kind.kind == "Pod"
input.request.object.spec.hostNetwork == true
msg := "Sharing the host network namespace is not allowed"
}
# Deny hostPath volumes
deny[msg] {
input.request.kind.kind == "Pod"
volume := input.request.object.spec.volumes[_]
volume.hostPath
msg := sprintf("hostPath volumes are not allowed: %v", [volume.name])
}
# Require dropping ALL capabilities
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not drops_all_capabilities(container)
msg := sprintf("Container must drop ALL capabilities: %v", [container.name])
}
drops_all_capabilities(container) {
container.securityContext.capabilities.drop[_] == "ALL"
}
# Deny dangerous capabilities
dangerous_capabilities := [
"CAP_SYS_ADMIN",
"CAP_NET_ADMIN",
"CAP_SYS_PTRACE",
"CAP_SYS_MODULE",
]
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
capability := container.securityContext.capabilities.add[_]
dangerous_capabilities[_] == capability
msg := sprintf("Capability %v is not allowed for container: %v", [capability, container.name])
}
# Require seccomp profile
deny[msg] {
input.request.kind.kind == "Pod"
not input.request.object.spec.securityContext.seccompProfile
msg := "Pod must define a seccomp profile"
}

View File

@@ -0,0 +1,131 @@
package compliance.pci
import future.keywords.if
# PCI-DSS Requirement 1.2: Firewall Configuration
# Require network policies for cardholder data
deny[msg] {
input.kind == "Namespace"
input.metadata.labels["pci.scope"] == "in-scope"
not input.metadata.annotations["network-policy.enabled"] == "true"
msg := {
"control": "PCI-DSS 1.2",
"severity": "high",
"violation": sprintf("PCI in-scope namespace requires network policy: %v", [input.metadata.name]),
"remediation": "Create NetworkPolicy to restrict traffic and add annotation",
}
}
# PCI-DSS Requirement 2.2: System Hardening
# Container hardening - read-only filesystem
deny[msg] {
input.kind == "Pod"
input.metadata.labels["pci.scope"] == "in-scope"
container := input.spec.containers[_]
not container.securityContext.readOnlyRootFilesystem
msg := {
"control": "PCI-DSS 2.2",
"severity": "high",
"violation": sprintf("PCI container requires read-only filesystem: %v", [container.name]),
"remediation": "Set securityContext.readOnlyRootFilesystem: true",
}
}
# Container hardening - no privilege escalation
deny[msg] {
input.kind == "Pod"
input.metadata.labels["pci.scope"] == "in-scope"
container := input.spec.containers[_]
not container.securityContext.allowPrivilegeEscalation == false
msg := {
"control": "PCI-DSS 2.2",
"severity": "high",
"violation": sprintf("PCI container allows privilege escalation: %v", [container.name]),
"remediation": "Set securityContext.allowPrivilegeEscalation: false",
}
}
# PCI-DSS Requirement 3.4: Encryption of Cardholder Data
# Require encryption for PCI data at rest
deny[msg] {
input.kind == "PersistentVolumeClaim"
input.metadata.labels["pci.scope"] == "in-scope"
not input.metadata.annotations["volume.encryption.enabled"] == "true"
msg := {
"control": "PCI-DSS 3.4",
"severity": "critical",
"violation": sprintf("PCI volume requires encryption: %v", [input.metadata.name]),
"remediation": "Enable volume encryption",
}
}
# Require TLS for PCI data in transit
deny[msg] {
input.kind == "Service"
input.metadata.labels["pci.scope"] == "in-scope"
not input.metadata.annotations["tls.enabled"] == "true"
msg := {
"control": "PCI-DSS 4.1",
"severity": "critical",
"violation": sprintf("PCI service requires TLS encryption: %v", [input.metadata.name]),
"remediation": "Enable TLS for data in transit",
}
}
# PCI-DSS Requirement 8.2.1: Strong Authentication
# Require MFA for payment endpoints
deny[msg] {
input.kind == "Ingress"
input.metadata.labels["payment.enabled"] == "true"
not input.metadata.annotations["mfa.required"] == "true"
msg := {
"control": "PCI-DSS 8.2.1",
"severity": "high",
"violation": sprintf("Payment ingress requires MFA: %v", [input.metadata.name]),
"remediation": "Enable MFA via annotation: mfa.required=true",
}
}
# PCI-DSS Requirement 10.2: Audit Logging
# Require audit logging for PCI components
deny[msg] {
input.kind == "Deployment"
input.metadata.labels["pci.scope"] == "in-scope"
not has_audit_logging(input)
msg := {
"control": "PCI-DSS 10.2",
"severity": "high",
"violation": sprintf("PCI deployment requires audit logging: %v", [input.metadata.name]),
"remediation": "Deploy audit logging sidecar or enable centralized logging",
}
}
has_audit_logging(resource) {
resource.spec.template.metadata.annotations["audit.enabled"] == "true"
}
has_audit_logging(resource) {
container := resource.spec.template.spec.containers[_]
contains(container.name, "audit")
}
# PCI-DSS Requirement 11.3: Penetration Testing
# Require security testing evidence for PCI deployments
deny[msg] {
input.kind == "Deployment"
input.metadata.labels["pci.scope"] == "in-scope"
input.metadata.namespace == "production"
not input.metadata.annotations["security-testing.date"]
msg := {
"control": "PCI-DSS 11.3",
"severity": "medium",
"violation": sprintf("PCI deployment requires security testing evidence: %v", [input.metadata.name]),
"remediation": "Add annotation: security-testing.date=YYYY-MM-DD",
}
}

View File

@@ -0,0 +1,107 @@
package compliance.soc2
import future.keywords.if
# SOC2 CC6.1: Logical and Physical Access Controls
# Deny overly permissive RBAC
deny[msg] {
input.kind == "RoleBinding"
input.roleRef.name == "cluster-admin"
not startswith(input.subjects[_].name, "system:")
msg := {
"control": "SOC2 CC6.1",
"severity": "high",
"violation": sprintf("Overly permissive cluster-admin binding: %v", [input.metadata.name]),
"remediation": "Use least-privilege roles instead of cluster-admin",
}
}
# Require authentication for external services
deny[msg] {
input.kind == "Service"
input.spec.type == "LoadBalancer"
not input.metadata.annotations["auth.required"] == "true"
msg := {
"control": "SOC2 CC6.1",
"severity": "medium",
"violation": sprintf("External service without authentication: %v", [input.metadata.name]),
"remediation": "Add annotation: auth.required=true",
}
}
# SOC2 CC6.6: Encryption in Transit
# Require TLS for Ingress
deny[msg] {
input.kind == "Ingress"
not input.spec.tls
msg := {
"control": "SOC2 CC6.6",
"severity": "high",
"violation": sprintf("Ingress without TLS: %v", [input.metadata.name]),
"remediation": "Configure spec.tls with valid certificates",
}
}
# Require TLS for LoadBalancer
deny[msg] {
input.kind == "Service"
input.spec.type == "LoadBalancer"
not input.metadata.annotations["service.beta.kubernetes.io/aws-load-balancer-ssl-cert"]
msg := {
"control": "SOC2 CC6.6",
"severity": "high",
"violation": sprintf("LoadBalancer without SSL/TLS: %v", [input.metadata.name]),
"remediation": "Add SSL certificate annotation",
}
}
# SOC2 CC6.7: Encryption at Rest
# Require encrypted volumes for confidential data
deny[msg] {
input.kind == "PersistentVolumeClaim"
input.metadata.labels["data-classification"] == "confidential"
not input.metadata.annotations["volume.beta.kubernetes.io/storage-encrypted"] == "true"
msg := {
"control": "SOC2 CC6.7",
"severity": "high",
"violation": sprintf("Unencrypted volume for confidential data: %v", [input.metadata.name]),
"remediation": "Enable volume encryption annotation",
}
}
# SOC2 CC7.2: System Monitoring
# Require audit logging for critical systems
deny[msg] {
input.kind == "Deployment"
input.metadata.labels["critical-system"] == "true"
not has_audit_logging(input)
msg := {
"control": "SOC2 CC7.2",
"severity": "medium",
"violation": sprintf("Critical system without audit logging: %v", [input.metadata.name]),
"remediation": "Enable audit logging via sidecar or annotations",
}
}
has_audit_logging(resource) {
resource.spec.template.metadata.annotations["audit.enabled"] == "true"
}
# SOC2 CC8.1: Change Management
# Require approval for production changes
deny[msg] {
input.kind == "Deployment"
input.metadata.namespace == "production"
not input.metadata.annotations["change-request.id"]
msg := {
"control": "SOC2 CC8.1",
"severity": "medium",
"violation": sprintf("Production deployment without change request: %v", [input.metadata.name]),
"remediation": "Add annotation: change-request.id=CR-XXXX",
}
}

View File

@@ -0,0 +1,223 @@
package terraform.security
import future.keywords.if
# AWS S3 Bucket Security
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not has_encryption(resource)
msg := {
"resource": resource.name,
"type": "aws_s3_bucket",
"severity": "high",
"violation": "S3 bucket must have encryption enabled",
"remediation": "Add server_side_encryption_configuration block",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not has_versioning(resource)
msg := {
"resource": resource.name,
"type": "aws_s3_bucket",
"severity": "medium",
"violation": "S3 bucket should have versioning enabled",
"remediation": "Add versioning configuration with enabled = true",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket_public_access_block"
resource.change.after.block_public_acls == false
msg := {
"resource": resource.name,
"type": "aws_s3_bucket_public_access_block",
"severity": "high",
"violation": "S3 bucket must block public ACLs",
"remediation": "Set block_public_acls = true",
}
}
has_encryption(resource) {
resource.change.after.server_side_encryption_configuration
}
has_versioning(resource) {
resource.change.after.versioning[_].enabled == true
}
# AWS EC2 Security
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
not resource.change.after.metadata_options.http_tokens == "required"
msg := {
"resource": resource.name,
"type": "aws_instance",
"severity": "high",
"violation": "EC2 instance must use IMDSv2",
"remediation": "Set metadata_options.http_tokens = required",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
resource.change.after.associate_public_ip_address == true
is_production
msg := {
"resource": resource.name,
"type": "aws_instance",
"severity": "high",
"violation": "Production EC2 instances cannot have public IPs",
"remediation": "Set associate_public_ip_address = false",
}
}
is_production {
input.variables.environment == "production"
}
# AWS RDS Security
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
not resource.change.after.storage_encrypted
msg := {
"resource": resource.name,
"type": "aws_db_instance",
"severity": "high",
"violation": "RDS instance must have encryption enabled",
"remediation": "Set storage_encrypted = true",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
resource.change.after.publicly_accessible == true
msg := {
"resource": resource.name,
"type": "aws_db_instance",
"severity": "critical",
"violation": "RDS instance cannot be publicly accessible",
"remediation": "Set publicly_accessible = false",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
backup_retention := resource.change.after.backup_retention_period
backup_retention < 7
msg := {
"resource": resource.name,
"type": "aws_db_instance",
"severity": "medium",
"violation": "RDS instance must have at least 7 days backup retention",
"remediation": "Set backup_retention_period >= 7",
}
}
# AWS IAM Security
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_policy"
statement := resource.change.after.policy.Statement[_]
statement.Action[_] == "*"
msg := {
"resource": resource.name,
"type": "aws_iam_policy",
"severity": "high",
"violation": "IAM policy cannot use wildcard actions",
"remediation": "Specify explicit actions instead of *",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_policy"
statement := resource.change.after.policy.Statement[_]
statement.Resource[_] == "*"
statement.Effect == "Allow"
msg := {
"resource": resource.name,
"type": "aws_iam_policy",
"severity": "high",
"violation": "IAM policy cannot use wildcard resources with Allow",
"remediation": "Specify explicit resource ARNs",
}
}
# AWS Security Group Rules
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group_rule"
resource.change.after.type == "ingress"
resource.change.after.from_port == 22
is_open_to_internet(resource.change.after.cidr_blocks)
msg := {
"resource": resource.name,
"type": "aws_security_group_rule",
"severity": "critical",
"violation": "Security group allows SSH from internet",
"remediation": "Restrict SSH access to specific IP ranges",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group_rule"
resource.change.after.type == "ingress"
resource.change.after.from_port == 3389
is_open_to_internet(resource.change.after.cidr_blocks)
msg := {
"resource": resource.name,
"type": "aws_security_group_rule",
"severity": "critical",
"violation": "Security group allows RDP from internet",
"remediation": "Restrict RDP access to specific IP ranges",
}
}
is_open_to_internet(cidr_blocks) {
cidr_blocks[_] == "0.0.0.0/0"
}
# AWS KMS Security
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_kms_key"
not resource.change.after.enable_key_rotation
msg := {
"resource": resource.name,
"type": "aws_kms_key",
"severity": "medium",
"violation": "KMS key must have automatic rotation enabled",
"remediation": "Set enable_key_rotation = true",
}
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_kms_key"
deletion_window := resource.change.after.deletion_window_in_days
deletion_window < 30
msg := {
"resource": resource.name,
"type": "aws_kms_key",
"severity": "medium",
"violation": "KMS key deletion window must be at least 30 days",
"remediation": "Set deletion_window_in_days >= 30",
}
}