Terraform Guardrails
Enforce Terraform best practices such as provider/module version pinning and remote backend configuration, plus AWS resource security checks relevant to SOC 2: encryption at rest, logging, public-access blocking, network ingress limits, WAF, GuardDuty, and VPC flow logs.
terraform to your lunar-config.yml:uses: github://earthly/lunar-lib/policies/terraform@v1.0.5
Included Guardrails
This policy includes 33 guardrails that enforce standards for your deployment and infrastructure.
provider-versions-pinned
Requires Terraform providers to specify version constraints in required_providers. Unpinned providers can introduce breaking changes unexpectedly.
module-versions-pinned
Requires Terraform modules to use pinned versions or commit SHAs. Unpinned modules make infrastructure deployments non-reproducible.
remote-backend
Requires Terraform to use a remote backend for state management. Local state files are fragile and cannot be shared across teams.
min-provider-versions
Enforces minimum version requirements for Terraform providers. Ensures providers meet security and compatibility baselines.
aws-alb-waf-enabled
Requires every internet-facing Application Load Balancer to have an AWS WAF web ACL associated. Unprotected public ALBs are exposed to common web exploits such as SQL injection and cross-site scripting.
aws-cloudtrail-multi-region
Requires a multi-region CloudTrail trail that delivers events to CloudWatch Logs, so API activity is captured across every region and available for monitoring and alerting.
aws-security-group-no-public-postgres
Requires that no security group allows unrestricted ingress (0.0.0.0/0 or ::/0) to the PostgreSQL port. Databases must never be reachable directly from the public internet.
aws-security-group-no-public-ssh
Requires that no security group allows unrestricted ingress (0.0.0.0/0 or ::/0) to the SSH port. SSH must be reached through a bastion or VPN, never open to the entire internet.
aws-eks-control-plane-logging
Requires EKS clusters to enable control-plane logging to CloudWatch for the required log types (api, audit, authenticator, controllerManager, scheduler), giving auditable visibility into cluster activity.
aws-elb-access-logging
Requires Elastic Load Balancers to have access logging enabled so request traffic is recorded to S3 for audit, troubleshooting, and security investigations.
aws-ebs-snapshot-encryption
Requires EBS snapshots to be encrypted at rest. Snapshots inherit data from their source volumes and must not leave that data unencrypted when copied or shared.
aws-ebs-volume-encryption
Requires EBS volumes (standalone and instance block devices) to be encrypted at rest, protecting data on disk with KMS-managed keys.
aws-elb-https-only
Requires load balancers to enforce encrypted transport. HTTP listeners must redirect to HTTPS, and listeners must use HTTPS/TLS so traffic in transit is never sent in cleartext.
aws-guardduty-enabled
Requires Amazon GuardDuty to be enabled so the account has continuous threat detection across CloudTrail, VPC flow, and DNS telemetry.
aws-rds-cloudwatch-logging
Requires RDS instances and clusters to export database logs to CloudWatch, so engine, audit, and error logs are retained off-host for monitoring.
aws-s3-block-public-access
Requires every S3 bucket to have public access fully blocked through a public access block configuration, preventing accidental public exposure of stored objects.
aws-s3-access-logging
Requires S3 buckets to have server access logging enabled, recording every request to a logging bucket for audit and forensic purposes.
aws-vpc-flow-logs
Requires every VPC to have flow logs enabled, capturing accepted and rejected network traffic for security monitoring and incident response.
aws-security-group-no-public-admin-ports
Requires that no security group allows unrestricted ingress (0.0.0.0/0 or ::/0) to sensitive admin or database ports (RDP, MSSQL, Oracle, MySQL, Telnet, SMB, Elasticsearch, and more). SSH and PostgreSQL have their own dedicated checks.
aws-rds-encryption-at-rest
Requires RDS instances and clusters to set storage_encrypted = true so database storage is encrypted at rest with KMS. Replicas and snapshot/PITR restores that inherit encryption are not flagged.
aws-rds-not-publicly-accessible
Requires RDS instances to keep publicly_accessible = false so databases are not reachable directly from the public internet.
aws-rds-snapshot-encryption
Requires RDS snapshots to be encrypted at rest. Snapshots inherit encryption from their source DB, so this verifies the referenced instance or cluster sets storage_encrypted = true.
aws-s3-encryption-at-rest
Requires every S3 bucket to declare a server-side encryption configuration, making encryption at rest an explicit and auditable choice rather than relying on the implicit default.
aws-s3-no-static-website
Forbids S3 buckets from hosting a public static website, which would serve bucket contents directly to the internet.
aws-s3-no-public-acl
Forbids S3 buckets from granting public access through canned ACLs (public-read/public-read-write) or grants to the AllUsers/AuthenticatedUsers groups.
aws-iam-password-min-length
Requires an IAM account password policy that enforces a minimum password length (default 14). The account password policy is a global baseline, so its absence is treated as a violation.
aws-iam-no-direct-user-policies
Forbids attaching IAM policies (inline or managed) directly to users. Policies should be attached to groups or roles so access is granted through reviewable, reusable boundaries.
aws-acm-cert-dns-validation
Requires ACM certificates to use DNS validation so issuance and renewal are automatic and auditable. Imported certificates are not flagged.
aws-eks-private-endpoint
Requires EKS clusters to enable private API-server endpoint access, so the control plane is reachable from within the VPC rather than only over the public internet.
aws-dynamodb-encryption
Requires DynamoDB tables to declare server-side encryption explicitly. DynamoDB is encrypted by default with an AWS-owned key; this makes encryption (optionally customer-managed) a deliberate, auditable choice.
aws-lambda-not-public
Forbids Lambda functions from being publicly invokable through a principal "*" permission without a source scope, or a function URL with authorization_type = NONE.
aws-cloudtrail-log-file-validation
Requires CloudTrail trails to enable log-file integrity validation so tampering with delivered logs can be detected.
aws-cloudtrail-kms-encryption
Requires CloudTrail trails to encrypt delivered logs with a KMS customer-managed key.
How Guardrails Fit into Lunar
Lunar guardrails define your engineering standards as code. They evaluate data collected by integrations and produce pass/fail checks with actionable feedback.
Policies support gradual enforcement—from silent scoring to blocking PRs or deployments—letting you roll out standards at your own pace without disrupting existing workflows.
Learn How Lunar Works →Required Integrations
This policy evaluates data gathered by one or more of the following integration(s).
Make sure to enable them in your lunar-config.yml.
Configuration
Configure this policy in your lunar-config.yml.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
required_backend_types
|
Required | — | Comma-separated list of approved backend types (empty = any remote backend) |
min_provider_versions
|
Optional |
{}
|
JSON object mapping provider names to minimum versions (e.g., {"aws": "5.0", "random": "3.0"}) |
ssh_port
|
Optional |
22
|
TCP port treated as SSH for the public-ingress check |
postgres_port
|
Optional |
5432
|
TCP port treated as PostgreSQL for the public-ingress check |
eks_required_log_types
|
Optional |
api,audit,authenticator,controllerManager,scheduler
|
Comma-separated EKS control-plane log types that must be enabled |
require_cloudtrail_cloudwatch
|
Optional |
true
|
Whether CloudTrail must also deliver logs to CloudWatch Logs (true/false) |
extra_admin_ports
|
Required | — | Additional comma-separated TCP ports to treat as sensitive for the public admin-ports check |
min_password_length
|
Optional |
14
|
Minimum IAM account password length required by aws-iam-password-min-length |
Documentation
View on GitHubTerraform Guardrails
Enforces Terraform-specific configuration best practices.
Overview
This policy enforces Terraform-specific standards that don't transfer to other IaC frameworks: provider version pinning, module version pinning, and remote backend usage. It also bundles a set of AWS resource security checks relevant to SOC 2 — such as encryption at rest, logging, public-access blocking, and network ingress limits — each individually includable. All checks read from .iac.native.terraform.files[] and analyze the parsed HCL. For generic IaC checks (validity, WAF, datastores), see the iac policy.
Policies
This policy provides the following guardrails (use include to select a subset):
| Policy | Description | Failure Meaning |
|---|---|---|
provider-versions-pinned |
Providers specify version constraints | Provider in required_providers has no version field |
module-versions-pinned |
Modules use pinned versions | Module missing version or ?ref= in source |
remote-backend |
Remote backend configured | No terraform { backend {} } block found |
min-provider-versions |
Providers meet minimum version requirements | Provider version constraint below required minimum |
The checks below are AWS resource security guardrails relevant to SOC 2 (tagged with the soc2 keyword). Each is individually includable — a future SOC 2 starter-pack can bundle them all:
| Policy | Description | Failure Meaning |
|---|---|---|
aws-alb-waf-enabled |
Public ALBs have a WAF web ACL associated | Internet-facing aws_lb with no aws_wafv2_web_acl_association |
aws-cloudtrail-multi-region |
CloudTrail is multi-region and ships to CloudWatch | No multi-region aws_cloudtrail, or no CloudWatch Logs group |
aws-security-group-no-public-postgres |
No public ingress to PostgreSQL | A security group allows 0.0.0.0/0 to port 5432 |
aws-security-group-no-public-ssh |
No public ingress to SSH | A security group allows 0.0.0.0/0 to port 22 |
aws-eks-control-plane-logging |
EKS control-plane logging enabled | aws_eks_cluster missing required enabled_cluster_log_types |
aws-elb-access-logging |
Load balancers log access requests | aws_lb / aws_elb without access logging enabled |
aws-ebs-snapshot-encryption |
EBS snapshots encrypted at rest | aws_ebs_snapshot without encrypted = true |
aws-ebs-volume-encryption |
EBS volumes encrypted at rest | aws_ebs_volume or block device without encrypted = true |
aws-elb-https-only |
Load balancers enforce HTTPS/TLS | Plaintext HTTP listener without an HTTPS redirect |
aws-guardduty-enabled |
GuardDuty detector enabled | No aws_guardduty_detector with enable = true |
aws-rds-cloudwatch-logging |
RDS exports logs to CloudWatch | RDS instance/cluster without enabled_cloudwatch_logs_exports |
aws-s3-block-public-access |
S3 buckets block public access | Bucket without a full aws_s3_bucket_public_access_block |
aws-s3-access-logging |
S3 buckets log access requests | Bucket without server access logging configured |
aws-vpc-flow-logs |
VPCs have flow logs enabled | aws_vpc without a matching aws_flow_log |
aws-security-group-no-public-admin-ports |
No public ingress to sensitive admin/database ports | A security group allows 0.0.0.0/0 to a port like RDP, MySQL, MSSQL, or Telnet |
aws-rds-encryption-at-rest |
RDS storage is encrypted at rest | aws_db_instance / aws_rds_cluster without storage_encrypted = true |
aws-rds-not-publicly-accessible |
RDS is not publicly accessible | An RDS instance sets publicly_accessible = true |
aws-rds-snapshot-encryption |
RDS snapshots encrypted at rest | Snapshot whose source DB is not storage_encrypted |
aws-s3-encryption-at-rest |
S3 buckets declare encryption at rest | Bucket without a server-side encryption configuration |
aws-s3-no-static-website |
S3 buckets do not host static websites | Bucket with a website configuration (public hosting) |
aws-s3-no-public-acl |
S3 buckets do not grant public ACL access | Bucket with a public-read/public-read-write ACL or AllUsers grant |
aws-iam-password-min-length |
IAM password policy enforces a minimum length | No aws_iam_account_password_policy, or length below the minimum |
aws-iam-no-direct-user-policies |
No IAM policies attached directly to users | Inline/managed policy attached to a user instead of a group or role |
aws-acm-cert-dns-validation |
ACM certificates use DNS validation | aws_acm_certificate using EMAIL (or unset) validation |
aws-eks-private-endpoint |
EKS clusters enable private endpoint access | Cluster without endpoint_private_access / cluster_endpoint_private_access |
aws-dynamodb-encryption |
DynamoDB tables declare encryption at rest | Table without a server_side_encryption { enabled = true } block |
aws-lambda-not-public |
Lambda functions are not publicly invokable | principal = "*" permission without source scope, or function URL with authorization_type = NONE |
aws-cloudtrail-log-file-validation |
CloudTrail validates log-file integrity | Trail without enable_log_file_validation = true |
aws-cloudtrail-kms-encryption |
CloudTrail logs encrypted with KMS | Trail without kms_key_id |
Required Data
This policy reads from the following Component JSON paths:
| Path | Type | Provided By |
|---|---|---|
.iac.native.terraform.files[] |
array | terraform collector |
Note: Ensure the terraform collector is configured before enabling this policy.
Installation
Add to your lunar-config.yml:
collectors:
- uses: github://earthly/lunar-lib/collectors/terraform@main
on: [infra]
policies:
- uses: github://earthly/lunar-lib/policies/terraform@main
on: [infra]
enforcement: report-pr
# include: [provider-versions-pinned, remote-backend] # Only run specific checks
# with:
# required_backend_types: "s3,gcs,remote" # Restrict allowed backend types
# min_provider_versions: '{"aws": "5.0", "random": "3.0"}' # Enforce minimum versions
Examples
Passing Example
A component with pinned providers, versioned modules, and a remote backend:
{
"iac": {
"native": {
"terraform": {
"files": [
{
"path": "main.tf",
"hcl": {
"terraform": [
{
"required_providers": [
{
"aws": {"source": "hashicorp/aws", "version": "~> 5.0"},
"random": {"source": "hashicorp/random", "version": ">= 3.0"}
}
],
"backend": [
{"s3": {"bucket": "my-state", "key": "state.tfstate"}}
]
}
],
"module": {
"vpc": [{"source": "terraform-aws-modules/vpc/aws", "version": "5.1.0"}]
}
}
}
]
}
}
}
}
Failing Example
A component with unpinned providers, unversioned modules, and no backend:
{
"iac": {
"native": {
"terraform": {
"files": [
{
"path": "main.tf",
"hcl": {
"terraform": [
{
"required_providers": [
{
"aws": {"source": "hashicorp/aws"},
"random": {"source": "hashicorp/random"}
}
]
}
],
"module": {
"vpc": [{"source": "terraform-aws-modules/vpc/aws"}],
"rds": [{"source": "git::https://github.com/org/tf-module.git"}]
}
}
}
]
}
}
}
}
Failure messages:
Providers without version constraints: aws, random. Add version constraints in required_providers to ensure reproducible deployments.Modules without pinned versions: vpc, rds. Add version constraints or use ?ref= to pin module sources.No backend configured. Terraform state is stored locally, which is fragile and cannot be shared across teams.
Remediation
When this policy fails, resolve it by:
- For
provider-versions-pinnedfailures: Addversionconstraints to each provider inrequired_providers:terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } - For
module-versions-pinnedfailures: Addversionto registry modules or?ref=to git sources:module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.1.0" } - For
remote-backendfailures: Configure a remote backend for shared state:terraform { backend "s3" { bucket = "my-terraform-state" key = "state.tfstate" region = "us-east-1" } }
Open Source
This policy is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Common Use Cases
Explore how individual guardrails work with specific integrations.
Ready to Automate Your Standards?
See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 200+ built-in guardrails.