Initial commit
This commit is contained in:
9
skills/compliance/policy-opa/assets/.gitkeep
Normal file
9
skills/compliance/policy-opa/assets/.gitkeep
Normal 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.
|
||||
234
skills/compliance/policy-opa/assets/ci-cd-pipeline.yaml
Normal file
234
skills/compliance/policy-opa/assets/ci-cd-pipeline.yaml
Normal 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
|
||||
159
skills/compliance/policy-opa/assets/gdpr-compliance.rego
Normal file
159
skills/compliance/policy-opa/assets/gdpr-compliance.rego
Normal 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>",
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
20
skills/compliance/policy-opa/assets/k8s-constraint.yaml
Normal file
20
skills/compliance/policy-opa/assets/k8s-constraint.yaml
Normal 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
|
||||
90
skills/compliance/policy-opa/assets/k8s-pod-security.rego
Normal file
90
skills/compliance/policy-opa/assets/k8s-pod-security.rego
Normal 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"
|
||||
}
|
||||
131
skills/compliance/policy-opa/assets/pci-dss-compliance.rego
Normal file
131
skills/compliance/policy-opa/assets/pci-dss-compliance.rego
Normal 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",
|
||||
}
|
||||
}
|
||||
107
skills/compliance/policy-opa/assets/soc2-compliance.rego
Normal file
107
skills/compliance/policy-opa/assets/soc2-compliance.rego
Normal 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",
|
||||
}
|
||||
}
|
||||
223
skills/compliance/policy-opa/assets/terraform-security.rego
Normal file
223
skills/compliance/policy-opa/assets/terraform-security.rego
Normal 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",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user