From 65cc3b576bb8c7f9b2ce93c70fb25bf6c7227fb3 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Thu, 16 Apr 2026 11:58:22 -0600 Subject: [PATCH 1/9] debug(oidc): print decoded JWT payload to diagnose audience mismatch --- scripts/aws-oidc-setup.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/aws-oidc-setup.sh b/scripts/aws-oidc-setup.sh index 1d7e2c1..0ddc69c 100755 --- a/scripts/aws-oidc-setup.sh +++ b/scripts/aws-oidc-setup.sh @@ -53,6 +53,17 @@ echo "Session Name : $SESSION_NAME" echo "Token file : $AWS_WEB_IDENTITY_TOKEN_FILE" echo "Obteniendo credenciales temporales via STS..." +echo "=== Decoding OIDC Token ===" +python3 -c " +import json, base64, sys +t = open('${BITBUCKET_STEP_OIDC_TOKEN}').read().strip() +payload = t.split('.')[1] +padding = 4 - len(payload) % 4 +if padding != 4: payload += '=' * padding +print(json.dumps(json.loads(base64.b64decode(payload)), indent=2)) +" +echo "===========================" + CREDS=$(aws sts assume-role-with-web-identity \ --role-arn "$AWS_ROLE_ARN" \ --role-session-name "$SESSION_NAME" \ From 675c55275bd642f05ee1bac538adfab1ac72cb36 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Thu, 16 Apr 2026 12:05:49 -0600 Subject: [PATCH 2/9] fix: mover oidc audiences a definitions para corregir Incorrect token audience --- bitbucket-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 215ff41..6e797f4 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -10,12 +10,14 @@ image: atlassian/default-image:5 options: + max-time: 120 + +definitions: oidc: audiences: - sts.amazonaws.com - definitions: - steps: + steps: - step: ¬ify-start name: Notify Start script: From 1ab595aa8317df2d00ad7f283b7cdb2fbbfded01 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Thu, 16 Apr 2026 12:12:26 -0600 Subject: [PATCH 3/9] fix: corregir lectura del token OIDC en aws-oidc-setup.sh --- scripts/aws-oidc-setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/aws-oidc-setup.sh b/scripts/aws-oidc-setup.sh index 0ddc69c..1241df1 100755 --- a/scripts/aws-oidc-setup.sh +++ b/scripts/aws-oidc-setup.sh @@ -56,7 +56,7 @@ echo "Obteniendo credenciales temporales via STS..." echo "=== Decoding OIDC Token ===" python3 -c " import json, base64, sys -t = open('${BITBUCKET_STEP_OIDC_TOKEN}').read().strip() +t = open('${AWS_WEB_IDENTITY_TOKEN_FILE}').read().strip() payload = t.split('.')[1] padding = 4 - len(payload) % 4 if padding != 4: payload += '=' * padding From b1c0be4ea61a20829882e6c6e20e1bad4ba9eb64 Mon Sep 17 00:00:00 2001 From: Evert Romero Date: Fri, 17 Apr 2026 10:53:21 -0600 Subject: [PATCH 4/9] fix(pipeline): instalar AWS CLI v2 en cada step que lo requiere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El pipeline fallaba porque cada step corre en un contenedor fresco y AWS CLI instalado en el step 01 no persistía para los steps subsecuentes. Cambios: - Agregar instalación de AWS CLI v2 en steps 03, 05, 06, 07 - Tanto para ramas developer como master - Usar instalación oficial desde awscli.amazonaws.com Fixes pipeline fallido: bash: aws: command not found --- bitbucket-pipelines.yml | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 9c9eb67..fe857de 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -17,6 +17,15 @@ options: definitions: steps: + - step: &install-aws-cli + name: Install AWS CLI + script: + - 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 + - step: ¬ify-start name: Notify Start script: @@ -77,6 +86,11 @@ pipelines: 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 - cd terraform - wget -q "https://releases.hashicorp.com/terraform/1.11.4/terraform_1.11.4_linux_amd64.zip" @@ -119,6 +133,11 @@ pipelines: 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 - | if [ -d build/ ] && [ "$(ls -A build/ 2>/dev/null)" ]; then @@ -138,6 +157,11 @@ pipelines: name: 06_install 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 - | JAR_LOCAL_PATTERN="build/libs/*.jar" JAR_S3_URI="s3://${DEV_S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar" @@ -164,6 +188,11 @@ pipelines: 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 - echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key - chmod 600 ~/.ssh/sacc4_key @@ -207,6 +236,11 @@ pipelines: 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 - cd terraform - wget -q "https://releases.hashicorp.com/terraform/1.11.4/terraform_1.11.4_linux_amd64.zip" @@ -249,6 +283,11 @@ pipelines: 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 - | if [ -d build/ ] && [ "$(ls -A build/ 2>/dev/null)" ]; then @@ -268,6 +307,11 @@ pipelines: name: 06_install 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 - | JAR_LOCAL_PATTERN="build/libs/*.jar" JAR_S3_URI="s3://${PROD_S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar" @@ -305,6 +349,11 @@ pipelines: trigger: manual 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 - echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key - chmod 600 ~/.ssh/sacc4_key From f32b58fc46cbc2b1b9c424618dddcd0779557cb4 Mon Sep 17 00:00:00 2001 From: Evert Romero Date: Fri, 17 Apr 2026 11:11:06 -0600 Subject: [PATCH 5/9] fix(pipeline): implementar manejo robusto de state locks de Terraform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- bitbucket-pipelines.yml | 34 ++++++- scripts/terraform-lock-cleanup.sh | 164 ++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 scripts/terraform-lock-cleanup.sh diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index fe857de..f081ec9 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -81,6 +81,19 @@ pipelines: - 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 +112,8 @@ 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 + - terraform plan -lock-timeout=5m -var-file=environments/dev.tfvars -var="db_password=${DEV_DB_PASSWORD}" -out=dev.tfplan + - terraform apply -lock-timeout=5m -auto-approve dev.tfplan - terraform output -json > terraform-outputs.json - cat terraform-outputs.json artifacts: @@ -231,6 +244,19 @@ pipelines: - 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,8 +275,8 @@ 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 + - terraform plan -lock-timeout=5m -var-file=environments/prod.tfvars -var="db_password=${PROD_DB_PASSWORD}" -out=prod.tfplan + - terraform apply -lock-timeout=5m -auto-approve prod.tfplan - terraform output -json > terraform-outputs.json - cat terraform-outputs.json artifacts: diff --git a/scripts/terraform-lock-cleanup.sh b/scripts/terraform-lock-cleanup.sh new file mode 100644 index 0000000..d26eb9c --- /dev/null +++ b/scripts/terraform-lock-cleanup.sh @@ -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 +# 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 ===" From defce6933de9231fbafb00e682d276b923745308 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Mon, 20 Apr 2026 17:47:15 -0600 Subject: [PATCH 6/9] feat(pipeline): Add SSH key rotation, health checks, and manual approval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- GUIA_DESARROLLO.md | 399 ++++++++++++++++++++++++++++++++++++++++ bitbucket-pipelines.yml | 131 ++++++++++++- scripts/health-check.sh | 113 ++++++++++-- terraform/main.tf | 8 +- 4 files changed, 623 insertions(+), 28 deletions(-) create mode 100644 GUIA_DESARROLLO.md diff --git a/GUIA_DESARROLLO.md b/GUIA_DESARROLLO.md new file mode 100644 index 0000000..b6d8371 --- /dev/null +++ b/GUIA_DESARROLLO.md @@ -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 diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index f081ec9..0e7b807 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -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 @@ -112,8 +112,15 @@ pipelines: - terraform version - export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-mx-central-1}" - terraform init -backend-config=backend.dev.hcl - - terraform plan -lock-timeout=5m -var-file=environments/dev.tfvars -var="db_password=${DEV_DB_PASSWORD}" -out=dev.tfplan - - terraform apply -lock-timeout=5m -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: @@ -167,7 +174,25 @@ pipelines: - echo "Publish condicional completado." - step: - name: 06_install + name: 06_update_ssh_keys + script: + - set -euo pipefail + - mkdir -p ~/.ssh + - echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key + - chmod 600 ~/.ssh/sacc4_key + # 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 + - | + DEV_PUB_KEY=$(echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d | ssh-keygen -y -f /dev/stdin) + ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \ + -i ~/.ssh/sacc4_key \ + -o StrictHostKeyChecking=no \ + "${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \ + "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 @@ -197,7 +222,7 @@ pipelines: - echo "Install condicional completado." - step: - name: 07_deploy + name: 08_deploy oidc: true script: - set -euo pipefail @@ -226,6 +251,33 @@ 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_SERVER_IP_PROYECTOSACC}: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 @@ -275,12 +327,18 @@ pipelines: - terraform version - export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-mx-central-1}" - terraform init -backend-config=backend.prod.hcl + # 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 - - terraform apply -lock-timeout=5m -auto-approve 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 @@ -330,7 +388,25 @@ pipelines: - echo "Publish condicional completado." - step: - name: 06_install + name: 06_update_ssh_keys + script: + - set -euo pipefail + - mkdir -p ~/.ssh + - echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key + - chmod 600 ~/.ssh/sacc4_key + # 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 "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d | ssh-keygen -y -f /dev/stdin) + ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \ + -i ~/.ssh/sacc4_key \ + -o StrictHostKeyChecking=no \ + "${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \ + "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 @@ -360,7 +436,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}" @@ -369,7 +445,7 @@ 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 @@ -381,6 +457,16 @@ pipelines: - ./aws/install - aws --version - source scripts/aws-oidc-setup.sh prod + # Aplicar Terraform plan previamente generado y guardado en artifact + - | + 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 "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key - chmod 600 ~/.ssh/sacc4_key - | @@ -399,3 +485,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_SERVER_IP_PROYECTOSACC}: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" diff --git a/scripts/health-check.sh b/scripts/health-check.sh index 5b8345b..54c2522 100755 --- a/scripts/health-check.sh +++ b/scripts/health-check.sh @@ -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 "$@" diff --git a/terraform/main.tf b/terraform/main.tf index 4ba021b..928e411 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -116,12 +116,16 @@ resource "aws_security_group" "ec2_api" { vpc_id = aws_vpc.main.id description = "Security Group para la API backend de ${var.project_name}" + # NOTA DE SEGURIDAD: Acceso SSH controlado EXCLUSIVAMENTE por llaves SSH + # administradas por el pipeline CI/CD (key-based auth), NO por restricción de IP. + # El pipeline inyecta y rota las llaves públicas en authorized_keys del usuario thoth. + # Considerar migrar a AWS Systems Manager Session Manager para eliminar acceso SSH directo. ingress { - description = "SSH desde IPs confiables" + 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"] # SSH desde cualquier IP (pipeline Bitbucket + administración) + cidr_blocks = ["0.0.0.0/0"] } ingress { From 15e499d97078dec0d5d9132179a2a2dc4587b578 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Mon, 27 Apr 2026 10:20:05 -0600 Subject: [PATCH 7/9] ci: actualizar pipeline con nuevas variables SSH y IPs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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/ --- DATOS_CONEXION.md | 31 ++++++++++++++++++++++++ bitbucket-pipelines.yml | 52 ++++++++++++++++++++--------------------- keys/osiris_key | 7 ++++++ keys/thoth_key | 7 ++++++ 4 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 DATOS_CONEXION.md create mode 100644 keys/osiris_key create mode 100644 keys/thoth_key diff --git a/DATOS_CONEXION.md b/DATOS_CONEXION.md new file mode 100644 index 0000000..f0c768d --- /dev/null +++ b/DATOS_CONEXION.md @@ -0,0 +1,31 @@ +# 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 | + +## 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 +- `keys/thoth_key.pub` - Llave pública para usuario thoth +- `keys/osiris_key` - Llave privada para usuario osiris +- `keys/osiris_key.pub` - Llave pública para usuario osiris + +## Conexión de Prueba +```bash +ssh -i keys/thoth_key thoth@78.12.135.184 +ssh -i keys/osiris_key osiris@78.12.135.184 +``` diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 0e7b807..dc5203d 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -74,9 +74,9 @@ pipelines: - ./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 - export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}" - export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}" - bash scripts/telegram-pipeline-notify.sh start @@ -178,16 +178,16 @@ pipelines: script: - set -euo pipefail - 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 # 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 - | - DEV_PUB_KEY=$(echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d | ssh-keygen -y -f /dev/stdin) - ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \ + DEV_PUB_KEY=$(echo "${SSH_PRIVATE_KEY_THOTH}" | ssh-keygen -y -f /dev/stdin) + 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/.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." @@ -209,12 +209,12 @@ 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 + echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key chmod 600 ~/.ssh/sacc4_key - ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \ + 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." @@ -232,13 +232,13 @@ pipelines: - ./aws/install - aws --version - source scripts/aws-oidc-setup.sh dev - - 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 -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \ + 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 @@ -261,7 +261,7 @@ pipelines: - echo "=== Health Check post-deploy (DEV) ===" - | export ENV=dev - export HEALTH_URL="http://${DEV_SERVER_IP_PROYECTOSACC}:8080/actuator/health" + export HEALTH_URL="http://${DEV_INSTANCE_IP}:8080/actuator/health" export MAX_RETRIES=12 export RETRY_INTERVAL=10 # Verificar CloudFront @@ -289,9 +289,9 @@ pipelines: - ./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 - export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}" - export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}" - bash scripts/telegram-pipeline-notify.sh start @@ -392,16 +392,16 @@ pipelines: script: - set -euo pipefail - 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 # 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 "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d | ssh-keygen -y -f /dev/stdin) - ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \ + PROD_PUB_KEY=$(echo "${SSH_PRIVATE_KEY_THOTH}" | ssh-keygen -y -f /dev/stdin) + 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/.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." @@ -423,12 +423,12 @@ 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 + echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key chmod 600 ~/.ssh/sacc4_key - ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \ + 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." @@ -467,13 +467,13 @@ pipelines: else echo "WARNING: No se encontró prod.tfplan. Terraform apply saltado." fi - - 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 -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \ + 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 @@ -496,7 +496,7 @@ pipelines: - echo "=== Health Check post-deploy (PROD) ===" - | export ENV=prod - export HEALTH_URL="http://${PROD_SERVER_IP_PROYECTOSACC}:8080/actuator/health" + export HEALTH_URL="http://${PROD_INSTANCE_IP}:8080/actuator/health" export MAX_RETRIES=12 export RETRY_INTERVAL=10 # Verificar CloudFront diff --git a/keys/osiris_key b/keys/osiris_key new file mode 100644 index 0000000..be74df0 --- /dev/null +++ b/keys/osiris_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAdkhvJ7Il4Yrw1Zqu9P1PcFbdc2HciYqb7tCaj60a+vwAAAJhowcCaaMHA +mgAAAAtzc2gtZWQyNTUxOQAAACAdkhvJ7Il4Yrw1Zqu9P1PcFbdc2HciYqb7tCaj60a+vw +AAAEBaErHCg+OQP9abpbe9+Eee80Cncbntxz5TXkuqBkA0xx2SG8nsiXhivDVmq70/U9wV +t1zYdyJipvu0JqPrRr6/AAAAEG9zaXJpc0BjY3NvZnQuYWkBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/keys/thoth_key b/keys/thoth_key new file mode 100644 index 0000000..59739dc --- /dev/null +++ b/keys/thoth_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACC8ad1D22SDWfkWGeO9qwGKrBFJmLe68Kk90aF/skUgYAAAAJimLIkZpiyJ +GQAAAAtzc2gtZWQyNTUxOQAAACC8ad1D22SDWfkWGeO9qwGKrBFJmLe68Kk90aF/skUgYA +AAAEC+p75hAW4PuYLQcy6dlR0taqeoNNFknaUbOHx7kdEKYrxp3UPbZINZ+RYZ472rAYqs +EUmYt7rwqT3RoX+yRSBgAAAAD3Rob3RoQGNjc29mdC5haQECAwQFBg== +-----END OPENSSH PRIVATE KEY----- From 2e3627fb667a5a54508eb1cd3bc751e0331a6533 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Mon, 27 Apr 2026 13:26:12 -0600 Subject: [PATCH 8/9] feat(pipeline): agregar soporte para llaves SSH con passphrase en DEV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- DATOS_CONEXION.md | 65 +++++- bitbucket-pipelines.yml | 42 +++- docs/VALIDACION_CUENTA_AWS.md | 112 ++++++++++ scripts/validar_conexion_rds.sh | 363 ++++++++++++++++++++++++++++++++ 4 files changed, 574 insertions(+), 8 deletions(-) create mode 100644 docs/VALIDACION_CUENTA_AWS.md create mode 100644 scripts/validar_conexion_rds.sh diff --git a/DATOS_CONEXION.md b/DATOS_CONEXION.md index f0c768d..a86a9b2 100644 --- a/DATOS_CONEXION.md +++ b/DATOS_CONEXION.md @@ -9,6 +9,22 @@ | 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 ``` @@ -19,13 +35,56 @@ SSH_PRIVATE_KEY_OSIRIS=(contenido de keys/osiris_key) ``` ## Archivos de Llaves -- `keys/thoth_key` - Llave privada para usuario thoth +- `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 +- `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 -## Conexión de Prueba ```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` diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index dc5203d..1f232e4 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -68,7 +68,7 @@ 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 @@ -77,6 +77,14 @@ pipelines: - echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key - chmod 600 ~/.ssh/sacc4_key - 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 @@ -177,11 +185,18 @@ pipelines: name: 06_update_ssh_keys script: - set -euo pipefail + - 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 - # 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 + - 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" \ @@ -195,7 +210,7 @@ pipelines: name: 07_install 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 @@ -209,8 +224,16 @@ pipelines: fi if [ "${HAS_LOCAL_JAR}" = "true" ]; then echo "INFO: Artefacto JAR encontrado localmente. Procediendo con instalación en servidor." + 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 + " ssh -p "22" \ -i ~/.ssh/sacc4_key \ -o StrictHostKeyChecking=no \ @@ -226,14 +249,23 @@ pipelines: 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 + - 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 + " - | ssh -p "22" \ -i ~/.ssh/sacc4_key \ diff --git a/docs/VALIDACION_CUENTA_AWS.md b/docs/VALIDACION_CUENTA_AWS.md new file mode 100644 index 0000000..2bd6264 --- /dev/null +++ b/docs/VALIDACION_CUENTA_AWS.md @@ -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í. diff --git a/scripts/validar_conexion_rds.sh b/scripts/validar_conexion_rds.sh new file mode 100644 index 0000000..2f101ff --- /dev/null +++ b/scripts/validar_conexion_rds.sh @@ -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 -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}" From 7e0c764f3f3e1d3d969e4fbcd5cc78acbbcbf7f7 Mon Sep 17 00:00:00 2001 From: Evert Daniel Romero Garrido Date: Thu, 7 May 2026 09:44:44 -0600 Subject: [PATCH 9/9] 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 --- bitbucket-pipelines.yml | 39 ++++++++++- terraform/environments/prod.tfvars | 6 +- terraform/main.tf | 109 ++++++++++++++++++++++++----- terraform/user-data.sh | 72 +++++++++++++++++++ terraform/variables.tf | 6 ++ 5 files changed, 210 insertions(+), 22 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 1f232e4..f99aae7 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -315,7 +315,7 @@ 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 @@ -324,6 +324,14 @@ pipelines: - echo "${SSH_PRIVATE_KEY_THOTH}" > ~/.ssh/sacc4_key - chmod 600 ~/.ssh/sacc4_key - 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 @@ -423,9 +431,18 @@ pipelines: name: 06_update_ssh_keys script: - set -euo pipefail + - 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 - | @@ -441,7 +458,7 @@ pipelines: name: 07_install 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 @@ -455,8 +472,16 @@ pipelines: fi if [ "${HAS_LOCAL_JAR}" = "true" ]; then echo "INFO: Artefacto JAR encontrado localmente. Procediendo con instalación en servidor." + 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 + " ssh -p "22" \ -i ~/.ssh/sacc4_key \ -o StrictHostKeyChecking=no \ @@ -483,7 +508,7 @@ pipelines: 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 @@ -501,6 +526,14 @@ pipelines: 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 \ diff --git a/terraform/environments/prod.tfvars b/terraform/environments/prod.tfvars index 9e66608..05748ff 100644 --- a/terraform/environments/prod.tfvars +++ b/terraform/environments/prod.tfvars @@ -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" diff --git a/terraform/main.tf b/terraform/main.tf index 928e411..80e3f74 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -116,12 +116,8 @@ resource "aws_security_group" "ec2_api" { vpc_id = aws_vpc.main.id description = "Security Group para la API backend de ${var.project_name}" - # NOTA DE SEGURIDAD: Acceso SSH controlado EXCLUSIVAMENTE por llaves SSH - # administradas por el pipeline CI/CD (key-based auth), NO por restricción de IP. - # El pipeline inyecta y rota las llaves públicas en authorized_keys del usuario thoth. - # Considerar migrar a AWS Systems Manager Session Manager para eliminar acceso SSH directo. ingress { - description = "SSH - Acceso controlado por llaves CI/CD (no por IP)" + description = "SSH desde IPs confiables" from_port = 22 to_port = 22 protocol = "tcp" @@ -152,6 +148,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 @@ -254,6 +285,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 = "*" } ] }) @@ -276,23 +324,35 @@ 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 + 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 { + ignore_changes = [ + iam_instance_profile, + user_data, + tags["AutoStart"], + tags["AutoStop"], + tags["Schedule"], + tags["ScheduleHours"], + tags["ScheduleTimezone"], + ] } } @@ -326,8 +386,23 @@ resource "aws_db_instance" "main" { backup_retention_period = 7 tags = { - Name = "${var.project_name}-rds" - Environment = var.environment + 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 { + ignore_changes = [ + tags["AutoStart"], + tags["AutoStop"], + tags["Schedule"], + tags["ScheduleHours"], + tags["ScheduleTimezone"], + ] } } diff --git a/terraform/user-data.sh b/terraform/user-data.sh index eb3dc71..63445ea 100755 --- a/terraform/user-data.sh +++ b/terraform/user-data.sh @@ -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 # ------------------------------------------------------------------------------- diff --git a/terraform/variables.tf b/terraform/variables.tf index 20c2022..fbaf240 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -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) # -------------------------------------------------------------------------------