Análise de Terraform Plan

Revisão de Segurança de Infraestrutura AWS

20 Jan 2026
main
Terraform v1.6.4
CRITICAL

Ambiente de Produção

Esta análise foi executada contra o ambiente production. Todas as recomendações devem ser implementadas com cautela e testadas em staging primeiro.

Resumo Executivo

Visão geral dos problemas de segurança identificados

3 Critical
4 High
2 Medium
1 Low

Score de Segurança

Baseado nas melhores práticas AWS e CIS Benchmark

35 /100

🔍 Principais Pontos de Atenção

  • Security Group permite acesso SSH (porta 22) de qualquer IP (0.0.0.0/0)
  • Instância RDS está configurada como publicly_accessible = true
  • Bucket S3 não possui block_public_access habilitado
  • Credenciais do banco de dados estão hardcoded no código Terraform
  • Volumes EBS da EC2 não possuem encryption at rest habilitado

Vulnerabilidades Identificadas

10 issues

Lista detalhada de problemas de segurança encontrados no plano Terraform, ordenados por severidade.

Security Group Excessivamente Permissivo

aws_security_group.web_sg

CRITICAL

O Security Group permite tráfego SSH (porta 22) de qualquer origem (0.0.0.0/0), expondo a instância a ataques de força bruta e varreduras automatizadas.

⚠️ Risco

Atacantes podem tentar explorar vulnerabilidades SSH ou realizar ataques de força bruta contra a instância EC2, potencialmente comprometendo todo o ambiente.

Instância RDS Publicamente Acessível

aws_db_instance.production_db

CRITICAL

O banco de dados RDS está configurado com publicly_accessible = true, permitindo conexões diretas da internet.

⚠️ Risco

Exposição direta do banco de dados à internet aumenta drasticamente a superfície de ataque, permitindo tentativas de exploração de vulnerabilidades do PostgreSQL.

Credenciais Hardcoded no Código

aws_db_instance.production_db

CRITICAL

A senha do banco de dados está definida diretamente no código Terraform como plain text, violando práticas básicas de segurança.

⚠️ Risco

Credenciais expostas no repositório podem ser acessadas por qualquer pessoa com acesso ao código, incluindo em históricos do Git, resultando em acesso não autorizado ao banco.

Bucket S3 sem Block Public Access

aws_s3_bucket.app_assets

HIGH

O bucket S3 não possui as configurações de block_public_access habilitadas, permitindo que objetos sejam tornados públicos acidentalmente.

⚠️ Risco

Configurações incorretas de ACL ou políticas podem expor dados sensíveis publicamente, como já ocorreu em diversos incidentes de vazamento de dados.

Volumes EBS sem Criptografia

aws_instance.web_server

HIGH

Os volumes EBS anexados à instância EC2 não possuem encryption at rest habilitado.

⚠️ Risco

Dados armazenados nos volumes podem ser acessados se o disco físico for comprometido ou se snapshots forem compartilhados incorretamente.

RDS sem Backup Automático

aws_db_instance.production_db

HIGH

O banco de dados não possui backup_retention_period configurado, desabilitando backups automáticos.

⚠️ Risco

Perda de dados irrecuperável em caso de falhas, corrupção ou ataques de ransomware, comprometendo a continuidade do negócio.

S3 sem Versionamento

aws_s3_bucket.app_assets

MEDIUM

O bucket não possui versionamento habilitado, impossibilitando a recuperação de versões anteriores de objetos.

⚠️ Risco

Exclusões acidentais ou sobrescritas maliciosas de arquivos são permanentes, sem possibilidade de recuperação.

IAM Role com Permissões Excessivas

aws_iam_role.ec2_role

HIGH

A role IAM utiliza Action: "*" e Resource: "*", concedendo permissões administrativas completas.

⚠️ Risco

Em caso de comprometimento da instância, o atacante terá acesso total a todos os recursos da conta AWS.

VPC Usando Subnet Pública para RDS

aws_db_subnet_group.main

MEDIUM

O grupo de subnets do RDS inclui subnets públicas, quando deveria utilizar apenas subnets privadas.

⚠️ Risco

Recursos de banco de dados em subnets públicas têm maior exposição, mesmo com Security Groups restritivos.

Logs de Acesso S3 Desabilitados

aws_s3_bucket.app_assets

LOW

O bucket não possui logging de acesso configurado, impossibilitando auditoria de acessos.

⚠️ Risco

Impossível detectar acessos não autorizados ou investigar incidentes de segurança relacionados ao bucket.


Recomendações de Código

Boas Práticas

Sugestões de melhorias com exemplos de código Terraform seguindo as melhores práticas de segurança AWS.

Recomendação 1/4

Restringir Security Group SSH

Limite o acesso SSH apenas a IPs conhecidos, como VPN corporativa ou bastion host.

✗ Antes
resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Security group for web server"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # INSEGURO!
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
✓ Depois
resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Security group for web server"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.vpn_cidr]  # Apenas VPN
    description = "SSH from corporate VPN"
  }

  ingress {
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    security_groups = [aws_security_group.bastion.id]
    description     = "SSH from bastion host"
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS from anywhere"
  }
}

💡 Por que essa mudança é importante: Restringir o acesso SSH a CIDRs específicos (VPN corporativa) ou utilizar bastion hosts elimina a exposição direta à internet, reduzindo drasticamente a superfície de ataque. Utilize também AWS Systems Manager Session Manager como alternativa ao SSH tradicional.

