Terraform Atlantis Security: Complete Production Deployment Guide 2025
Learn proven steps to harden Terraform Atlantis: auth, secrets, VCS webhooks, least-privilege CI/CD, runtime isolation—your roadmap to secure production IaC.
Atlantis automates Terraform operations through pull requests, making infrastructure-as-code more collaborative. However, this automation introduces security risks that must be addressed for production environments.
Server Security Fundamentals
Securing the Atlantis server itself is the first critical layer of defense, as it runs with privileged access to your infrastructure.
Network Security Configuration
Properly isolate and protect the Atlantis server with these firewall configurations:
# Example iptables rules for Atlantis server
# Allow incoming webhook connections only from VCS IPs
iptables -A INPUT -p tcp -s <GITHUB_WEBHOOK_IPS> --dport 4141 -j ACCEPT
# Allow outgoing connections to infrastructure providers
iptables -A OUTPUT -p tcp -d <AWS_API_ENDPOINT> --dport 443 -j ACCEPT
# Deny all other incoming traffic to Atlantis port
iptables -A INPUT -p tcp --dport 4141 -j DROP
Best practices:
- Place Atlantis behind a reverse proxy with TLS termination
- Restrict incoming traffic to only VCS provider IP ranges
- Configure egress rules to limit outbound connectivity
- For GitHub, only allow traffic from GitHub's webhook IP ranges
TLS/SSL Implementation
Always enable HTTPS for Atlantis communications:
# Start Atlantis with SSL certificates
atlantis server \
--ssl-cert-file=/path/to/cert.pem \
--ssl-key-file=/path/to/key.pem \
--repo-allowlist="github.com/yourorg/*" \
--atlantis-url="https://atlantis.example.com"
- Use valid certificates from trusted CAs
- Configure strong TLS ciphers and disable older protocols
- Implement automatic certificate renewal
OS-Level Hardening
Run Atlantis with a minimally privileged account:
# Create a dedicated non-root user
sudo useradd -r -m -s /bin/false atlantis
# Ensure proper permissions on data directory
sudo mkdir -p /var/lib/atlantis
sudo chown atlantis:atlantis /var/lib/atlantis
sudo chmod 700 /var/lib/atlantis
- Install only necessary packages on the host
- Configure secure SSH settings (key-based auth, disable root login)
- Apply system updates and security patches regularly
- Use a minimal base image if deploying with containers
Container Security (Docker Deployment)
When running Atlantis as a container, implement these security controls:
# Run Atlantis container with security options
docker run --name atlantis \
--user atlantis \
--read-only \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--mount type=volume,source=atlantis-data,target=/var/lib/atlantis \
-p 4141:4141 \
-e ATLANTIS_GH_TOKEN="$GITHUB_TOKEN" \
-e ATLANTIS_GH_WEBHOOK_SECRET="$WEBHOOK_SECRET" \
-e ATLANTIS_REPO_ALLOWLIST="github.com/myorg/*" \
ghcr.io/runatlantis/atlantis:latest server
- Run containers as non-root user
- Use read-only filesystems with specific volume mounts
- Drop unnecessary capabilities
- Implement resource limits to prevent DoS attacks
VCS Webhook Security
The connection between version control systems and Atlantis requires strong security controls to prevent unauthorized access and potential exploitation.
Webhook Secrets Implementation
Generate and configure strong webhook secrets to validate incoming requests:
# Generate a random webhook secret
webhook_secret=$(openssl rand -hex 32)
# Set webhook secrets as environment variables
export ATLANTIS_GH_WEBHOOK_SECRET="$webhook_secret"
export ATLANTIS_GITLAB_WEBHOOK_SECRET="$webhook_secret"
export ATLANTIS_BITBUCKET_WEBHOOK_SECRET="$webhook_secret" # For Bitbucket Server
- Generate secrets at least 24 characters long with high entropy
- Store secrets securely in environment variables or a secrets manager
- Rotate webhook secrets periodically
IP Allowlisting for Webhooks
Restrict webhook access to only known VCS provider IP addresses:
# Nginx configuration to restrict access by IP
server {
listen 443 ssl;
server_name atlantis.example.com;
# Allow GitHub webhook IPs
allow 192.30.252.0/22;
allow 185.199.108.0/22;
allow 140.82.112.0/20;
# Deny all other IPs
deny all;
location / {
proxy_pass http://localhost:4141;
}
}
Note: Bitbucket Cloud doesn't support webhook secrets, so IP allowlisting is essential for Bitbucket integrations.
Repository Allowlist
Restrict which repositories can trigger Atlantis operations:
# Start Atlantis with repository allowlist
atlantis server \
--repo-allowlist="github.com/myorg/*" \
--gh-webhook-secret="$WEBHOOK_SECRET"
- Use specific patterns to limit repository access
- Never use
--repo-allowlist="*"
in production
Cloud Provider Credentials Security
Atlantis requires access to cloud provider credentials to execute Terraform operations. Securing these credentials is critical.
Implementing IAM Roles with Least Privilege
For AWS, use IAM roles instead of static access keys:
# Example IAM role for Atlantis with least privilege
resource "aws_iam_role" "atlantis_role" {
name = "atlantis-terraform-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "ec2.amazonaws.com" # If running on EC2
}
}]
})
}
resource "aws_iam_policy" "atlantis_policy" {
name = "atlantis-terraform-policy"
description = "Policy for Atlantis to manage terraform resources"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
Resource = [
"arn:aws:s3:::${var.terraform_state_bucket}",
"arn:aws:s3:::${var.terraform_state_bucket}/*"
]
},
# Add only required permissions for specific resources
]
})
}
OIDC Implementation
Use OpenID Connect for federated authentication with cloud providers:
AWS OIDC Example:
# Create an OIDC provider for Atlantis
resource "aws_iam_openid_connect_provider" "atlantis" {
url = "https://your-atlantis-domain"
client_id_list = ["atlantis"]
thumbprint_list = ["<certificate-thumbprint>"]
}
resource "aws_iam_role" "atlantis_oidc_role" {
name = "atlantis-oidc-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Action = "sts:AssumeRoleWithWebIdentity",
Effect = "Allow",
Principal = {
Federated = aws_iam_openid_connect_provider.atlantis.arn
},
Condition = {
StringEquals = {
"${aws_iam_openid_connect_provider.atlantis.url}:sub": "system:serviceaccount:atlantis:atlantis"
}
}
}]
})
}
Azure Workload Identity Example:
resource "azuread_application_federated_identity_credential" "atlantis" {
application_object_id = azuread_application.atlantis.object_id
display_name = "atlantis-federation"
description = "Federated identity for Atlantis"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://your-atlantis-domain"
subject = "system:serviceaccount:atlantis:atlantis"
}
Avoiding Hardcoded Secrets
Use environment variables or secure vaults instead of hardcoded credentials:
# Example Terraform provider configuration using Vault
provider "vault" {
address = "https://vault.example.com"
}
data "vault_aws_access_credentials" "aws_creds" {
backend = "aws"
role = "atlantis"
}
provider "aws" {
access_key = data.vault_aws_access_credentials.aws_creds.access_key
secret_key = data.vault_aws_access_credentials.aws_creds.secret_key
region = var.aws_region
}
- Implement automated credential rotation
- Configure alerts for credential usage patterns
- Enable detailed logging for credential access
Repo-Level Security with atlantis.yaml
Controlling what Atlantis can do at the repository level is critical for preventing misuse and exploits.
Server-Side Configuration
Limit what repositories can override in their atlantis.yaml
files:
# repos.yaml (server-side config)
repos:
- id: /.*/ # Applies to all repositories
# Explicitly limit what repos can override
allowed_overrides: [workflow]
# Don't allow repos to define custom workflows
allow_custom_workflows: false
# Define secure defaults
apply_requirements: [approved, mergeable]
Critical security practices:
- Never set
allow_custom_workflows: true
without strict controls - Only include necessary keys in
allowed_overrides
- Use explicit repository IDs where possible
Restricting Dangerous Provider Usage
Create a pre-workflow hook script to validate providers:
#!/bin/bash
set -euo pipefail
# Define allowed providers
PROVIDER_ALLOWLIST=(
'registry.terraform.io/hashicorp/aws'
'registry.terraform.io/hashicorp/azurerm'
'registry.terraform.io/hashicorp/google'
'registry.terraform.io/hashicorp/kubernetes'
'registry.terraform.io/hashicorp/vault'
'registry.terraform.io/hashicorp/random'
'registry.terraform.io/hashicorp/template'
'registry.terraform.io/hashicorp/null'
)
# Get provider selections from terraform
mapfile -t PROVIDER_SELECTIONS < <(terraform version -json | jq -r '.provider_selections | keys | .[]')
# Validate each provider
for selection in "${PROVIDER_SELECTIONS[@]}"; do
if ! [[ " ${PROVIDER_ALLOWLIST[*]} " =~ ${selection} ]]; then
echo "ERROR: Provider '$selection' is not in the allowed list"
exit 1
fi
done
echo "All providers validated successfully"
exit 0
Configure this in your server-side repo configuration:
# repos.yaml
repos:
- id: /.*/
pre_workflow_hooks:
- run: /path/to/validate_providers.sh
VCS Branch Protection Integration
Configure branch protection rules in your VCS:
- Require pull request reviews before merging
- Require approval from specific teams for critical infrastructure
- Dismiss stale PR approvals when new commits are pushed
- Require status checks to pass before merging
Set up corresponding Atlantis apply requirements:
# atlantis.yaml
version: 3
projects:
- dir: production
apply_requirements: [approved, mergeable, undiverged]
approved
: Requires PR approval before Atlantis can apply changesmergeable
: Ensures branch protection rules are satisfiedundiverged
: Prevents applies if there are changes on base branch
Audit Logging and Monitoring
Establishing comprehensive logging and monitoring for Atlantis activities is essential for security and compliance.
Configuring Atlantis Server Logging
# Set logging level using environment variables
export ATLANTIS_LOG_LEVEL="info"
# Or via command line when starting Atlantis
atlantis server --log-level="info"
For containerized deployments, implement log forwarding:
# Example Docker configuration with log forwarding
docker run \
--name atlantis \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag=atlantis \
runatlantis/atlantis:latest server
Monitoring Terraform Atlantis Activities
Track these key metrics to ensure Atlantis operates correctly:
- Number of plans/applies executed (success vs failure)
- Duration of plan/apply operations
- Authentication failures
- Resource creation/deletion rates
- Off-hours operation attempts
Example Prometheus configuration:
# Example Prometheus configuration for scraping Atlantis metrics
scrape_configs:
- job_name: 'atlantis'
scrape_interval: 15s
static_configs:
- targets: ['atlantis:4141']
Setting Up Alerting for Dangerous Operations
Configure alerts for potentially dangerous operations:
# Example alert rule in Prometheus
groups:
- name: atlantis_alerts
rules:
- alert: AtlantisHighVolumeResourceDeletion
expr: rate(atlantis_resource_deletion_total[5m]) > 10
for: 5m
labels:
severity: critical
annotations:
summary: "High volume of resource deletions"
description: "Atlantis is deleting resources at an abnormal rate"
Key operations to monitor and alert on:
- Mass resource deletions
- Changes to security groups or IAM roles
- Modifications to network infrastructure
- Changes to production environments during off-hours
Handling Sensitive Data
Terraform operations can potentially expose sensitive information in plan outputs and PR comments.
Marking Variables as Sensitive
variable "database_password" {
description = "Password for the database"
type = string
sensitive = true # Prevents value from appearing in plan output
}
Using External Secret Providers
Integrate with secret management services to retrieve sensitive data at runtime:
# Example using HashiCorp Vault provider
data "vault_generic_secret" "database_credentials" {
path = "secret/database/credentials"
}
resource "aws_db_instance" "database" {
username = data.vault_generic_secret.database_credentials.data["username"]
password = data.vault_generic_secret.database_credentials.data["password"]
}
Filtering Sensitive Data from Plan Output
Implement a pre-workflow hook to filter sensitive information:
#!/bin/bash
# filter-sensitive-output.sh
# This script filters sensitive data from Terraform plan output
# Create a temporary file for filtered output
TEMP_FILE=$(mktemp)
# Filter sensitive patterns from PLANFILE
cat "$PLANFILE" | \
sed -E 's/(password|secret|key|token|credential)([[:space:]]*=[[:space:]]*)\".+\"/\1\2\"[REDACTED]\"/gi' > "$TEMP_FILE"
# Replace original plan file with filtered content
cat "$TEMP_FILE" > "$PLANFILE"
rm "$TEMP_FILE"
Configure this in your Atlantis setup:
# repos.yaml
repos:
- id: github.com/your-org/your-repo
pre_workflow_hooks:
- run: scripts/filter-sensitive-output.sh
description: "Filter sensitive data from plan output"
commands: plan
Securing State Files
Configure remote state with proper encryption and access controls:
terraform {
backend "s3" {
bucket = "secure-terraform-state"
key = "project/terraform.tfstate"
region = "us-west-2"
encrypt = true # Enable server-side encryption
dynamodb_table = "terraform-locks" # Enable state locking
}
}
Comprehensive Security Checklist
Server Security
- [ ] Deploy Atlantis behind a TLS-enabled reverse proxy
- [ ] Configure firewall rules to restrict inbound/outbound traffic
- [ ] Run Atlantis as a non-root user with minimal permissions
- [ ] Apply OS-level hardening and regular security updates
- [ ] Implement container security best practices if using Docker
- [ ] Enable web UI authentication with strong credentials
Webhook Security
- [ ] Generate and configure strong webhook secrets (>24 characters)
- [ ] Implement IP allowlisting for VCS providers
- [ ] Use
--repo-allowlist
to restrict which repositories can trigger Atlantis - [ ] Set
--allow-fork-prs=false
unless explicitly needed - [ ] Configure proper authentication for Azure DevOps webhooks
Cloud Provider Credentials
- [ ] Implement IAM roles with least privilege access
- [ ] Use OIDC for federated authentication where possible
- [ ] Avoid storing static credentials in configuration files
- [ ] Implement automated credential rotation
- [ ] Enable monitoring and alerting for credential usage
Repo-Level Security
- [ ] Restrict
allowed_overrides
in server-side configuration - [ ] Never enable
allow_custom_workflows
without strict controls - [ ] Implement branch protection rules in your VCS
- [ ] Configure appropriate
apply_requirements
(approved
,mergeable
) - [ ] Implement provider restrictions to prevent arbitrary code execution
- [ ] Use pre-workflow hooks to validate security requirements
Audit Logging and Monitoring
- [ ] Configure appropriate log levels for Atlantis
- [ ] Set up log forwarding to centralized logging system
- [ ] Implement structured logging for SIEM compatibility
- [ ] Configure metrics collection with Prometheus or similar
- [ ] Implement alerting for suspicious operations
- [ ] Establish baseline metrics for normal operations
- [ ] Include Atlantis monitoring in security incident response plans
Sensitive Data Protection
- [ ] Mark all sensitive variables with
sensitive = true
- [ ] Use external secret providers like Vault or cloud provider secret stores
- [ ] Implement filtering for plan outputs to redact sensitive information
- [ ] Configure remote state with encryption and access controls
- [ ] Implement policy checks for detecting sensitive data
- [ ] Train team members on secure practices for handling sensitive data
By implementing these security measures, you can significantly reduce the risk of security incidents when running Atlantis in production environments. Regular security reviews and updates to your configurations will ensure your Terraform automation remains secure as your infrastructure evolves.