Article ยท part of a guide
Securing Terraform Atlantis in production
Step-by-step guide to harden Terraform Atlantis: secure secrets, enforce policies, isolate environments and automate audits for production.

Key takeaways
- The most critical action for securing Atlantis is updating to v0.30.0 or later to address CVE-2024-52009, which exposes GitHub credentials in logs.
- Hardening Atlantis requires defense-in-depth across network isolation, container security, webhook secrets, repository allowlists, and branch protection.
- Cloud credentials should use IAM AssumeRole or Vault dynamic credentials rather than long-lived secrets, with secrets managed via tools like Kubernetes External Secrets Operator.
- Production deployments need audit logging, Prometheus metrics, security alerting, and team-based RBAC requiring multi-person approval for production changes.
What Immediate Action Does the Atlantis CVE Require?
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
How Do You Configure Firewall and Network Security for Atlantis?
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 # DNSKubernetes 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: 22Container 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: 512MiSSL/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"How Do You Secure the VCS Integration?
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=falseRepository 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:
- applyBranch 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
How Should You Secure Cloud Provider Credentials?
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
- planKubernetes 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_secretHow Do You Set Up Audit Logging and Monitoring?
Comprehensive logging enables threat detection
Structured Logging Configuration:
atlantis server \
--log-level="info" \
--stats-namespace="atlantis-prod" \
--enable-policy-checksEssential 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_locksSecurity 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: warningSIEM 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_risksHow Do You Control User Permissions and Access?
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=trueRepository-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]How Do You Protect Sensitive Data in Plan Output?
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.txtVariable File Security:
# Restrict variable file access
atlantis server \
--var-file-allowlist="/secure/tfvars" \
--restrict-file-list=trueWhat Belongs on a 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
What Does a Full Enterprise Security Configuration Look Like?
#!/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 config stacks several layers at once: authentication, authorization, encryption, monitoring, and policy enforcement. Together they give a production Atlantis deployment a much stronger security posture. If maintaining all of this yourself is more operational burden than you want, see how self-hosted Atlantis compares to a managed platform.
This post is part of our Atlantis collection.
Frequently asked questions
What should I do about the Atlantis CVE-2024-52009 vulnerability?
Update Atlantis to v0.30.0 or later immediately. The vulnerability exposes GitHub tokens in Atlantis logs during rotation, letting anyone with log access impersonate Atlantis and gain administrative privileges. After patching, review your logs for exposed tokens, for example by grepping for the ghs_ prefix.
How do you secure webhooks and repositories in Atlantis?
Generate a strong webhook secret of 32 or more characters and pass it via the gh-webhook-secret flag so Atlantis can verify requests actually come from your VCS provider. Restrict which repositories Atlantis processes with a repository allowlist, never a wildcard, and disable fork PRs. Branch protection rules and apply requirements like approved and mergeable then enforce approval before any apply.
How should Atlantis authenticate to cloud providers securely?
Avoid long-lived credentials. Use IAM AssumeRole with short session durations and an external ID condition, or pull dynamic short-lived credentials from HashiCorp Vault. On Kubernetes, manage remaining secrets like the GitHub token and webhook secret through a tool such as External Secrets Operator rather than hardcoding them.
How do you prevent Terraform plan output from leaking secrets in Atlantis?
Add a custom plan workflow that sanitizes the output before it gets posted as a PR comment, for example sed rules that redact AWS access key patterns and password or secret assignments. Also restrict which variable files Atlantis can read using the var-file-allowlist and restrict-file-list options.
About the author

CEO at Scalr
Sebastian Stadil is the CEO of Scalr with 15+ years of DevOps experience. He started with AWS in 2004 and advised early Microsoft Azure and Google Cloud.
Part of this guide
4 sheets