Securing Terraform Atlantis in production (June 2025)
Step-by-step guide to harden Terraform Atlantis: secure secrets, enforce policies, isolate environments and automate audits for production.
tl;dr
Terraform Atlantis automates infrastructure changes through pull requests, making it a critical attack surface that requires comprehensive security measures.
The most critical immediate action is updating to Atlantis v0.30.0+ to address CVE-2024-52009, which exposes GitHub credentials in logs. Beyond patching, organizations must implement defense-in-depth strategies covering network security, authentication, secrets management, and operational monitoring.
Critical Security Vulnerabilities and Immediate Actions
CVE-2024-52009: GitHub credential exposure requires urgent patching
Severity: Critical - GitHub tokens are exposed in Atlantis logs during rotation, allowing attackers with log access to impersonate Atlantis and gain administrative privileges.
Immediate Action Required:
# Check your current version
atlantis version
# Update to v0.30.0 or later
docker pull ghcr.io/runatlantis/atlantis:v0.30.0
# Review logs for exposed tokens
grep -r "ghs_" /var/log/atlantis/
Additional Active Vulnerabilities:
- Multiple Go runtime CVEs affecting container images
- Alpine Linux vulnerabilities in base images
- Design-level risks from malicious Terraform code execution
Network Security and Infrastructure Hardening
Implement network isolation with defense-in-depth architecture
Essential Firewall Configuration:
# Core iptables rules
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP
# Allow webhook traffic only from VCS providers
-A INPUT -p tcp --dport 4141 -s 140.82.112.0/20 -j ACCEPT # GitHub
-A INPUT -p tcp --dport 4141 -s 35.232.176.0/20 -j ACCEPT # GitLab
# Essential outbound traffic only
-A OUTPUT -p tcp --dport 443 -j ACCEPT # HTTPS
-A OUTPUT -p tcp --dport 22 -j ACCEPT # SSH for Git
-A OUTPUT -p udp --dport 53 -j ACCEPT # DNS
Kubernetes Network Policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: atlantis-network-policy
spec:
podSelector:
matchLabels:
app: atlantis
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-controllers
ports:
- port: 4141
egress:
- to:
- namespaceSelector: {}
ports:
- port: 443
- port: 22
Container and OS hardening prevents privilege escalation
Secure Kubernetes Deployment:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: atlantis
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 100
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: atlantis
image: ghcr.io/runatlantis/atlantis:v0.30.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
resources:
limits:
cpu: 2
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
SSL/TLS Configuration:
atlantis server \
--ssl-cert-file="/etc/ssl/certs/atlantis.crt" \
--ssl-key-file="/etc/ssl/private/atlantis.key" \
--atlantis-url="https://atlantis.company.com"
VCS Integration Security
Webhook security prevents unauthorized command execution
GitHub Webhook Configuration:
# Generate secure webhook secret
WEBHOOK_SECRET=$(openssl rand -hex 32)
# Configure Atlantis
atlantis server \
--gh-webhook-secret="$WEBHOOK_SECRET" \
--repo-allowlist="github.com/company/*,!github.com/company/untrusted-*" \
--allow-fork-prs=false
Repository Security Configuration (atlantis.yaml):
version: 3
automerge: false
parallel_plan: true
parallel_apply: false
projects:
- name: production
dir: ./environments/prod
workflow: secure-production
apply_requirements: [approved, mergeable]
import_requirements: [approved, mergeable]
plan_requirements: [mergeable]
workflows:
secure-production:
plan:
steps:
- init
- run: |
# Validate no hardcoded credentials
if grep -r "AKIA\|secret_key\|password" .; then
echo "Potential credentials detected"
exit 1
fi
- plan
apply:
steps:
- apply
Branch protection enforces approval workflows
GitHub Branch Protection Settings:
- Require pull request reviews (minimum 2 for production)
- Dismiss stale reviews on new commits
- Require review from CODEOWNERS
- Require status checks including Atlantis
- Include administrators in restrictions
CODEOWNERS Configuration:
# Global infrastructure requires DevOps approval
*.tf @company/devops-team
# Production requires additional security review
/production/ @company/senior-devops @company/security-team
# Security-sensitive resources
**/iam/ @company/security-team
**/security-groups/ @company/security-team
Cloud Provider Credential Security
IAM roles eliminate long-lived credentials
AWS AssumeRole Configuration:
provider "aws" {
assume_role {
role_arn = "arn:aws:iam::${var.account_id}:role/AtlantisRole"
session_name = "atlantis-${var.atlantis_user}-${var.atlantis_pull_num}"
duration_seconds = 3600 # 1 hour maximum
}
}
Trust Policy with Conditions:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ATLANTIS_ACCOUNT:role/atlantis-ecs-task"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id"
}
}
}]
}
Vault integration provides dynamic credentials
Vault AWS Dynamic Credentials:
resource "vault_aws_secret_backend_role" "atlantis" {
backend = vault_aws_secret_backend.aws.path
name = "atlantis-role"
credential_type = "assumed_role"
role_arns = ["arn:aws:iam::ACCOUNT:role/AtlantisVaultRole"]
default_sts_ttl = 3600
max_sts_ttl = 7200
}
Custom Workflow with Vault:
workflows:
vault-auth:
plan:
steps:
- run: |
export VAULT_TOKEN=$(vault write -field=token auth/kubernetes/login \
role=atlantis jwt=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token))
- init
- plan
Kubernetes secrets management
External Secrets Operator Integration:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: atlantis-credentials
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: atlantis-secrets
creationPolicy: Owner
data:
- secretKey: github-token
remoteRef:
key: secret/atlantis
property: github_token
- secretKey: webhook-secret
remoteRef:
key: secret/atlantis
property: webhook_secret
Audit Logging and Monitoring
Comprehensive logging enables threat detection
Structured Logging Configuration:
atlantis server \
--log-level="info" \
--stats-namespace="atlantis-prod" \
--enable-policy-checks
Essential Prometheus Metrics:
# Critical metrics to monitor
- atlantis_cmd_autoplan_execution_error
- atlantis_cmd_apply_execution_error
- atlantis_webhook_auth_failures
- atlantis_policy_check_failures
- atlantis_active_locks
Security Alerting Rules:
groups:
- name: atlantis-security
rules:
- alert: HighAuthenticationFailureRate
expr: rate(atlantis_webhook_auth_failures[5m]) > 0.1
labels:
severity: critical
annotations:
summary: "Potential authentication attack detected"
- alert: PolicyViolations
expr: increase(atlantis_policy_check_failures[1h]) > 5
labels:
severity: warning
SIEM integration for security operations
Log Aggregation Pattern:
logging:
siem_events:
- authentication_failures
- policy_check_overrides
- external_data_source_usage
- after_hours_production_changes
- credential_exposure_risks
User Permissions and Access Control
Team-based RBAC limits blast radius
GitHub Teams Configuration:
atlantis server \
--gh-team-allowlist="devops:apply,security:apply,developers:plan" \
--require-approval=true \
--require-mergeable=true
Repository-Level Permissions:
# repos.yaml (server-side)
repos:
- id: /.*production.*/
apply_requirements: [approved, mergeable]
allowed_overrides: [] # No overrides for production
- id: /.*staging.*/
apply_requirements: [mergeable]
allowed_overrides: [workflow]
Multi-person approval for production changes
Approval Workflow Configuration:
repos:
- id: "github.com/company/production-infra"
apply_requirements: [approved, mergeable, undiverged]
plan_requirements: [approved]
import_requirements: [approved, mergeable]
Sensitive Data Protection
Plan output sanitization prevents credential exposure
Custom Sanitization Workflow:
workflows:
secure-plan:
plan:
steps:
- init
- run: |
terraform plan -out=tfplan -no-color > plan.txt
# Sanitize AWS keys
sed -i 's/AKIA[0-9A-Z]\{16\}/AKIA****************/g' plan.txt
# Sanitize generic secrets
sed -i 's/\(password\|secret\|key\)\s*=\s*"[^"]*"/\1 = "***REDACTED***"/gi' plan.txt
cat plan.txt
Variable File Security:
# Restrict variable file access
atlantis server \
--var-file-allowlist="/secure/tfvars" \
--restrict-file-list=true
Production Deployment Security Checklist
Pre-deployment requirements
- [ ] Update to Atlantis v0.30.0+ to patch CVE-2024-52009
- [ ] Generate strong webhook secrets (32+ characters)
- [ ] Configure SSL/TLS certificates
- [ ] Set up IAM roles/managed identities (no hardcoded credentials)
- [ ] Create repository allowlist (never use
--repo-allowlist=*
) - [ ] Enable web UI authentication
- [ ] Configure structured logging
- [ ] Set up monitoring and alerting
- [ ] Implement network segmentation
Operational security
- [ ] Enable apply requirements for production
- [ ] Configure branch protection rules
- [ ] Implement CODEOWNERS for sensitive resources
- [ ] Set up policy-as-code checks
- [ ] Configure audit log retention (compliance requirements)
- [ ] Test incident response procedures
- [ ] Schedule regular security assessments
- [ ] Document break-glass procedures
Continuous security
- [ ] Monitor for new CVEs and security advisories
- [ ] Regularly update container images
- [ ] Rotate credentials and secrets quarterly
- [ ] Review access logs monthly
- [ ] Conduct security training for teams
- [ ] Test disaster recovery procedures
- [ ] Update security policies based on threats
Enterprise Security Configuration Example
#!/bin/bash
# Complete secure Atlantis deployment
atlantis server \
--atlantis-url="https://atlantis.company.com" \
--repo-allowlist="github.com/company/*,!github.com/company/experimental-*" \
--gh-webhook-secret="${WEBHOOK_SECRET}" \
--ssl-cert-file="/etc/ssl/certs/atlantis.crt" \
--ssl-key-file="/etc/ssl/private/atlantis.key" \
--web-basic-auth=true \
--web-username="admin" \
--web-password="${WEB_PASSWORD}" \
--gh-team-allowlist="devops:apply,security:apply,developers:plan" \
--var-file-allowlist="/secure/configs" \
--restrict-file-list=true \
--enable-policy-checks=true \
--silence-fork-pr-errors=true \
--log-level="info" \
--stats-namespace="atlantis-prod" \
--repo-config="/etc/atlantis/repos.yaml"
This configuration implements multiple security layers including authentication, authorization, encryption, monitoring, and policy enforcement, providing a robust security posture for production Atlantis deployments.