Quick Answer
Sentinel policies are written as code that evaluates Terraform plans before apply. In banking, they enforce PCI-DSS requirements (encryption at rest, no public access), cost controls (instance size limits), tagging standards, and network segmentation rules — all as hard-mandatory checks that cannot be overridden.
Detailed Answer
Think of Sentinel as a building code inspector who reviews blueprints before construction begins. The architect (engineer) designs the building (infrastructure), submits the blueprints (Terraform plan) to the inspector (Sentinel), and construction (apply) only proceeds if the blueprints comply with all building codes (policies). Unlike a human inspector who might miss details or be inconsistent, Sentinel checks every single blueprint against every single code — automatically, consistently, and without exception. Sentinel policies in Terraform Enterprise evaluate the Terraform plan, state, and configuration before an apply is permitted. Each policy is a code file written in the Sentinel language that imports Terraform plan data and applies boolean logic to determine pass or fail. Policies are organized into policy sets, and each set is attached to specific workspaces. For a bank, you might have a 'pci-dss-mandatory' policy set attached to all production workspaces, a 'cost-governance' set attached to all workspaces, and a 'network-segmentation' set attached to networking workspaces. Policy sets reference a Git repository, so policies are version-controlled, peer-reviewed, and have their own CI/CD lifecycle. PCI-DSS compliance policies are the cornerstone for banking. These include: all storage resources (S3, EBS, RDS, EFS) must have encryption at rest enabled using customer-managed KMS keys (not AWS-managed keys), no security group rules can allow ingress from 0.0.0.0/0 on any port, all RDS instances must have automated backups with a minimum 7-day retention, all CloudTrail logs must be enabled and sent to a centralized logging account, all EC2 instances must be deployed in private subnets (no public IP assignment), and all load balancers must have access logging enabled. Each of these requirements becomes a Sentinel policy that evaluates the Terraform plan and blocks the apply if any resource violates the rule. Sentinel enforcement levels create a tiered governance model. Hard-mandatory policies cannot be overridden by anyone — they represent non-negotiable regulatory requirements like encryption at rest. Soft-mandatory policies can be overridden by workspace administrators with a documented reason — useful for temporary exceptions during migrations or emergency fixes. Advisory policies log warnings but do not block applies — useful for best practices that are not yet mandatory, like tagging standards for new cost centers. In banking, most PCI-DSS policies are hard-mandatory, cost governance policies are soft-mandatory (to allow approved exceptions for high-performance workloads), and new policy rollouts start as advisory for a sprint to identify false positives before becoming mandatory. Writing effective Sentinel policies requires understanding the Terraform plan structure. The tfplan/v2 import provides access to all resource changes (creates, updates, deletes), their before and after values, and the provider configuration. Policies filter resources by type (aws_s3_bucket, aws_db_instance), then assert conditions on their attributes. For complex policies, you can use Sentinel modules — reusable functions shared across policies. For example, a 'require_tags' module that checks for mandatory tags (cost-center, owner, data-classification, compliance-scope) can be imported by multiple policies. Testing Sentinel policies uses the sentinel test command with mock Terraform plan data, ensuring policies behave correctly before deployment. The biggest gotcha is policy sprawl and maintenance burden. As the organization adds more policies, engineers face longer feedback cycles — waiting for 50 policies to evaluate on every plan. Keep policies focused and fast by using resource-type filters early in the policy to skip irrelevant resources. Another gotcha is false positives — a policy that blocks legitimate infrastructure changes erodes trust in the governance system and leads to engineers finding workarounds. Invest in thorough policy testing with both passing and failing plan fixtures. Test edge cases like resource updates (not just creates), data sources, and resources managed by different providers. Finally, communicate policy changes clearly — when a new hard-mandatory policy is introduced, give teams a sprint to remediate existing infrastructure before enforcement begins.
Code Example
# Sentinel Policy: Enforce encryption at rest on all storage
# policies/pci-dss/enforce-encryption.sentinel
import "tfplan/v2" as tfplan
import "strings"
# Find all S3 buckets being created or updated
s3_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
# Find all RDS instances being created or updated
rds_instances = filter tfplan.resource_changes as _, rc {
rc.type is "aws_db_instance" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
# Find all EBS volumes
ebs_volumes = filter tfplan.resource_changes as _, rc {
rc.type is "aws_ebs_volume" and
rc.change.actions contains "create"
}
# Rule: S3 buckets must have server-side encryption
s3_encrypted = rule {
all s3_buckets as _, bucket {
bucket.change.after.server_side_encryption_configuration is not null
}
}
# Rule: RDS must use encryption with customer-managed KMS key
rds_encrypted = rule {
all rds_instances as _, db {
db.change.after.storage_encrypted is true and
db.change.after.kms_key_id is not null and
not strings.has_prefix(db.change.after.kms_key_id, "alias/aws/")
}
}
# Rule: EBS volumes must be encrypted
ebs_encrypted = rule {
all ebs_volumes as _, vol {
vol.change.after.encrypted is true
}
}
main = rule {
s3_encrypted and rds_encrypted and ebs_encrypted
}
---
# Sentinel Policy: No public network access
# policies/pci-dss/no-public-access.sentinel
import "tfplan/v2" as tfplan
# Find security group rules allowing 0.0.0.0/0
sg_rules = filter tfplan.resource_changes as _, rc {
rc.type is "aws_security_group_rule" and
rc.change.actions contains "create" and
rc.change.after.type is "ingress"
}
no_open_ingress = rule {
all sg_rules as _, rule {
rule.change.after.cidr_blocks not contains "0.0.0.0/0"
}
}
# No EC2 instances with public IPs
ec2_instances = filter tfplan.resource_changes as _, rc {
rc.type is "aws_instance" and
rc.change.actions contains "create"
}
no_public_ec2 = rule {
all ec2_instances as _, instance {
instance.change.after.associate_public_ip_address is false or
instance.change.after.associate_public_ip_address is null
}
}
main = rule {
no_open_ingress and no_public_ec2
}
---
# Sentinel Policy: Mandatory tagging for audit trail
# policies/governance/mandatory-tags.sentinel
import "tfplan/v2" as tfplan
# Required tags for all taggable resources
required_tags = ["cost-center", "owner", "data-classification", "compliance-scope"]
# All resources that support tags
taggable_resources = filter tfplan.resource_changes as _, rc {
rc.change.actions contains "create" and
rc.change.after.tags is not null
}
mandatory_tags = rule {
all taggable_resources as _, resource {
all required_tags as tag {
resource.change.after.tags contains tag
}
}
}
main = rule { mandatory_tags }
---
# Policy set configuration in Terraform
resource "tfe_policy_set" "pci_dss" {
name = "pci-dss-mandatory"
description = "PCI-DSS compliance policies - hard mandatory"
organization = "bank-platform"
kind = "sentinel"
enforcement_mode = "hard-mandatory" # Cannot be overridden
vcs_repo {
identifier = "bank/sentinel-policies"
branch = "main"
oauth_token_id = var.github_oauth_id
}
# Attach to all production workspaces
workspace_ids = [
tfe_workspace.payments_prod.id,
tfe_workspace.settlements_prod.id,
tfe_workspace.fraud_detector_prod.id,
]
}◈ Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐ │ Sentinel Policy Enforcement in TFE │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌───────────────────────┐ │ │ │ Engineer │───→│ Git Push / │───→│ TFE Workspace │ │ │ │ writes │ │ PR Merge │ │ Queues Plan │ │ │ │ Terraform│ └──────────────┘ └───────────┬───────────┘ │ │ └──────────┘ │ │ │ ▼ │ │ ┌────────────────────────────────┐ │ │ │ Terraform Plan Generated │ │ │ └────────────────┬───────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Sentinel Policy Evaluation │ │ │ │ │ │ │ │ ┌─────────────────────┐ Enforcement Levels: │ │ │ │ │ PCI-DSS Policies │ ■ Hard-mandatory (no override) │ │ │ │ │ • Encryption at rest│ ■ Soft-mandatory (admin override)│ │ │ │ │ • No public access │ ■ Advisory (warn only) │ │ │ │ │ • Audit logging │ │ │ │ │ └─────────────────────┘ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ Cost Governance │ │ │ │ │ │ • Instance limits │ │ │ │ │ │ • Tag requirements │ │ │ │ │ └─────────────────────┘ │ │ │ └───────────────┬──────────────────────┬─────────────────────┘ │ │ │ │ │ │ ┌──────▼──────┐ ┌──────▼──────┐ │ │ │ PASS │ │ FAIL │ │ │ │ → Approve │ │ → Block │ │ │ │ & Apply │ │ Apply │ │ │ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────────┘
Quick Answer
Terraform Cloud enforces governance through Sentinel policies that evaluate plans as code before apply, cost estimation that flags unexpected spend, run tasks that integrate external checks like security scanners, and VCS workflows that trigger plans on pull requests. This shifts enforcement left into the plan phase so teams get fast feedback without needing manual approval for every change.
Detailed Answer
Think of a highway with automated safety systems. Speed cameras (Sentinel policies) automatically flag violations, fuel cost displays (cost estimation) warn drivers before they commit to a route, roadside inspection stations (run tasks) check specific safety requirements, and GPS-guided lanes (VCS workflows) route each vehicle through the correct path. The highway keeps moving because enforcement is automated, not manual. Terraform Cloud Enterprise provides a collaborative platform where infrastructure changes follow a standardized workflow: code is committed to VCS, a plan is triggered, governance checks run, and apply executes only after all checks pass. Sentinel is HashiCorp's policy-as-code framework that evaluates Terraform plans, state, and configuration using a policy language. Policies can enforce rules like requiring encryption on all S3 buckets, restricting instance types to cost-approved sizes, mandating specific tags on every resource, or preventing deletion of production databases. Policies are organized into policy sets that are applied to specific workspaces or all workspaces in an organization. Internally, the run pipeline processes stages in order: VCS trigger, terraform plan, cost estimation, Sentinel policy check, run tasks, and terraform apply. Cost estimation parses the plan output and calculates the monthly cost delta using HashiCorp's pricing database, surfacing changes like adding a db.r6g.2xlarge that increases monthly spend by $1,200. Run tasks are webhook-based integrations that send the plan JSON to external systems — security scanners like Snyk or Prisma Cloud, compliance checkers, or custom approval systems — and wait for a pass/fail response. Each run task can be advisory (warning only) or mandatory (blocking apply). The entire pipeline runs automatically on pull request creation, giving developers feedback in minutes rather than waiting for a manual review. At production scale, governance design requires balancing safety with velocity. Hard-mandatory Sentinel policies should cover non-negotiable rules like encryption and tagging. Soft-mandatory policies allow overrides with justification for edge cases like temporary large instances for data migration. Advisory policies educate teams about best practices without blocking. Cost estimation thresholds can be set to require manager approval for changes exceeding a dollar amount. VCS workflows should use speculative plans on pull requests (plan only, no apply) so developers see the impact before merging, and auto-apply on the main branch for environments like dev where speed matters more than manual gates. The non-obvious gotcha is that Sentinel policies execute after the plan phase, so they cannot prevent Terraform from planning invalid configurations — they can only block the apply. If a Sentinel policy references a resource attribute that does not exist in the plan (because the resource was removed), the policy can fail with a confusing error rather than a clean policy violation. Teams should test Sentinel policies against mock plan data in CI using the Sentinel CLI before deploying them to Terraform Cloud. Another trap is over-engineering run tasks: each run task adds latency to the pipeline, and if the external system is slow or unreliable, it blocks every infrastructure change across the organization.
Code Example
# Sentinel policy: require encryption on all S3 buckets
# policies/s3-encryption-required.sentinel
import "tfplan/v2" as tfplan
# Find all S3 bucket resources being created or updated
s3_buckets = filter tfplan.resource_changes as _, rc {
# Match only aws_s3_bucket resources with create or update actions
rc.type is "aws_s3_bucket" and
rc.mode is "managed" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
# Check that every bucket has a server-side encryption configuration
encryption_check = rule {
all s3_buckets as _, bucket {
# Verify the bucket_encryption block is not null after apply
bucket.change.after.server_side_encryption_configuration is not null
}
}
# Main rule that must pass for the apply to proceed
main = rule {
encryption_check
}
# sentinel.hcl — Policy set configuration
# policy "s3-encryption-required" {
# source = "./policies/s3-encryption-required.sentinel"
# enforcement_level = "hard-mandatory" # Cannot be overridden
# }
# terraform-cloud workspace configuration via CLI
# Create a workspace connected to VCS with auto-apply disabled for production
# terraform login
# terraform workspace new payments-prod -organization=company
# .terraform-cloud.auto.tfvars — Workspace variable defaults
# These are set in the Terraform Cloud UI or API for the workspace
# environment = "prod"
# team = "payments"
# cost_threshold = 500
# Run task configuration via API (register a security scanner)
# curl -s -X POST \
# -H "Authorization: Bearer $TFC_TOKEN" \
# -H "Content-Type: application/vnd.api+json" \
# https://app.terraform.io/api/v2/organizations/company/tasks \
# -d '{"data":{"type":"tasks","attributes":{"name":"snyk-iac-scan","url":"https://hooks.snyk.io/terraform-cloud","category":"task","hmac-key":"secret-key"}}}'◈ Architecture Diagram
┌──────────┐
│ VCS Push │
└────┬─────┘
↓
┌──────────┐
│ tf plan │
└────┬─────┘
↓
┌──────────┐
│ Cost Est │
└────┬─────┘
↓
┌──────────┐
│ Sentinel │
└────┬─────┘
↓
┌──────────┐
│ Run Task │
└────┬─────┘
↓
┌──────────┐
│ tf apply │
└──────────┘