Initial commit
This commit is contained in:
40
skills/compliance/policy-opa/references/EXAMPLE.md
Normal file
40
skills/compliance/policy-opa/references/EXAMPLE.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Reference Document Template
|
||||
|
||||
This file contains detailed reference material that Claude should load only when needed.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Section 1](#section-1)
|
||||
- [Section 2](#section-2)
|
||||
- [Security Standards](#security-standards)
|
||||
|
||||
## Section 1
|
||||
|
||||
Detailed information, schemas, or examples that are too large for SKILL.md.
|
||||
|
||||
## Section 2
|
||||
|
||||
Additional reference material.
|
||||
|
||||
## Security Standards
|
||||
|
||||
### OWASP Top 10
|
||||
|
||||
Reference relevant OWASP categories:
|
||||
- A01: Broken Access Control
|
||||
- A02: Cryptographic Failures
|
||||
- etc.
|
||||
|
||||
### CWE Mappings
|
||||
|
||||
Map to relevant Common Weakness Enumeration categories:
|
||||
- CWE-79: Cross-site Scripting
|
||||
- CWE-89: SQL Injection
|
||||
- etc.
|
||||
|
||||
### MITRE ATT&CK
|
||||
|
||||
Reference relevant tactics and techniques if applicable:
|
||||
- TA0001: Initial Access
|
||||
- T1190: Exploit Public-Facing Application
|
||||
- etc.
|
||||
507
skills/compliance/policy-opa/references/compliance-frameworks.md
Normal file
507
skills/compliance/policy-opa/references/compliance-frameworks.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# Compliance Framework Policy Templates
|
||||
|
||||
Policy templates mapped to specific compliance framework controls for SOC2, PCI-DSS, GDPR, HIPAA, and NIST.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [SOC2 Trust Services Criteria](#soc2-trust-services-criteria)
|
||||
- [PCI-DSS Requirements](#pci-dss-requirements)
|
||||
- [GDPR Data Protection](#gdpr-data-protection)
|
||||
- [HIPAA Security Rules](#hipaa-security-rules)
|
||||
- [NIST Cybersecurity Framework](#nist-cybersecurity-framework)
|
||||
|
||||
## SOC2 Trust Services Criteria
|
||||
|
||||
### CC6.1: Logical and Physical Access Controls
|
||||
|
||||
**Control**: The entity implements logical access security software, infrastructure, and architectures over protected information assets to protect them from security events.
|
||||
|
||||
```rego
|
||||
package compliance.soc2.cc6_1
|
||||
|
||||
# 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",
|
||||
"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",
|
||||
"violation": sprintf("External service without authentication: %v", [input.metadata.name]),
|
||||
"remediation": "Add auth.required=true annotation"
|
||||
}
|
||||
}
|
||||
|
||||
# Require MFA for admin access
|
||||
deny[msg] {
|
||||
input.kind == "RoleBinding"
|
||||
contains(input.roleRef.name, "admin")
|
||||
not input.metadata.annotations["mfa.required"] == "true"
|
||||
msg := {
|
||||
"control": "SOC2 CC6.1",
|
||||
"violation": sprintf("Admin role without MFA requirement: %v", [input.metadata.name]),
|
||||
"remediation": "Add mfa.required=true annotation"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CC6.6: Encryption in Transit
|
||||
|
||||
**Control**: The entity protects information transmitted to external parties during transmission.
|
||||
|
||||
```rego
|
||||
package compliance.soc2.cc6_6
|
||||
|
||||
# Require TLS for external services
|
||||
deny[msg] {
|
||||
input.kind == "Ingress"
|
||||
not input.spec.tls
|
||||
msg := {
|
||||
"control": "SOC2 CC6.6",
|
||||
"violation": sprintf("Ingress without TLS: %v", [input.metadata.name]),
|
||||
"remediation": "Configure spec.tls with valid certificates"
|
||||
}
|
||||
}
|
||||
|
||||
# Require TLS for LoadBalancer services
|
||||
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",
|
||||
"violation": sprintf("LoadBalancer without SSL/TLS: %v", [input.metadata.name]),
|
||||
"remediation": "Add SSL certificate annotation"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CC6.7: Encryption at Rest
|
||||
|
||||
**Control**: The entity protects information at rest.
|
||||
|
||||
```rego
|
||||
package compliance.soc2.cc6_7
|
||||
|
||||
# Require encrypted volumes
|
||||
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",
|
||||
"violation": sprintf("Unencrypted volume for confidential data: %v", [input.metadata.name]),
|
||||
"remediation": "Enable volume encryption annotation"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CC7.2: System Monitoring
|
||||
|
||||
**Control**: The entity monitors system components and the operation of those components for anomalies.
|
||||
|
||||
```rego
|
||||
package compliance.soc2.cc7_2
|
||||
|
||||
# Require audit logging
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
input.metadata.labels["critical-system"] == "true"
|
||||
not has_audit_logging(input)
|
||||
msg := {
|
||||
"control": "SOC2 CC7.2",
|
||||
"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"
|
||||
}
|
||||
```
|
||||
|
||||
## PCI-DSS Requirements
|
||||
|
||||
### Requirement 1.2: Firewall Configuration
|
||||
|
||||
**Control**: Build firewall and router configurations that restrict connections between untrusted networks.
|
||||
|
||||
```rego
|
||||
package compliance.pci.req1_2
|
||||
|
||||
# Require network policies for cardholder data
|
||||
deny[msg] {
|
||||
input.kind == "Namespace"
|
||||
input.metadata.labels["pci.scope"] == "in-scope"
|
||||
not has_network_policy(input.metadata.name)
|
||||
msg := {
|
||||
"control": "PCI-DSS 1.2",
|
||||
"violation": sprintf("PCI in-scope namespace without network policy: %v", [input.metadata.name]),
|
||||
"remediation": "Create NetworkPolicy to restrict traffic"
|
||||
}
|
||||
}
|
||||
|
||||
has_network_policy(namespace) {
|
||||
# Check if NetworkPolicy exists in data (requires external data)
|
||||
data.network_policies[namespace]
|
||||
}
|
||||
```
|
||||
|
||||
### Requirement 2.2: System Hardening
|
||||
|
||||
**Control**: Develop configuration standards for all system components.
|
||||
|
||||
```rego
|
||||
package compliance.pci.req2_2
|
||||
|
||||
# Container hardening requirements
|
||||
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",
|
||||
"violation": sprintf("PCI container without read-only filesystem: %v", [container.name]),
|
||||
"remediation": "Set securityContext.readOnlyRootFilesystem: true"
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
"violation": sprintf("PCI container allows privilege escalation: %v", [container.name]),
|
||||
"remediation": "Set securityContext.allowPrivilegeEscalation: false"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Requirement 8.2.1: Strong Authentication
|
||||
|
||||
**Control**: Render all authentication credentials unreadable during transmission and storage.
|
||||
|
||||
```rego
|
||||
package compliance.pci.req8_2_1
|
||||
|
||||
# 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",
|
||||
"violation": sprintf("Payment ingress without MFA: %v", [input.metadata.name]),
|
||||
"remediation": "Enable MFA via annotation: mfa.required=true"
|
||||
}
|
||||
}
|
||||
|
||||
# Password strength requirements
|
||||
deny[msg] {
|
||||
input.kind == "ConfigMap"
|
||||
input.metadata.name == "auth-config"
|
||||
to_number(input.data["password.minLength"]) < 12
|
||||
msg := {
|
||||
"control": "PCI-DSS 8.2.1",
|
||||
"violation": "Password minimum length below requirement",
|
||||
"remediation": "Set password.minLength to at least 12"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Requirement 10.2: Audit Logging
|
||||
|
||||
**Control**: Implement automated audit trails for all system components.
|
||||
|
||||
```rego
|
||||
package compliance.pci.req10_2
|
||||
|
||||
# Require audit logging for PCI components
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
input.metadata.labels["pci.scope"] == "in-scope"
|
||||
not has_audit_sidecar(input)
|
||||
msg := {
|
||||
"control": "PCI-DSS 10.2",
|
||||
"violation": sprintf("PCI deployment without audit logging: %v", [input.metadata.name]),
|
||||
"remediation": "Deploy audit logging sidecar"
|
||||
}
|
||||
}
|
||||
|
||||
has_audit_sidecar(resource) {
|
||||
container := resource.spec.template.spec.containers[_]
|
||||
contains(container.name, "audit")
|
||||
}
|
||||
```
|
||||
|
||||
## GDPR Data Protection
|
||||
|
||||
### Article 25: Data Protection by Design
|
||||
|
||||
**Control**: The controller shall implement appropriate technical and organizational measures.
|
||||
|
||||
```rego
|
||||
package compliance.gdpr.art25
|
||||
|
||||
# Require data classification labels
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
processes_personal_data(input)
|
||||
not input.metadata.labels["data-classification"]
|
||||
msg := {
|
||||
"control": "GDPR Article 25",
|
||||
"violation": sprintf("Deployment processing personal data without classification: %v", [input.metadata.name]),
|
||||
"remediation": "Add data-classification label"
|
||||
}
|
||||
}
|
||||
|
||||
# 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",
|
||||
"violation": sprintf("Excessive replicas for personal data: %v", [input.metadata.name]),
|
||||
"remediation": "Reduce replicas or add justification annotation"
|
||||
}
|
||||
}
|
||||
|
||||
processes_personal_data(resource) {
|
||||
resource.metadata.labels["data-type"] == "personal"
|
||||
}
|
||||
|
||||
processes_personal_data(resource) {
|
||||
contains(lower(resource.metadata.name), "user")
|
||||
}
|
||||
```
|
||||
|
||||
### Article 32: Security of Processing
|
||||
|
||||
**Control**: Implement appropriate technical and organizational measures to ensure a level of security appropriate to the risk.
|
||||
|
||||
```rego
|
||||
package compliance.gdpr.art32
|
||||
|
||||
# Require encryption for personal data
|
||||
deny[msg] {
|
||||
input.kind == "PersistentVolumeClaim"
|
||||
input.metadata.labels["data-type"] == "personal"
|
||||
not input.metadata.annotations["volume.encryption.enabled"] == "true"
|
||||
msg := {
|
||||
"control": "GDPR Article 32",
|
||||
"violation": sprintf("Personal data volume without 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",
|
||||
"violation": sprintf("Personal data service without TLS: %v", [input.metadata.name]),
|
||||
"remediation": "Enable TLS encryption"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## HIPAA Security Rules
|
||||
|
||||
### 164.308: Administrative Safeguards
|
||||
|
||||
**Control**: Implement policies and procedures to prevent, detect, contain, and correct security violations.
|
||||
|
||||
```rego
|
||||
package compliance.hipaa.admin
|
||||
|
||||
# Require access control policies
|
||||
deny[msg] {
|
||||
input.kind == "Namespace"
|
||||
input.metadata.labels["phi-data"] == "true"
|
||||
not input.metadata.annotations["access-control.policy"]
|
||||
msg := {
|
||||
"control": "HIPAA 164.308",
|
||||
"violation": sprintf("PHI namespace without access control policy: %v", [input.metadata.name]),
|
||||
"remediation": "Document access control policy in annotation"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 164.312: Technical Safeguards
|
||||
|
||||
**Control**: Implement technical policies and procedures for electronic information systems.
|
||||
|
||||
```rego
|
||||
package compliance.hipaa.technical
|
||||
|
||||
# Encryption in transit for PHI
|
||||
deny[msg] {
|
||||
input.kind == "Service"
|
||||
input.metadata.labels["phi-data"] == "true"
|
||||
not input.metadata.annotations["tls.enabled"] == "true"
|
||||
msg := {
|
||||
"control": "HIPAA 164.312",
|
||||
"violation": sprintf("PHI service without TLS: %v", [input.metadata.name]),
|
||||
"remediation": "Enable TLS for data in transit"
|
||||
}
|
||||
}
|
||||
|
||||
# Audit logging for PHI access
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
input.metadata.labels["phi-data"] == "true"
|
||||
not has_audit_logging(input)
|
||||
msg := {
|
||||
"control": "HIPAA 164.312",
|
||||
"violation": sprintf("PHI deployment without audit logging: %v", [input.metadata.name]),
|
||||
"remediation": "Enable audit logging for all PHI access"
|
||||
}
|
||||
}
|
||||
|
||||
has_audit_logging(resource) {
|
||||
resource.spec.template.metadata.annotations["audit.enabled"] == "true"
|
||||
}
|
||||
|
||||
# Authentication controls
|
||||
deny[msg] {
|
||||
input.kind == "Ingress"
|
||||
input.metadata.labels["phi-data"] == "true"
|
||||
not input.metadata.annotations["auth.method"]
|
||||
msg := {
|
||||
"control": "HIPAA 164.312",
|
||||
"violation": sprintf("PHI ingress without authentication: %v", [input.metadata.name]),
|
||||
"remediation": "Configure authentication method"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## NIST Cybersecurity Framework
|
||||
|
||||
### PR.AC-4: Access Control
|
||||
|
||||
**Control**: Access permissions and authorizations are managed, incorporating the principles of least privilege and separation of duties.
|
||||
|
||||
```rego
|
||||
package compliance.nist.pr_ac_4
|
||||
|
||||
# Least privilege - no wildcard permissions
|
||||
deny[msg] {
|
||||
input.kind == "Role"
|
||||
rule := input.rules[_]
|
||||
rule.verbs[_] == "*"
|
||||
msg := {
|
||||
"control": "NIST PR.AC-4",
|
||||
"violation": sprintf("Wildcard permissions in role: %v", [input.metadata.name]),
|
||||
"remediation": "Specify explicit verb permissions"
|
||||
}
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.kind == "Role"
|
||||
rule := input.rules[_]
|
||||
rule.resources[_] == "*"
|
||||
msg := {
|
||||
"control": "NIST PR.AC-4",
|
||||
"violation": sprintf("Wildcard resources in role: %v", [input.metadata.name]),
|
||||
"remediation": "Specify explicit resource permissions"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PR.DS-1: Data-at-Rest Protection
|
||||
|
||||
**Control**: Data-at-rest is protected.
|
||||
|
||||
```rego
|
||||
package compliance.nist.pr_ds_1
|
||||
|
||||
# Require encryption for sensitive data
|
||||
deny[msg] {
|
||||
input.kind == "PersistentVolumeClaim"
|
||||
input.metadata.labels["data-sensitivity"] == "high"
|
||||
not input.metadata.annotations["volume.encryption"] == "enabled"
|
||||
msg := {
|
||||
"control": "NIST PR.DS-1",
|
||||
"violation": sprintf("Sensitive data volume without encryption: %v", [input.metadata.name]),
|
||||
"remediation": "Enable volume encryption for data-at-rest protection"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PR.DS-2: Data-in-Transit Protection
|
||||
|
||||
**Control**: Data-in-transit is protected.
|
||||
|
||||
```rego
|
||||
package compliance.nist.pr_ds_2
|
||||
|
||||
# Require TLS for external traffic
|
||||
deny[msg] {
|
||||
input.kind == "Ingress"
|
||||
not input.spec.tls
|
||||
msg := {
|
||||
"control": "NIST PR.DS-2",
|
||||
"violation": sprintf("Ingress without TLS: %v", [input.metadata.name]),
|
||||
"remediation": "Configure TLS for data-in-transit protection"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Framework Compliance
|
||||
|
||||
Example policy that maps to multiple frameworks:
|
||||
|
||||
```rego
|
||||
package compliance.multi_framework
|
||||
|
||||
# Encryption requirement - maps to multiple frameworks
|
||||
deny[msg] {
|
||||
input.kind == "Service"
|
||||
input.spec.type == "LoadBalancer"
|
||||
not has_tls_encryption(input)
|
||||
|
||||
msg := {
|
||||
"violation": sprintf("External service without TLS encryption: %v", [input.metadata.name]),
|
||||
"remediation": "Enable TLS/SSL for external services",
|
||||
"frameworks": {
|
||||
"SOC2": "CC6.6 - Encryption in Transit",
|
||||
"PCI-DSS": "4.1 - Use strong cryptography",
|
||||
"GDPR": "Article 32 - Security of Processing",
|
||||
"HIPAA": "164.312 - Technical Safeguards",
|
||||
"NIST": "PR.DS-2 - Data-in-Transit Protection"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
has_tls_encryption(service) {
|
||||
service.metadata.annotations["service.beta.kubernetes.io/aws-load-balancer-ssl-cert"]
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [SOC2 Trust Services Criteria](https://www.aicpa.org/interestareas/frc/assuranceadvisoryservices/aicpasoc2report.html)
|
||||
- [PCI-DSS Requirements](https://www.pcisecuritystandards.org/document_library)
|
||||
- [GDPR Official Text](https://gdpr.eu/)
|
||||
- [HIPAA Security Rule](https://www.hhs.gov/hipaa/for-professionals/security/index.html)
|
||||
- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
|
||||
623
skills/compliance/policy-opa/references/iac-policies.md
Normal file
623
skills/compliance/policy-opa/references/iac-policies.md
Normal file
@@ -0,0 +1,623 @@
|
||||
# Infrastructure-as-Code Security Policies
|
||||
|
||||
OPA policies for validating infrastructure-as-code configurations in Terraform, CloudFormation, and other IaC tools.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Terraform Policies](#terraform-policies)
|
||||
- [AWS CloudFormation](#aws-cloudformation)
|
||||
- [Azure ARM Templates](#azure-arm-templates)
|
||||
- [GCP Deployment Manager](#gcp-deployment-manager)
|
||||
|
||||
## Terraform Policies
|
||||
|
||||
### S3 Bucket Security
|
||||
|
||||
```rego
|
||||
package terraform.aws.s3
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_s3_bucket"
|
||||
not has_encryption(resource)
|
||||
|
||||
msg := sprintf("S3 bucket must have encryption enabled: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_s3_bucket"
|
||||
not has_versioning(resource)
|
||||
|
||||
msg := sprintf("S3 bucket must have versioning enabled: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_s3_bucket_public_access_block"
|
||||
resource.change.after.block_public_acls == false
|
||||
|
||||
msg := sprintf("S3 bucket must block public ACLs: %v", [resource.name])
|
||||
}
|
||||
|
||||
has_encryption(resource) {
|
||||
resource.change.after.server_side_encryption_configuration
|
||||
}
|
||||
|
||||
has_versioning(resource) {
|
||||
resource.change.after.versioning[_].enabled == true
|
||||
}
|
||||
```
|
||||
|
||||
### EC2 Instance Security
|
||||
|
||||
```rego
|
||||
package terraform.aws.ec2
|
||||
|
||||
# Deny instances without IMDSv2
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_instance"
|
||||
not resource.change.after.metadata_options.http_tokens == "required"
|
||||
|
||||
msg := sprintf("EC2 instance must use IMDSv2: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Deny instances with public IPs in production
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_instance"
|
||||
resource.change.after.associate_public_ip_address == true
|
||||
is_production_environment
|
||||
|
||||
msg := sprintf("Production EC2 instances cannot have public IPs: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Require monitoring
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_instance"
|
||||
resource.change.after.monitoring != true
|
||||
|
||||
msg := sprintf("EC2 instance must have detailed monitoring enabled: %v", [resource.name])
|
||||
}
|
||||
|
||||
is_production_environment {
|
||||
input.variables.environment == "production"
|
||||
}
|
||||
```
|
||||
|
||||
### RDS Database Security
|
||||
|
||||
```rego
|
||||
package terraform.aws.rds
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_db_instance"
|
||||
not resource.change.after.storage_encrypted
|
||||
|
||||
msg := sprintf("RDS instance must have encryption enabled: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_db_instance"
|
||||
resource.change.after.publicly_accessible == true
|
||||
|
||||
msg := sprintf("RDS instance cannot be publicly accessible: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_db_instance"
|
||||
not resource.change.after.backup_retention_period
|
||||
|
||||
msg := sprintf("RDS instance must have backup retention configured: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_db_instance"
|
||||
resource.change.after.backup_retention_period < 7
|
||||
|
||||
msg := sprintf("RDS instance must have at least 7 days backup retention: %v", [resource.name])
|
||||
}
|
||||
```
|
||||
|
||||
### IAM Policy Security
|
||||
|
||||
```rego
|
||||
package terraform.aws.iam
|
||||
|
||||
# Deny wildcard actions in IAM policies
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_iam_policy"
|
||||
statement := resource.change.after.policy.Statement[_]
|
||||
statement.Action[_] == "*"
|
||||
|
||||
msg := sprintf("IAM policy cannot use wildcard actions: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Deny wildcard resources
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_iam_policy"
|
||||
statement := resource.change.after.policy.Statement[_]
|
||||
statement.Resource[_] == "*"
|
||||
statement.Effect == "Allow"
|
||||
|
||||
msg := sprintf("IAM policy cannot use wildcard resources with Allow: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Deny policies without conditions for sensitive actions
|
||||
sensitive_actions := [
|
||||
"iam:CreateUser",
|
||||
"iam:DeleteUser",
|
||||
"iam:AttachUserPolicy",
|
||||
"kms:Decrypt",
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_iam_policy"
|
||||
statement := resource.change.after.policy.Statement[_]
|
||||
action := statement.Action[_]
|
||||
sensitive_actions[_] == action
|
||||
not statement.Condition
|
||||
|
||||
msg := sprintf("Sensitive IAM action requires conditions: %v in %v", [action, resource.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Security Group Rules
|
||||
|
||||
```rego
|
||||
package terraform.aws.security_groups
|
||||
|
||||
# Deny SSH from internet
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_security_group_rule"
|
||||
resource.change.after.type == "ingress"
|
||||
resource.change.after.from_port == 22
|
||||
resource.change.after.to_port == 22
|
||||
is_open_to_internet(resource.change.after.cidr_blocks)
|
||||
|
||||
msg := sprintf("Security group rule allows SSH from internet: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Deny RDP from internet
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_security_group_rule"
|
||||
resource.change.after.type == "ingress"
|
||||
resource.change.after.from_port == 3389
|
||||
resource.change.after.to_port == 3389
|
||||
is_open_to_internet(resource.change.after.cidr_blocks)
|
||||
|
||||
msg := sprintf("Security group rule allows RDP from internet: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Deny unrestricted ingress
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_security_group_rule"
|
||||
resource.change.after.type == "ingress"
|
||||
is_open_to_internet(resource.change.after.cidr_blocks)
|
||||
not is_allowed_public_port(resource.change.after.from_port)
|
||||
|
||||
msg := sprintf("Security group rule allows unrestricted ingress: %v", [resource.name])
|
||||
}
|
||||
|
||||
is_open_to_internet(cidr_blocks) {
|
||||
cidr_blocks[_] == "0.0.0.0/0"
|
||||
}
|
||||
|
||||
# Allowed public ports (HTTP/HTTPS)
|
||||
is_allowed_public_port(port) {
|
||||
port == 80
|
||||
}
|
||||
|
||||
is_allowed_public_port(port) {
|
||||
port == 443
|
||||
}
|
||||
```
|
||||
|
||||
### KMS Key Security
|
||||
|
||||
```rego
|
||||
package terraform.aws.kms
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_kms_key"
|
||||
not resource.change.after.enable_key_rotation
|
||||
|
||||
msg := sprintf("KMS key must have automatic rotation enabled: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_kms_key"
|
||||
not resource.change.after.deletion_window_in_days
|
||||
|
||||
msg := sprintf("KMS key must have deletion window configured: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_kms_key"
|
||||
resource.change.after.deletion_window_in_days < 30
|
||||
|
||||
msg := sprintf("KMS key deletion window must be at least 30 days: %v", [resource.name])
|
||||
}
|
||||
```
|
||||
|
||||
### CloudWatch Logging
|
||||
|
||||
```rego
|
||||
package terraform.aws.logging
|
||||
|
||||
# Require CloudWatch logs for Lambda
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_lambda_function"
|
||||
not has_cloudwatch_logs(resource.name)
|
||||
|
||||
msg := sprintf("Lambda function must have CloudWatch logs configured: %v", [resource.name])
|
||||
}
|
||||
|
||||
has_cloudwatch_logs(function_name) {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_cloudwatch_log_group"
|
||||
contains(resource.change.after.name, function_name)
|
||||
}
|
||||
```
|
||||
|
||||
## AWS CloudFormation
|
||||
|
||||
### S3 Bucket Security
|
||||
|
||||
```rego
|
||||
package cloudformation.aws.s3
|
||||
|
||||
deny[msg] {
|
||||
resource := input.Resources[name]
|
||||
resource.Type == "AWS::S3::Bucket"
|
||||
not has_bucket_encryption(resource)
|
||||
|
||||
msg := sprintf("S3 bucket must have encryption: %v", [name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.Resources[name]
|
||||
resource.Type == "AWS::S3::Bucket"
|
||||
not has_versioning(resource)
|
||||
|
||||
msg := sprintf("S3 bucket must have versioning enabled: %v", [name])
|
||||
}
|
||||
|
||||
has_bucket_encryption(resource) {
|
||||
resource.Properties.BucketEncryption
|
||||
}
|
||||
|
||||
has_versioning(resource) {
|
||||
resource.Properties.VersioningConfiguration.Status == "Enabled"
|
||||
}
|
||||
```
|
||||
|
||||
### EC2 Security Groups
|
||||
|
||||
```rego
|
||||
package cloudformation.aws.ec2
|
||||
|
||||
deny[msg] {
|
||||
resource := input.Resources[name]
|
||||
resource.Type == "AWS::EC2::SecurityGroup"
|
||||
rule := resource.Properties.SecurityGroupIngress[_]
|
||||
rule.CidrIp == "0.0.0.0/0"
|
||||
rule.FromPort == 22
|
||||
|
||||
msg := sprintf("Security group allows SSH from internet: %v", [name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.Resources[name]
|
||||
resource.Type == "AWS::EC2::SecurityGroup"
|
||||
rule := resource.Properties.SecurityGroupIngress[_]
|
||||
rule.CidrIp == "0.0.0.0/0"
|
||||
rule.FromPort == 3389
|
||||
|
||||
msg := sprintf("Security group allows RDP from internet: %v", [name])
|
||||
}
|
||||
```
|
||||
|
||||
### RDS Database
|
||||
|
||||
```rego
|
||||
package cloudformation.aws.rds
|
||||
|
||||
deny[msg] {
|
||||
resource := input.Resources[name]
|
||||
resource.Type == "AWS::RDS::DBInstance"
|
||||
not resource.Properties.StorageEncrypted
|
||||
|
||||
msg := sprintf("RDS instance must have encryption enabled: %v", [name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.Resources[name]
|
||||
resource.Type == "AWS::RDS::DBInstance"
|
||||
resource.Properties.PubliclyAccessible == true
|
||||
|
||||
msg := sprintf("RDS instance cannot be publicly accessible: %v", [name])
|
||||
}
|
||||
```
|
||||
|
||||
## Azure ARM Templates
|
||||
|
||||
### Storage Account Security
|
||||
|
||||
```rego
|
||||
package azure.storage
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Storage/storageAccounts"
|
||||
not resource.properties.supportsHttpsTrafficOnly
|
||||
|
||||
msg := sprintf("Storage account must require HTTPS: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Storage/storageAccounts"
|
||||
resource.properties.allowBlobPublicAccess == true
|
||||
|
||||
msg := sprintf("Storage account must disable public blob access: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Storage/storageAccounts"
|
||||
not resource.properties.minimumTlsVersion == "TLS1_2"
|
||||
|
||||
msg := sprintf("Storage account must use TLS 1.2 minimum: %v", [resource.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Virtual Machine Security
|
||||
|
||||
```rego
|
||||
package azure.compute
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Compute/virtualMachines"
|
||||
not has_managed_identity(resource)
|
||||
|
||||
msg := sprintf("Virtual machine should use managed identity: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Compute/virtualMachines"
|
||||
not has_disk_encryption(resource)
|
||||
|
||||
msg := sprintf("Virtual machine must have disk encryption: %v", [resource.name])
|
||||
}
|
||||
|
||||
has_managed_identity(vm) {
|
||||
vm.identity.type
|
||||
}
|
||||
|
||||
has_disk_encryption(vm) {
|
||||
vm.properties.storageProfile.osDisk.encryptionSettings
|
||||
}
|
||||
```
|
||||
|
||||
### Network Security Groups
|
||||
|
||||
```rego
|
||||
package azure.network
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Network/networkSecurityGroups"
|
||||
rule := resource.properties.securityRules[_]
|
||||
rule.properties.access == "Allow"
|
||||
rule.properties.sourceAddressPrefix == "*"
|
||||
rule.properties.destinationPortRange == "22"
|
||||
|
||||
msg := sprintf("NSG allows SSH from internet: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "Microsoft.Network/networkSecurityGroups"
|
||||
rule := resource.properties.securityRules[_]
|
||||
rule.properties.access == "Allow"
|
||||
rule.properties.sourceAddressPrefix == "*"
|
||||
rule.properties.destinationPortRange == "3389"
|
||||
|
||||
msg := sprintf("NSG allows RDP from internet: %v", [resource.name])
|
||||
}
|
||||
```
|
||||
|
||||
## GCP Deployment Manager
|
||||
|
||||
### GCS Bucket Security
|
||||
|
||||
```rego
|
||||
package gcp.storage
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "storage.v1.bucket"
|
||||
not has_uniform_access(resource)
|
||||
|
||||
msg := sprintf("GCS bucket must use uniform bucket-level access: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "storage.v1.bucket"
|
||||
not has_encryption(resource)
|
||||
|
||||
msg := sprintf("GCS bucket must have encryption configured: %v", [resource.name])
|
||||
}
|
||||
|
||||
has_uniform_access(bucket) {
|
||||
bucket.properties.iamConfiguration.uniformBucketLevelAccess.enabled == true
|
||||
}
|
||||
|
||||
has_encryption(bucket) {
|
||||
bucket.properties.encryption
|
||||
}
|
||||
```
|
||||
|
||||
### Compute Instance Security
|
||||
|
||||
```rego
|
||||
package gcp.compute
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "compute.v1.instance"
|
||||
not has_service_account(resource)
|
||||
|
||||
msg := sprintf("Compute instance should use service account: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "compute.v1.instance"
|
||||
not has_disk_encryption(resource)
|
||||
|
||||
msg := sprintf("Compute instance must have disk encryption: %v", [resource.name])
|
||||
}
|
||||
|
||||
has_service_account(instance) {
|
||||
instance.properties.serviceAccounts
|
||||
}
|
||||
|
||||
has_disk_encryption(instance) {
|
||||
instance.properties.disks[_].diskEncryptionKey
|
||||
}
|
||||
```
|
||||
|
||||
### Firewall Rules
|
||||
|
||||
```rego
|
||||
package gcp.network
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "compute.v1.firewall"
|
||||
resource.properties.direction == "INGRESS"
|
||||
"0.0.0.0/0" == resource.properties.sourceRanges[_]
|
||||
allowed := resource.properties.allowed[_]
|
||||
allowed.ports[_] == "22"
|
||||
|
||||
msg := sprintf("Firewall rule allows SSH from internet: %v", [resource.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resources[_]
|
||||
resource.type == "compute.v1.firewall"
|
||||
resource.properties.direction == "INGRESS"
|
||||
"0.0.0.0/0" == resource.properties.sourceRanges[_]
|
||||
allowed := resource.properties.allowed[_]
|
||||
allowed.ports[_] == "3389"
|
||||
|
||||
msg := sprintf("Firewall rule allows RDP from internet: %v", [resource.name])
|
||||
}
|
||||
```
|
||||
|
||||
## Conftest Integration
|
||||
|
||||
Example using Conftest for Terraform validation:
|
||||
|
||||
```bash
|
||||
# Install conftest
|
||||
brew install conftest
|
||||
|
||||
# Create policy directory
|
||||
mkdir -p policy
|
||||
|
||||
# Write policy (policy/terraform.rego)
|
||||
package main
|
||||
|
||||
deny[msg] {
|
||||
resource := input.resource_changes[_]
|
||||
resource.type == "aws_s3_bucket"
|
||||
not resource.change.after.server_side_encryption_configuration
|
||||
msg := sprintf("S3 bucket must have encryption: %v", [resource.name])
|
||||
}
|
||||
|
||||
# Generate Terraform plan
|
||||
terraform plan -out=tfplan.binary
|
||||
terraform show -json tfplan.binary > tfplan.json
|
||||
|
||||
# Run conftest
|
||||
conftest test tfplan.json
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: IaC Policy Validation
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup OPA
|
||||
uses: open-policy-agent/setup-opa@v2
|
||||
|
||||
- name: Generate Terraform Plan
|
||||
run: |
|
||||
terraform init
|
||||
terraform plan -out=tfplan.binary
|
||||
terraform show -json tfplan.binary > tfplan.json
|
||||
|
||||
- name: Validate with OPA
|
||||
run: |
|
||||
opa eval --data policies/ --input tfplan.json \
|
||||
--format pretty 'data.terraform.deny' > violations.txt
|
||||
|
||||
if [ -s violations.txt ]; then
|
||||
cat violations.txt
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
iac-validation:
|
||||
image: openpolicyagent/opa:latest
|
||||
script:
|
||||
- terraform init
|
||||
- terraform plan -out=tfplan.binary
|
||||
- terraform show -json tfplan.binary > tfplan.json
|
||||
- opa eval --data policies/ --input tfplan.json 'data.terraform.deny'
|
||||
only:
|
||||
- merge_requests
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Conftest](https://www.conftest.dev/)
|
||||
- [Terraform Sentinel](https://www.terraform.io/docs/cloud/sentinel/index.html)
|
||||
- [AWS CloudFormation Guard](https://github.com/aws-cloudformation/cloudformation-guard)
|
||||
- [Azure Policy](https://docs.microsoft.com/en-us/azure/governance/policy/)
|
||||
- [Checkov](https://www.checkov.io/)
|
||||
550
skills/compliance/policy-opa/references/kubernetes-security.md
Normal file
550
skills/compliance/policy-opa/references/kubernetes-security.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Kubernetes Security Policies
|
||||
|
||||
Comprehensive OPA policies for Kubernetes security best practices and admission control.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Pod Security](#pod-security)
|
||||
- [RBAC Security](#rbac-security)
|
||||
- [Network Security](#network-security)
|
||||
- [Image Security](#image-security)
|
||||
- [Secret Management](#secret-management)
|
||||
|
||||
## Pod Security
|
||||
|
||||
### Privileged Containers
|
||||
|
||||
Deny privileged containers:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.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])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.initContainers[_]
|
||||
container.securityContext.privileged == true
|
||||
|
||||
msg := sprintf("Privileged init container is not allowed: %v", [container.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Run as Non-Root
|
||||
|
||||
Enforce containers run as non-root:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.non_root
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
container.securityContext.runAsUser == 0
|
||||
|
||||
msg := sprintf("Container cannot run as UID 0 (root): %v", [container.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Read-Only Root Filesystem
|
||||
|
||||
Require read-only root filesystem:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.readonly_root
|
||||
|
||||
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])
|
||||
}
|
||||
```
|
||||
|
||||
### Capabilities
|
||||
|
||||
Restrict Linux capabilities:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.capabilities
|
||||
|
||||
# Denied capabilities
|
||||
denied_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[_]
|
||||
denied_capabilities[_] == capability
|
||||
|
||||
msg := sprintf("Capability %v is not allowed for container: %v", [capability, container.name])
|
||||
}
|
||||
|
||||
# Require dropping ALL capabilities by default
|
||||
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"
|
||||
}
|
||||
```
|
||||
|
||||
### Host Namespaces
|
||||
|
||||
Prevent use of host namespaces:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.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"
|
||||
}
|
||||
```
|
||||
|
||||
### Host Paths
|
||||
|
||||
Restrict hostPath volumes:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.host_path
|
||||
|
||||
# Allowed host paths (if any)
|
||||
allowed_host_paths := [
|
||||
"/var/log/pods", # Example: log collection
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
volume := input.request.object.spec.volumes[_]
|
||||
volume.hostPath
|
||||
not is_allowed_host_path(volume.hostPath.path)
|
||||
|
||||
msg := sprintf("hostPath volume is not allowed: %v", [volume.hostPath.path])
|
||||
}
|
||||
|
||||
is_allowed_host_path(path) {
|
||||
allowed_host_paths[_] == path
|
||||
}
|
||||
```
|
||||
|
||||
### Security Context
|
||||
|
||||
Comprehensive pod security context validation:
|
||||
|
||||
```rego
|
||||
package kubernetes.admission.security_context
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
not input.request.object.spec.securityContext
|
||||
|
||||
msg := "Pod must define a security context"
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
pod_security := input.request.object.spec.securityContext
|
||||
not pod_security.runAsNonRoot
|
||||
|
||||
msg := "Pod security context must set runAsNonRoot: true"
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
pod_security := input.request.object.spec.securityContext
|
||||
not pod_security.seccompProfile
|
||||
|
||||
msg := "Pod must define a seccomp profile"
|
||||
}
|
||||
```
|
||||
|
||||
## RBAC Security
|
||||
|
||||
### Wildcard Permissions
|
||||
|
||||
Prevent wildcard RBAC permissions:
|
||||
|
||||
```rego
|
||||
package kubernetes.rbac.wildcards
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Role"
|
||||
rule := input.request.object.rules[_]
|
||||
rule.verbs[_] == "*"
|
||||
|
||||
msg := sprintf("Role contains wildcard verb permission in rule: %v", [rule])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Role"
|
||||
rule := input.request.object.rules[_]
|
||||
rule.resources[_] == "*"
|
||||
|
||||
msg := sprintf("Role contains wildcard resource permission in rule: %v", [rule])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "ClusterRole"
|
||||
rule := input.request.object.rules[_]
|
||||
rule.verbs[_] == "*"
|
||||
|
||||
msg := sprintf("ClusterRole contains wildcard verb permission in rule: %v", [rule])
|
||||
}
|
||||
```
|
||||
|
||||
### Cluster Admin
|
||||
|
||||
Restrict cluster-admin usage:
|
||||
|
||||
```rego
|
||||
package kubernetes.rbac.cluster_admin
|
||||
|
||||
# System accounts allowed to use cluster-admin
|
||||
allowed_system_accounts := [
|
||||
"system:kube-controller-manager",
|
||||
"system:kube-scheduler",
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "ClusterRoleBinding"
|
||||
input.request.object.roleRef.name == "cluster-admin"
|
||||
subject := input.request.object.subjects[_]
|
||||
not is_allowed_system_account(subject)
|
||||
|
||||
msg := sprintf("cluster-admin binding not allowed for subject: %v", [subject.name])
|
||||
}
|
||||
|
||||
is_allowed_system_account(subject) {
|
||||
allowed_system_accounts[_] == subject.name
|
||||
}
|
||||
```
|
||||
|
||||
### Service Account Token Mounting
|
||||
|
||||
Control service account token auto-mounting:
|
||||
|
||||
```rego
|
||||
package kubernetes.rbac.service_account_tokens
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
input.request.object.spec.automountServiceAccountToken == true
|
||||
not requires_service_account(input.request.object)
|
||||
|
||||
msg := "Pod should not auto-mount service account token unless required"
|
||||
}
|
||||
|
||||
requires_service_account(pod) {
|
||||
pod.metadata.annotations["requires-service-account"] == "true"
|
||||
}
|
||||
```
|
||||
|
||||
## Network Security
|
||||
|
||||
### Network Policies Required
|
||||
|
||||
Require network policies for namespaces:
|
||||
|
||||
```rego
|
||||
package kubernetes.network.policies_required
|
||||
|
||||
# Check if namespace has network policies (requires admission controller data)
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Namespace"
|
||||
not has_network_policy_annotation(input.request.object)
|
||||
|
||||
msg := sprintf("Namespace must have network policy annotation: %v", [input.request.object.metadata.name])
|
||||
}
|
||||
|
||||
has_network_policy_annotation(namespace) {
|
||||
namespace.metadata.annotations["network-policy.enabled"] == "true"
|
||||
}
|
||||
```
|
||||
|
||||
### Deny Default Network Policy
|
||||
|
||||
Implement default-deny network policy:
|
||||
|
||||
```rego
|
||||
package kubernetes.network.default_deny
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "NetworkPolicy"
|
||||
not is_default_deny(input.request.object)
|
||||
input.request.object.metadata.labels["policy-type"] == "default"
|
||||
|
||||
msg := "Default network policy must be deny-all"
|
||||
}
|
||||
|
||||
is_default_deny(network_policy) {
|
||||
# Check for empty ingress rules (deny all ingress)
|
||||
not network_policy.spec.ingress
|
||||
# Check for ingress type
|
||||
network_policy.spec.policyTypes[_] == "Ingress"
|
||||
}
|
||||
```
|
||||
|
||||
### Service Type LoadBalancer
|
||||
|
||||
Restrict external LoadBalancer services:
|
||||
|
||||
```rego
|
||||
package kubernetes.network.loadbalancer
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Service"
|
||||
input.request.object.spec.type == "LoadBalancer"
|
||||
not is_approved_for_external_exposure(input.request.object)
|
||||
|
||||
msg := sprintf("LoadBalancer service requires approval annotation: %v", [input.request.object.metadata.name])
|
||||
}
|
||||
|
||||
is_approved_for_external_exposure(service) {
|
||||
service.metadata.annotations["external-exposure.approved"] == "true"
|
||||
}
|
||||
```
|
||||
|
||||
## Image Security
|
||||
|
||||
### Image Registry Whitelist
|
||||
|
||||
Allow only approved image registries:
|
||||
|
||||
```rego
|
||||
package kubernetes.images.registry_whitelist
|
||||
|
||||
approved_registries := [
|
||||
"gcr.io/my-company",
|
||||
"docker.io/my-company",
|
||||
"quay.io/my-company",
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
not is_approved_registry(container.image)
|
||||
|
||||
msg := sprintf("Image from unapproved registry: %v", [container.image])
|
||||
}
|
||||
|
||||
is_approved_registry(image) {
|
||||
startswith(image, approved_registries[_])
|
||||
}
|
||||
```
|
||||
|
||||
### Image Tags
|
||||
|
||||
Prevent latest tag and require specific tags:
|
||||
|
||||
```rego
|
||||
package kubernetes.images.tags
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
endswith(container.image, ":latest")
|
||||
|
||||
msg := sprintf("Container uses 'latest' tag: %v", [container.name])
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
not contains(container.image, ":")
|
||||
|
||||
msg := sprintf("Container image must specify a tag: %v", [container.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Image Vulnerability Scanning
|
||||
|
||||
Require vulnerability scan results:
|
||||
|
||||
```rego
|
||||
package kubernetes.images.vulnerability_scanning
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
not has_scan_annotation(input.request.object)
|
||||
|
||||
msg := "Pod must have vulnerability scan results annotation"
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
scan_result := input.request.object.metadata.annotations["vulnerability-scan.result"]
|
||||
scan_result == "failed"
|
||||
|
||||
msg := "Pod image failed vulnerability scan"
|
||||
}
|
||||
|
||||
has_scan_annotation(pod) {
|
||||
pod.metadata.annotations["vulnerability-scan.result"]
|
||||
}
|
||||
```
|
||||
|
||||
## Secret Management
|
||||
|
||||
### Environment Variable Secrets
|
||||
|
||||
Prevent secrets in environment variables:
|
||||
|
||||
```rego
|
||||
package kubernetes.secrets.env_vars
|
||||
|
||||
sensitive_keywords := [
|
||||
"password",
|
||||
"token",
|
||||
"apikey",
|
||||
"secret",
|
||||
"credential",
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
container := input.request.object.spec.containers[_]
|
||||
env := container.env[_]
|
||||
is_sensitive_name(env.name)
|
||||
env.value # Direct value, not from secret
|
||||
|
||||
msg := sprintf("Sensitive data in environment variable: %v in container %v", [env.name, container.name])
|
||||
}
|
||||
|
||||
is_sensitive_name(name) {
|
||||
lower_name := lower(name)
|
||||
contains(lower_name, sensitive_keywords[_])
|
||||
}
|
||||
```
|
||||
|
||||
### Secret Volume Permissions
|
||||
|
||||
Restrict secret volume mount permissions:
|
||||
|
||||
```rego
|
||||
package kubernetes.secrets.volume_permissions
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Pod"
|
||||
volume := input.request.object.spec.volumes[_]
|
||||
volume.secret
|
||||
volume_mount := input.request.object.spec.containers[_].volumeMounts[_]
|
||||
volume_mount.name == volume.name
|
||||
not volume_mount.readOnly
|
||||
|
||||
msg := sprintf("Secret volume mount must be read-only: %v", [volume.name])
|
||||
}
|
||||
```
|
||||
|
||||
### External Secrets
|
||||
|
||||
Require use of external secret management:
|
||||
|
||||
```rego
|
||||
package kubernetes.secrets.external
|
||||
|
||||
deny[msg] {
|
||||
input.request.kind.kind == "Secret"
|
||||
input.request.object.metadata.labels["environment"] == "production"
|
||||
not input.request.object.metadata.annotations["external-secret.enabled"] == "true"
|
||||
|
||||
msg := sprintf("Production secrets must use external secret management: %v", [input.request.object.metadata.name])
|
||||
}
|
||||
```
|
||||
|
||||
## Admission Control Integration
|
||||
|
||||
Example OPA Gatekeeper ConstraintTemplate:
|
||||
|
||||
```yaml
|
||||
apiVersion: templates.gatekeeper.sh/v1beta1
|
||||
kind: ConstraintTemplate
|
||||
metadata:
|
||||
name: k8spodsecsecurity
|
||||
spec:
|
||||
crd:
|
||||
spec:
|
||||
names:
|
||||
kind: K8sPodSecSecurity
|
||||
targets:
|
||||
- target: admission.k8s.gatekeeper.sh
|
||||
rego: |
|
||||
package k8spodsecurity
|
||||
|
||||
violation[{"msg": msg}] {
|
||||
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])
|
||||
}
|
||||
```
|
||||
|
||||
Example Constraint:
|
||||
|
||||
```yaml
|
||||
apiVersion: constraints.gatekeeper.sh/v1beta1
|
||||
kind: K8sPodSecSecurity
|
||||
metadata:
|
||||
name: pod-security-policy
|
||||
spec:
|
||||
match:
|
||||
kinds:
|
||||
- apiGroups: [""]
|
||||
kinds: ["Pod"]
|
||||
namespaces:
|
||||
- "production"
|
||||
- "staging"
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
|
||||
- [OPA Gatekeeper Library](https://github.com/open-policy-agent/gatekeeper-library)
|
||||
- [NSA Kubernetes Hardening Guide](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/)
|
||||
- [CIS Kubernetes Benchmark](https://www.cisecurity.org/benchmark/kubernetes)
|
||||
505
skills/compliance/policy-opa/references/rego-patterns.md
Normal file
505
skills/compliance/policy-opa/references/rego-patterns.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# Common Rego Patterns for Security and Compliance
|
||||
|
||||
This reference provides common Rego patterns for implementing security and compliance policies in OPA.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Basic Patterns](#basic-patterns)
|
||||
- [Security Patterns](#security-patterns)
|
||||
- [Compliance Patterns](#compliance-patterns)
|
||||
- [Advanced Patterns](#advanced-patterns)
|
||||
|
||||
## Basic Patterns
|
||||
|
||||
### Deny Rules
|
||||
|
||||
Most common pattern - deny when condition is met:
|
||||
|
||||
```rego
|
||||
package example
|
||||
|
||||
deny[msg] {
|
||||
condition_is_true
|
||||
msg := "Descriptive error message"
|
||||
}
|
||||
```
|
||||
|
||||
### Allow Rules
|
||||
|
||||
Whitelist pattern - allow specific cases:
|
||||
|
||||
```rego
|
||||
package example
|
||||
|
||||
default allow = false
|
||||
|
||||
allow {
|
||||
input.user.role == "admin"
|
||||
}
|
||||
|
||||
allow {
|
||||
input.user.id == input.resource.owner
|
||||
}
|
||||
```
|
||||
|
||||
### Array Iteration
|
||||
|
||||
Iterate over arrays to check conditions:
|
||||
|
||||
```rego
|
||||
package example
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
container.image == "vulnerable:latest"
|
||||
msg := sprintf("Vulnerable image detected: %v", [container.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Object Key Checking
|
||||
|
||||
Verify required keys exist:
|
||||
|
||||
```rego
|
||||
package example
|
||||
|
||||
required_labels := ["app", "environment", "owner"]
|
||||
|
||||
deny[msg] {
|
||||
missing := required_labels[_]
|
||||
not input.metadata.labels[missing]
|
||||
msg := sprintf("Missing required label: %v", [missing])
|
||||
}
|
||||
```
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### Privileged Container Check
|
||||
|
||||
Deny privileged containers:
|
||||
|
||||
```rego
|
||||
package kubernetes.security
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
container.securityContext.privileged == true
|
||||
msg := sprintf("Privileged container not allowed: %v", [container.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Host Path Volume Check
|
||||
|
||||
Prevent hostPath volumes:
|
||||
|
||||
```rego
|
||||
package kubernetes.security
|
||||
|
||||
deny[msg] {
|
||||
volume := input.spec.volumes[_]
|
||||
volume.hostPath
|
||||
msg := sprintf("hostPath volumes not allowed: %v", [volume.name])
|
||||
}
|
||||
```
|
||||
|
||||
### Image Registry Whitelist
|
||||
|
||||
Allow only approved registries:
|
||||
|
||||
```rego
|
||||
package kubernetes.security
|
||||
|
||||
allowed_registries := [
|
||||
"gcr.io/company",
|
||||
"docker.io/company",
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
image := container.image
|
||||
not startswith_any(image, allowed_registries)
|
||||
msg := sprintf("Image from unauthorized registry: %v", [image])
|
||||
}
|
||||
|
||||
startswith_any(str, prefixes) {
|
||||
startswith(str, prefixes[_])
|
||||
}
|
||||
```
|
||||
|
||||
### Network Policy Enforcement
|
||||
|
||||
Require network policies for namespaces:
|
||||
|
||||
```rego
|
||||
package kubernetes.security
|
||||
|
||||
deny[msg] {
|
||||
input.kind == "Namespace"
|
||||
not input.metadata.labels["network-policy"]
|
||||
msg := "Namespace must have network-policy label"
|
||||
}
|
||||
```
|
||||
|
||||
### Secret in Environment Variables
|
||||
|
||||
Prevent secrets in environment variables:
|
||||
|
||||
```rego
|
||||
package kubernetes.security
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
env := container.env[_]
|
||||
contains(lower(env.name), "password")
|
||||
env.value # Direct value, not from secret
|
||||
msg := sprintf("Secret in environment variable: %v", [env.name])
|
||||
}
|
||||
```
|
||||
|
||||
## Compliance Patterns
|
||||
|
||||
### SOC2 CC6.1: Access Control
|
||||
|
||||
```rego
|
||||
package compliance.soc2
|
||||
|
||||
# Deny cluster-admin for non-system accounts
|
||||
deny[msg] {
|
||||
input.kind == "RoleBinding"
|
||||
input.roleRef.name == "cluster-admin"
|
||||
not startswith(input.subjects[_].name, "system:")
|
||||
msg := sprintf("SOC2 CC6.1: cluster-admin role binding not allowed for %v", [input.metadata.name])
|
||||
}
|
||||
|
||||
# Require authentication labels
|
||||
deny[msg] {
|
||||
input.kind == "Service"
|
||||
input.spec.type == "LoadBalancer"
|
||||
not input.metadata.annotations["auth.required"]
|
||||
msg := "SOC2 CC6.1: LoadBalancer services must require authentication"
|
||||
}
|
||||
```
|
||||
|
||||
### PCI-DSS 8.2.1: Strong Authentication
|
||||
|
||||
```rego
|
||||
package compliance.pci
|
||||
|
||||
# Require MFA annotation
|
||||
deny[msg] {
|
||||
input.kind == "Ingress"
|
||||
input.metadata.annotations["payment.enabled"] == "true"
|
||||
not input.metadata.annotations["mfa.required"] == "true"
|
||||
msg := "PCI-DSS 8.2.1: Payment endpoints must require MFA"
|
||||
}
|
||||
|
||||
# Password complexity requirements
|
||||
deny[msg] {
|
||||
input.kind == "ConfigMap"
|
||||
input.data["password.minLength"]
|
||||
to_number(input.data["password.minLength"]) < 12
|
||||
msg := "PCI-DSS 8.2.1: Minimum password length must be 12"
|
||||
}
|
||||
```
|
||||
|
||||
### GDPR Article 25: Data Protection by Design
|
||||
|
||||
```rego
|
||||
package compliance.gdpr
|
||||
|
||||
# Require data classification
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
processes_personal_data(input)
|
||||
not input.metadata.labels["data-classification"]
|
||||
msg := "GDPR Art25: Deployments processing personal data must have data-classification label"
|
||||
}
|
||||
|
||||
# Require encryption for personal data
|
||||
deny[msg] {
|
||||
input.kind == "PersistentVolumeClaim"
|
||||
input.metadata.labels["data-type"] == "personal"
|
||||
not input.metadata.annotations["volume.encryption.enabled"] == "true"
|
||||
msg := "GDPR Art25: Personal data volumes must use encryption"
|
||||
}
|
||||
|
||||
processes_personal_data(resource) {
|
||||
resource.metadata.labels["data-type"] == "personal"
|
||||
}
|
||||
|
||||
processes_personal_data(resource) {
|
||||
contains(lower(resource.metadata.name), "user")
|
||||
}
|
||||
```
|
||||
|
||||
### HIPAA 164.312: Technical Safeguards
|
||||
|
||||
```rego
|
||||
package compliance.hipaa
|
||||
|
||||
# Require encryption in transit
|
||||
deny[msg] {
|
||||
input.kind == "Service"
|
||||
input.metadata.labels["phi-data"] == "true"
|
||||
not input.metadata.annotations["tls.enabled"] == "true"
|
||||
msg := "HIPAA 164.312: Services handling PHI must use TLS encryption"
|
||||
}
|
||||
|
||||
# Audit logging requirement
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
input.metadata.labels["phi-data"] == "true"
|
||||
not has_audit_logging(input)
|
||||
msg := "HIPAA 164.312: PHI deployments must enable audit logging"
|
||||
}
|
||||
|
||||
has_audit_logging(resource) {
|
||||
resource.spec.template.metadata.annotations["audit.enabled"] == "true"
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Helper Functions
|
||||
|
||||
Create reusable helper functions:
|
||||
|
||||
```rego
|
||||
package helpers
|
||||
|
||||
# Check if string starts with any prefix
|
||||
startswith_any(str, prefixes) {
|
||||
startswith(str, prefixes[_])
|
||||
}
|
||||
|
||||
# Check if array contains value
|
||||
array_contains(arr, val) {
|
||||
arr[_] == val
|
||||
}
|
||||
|
||||
# Get all containers (including init containers)
|
||||
all_containers[container] {
|
||||
container := input.spec.containers[_]
|
||||
}
|
||||
|
||||
all_containers[container] {
|
||||
container := input.spec.initContainers[_]
|
||||
}
|
||||
|
||||
# Safe label access with default
|
||||
get_label(resource, key, default_val) = val {
|
||||
val := resource.metadata.labels[key]
|
||||
} else = default_val
|
||||
```
|
||||
|
||||
### Multi-Framework Mapping
|
||||
|
||||
Map single policy to multiple frameworks:
|
||||
|
||||
```rego
|
||||
package multi_framework
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
not container.securityContext.readOnlyRootFilesystem
|
||||
|
||||
msg := {
|
||||
"violation": "Container filesystem must be read-only",
|
||||
"container": container.name,
|
||||
"frameworks": {
|
||||
"SOC2": "CC6.1",
|
||||
"PCI-DSS": "2.2",
|
||||
"NIST": "CM-7",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Severity Levels
|
||||
|
||||
Add severity to violations:
|
||||
|
||||
```rego
|
||||
package severity
|
||||
|
||||
violations[violation] {
|
||||
container := input.spec.containers[_]
|
||||
container.securityContext.privileged == true
|
||||
|
||||
violation := {
|
||||
"message": sprintf("Privileged container: %v", [container.name]),
|
||||
"severity": "critical",
|
||||
"remediation": "Set securityContext.privileged to false"
|
||||
}
|
||||
}
|
||||
|
||||
violations[violation] {
|
||||
not input.spec.securityContext.runAsNonRoot
|
||||
|
||||
violation := {
|
||||
"message": "Pod does not enforce non-root user",
|
||||
"severity": "high",
|
||||
"remediation": "Set spec.securityContext.runAsNonRoot to true"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
Allow policy exceptions with justification:
|
||||
|
||||
```rego
|
||||
package exceptions
|
||||
|
||||
default allow = false
|
||||
|
||||
# Check for valid exception
|
||||
has_exception {
|
||||
input.metadata.annotations["policy.exception"] == "true"
|
||||
input.metadata.annotations["policy.justification"]
|
||||
input.metadata.annotations["policy.approver"]
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
violates_policy
|
||||
not has_exception
|
||||
msg := "Policy violation - no valid exception found"
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
violates_policy
|
||||
has_exception
|
||||
not is_valid_approver
|
||||
msg := "Policy exception requires valid approver"
|
||||
}
|
||||
```
|
||||
|
||||
### Data Validation
|
||||
|
||||
Validate external data sources:
|
||||
|
||||
```rego
|
||||
package data_validation
|
||||
|
||||
import data.approved_images
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
not image_approved(container.image)
|
||||
msg := sprintf("Image not in approved list: %v", [container.image])
|
||||
}
|
||||
|
||||
image_approved(image) {
|
||||
approved_images[_] == image
|
||||
}
|
||||
|
||||
# Validate with external API (requires OPA bundle with data)
|
||||
deny[msg] {
|
||||
input.kind == "Deployment"
|
||||
namespace := input.metadata.namespace
|
||||
not data.namespaces[namespace].approved
|
||||
msg := sprintf("Deployment to unapproved namespace: %v", [namespace])
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Patterns
|
||||
|
||||
Write comprehensive tests:
|
||||
|
||||
```rego
|
||||
package example_test
|
||||
|
||||
import data.example
|
||||
|
||||
# Test deny rule
|
||||
test_deny_privileged {
|
||||
input := {
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "app",
|
||||
"securityContext": {"privileged": true}
|
||||
}]
|
||||
}
|
||||
}
|
||||
count(example.deny) > 0
|
||||
}
|
||||
|
||||
# Test allow case
|
||||
test_allow_unprivileged {
|
||||
input := {
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "app",
|
||||
"securityContext": {"privileged": false}
|
||||
}]
|
||||
}
|
||||
}
|
||||
count(example.deny) == 0
|
||||
}
|
||||
|
||||
# Test with multiple containers
|
||||
test_multiple_containers {
|
||||
input := {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{"name": "app1", "securityContext": {"privileged": false}},
|
||||
{"name": "app2", "securityContext": {"privileged": true}}
|
||||
]
|
||||
}
|
||||
}
|
||||
count(example.deny) == 1
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Index Data Structures
|
||||
|
||||
Use indexed data for faster lookups:
|
||||
|
||||
```rego
|
||||
# Slow - iterates every time
|
||||
approved_images := ["image1:v1", "image2:v1", "image3:v1"]
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
not array_contains(approved_images, container.image)
|
||||
msg := "Image not approved"
|
||||
}
|
||||
|
||||
# Fast - uses indexing
|
||||
approved_images_set := {
|
||||
"image1:v1",
|
||||
"image2:v1",
|
||||
"image3:v1"
|
||||
}
|
||||
|
||||
deny[msg] {
|
||||
container := input.spec.containers[_]
|
||||
not approved_images_set[container.image]
|
||||
msg := "Image not approved"
|
||||
}
|
||||
```
|
||||
|
||||
### Partial Evaluation
|
||||
|
||||
Use comprehensions for efficiency:
|
||||
|
||||
```rego
|
||||
# Collect all violations at once
|
||||
all_violations := [msg |
|
||||
container := input.spec.containers[_]
|
||||
violates_policy(container)
|
||||
msg := format_message(container)
|
||||
]
|
||||
|
||||
deny[msg] {
|
||||
msg := all_violations[_]
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Rego Language Reference](https://www.openpolicyagent.org/docs/latest/policy-reference/)
|
||||
- [OPA Best Practices](https://www.openpolicyagent.org/docs/latest/policy-performance/)
|
||||
- [Rego Style Guide](https://github.com/open-policy-agent/opa/blob/main/docs/content/policy-language.md)
|
||||
Reference in New Issue
Block a user