322 lines
8.0 KiB
Markdown
Executable File

# Terraform AWS S3 Backend Module
## Overview
This Terraform module creates a secure S3 backend for Terraform state management with DynamoDB state locking. It provides encrypted state storage, version control, and concurrent access protection using AWS best practices.
## Features
- S3 bucket with versioning enabled
- KMS encryption for state files
- DynamoDB table for state locking
- IAM role for secure access
- Public access blocking
- Resource grouping for easy management
- Configurable force destroy option
- Least privilege IAM policies
## Resources Created
### Storage
- S3 Bucket with:
- Versioning enabled
- KMS encryption
- Public access blocked
- Unique naming with random suffix
### State Locking
- DynamoDB Table with:
- Pay-per-request billing
- LockID hash key
### Security
- KMS Key for encryption
- IAM Role for state access
- IAM Policy with minimal permissions
- Resource Group for organization
## Usage
### Basic Example
```hcl
module "terraform_backend" {
source = "git@github.com:webuildyourcloud/terraform-aws-s3-backend.git"
namespace = "myorg-prod"
force_destroy_state = false
}
```
### With Custom IAM Principals
```hcl
module "terraform_backend" {
source = "git@github.com:webuildyourcloud/terraform-aws-s3-backend.git"
namespace = "myorg-dev"
force_destroy_state = true
principle_arns = [
"arn:aws:iam::123456789012:user/terraform",
"arn:aws:iam::123456789012:role/TerraformExecutionRole"
]
}
```
### For CI/CD Pipeline
```hcl
module "terraform_backend" {
source = "git@github.com:webuildyourcloud/terraform-aws-s3-backend.git"
namespace = "cicd-terraform"
force_destroy_state = false
principle_arns = [
"arn:aws:iam::123456789012:role/GitHubActionsRole",
"arn:aws:iam::123456789012:role/JenkinsRole"
]
}
# Output the backend configuration
output "terraform_backend_config" {
value = module.terraform_backend.config
}
```
## Variables
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| namespace | Project namespace for unique resource naming | `string` | `"s3backend"` | no |
| principle_arns | List of principal ARNs allowed to assume the IAM role | `list(string)` | `null` | no |
| force_destroy_state | Force destroy the S3 bucket containing state files | `bool` | `true` | no |
## Outputs
| Name | Description |
|------|-------------|
| config | Complete backend configuration object containing bucket, region, role_arn, and dynamodb_table |
The `config` output provides:
```hcl
{
bucket = "namespace-randomstring-state-bucket"
region = "current-region"
role_arn = "arn:aws:iam::account-id:role/namespace-randomstring-tf-assume-role"
dynamodb_table = "namespace-randomstring-state-lock"
}
```
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 0.15 |
| aws | ~> 3.28 |
| random | ~> 3.0 |
## Configuring Terraform Backend
After creating the backend, configure Terraform to use it:
### Step 1: Create the Backend Resources
```bash
terraform init
terraform apply
```
### Step 2: Note the Outputs
```bash
terraform output config
```
### Step 3: Configure Your Terraform Backend
Create or update `backend.tf`:
```hcl
terraform {
backend "s3" {
bucket = "myorg-prod-xyz123-state-bucket"
key = "path/to/statefile.tfstate"
region = "us-east-1"
dynamodb_table = "myorg-prod-xyz123-state-lock"
encrypt = true
role_arn = "arn:aws:iam::123456789012:role/myorg-prod-xyz123-tf-assume-role"
}
}
```
### Step 4: Migrate Existing State (if applicable)
```bash
terraform init -migrate-state
```
## IAM Permissions
The module creates an IAM role with the following permissions:
### S3 Permissions
- `s3:ListBucket` - List bucket contents
- `s3:GetObject` - Read state files
- `s3:PutObject` - Write state files
- `s3:DeleteObject` - Delete old state versions
### DynamoDB Permissions
- `dynamodb:GetItem` - Read lock status
- `dynamodb:PutItem` - Acquire lock
- `dynamodb:DeleteItem` - Release lock
## Security Features
### Encryption at Rest
All state files are encrypted using AWS KMS with a dedicated encryption key.
### Encryption in Transit
All S3 API calls use HTTPS (TLS/SSL).
### Public Access Blocked
The module explicitly blocks:
- Public ACLs
- Public bucket policies
- Public object access
- Public bucket access
### Versioning
S3 bucket versioning is enabled to:
- Protect against accidental deletion
- Allow state recovery
- Maintain state history
### State Locking
DynamoDB table prevents:
- Concurrent state modifications
- State corruption
- Race conditions
## Important Notes
1. **Unique Naming**: The module generates unique bucket names using random strings
2. **Bootstrapping**: This module must be deployed without a backend initially
3. **State Migration**: After creation, migrate your state to the new backend
4. **Force Destroy**: Set `force_destroy_state = false` for production
5. **IAM Principals**: If not specified, defaults to the current caller's ARN
6. **KMS Costs**: KMS key incurs monthly charges
7. **Region Locked**: Backend resources are created in the current region
## Best Practices
1. **Separate Backends**: Use different backends for different environments
2. **Least Privilege**: Only grant access to necessary IAM principals
3. **State File Paths**: Use descriptive key paths (e.g., `env/prod/vpc/terraform.tfstate`)
4. **Backup**: Enable S3 Cross-Region Replication for critical state files
5. **Monitoring**: Set up CloudWatch alarms for bucket access
6. **Lifecycle Policies**: Configure S3 lifecycle policies for old versions
7. **Resource Groups**: Use the created resource group for cost tracking
8. **Documentation**: Document backend configuration for team members
## Example: Complete Setup
```hcl
# Step 1: Create backend infrastructure
module "backend" {
source = "git@github.com:webuildyourcloud/terraform-aws-s3-backend.git"
namespace = "myapp-prod"
force_destroy_state = false
principle_arns = [
"arn:aws:iam::123456789012:role/TerraformRole"
]
}
# Step 2: Output configuration for easy reference
output "backend_bucket" {
value = module.backend.config.bucket
description = "S3 bucket name for Terraform state"
}
output "backend_dynamodb_table" {
value = module.backend.config.dynamodb_table
description = "DynamoDB table name for state locking"
}
output "backend_role_arn" {
value = module.backend.config.role_arn
description = "IAM role ARN for backend access"
}
output "backend_region" {
value = module.backend.config.region
description = "AWS region for backend resources"
}
```
## Troubleshooting
### Cannot access state file
- Verify IAM role has correct permissions
- Check if `role_arn` is correctly specified in backend configuration
- Ensure principal is allowed in `principle_arns`
### State locking errors
- Verify DynamoDB table exists
- Check for orphaned locks in DynamoDB
- Ensure sufficient DynamoDB capacity (should not be an issue with PAY_PER_REQUEST)
### Backend initialization fails
- Verify bucket and table names are correct
- Check AWS credentials have appropriate permissions
- Ensure region matches where resources were created
### Bucket name conflicts
- The module uses random suffixes to prevent conflicts
- If conflicts occur, destroy and recreate with a new namespace
## State File Organization
Recommended key structure:
```
<environment>/<project>/<component>/terraform.tfstate
Examples:
prod/networking/vpc/terraform.tfstate
prod/compute/eks/terraform.tfstate
prod/data/rds/terraform.tfstate
staging/networking/vpc/terraform.tfstate
dev/compute/eks/terraform.tfstate
```
## Migration Guide
### From Local State
```bash
# 1. Create backend
terraform apply -target=module.backend
# 2. Add backend configuration to your code
# 3. Initialize with migration
terraform init -migrate-state
# 4. Verify
terraform state list
```
### From Another S3 Backend
Update backend configuration and run:
```bash
terraform init -migrate-state -reconfigure
```
## License
This module is provided as-is for use within your organization.