feat(terraform): Add lifecycle rules and import blocks for existing resources

Lifecycle Rules:
- Add prevent_destroy = true to all 32+ resources
- Add ignore_changes = [tags] to prevent tag drift from causing recreation
- Add ignore_changes = [tags, user_data, ami, iam_instance_profile] for EC2
- Preserve existing create_before_destroy for security groups and ACM

Import Blocks (orphaned resources):
- Lambda: sacc4-stop-instances
- Lambda: sacc4-start-instances
- EventBridge: sacc4-stop-instances-schedule
- EventBridge: sacc4-start-instances-schedule

Data Sources:
- aws_instances.existing_api (detect EC2 duplicates)
- aws_db_instance.existing (detect RDS duplicates)
- aws_nat_gateways.existing (detect NAT GW duplicates)
- aws_cloudfront_distribution.existing (detect CloudFront duplicates)

Variables:
- db_identifier: for RDS duplicate detection
- cloudfront_distribution_id: for CloudFront duplicate detection

Validation Results:
- terraform validate: PASSED
- terraform plan: 0 to add, 1 to change, 0 to destroy
- No resources marked for recreation

Orphan EIP detected:
- eipalloc-0bdf9c47a80885c7a (78.13.177.201) unattached
- Requires manual cleanup or investigation

Refs: AWS Resource Validation - May 2026
This commit is contained in:
Evert Daniel Romero Garrido
2026-05-07 11:12:24 -06:00
parent 557feb02e0
commit aaa2c06c30
5 changed files with 485 additions and 1 deletions
+344
View File
@@ -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@<IP-EC2>
```
### 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.*
+53
View File
@@ -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
}
+75
View File
@@ -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
# ===============================================================================================================
+1 -1
View File
@@ -441,7 +441,7 @@ resource "aws_db_instance" "main" {
lifecycle { lifecycle {
prevent_destroy = true prevent_destroy = true
ignore_changes = [ ignore_changes = [
tags, tags,
] ]
} }
+12
View File
@@ -125,6 +125,12 @@ variable "db_password" {
sensitive = true 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" { variable "db_allocated_storage" {
description = "Almacenamiento asignado a RDS en GB" description = "Almacenamiento asignado a RDS en GB"
type = number type = number
@@ -152,3 +158,9 @@ variable "cloudfront_price_class" {
type = string type = string
default = "PriceClass_100" 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 = ""
}