TL;DR
Learn how to implement security policies and governance for your infrastructure using HashiCorp Sentinel
Securing Your Terraform Code with Sentinel
Learn how to implement security policies and governance for your infrastructure using HashiCorp Sentinel and Policy as Code principles.
$1
Sentinel is HashiCorp's policy as code framework that enables fine-grained, logic-based policy decisions that can be enforced across all HashiCorp products.
$1
1. Automated policy enforcement
2. Version-controlled policies
3. Test-driven policy development
4. Integration with Terraform Enterprise/Cloud
5. Customizable rules and logic
$1
`` import "tfplan" main = rule {
all tfplan.resources.aws_instance as _, instances {
all instances as _, r {
r.applied.instance_type in ["t2.micro", "t3.micro"]
}
}
}
hcl
`policy.sentinel
Main rule
$1
$1
` import "tfplan" allowed_types = [
"t2.micro",
"t2.small",
"t3.micro",
"t3.small",
] instance_type_allowed = rule {
all tfplan.resources.aws_instance as _, instances {
all instances as _, r {
r.applied.instance_type in allowed_types
}
}
} main = rule {
instance_type_allowed
}
hcl
`restrict-instance-type.sentinel
Allowed instance types
Check instance types
$1
` import "tfplan" required_tags = [
"Environment",
"Owner",
"CostCenter",
] validate_tags = func(tags) {
for required_tags as rt {
if length(filter tags as _, t { t == rt }) == 0 {
return false
}
}
return true
} tag_policy = rule {
all tfplan.resources.aws_instance as _, instances {
all instances as _, r {
validate_tags(keys(r.applied.tags))
}
}
} main = rule {
tag_policy
}
hcl
`enforce-tags.sentinel
$1
` import "tfplan" restricted_ports = [22, 3389] validate_sg_rules = rule {
all tfplan.resources.aws_security_group as _, sgs {
all sgs as _, sg {
all sg.applied.ingress as ingress {
!(ingress.from_port in restricted_ports and
ingress.cidr_blocks contains "0.0.0.0/0")
}
}
}
} main = rule {
validate_sg_rules
}
hcl
`secure-ports.sentinel
Restricted ports
Check security group rules
$1
$1
` import "tfrun"
import "decimal" max_monthly_cost = decimal.new(1000) cost_estimate_valid = rule {
decimal.new(tfrun.cost_estimate.monthly_cost) <= max_monthly_cost
} main = rule {
cost_estimate_valid
}
hcl
`cost-control.sentinel
Maximum allowed monthly cost
Validate cost estimate
$1
` import "tfplan"
import "strings" validate_encryption = rule {
all tfplan.resources.aws_ebs_volume as _, volumes {
all volumes as _, volume {
volume.applied.encrypted is true
}
}
} validate_backups = rule {
all tfplan.resources.aws_db_instance as _, dbs {
all dbs as _, db {
db.applied.backup_retention_period >= 7
}
}
} main = rule {
validate_encryption and
validate_backups
}
hcl
`compliance.sentinel
Check encryption requirements
Check backup requirements
$1
` import "tfplan"
import "ip" allowed_vpc_cidrs = [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
] validate_vpc_cidrs = rule {
all tfplan.resources.aws_vpc as _, vpcs {
all vpcs as _, vpc {
any allowed_vpc_cidrs as allowed_cidr {
ip.in_cidr(vpc.applied.cidr_block, allowed_cidr)
}
}
}
} main = rule {
validate_vpc_cidrs
}
hcl
`network-security.sentinel
Allowed VPC CIDR ranges
Validate VPC CIDR blocks
$1
$1
` mock "tfplan" {
data = {
resources = {
aws_instance = {
"app_server" = {
applied = {
instance_type = "t2.micro"
tags = {
"Environment" = "production"
"Owner" = "DevOps"
}
}
}
}
}
}
}
hcl
`mock-tfplan.sentinel
$1
` test {
rules = {
main = true
}
} mock "tfplan" {
module {
source = "mock-tfplan.sentinel"
}
}
hcl
`test/restrict-instance-type/pass.hcl
$1
` policy "restrict-instance-type" {
enforcement_level = "hard-mandatory"
} policy "enforce-tags" {
enforcement_level = "soft-mandatory"
} policy "secure-ports" {
enforcement_level = "advisory"
}
hcl
`policy-set.sentinel
$1
$1
` import "tfrun"
import "tfplan" workspace_allowed = rule {
tfrun.workspace.name matches "^(prod|staging|dev)-*"
} main = rule {
workspace_allowed
}
hcl
`workspace-policy.sentinel
$1
` import "tfrun"
import "tfconfig" validate_providers = rule {
all tfconfig.providers as _, provider {
provider.version_constraint is not null
}
} validate_modules = rule {
all tfconfig.modules as _, module {
module.source matches "^git@github.com:company/"
}
} main = rule {
validate_providers and
validate_modules
}
hcl
`organization-policy.sentinel
Validate provider configurations
Validate module sources
$1
$1
` policies/
├── common/
│ ├── required-tags.sentinel
│ └── allowed-providers.sentinel
├── security/
│ ├── encryption.sentinel
│ └── network-access.sentinel
└── compliance/
├── backup-retention.sentinel
└── audit-logging.sentinel
`
$1
` sentinel test sentinel test restrict-instance-type.sentinel sentinel test -verbose
bash
`Run policy checks
Test specific policy
Verbose output
$1
` name: Sentinel Policy Checks on: [pull_request] jobs:
sentinel:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install 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/
- name: Run Sentinel Tests
run: |
cd policies
sentinel test
yaml
``.github/workflows/sentinel.yml
$1
Implementing Sentinel policies helps:
Remember to:
1. Start with basic policies
2. Test thoroughly
3. Implement gradually
4. Monitor and adjust
5. Keep policies version controlled
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.