Recomendação 2/4

Configurar RDS em Subnet Privada com Secrets Manager

Mova o RDS para subnets privadas e utilize AWS Secrets Manager para gerenciar credenciais.

✗ Antes
resource "aws_db_instance" "production_db" {
  identifier        = "production-db"
  engine            = "postgres"
  engine_version    = "14.7"
  instance_class    = "db.t3.medium"
  allocated_storage = 100

  db_name  = "appdb"
  username = "admin"
  password = "MySecretPass123!"  # NUNCA FAÇA ISSO!

  publicly_accessible = true  # INSEGURO!
  skip_final_snapshot = true

  vpc_security_group_ids = [aws_security_group.db_sg.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name
}
✓ Depois
# Criar secret no Secrets Manager
resource "aws_secretsmanager_secret" "db_credentials" {
  name = "production-db-credentials"
}

resource "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = aws_secretsmanager_secret.db_credentials.id
  secret_string = jsonencode({
    username = "admin"
    password = random_password.db_password.result
  })
}

resource "random_password" "db_password" {
  length  = 32
  special = true
}

resource "aws_db_instance" "production_db" {
  identifier        = "production-db"
  engine            = "postgres"
  engine_version    = "14.7"
  instance_class    = "db.t3.medium"
  allocated_storage = 100
  storage_encrypted = true  # Criptografia habilitada
  kms_key_id        = aws_kms_key.rds.arn

  db_name  = "appdb"
  username = jsondecode(
    aws_secretsmanager_secret_version.db_credentials.secret_string
  )["username"]
  password = jsondecode(
    aws_secretsmanager_secret_version.db_credentials.secret_string
  )["password"]

  publicly_accessible     = false  # Apenas acesso interno
  multi_az                = true   # Alta disponibilidade
  backup_retention_period = 30     # 30 dias de backup
  deletion_protection     = true

  vpc_security_group_ids = [aws_security_group.db_sg.id]
  db_subnet_group_name   = aws_db_subnet_group.private.name
}

💡 Por que essa mudança é importante: Utilizar AWS Secrets Manager elimina credenciais do código, permite rotação automática de senhas e integra com IAM para controle de acesso. Manter o RDS em subnets privadas impede qualquer acesso direto da internet, forçando o tráfego a passar por camadas de segurança da VPC.

Recomendação 3/4

Habilitar Block Public Access no S3

Configure todas as proteções contra acesso público acidental no bucket S3.

✗ Antes
resource "aws_s3_bucket" "app_assets" {
  bucket = "myapp-production-assets"

  tags = {
    Environment = "production"
  }
}

# Nenhuma configuração de segurança adicional
✓ Depois
resource "aws_s3_bucket" "app_assets" {
  bucket = "myapp-production-assets"

  tags = {
    Environment = "production"
  }
}

resource "aws_s3_bucket_public_access_block" "app_assets" {
  bucket = aws_s3_bucket.app_assets.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "app_assets" {
  bucket = aws_s3_bucket.app_assets.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "app_assets" {
  bucket = aws_s3_bucket.app_assets.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.s3.arn
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_logging" "app_assets" {
  bucket = aws_s3_bucket.app_assets.id

  target_bucket = aws_s3_bucket.logs.id
  target_prefix = "s3-access-logs/app-assets/"
}

💡 Por que essa mudança é importante: O block_public_access_block previne que qualquer configuração torne o bucket ou seus objetos públicos. O versionamento permite recuperar arquivos deletados ou modificados. A criptografia com KMS protege dados em repouso, e o logging habilita auditoria completa de acessos.

Recomendação 4/4

Aplicar Princípio do Menor Privilégio em IAM

Substitua permissões wildcard por permissões específicas e granulares.

✗ Antes
resource "aws_iam_role" "ec2_role" {
  name = "ec2-application-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_role_policy" "ec2_policy" {
  name = "ec2-full-access"
  role = aws_iam_role.ec2_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "*"        # PERIGOSO!
      Resource = "*"        # PERIGOSO!
    }]
  })
}
✓ Depois
resource "aws_iam_role" "ec2_role" {
  name = "ec2-application-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
  })
}

# Política específica para S3
resource "aws_iam_role_policy" "s3_access" {
  name = "s3-app-assets-access"
  role = aws_iam_role.ec2_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ]
      Resource = [
        aws_s3_bucket.app_assets.arn,
        "\${aws_s3_bucket.app_assets.arn}/*"
      ]
    }]
  })
}

# Política para Secrets Manager
resource "aws_iam_role_policy" "secrets_access" {
  name = "secrets-manager-access"
  role = aws_iam_role.ec2_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "secretsmanager:GetSecretValue"
      ]
      Resource = [
        aws_secretsmanager_secret.db_credentials.arn
      ]
    }]
  })
}

# Política para CloudWatch Logs
resource "aws_iam_role_policy" "cloudwatch_access" {
  name = "cloudwatch-logs-access"
  role = aws_iam_role.ec2_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ]
      Resource = [
        "arn:aws:logs:\${var.region}:\${data.aws_caller_identity.current.account_id}:log-group:/app/*"
      ]
    }]
  })
}

💡 Por que essa mudança é importante: Políticas IAM devem seguir o princípio do menor privilégio: conceda apenas as permissões necessárias para a função específica. Isso limita o blast radius em caso de comprometimento. Separe políticas por serviço para facilitar auditoria e revogação granular de permissões.