diff --git a/examples/cross_account_vault_policy/README.md b/examples/cross_account_vault_policy/README.md new file mode 100644 index 0000000..7e06cde --- /dev/null +++ b/examples/cross_account_vault_policy/README.md @@ -0,0 +1,391 @@ +# Cross-Account Backup Vault Policy Example + +This example demonstrates how to configure an AWS Backup vault with access policies for cross-account backup scenarios. This is essential for enterprise disaster recovery strategies where backups are copied to a centralized DR account. + +## Features Demonstrated + +- **Backup Vault Policy**: IAM policy for cross-account backup access +- **Cross-Account Permissions**: Secure access control for source accounts +- **KMS Encryption**: Customer-managed KMS key for vault encryption +- **Vault Lock**: Compliance-grade immutable backup retention +- **Audit Access**: Special permissions for compliance and audit roles + +## Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Source Acct 1 │ │ Source Acct 2 │ │ Audit Acct │ +│ (Production) │ │ (Staging) │ │ (Compliance) │ +│ │ │ │ │ │ +│ ┌─────────────┐│ │ ┌─────────────┐│ │ ┌─────────────┐│ +│ │Backup Plans ││ │ │Backup Plans ││ │ │ Audit Role ││ +│ │ ││ │ │ ││ │ │ ││ +│ │Copy Actions ││ │ │Copy Actions ││ │ │Read Access ││ +│ └─────────────┘│ │ └─────────────┘│ │ └─────────────┘│ +└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘ + │ │ │ + │ Copy Backups │ Audit │ + └──────────┐ ┌────────┘ Access │ + │ │ │ + ▼ ▼ ▼ + ┌─────────────────────────────────────────────┐ + │ Destination Account │ + │ (DR Account) │ + │ │ + │ ┌─────────────────────────────────────┐ │ + │ │ DR Backup Vault │ │ + │ │ │ │ + │ │ • Vault Access Policy │ │ + │ │ • KMS Encryption │ │ + │ │ • Vault Lock (Compliance) │ │ + │ │ • Cross-Region Replication Ready │ │ + │ └─────────────────────────────────────┘ │ + └─────────────────────────────────────────────┘ +``` + +## Use Cases + +### 1. Enterprise Disaster Recovery +- Central DR account receives backups from all production accounts +- Immutable backup storage with vault lock for compliance +- Encrypted storage with customer-managed KMS keys + +### 2. Compliance and Audit Requirements +- SOX, HIPAA, PCI DSS compliance through immutable backups +- Audit trails for all cross-account backup operations +- Separate audit account access for independent verification + +### 3. Multi-Account AWS Organizations +- Member accounts backup to organization's backup account +- Centralized backup governance and cost management +- Simplified backup monitoring and alerting + +### 4. Hybrid and Multi-Cloud Strategies +- AWS-to-AWS backup copy for additional resilience +- Integration with on-premises backup strategies +- Cross-region disaster recovery scenarios + +## Configuration + +### Basic Configuration + +```hcl +module "cross_account_backup" { + source = "lgallard/backup/aws//examples/cross_account_vault_policy" + + # Source accounts allowed to copy backups + source_account_ids = [ + "123456789012", # Production account + "987654321098" # Staging account + ] + + # Regions from which backups can be copied + allowed_source_regions = ["us-east-1", "us-west-2"] + + # Compliance settings + enable_vault_lock = true + min_retention_days = 30 + max_retention_days = 2555 # 7 years + lock_changeable_for_days = 7 # Compliance mode after 7 days + + # Audit access + audit_role_arn = "arn:aws:iam::999999999999:role/BackupAuditRole" + + tags = { + Purpose = "DisasterRecovery" + Compliance = "SOX" + Environment = "production" + } +} +``` + +### Advanced Configuration + +For more complex scenarios, you can customize the vault policy: + +```hcl +# Custom vault policy with additional conditions +data "aws_iam_policy_document" "custom_vault_policy" { + statement { + sid = "AllowCrossAccountBackupCopy" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [ + for account_id in var.source_account_ids : + "arn:aws:iam::${account_id}:role/AWSBackupServiceRole" + ] + } + + actions = ["backup:CopyIntoBackupVault"] + resources = ["*"] + + # Additional security conditions + condition { + test = "StringEquals" + variable = "backup:CopySourceRegion" + values = var.allowed_source_regions + } + + condition { + test = "DateGreaterThan" + variable = "aws:CurrentTime" + values = ["2024-01-01T00:00:00Z"] + } + + condition { + test = "IpAddress" + variable = "aws:SourceIp" + values = ["10.0.0.0/8", "172.16.0.0/12"] # Corporate IP ranges + } + } +} + +module "advanced_cross_account_backup" { + source = "lgallard/backup/aws" + + vault_name = "secure-dr-vault" + vault_policy = data.aws_iam_policy_document.custom_vault_policy.json + + # Additional security configuration + vault_policy_bypass_security_validation = false # Strict security validation + + # ... other configuration +} +``` + +## Source Account Setup + +### 1. IAM Policy for Source Accounts + +Create this IAM policy in each source account: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowBackupCopyToDestination", + "Effect": "Allow", + "Action": [ + "backup:CopyIntoBackupVault", + "backup:DescribeBackupVault" + ], + "Resource": [ + "arn:aws:backup:*:DESTINATION_ACCOUNT_ID:backup-vault:*" + ] + }, + { + "Sid": "AllowKMSAccess", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:CreateGrant" + ], + "Resource": "arn:aws:kms:*:DESTINATION_ACCOUNT_ID:key/*", + "Condition": { + "StringEquals": { + "kms:ViaService": "backup.REGION.amazonaws.com" + } + } + } + ] +} +``` + +### 2. Backup Plan with Copy Actions + +```hcl +resource "aws_backup_plan" "source_plan" { + name = "production-backup-plan" + + rule { + rule_name = "daily_backup_with_copy" + target_vault_name = "source-vault" + schedule = "cron(0 5 ? * * *)" # 5 AM daily + + lifecycle { + cold_storage_after = 30 + delete_after = 120 + } + + # Copy to DR account + copy_action { + destination_vault_arn = "arn:aws:backup:us-east-1:DR_ACCOUNT_ID:backup-vault:dr-vault-xxx" + + lifecycle { + cold_storage_after = 30 + delete_after = 2555 # 7 years in DR account + } + } + } +} +``` + +## Security Considerations + +### 1. KMS Key Management +- Uses customer-managed KMS keys for encryption +- Cross-account access properly configured +- Key rotation enabled by default + +### 2. Least Privilege Access +- Vault policy grants minimum required permissions +- Source account access restricted to specific accounts and regions +- Audit access is read-only + +### 3. Compliance Features +- Vault lock prevents premature deletion +- Immutable backups for regulatory compliance +- Audit trail for all operations + +### 4. Network Security +- Optional IP address restrictions +- VPC endpoint support for private communication +- CloudTrail logging for all API calls + +## Monitoring and Alerting + +### CloudWatch Metrics +- `AWS/Backup NumberOfBackupJobsCompleted` +- `AWS/Backup NumberOfBackupJobsFailed` +- `AWS/Backup NumberOfRecoveryPointsCreated` + +### CloudTrail Events +- `CopyIntoBackupVault` - Cross-account copy operations +- `PutBackupVaultAccessPolicy` - Policy changes +- `DeleteBackupVaultAccessPolicy` - Policy removals + +### SNS Notifications +```hcl +# Add to your backup configuration +notifications = { + backup_vault_events = [ + "BACKUP_JOB_STARTED", + "BACKUP_JOB_COMPLETED", + "BACKUP_JOB_FAILED", + "COPY_JOB_STARTED", + "COPY_JOB_SUCCESSFUL", + "COPY_JOB_FAILED" + ] + sns_topic_arn = aws_sns_topic.backup_notifications.arn +} +``` + +## Cost Optimization + +### 1. Lifecycle Management +- Move to cold storage after 30 days (75% cost reduction) +- Archive tier for long-term retention (90% cost reduction) +- Appropriate deletion schedules + +### 2. Cross-Region Considerations +- Copy only critical backups cross-region +- Use lifecycle policies to minimize storage costs +- Monitor cross-region data transfer charges + +### 3. Backup Frequency +- Daily backups for production workloads +- Weekly backups for development environments +- Point-in-time recovery for databases + +## Testing and Validation + +### 1. Backup Copy Testing +```bash +# Test backup copy from source account +aws backup start-backup-job \ + --backup-vault-name source-vault \ + --resource-arn arn:aws:dynamodb:us-east-1:123456789012:table/test-table \ + --iam-role-arn arn:aws:iam::123456789012:role/AWSBackupServiceRole + +# Verify in destination account +aws backup list-recovery-points-by-backup-vault \ + --backup-vault-name dr-vault-xxx +``` + +### 2. Restore Testing +```bash +# Test restore from DR account +aws backup start-restore-job \ + --recovery-point-arn arn:aws:backup:us-east-1:DR_ACCOUNT:recovery-point:xxx \ + --metadata '{"TableName":"test-table-restored"}' \ + --iam-role-arn arn:aws:iam::DR_ACCOUNT:role/AWSBackupServiceRole +``` + +## Troubleshooting + +### Common Issues + +1. **Access Denied Errors** + - Verify source account IAM policies + - Check KMS key permissions + - Confirm vault policy allows source account + +2. **Copy Job Failures** + - Check network connectivity + - Verify region restrictions in vault policy + - Review CloudTrail logs for detailed errors + +3. **Encryption Issues** + - Ensure KMS key policy allows cross-account access + - Verify `kms:ViaService` condition is correct + - Check key permissions for backup service + +## Integration with Other Services + +### AWS Organizations +```hcl +# Organization-wide backup policy +resource "aws_organizations_policy" "backup_policy" { + name = "CrossAccountBackupPolicy" + description = "Backup policy for all organization accounts" + type = "BACKUP_POLICY" + + content = jsonencode({ + plans = { + OrgBackupPlan = { + regions = ["us-east-1", "us-west-2"] + + copy_actions = { + arn:aws:backup:us-east-1:DR_ACCOUNT_ID:backup-vault:dr-vault = { + target_backup_vault_arn = "arn:aws:backup:us-east-1:DR_ACCOUNT_ID:backup-vault:dr-vault" + lifecycle = { + delete_after_days = 2555 + } + } + } + } + } + }) +} +``` + +### AWS Config +```hcl +# Monitor vault policy changes +resource "aws_config_config_rule" "backup_vault_policy_check" { + name = "backup-vault-policy-check" + + source { + owner = "AWS" + source_identifier = "BACKUP_VAULT_ACCESS_POLICY_CONFIGURED" + } + + depends_on = [aws_config_configuration_recorder.recorder] +} +``` + +## Related Examples + +- [Simple Plan](../simple_plan/) - Basic backup configuration +- [Secure Backup Configuration](../secure_backup_configuration/) - Additional security features +- [Organization Backup Policy](../organization_backup_policy/) - Enterprise governance + +## Additional Resources + +- [AWS Backup Cross-Account Documentation](https://docs.aws.amazon.com/aws-backup/latest/devguide/create-cross-account-backup.html) +- [Vault Access Policy Guide](https://docs.aws.amazon.com/aws-backup/latest/devguide/create-a-vault-access-policy.html) +- [AWS Backup Security Best Practices](https://docs.aws.amazon.com/aws-backup/latest/devguide/security-best-practices.html) \ No newline at end of file diff --git a/examples/cross_account_vault_policy/main.tf b/examples/cross_account_vault_policy/main.tf new file mode 100644 index 0000000..7a31bd7 --- /dev/null +++ b/examples/cross_account_vault_policy/main.tf @@ -0,0 +1,184 @@ +# Cross-Account Backup Vault Policy Example +# This example demonstrates how to configure a backup vault with access policies +# for cross-account backup scenarios + +# Local data for constructing the vault policy +data "aws_caller_identity" "current" {} + +data "aws_iam_policy_document" "cross_account_vault_policy" { + # Allow cross-account backup copy operations + statement { + sid = "AllowCrossAccountBackupCopy" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [for id in var.source_account_ids : "arn:aws:iam::${id}:root"] + } + + actions = [ + "backup:CopyIntoBackupVault" + ] + + resources = ["arn:aws:backup:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:backup-vault:${var.vault_name_prefix}-*"] + + condition { + test = "StringEquals" + variable = "backup:CopySourceRegion" + values = var.allowed_source_regions + } + + condition { + test = "Null" + variable = "backup:CopySourceRegion" + values = ["false"] + } + } + + # Allow organization-level access for audit and compliance + statement { + sid = "AllowOrganizationAuditAccess" + effect = "Allow" + + principals { + type = "AWS" + identifiers = [var.audit_role_arn] + } + + actions = [ + "backup:DescribeBackupVault", + "backup:ListRecoveryPointsByBackupVault" + ] + + resources = ["arn:aws:backup:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:backup-vault:${var.vault_name_prefix}-*"] + } +} + +# AWS Backup configuration with vault policy +module "aws_backup_cross_account" { + source = "../.." + + # Vault configuration + vault_name = "${var.vault_name_prefix}-${random_id.vault_suffix.hex}" + vault_kms_key_arn = aws_kms_key.backup_vault_key.arn + + # Vault access policy for cross-account scenarios + vault_policy = data.aws_iam_policy_document.cross_account_vault_policy.json + + # Vault lock for compliance (optional) + locked = true + min_retention_days = 30 + max_retention_days = 365 + changeable_for_days = 7 # Governance mode for 7 days, then compliance mode + + # Basic backup plan for the destination vault + plan_name = "cross-account-dr-plan" + + rules = [ + { + name = "daily-backup" + schedule = "cron(0 2 * * ? *)" # 2 AM daily + start_window = 480 # 8 hours + completion_window = 720 # 12 hours + lifecycle = { + cold_storage_after = 30 # Move to cold storage after 30 days + delete_after = 365 # Keep for 1 year + } + recovery_point_tags = { + BackupType = "CrossAccountDR" + Compliance = "Required" + } + } + ] + + # Selection for resources to backup (can be empty for destination-only vault) + selections = [] + + tags = { + Purpose = "CrossAccountBackup" + Environment = "production" + Compliance = "SOX" + CostCenter = "IT-DR" + } +} + +# KMS key for backup vault encryption +resource "aws_kms_key" "backup_vault_key" { + description = "KMS key for cross-account backup vault" + deletion_window_in_days = 7 + enable_key_rotation = true + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "Enable IAM User Permissions" + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + } + Action = [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:DescribeKey", + "kms:CreateGrant", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GetKeyPolicy", + "kms:PutKeyPolicy" + ] + Resource = "*" + }, + { + Sid = "Allow AWS Backup Service" + Effect = "Allow" + Principal = { + Service = "backup.amazonaws.com" + } + Action = [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:DescribeKey" + ] + Resource = "*" + }, + { + Sid = "Allow Cross Account Access for Backup Copy" + Effect = "Allow" + Principal = { + AWS = [for id in var.source_account_ids : "arn:aws:iam::${id}:root"] + } + Action = [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:CreateGrant" + ] + Resource = "*" + Condition = { + StringEquals = { + "kms:ViaService" = "backup.${data.aws_region.current.name}.amazonaws.com" + } + } + } + ] + }) + + tags = { + Name = "backup-vault-key" + Purpose = "CrossAccountBackup" + Environment = "production" + } +} + +resource "aws_kms_alias" "backup_vault_key_alias" { + name = "alias/backup-vault-cross-account" + target_key_id = aws_kms_key.backup_vault_key.key_id +} + +# Random suffix for unique vault name +resource "random_id" "vault_suffix" { + byte_length = 4 +} + +# Data sources for region information +data "aws_region" "current" {} diff --git a/examples/cross_account_vault_policy/outputs.tf b/examples/cross_account_vault_policy/outputs.tf new file mode 100644 index 0000000..41913de --- /dev/null +++ b/examples/cross_account_vault_policy/outputs.tf @@ -0,0 +1,208 @@ +# Outputs for Cross-Account Backup Vault Policy Example + +output "vault_arn" { + description = "ARN of the backup vault" + value = module.aws_backup_cross_account.vault_arn +} + +output "vault_name" { + description = "Name of the backup vault" + value = module.aws_backup_cross_account.vault_id +} + +output "vault_policy_attached" { + description = "Whether a vault access policy is attached" + value = module.aws_backup_cross_account.vault_policy_attached +} + +output "vault_policy_details" { + description = "Vault policy configuration details" + value = module.aws_backup_cross_account.vault_policy_details +} + +output "kms_key_id" { + description = "ID of the KMS key used for vault encryption" + value = aws_kms_key.backup_vault_key.key_id +} + +output "kms_key_arn" { + description = "ARN of the KMS key used for vault encryption" + value = aws_kms_key.backup_vault_key.arn +} + +output "backup_plan_arn" { + description = "ARN of the backup plan" + value = module.aws_backup_cross_account.plan_arn +} + +output "backup_plan_version" { + description = "Version of the backup plan" + value = module.aws_backup_cross_account.plan_version +} + +# Security and management information +output "cross_account_setup_summary" { + description = "Summary of cross-account backup configuration" + value = { + destination_account = data.aws_caller_identity.current.account_id + destination_region = data.aws_region.current.name + vault_name = module.aws_backup_cross_account.vault_id + vault_arn = module.aws_backup_cross_account.vault_arn + + source_accounts = { + allowed_account_ids = var.source_account_ids + allowed_regions = var.allowed_source_regions + } + + compliance_features = { + vault_lock_enabled = var.enable_vault_lock + min_retention_days = var.min_retention_days + max_retention_days = var.max_retention_days + kms_encryption_enabled = true + audit_access_enabled = true + } + + policy_configuration = { + vault_policy_attached = module.aws_backup_cross_account.vault_policy_attached + policy_principals = var.source_account_ids + audit_role_arn = var.audit_role_arn + } + } +} + +# Instructions for source accounts +output "source_account_instructions" { + description = "Instructions for configuring source accounts" + value = { + description = "Configuration steps for source accounts to copy backups to this destination vault" + + step_1_iam_policy = { + description = "Create IAM policy in source accounts with these permissions" + policy_document = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AllowBackupCopyToDestination" + Effect = "Allow" + Action = [ + "backup:CopyIntoBackupVault", + "backup:DescribeBackupVault" + ] + Resource = [ + module.aws_backup_cross_account.vault_arn, + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:backup-vault:*" + ] + }, + { + Sid = "AllowKMSAccess" + Effect = "Allow" + Action = [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:CreateGrant" + ] + Resource = aws_kms_key.backup_vault_key.arn + Condition = { + StringEquals = { + "kms:ViaService" = "backup.${data.aws_region.current.name}.amazonaws.com" + } + } + } + ] + }) + } + + step_2_backup_plan = { + description = "Configure backup plans in source accounts with copy actions" + copy_action_example = { + destination_vault_arn = module.aws_backup_cross_account.vault_arn + lifecycle = { + cold_storage_after = var.cold_storage_after_days + delete_after = var.delete_after_days + } + } + } + + step_3_testing = { + description = "Test cross-account backup copy" + test_commands = [ + "# Create a test backup in source account", + "aws backup start-backup-job --backup-vault-name source-vault --resource-arn YOUR_RESOURCE_ARN --iam-role-arn YOUR_BACKUP_ROLE_ARN", + "", + "# Verify backup appears in destination vault", + "aws backup list-recovery-points-by-backup-vault --backup-vault-name ${module.aws_backup_cross_account.vault_id}" + ] + } + } +} + +# Management and monitoring information +output "management_information" { + description = "Management commands and monitoring setup" + value = { + aws_cli_commands = { + describe_vault = "aws backup describe-backup-vault --backup-vault-name ${module.aws_backup_cross_account.vault_id}" + get_vault_policy = "aws backup get-backup-vault-access-policy --backup-vault-name ${module.aws_backup_cross_account.vault_id}" + list_recovery_points = "aws backup list-recovery-points-by-backup-vault --backup-vault-name ${module.aws_backup_cross_account.vault_id}" + describe_kms_key = "aws kms describe-key --key-id ${aws_kms_key.backup_vault_key.key_id}" + } + + console_urls = { + backup_vault = "https://console.aws.amazon.com/backup/home?region=${data.aws_region.current.name}#/backupvaults/details/${module.aws_backup_cross_account.vault_id}" + backup_plan = "https://console.aws.amazon.com/backup/home?region=${data.aws_region.current.name}#/plans" + kms_key = "https://console.aws.amazon.com/kms/home?region=${data.aws_region.current.name}#/kms/keys/${aws_kms_key.backup_vault_key.key_id}" + } + + monitoring = { + cloudwatch_metrics = [ + "AWS/Backup NumberOfBackupJobsCreated", + "AWS/Backup NumberOfBackupJobsCompleted", + "AWS/Backup NumberOfBackupJobsFailed" + ] + cloudtrail_events = [ + "CopyIntoBackupVault", + "CreateBackupVault", + "PutBackupVaultAccessPolicy" + ] + } + } +} + +# Security considerations +output "security_notes" { + description = "Important security considerations for cross-account backup setup" + value = { + kms_encryption = { + status = "Enabled with customer-managed key" + key_id = aws_kms_key.backup_vault_key.key_id + key_arn = aws_kms_key.backup_vault_key.arn + rotation = var.enable_kms_key_rotation ? "Enabled" : "Disabled" + note = "Source accounts need kms:Decrypt and kms:GenerateDataKey permissions" + } + + vault_lock = { + status = var.enable_vault_lock ? "Enabled" : "Disabled" + mode = var.lock_changeable_for_days != null ? "Compliance (${var.lock_changeable_for_days} day grace period)" : "Governance" + min_retention = var.min_retention_days + max_retention = var.max_retention_days + immutable_after = var.lock_changeable_for_days != null ? "${var.lock_changeable_for_days} days" : "Never (governance mode)" + } + + access_control = { + vault_policy = "Cross-account access restricted to specified accounts and regions" + audit_access = "Audit role can describe vault and list recovery points" + source_account_ids = var.source_account_ids + allowed_regions = var.allowed_source_regions + principle_of_least_privilege = "Policy follows least privilege - only necessary permissions granted" + } + + recommendations = [ + "Review source account permissions regularly", + "Monitor CloudTrail for unusual cross-account backup activity", + "Set up CloudWatch alarms for backup job failures", + "Use AWS Config to monitor vault policy changes", + "Implement backup job notifications via SNS", + "Test restore procedures regularly from both source and destination accounts" + ] + } +} diff --git a/examples/cross_account_vault_policy/variables.tf b/examples/cross_account_vault_policy/variables.tf new file mode 100644 index 0000000..8f63682 --- /dev/null +++ b/examples/cross_account_vault_policy/variables.tf @@ -0,0 +1,173 @@ +# Variables for Cross-Account Backup Vault Policy Example + +variable "source_account_ids" { + description = "List of source AWS account IDs that are allowed to copy backups to this vault" + type = list(string) + default = ["123456789012", "987654321098"] + + validation { + condition = alltrue([ + for account_id in var.source_account_ids : + can(regex("^[0-9]{12}$", account_id)) + ]) + error_message = "All account IDs must be exactly 12 digits." + } +} + +variable "allowed_source_regions" { + description = "List of AWS regions from which cross-account backup copies are allowed" + type = list(string) + default = ["us-east-1", "us-west-2"] + + validation { + condition = alltrue([ + for region in var.allowed_source_regions : + can(regex("^[a-z]{2}-[a-z]+-[0-9]{1}$", region)) + ]) + error_message = "All regions must be valid AWS region names (e.g., us-east-1, eu-west-1)." + } +} + +variable "audit_role_arn" { + description = "ARN of the audit role that can access the backup vault for compliance purposes" + type = string + default = "arn:aws:iam::999999999999:role/AWSBackupAuditRole" + + validation { + condition = can(regex("^arn:aws:iam::[0-9]{12}:role/", var.audit_role_arn)) + error_message = "The audit_role_arn must be a valid IAM role ARN." + } +} + +variable "vault_name_prefix" { + description = "Prefix for the backup vault name (random suffix will be added)" + type = string + default = "dr-vault" + + validation { + condition = can(regex("^[0-9A-Za-z-_]{2,40}$", var.vault_name_prefix)) + error_message = "The vault_name_prefix must be 2-40 characters and contain only alphanumeric characters, hyphens, and underscores." + } +} + +variable "enable_vault_lock" { + description = "Enable vault lock for compliance. When enabled, backups cannot be deleted before the retention period expires" + type = bool + default = true +} + +variable "min_retention_days" { + description = "Minimum retention period in days for backup vault lock" + type = number + default = 30 + + validation { + condition = var.min_retention_days >= 7 && var.min_retention_days <= 2555 + error_message = "The min_retention_days must be between 7 and 2555 days." + } +} + +variable "max_retention_days" { + description = "Maximum retention period in days for backup vault lock" + type = number + default = 365 + + validation { + condition = var.max_retention_days >= 7 && var.max_retention_days <= 2555 + error_message = "The max_retention_days must be between 7 and 2555 days." + } +} + +variable "lock_changeable_for_days" { + description = "Number of days before the vault lock becomes immutable (compliance mode). Use null for governance mode only" + type = number + default = 7 + + validation { + condition = var.lock_changeable_for_days == null || (var.lock_changeable_for_days >= 3 && var.lock_changeable_for_days <= 365) + error_message = "The lock_changeable_for_days must be between 3 and 365 days or null." + } +} + +variable "tags" { + description = "A mapping of tags to assign to all resources" + type = map(string) + default = { + Purpose = "CrossAccountBackup" + Environment = "production" + Compliance = "SOX" + CostCenter = "IT-DR" + } +} + +variable "enable_kms_key_rotation" { + description = "Enable automatic rotation of the KMS key used for vault encryption" + type = bool + default = true +} + +variable "kms_key_deletion_window" { + description = "Number of days to wait before deleting the KMS key when destroyed" + type = number + default = 7 + + validation { + condition = var.kms_key_deletion_window >= 7 && var.kms_key_deletion_window <= 30 + error_message = "The kms_key_deletion_window must be between 7 and 30 days." + } +} + +variable "backup_schedule" { + description = "Cron expression for backup schedule (default: daily at 2 AM)" + type = string + default = "cron(0 2 * * ? *)" + + validation { + condition = can(regex("^cron\\([^)]+\\)$", var.backup_schedule)) + error_message = "The backup_schedule must be a valid cron expression (e.g., 'cron(0 2 * * ? *)')." + } +} + +variable "backup_start_window" { + description = "Number of minutes before beginning a backup job" + type = number + default = 480 + + validation { + condition = var.backup_start_window >= 60 && var.backup_start_window <= 43200 + error_message = "The backup_start_window must be between 60 and 43200 minutes." + } +} + +variable "backup_completion_window" { + description = "Number of minutes for AWS Backup to complete a backup job" + type = number + default = 720 + + validation { + condition = var.backup_completion_window >= 120 && var.backup_completion_window <= 43200 + error_message = "The backup_completion_window must be between 120 and 43200 minutes." + } +} + +variable "cold_storage_after_days" { + description = "Number of days after backup creation to move to cold storage" + type = number + default = 30 + + validation { + condition = var.cold_storage_after_days >= 30 + error_message = "The cold_storage_after_days must be at least 30 days (AWS requirement)." + } +} + +variable "delete_after_days" { + description = "Number of days after backup creation to delete the backup" + type = number + default = 365 + + validation { + condition = var.delete_after_days >= 1 + error_message = "The delete_after_days must be at least 1 day." + } +} diff --git a/examples/cross_account_vault_policy/versions.tf b/examples/cross_account_vault_policy/versions.tf new file mode 100644 index 0000000..41143c8 --- /dev/null +++ b/examples/cross_account_vault_policy/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.1" + } + } +} diff --git a/main.tf b/main.tf index f60d571..c3a44fb 100644 --- a/main.tf +++ b/main.tf @@ -31,6 +31,17 @@ locals { local.should_create_airgapped_vault ? try(aws_backup_logically_air_gapped_vault.ab_airgapped_vault[0].name, null) : null ) + # Vault policy configuration + should_create_vault_policy = var.enabled && var.vault_policy != null && local.should_create_vault + + # Vault policy security validation (moved from variables.tf due to cross-variable reference restriction) + vault_policy_security_check = var.vault_policy != null && !var.vault_policy_bypass_security_validation ? ( + can(jsondecode(var.vault_policy)) ? !contains(lower(var.vault_policy), "\"*\"") : true + ) : true + + # Validate security check - will fail plan if wildcard is detected and bypass is not enabled + _validate_vault_policy_security = local.vault_policy_security_check ? null : tobool("The vault_policy contains wildcard permissions (*) which may be overly permissive. Review security implications or set vault_policy_bypass_security_validation=true to override this check.") + # Rule processing (matching existing logic for compatibility) rule = var.rule_name == null ? [] : [{ name = var.rule_name @@ -149,6 +160,19 @@ resource "aws_backup_vault_lock_configuration" "ab_vault_lock_configuration" { } } +# AWS Backup vault access policy +resource "aws_backup_vault_policy" "ab_vault_policy" { + count = local.should_create_vault_policy ? 1 : 0 + + backup_vault_name = local.vault_name + policy = var.vault_policy + + depends_on = [ + aws_backup_vault.ab_vault, + aws_backup_logically_air_gapped_vault.ab_airgapped_vault + ] +} + # Legacy AWS Backup plan (for backward compatibility) with optimized timeouts resource "aws_backup_plan" "ab_plan" { count = local.should_create_legacy_plan ? 1 : 0 diff --git a/outputs.tf b/outputs.tf index 0c3aaee..8379c7c 100644 --- a/outputs.tf +++ b/outputs.tf @@ -68,6 +68,42 @@ output "airgapped_vault_arn" { # sensitive = true # } +# Vault Policy +output "vault_policy_attached" { + description = "Whether a vault access policy is attached to the backup vault" + value = length(aws_backup_vault_policy.ab_vault_policy) > 0 +} + +output "vault_policy_details" { + description = "Vault policy configuration details and management information" + value = length(aws_backup_vault_policy.ab_vault_policy) > 0 ? { + vault_name = try(aws_backup_vault_policy.ab_vault_policy[0].backup_vault_name, null) + policy_length = try(length(aws_backup_vault_policy.ab_vault_policy[0].policy), 0) + + # Management information + management_commands = { + describe_policy = "aws backup get-backup-vault-access-policy --backup-vault-name ${try(aws_backup_vault_policy.ab_vault_policy[0].backup_vault_name, "VAULT_NAME")}" + delete_policy = "aws backup delete-backup-vault-access-policy --backup-vault-name ${try(aws_backup_vault_policy.ab_vault_policy[0].backup_vault_name, "VAULT_NAME")}" + list_vaults = "aws backup list-backup-vaults" + } + + # Security and usage notes + security_notes = { + purpose = "Controls access to backup vault for cross-account scenarios and compliance" + scope = "Applied to backup vault: ${try(aws_backup_vault_policy.ab_vault_policy[0].backup_vault_name, "unknown")}" + use_cases = [ + "Cross-account backup copy operations", + "Centralized backup management", + "Compliance and audit requirements", + "Resource-specific access control" + ] + } + + # Console URL for policy management + console_url = "https://console.aws.amazon.com/backup/home#/backupvaults/details/${try(aws_backup_vault_policy.ab_vault_policy[0].backup_vault_name, "VAULT_NAME")}" + } : null +} + # Legacy Plan output "plan_id" { description = "The id of the backup plan" diff --git a/variables.tf b/variables.tf index 034a4b2..8f17b9b 100644 --- a/variables.tf +++ b/variables.tf @@ -55,6 +55,35 @@ variable "vault_type" { } } +# +# AWS Backup vault policy configuration +# +variable "vault_policy_bypass_security_validation" { + description = "Bypass security validations for vault policy (wildcard permissions check). Enable for advanced use cases that require broad permissions. Use with caution." + type = bool + default = false +} + +variable "vault_policy" { + description = "IAM policy document for the backup vault access control. Enables cross-account backup access, resource-specific permissions, and compliance controls. Must be valid JSON. Leave null to disable vault policy." + type = string + default = null + + validation { + condition = var.vault_policy == null ? true : can(jsondecode(var.vault_policy)) + error_message = "The vault_policy must be a valid JSON policy document." + } + + validation { + condition = var.vault_policy == null ? true : ( + can(jsondecode(var.vault_policy)) ? + can(lookup(jsondecode(var.vault_policy), "Version", null)) : false + ) + error_message = "The vault_policy must include a Version field (typically '2012-10-17')." + } + +} + # # AWS Backup vault lock configuration #