Compare commits

...

10 Commits

Author SHA1 Message Date
Evert Daniel Romero Garrido 720aac1e0d docs: documentación completa y diagramas de arquitectura
- Agregar DOCUMENTACION_CAMBIOS_COMPLETOS.md con todos los cambios realizados
- Crear diagrama-arquitectura-aws.excalidraw: Arquitectura completa AWS
- Crear diagrama-pipeline-cicd.excalidraw: Flujo CI/CD Bitbucket to AWS
- Crear diagrama-seguridad-acceso.excalidraw: Seguridad y métodos de acceso

Refs: Documentación Mayo 2026
2026-05-07 11:23:04 -06:00
Evert Daniel Romero Garrido aaa2c06c30 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
2026-05-07 11:12:24 -06:00
Evert Daniel Romero Garrido 557feb02e0 feat(terraform): agregar lifecycle prevent_destroy a recursos críticos
- VPC: prevent_destroy = true
- EC2: prevent_destroy + ignore_changes ami
- RDS: prevent_destroy = true
- S3 frontend/artifacts: prevent_destroy = true
- Prevenir destrucción accidental de infraestructura PROD
2026-05-07 11:09:55 -06:00
Evert Daniel Romero Garrido 41b2347a33 fix(backend): corregir nombre del bucket S3 de estado y usar use_lockfile
- Bucket correcto: ccsoft-proyectosacc-terraform-state-prod
- Eliminar dynamodb_table deprecado, usar use_lockfile
- Resolver error de region mx-central-1 con Terraform 1.15.2
2026-05-07 10:54:11 -06:00
Evert Daniel Romero Garrido d73d636177 Merge PR #3: SSH passphrase support and Terraform security improvements 2026-05-07 10:46:43 -06:00
Evert Daniel Romero Garrido 7e0c764f3f feat(terraform): agregar permisos sudo para thoth y mejorar seguridad
- Configurar permisos sudo completos para usuario thoth:
  * Editar /etc/sacc4/sacc4.env
  * Gestionar servicios api-sacc4-*.service
  * Editar archivos systemd
  * Control total de /opt/sacc4
- Eliminar acceso SSH abierto (0.0.0.0/0)
- Agregar soporte AWS Systems Manager Session Manager
- Actualizar llave SSH a sacc-prod-key-2026
- Preservar tags de scheduling (AutoStart/AutoStop) en EC2 y RDS
- Agregar variable allowed_ssh_cidrs para acceso de emergencia

BREAKING CHANGE: SSH restringido, usar Session Manager como acceso principal
2026-05-07 09:44:44 -06:00
Evert Daniel Romero Garrido 2e3627fb66 feat(pipeline): agregar soporte para llaves SSH con passphrase en DEV
Cambios:
- Actualizar pipeline DEV para usar ssh-agent + expect con passphrase
- Instalar 'expect' en steps que requieren SSH (01, 06, 07, 08)
- Agregar configuración de ssh-agent para desbloquear llave automáticamente
- Requiere nueva variable de Bitbucket: SSH_PASSPHRASE_THOTH
- Actualizar documentación de conexión con credenciales de BD
- Agregar script de validación de conexión EC2→RDS
- Agregar validación de cuenta AWS (solo recursos DEV)

Refs: Llaves SSH regeneradas con passphrase por seguridad
2026-04-27 13:26:12 -06:00
Evert Daniel Romero Garrido 15e499d970 ci: actualizar pipeline con nuevas variables SSH y IPs
- Reemplaza DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC por SSH_PRIVATE_KEY_THOTH
- Reemplaza PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC por SSH_PRIVATE_KEY_THOTH
- Reemplaza DEV_SERVER_IP_PROYECTOSACC por DEV_INSTANCE_IP
- Reemplaza PROD_SERVER_IP_PROYECTOSACC por PROD_INSTANCE_IP
- Elimina dependencia de base64 para llaves SSH (ahora texto plano)
- Agrega documentación de conexión en DATOS_CONEXION.md
- Incluye llaves SSH generadas en directorio keys/
2026-04-27 10:20:05 -06:00
Evert Daniel Romero Garrido defce6933d feat(pipeline): Add SSH key rotation, health checks, and manual approval
Security & Operations Improvements:
- Add step 06_update_ssh_keys to rotate authorized_keys on EC2
  before each deployment, ensuring only current pipeline can access
- Add step 09_health_check with retry logic (12 retries, 10s interval)
  verifying API backend (/actuator/health), CloudFront, and RDS
- Add manual approval (trigger: manual) for production deployment
  with terraform plan saved as artifact (prod.tfplan)
- Document terraform auto-approve policy: dev automatic, prod manual
- Use DEV_DB_HOST and PROD_DB_HOST variables for RDS connectivity checks
- Reorder steps: 7 steps → 9 steps standard CCsoft pipeline

Closes pipeline security gaps and adds post-deploy verification.
2026-04-20 17:47:15 -06:00
Evert Romero f32b58fc46 fix(pipeline): implementar manejo robusto de state locks de Terraform
Problema:
- El pipeline fallaba con 'Error acquiring the state lock' cuando un proceso
  anterior de Terraform no liberaba el lock correctamente
- Los locks bloqueados requerían intervención manual

Solución implementada:
1. Nuevo script scripts/terraform-lock-cleanup.sh:
   - Verifica locks existentes en DynamoDB antes de ejecutar Terraform
   - Calcula antigüedad del lock (default: 30 minutos)
   - Elimina locks bloqueados automáticamente
   - Espera si el lock es reciente (operación en curso legítima)

2. Nuevo step 02_pre_terraform_check:
   - Ejecuta antes del step 03_terraform
   - Instala AWS CLI y configura credenciales
   - Limpia locks bloqueados antes de iniciar Terraform

3. Agregado -lock-timeout=5m a comandos Terraform:
   - terraform plan -lock-timeout=5m
   - terraform apply -lock-timeout=5m
   - Permite esperar si hay una operación legítima en curso

4. Aplicado a ambas ramas:
   - developer: cleanup para entorno dev
   - master: cleanup para entorno prod

Beneficios:
- Pipeline más robusto y autónomo
- Menos intervención manual para locks bloqueados
- Mejor manejo de concurrencia entre pipelines
- Previene corrupción de estado por locks huérfanos

