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.