Terraform
TerraformIntermediate

Securing Your Terraform Code with Sentinel and Policy as Code

DevHub Team
5 min read
IaCSecurityDevOpsCompliance

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

``hcl

policy.sentinel

import "tfplan"

Main rule

main = rule {

all tfplan.resources.aws_instance as _, instances {

all instances as _, r {

r.applied.instance_type in ["t2.micro", "t3.micro"]

}

}

}

`

$1

$1

`hcl

restrict-instance-type.sentinel

import "tfplan"

Allowed instance types

allowed_types = [

"t2.micro",

"t2.small",

"t3.micro",

"t3.small",

]

Check instance types

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

}

`

$1

`hcl

enforce-tags.sentinel

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

}

`

$1

`hcl

secure-ports.sentinel

import "tfplan"

Restricted ports

restricted_ports = [22, 3389]

Check security group rules

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

}

`

$1

$1

`hcl

cost-control.sentinel

import "tfrun"

import "decimal"

Maximum allowed monthly cost

max_monthly_cost = decimal.new(1000)

Validate cost estimate

cost_estimate_valid = rule {

decimal.new(tfrun.cost_estimate.monthly_cost) <= max_monthly_cost

}

main = rule {

cost_estimate_valid

}

`

$1

`hcl

compliance.sentinel

import "tfplan"

import "strings"

Check encryption requirements

validate_encryption = rule {

all tfplan.resources.aws_ebs_volume as _, volumes {

all volumes as _, volume {

volume.applied.encrypted is true

}

}

}

Check backup requirements

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

}

`

$1

`hcl

network-security.sentinel

import "tfplan"

import "ip"

Allowed VPC CIDR ranges

allowed_vpc_cidrs = [

"10.0.0.0/8",

"172.16.0.0/12",

"192.168.0.0/16",

]

Validate VPC CIDR blocks

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

}

`

$1

$1

`hcl

mock-tfplan.sentinel

mock "tfplan" {

data = {

resources = {

aws_instance = {

"app_server" = {

applied = {

instance_type = "t2.micro"

tags = {

"Environment" = "production"

"Owner" = "DevOps"

}

}

}

}

}

}

}

`

$1

`hcl

test/restrict-instance-type/pass.hcl

test {

rules = {

main = true

}

}

mock "tfplan" {

module {

source = "mock-tfplan.sentinel"

}

}

`

$1

`hcl

policy-set.sentinel

policy "restrict-instance-type" {

enforcement_level = "hard-mandatory"

}

policy "enforce-tags" {

enforcement_level = "soft-mandatory"

}

policy "secure-ports" {

enforcement_level = "advisory"

}

`

$1

$1

`hcl

workspace-policy.sentinel

import "tfrun"

import "tfplan"

workspace_allowed = rule {

tfrun.workspace.name matches "^(prod|staging|dev)-*"

}

main = rule {

workspace_allowed

}

`

$1

`hcl

organization-policy.sentinel

import "tfrun"

import "tfconfig"

Validate provider configurations

validate_providers = rule {

all tfconfig.providers as _, provider {

provider.version_constraint is not null

}

}

Validate module sources

validate_modules = rule {

all tfconfig.modules as _, module {

module.source matches "^git@github.com:company/"

}

}

main = rule {

validate_providers and

validate_modules

}

`

$1

$1

`

policies/

├── common/

│ ├── required-tags.sentinel

│ └── allowed-providers.sentinel

├── security/

│ ├── encryption.sentinel

│ └── network-access.sentinel

└── compliance/

├── backup-retention.sentinel

└── audit-logging.sentinel

`

$1

`bash

Run policy checks

sentinel test

Test specific policy

sentinel test restrict-instance-type.sentinel

Verbose output

sentinel test -verbose

`

$1

`yaml

.github/workflows/sentinel.yml

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

``

$1

Implementing Sentinel policies helps:

  • Enforce security standards
  • Maintain compliance
  • Control costs
  • Standardize infrastructure
  • Prevent misconfigurations
  • 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.