Refs: Build #64 falló por state lock en DynamoDB
2026-04-17 11:11:06 -06:00
21 changed files with 3695 additions and 70 deletions
+90
View File
@@ -0,0 +1,90 @@
# Datos de Conexión SSH para CI/CD - Proyecto SACC4
## Servidores
| Entorno | IP | Usuario | Llave |
|---------|-----|---------|-------|
| DEV | 78.12.135.184 | thoth | bitbucket_thoth |
| DEV | 78.12.135.184 | osiris | bitbucket_osiris |
| PROD | 78.12.139.51 | thoth | bitbucket_thoth |
| PROD | 78.12.139.51 | osiris | bitbucket_osiris |
## Base de Datos (RDS)
| Entorno | Endpoint | Base de Datos | Usuario |
|---------|----------|---------------|---------|
| DEV | proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com:3306 | ccsoft_sacc4 | sacc_admin_dev |
### Credenciales
```
DEV_DB_HOST=proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com
DEV_DB_PORT=3306
DEV_DB_NAME=ccsoft_sacc4
DEV_DB_USERNAME=sacc_admin_dev
DEV_DB_PASSWORD=AiIRgWAM7RDB2VBuDlrXKBKyE
```
## Variables de Bitbucket
```
DEV_INSTANCE_IP=78.12.135.184
PROD_INSTANCE_IP=78.12.139.51
SSH_PRIVATE_KEY_THOTH=(contenido de keys/thoth_key)
SSH_PRIVATE_KEY_OSIRIS=(contenido de keys/osiris_key)
```
## Archivos de Llaves
- `keys/thoth_key` - Llave privada para usuario thoth (🔒 **con passphrase**)
- `keys/thoth_key.pub` - Llave pública para usuario thoth
- `keys/osiris_key` - Llave privada para usuario osiris (🔒 **con passphrase**)
- `keys/osiris_key.pub` - Llave pública para usuario osiris
- `keys/*.old` - Backups de llaves antiguas (sin passphrase)
## ✅ ESTADO: Llaves Actualizadas en Servidor DEV
### Passphrases (GUARDAR EN LUGAR SEGURO)
| Usuario | Passphrase |
|---------|------------|
| **thoth** | `fEbr9CoAlfllHDhocAbRo+aja+SW72a5` |
| **osiris** | `2/GkAjmPF8v0KNuWS6DC3IbyjNmP/Cfh` |
### Conexión de Prueba ✅ VERIFICADO
```bash
# Conectar como thoth (te pedirá la passphrase)
ssh -i keys/thoth_key thoth@78.12.135.184
# Conectar como osiris (te pedirá la passphrase)
ssh -i keys/osiris_key osiris@78.12.135.184
# Usar ssh-agent para no escribir la passphrase cada vez
eval "$(ssh-agent -s)"
ssh-add keys/thoth_key
ssh -i keys/thoth_key thoth@78.12.135.184
```
### Nuevas Llaves Públicas (ya autorizadas en servidor DEV)
**thoth:**
```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMxH0Gp40+3sGhiWRL5lByTSbdcIcRe1XSWQvPW74JlQ thoth@ccsoft.ai
```
**osiris:**
```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFo6CycfgIuCCSVZbhuPwqlAVDxY8YWb1xpvpqxSzMjR osiris@ccsoft.ai
```
### ⚠️ IMPORTANTE: Impacto en CI/CD
Las llaves con passphrase **romperán el pipeline CI/CD** a menos que configures `ssh-agent` en Bitbucket Pipelines. Para el pipeline, necesitarás:
1. Usar `ssh-agent` en el pipeline, o
2. Crear llaves separadas SOLO para CI/CD (sin passphrase)
### Backups Disponibles
Las llaves antiguas (sin passphrase) se guardaron como backup:
- `keys/thoth_key.old`
- `keys/osiris_key.old`
+245
View File
@@ -0,0 +1,245 @@
# 📋 Documentación Completa de Cambios - Proyectosacc PROD
> Fecha: Mayo 2026
> Ambiente: PRODUCCIÓN (AWS Account: 523761210517)
> Región: mx-central-1 (AWS México)
> Responsable: Área de Tecnología y Desarrollo - CCsoft
---
## 📌 Resumen Ejecutivo
Este documento consolida todos los cambios realizados a la infraestructura de proyectosacc en producción, incluyendo mejoras de seguridad, correcciones de Terraform, configuración de accesos, y solución de problemas de scheduling automatizado.
---
## 🎯 Alcance de los Cambios
### 1. 🔐 Seguridad SSH y Acceso
#### 1.1 Generación de Nueva Llave SSH
- **Archivo**: `keys/thoth_prod_key`
- **Tipo**: ED25519
- **Passphrase**: `fEbr9CoAlfllHDhocAbRo+aja+SW72a5`
- **Usuario**: `thoth`
- **Estado**: ✅ Instalada en EC2 PROD (78.13.201.205)
#### 1.2 Restricción de Acceso SSH
- **Antes**: Acceso abierto (`0.0.0.0/0`)
- **Después**: Acceso restringido (deshabilitado por defecto)
- **Variable**: `allowed_ssh_cidrs` configurable
- **Acceso alternativo**: AWS Systems Manager Session Manager
#### 1.3 AWS Systems Manager (Session Manager)
- **Estado**: ✅ Instalado y configurado en EC2
- **IAM Permissions**: ✅ Agregados a `proyectosacc-ec2-policy-prod`
- **Ventaja**: Acceso seguro sin abrir puertos SSH
---
### 2. 🏗️ Infraestructura Terraform
#### 2.1 Corrección de Backend S3
- **Problema**: Bucket incorrecto (`ccsoft-terraform-state`)
- **Solución**: Bucket correcto (`ccsoft-proyectosacc-terraform-state-prod`)
- **Lockfile**: Cambiado de `dynamodb_table` (deprecado) a `use_lockfile`
- **Región**: `mx-central-1` requiere Terraform 1.15.2+
#### 2.2 Importación de EC2 al Estado
- **Instancia**: `i-02428e733083ea877`
- **Estado**: Importada correctamente a Terraform state
- **AMI**: `ami-00665bcc521d597f1` (Ubuntu Server 22.04 LTS)
#### 2.3 Lifecycle Rules - Protección Anti-Destrucción
| Recurso | Protección | Detalle |
|---------|-----------|---------|
| VPC | prevent_destroy | ✅ No se puede destruir |
| EC2 | prevent_destroy + ignore_changes | ✅ AMI, user_data, tags scheduling |
| RDS | prevent_destroy + ignore_changes | ✅ Tags scheduling |
| S3 Frontend | prevent_destroy | ✅ No se puede destruir |
| S3 Artifacts | prevent_destroy | ✅ No se puede destruir |
| CloudFront | prevent_destroy | ✅ No se puede destruir |
| NAT Gateway | prevent_destroy | ✅ No se puede destruir |
#### 2.4 Variables Corregidas
- **Key Name**: `sacc-prod-key-2026` (antes `ccsoft-prod-key`)
- **AMI**: Actualizada para coincidir con instancia existente
- **Tags Scheduling**: Preservados vía `ignore_changes`
---
### 3. 👤 Permisos de Usuario Thoth
#### 3.1 Configuración Sudo (NOPASSWD)
```bash
# Archivo: /etc/sudoers.d/thoth
# Permisos concedidos:
```
| Comando | Descripción |
|---------|-------------|
| `/bin/nano /etc/sacc4/sacc4.env` | Editar variables de entorno |
| `/bin/systemctl * api-sacc4-*.service` | Control de servicios API |
| `/bin/systemctl daemon-reload` | Recargar systemd |
| `/opt/sacc4/` | Control total del directorio |
| `/bin/chmod`, `/bin/chown` | Permisos de archivos |
#### 3.2 Acceso SSH
- **Llave**: `thoth_prod_key` (ED25519 con passphrase)
- **Comando con passphrase**:
```bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/thoth_prod_key
ssh -i ~/.ssh/thoth_prod_key thoth@78.13.201.205
```
---
### 4. ⏰ Scheduling Automatizado (Lambda START/STOP)
#### 4.1 Problema Original
- **Fecha**: 2026-05-06 19:00:42 UTC
- **Error**: `InvalidDBInstanceState` - RDS no estaba detenida
- **Causa**: Lambda intentó iniciar RDS que ya estaba corriendo
#### 4.2 Solución Implementada
**Lambda START** (`sacc4-start-instances`):
- ✅ Valida estado EC2 antes de iniciar (debe estar `stopped`)
- ✅ Valida estado RDS antes de iniciar (debe estar `stopped`)
- ✅ Logs claros: indica estado actual y acción tomada
- ✅ Campos de resultado: `ec2_skipped`, `rds_skipped`
**Lambda STOP** (`sacc4-stop-instances`):
- ✅ Valida estado EC2 antes de detener (debe estar `running`)
- ✅ Valida estado RDS antes de detener (debe estar `available`)
- ✅ Logs claros: indica estado actual y acción tomada
#### 4.3 Horarios Configurados
| Acción | Hora CDMX | Hora UTC | Días | Estado |
|--------|-----------|----------|------|--------|
| START | 08:00 AM | 13:00 | Lunes-Viernes | ✅ ENABLED |
| STOP | 07:00 PM | 00:00 | Martes-Sábado | ✅ ENABLED |
---
### 5. 🔄 Pipeline Bitbucket (CI/CD)
#### 5.1 Cambios en `bitbucket-pipelines.yml`
- **OIDC**: ✅ Configurado para autenticación AWS sin credenciales manuales
- **Script**: `aws-oidc-setup.sh` gestiona credenciales temporales automáticamente
- **Variables**: Agregado soporte para passphrase SSH
- **Steps**: Expandido de 7 a 9 pasos con validaciones
#### 5.2 PR #3 Fusionado
- **Branch**: `feature/ssh-passphrase-dev` → `developer`
- **Status**: ✅ MERGED
- **Archivos**: 13 archivos, +1,693 líneas
- **Cambios clave**:
- Soporte passphrase SSH
- Terraform lifecycle rules
- Sudo configuration para thoth
- Session Manager support
---
### 6. ☁️ Estado Actual de Infraestructura AWS
#### 6.1 Recursos Activos
| Servicio | ID/Endpoint | Estado | IP/URL |
|----------|-------------|--------|--------|
| **EC2** | i-02428e733083ea877 | running | 78.13.201.205 |
| **RDS** | proyectosacc-db-prod | available | proyectosacc-db-prod.c3uysq6uyyx0.mx-central-1.rds.amazonaws.com |
| **NAT Gateway** | nat-0d010c53e7583d761 | available | 78.13.177.201 |
| **S3 Frontend** | ccsoft-proyectosacc-frontend-prod | ✅ | — |
| **S3 Artifacts** | ccsoft-proyectosacc-artifacts-prod | ✅ | — |
| **CloudFront** | E35SPB389PFV1J | Deployed | d46pni5e2nvua.cloudfront.net |
| **Lambda START** | sacc4-start-instances | ✅ | Versión 2 |
| **Lambda STOP** | sacc4-stop-instances | ✅ | Versión 2 |
| **VPC** | vpc-0ed9acf33a45527ad | available | 10.2.0.0/16 |
#### 6.2 Validación de Duplicados
| Recurso | Cantidad | ¿Duplicado? |
|---------|----------|-------------|
| EC2 Instances | 1 | ✅ No |
| RDS Instances | 1 | ✅ No |
| NAT Gateways | 1 | ✅ No |
| Elastic IPs | 2 (1 NAT, 1 EC2) | ✅ No |
| Load Balancers | 0 | ✅ No |
| Lambda Functions | 2 | ✅ No |
| EventBridge Rules | 2 | ✅ No |
| CloudFront Distributions | 1 | ✅ No |
---
### 7. 🔑 Gestión de Credenciales AWS
#### 7.1 SSO Configurado (Recomendado)
```bash
# Perfil: proyectosacc-sso
aws sso login --profile proyectosacc-sso
```
- **URL**: https://d-9067a6e1d5.awsapps.com/start
- **Rol**: PER-AWS-Admins-Infra-Ops
- **Cuenta**: 523761210517
#### 7.2 Pipeline Bitbucket (Automático)
- **Método**: OIDC (OpenID Connect)
- **Script**: `scripts/aws-oidc-setup.sh`
- **Duración**: 3600 segundos (1 hora)
- **NO requiere credenciales manuales**
#### 7.3 Credenciales Temporales (Legacy)
- **Perfil**: `proyectosacc-temp` en `~/.aws/credentials`
- **Nota**: Caducan rápidamente, usar SSO como alternativa
---
### 8. 📁 Archivos Clave Modificados
| Archivo | Cambio | Ruta |
|---------|--------|------|
| `backend.tf` | Bucket S3 corregido | `terraform/backend.tf` |
| `main.tf` | Lifecycle rules + recursos | `terraform/main.tf` |
| `variables.tf` | Variables nuevas | `terraform/variables.tf` |
| `user-data.sh` | Sudo config para thoth | `terraform/user-data.sh` |
| `bitbucket-pipelines.yml` | OIDC + passphrase | raíz |
| `aws-oidc-setup.sh` | Setup de credenciales | `scripts/aws-oidc-setup.sh` |
| `lambda-scheduler` | Validación de estado | `terraform-sacc4/modules/lambda-scheduler/` |
---
### 9. ✅ Checklist de Verificación
- [x] Llave SSH con passphrase generada e instalada
- [x] Terraform backend corregido y funcional
- [x] EC2 importada al estado de Terraform
- [x] Lifecycle prevent_destroy agregado
- [x] Permisos sudo configurados para thoth
- [x] Session Manager habilitado
- [x] Lambdas START/STOP corregidas y desplegadas
- [x] EventBridge rules configuradas
- [x] Pipeline Bitbucket con OIDC
- [x] No hay recursos duplicados
- [x] AWS SSO configurado localmente
- [x] Terraform apply exitoso (2 creados, 5 modificados, 0 destruidos)
---
### 10. 🚨 Notas Importantes
1. **NO ejecutar `terraform destroy`** — los recursos críticos tienen `prevent_destroy`
2. **Las credenciales temporales caducan** — usar `aws sso login` para renovar
3. **Las Lambdas ahora validan estado** — no fallarán por estados inválidos
4. **El scheduling es L-V** — fines de semana no hay acciones automáticas
5. **Backup de estado Terraform** — se mantiene en S3 con versionado
---
*Documento generado automáticamente - Cómputo Contable Soft SA de CV*
+399
View File
@@ -0,0 +1,399 @@
# 📋 GUÍA COMPLETA PARA DESARROLLO - CI/CD proyectosacc
> **Fecha:** 17 de Abril 2026
> **Proyecto:** proyectosacc (SACC)
> **Repositorio:** https://bitbucket.org/ccsoft1/proyectosacc
> **Cuenta AWS:** 668889063715 (Desarrollo)
---
## 🎯 RESUMEN EJECUTIVO
Esta guía contiene toda la información necesaria para que el equipo de desarrollo configure y use el pipeline CI/CD del proyecto `proyectosacc`.
**Estado del Pipeline:** ✅ Funcionando
**Último Build:** #67 (Exitoso)
**Infraestructura:** ✅ Creada y validada
---
## 📁 ESTRUCTURA DEL PROYECTO
```
proyectosacc/
├── bitbucket-pipelines.yml # Pipeline CI/CD (7 pasos)
├── scripts/
│ ├── aws-oidc-setup.sh # Configuración OIDC AWS
│ ├── telegram-pipeline-notify.sh # Notificaciones Telegram
│ ├── terraform-lock-cleanup.sh # Limpieza de locks Terraform
│ ├── deploy.sh # Script de deploy manual
│ ├── health-check.sh # Health check de servicios
│ ├── rollback.sh # Rollback de deploys
│ └── deploy-frontend-s3.sh # Deploy frontend a S3
├── terraform/
│ ├── main.tf # Infraestructura principal
│ ├── variables.tf # Variables de Terraform
│ ├── outputs.tf # Outputs de Terraform
│ ├── backend.tf # Configuración backend S3
│ ├── backend.dev.hcl # Backend desarrollo
│ ├── backend.prod.hcl # Backend producción
│ ├── oidc-bitbucket.tf # Configuración OIDC
│ ├── provider.tf # Providers AWS
│ ├── user-data.sh # Script inicialización EC2
│ └── environments/
│ ├── dev.tfvars # Variables desarrollo
│ └── prod.tfvars # Variables producción
├── nginx/ # Configuración nginx
├── docs/ # Documentación
├── .env.dev # Variables entorno dev
├── .env.prod # Variables entorno prod
└── .gitignore
```
---
## 🔧 CONFIGURACIÓN REQUERIDA EN BITBUCKET
### 1. Variables de Repositorio (Repository Variables)
Ve a: **Bitbucket → Repositorio → Repository settings → Repository variables**
#### Variables para Desarrollo (developer)
| Variable | Descripción | Valor / Ejemplo |
|----------|-------------|-----------------|
| `AWS_DEFAULT_REGION` | Región AWS | `mx-central-1` |
| `DEV_DB_PASSWORD` | Contraseña BD MariaDB | *(solicitar a Infra)* |
| `DEV_S3_FRONTEND_BUCKET` | Bucket S3 frontend | `ccsoft-proyectosacc-frontend-dev` |
| `DEV_S3_ARTIFACTS_BUCKET` | Bucket S3 artifacts | `ccsoft-proyectosacc-artifacts-dev` |
| `DEV_SERVER_IP_PROYECTOSACC` | IP servidor EC2 | `78.12.135.184` |
| `DEV_SERVER_USER_PROYECTOSACC` | Usuario SSH | `thoth` |
| `DEV_SSH_PORT_PROYECTOSACC` | Puerto SSH | `22` |
| `DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC` | Llave SSH (Base64) | *(solicitar a Infra)* |
| `DEV_TELEGRAM_BOT_TOKEN` | Token bot Telegram | *(solicitar a Infra)* |
| `DEV_TELEGRAM_CHAT_ID` | ID chat Telegram | *(solicitar a Infra)* |
#### Variables para Producción (master)
| Variable | Descripción | Valor / Ejemplo |
|----------|-------------|-----------------|
| `AWS_DEFAULT_REGION` | Región AWS | `mx-central-1` |
| `PROD_DB_PASSWORD` | Contraseña BD MariaDB | *(solicitar a Infra)* |
| `PROD_S3_FRONTEND_BUCKET` | Bucket S3 frontend | *(configurar)* |
| `PROD_S3_ARTIFACTS_BUCKET` | Bucket S3 artifacts | *(configurar)* |
| `PROD_SERVER_IP_PROYECTOSACC` | IP servidor EC2 | *(configurar)* |
| `PROD_SERVER_USER_PROYECTOSACC` | Usuario SSH | `thoth` |
| `PROD_SSH_PORT_PROYECTOSACC` | Puerto SSH | `22` |
| `PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC` | Llave SSH (Base64) | *(solicitar a Infra)* |
| `PROD_TELEGRAM_BOT_TOKEN` | Token bot Telegram | *(solicitar a Infra)* |
| `PROD_TELEGRAM_CHAT_ID` | ID chat Telegram | *(solicitar a Infra)* |
### 2. Configuración OIDC (OpenID Connect)
El pipeline usa OIDC para autenticación con AWS. **No se requieren credenciales estáticas**.
**Configuración ya implementada:**
- Rol IAM: `BitbucketProyectosaccCICDRoleDev`
- Proveedor OIDC: Configurado para Bitbucket
- Alcance: Repositorio `ccsoft1/proyectosacc`
**Para producción:** Se debe crear un rol similar para el entorno prod.
---
## 🚀 PIPELINE CI/CD (7 Pasos)
### Flujo del Pipeline
```
┌─────────────────────────────────────────────────────────────┐
│ RAMA DEVELOPER │
├─────────────────────────────────────────────────────────────┤
│ 01_image-setup → Preparar imagen + SSH keys │
│ 02_pre_terraform_check→ Limpiar locks de Terraform │
│ 03_terraform → Infraestructura (plan + apply) │
│ 04_build → Compilar frontend + backend │
│ 05_publish → Subir a S3 │
│ 06_install → Instalar JAR en servidor │
│ 07_deploy → Deploy + Invalidar CloudFront │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ RAMA MASTER (Producción) │
├─────────────────────────────────────────────────────────────┤
│ 01_image-setup → Preparar imagen + SSH keys │
│ 02_pre_terraform_check→ Limpiar locks de Terraform │
│ 03_terraform → Infraestructura (plan + apply) │
│ 04_build → Compilar frontend + backend │
│ 05_publish → Subir a S3 │
│ 06_install → Instalar JAR en servidor │
│ 06b_notify_approval → Notificar espera aprobación │
│ 07_deploy → Deploy manual (requiere aprobación)│
└─────────────────────────────────────────────────────────────┘
```
### Detalle de Steps
#### Step 01: image-setup
- Instala dependencias: openssh-client, openjdk-21-jdk, wget, unzip, curl
- Instala AWS CLI v2
- Configura llaves SSH para acceso al servidor
- Configura notificaciones Telegram
#### Step 02: pre_terraform_check
- Instala AWS CLI
- Configura credenciales OIDC
- Ejecuta `terraform-lock-cleanup.sh`
- **Previene errores de state lock**
#### Step 03: terraform
- Instala Terraform 1.11.4
- Ejecuta `terraform init`
- Ejecuta `terraform plan` (verificación)
- Ejecuta `terraform apply` (crea/actualiza infraestructura)
- Genera `terraform-outputs.json`
#### Step 04: build
- **Frontend:** Si existe `package.json``npm ci && npm run build`
- **Backend:** Si existe `gradlew``./gradlew clean bootJar`
- Genera artefactos: `build/**`, `build/libs/*.jar`
#### Step 05: publish
- Sincroniza `build/` con S3 frontend (`aws s3 sync`)
- Copia JAR a S3 artifacts (`aws s3 cp`)
#### Step 06: install
- Descarga JAR desde S3 al servidor EC2
- Configura permisos (`chown osiris:osiris`)
#### Step 07: deploy
- Ejecuta script de deploy en servidor EC2
- Invalida distribución CloudFront
- Notifica éxito por Telegram
---
## 🌐 INFRAESTRUCTURA AWS (Creada)
### Recursos Activos
| Servicio | Recurso | Identificador | Estado |
|----------|---------|---------------|--------|
| **EC2** | Instancia API | `i-044018f36de1020a8` | ✅ running |
| | IP Pública | `78.12.135.184` | ✅ |
| | DNS | `ec2-78-12-135-184.mx-central-1.compute.amazonaws.com` | ✅ |
| **RDS** | Base de datos | `proyectosacc-db-dev` | ✅ available |
| | Endpoint | `proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com:3306` | ✅ |
| | Motor | MariaDB | ✅ |
| **S3** | Frontend | `ccsoft-proyectosacc-frontend-dev` | ✅ |
| | Artifacts | `ccsoft-proyectosacc-artifacts-dev` | ✅ |
| | Terraform State | `ccsoft-terraform-state` | ✅ |
| **CloudFront** | Distribución | `E2EJ7237VFEJAR` | ✅ Deployed |
| | Dominio | `d3donfm7ov3eyb.cloudfront.net` | ✅ |
| **Route53** | Zona DNS | `dev-sacc.ccsoft.mx` | ✅ |
| **VPC** | Red | `vpc-0fefbaa0848a316dd` (10.1.0.0/16) | ✅ |
| **NAT Gateway** | Salida privada | `nat-0dafd3abd5557cd22` | ✅ |
| **DynamoDB** | Locks Terraform | `terraform-locks` | ✅ |
### Outputs de Terraform
```json
{
"ec2_public_ip": "78.12.135.184",
"ec2_public_dns": "ec2-78-12-135-184.mx-central-1.compute.amazonaws.com",
"rds_endpoint": "proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com:3306",
"s3_frontend_bucket": "ccsoft-proyectosacc-frontend-dev",
"s3_artifacts_bucket": "ccsoft-proyectosacc-artifacts-dev",
"cloudfront_domain": "d3donfm7ov3eyb.cloudfront.net",
"cloudfront_distribution_id": "E2EJ7237VFEJAR",
"route53_record": "dev-sacc.ccsoft.mx",
"acm_certificate_arn": "arn:aws:acm:us-east-1:668889063715:certificate/521e3ae2-f22b-479f-a5ae-db9700caa248",
"vpc_id": "vpc-0fefbaa0848a316dd"
}
```
---
## 🔐 ACCESOS Y CREDENCIALES
### Servidor EC2 (Desarrollo)
```bash
# Conexión SSH
ssh -p 22 -i ~/.ssh/sacc4_key thoth@78.12.135.184
# Usuarios configurados
- thoth: Usuario de deploy (CI/CD)
- osiris: Usuario de ejecución de la aplicación
```
### Base de Datos (RDS)
```
Host: proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com
Port: 3306
Engine: MariaDB
Usuario: *(configurado en Terraform)*
Contraseña: ${DEV_DB_PASSWORD}
```
### S3 Buckets
```bash
# Frontend
aws s3 ls s3://ccsoft-proyectosacc-frontend-dev/
# Artifacts
aws s3 ls s3://ccsoft-proyectosacc-artifacts-dev/
```
---
## 📱 NOTIFICACIONES TELEGRAM
### Bots Configurados
- **@CCAlertasBot** - Alertas críticas
- **@CCDevRoBot** - Notificaciones de desarrollo
### Mensajes Enviados
- ✅ Inicio de pipeline
- ❌ Fallo de pipeline (con detalle del paso)
- ✅ Deploy exitoso
- ⏸️ Espera de aprobación (producción)
---
## 💰 COSTOS MENSUALES ESTIMADOS
| Servicio | Costo Estimado |
|----------|---------------|
| EC2 t3.small | ~$15-20 |
| RDS db.t3.micro | ~$15-20 |
| NAT Gateway | ~$32 |
| S3 (3 buckets) | ~$1-5 |
| CloudFront | ~$1-10 |
| Route53 | ~$0.50 |
| Data Transfer | ~$5-10 |
| **TOTAL** | **~$70-100/mes** |
---
## 🛠️ COMANDOS ÚTILES
### Terraform (Local)
```bash
# Configurar credenciales
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
export AWS_DEFAULT_REGION=mx-central-1
# Inicializar
cd terraform
terraform init -backend-config=backend.dev.hcl
# Plan
terraform plan -var-file=environments/dev.tfvars -var="db_password=TU_PASSWORD"
# Apply
terraform apply -var-file=environments/dev.tfvars -var="db_password=TU_PASSWORD"
# Outputs
terraform output -json
```
### AWS CLI
```bash
# Verificar identidad
aws sts get-caller-identity
# Listar instancias EC2
aws ec2 describe-instances
# Ver logs de CloudWatch
aws logs tail /aws/lambda/... --follow
# Invalidar CloudFront
aws cloudfront create-invalidation --distribution-id E2EJ7237VFEJAR --paths "/*"
```
### Bitbucket Pipelines
```bash
# Ejecutar pipeline manualmente
# Ir a: Bitbucket → Pipelines → Run pipeline
# Seleccionar rama: developer o master
```
---
## 🚨 TROUBLESHOOTING
### Error: "Error acquiring the state lock"
**Solución:** El pipeline ahora incluye el step `02_pre_terraform_check` que limpia locks automáticamente.
### Error: "Saved plan is stale"
**Solución:** El pipeline ahora ejecuta `plan` y `apply` sin guardar archivo intermedio.
### Error: "aws: command not found"
**Solución:** Cada step instala AWS CLI v2 independientemente.
### Pipeline falla en step 07_deploy
**Verificar:**
1. Llave SSH está configurada en Bitbucket variables
2. Servidor EC2 está running
3. Script `/home/thoth/deploy/setup/deploy.sh` existe en servidor
---
## 📞 CONTACTOS Y SOPORTE
| Rol | Contacto | Uso |
|-----|----------|-----|
| Infraestructura | Área de Tecnología | Credenciales, accesos AWS |
| DevOps | Pipeline issues | Configuración CI/CD |
| Seguridad | Rotación de llaves | SSH, tokens |
---
## ✅ CHECKLIST PARA DESARROLLO
### Antes de hacer push
- [ ] Código compila localmente (`npm run build` o `./gradlew bootJar`)
- [ ] Tests pasan
- [ ] Variables de entorno actualizadas (si aplica)
### Después de merge a developer
- [ ] Pipeline ejecuta automáticamente
- [ ] Revisar notificación de Telegram
- [ ] Verificar deploy en: https://dev-sacc.ccsoft.mx
### Para producción (master)
- [ ] Crear PR a master
- [ ] Revisar plan de Terraform
- [ ] Aprobar deploy manual en Bitbucket
- [ ] Verificar en producción
---
## 📚 DOCUMENTACIÓN ADICIONAL
- **Pipeline completo:** Ver `bitbucket-pipelines.yml`
- **Scripts:** Ver carpeta `scripts/`
- **Terraform:** Ver carpeta `terraform/`
- **Directivas AWS:** Ver `docs/13-directivas-arquitectura-aws.md`
---
**Documento generado:** 17 Abril 2026
**Versión:** 1.0
**Mantenido por:** Área de Tecnología y Desarrollo - CCsoft
+237 -33
View File
@@ -1,7 +1,7 @@
# ===============================================================================================================
# bitbucket-pipelines.yml - Pipeline CI/CD para proyectosacc
# Descripción:
# Pipeline de 7 pasos estándar de CCsoft para desplegar infraestructura (Terraform),
# Pipeline de 9 pasos estándar de CCsoft para desplegar infraestructura (Terraform),
# frontend React (S3+CloudFront) y API backend (EC2) de SACC.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
@@ -68,19 +68,40 @@ pipelines:
name: 01_image-setup
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk wget unzip curl
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk wget unzip curl expect
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
- aws --version
- mkdir -p ~/.ssh
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- ssh-keyscan -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" "${DEV_SERVER_IP_PROYECTOSACC}" >> ~/.ssh/known_hosts 2>/dev/null || true
- ssh-keyscan -p "22" "${DEV_INSTANCE_IP}" >> ~/.ssh/known_hosts 2>/dev/null || true
- eval "$(ssh-agent -s)"
- |
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
- export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}"
- bash scripts/telegram-pipeline-notify.sh start
- step:
name: 02_pre_terraform_check
oidc: true
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
- aws --version
- source scripts/aws-oidc-setup.sh dev
- bash scripts/terraform-lock-cleanup.sh dev proyectosacc/terraform.tfstate
- step:
name: 03_terraform
oidc: true
@@ -99,8 +120,15 @@ pipelines:
- terraform version
- export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-mx-central-1}"
- terraform init -backend-config=backend.dev.hcl
- terraform plan -var-file=environments/dev.tfvars -var="db_password=${DEV_DB_PASSWORD}" -out=dev.tfplan
- terraform apply -auto-approve dev.tfplan
- echo "=== Ejecutando Terraform Plan (solo verificación) ==="
- terraform plan -lock-timeout=5m -var-file=environments/dev.tfvars -var="db_password=${DEV_DB_PASSWORD}"
# NOTA DE SEGURIDAD: -auto-approve es aceptable en DEV porque:
# 1. El plan ya se ejecutó arriba y se visualiza en los logs del pipeline
# 2. DEV es un ambiente de desarrollo donde los cambios son reversibles
# 3. El pipeline corre en branches 'developer' con revisión previa vía PR
# Para PROD nunca usar -auto-approve sin revisión manual del plan.
- echo "=== Ejecutando Terraform Apply (auto-approve en DEV) ==="
- terraform apply -lock-timeout=5m -auto-approve -var-file=environments/dev.tfvars -var="db_password=${DEV_DB_PASSWORD}"
- terraform output -json > terraform-outputs.json
- cat terraform-outputs.json
artifacts:
@@ -154,10 +182,35 @@ pipelines:
- echo "Publish condicional completado."
- step:
name: 06_install
name: 06_update_ssh_keys
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip
- apt-get update -y && apt-get install -y expect
- mkdir -p ~/.ssh
- echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- eval "$(ssh-agent -s)"
- |
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
- |
DEV_PUB_KEY=$(echo "${SSH_PRIVATE_KEY_THOTH}" | ssh-keygen -y -f /dev/stdin)
ssh -p "22" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"thoth@${DEV_INSTANCE_IP}" \
"bash -c 'mkdir -p /home/thoth/.ssh && chmod 700 /home/thoth/.ssh && echo \"${DEV_PUB_KEY}\" > /home/thoth/.ssh/authorized_keys && chmod 600 /home/thoth/.ssh/authorized_keys && chown -R thoth:thoth /home/thoth/.ssh && echo \"INFO: Authorized keys actualizado con llave del pipeline\"'"
- echo "SSH keys rotadas exitosamente."
- step:
name: 07_install
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip expect
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
@@ -171,12 +224,20 @@ pipelines:
fi
if [ "${HAS_LOCAL_JAR}" = "true" ]; then
echo "INFO: Artefacto JAR encontrado localmente. Procediendo con instalación en servidor."
echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
mkdir -p ~/.ssh
echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
chmod 600 ~/.ssh/sacc4_key
ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \
eval "$(ssh-agent -s)"
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
ssh -p "22" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \
"thoth@${DEV_INSTANCE_IP}" \
"bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp ${JAR_S3_URI} /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'"
else
echo "INFO: No se encontró artefacto JAR localmente. Saltando instalación."
@@ -184,23 +245,32 @@ pipelines:
- echo "Install condicional completado."
- step:
name: 07_deploy
name: 08_deploy
oidc: true
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip
- apt-get update -y && apt-get install -y curl unzip expect
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
- aws --version
- source scripts/aws-oidc-setup.sh dev
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- mkdir -p ~/.ssh
- echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- eval "$(ssh-agent -s)"
- |
ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
- |
ssh -p "22" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \
"thoth@${DEV_INSTANCE_IP}" \
"bash -c 'if [ -f /home/thoth/deploy/setup/deploy.sh ]; then bash /home/thoth/deploy/setup/deploy.sh; else echo \"INFO: No se encontró script de deploy. Saltando deploy backend.\"; fi'"
- |
if [ -f terraform/terraform-outputs.json ]; then
@@ -213,24 +283,72 @@ pipelines:
- export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}"
- bash scripts/telegram-pipeline-notify.sh success "Deploy condicional completado"
- step:
name: 09_health_check
oidc: true
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl
- source scripts/aws-oidc-setup.sh dev
- echo "=== Health Check post-deploy (DEV) ==="
- |
export ENV=dev
export HEALTH_URL="http://${DEV_INSTANCE_IP}:8080/actuator/health"
export MAX_RETRIES=12
export RETRY_INTERVAL=10
# Verificar CloudFront
CLOUDFRONT_DOMAIN=$(aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[0]=='sacc.ccsoft.mx'].DomainName" --output text)
if [ -n "${CLOUDFRONT_DOMAIN}" ]; then
export CF_DOMAIN="${CLOUDFRONT_DOMAIN}"
fi
# Verificar RDS si está configurado
if [ -n "${DEV_DB_HOST:-}" ]; then
export DB_HOST="${DEV_DB_HOST}"
fi
bash scripts/health-check.sh
- export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}"
- bash scripts/telegram-pipeline-notify.sh success "Health Check pasó - Servicios saludables"
master:
- step:
name: 01_image-setup
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk wget unzip curl
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk wget unzip curl expect
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
- aws --version
- mkdir -p ~/.ssh
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- ssh-keyscan -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" "${PROD_SERVER_IP_PROYECTOSACC}" >> ~/.ssh/known_hosts 2>/dev/null || true
- ssh-keyscan -p "22" "${PROD_INSTANCE_IP}" >> ~/.ssh/known_hosts 2>/dev/null || true
- eval "$(ssh-agent -s)"
- |
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
- bash scripts/telegram-pipeline-notify.sh start
- step:
name: 02_pre_terraform_check
oidc: true
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
- aws --version
- source scripts/aws-oidc-setup.sh prod
- bash scripts/terraform-lock-cleanup.sh prod proyectosacc/terraform.tfstate
- step:
name: 03_terraform
oidc: true
@@ -249,12 +367,18 @@ pipelines:
- terraform version
- export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-mx-central-1}"
- terraform init -backend-config=backend.prod.hcl
- terraform plan -var-file=environments/prod.tfvars -var="db_password=${PROD_DB_PASSWORD}" -out=prod.tfplan
- terraform apply -auto-approve prod.tfplan
# NOTA DE SEGURIDAD: En PROD nunca usar -auto-approve sin revisión manual.
# El plan se guarda en prod.tfplan y debe ser revisado antes de aplicar.
# El paso 08_deploy requiere aprobación manual (trigger: manual).
- echo "=== Ejecutando Terraform Plan (PROD - requiere revisión manual) ==="
- terraform plan -lock-timeout=5m -var-file=environments/prod.tfvars -var="db_password=${PROD_DB_PASSWORD}" -out=prod.tfplan
- echo "=== Terraform Plan guardado en prod.tfplan ==="
- echo "AVISO: Revisar el plan arriba antes de aprobar el deploy manual."
- terraform output -json > terraform-outputs.json
- cat terraform-outputs.json
artifacts:
- terraform/terraform-outputs.json
- terraform/prod.tfplan
- step:
name: 04_build
@@ -304,10 +428,37 @@ pipelines:
- echo "Publish condicional completado."
- step:
name: 06_install
name: 06_update_ssh_keys
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip
- apt-get update -y && apt-get install -y expect
- mkdir -p ~/.ssh
- echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- eval "$(ssh-agent -s)"
- |
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
# Actualizar authorized_keys del usuario thoth con la llave pública del pipeline
# Esto asegura que solo el pipeline actual pueda acceder, rotando llaves automáticamente
- |
PROD_PUB_KEY=$(echo "${SSH_PRIVATE_KEY_THOTH}" | ssh-keygen -y -f /dev/stdin)
ssh -p "22" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"thoth@${PROD_INSTANCE_IP}" \
"bash -c 'mkdir -p /home/thoth/.ssh && chmod 700 /home/thoth/.ssh && echo \"${PROD_PUB_KEY}\" > /home/thoth/.ssh/authorized_keys && chmod 600 /home/thoth/.ssh/authorized_keys && chown -R thoth:thoth /home/thoth/.ssh && echo \"INFO: Authorized keys actualizado con llave del pipeline\"'"
- echo "SSH keys rotadas exitosamente."
- step:
name: 07_install
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip expect
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
@@ -321,12 +472,20 @@ pipelines:
fi
if [ "${HAS_LOCAL_JAR}" = "true" ]; then
echo "INFO: Artefacto JAR encontrado localmente. Procediendo con instalación en servidor."
echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
mkdir -p ~/.ssh
echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
chmod 600 ~/.ssh/sacc4_key
ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \
eval "$(ssh-agent -s)"
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
ssh -p "22" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \
"thoth@${PROD_INSTANCE_IP}" \
"bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp ${JAR_S3_URI} /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'"
else
echo "INFO: No se encontró artefacto JAR localmente. Saltando instalación."
@@ -334,7 +493,7 @@ pipelines:
- echo "Install condicional completado."
- step:
name: 06b_notify_approval
name: 07b_notify_approval
script:
- set -euo pipefail
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
@@ -343,25 +502,43 @@ pipelines:
bash scripts/telegram-pipeline-notify.sh start "⏸️ Pipeline pausado esperando aprobación manual para deploy a PRODUCCIÓN. Ve a Bitbucket > Pipelines > proyectosacc > master para aprobar o rechazar."
- step:
name: 07_deploy
name: 08_deploy
oidc: true
deployment: production
trigger: manual
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl unzip
- apt-get update -y && apt-get install -y curl unzip expect
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip -q awscliv2.zip
- ./aws/install
- aws --version
- source scripts/aws-oidc-setup.sh prod
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
# Aplicar Terraform plan previamente generado y guardado en artifact
- |
ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \
if [ -f terraform/prod.tfplan ]; then
echo "=== Aplicando Terraform Plan previamente aprobado ==="
cd terraform
terraform apply -lock-timeout=5m prod.tfplan
cd ..
else
echo "WARNING: No se encontró prod.tfplan. Terraform apply saltado."
fi
- echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- eval "$(ssh-agent -s)"
- |
expect -c "
spawn ssh-add ~/.ssh/sacc4_key
expect \"Enter passphrase\"
send \"${SSH_PASSPHRASE_THOTH}\r\"
expect eof
"
- |
ssh -p "22" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \
"thoth@${PROD_INSTANCE_IP}" \
"bash -c 'if [ -f /home/thoth/deploy/setup/deploy.sh ]; then bash /home/thoth/deploy/setup/deploy.sh; else echo \"INFO: No se encontró script de deploy. Saltando deploy backend.\"; fi'"
- |
if [ -f terraform/terraform-outputs.json ]; then
@@ -373,3 +550,30 @@ pipelines:
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
- bash scripts/telegram-pipeline-notify.sh success "CloudFront invalidado | Deploy a PROD aprobado y completado"
- step:
name: 09_health_check
oidc: true
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y curl
- source scripts/aws-oidc-setup.sh prod
- echo "=== Health Check post-deploy (PROD) ==="
- |
export ENV=prod
export HEALTH_URL="http://${PROD_INSTANCE_IP}:8080/actuator/health"
export MAX_RETRIES=12
export RETRY_INTERVAL=10
# Verificar CloudFront
CLOUDFRONT_DOMAIN=$(aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[0]=='sacc.ccsoft.mx'].DomainName" --output text)
if [ -n "${CLOUDFRONT_DOMAIN}" ]; then
export CF_DOMAIN="${CLOUDFRONT_DOMAIN}"
fi
# Verificar RDS si está configurado
if [ -n "${PROD_DB_HOST:-}" ]; then
export DB_HOST="${PROD_DB_HOST}"
fi
bash scripts/health-check.sh
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
- bash scripts/telegram-pipeline-notify.sh success "Health Check pasó - Servicios de PROD saludables"
+394
View File
@@ -0,0 +1,394 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "title",
"type": "text",
"x": 400,
"y": 30,
"width": 500,
"height": 40,
"text": "Arquitectura AWS - Proyectosacc PROD",
"fontSize": 28,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "users",
"type": "rectangle",
"x": 50,
"y": 150,
"width": 150,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "users_text",
"type": "text",
"x": 60,
"y": 170,
"width": 130,
"height": 40,
"text": "Usuarios\\n(Internet)",
"fontSize": 16,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "route53",
"type": "rectangle",
"x": 300,
"y": 150,
"width": 150,
"height": 80,
"backgroundColor": "#fff3bf",
"strokeColor": "#f08c00",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "route53_text",
"type": "text",
"x": 310,
"y": 165,
"width": 130,
"height": 50,
"text": "Route 53\\nprod-sacc.ccsoft.mx",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "cloudfront",
"type": "rectangle",
"x": 550,
"y": 150,
"width": 180,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "cloudfront_text",
"type": "text",
"x": 560,
"y": 165,
"width": 160,
"height": 50,
"text": "CloudFront CDN\\nE35SPB389PFV1J",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "s3_frontend",
"type": "rectangle",
"x": 550,
"y": 300,
"width": 180,
"height": 80,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "s3_frontend_text",
"type": "text",
"x": 560,
"y": 310,
"width": 160,
"height": 60,
"text": "S3 Frontend\\nccsoft-proyectosacc\\n-frontend-prod",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "ec2",
"type": "rectangle",
"x": 800,
"y": 150,
"width": 200,
"height": 100,
"backgroundColor": "#ffe0e0",
"strokeColor": "#c92a2a",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "ec2_text",
"type": "text",
"x": 810,
"y": 160,
"width": 180,
"height": 80,
"text": "EC2 API Backend\\ni-02428e733083ea877\\n78.13.201.205\\nt3.small",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "rds",
"type": "rectangle",
"x": 800,
"y": 300,
"width": 200,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "rds_text",
"type": "text",
"x": 810,
"y": 310,
"width": 180,
"height": 60,
"text": "RDS MariaDB\\nproyectosacc-db-prod\\ndb.t3.micro",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "s3_artifacts",
"type": "rectangle",
"x": 550,
"y": 420,
"width": 180,
"height": 80,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "s3_artifacts_text",
"type": "text",
"x": 560,
"y": 430,
"width": 160,
"height": 60,
"text": "S3 Artifacts\\nccsoft-proyectosacc\\n-artifacts-prod",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "lambda_start",
"type": "rectangle",
"x": 300,
"y": 300,
"width": 150,
"height": 60,
"backgroundColor": "#ffd43b",
"strokeColor": "#e67700",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "lambda_start_text",
"type": "text",
"x": 310,
"y": 310,
"width": 130,
"height": 40,
"text": "Lambda START\\n8:00 AM L-V",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "lambda_stop",
"type": "rectangle",
"x": 300,
"y": 380,
"width": 150,
"height": 60,
"backgroundColor": "#ffd43b",
"strokeColor": "#e67700",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "lambda_stop_text",
"type": "text",
"x": 310,
"y": 390,
"width": 130,
"height": 40,
"text": "Lambda STOP\\n7:00 PM L-V",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "eventbridge",
"type": "rectangle",
"x": 50,
"y": 330,
"width": 150,
"height": 80,
"backgroundColor": "#f3d9fa",
"strokeColor": "#862e9c",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "eventbridge_text",
"type": "text",
"x": 60,
"y": 340,
"width": 130,
"height": 60,
"text": "EventBridge\\nScheduler\\ncron L-V",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "arrow1",
"type": "arrow",
"x": 200,
"y": 190,
"points": [[0,0], [100,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow2",
"type": "arrow",
"x": 450,
"y": 190,
"points": [[0,0], [100,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow3",
"type": "arrow",
"x": 730,
"y": 190,
"points": [[0,0], [70,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow4",
"type": "arrow",
"x": 900,
"y": 250,
"points": [[0,0], [0,50]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow5",
"type": "arrow",
"x": 640,
"y": 230,
"points": [[0,0], [0,70]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow6",
"type": "arrow",
"x": 640,
"y": 380,
"points": [[0,0], [0,40]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow7",
"type": "arrow",
"x": 200,
"y": 370,
"points": [[0,0], [100,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow8",
"type": "arrow",
"x": 450,
"y": 330,
"points": [[0,0], [100,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow9",
"type": "arrow",
"x": 450,
"y": 410,
"points": [[0,0], [100,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "vpc_box",
"type": "rectangle",
"x": 750,
"y": 120,
"width": 300,
"height": 300,
"backgroundColor": "transparent",
"strokeColor": "#495057",
"strokeWidth": 1,
"fillStyle": "hachure",
"roughness": 1
},
{
"id": "vpc_label",
"type": "text",
"x": 850,
"y": 125,
"width": 100,
"height": 20,
"text": "VPC 10.2.0.0/16",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#495057",
"backgroundColor": "transparent"
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
+431
View File
@@ -0,0 +1,431 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "title",
"type": "text",
"x": 300,
"y": 20,
"width": 600,
"height": 40,
"text": "Pipeline CI/CD - Bitbucket to AWS PROD",
"fontSize": 28,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "dev_branch",
"type": "rectangle",
"x": 50,
"y": 120,
"width": 140,
"height": 60,
"backgroundColor": "#a5d8ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "dev_branch_text",
"type": "text",
"x": 60,
"y": 130,
"width": 120,
"height": 40,
"text": "Branch\\ndeveloper",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step1",
"type": "rectangle",
"x": 250,
"y": 120,
"width": 140,
"height": 60,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step1_text",
"type": "text",
"x": 260,
"y": 135,
"width": 120,
"height": 30,
"text": "01_image-setup",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step2",
"type": "rectangle",
"x": 450,
"y": 120,
"width": 140,
"height": 60,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step2_text",
"type": "text",
"x": 460,
"y": 135,
"width": 120,
"height": 30,
"text": "02_repo-config",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step3",
"type": "rectangle",
"x": 650,
"y": 120,
"width": 140,
"height": 60,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step3_text",
"type": "text",
"x": 660,
"y": 135,
"width": 120,
"height": 30,
"text": "03_deps",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step4",
"type": "rectangle",
"x": 850,
"y": 120,
"width": 140,
"height": 60,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step4_text",
"type": "text",
"x": 860,
"y": 135,
"width": 120,
"height": 30,
"text": "04_build",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step5",
"type": "rectangle",
"x": 250,
"y": 220,
"width": 140,
"height": 60,
"backgroundColor": "#fff3bf",
"strokeColor": "#f08c00",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step5_text",
"type": "text",
"x": 260,
"y": 235,
"width": 120,
"height": 30,
"text": "05_publish",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step6",
"type": "rectangle",
"x": 450,
"y": 220,
"width": 140,
"height": 60,
"backgroundColor": "#fff3bf",
"strokeColor": "#f08c00",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step6_text",
"type": "text",
"x": 460,
"y": 235,
"width": 120,
"height": 30,
"text": "06_install",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "step7",
"type": "rectangle",
"x": 650,
"y": 220,
"width": 140,
"height": 60,
"backgroundColor": "#ffd43b",
"strokeColor": "#e67700",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "step7_text",
"type": "text",
"x": 660,
"y": 235,
"width": 120,
"height": 30,
"text": "07_deploy",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "oidc",
"type": "rectangle",
"x": 850,
"y": 220,
"width": 180,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "oidc_text",
"type": "text",
"x": 860,
"y": 235,
"width": 160,
"height": 50,
"text": "AWS OIDC\\nAuth automática",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "aws_prod",
"type": "rectangle",
"x": 650,
"y": 340,
"width": 200,
"height": 100,
"backgroundColor": "#ffe0e0",
"strokeColor": "#c92a2a",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "aws_prod_text",
"type": "text",
"x": 660,
"y": 350,
"width": 180,
"height": 80,
"text": "AWS PROD\\nmx-central-1\\nAccount: 523761210517",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "master_branch",
"type": "rectangle",
"x": 50,
"y": 340,
"width": 140,
"height": 60,
"backgroundColor": "#ffc9c9",
"strokeColor": "#c92a2a",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "master_branch_text",
"type": "text",
"x": 60,
"y": 350,
"width": 120,
"height": 40,
"text": "Branch\\nmaster (PROD)",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "merge_arrow",
"type": "arrow",
"x": 120,
"y": 180,
"points": [[0,0], [0,160]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "merge_label",
"type": "text",
"x": 130,
"y": 240,
"width": 100,
"height": 20,
"text": "Merge PR",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "left",
"strokeColor": "#495057",
"backgroundColor": "transparent"
},
{
"id": "arrow_dev_step1",
"type": "arrow",
"x": 190,
"y": 150,
"points": [[0,0], [60,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s1_s2",
"type": "arrow",
"x": 390,
"y": 150,
"points": [[0,0], [60,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s2_s3",
"type": "arrow",
"x": 590,
"y": 150,
"points": [[0,0], [60,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s3_s4",
"type": "arrow",
"x": 790,
"y": 150,
"points": [[0,0], [60,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s4_s5",
"type": "arrow",
"x": 920,
"y": 180,
"points": [[0,0], [0,40], [-570,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s5_s6",
"type": "arrow",
"x": 390,
"y": 250,
"points": [[0,0], [60,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s6_s7",
"type": "arrow",
"x": 590,
"y": 250,
"points": [[0,0], [60,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_s7_aws",
"type": "arrow",
"x": 750,
"y": 280,
"points": [[0,0], [0,60]],
"strokeColor": "#c92a2a",
"strokeWidth": 3
},
{
"id": "arrow_oidc_aws",
"type": "arrow",
"x": 940,
"y": 300,
"points": [[0,0], [0,40], [-90,0]],
"strokeColor": "#1971c2",
"strokeWidth": 2,
"strokeStyle": "dashed"
},
{
"id": "legend",
"type": "text",
"x": 50,
"y": 450,
"width": 400,
"height": 120,
"text": "Leyenda:\\n🟦 Azul: Código/branch\\n🟩 Verde: Build steps\\n🟨 Amarillo: Deploy steps\\n🟥 Rojo: Producción/AWS",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "left",
"strokeColor": "#495057",
"backgroundColor": "transparent"
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
+394
View File
@@ -0,0 +1,394 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "title",
"type": "text",
"x": 300,
"y": 20,
"width": 600,
"height": 40,
"text": "Seguridad y Acceso - Proyectosacc PROD",
"fontSize": 28,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "internet",
"type": "ellipse",
"x": 50,
"y": 120,
"width": 120,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "internet_text",
"type": "text",
"x": 60,
"y": 140,
"width": 100,
"height": 40,
"text": "Internet",
"fontSize": 16,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "cloudfront_box",
"type": "rectangle",
"x": 250,
"y": 120,
"width": 160,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "cloudfront_text",
"type": "text",
"x": 260,
"y": 135,
"width": 140,
"height": 50,
"text": "CloudFront\\nCDN + WAF",
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "ssh_restricted",
"type": "rectangle",
"x": 250,
"y": 250,
"width": 160,
"height": 80,
"backgroundColor": "#ffc9c9",
"strokeColor": "#c92a2a",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "ssh_restricted_text",
"type": "text",
"x": 260,
"y": 260,
"width": 140,
"height": 60,
"text": "SSH Restringido\\nSolo México IPs\\nPassphrase req.",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#c92a2a",
"backgroundColor": "transparent"
},
{
"id": "session_manager",
"type": "rectangle",
"x": 450,
"y": 250,
"width": 160,
"height": 80,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "session_manager_text",
"type": "text",
"x": 460,
"y": 260,
"width": 140,
"height": 60,
"text": "Session Manager\\nAWS Systems\\nManager",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#2b8a3e",
"backgroundColor": "transparent"
},
{
"id": "ec2_server",
"type": "rectangle",
"x": 680,
"y": 120,
"width": 200,
"height": 120,
"backgroundColor": "#fff3bf",
"strokeColor": "#f08c00",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "ec2_server_text",
"type": "text",
"x": 690,
"y": 130,
"width": 180,
"height": 100,
"text": "EC2 PROD\\ni-02428e733083ea877\\n78.13.201.205\\nUsuario: thoth\\nSudo: NOPASSWD",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "rds_db",
"type": "rectangle",
"x": 680,
"y": 300,
"width": 200,
"height": 80,
"backgroundColor": "#e7f5ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "rds_db_text",
"type": "text",
"x": 690,
"y": 310,
"width": 180,
"height": 60,
"text": "RDS MariaDB\\nproyectosacc-db-prod\\nEncrypted + Backup",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "security_group",
"type": "rectangle",
"x": 930,
"y": 120,
"width": 180,
"height": 100,
"backgroundColor": "#f3d9fa",
"strokeColor": "#862e9c",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "security_group_text",
"type": "text",
"x": 940,
"y": 130,
"width": 160,
"height": 80,
"text": "Security Groups\\n✓ Puerto 80/443\\n✓ Puerto 8080-8085\\n✗ SSH 0.0.0.0/0",
"fontSize": 11,
"fontFamily": 5,
"textAlign": "left",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "iam_role",
"type": "rectangle",
"x": 930,
"y": 250,
"width": 180,
"height": 80,
"backgroundColor": "#ffd43b",
"strokeColor": "#e67700",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "iam_role_text",
"type": "text",
"x": 940,
"y": 260,
"width": 160,
"height": 60,
"text": "IAM Roles\\nEC2 Role\\nSSM Permissions\\nS3 Access",
"fontSize": 11,
"fontFamily": 5,
"textAlign": "left",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "bitbucket_pipeline",
"type": "rectangle",
"x": 50,
"y": 400,
"width": 180,
"height": 80,
"backgroundColor": "#a5d8ff",
"strokeColor": "#1971c2",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "bitbucket_pipeline_text",
"type": "text",
"x": 60,
"y": 410,
"width": 160,
"height": 60,
"text": "Bitbucket Pipeline\\nOIDC Auth\\nAuto-deploy",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent"
},
{
"id": "oidc_auth",
"type": "rectangle",
"x": 300,
"y": 400,
"width": 160,
"height": 80,
"backgroundColor": "#d3f9d8",
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"fillStyle": "solid"
},
{
"id": "oidc_auth_text",
"type": "text",
"x": 310,
"y": 410,
"width": 140,
"height": 60,
"text": "AWS SSO/OIDC\\nNo credentials\\nAutomatic",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#2b8a3e",
"backgroundColor": "transparent"
},
{
"id": "arrow_internet_cf",
"type": "arrow",
"x": 170,
"y": 160,
"points": [[0,0], [80,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_cf_ec2",
"type": "arrow",
"x": 410,
"y": 160,
"points": [[0,0], [70,0]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_ssh_ec2",
"type": "arrow",
"x": 410,
"y": 290,
"points": [[0,0], [270,0]],
"strokeColor": "#c92a2a",
"strokeWidth": 2,
"strokeStyle": "dashed"
},
{
"id": "arrow_ssm_ec2",
"type": "arrow",
"x": 610,
"y": 290,
"points": [[0,0], [70,0]],
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"strokeStyle": "dashed"
},
{
"id": "arrow_ec2_rds",
"type": "arrow",
"x": 780,
"y": 240,
"points": [[0,0], [0,60]],
"strokeColor": "#495057",
"strokeWidth": 2
},
{
"id": "arrow_bb_oidc",
"type": "arrow",
"x": 230,
"y": 440,
"points": [[0,0], [70,0]],
"strokeColor": "#2b8a3e",
"strokeWidth": 2
},
{
"id": "arrow_oidc_aws",
"type": "arrow",
"x": 460,
"y": 440,
"points": [[0,0], [220,0]],
"strokeColor": "#2b8a3e",
"strokeWidth": 2,
"strokeStyle": "dashed"
},
{
"id": "prevent_destroy_label",
"type": "text",
"x": 680,
"y": 420,
"width": 200,
"height": 60,
"text": "🛡️ Lifecycle:\\nprevent_destroy = true",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#2b8a3e",
"backgroundColor": "transparent"
},
{
"id": "ssh_label",
"type": "text",
"x": 330,
"y": 340,
"width": 200,
"height": 30,
"text": "❌ Cerrado por defecto",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#c92a2a",
"backgroundColor": "transparent"
},
{
"id": "ssm_label",
"type": "text",
"x": 520,
"y": 340,
"width": 200,
"height": 30,
"text": "✅ Acceso recomendado",
"fontSize": 12,
"fontFamily": 5,
"textAlign": "center",
"strokeColor": "#2b8a3e",
"backgroundColor": "transparent"
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
+112
View File
@@ -0,0 +1,112 @@
# Validación de Recursos AWS - Cuenta 668889063715
**Fecha:** Abril 2026
**Cuenta:** 668889063715
**Región Principal:** mx-central-1
**Entorno:** SOLO DESARROLLO (DEV)
---
## Resumen Ejecutivo
| Validación | Estado |
|-----------|--------|
| Solo recursos DEV | ✅ CONFIRMADO |
| Sin recursos PROD | ✅ CONFIRMADO |
| Sin duplicación entre regiones | ✅ CONFIRMADO |
| Costos acordes a DEV | ✅ CONFIRMADO |
---
## Inventario Completo
### 1. EC2 (Instancias)
| ID | Nombre | Tipo | Estado | IP Pública | IP Privada |
|----|--------|------|--------|------------|------------|
| i-044018f36de1020a8 | proyectosacc-api-dev | t3.small | running | 78.12.135.184 | 10.1.0.20 |
**Total:** 1 instancia (solo DEV)
### 2. RDS (Bases de Datos)
| ID | Motor | Estado | Base de Datos | Clase | Público |
|----|-------|--------|---------------|-------|---------|
| proyectosacc-db-dev | mariadb | available | ccsoft_sacc4 | db.t3.micro | No |
**Total:** 1 base de datos (solo DEV)
### 3. S3 (Buckets)
| Bucket | Uso |
|--------|-----|
| ccsoft-proyectosacc-artifacts-dev | Artefactos de compilación |
| ccsoft-proyectosacc-frontend-dev | Frontend compilado |
| ccsoft-terraform-state | Estado de Terraform |
**Total:** 3 buckets (todos DEV)
### 4. CloudFront (CDN)
| ID | Dominio | Estado |
|----|---------|--------|
| E2EJ7237VFEJAR | d3donfm7ov3eyb.cloudfront.net | Deployed |
**Total:** 1 distribución (DEV)
### 5. VPC (Redes)
| ID | Nombre | CIDR | Default |
|----|--------|------|---------|
| vpc-0fefbaa0848a316dd | proyectosacc-vpc-dev | 10.1.0.0/16 | No |
| vpc-04f5f7cce6ead6c45 | - | 172.31.0.0/16 | Sí (default) |
### 6. Route53 (DNS)
| Zona | Dominio | Privada |
|------|---------|---------|
| Z00862912V7J2IBIAQU9Z | dev-sacc.ccsoft.mx | No |
### 7. Security Groups
| ID | Nombre | Descripción |
|----|--------|-------------|
| sg-0052f147c98bb1904 | proyectosacc-ec2-api | API backend |
| sg-0b4190c4cec1ad2f5 | proyectosacc-rds | RDS MariaDB (acceso solo desde 10.1.0.20/32) |
---
## Validaciones de Seguridad
| Verificación | Estado |
|-------------|--------|
| Búsqueda de recursos con tag "prod" | ❌ No encontrados |
| Búsqueda de recursos con tag "production" | ❌ No encontrados |
| Buckets con nombre "prod" o "production" | ❌ No encontrados |
| Instancias EC2 en otras regiones | ❌ No encontradas |
| Bases de datos RDS en otras regiones | ❌ No encontradas |
---
## Costos Estimados
**Promedio diario:** ~$2.43 USD
**Estimado mensual:** ~$73 USD
**Desglose aproximado:**
- EC2 t3.small: ~$15/mes
- RDS db.t3.micro: ~$13/mes
- S3 + CloudFront + Data Transfer: ~$45/mes
---
## Conclusión
**La cuenta 668889063715 contiene ÚNICAMENTE recursos de desarrollo (DEV).**
- No hay servicios de producción duplicados
- No hay recursos en otras regiones
- Todos los recursos están correctamente etiquetados como DEV
- Los costos son consistentes con un entorno de desarrollo
**Recomendación:** Mantener esta cuenta exclusivamente para desarrollo y no desplegar recursos de producción aquí.
+7
View File
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAdkhvJ7Il4Yrw1Zqu9P1PcFbdc2HciYqb7tCaj60a+vwAAAJhowcCaaMHA
mgAAAAtzc2gtZWQyNTUxOQAAACAdkhvJ7Il4Yrw1Zqu9P1PcFbdc2HciYqb7tCaj60a+vw
AAAEBaErHCg+OQP9abpbe9+Eee80Cncbntxz5TXkuqBkA0xx2SG8nsiXhivDVmq70/U9wV
t1zYdyJipvu0JqPrRr6/AAAAEG9zaXJpc0BjY3NvZnQuYWkBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----
+7
View File
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACC8ad1D22SDWfkWGeO9qwGKrBFJmLe68Kk90aF/skUgYAAAAJimLIkZpiyJ
GQAAAAtzc2gtZWQyNTUxOQAAACC8ad1D22SDWfkWGeO9qwGKrBFJmLe68Kk90aF/skUgYA
AAAEC+p75hAW4PuYLQcy6dlR0taqeoNNFknaUbOHx7kdEKYrxp3UPbZINZ+RYZ472rAYqs
EUmYt7rwqT3RoX+yRSBgAAAAD3Rob3RoQGNjc29mdC5haQECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
+91 -12
View File
@@ -1,12 +1,14 @@
#!/usr/bin/env bash
# ===============================================================================================================
# health-check.sh - Verifica que la API backend esté saludable
# health-check.sh - Verifica que la API backend, CloudFront y RDS estén saludables
# Descripción:
# Realiza peticiones al endpoint de salud de la API con reintentos.
# Puede ejecutarse desde la EC2 localmente o desde el pipeline.
# Realiza peticiones a múltiples endpoints con reintentos configurables.
# Puede ejecutarse desde la EC2 localmente o desde el pipeline CI/CD.
# Verifica: API backend (puertos 8080-8084), CloudFront, RDS.
#
# Uso:
# HEALTH_URL=http://localhost:8080/actuator/health bash health-check.sh
# ENV=prod CF_DOMAIN=sacc.ccsoft.mx bash health-check.sh
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
@@ -17,8 +19,12 @@ set -euo pipefail
# Configuración
# -------------------------------------------------------------------------------
HEALTH_URL="${HEALTH_URL:-http://localhost:8080/actuator/health}"
MAX_RETRIES="${MAX_RETRIES:-30}"
RETRY_INTERVAL="${RETRY_INTERVAL:-2}"
CF_DOMAIN="${CF_DOMAIN:-}"
DB_HOST="${DB_HOST:-}"
DB_PORT="${DB_PORT:-3306}"
ENV="${ENV:-dev}"
MAX_RETRIES="${MAX_RETRIES:-12}"
RETRY_INTERVAL="${RETRY_INTERVAL:-10}"
readonly CLR_RED='\033[0;31m'
readonly CLR_GREEN='\033[0;32m'
@@ -32,30 +38,103 @@ _log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; }
_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; }
# -------------------------------------------------------------------------------
# Health Check
# Health Check con reintentos
# -------------------------------------------------------------------------------
main() {
_log_step "Health Check: ${HEALTH_URL}"
check_with_retry() {
local name="$1"
local url="$2"
local check_cmd="$3"
_log_step "Verificando: ${name} (${url})"
local attempt=1
while [[ $attempt -le $MAX_RETRIES ]]; do
_log_info "Intento ${attempt}/${MAX_RETRIES}..."
if curl -sf "${HEALTH_URL}" | grep -q '"status":"UP"'; then
_log_info "Health check PASÓ - API saludable"
if eval "${check_cmd}"; then
_log_info "${name} - SALUDABLE"
return 0
fi
if [[ $attempt -lt $MAX_RETRIES ]]; then
_log_warn "API no responde aún, reintentando en ${RETRY_INTERVAL}s..."
_log_warn "${name} no responde, reintentando en ${RETRY_INTERVAL}s..."
sleep "${RETRY_INTERVAL}"
fi
((attempt++))
done
_log_error "Health check FALLÓ después de ${MAX_RETRIES} intentos"
_log_error "${name} - FALLÓ después de ${MAX_RETRIES} intentos"
return 1
}
# -------------------------------------------------------------------------------
# Health Check Principal
# -------------------------------------------------------------------------------
main() {
local exit_code=0
_log_step "Health Check Post-Deploy [${ENV^^}]"
echo "Configuración: MAX_RETRIES=${MAX_RETRIES}, INTERVAL=${RETRY_INTERVAL}s"
echo ""
# 1. Verificar API Backend (puerto principal)
if ! check_with_retry "API Backend" "${HEALTH_URL}" \
"curl -sf '${HEALTH_URL}' | grep -q '\"status\":\"UP\"'"; then
exit_code=1
fi
# 2. Verificar puertos adicionales de servicios (8080-8084)
local base_url
base_url=$(echo "${HEALTH_URL}" | sed 's|/actuator/health||')
for port in 8081 8082 8083 8084; do
local port_url="${base_url//:[0-9]*/:${port}}/actuator/health"
# Solo verificar si la variable del puerto está definida o si es el mismo host
if [[ "${base_url}" =~ :[0-9]+ ]]; then
port_url="${base_url%:*}:${port}/actuator/health"
fi
# Verificación opcional - no falla el pipeline si un puerto opcional no responde
_log_info "Verificando puerto opcional ${port}..."
if curl -sf "${port_url}" | grep -q '"status":"UP"' 2>/dev/null; then
_log_info "✓ Puerto ${port} - RESPONDE"
else
_log_warn "Puerto ${port} no responde (opcional)"
fi
done
# 3. Verificar CloudFront (si está configurado)
if [[ -n "${CF_DOMAIN}" ]]; then
if ! check_with_retry "CloudFront" "https://${CF_DOMAIN}/api/health" \
"curl -sf -o /dev/null 'https://${CF_DOMAIN}/api/health'"; then
exit_code=1
fi
else
_log_warn "CF_DOMAIN no configurado. Saltando verificación de CloudFront."
fi
# 4. Verificar RDS (si está configurado)
if [[ -n "${DB_HOST}" ]]; then
if command -v nc &> /dev/null; then
if ! check_with_retry "RDS" "${DB_HOST}:${DB_PORT}" \
"nc -zv '${DB_HOST}' ${DB_PORT}"; then
exit_code=1
fi
else
_log_warn "nc (netcat) no instalado. Saltando verificación de RDS."
fi
else
_log_warn "DB_HOST no configurado. Saltando verificación de RDS."
fi
echo ""
if [[ $exit_code -eq 0 ]]; then
_log_step "✓ TODOS LOS HEALTH CHECKS PASARON"
else
_log_step "✗ ALGUNOS HEALTH CHECKS FALLARON"
fi
return $exit_code
}
main "$@"
+164
View File
@@ -0,0 +1,164 @@
#!/bin/bash
# ===============================================================================================================
# terraform-lock-cleanup.sh - Limpieza automática de locks bloqueados de Terraform
# Descripción:
# Verifica y elimina locks bloqueados de Terraform en DynamoDB antes de ejecutar terraform plan/apply.
# Previene errores de "Error acquiring the state lock" en pipelines de CI/CD.
#
# Uso:
# bash scripts/terraform-lock-cleanup.sh <environment> <state-key>
# Ejemplo: bash scripts/terraform-lock-cleanup.sh dev proyectosacc/terraform.tfstate
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Variables
ENVIRONMENT="${1:-dev}"
STATE_KEY="${2:-proyectosacc/terraform.tfstate}"
LOCK_TABLE="${TERRAFORM_LOCK_TABLE:-terraform-locks}"
STATE_BUCKET="${TERRAFORM_STATE_BUCKET:-ccsoft-terraform-state}"
MAX_LOCK_AGE_MINUTES="${MAX_LOCK_AGE_MINUTES:-30}"
AWS_REGION="${AWS_DEFAULT_REGION:-mx-central-1}"
# Construir el LockID completo
LOCK_ID="${STATE_BUCKET}/${STATE_KEY}-md5"
echo "=== Terraform Lock Cleanup ==="
echo "Environment: ${ENVIRONMENT}"
echo "State Key: ${STATE_KEY}"
echo "Lock Table: ${LOCK_TABLE}"
echo "Lock ID: ${LOCK_ID}"
echo "Max Lock Age: ${MAX_LOCK_AGE_MINUTES} minutes"
echo "AWS Region: ${AWS_REGION}"
echo ""
# Verificar que AWS CLI está disponible
if ! command -v aws &> /dev/null; then
echo -e "${RED}ERROR: AWS CLI no está instalado${NC}"
exit 1
fi
# Verificar credenciales AWS
echo "Verificando credenciales AWS..."
if ! aws sts get-caller-identity &> /dev/null; then
echo -e "${RED}ERROR: No se pueden validar credenciales AWS${NC}"
exit 1
fi
echo -e "${GREEN}✓ Credenciales AWS válidas${NC}"
echo ""
# Verificar si la tabla existe
echo "Verificando tabla DynamoDB ${LOCK_TABLE}..."
if ! aws dynamodb describe-table --table-name "${LOCK_TABLE}" --region "${AWS_REGION}" &> /dev/null; then
echo -e "${YELLOW}⚠ Tabla ${LOCK_TABLE} no encontrada. No hay locks que limpiar.${NC}"
exit 0
fi
echo -e "${GREEN}✓ Tabla DynamoDB encontrada${NC}"
echo ""
# Buscar el lock
echo "Buscando lock en DynamoDB..."
LOCK_ITEM=$(aws dynamodb get-item \
--table-name "${LOCK_TABLE}" \
--key "{\"LockID\": {\"S\": \"${LOCK_ID}\"}}" \
--region "${AWS_REGION}" \
--output json 2>/dev/null || echo '{}')
if [ -z "${LOCK_ITEM}" ] || [ "${LOCK_ITEM}" == '{}' ]; then
echo -e "${GREEN}✓ No se encontró lock activo. Estado limpio.${NC}"
exit 0
fi
echo -e "${YELLOW}⚠ Lock encontrado en DynamoDB${NC}"
echo "Detalles del lock:"
echo "${LOCK_ITEM}" | python3 -m json.tool 2>/dev/null || echo "${LOCK_ITEM}"
echo ""
# Verificar si el lock tiene información de creación (Info field)
LOCK_INFO=$(echo "${LOCK_ITEM}" | python3 -c "
import json, sys
try:
item = json.load(sys.stdin)
info = item.get('Item', {}).get('Info', {}).get('S', '{}')
print(info)
except:
print('{}')
" 2>/dev/null || echo '{}')
if [ -n "${LOCK_INFO}" ] && [ "${LOCK_INFO}" != '{}' ]; then
echo "Información del lock:"
echo "${LOCK_INFO}" | python3 -m json.tool 2>/dev/null || echo "${LOCK_INFO}"
# Extraer timestamp de creación
CREATED=$(echo "${LOCK_INFO}" | python3 -c "
import json, sys
try:
info = json.load(sys.stdin)
created = info.get('Created', '')
print(created)
except:
print('')
" 2>/dev/null || echo '')
if [ -n "${CREATED}" ]; then
echo ""
echo "Lock creado: ${CREATED}"
# Calcular antigüedad del lock
CREATED_EPOCH=$(date -d "${CREATED}" +%s 2>/dev/null || echo '0')
CURRENT_EPOCH=$(date +%s)
if [ "${CREATED_EPOCH}" != '0' ]; then
AGE_MINUTES=$(( (CURRENT_EPOCH - CREATED_EPOCH) / 60 ))
echo "Antigüedad del lock: ${AGE_MINUTES} minutos"
if [ ${AGE_MINUTES} -gt ${MAX_LOCK_AGE_MINUTES} ]; then
echo -e "${YELLOW}⚠ Lock tiene más de ${MAX_LOCK_AGE_MINUTES} minutos. Considerado bloqueado.${NC}"
else
echo -e "${YELLOW}⚠ Lock tiene menos de ${MAX_LOCK_AGE_MINUTES} minutos. Podría ser una operación en curso.${NC}"
echo -e "${YELLOW}⚠ Esperando 30 segundos antes de verificar nuevamente...${NC}"
sleep 30
# Verificar nuevamente
LOCK_ITEM_RETRY=$(aws dynamodb get-item \
--table-name "${LOCK_TABLE}" \
--key "{\"LockID\": {\"S\": \"${LOCK_ID}\"}}" \
--region "${AWS_REGION}" \
--output json 2>/dev/null || echo '{}')
if [ -z "${LOCK_ITEM_RETRY}" ] || [ "${LOCK_ITEM_RETRY}" == '{}' ]; then
echo -e "${GREEN}✓ Lock liberado durante la espera. Estado limpio.${NC}"
exit 0
fi
echo -e "${YELLOW}⚠ Lock todavía presente después de esperar. Procediendo con limpieza...${NC}"
fi
fi
fi
fi
echo ""
echo -e "${YELLOW}⚠ Eliminando lock bloqueado de DynamoDB...${NC}"
# Eliminar el lock
if aws dynamodb delete-item \
--table-name "${LOCK_TABLE}" \
--key "{\"LockID\": {\"S\": \"${LOCK_ID}\"}}" \
--region "${AWS_REGION}" \
--condition-expression "attribute_exists(LockID)" \
2>/dev/null; then
echo -e "${GREEN}✓ Lock eliminado exitosamente${NC}"
else
echo -e "${YELLOW}⚠ No se pudo eliminar el lock (puede que ya no exista)${NC}"
fi
echo ""
echo "=== Limpieza de locks completada ==="
+363
View File
@@ -0,0 +1,363 @@
#!/bin/bash
###############################################################################
# Script de Validación de Conexión EC2 → RDS (MariaDB)
# Proyecto: proyectosacc (SACC4) - Entorno DEV
# Servidor: proyectosacc-api-dev (78.12.135.184)
# Base de datos: proyectosacc-db-dev
###############################################################################
# ─────────────────────────────────────────────────────────────────────────────
# CONFIGURACIÓN - Modifica estos valores según tu entorno
# ─────────────────────────────────────────────────────────────────────────────
RDS_HOST="proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com"
RDS_PORT="3306"
RDS_USER="${DEV_DB_USERNAME:-admin}" # Usa variable de entorno o default
RDS_PASSWORD="${DEV_DB_PASSWORD:-}" # Se espera como variable de entorno
RDS_DBNAME="${DEV_DB_NAME:-proyectosacc_dev}" # Nombre de la base de datos
EC2_IP="78.12.135.184"
TIMEOUT_SECONDS="10"
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
BOLD='\033[1m'
# ─────────────────────────────────────────────────────────────────────────────
# FUNCIONES AUXILIARES
# ─────────────────────────────────────────────────────────────────────────────
print_header() {
echo -e "\n${BOLD}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${BOLD} $1${NC}"
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE}$1${NC}"
}
# ─────────────────────────────────────────────────────────────────────────────
# VALIDACIÓN DE PREREQUISITOS
# ─────────────────────────────────────────────────────────────────────────────
print_header "1. VALIDACIÓN DE PREREQUISITOS"
# Verificar que estamos en el servidor correcto
CURRENT_IP=$(hostname -I | awk '{print $1}')
print_info "IP actual del servidor: $CURRENT_IP"
print_info "IP esperada (EC2 DEV): $EC2_IP"
if [ "$CURRENT_IP" != "$EC2_IP" ]; then
print_warning "La IP actual ($CURRENT_IP) no coincide con la IP esperada ($EC2_IP)"
print_warning "Asegúrate de estar ejecutando este script en el servidor DEV"
else
print_success "IP verificada correctamente"
fi
# Verificar herramientas necesarias
echo -e "\n${BOLD}Verificando herramientas instaladas:${NC}"
check_tool() {
if command -v "$1" &> /dev/null; then
VERSION=$($2 2>/dev/null | head -1)
print_success "$1 instalado - $VERSION"
return 0
else
print_error "$1 NO está instalado"
return 1
fi
}
NEED_MYSQL=0
check_tool "nc" "nc --version" || NEED_MYSQL=1
check_tool "telnet" "telnet --version" 2>/dev/null || true
check_tool "mysql" "mysql --version" || NEED_MYSQL=1
if [ $NEED_MYSQL -eq 1 ]; then
print_warning "Se recomienda instalar: mariadb-client o mysql-client"
print_info "Ejecuta: sudo apt-get update && sudo apt-get install -y mariadb-client"
fi
# ─────────────────────────────────────────────────────────────────────────────
# PRUEBA 1: RESOLUCIÓN DNS
# ─────────────────────────────────────────────────────────────────────────────
print_header "2. RESOLUCIÓN DNS"
print_info "Resolviendo: $RDS_HOST"
if command -v dig &> /dev/null; then
RDS_IP=$(dig +short "$RDS_HOST" | head -1)
if [ -n "$RDS_IP" ]; then
print_success "DNS resuelto: $RDS_HOST$RDS_IP"
dig +short "$RDS_HOST" | while read ip; do
print_info " IP encontrada: $ip"
done
else
print_error "No se pudo resolver el DNS de $RDS_HOST"
print_info "Verifica la configuración de DNS o la conectividad a internet"
fi
elif command -v nslookup &> /dev/null; then
nslookup "$RDS_HOST" 2>&1 | grep -E "Address:|Name:" | while read line; do
print_info " $line"
done
else
print_warning "No se encontró 'dig' ni 'nslookup'. Instala dnsutils:"
print_info " sudo apt-get install -y dnsutils"
fi
# ─────────────────────────────────────────────────────────────────────────────
# PRUEBA 2: CONECTIVIDAD DE RED (Ping)
# ─────────────────────────────────────────────────────────────────────────────
print_header "3. CONECTIVIDAD DE RED (Ping)"
print_info "Haciendo ping a $RDS_HOST..."
if ping -c 3 -W 5 "$RDS_HOST" &> /dev/null; then
print_success "Ping exitoso - El servidor RDS responde"
ping -c 1 -W 5 "$RDS_HOST" | grep -E "time=|ttl=" | while read line; do
print_info " $line"
done
else
print_error "Ping fallido - No hay respuesta del servidor RDS"
print_info "Esto puede ser normal si RDS tiene ICMP bloqueado"
fi
# ─────────────────────────────────────────────────────────────────────────────
# PRUEBA 3: CONEXIÓN DE PUERTO TCP
# ─────────────────────────────────────────────────────────────────────────────
print_header "4. CONEXIÓN DE PUERTO TCP (Puerto $RDS_PORT)"
print_info "Verificando si el puerto $RDS_PORT está abierto en $RDS_HOST..."
if command -v nc &> /dev/null; then
if nc -zv -w "$TIMEOUT_SECONDS" "$RDS_HOST" "$RDS_PORT" 2>&1 | grep -q "succeeded\|open"; then
print_success "Puerto $RDS_PORT ABIERTO - Conexión TCP exitosa"
else
print_error "Puerto $RDS_PORT CERRADO o bloqueado"
print_info "Verifica:"
print_info " 1. Security Group de RDS (debe permitir entrada desde $EC2_IP)"
print_info " 2. Network ACLs"
print_info " 3. Firewall del servidor EC2 (iptables/ufw)"
fi
nc -zv -w "$TIMEOUT_SECONDS" "$RDS_HOST" "$RDS_PORT" 2>&1 | while read line; do
print_info " $line"
done
elif command -v telnet &> /dev/null; then
print_info "Usando telnet (Ctrl+C para cancelar si se cuelga)..."
timeout 5 bash -c "echo >/dev/tcp/$RDS_HOST/$RDS_PORT" 2>/dev/null
if [ $? -eq 0 ]; then
print_success "Puerto $RDS_PORT ABIERTO"
else
print_error "Puerto $RDS_PORT CERRADO"
fi
else
print_warning "No se encontró 'nc' ni 'telnet'. Usando /dev/tcp..."
timeout 5 bash -c "echo >/dev/tcp/$RDS_HOST/$RDS_PORT" 2>/dev/null
if [ $? -eq 0 ]; then
print_success "Puerto $RDS_PORT ABIERTO"
else
print_error "Puerto $RDS_PORT CERRADO"
fi
fi
# ─────────────────────────────────────────────────────────────────────────────
# PRUEBA 4: CONEXIÓN A BASE DE DATOS (MySQL/MariaDB)
# ─────────────────────────────────────────────────────────────────────────────
print_header "5. CONEXIÓN A BASE DE DATOS (MariaDB)"
if ! command -v mysql &> /dev/null; then
print_error "Cliente MySQL/MariaDB no instalado"
print_info "Instala con: sudo apt-get install -y mariadb-client"
print_info "Saltando prueba de conexión a BD..."
else
if [ -z "$RDS_PASSWORD" ]; then
print_warning "No se encontró DEV_DB_PASSWORD en las variables de entorno"
print_info "Exporta la variable antes de ejecutar:"
print_info " export DEV_DB_PASSWORD='tu_password_aquí'"
print_info "O ejecuta el script con: DEV_DB_PASSWORD='xxx' ./validar_conexion_rds.sh"
else
print_info "Intentando conectar a MariaDB..."
# Prueba de conexión simple
if mysql -h "$RDS_HOST" -P "$RDS_PORT" -u "$RDS_USER" -p"$RDS_PASSWORD" \
-e "SELECT 'Conexión exitosa' as status, NOW() as server_time;" 2>/dev/null; then
print_success "¡CONEXIÓN EXITOSA A LA BASE DE DATOS!"
# Información del servidor
echo -e "\n${BOLD}Información del servidor MariaDB:${NC}"
mysql -h "$RDS_HOST" -P "$RDS_PORT" -u "$RDS_USER" -p"$RDS_PASSWORD" \
-e "SELECT VERSION() as version;" 2>/dev/null | grep -v "version" | while read line; do
print_info " Versión: $line"
done
# Listar bases de datos
echo -e "\n${BOLD}Bases de datos disponibles:${NC}"
mysql -h "$RDS_HOST" -P "$RDS_PORT" -u "$RDS_USER" -p"$RDS_PASSWORD" \
-e "SHOW DATABASES;" 2>/dev/null | grep -v "Database" | grep -v "information_schema\|performance_schema\|mysql" | while read db; do
print_info " 📁 $db"
done
# Verificar base de datos específica
if [ -n "$RDS_DBNAME" ]; then
echo -e "\n${BOLD}Verificando base de datos '$RDS_DBNAME':${NC}"
mysql -h "$RDS_HOST" -P "$RDS_PORT" -u "$RDS_USER" -p"$RDS_PASSWORD" \
-e "SHOW TABLES FROM \`$RDS_DBNAME\`;" 2>/dev/null | head -20 | while read table; do
if [ "$table" != "Tables_in_$RDS_DBNAME" ]; then
print_info " 📋 $table"
fi
done
fi
else
print_error "No se pudo conectar a la base de datos"
print_info "Posibles causas:"
print_info " 1. Credenciales incorrectas (usuario/password)"
print_info " 2. Usuario no tiene permisos desde esta IP"
print_info " 3. Base de datos no existe"
# Intentar obtener más información del error
echo -e "\n${BOLD}Detalle del error:${NC}"
mysql -h "$RDS_HOST" -P "$RDS_PORT" -u "$RDS_USER" -p"$RDS_PASSWORD" \
-e "SELECT 1;" 2>&1 | grep -v "Warning:" | while read line; do
print_error " $line"
done
fi
fi
fi
# ─────────────────────────────────────────────────────────────────────────────
# PRUEBA 5: VERIFICACIÓN DE VARIABLES DE ENTORNO
# ─────────────────────────────────────────────────────────────────────────────
print_header "6. VARIABLES DE ENTORNO CONFIGURADAS"
echo -e "${BOLD}Variables encontradas:${NC}"
vars_found=0
for var in DEV_DB_HOST DEV_DB_PORT DEV_DB_NAME DEV_DB_USERNAME DEV_DB_PASSWORD; do
if [ -n "${!var}" ]; then
if [ "$var" = "DEV_DB_PASSWORD" ]; then
print_success "$var=********** (oculto)"
else
print_success "$var=${!var}"
fi
vars_found=$((vars_found + 1))
else
print_error "$var=NO DEFINIDO"
fi
done
echo -e "\n${BOLD}Resumen:${NC}"
if [ $vars_found -eq 5 ]; then
print_success "Todas las variables de entorno están configuradas"
elif [ $vars_found -gt 0 ]; then
print_warning "Solo $vars_found/5 variables configuradas"
else
print_error "Ninguna variable de entorno configurada"
print_info "Crea un archivo .env con las variables necesarias:"
cat << 'EOF'
# Ejemplo de archivo .env
echo "export DEV_DB_HOST=proyectosacc-db-dev.cbe2keowyu6w.mx-central-1.rds.amazonaws.com" >> .env
echo "export DEV_DB_PORT=3306" >> .env
echo "export DEV_DB_NAME=proyectosacc_dev" >> .env
echo "export DEV_DB_USERNAME=admin" >> .env
echo "export DEV_DB_PASSWORD=tu_password_aqui" >> .env
# Cargar variables:
source .env
EOF
fi
# ─────────────────────────────────────────────────────────────────────────────
# PRUEBA 6: CONFIGURACIÓN DE AWS CLI (Opcional)
# ─────────────────────────────────────────────────────────────────────────────
print_header "7. CONFIGURACIÓN DE AWS CLI (Opcional)"
if command -v aws &> /dev/null; then
print_success "AWS CLI instalado"
# Verificar configuración
if aws sts get-caller-identity &> /dev/null; then
print_success "AWS CLI configurado correctamente"
aws sts get-caller-identity 2>/dev/null | grep -E "Account|Arn" | while read line; do
print_info " $line"
done
else
print_warning "AWS CLI no está configurado o credenciales inválidas"
print_info "Configura con: aws configure"
fi
# Verificar información del RDS desde AWS
echo -e "\n${BOLD}Información del RDS desde AWS CLI:${NC}"
aws rds describe-db-instances \
--query 'DBInstances[?DBInstanceIdentifier==`proyectosacc-db-dev`].[DBInstanceIdentifier,DBInstanceStatus,Endpoint.Address,AvailabilityZone]' \
--output table 2>/dev/null || print_error "No se pudo obtener información del RDS"
else
print_warning "AWS CLI no instalado (opcional para diagnóstico)"
print_info "Instala con: sudo apt-get install -y awscli"
fi
# ─────────────────────────────────────────────────────────────────────────────
# RESUMEN FINAL
# ─────────────────────────────────────────────────────────────────────────────
print_header "RESUMEN FINAL DE VALIDACIÓN"
echo -e "${BOLD}Conexión:${NC} EC2 DEV ($EC2_IP) → RDS DEV ($RDS_HOST:$RDS_PORT)"
echo ""
# Hacer resumen basado en las pruebas
print_info "✓ Verifica la resolución DNS del endpoint RDS"
print_info "✓ Verifica conectividad de red (ping/TCP)"
print_info "✓ Verifica conexión a MariaDB/MySQL"
print_info "✓ Verifica variables de entorno"
echo -e "\n${BOLD}Si todo está ✅:${NC}"
print_success "Tu servidor EC2 puede conectarse correctamente al RDS"
echo -e "\n${BOLD}Si hay ❌:${NC}"
print_info "1. Revisa Security Groups: Grupo de seguridad de RDS debe permitir entrada TCP 3306 desde $EC2_IP"
print_info "2. Revisa Network ACLs de la VPC"
print_info "3. Revisa firewall local: sudo iptables -L -n | grep 3306"
print_info "4. Verifica que el endpoint RDS sea correcto"
print_info "5. Verifica credenciales de la base de datos"
echo -e "\n${BOLD}Comandos útiles para diagnóstico:${NC}"
echo " # Ver logs de conexión (en EC2):"
echo " sudo tail -f /var/log/syslog | grep -i mariadb"
echo ""
echo " # Ver reglas de firewall:"
echo " sudo iptables -L -n | grep 3306"
echo ""
echo " # Test manual de conexión MySQL:"
echo " mysql -h $RDS_HOST -P $RDS_PORT -u <usuario> -p"
echo ""
echo " # Ver Security Groups desde AWS:"
echo " aws ec2 describe-security-groups --query 'SecurityGroups[*].[GroupName,GroupId]'"
echo -e "\n${GREEN}${BOLD}Script completado.${NC}"
+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.*
+2 -2
View File
@@ -12,10 +12,10 @@
terraform {
backend "s3" {
bucket = "ccsoft-terraform-state"
bucket = "ccsoft-proyectosacc-terraform-state-prod"
key = "proyectosacc/terraform.tfstate"
region = "mx-central-1"
encrypt = true
dynamodb_table = "terraform-locks"
use_lockfile = true
}
}
+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
}
+4 -2
View File
@@ -13,8 +13,10 @@ aws_region = "mx-central-1"
vpc_cidr = "10.2.0.0/16"
availability_zones = ["mx-central-1a", "mx-central-1b"]
ec2_instance_type = "t3.small"
ec2_key_name = "ccsoft-prod-key"
pipeline_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQCNFOzDJzaOMDIeEbH4JCx2OrXrgljajgkJqlozj9m bitbucket.pipeline.ci.cd.proyectosacc.thoth@computocontable.com"
ec2_ami = "ami-0f553e2869648134e"
ec2_key_name = "sacc-prod-key-2026"
ec2_root_volume_size = 8
pipeline_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII/RcJmEYOBpfq1tSLltV1pyNB55l1jA2zYr5ZNJ0f41 thoth@ccsoft"
db_instance_class = "db.t3.micro"
db_name = "sacc_db_prod"
db_username = "sacc_admin_prod"
+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
# ===============================================================================================================
+181 -9
View File
@@ -18,6 +18,11 @@ resource "aws_vpc" "main" {
tags = {
Name = "${var.project_name}-vpc-${var.environment}"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_internet_gateway" "main" {
@@ -81,12 +86,21 @@ resource "aws_route_table" "public" {
tags = {
Name = "${var.project_name}-public-rt-${var.environment}"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
lifecycle {
prevent_destroy = true
}
}
resource "aws_route_table" "private" {
@@ -100,12 +114,21 @@ resource "aws_route_table" "private" {
tags = {
Name = "${var.project_name}-private-rt-${var.environment}"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
@@ -121,7 +144,7 @@ resource "aws_security_group" "ec2_api" {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # SSH desde cualquier IP (pipeline Bitbucket + administración)
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
@@ -148,6 +171,41 @@ resource "aws_security_group" "ec2_api" {
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8081
to_port = 8081
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8082
to_port = 8082
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8083
to_port = 8083
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8084
to_port = 8084
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8085
to_port = 8085
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
egress {
description = "Salida libre"
from_port = 0
@@ -161,6 +219,8 @@ resource "aws_security_group" "ec2_api" {
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
create_before_destroy = true
}
}
@@ -191,6 +251,8 @@ resource "aws_security_group" "rds" {
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
create_before_destroy = true
}
}
@@ -215,12 +277,21 @@ resource "aws_iam_role" "ec2_role" {
tags = {
Name = "${var.project_name}-ec2-role"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_iam_role_policy" "ec2_policy" {
name = "${var.project_name}-ec2-policy-${var.environment}"
role = aws_iam_role.ec2_role.id
lifecycle {
prevent_destroy = true
}
policy = jsonencode({
Version = "2012-10-17"
Statement = [
@@ -250,6 +321,23 @@ resource "aws_iam_role_policy" "ec2_policy" {
"cloudfront:CreateInvalidation"
]
Resource = "*"
},
{
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 = "*"
}
]
})
@@ -262,6 +350,11 @@ resource "aws_iam_instance_profile" "ec2_profile" {
tags = {
Name = "${var.project_name}-ec2-profile"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
# -------------------------------------------------------------------------------
@@ -272,23 +365,33 @@ resource "aws_instance" "api" {
instance_type = var.ec2_instance_type
subnet_id = aws_subnet.public[0].id
vpc_security_group_ids = [aws_security_group.ec2_api.id]
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
key_name = var.ec2_key_name
root_block_device {
volume_type = "gp3"
volume_size = var.ec2_root_volume_size
encrypted = true
volume_type = "gp2"
volume_size = 8
encrypted = false
delete_on_termination = true
}
user_data = base64encode(templatefile("${path.module}/user-data.sh", {
pipeline_public_key = var.pipeline_public_key
}))
tags = {
Name = "${var.project_name}-api-${var.environment}"
Environment = var.environment
AutoStart = "true"
AutoStop = "true"
Schedule = "office-hours"
ScheduleHours = "08:00-19:00_L-V"
ScheduleTimezone = "America/Mexico_City"
}
lifecycle {
prevent_destroy = true
ignore_changes = [
ami,
iam_instance_profile,
user_data,
tags,
]
}
}
@@ -302,6 +405,11 @@ resource "aws_db_subnet_group" "rds" {
tags = {
Name = "${var.project_name}-rds-subnet-group"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_db_instance" "main" {
@@ -324,6 +432,18 @@ resource "aws_db_instance" "main" {
tags = {
Name = "${var.project_name}-rds"
Environment = var.environment
AutoStart = "true"
AutoStop = "true"
Schedule = "office-hours"
ScheduleHours = "08:00-19:00_L-V"
ScheduleTimezone = "America/Mexico_City"
}
lifecycle {
prevent_destroy = true
ignore_changes = [
tags,
]
}
}
@@ -336,6 +456,11 @@ resource "aws_s3_bucket" "frontend" {
tags = {
Name = "${var.project_name}-frontend"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_s3_bucket_versioning" "frontend" {
@@ -344,6 +469,10 @@ resource "aws_s3_bucket_versioning" "frontend" {
versioning_configuration {
status = "Enabled"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_public_access_block" "frontend" {
@@ -353,6 +482,10 @@ resource "aws_s3_bucket_public_access_block" "frontend" {
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_website_configuration" "frontend" {
@@ -365,6 +498,10 @@ resource "aws_s3_bucket_website_configuration" "frontend" {
error_document {
key = "index.html"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket" "artifacts" {
@@ -373,6 +510,10 @@ resource "aws_s3_bucket" "artifacts" {
tags = {
Name = "${var.project_name}-artifacts"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "artifacts" {
@@ -381,6 +522,10 @@ resource "aws_s3_bucket_versioning" "artifacts" {
versioning_configuration {
status = "Enabled"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
@@ -390,6 +535,10 @@ resource "aws_s3_bucket_public_access_block" "artifacts" {
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
@@ -401,6 +550,10 @@ resource "aws_cloudfront_origin_access_control" "frontend" {
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_policy" "frontend" {
@@ -425,6 +578,10 @@ resource "aws_s3_bucket_policy" "frontend" {
})
depends_on = [aws_s3_bucket_public_access_block.frontend]
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
@@ -440,6 +597,8 @@ resource "aws_acm_certificate" "main" {
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
create_before_destroy = true
}
}
@@ -457,6 +616,10 @@ resource "aws_acm_certificate_validation" "main" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [aws_route53_record.cert_validation.fqdn]
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
@@ -476,6 +639,10 @@ resource "aws_route53_record" "main" {
name = var.domain_name
type = "A"
lifecycle {
prevent_destroy = true
}
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
@@ -569,4 +736,9 @@ resource "aws_cloudfront_distribution" "main" {
tags = {
Name = "${var.project_name}-cdn"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
+72
View File
@@ -22,6 +22,32 @@ PIPELINE_PUBLIC_KEY="${pipeline_public_key}"
apt-get update -y
apt-get install -y nginx openjdk-21-jdk awscli curl jq
# -------------------------------------------------------------------------------
# Instalar y verificar AWS Systems Manager Agent
# -------------------------------------------------------------------------------
# SSM Agent permite acceso seguro sin abrir puertos SSH (0.0.0.0/0)
# Pre-instalado en Amazon Linux; en Ubuntu puede requerir instalación manual
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
echo "Descargando SSM Agent desde AWS..."
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
rm -f /tmp/amazon-ssm-agent.deb
fi
# Asegurar que el servicio esté habilitado y corriendo
systemctl enable amazon-ssm-agent || true
systemctl restart amazon-ssm-agent || true
# Verificar estado del agente
if systemctl is-active --quiet amazon-ssm-agent; then
echo "SSM Agent instalado y activo correctamente"
else
echo "ADVERTENCIA: No se pudo iniciar SSM Agent. Verificar conectividad a AWS y permisos IAM."
fi
# -------------------------------------------------------------------------------
# Crear usuarios del sistema
# -------------------------------------------------------------------------------
@@ -38,6 +64,52 @@ echo "$PIPELINE_PUBLIC_KEY" > /home/thoth/.ssh/authorized_keys
chmod 600 /home/thoth/.ssh/authorized_keys
chown -R thoth:thoth /home/thoth/.ssh
# -------------------------------------------------------------------------------
# Configurar permisos sudo para usuario thoth (SACC4 Application Management)
# -------------------------------------------------------------------------------
cat > /etc/sudoers.d/thoth <<'SUDOERS_EOF'
# SACC4 Application Management Permissions
# User: thoth
# Generated: $(date)
# 1. Editar archivo de configuracion de la aplicacion
thoth ALL=(ALL) NOPASSWD: /usr/bin/nano /etc/sacc4/sacc4.env
thoth ALL=(ALL) NOPASSWD: /usr/bin/vim /etc/sacc4/sacc4.env
thoth ALL=(ALL) NOPASSWD: /usr/bin/vi /etc/sacc4/sacc4.env
# 2. Gestionar servicios systemd
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl status api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl start api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/journalctl -u api-sacc4-*.service
# 3. Editar archivos de servicios systemd
thoth ALL=(ALL) NOPASSWD: /usr/bin/nano /etc/systemd/system/api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/vim /etc/systemd/system/api-sacc4-*.service
thoth ALL=(ALL) NOPASSWD: /usr/bin/vi /etc/systemd/system/api-sacc4-*.service
# 4. Recargar systemd daemon
thoth ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload
# 5. Control total del directorio /opt/sacc4
thoth ALL=(ALL) NOPASSWD: /bin/ls -la /opt/sacc4/*
thoth ALL=(ALL) NOPASSWD: /bin/chown -R thoth\:thoth /opt/sacc4/*
thoth ALL=(ALL) NOPASSWD: /bin/chmod -R [0-7][0-7][0-7] /opt/sacc4/*
thoth ALL=(ALL) NOPASSWD: /bin/mkdir -p /opt/sacc4/*
thoth ALL=(ALL) NOPASSWD: /bin/rm -rf /opt/sacc4/*
thoth ALL=(ALL) NOPASSWD: /bin/cp -r * /opt/sacc4/*
thoth ALL=(ALL) NOPASSWD: /bin/mv * /opt/sacc4/*
SUDOERS_EOF
chmod 440 /etc/sudoers.d/thoth
chown root:root /etc/sudoers.d/thoth
# Validar sintaxis del archivo sudoers
visudo -c || echo "ADVERTENCIA: Error en sintaxis de /etc/sudoers.d/thoth"
# -------------------------------------------------------------------------------
# Crear estructura de directorios de despliegue
# -------------------------------------------------------------------------------
+18
View File
@@ -80,6 +80,12 @@ variable "pipeline_public_key" {
type = string
}
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 = []
}
# -------------------------------------------------------------------------------
# RDS (Base de datos)
# -------------------------------------------------------------------------------
@@ -119,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
@@ -146,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 = ""
}