This repository contains enterprise-grade, security-first Sentinel and Open Policy Agent (OPA) policies for enforcing security, compliance, and operational best practices in AWS-specific Terraform workflows. The policies implement comprehensive governance with fail-secure defaults, defense in depth, and comprehensive validation following the AWS Well-Architected Framework.
NEW: Security-First Policy Framework - Production-ready policies with:
- Fail-secure defaults that DENY when uncertain
- Defense in depth with multiple validation layers
- Comprehensive error handling with actionable messages
- Performance optimization with efficient filtering
- Multi-framework compliance (SOC2, HIPAA, PCI-DSS, GDPR, SOX)
See SECURITY_FIRST_POLICIES.md for complete details.
- AWS Focus
- Security-First Design
- Enhanced Policy Features
- Prerequisites
- Policy Structure
- Testing Framework
- Usage
- Contributing
This repository is specifically designed for AWS environments and includes production-ready policies for:
- AWS Security Services: S3, RDS, EBS, Secrets Manager, KMS, IAM, Security Groups, NACLs
- AWS Compute Services: EC2, Lambda, ECS, EKS, Auto Scaling Groups
- AWS Storage Services: S3, EBS, EFS, FSx
- AWS Database Services: RDS, DynamoDB, Redshift, ElastiCache
- AWS Network Services: VPC, Subnets, Route Tables, NAT Gateways, Load Balancers
- AWS Monitoring & Logging: CloudWatch, CloudTrail, VPC Flow Logs
- AWS Cost Management: Resource tagging, instance sizing, reserved instances
All policies are written with AWS resource types, attributes, and best practices in mind, enforcing AWS Well-Architected Framework principles.
- Fail-Secure Defaults: Policies default to DENY when uncertain or validation fails
- Input Validation: Comprehensive validation and sanitization of all inputs
- Defense in Depth: Multiple layers of validation and error handling
- Least Privilege: Explicit allow lists rather than deny lists
- Comprehensive Error Handling: Actionable error messages for violations
# Input validation - fail secure on null/undefined
if resource is null {
print("SECURITY ERROR: Resource validation failed - null resource detected")
return false
}
# Check for computed values - handle gracefully but securely
if resource.change.after is computed {
print("WARNING: Applying conservative validation for computed resource")
return validate_with_computed_values(resource)
}
- Multi-service coverage: S3, RDS, EBS, EFS, DynamoDB, SNS, SQS, Lambda, KMS
- Customer-managed KMS key enforcement
- Environment-specific encryption controls
- Key rotation requirements
- Security Group ingress/egress validation with port-specific controls
- NACL rule ordering and conflict detection
- VPC security configuration validation
- Load Balancer and API Gateway security
- Environment-based cost limits with percentage controls
- Instance type restrictions by environment
- Storage optimization recommendations
- Cost allocation tagging enforcement
- Least privilege principle enforcement
- Trust relationship validation
- Privilege escalation risk detection
- Policy document security analysis
- Multi-framework support (SOC2, HIPAA, PCI-DSS, GDPR, SOX)
- Data classification and protection controls
- Audit trail requirements
- Change management compliance
- Terraform Enterprise or Terraform Cloud account
- Sentinel CLI (for local testing)
- Terraform CLI
- AWS CLI configured with appropriate credentials
- AWS account with necessary permissions
- OPA CLI
- conftest (optional, for easier testing)
- AWS CLI configured with appropriate credentials
- AWS account with necessary permissions
- Terragrunt CLI
- Terraform CLI
- jq (for JSON processing)
- AWS CLI configured with appropriate credentials
- AWS account with necessary permissions
-
Directory Structure:
. ├── terragrunt.hcl ├── env │ ├── prod │ │ └── terragrunt.hcl │ └── dev │ └── terragrunt.hcl └── policies ├── sentinel │ └── sentinel.hcl └── opa └── policy.rego -
Terragrunt Configuration:
# terragrunt.hcl terraform { before_hook "policy_check" { commands = ["plan", "apply"] execute = [ "bash", "-c", <<-EOF terragrunt show -json \ | opa eval --format pretty \ --data policies/opa \ --input - \ "data.terraform.deny" EOF ] } }
-
Running Policy Checks:
# Run Terragrunt with policy checks terragrunt plan # Run specific environment cd env/prod terragrunt plan
-
Policy Output Processing:
# Get policy violations for specific environment terragrunt show -json | jq -r '.resource_changes[] | select(.change.actions[] | contains("create"))'
-
Configure Sentinel in Terraform Enterprise/Cloud:
policy_set "security_policies" { name = "Security Policies" path = "./sentinel" enforcement_level = "hard-mandatory" }
-
Add Sentinel Files to Your Repository:
- Place
.sentinelfiles in your designated policy directory - Configure
sentinel.hclwith policy settings:
policy "enforce_encryption" { enforcement_level = "hard-mandatory" }
- Place
-
Local Testing:
sentinel test sentinel apply <policy-name>
-
Add OPA Files to Your Repository:
- Place
.regofiles in your designated policy directory - Structure policies using packages:
package terraform.policies - Place
-
Using with Terraform:
# Generate Terraform plan in JSON format terraform plan -out=tfplan terraform show -json tfplan > plan.json # Evaluate with OPA opa eval --data policy.rego --input plan.json "data.terraform.policies"
-
Using with conftest:
conftest test plan.json
import "tfplan/v2" as tfplan
# Example: Enforce encryption on AWS S3 buckets
aws_s3_encryption_required = rule {
all tfplan.resources.aws_s3_bucket as _, bucket {
bucket.applied.server_side_encryption_configuration is not null
}
}
# Example: Ensure AWS EC2 instances are not publicly accessible
aws_ec2_no_public_ip = rule {
all tfplan.resources.aws_instance as _, instance {
instance.applied.associate_public_ip_address is false
}
}package terraform.aws.policies
# Example: Deny AWS resources without proper encryption
deny[msg] {
resource := input.resource_changes[_]
aws_encrypted_resources := ["aws_s3_bucket", "aws_rds_instance", "aws_ebs_volume"]
resource.type == aws_encrypted_resources[_]
not is_encrypted(resource)
msg := sprintf("AWS Policy violation: %v must have encryption enabled", [resource.address])
}
# Helper function to check encryption for different AWS resource types
is_encrypted(resource) {
resource.type == "aws_s3_bucket"
resource.change.after.server_side_encryption_configuration != null
}
is_encrypted(resource) {
resource.type == "aws_rds_instance"
resource.change.after.storage_encrypted == true
}
is_encrypted(resource) {
resource.type == "aws_ebs_volume"
resource.change.after.encrypted == true
}-
GitHub Actions Workflow:
name: 'Terraform Plan and Policy Check' on: pull_request: branches: [ main ] push: branches: [ main ] jobs: terraform: name: 'Terraform and Policy Check' runs-on: ubuntu-latest env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} TERRAFORM_CLOUD_TOKEN: ${{ secrets.TF_CLOUD_TOKEN }} steps: # Checkout the repository - name: Checkout uses: actions/checkout@v2 # Install Terraform - name: Setup Terraform uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.0.0 cli_config_credentials_token: ${{ secrets.TF_CLOUD_TOKEN }} # Install Sentinel - name: Setup Sentinel run: | wget https://releases.hashicorp.com/sentinel/0.18.4/sentinel_0.18.4_linux_amd64.zip unzip sentinel_0.18.4_linux_amd64.zip sudo mv sentinel /usr/local/bin/ # Install OPA - name: Setup OPA run: | curl -L -o opa https://openpolicyagent.org/downloads/v0.42.0/opa_linux_amd64 chmod 755 opa sudo mv opa /usr/local/bin/ # Terraform Format Check - name: Terraform Format run: terraform fmt -check # Terraform Init - name: Terraform Init run: terraform init # Terraform Validate - name: Terraform Validate run: terraform validate # Terraform Plan - name: Terraform Plan run: | terraform plan -out=tfplan terraform show -json tfplan > plan.json # Sentinel Policy Check - name: Sentinel Policy Check run: | cd policies/sentinel sentinel test for f in *.sentinel; do sentinel apply "$f" done # OPA Policy Check - name: OPA Policy Check run: | cd policies/opa opa eval --data . --input ../../plan.json --format pretty "data.terraform.deny" # Comment Policy Results on PR - name: Comment Policy Results if: github.event_name == 'pull_request' uses: actions/github-script@v4 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const fs = require('fs'); const sentinelResults = fs.readFileSync('sentinel-results.txt', 'utf8'); const opaResults = fs.readFileSync('opa-results.txt', 'utf8'); const body = `### Policy Check Results #### Sentinel Policies \`\`\` ${sentinelResults} \`\`\` #### OPA Policies \`\`\` ${opaResults} \`\`\``; github.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: body });
-
Terraform Enterprise/Cloud Integration:
name: 'Terraform Enterprise Policy Check' on: pull_request: branches: [ main ] jobs: terraform: name: 'Terraform Enterprise Policy Check' runs-on: ubuntu-latest env: TF_TOKEN_app_terraform_io: ${{ secrets.TF_CLOUD_TOKEN }} steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Terraform uses: hashicorp/setup-terraform@v1 with: cli_config_credentials_token: ${{ secrets.TF_CLOUD_TOKEN }} - name: Terraform Init run: terraform init - name: Terraform Plan run: terraform plan # Terraform Cloud/Enterprise will automatically run policy checks # Results will be reported back to the GitHub PR
-
Pre-commit Hooks:
repos: - repo: local hooks: - id: policy-check name: Policy Check entry: conftest test language: system files: \.tf$
-
AWS Resource Tagging:
deny[msg] { resource := input.resource_changes[_] # Check AWS resources that support tagging aws_resources := ["aws_instance", "aws_s3_bucket", "aws_rds_instance", "aws_vpc"] resource.type == aws_resources[_] required_tags := ["Environment", "Owner", "CostCenter", "Project"] tag := required_tags[_] not resource.change.after.tags[tag] msg := sprintf("AWS Resource %v missing required tag: %v", [resource.address, tag]) } -
AWS Security Group Controls:
deny[msg] { sg := input.resource_changes[_] sg.type == "aws_security_group" rule := sg.change.after.ingress[_] rule.cidr_blocks[_] == "0.0.0.0/0" rule.to_port == 22 msg := sprintf("AWS Security Group %v: SSH port 22 cannot be open to 0.0.0.0/0", [sg.address]) } -
AWS S3 Bucket Security:
deny[msg] { bucket := input.resource_changes[_] bucket.type == "aws_s3_bucket" bucket.change.after.acl == "public-read" msg := sprintf("AWS S3 Bucket %v cannot have public-read ACL", [bucket.address]) } -
AWS RDS Encryption:
deny[msg] { rds := input.resource_changes[_] rds.type == "aws_db_instance" not rds.change.after.storage_encrypted msg := sprintf("AWS RDS instance %v must have storage encryption enabled", [rds.address]) }
# Run all tests
sentinel test
# Test specific policy
sentinel test <policy-name>.sentinel# Using OPA CLI
opa test . -v
# Using conftest
conftest verifyThe repository includes mock data for testing in:
examples/tests/mocks/- Mock Terraform plansexamples/tests/fixtures/- Test fixtures
- Fork the repository
- Create your feature branch
- Write or update tests
- Add or modify policies
- Submit a pull request
- AWS-Specific Focus: All policies must target AWS resources and services
- Write clear policy names and descriptions that specify AWS resource types
- Include comprehensive tests using AWS mock data
- Document any AWS-specific assumptions or prerequisites
- Follow existing policy structure for AWS resources
- Include both positive and negative test cases with AWS scenarios
- Reference AWS Well-Architected Framework principles where applicable
- Use AWS resource naming conventions (e.g.,
aws_s3_bucket,aws_ec2_instance) - Consider AWS regional differences and availability zones in policies