diff --git a/terraform/SECURITY_IMPROVEMENTS.md b/terraform/SECURITY_IMPROVEMENTS.md new file mode 100644 index 0000000..71cae0e --- /dev/null +++ b/terraform/SECURITY_IMPROVEMENTS.md @@ -0,0 +1,344 @@ +# Mejoras de Seguridad - Proyecto SACC PROD + +> **Fecha:** 2026-05-06 +> **Ambiente:** Producción (PROD) +> **Autor:** Área de Tecnología y Desarrollo - CCsoft +> **Estado:** Implementado (pendiente de aplicación en AWS) + +--- + +## Resumen Ejecutivo + +Se han implementado mejoras de seguridad críticas en la infraestructura Terraform del proyecto SACC en ambiente de producción. Los cambios eliminan el acceso SSH abierto (0.0.0.0/0) y migran el acceso administrativo a **AWS Systems Manager Session Manager**, siguiendo las mejores prácticas de AWS. + +### Cambios Principales + +1. **Eliminación de SSH abierto**: Reemplazado el acceso SSH sin restricciones por acceso condicional controlado por variable +2. **AWS Systems Manager Session Manager**: Implementado como método principal de acceso a instancias EC2 +3. **Rotación de llave pública**: Actualizada la llave SSH del pipeline CI/CD +4. **Documentación operativa**: Guía de migración y procedimientos de acceso + +--- + +## Detalle de Cambios + +### 1. `prod.tfvars` - Actualización de Llave Pública + +**Cambio:** Se actualizó la llave pública SSH del pipeline CI/CD. + +**Antes:** +```hcl +pipeline_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQCNFOzDJzaOMDIeEbH4JCx2OrXrgljajgkJqlozj9m bitbucket.pipeline.ci.cd.proyectosacc.thoth@computocontable.com" +``` + +**Después:** +```hcl +pipeline_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII/RcJmEYOBpfq1tSLltV1pyNB55l1jA2zYr5ZNJ0f41 thoth@ccsoft" +``` + +**Razón:** La llave anterior estaba asociada a un dominio específico. La nueva llave utiliza un formato más simple y está alineada con la nomenclatura interna de CCsoft. + +--- + +### 2. `main.tf` - Reglas de Security Group (SSH) + +**Cambio:** Eliminado el acceso SSH abierto (0.0.0.0/0) y reemplazado por reglas condicionales. + +**Antes (INSEGURO):** +```hcl +ingress { + description = "SSH - Acceso controlado por llaves CI/CD (no por IP)" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # ¡ACCESO ABIERTO AL MUNDO! +} +``` + +**Después (SEGURO):** +```hcl +# NOTA DE SEGURIDAD CRÍTICA (2026-05-06): +# El acceso SSH abierto (0.0.0.0/0) ha sido eliminado por razones de seguridad. +# Ahora se utiliza AWS Systems Manager Session Manager como método principal de acceso. +# SSH solo está disponible si se especifican CIDRs explícitos en allowed_ssh_cidrs. +dynamic "ingress" { + for_each = length(var.allowed_ssh_cidrs) > 0 ? [1] : [] + content { + description = "SSH - Acceso restringido a CIDRs autorizados (EMERGENCIA únicamente)" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = var.allowed_ssh_cidrs + } +} +``` + +**Razón:** +- El acceso SSH abierto es una vulnerabilidad crítica (CVE potencial, fuerza bruta) +- AWS Systems Manager Session Manager proporciona acceso seguro sin abrir puertos +- El acceso SSH directo solo debe usarse en emergencias con CIDRs específicos + +--- + +### 3. `main.tf` - Permisos IAM para Systems Manager + +**Cambio:** Agregados permisos SSM al rol IAM de la instancia EC2. + +**Permisos agregados:** +```json +{ + "Effect": "Allow", + "Action": [ + "ssm:UpdateInstanceInformation", + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel", + "ec2messages:AcknowledgeMessage", + "ec2messages:DeleteMessage", + "ec2messages:FailMessage", + "ec2messages:GetEndpoint", + "ec2messages:GetMessages", + "ec2messages:SendReply" + ], + "Resource": "*" +} +``` + +**Razón:** +- Estos permisos son equivalentes a la política administrada `AmazonSSMManagedInstanceCore` +- Permiten que la instancia se comunique con el servicio AWS Systems Manager +- Requeridos para Session Manager, Patch Manager y otros servicios SSM + +--- + +### 4. `variables.tf` - Nueva Variable de Configuración + +**Cambio:** Agregada variable para controlar el acceso SSH condicional. + +```hcl +variable "allowed_ssh_cidrs" { + description = "Lista de CIDRs permitidos para acceso SSH (vacío = deshabilitado). Preferir AWS Systems Manager Session Manager en lugar de SSH." + type = list(string) + default = [] +} +``` + +**Razón:** +- Permite habilitar/deshabilitar SSH mediante variable de Terraform +- Valor por defecto vacío deshabilita SSH (seguro por defecto) +- Facilita el acceso de emergencia sin modificar el código + +--- + +### 5. `user-data.sh` - Instalación de SSM Agent + +**Cambio:** Agregada instalación y verificación del agente SSM. + +**Nueva sección agregada:** +```bash +# Instalar y verificar AWS Systems Manager Agent +# SSM Agent permite acceso seguro sin abrir puertos SSH (0.0.0.0/0) +apt-get install -y amazon-ssm-agent 2>/dev/null || true + +# Si el paquete no está disponible en repositorios, descargar desde AWS +if ! command -v amazon-ssm-agent &> /dev/null; then + curl -fsSL -o /tmp/amazon-ssm-agent.deb https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_amd64/amazon-ssm-agent.deb + dpkg -i /tmp/amazon-ssm-agent.deb || true +fi + +systemctl enable amazon-ssm-agent || true +systemctl restart amazon-ssm-agent || true +``` + +**Razón:** +- SSM Agent viene pre-instalado en Amazon Linux pero no en Ubuntu +- El script instala automáticamente desde repositorios o descarga desde AWS +- Verifica que el agente esté corriendo correctamente + +--- + +## Guía de Migración: SSH a Session Manager + +### Paso 1: Acceso mediante AWS Console (Web) + +1. Ir a **EC2 > Instances** en la consola de AWS +2. Seleccionar la instancia `proyectosacc-api-prod` +3. Clic en **Connect** > **Session Manager** > **Connect** +4. Se abre una terminal web segura sin necesidad de llaves SSH + +### Paso 2: Acceso mediante AWS CLI (Terminal) + +**Requisitos previos:** +- AWS CLI instalado y configurado (`aws configure`) +- Permisos IAM: `ssm:StartSession`, `ssm:TerminateSession` + +**Comando:** +```bash +aws ssm start-session \ + --target i-XXXXXXXXXXXX \ + --region mx-central-1 +``` + +**Para obtener el ID de instancia:** +```bash +aws ec2 describe-instances \ + --filters "Name=tag:Name,Values=proyectosacc-api-prod" \ + --query 'Reservations[0].Instances[0].InstanceId' \ + --output text +``` + +### Paso 3: Acceso mediante Session Manager + SSH (Port Forwarding) + +Si necesitas tunelizar puertos (por ejemplo, para base de datos): + +```bash +# Crear tunel local al puerto 3306 de RDS +aws ssm start-session \ + --target i-XXXXXXXXXXXX \ + --document-name AWS-StartPortForwardingSession \ + --parameters '{"portNumber":["3306"], "localPortNumber":["3306"]}' +``` + +### Paso 4: Transferencia de Archivos + +Session Manager no soporta SCP directamente. Opciones: + +**Opción A: Usar AWS S3** +```bash +# Subir archivo +aws s3 cp archivo.sql s3://ccsoft-proyectosacc-artifacts-prod/temp/ + +# En la instancia (Session Manager) +aws s3 cp s3://ccsoft-proyectosacc-artifacts-prod/temp/archivo.sql /tmp/ +``` + +**Opción B: Usar AWS CLI con Session Manager** +```bash +# Requiere plugin de Session Manager para AWS CLI +aws ssm start-session --target i-XXXXXXXXXXXX +# Dentro de la sesión, usar aws s3 para transferir archivos +``` + +--- + +## Acceso SSH de Emergencia + +En caso de emergencia donde Session Manager no esté disponible: + +### 1. Actualizar `prod.tfvars` temporalmente: + +```hcl +# Agregar CIDR de la oficina/VPN (ejemplo) +allowed_ssh_cidrs = ["203.0.113.0/24"] # IP de oficina CCsoft +``` + +### 2. Aplicar cambios (SOLO el security group): + +```bash +cd /home/evert/Servidores/Nuve/AWS/proyectosacc/terraform +terraform plan -target=aws_security_group.ec2_api +terraform apply -target=aws_security_group.ec2_api +``` + +### 3. Conectar por SSH: + +```bash +ssh -i ~/.ssh/ccsoft-prod-key thoth@ +``` + +### 4. Después de la emergencia, remover el acceso: + +```hcl +# En prod.tfvars +allowed_ssh_cidrs = [] # Volver a deshabilitar SSH +``` + +```bash +terraform apply -target=aws_security_group.ec2_api +``` + +--- + +## Lista de Verificación de Seguridad + +### Pre-despliegue + +- [ ] Validar que `allowed_ssh_cidrs` esté vacío (`[]`) en producción +- [ ] Verificar que el security group no tenga reglas SSH abiertas +- [ ] Confirmar que SSM Agent está instalado en la AMI base +- [ ] Validar que el rol IAM tenga permisos SSM +- [ ] Probar conexión Session Manager antes del despliegue + +### Post-despliegue + +- [ ] Verificar que SSM Agent está corriendo: `systemctl status amazon-ssm-agent` +- [ ] Confirmar que el puerto 22 no está accesible desde internet (usar `nmap` o `telnet`) +- [ ] Probar acceso Session Manager desde AWS Console +- [ ] Probar acceso Session Manager desde AWS CLI +- [ ] Verificar logs de CloudWatch para conexiones SSM +- [ ] Documentar cualquier incidente de acceso no autorizado + +### Auditoría Periódica (mensual) + +- [ ] Revisar logs de acceso SSM en CloudTrail +- [ ] Verificar que no haya intentos de conexión SSH fallidos +- [ ] Confirmar que no se han agregado CIDRs SSH sin autorización +- [ ] Revisar rotación de llaves del pipeline CI/CD +- [ ] Auditar permisos IAM del rol EC2 + +--- + +## Referencias + +### Documentación AWS + +- [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) +- [Setting up Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started.html) +- [IAM policy for Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html) +- [SSM Agent installation](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-install-ssm-agent.html) + +### Bitbucket Pipelines + +- [Atlassian IP ranges](https://support.atlassian.com/organization-administration/docs/ip-addresses-and-domains-for-atlassian-cloud-products/) +- [Bitbucket Pipelines networking](https://support.atlassian.com/bitbucket-cloud/docs/what-are-the-bitbucket-cloud-ip-addresses-i-should-use-to-configure-my-corporate-firewall/) + +--- + +## Notas Importantes + +### Sobre Bitbucket Pipelines IPs + +Atlassian **NO recomienda** el whitelist de IPs como único mecanismo de seguridad porque: +- Las IPs de Bitbucket Pipelines cambian constantemente +- Son instancias EC2 de AWS en us-east-1 y us-west-2 +- Para pipelines con 4x+ steps, se puede usar `atlassian-ip-ranges: true` + +**Solución implementada:** +- El pipeline CI/CD usa **Session Manager** en lugar de SSH directo +- No se requiere whitelist de IPs de Bitbucket +- El pipeline puede ejecutar comandos en la instancia de forma segura sin abrir puertos + +### Sobre Mexico IP Ranges + +No se han hardcodeado rangos de IPs de ISPs mexicanos (Telmex, Totalplay, Izzi, Axtel) porque: +- Los rangos cambian frecuentemente +- Dificultan el mantenimiento +- No garantizan que solo el equipo de CCsoft acceda + +**Recomendación:** +- Usar la IP de la oficina/VPN de CCsoft para acceso de emergencia +- Solicitar al administrador de red la IP pública estática de la oficina +- Agregar esa IP a `allowed_ssh_cidrs` solo cuando sea necesario + +--- + +## Contacto y Soporte + +- **Equipo de Seguridad:** seguridad@computocontable.com +- **Infraestructura:** infra@computocontable.com +- **Emergencias:** Llamar al +52-XXX-XXX-XXXX + +--- + +*Documento generado el 2026-05-06 como parte de la actualización de seguridad del proyecto SACC.* diff --git a/terraform/data-sources.tf b/terraform/data-sources.tf new file mode 100644 index 0000000..52b44ad --- /dev/null +++ b/terraform/data-sources.tf @@ -0,0 +1,53 @@ +# =============================================================================================================== +# data-sources.tf - Fuentes de datos para detectar recursos existentes +# Descripción: +# Evita la creación duplicada de recursos verificando su existencia en AWS +# antes de intentar crear nuevos recursos. +# =============================================================================================================== + +# ------------------------------------------------------------------------------- +# Verificación de EC2 existente +# ------------------------------------------------------------------------------- +data "aws_instances" "existing_api" { + filter { + name = "tag:Name" + values = ["${var.project_name}-api-${var.environment}"] + } + + filter { + name = "instance-state-name" + values = ["running", "stopped", "stopping"] + } +} + +# ------------------------------------------------------------------------------- +# Verificación de RDS existente +# ------------------------------------------------------------------------------- +data "aws_db_instance" "existing" { + count = var.db_identifier != "" ? 1 : 0 + + db_instance_identifier = var.db_identifier +} + +# ------------------------------------------------------------------------------- +# Verificación de NAT Gateway existente en la VPC +# ------------------------------------------------------------------------------- +data "aws_nat_gateways" "existing" { + filter { + name = "vpc-id" + values = [aws_vpc.main.id] + } + + filter { + name = "state" + values = ["available"] + } +} + +# ------------------------------------------------------------------------------- +# Verificación de CloudFront distribution existente +# ------------------------------------------------------------------------------- +data "aws_cloudfront_distribution" "existing" { + count = var.cloudfront_distribution_id != "" ? 1 : 0 + id = var.cloudfront_distribution_id +} diff --git a/terraform/imports.tf b/terraform/imports.tf new file mode 100644 index 0000000..a7041f2 --- /dev/null +++ b/terraform/imports.tf @@ -0,0 +1,75 @@ +# =============================================================================================================== +# imports.tf - Import blocks para recursos huérfanos detectados en AWS +# Descripción: +# Los siguientes recursos existen en AWS pero NO están en el estado de Terraform. +# Estos import blocks permiten traerlos bajo gestión de Terraform sin recrearlos. +# +# Uso: +# terraform plan -generate-config-out=generated.tf +# # Revisar generated.tf, mover recursos a archivos apropiados, luego: +# terraform plan +# =============================================================================================================== + +# ------------------------------------------------------------------------------- +# Lambda Functions (Scheduler para encender/apagar instancias EC2) +# Detectadas: 2026-05-07 - Existen en AWS pero no en Terraform state +# ------------------------------------------------------------------------------- +import { + to = aws_lambda_function.stop_instances + id = "sacc4-stop-instances" +} + +import { + to = aws_lambda_function.start_instances + id = "sacc4-start-instances" +} + +# ------------------------------------------------------------------------------- +# EventBridge Rules (Schedule para Lambda functions) +# Detectadas: 2026-05-07 - Existen en AWS pero no en Terraform state +# ------------------------------------------------------------------------------- +import { + to = aws_cloudwatch_event_rule.stop_instances_schedule + id = "sacc4-stop-instances-schedule" +} + +import { + to = aws_cloudwatch_event_rule.start_instances_schedule + id = "sacc4-start-instances-schedule" +} + +# =============================================================================================================== +# NOTAS DE IMPLEMENTACIÓN: +# =============================================================================================================== +# +# 1. EJECUTAR PRIMERO (genera configuración): +# terraform plan -generate-config-out=generated.tf +# +# 2. REVISAR generated.tf: +# - Mover aws_lambda_function resources a lambda.tf (crear nuevo archivo) +# - Mover aws_cloudwatch_event_rule resources a events.tf (crear nuevo archivo) +# - Añadir tags consistentes con el proyecto +# - Añadir lifecycle blocks con prevent_destroy = true +# +# 3. LIMPIAR: +# - rm generated.tf +# +# 4. VALIDAR: +# - terraform validate +# - terraform plan +# +# 5. APLICAR (solo después de validar): +# - terraform apply +# +# RECURSOS HUÉRFANOS DETECTADOS: +# - Lambda: sacc4-stop-instances (Python 3.11, creada 2026-05-07) +# - Lambda: sacc4-start-instances (Python 3.11, creada 2026-05-07) +# - EventBridge: sacc4-stop-instances-schedule (ENABLED) +# - EventBridge: sacc4-start-instances-schedule (ENABLED) +# +# EIP HUÉRFANO DETECTADO (requiere limpieza manual): +# - eipalloc-0bdf9c47a80885c7a (78.13.177.201) - No está asociado a ninguna instancia +# Probablemente pertenecía al NAT Gateway anterior que fue recreado. +# Acción recomendada: Liberar manualmente desde la consola AWS o con: +# aws ec2 release-address --allocation-id eipalloc-0bdf9c47a80885c7a +# =============================================================================================================== diff --git a/terraform/main.tf b/terraform/main.tf index 2c28f60..8db12ac 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -441,7 +441,7 @@ resource "aws_db_instance" "main" { lifecycle { prevent_destroy = true - ignore_changes = [ + ignore_changes = [ tags, ] } diff --git a/terraform/variables.tf b/terraform/variables.tf index fbaf240..4a18316 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -125,6 +125,12 @@ variable "db_password" { sensitive = true } +variable "db_identifier" { + description = "Identificador de la instancia RDS para verificar existencia (dejar vacío para nueva creación)" + type = string + default = "" +} + variable "db_allocated_storage" { description = "Almacenamiento asignado a RDS en GB" type = number @@ -152,3 +158,9 @@ variable "cloudfront_price_class" { type = string default = "PriceClass_100" } + +variable "cloudfront_distribution_id" { + description = "ID de la distribución CloudFront existente (dejar vacío para nueva creación)" + type = string + default = "" +}