commit 85297b12a22087f1245ecbcc48df6655c75684dd Author: Evert Daniel Romero Garrido Date: Tue Apr 14 14:53:05 2026 -0600 Initial commit: Terraform infrastructure, pipelines, docs and scripts diff --git a/.atl/skill-registry.md b/.atl/skill-registry.md new file mode 100644 index 0000000..704c903 --- /dev/null +++ b/.atl/skill-registry.md @@ -0,0 +1,69 @@ +# Skill Registry — proyectosacc + +> Inventario de skills recomendados para el proyecto `proyectosacc`. +> Sub-proyecto de AWS/CI-CD de Cómputo Contable Soft SA de CV. + +## Descripción del Proyecto + +- **Tipo**: IaC/CD — Despliegue de aplicaciones vía SSH desde Bitbucket Pipelines +- **Target**: VM `172.16.20.208` (SSH, usuario + password) +- **Trigger CI/CD**: Bitbucket Pipelines +- **Componentes**: Pipeline config, deployment scripts, infra docs, operational runbooks + +## Skills Principales + +### CI/CD y Pipelines + +| Skill | Descripción | Trigger | +|-------|-------------|---------| +| `cicd-expert` | Pipelines CI/CD (GitHub Actions, GitLab CI, Jenkins). Seguridad, supply chain, GitOps. | Diseño de pipelines, gates de seguridad, troubleshooting CI/CD. | +| `bitbucket-workflow` | Mejores prácticas Bitbucket: PRs, Pipelines, Jira integration. | Trabajo con Bitbucket Cloud, pipelines, integración Atlassian. | +| `bitbucket-server` | Interacción con Bitbucket Server REST API. | Gestión de PRs vía API. | + +### Infrastructure as Code (IaC) + +| Skill | Descripción | Trigger | +|-------|-------------|---------| +| `terraform-best-practices` | Optimización Terraform/OpenTofu. | Código Terraform, IaC. | +| `terraform-specialist` | Terraform avanzado: state management, enterprise patterns. | Infraestructura compleja. | +| `ansible-expert` | Configuration management con Ansible. | Playbooks, roles, inventarios. | + +### AWS y Cloud + +| Skill | Descripción | Trigger | +|-------|-------------|---------| +| `aws-solution-architect` | Arquitecturas AWS serverless, CloudFormation, cost optimization. | Diseño de arquitectura AWS. | + +### Documentación y Gestión + +| Skill | Descripción | Trigger | +|-------|-------------|---------| +| `confluence` | Gestión de documentación en Confluence. | Lectura/escritura de páginas. | +| `jira` | Gestión de issues en Jira. | Tickets, sprints, backlog. | +| `jira-cli` | Jira desde línea de comandos. | Operaciones rápidas de issues. | +| `engram-memory-protocol` | Memoria persistente entre sesiones. | Decisiones, descubrimientos, preferencias. | + +### Control de Versiones + +| Skill | Descripción | Trigger | +|-------|-------------|---------| +| `branch-pr` | Workflow de PRs con sistema issue-first. | Creación de PRs. | +| `issue-creation` | Workflow de issues con sistema issue-first. | Reporte de bugs, features. | + +### Diagramas y Visualización + +| Skill | Descripción | Trigger | +|-------|-------------|---------| +| `excalidraw-diagram-generator` | Diagramas Excalidraw desde lenguaje natural. | Arquitectura, flujos, mind maps. | + +## Convenciones + +1. Los secrets se almacenan en Bitbucket Repository Variables o `.env` (nunca en el repo). +2. Pipeline estándar de 7 pasos (image → repo → deps → build → publish → install → deploy). +3. Scripts con `set -euo pipefail` y headers de CCsoft. +4. SSH deployment con usuario `osiris` o `thoth`. +5. Notificaciones vía Telegram (@CCAlertasBot, @CCDevRoBot). + +--- + +*Cómputo Contable Soft SA de CV — Skill Registry proyectosacc — Abril 2026* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c8f371 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# =============================================================================== +# 🚫 ARCHIVOS Y DIRECTORIOS IGNORADOS — Proyecto proyectosacc +# =============================================================================== + +# Variables de entorno y secrets +.env +.env.* +!.env.example + +# Terraform +terraform/.terraform/ +terraform/*.tfstate +terraform/*.tfstate.* +terraform/*.tfplan +terraform/.terraform.lock.hcl +terraform/crash.log +terraform/crash.*.log +terraform/override.tf +terraform/override.tf.json +terraform/*_override.tf +terraform/*_override.tf.json + +# Claves SSH y certificados +*.pem +*.key +*.pub +*.crt +*.p12 +*.pfx + +# Logs +*.log +logs/ + +# IDEs y editores +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Dependencias y builds +node_modules/ +build/ +dist/ +.gradle/ +gradle/ +*.jar +*.war +*.zip + +# Archivos temporales +tmp/ +temp/ +*.tmp +*.bak diff --git a/.tools/LICENSE.txt b/.tools/LICENSE.txt new file mode 100644 index 0000000..8142708 --- /dev/null +++ b/.tools/LICENSE.txt @@ -0,0 +1,92 @@ +License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +Parameters + +Licensor: HashiCorp, Inc. +Licensed Work: Terraform Version 1.6.0 or later. The Licensed Work is (c) 2024 + HashiCorp, Inc. +Additional Use Grant: You may make production use of the Licensed Work, provided + Your use does not include offering the Licensed Work to third + parties on a hosted or embedded basis in order to compete with + HashiCorp's paid version(s) of the Licensed Work. For purposes + of this license: + + A "competitive offering" is a Product that is offered to third + parties on a paid basis, including through paid support + arrangements, that significantly overlaps with the capabilities + of HashiCorp's paid version(s) of the Licensed Work. If Your + Product is not a competitive offering when You first make it + generally available, it will not become a competitive offering + later due to HashiCorp releasing a new version of the Licensed + Work with additional capabilities. In addition, Products that + are not provided on a paid basis are not competitive. + + "Product" means software that is offered to end users to manage + in their own environments or offered as a service on a hosted + basis. + + "Embedded" means including the source code or executable code + from the Licensed Work in a competitive offering. "Embedded" + also means packaging the competitive offering in such a way + that the Licensed Work must be accessed or downloaded for the + competitive offering to operate. + + Hosting or using the Licensed Work(s) for internal purposes + within an organization is not considered a competitive + offering. HashiCorp considers your organization to include all + of your affiliates under common control. + + For binding interpretive guidance on using HashiCorp products + under the Business Source License, please visit our FAQ. + (https://www.hashicorp.com/license-faq) +Change Date: Four years from the date the Licensed Work is published. +Change License: MPL 2.0 + +For information about alternative licensing arrangements for the Licensed Work, +please contact licensing@hashicorp.com. + +Notice + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. diff --git a/.tools/terraform b/.tools/terraform new file mode 100755 index 0000000..997f0e7 Binary files /dev/null and b/.tools/terraform differ diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..7ea56c4 --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,186 @@ +# =============================================================================================================== +# bitbucket-pipelines.yml - Pipeline CI/CD para proyectosacc +# Descripción: +# Pipeline de 7 pasos estándar de CCsoft para desplegar el frontend +# React (S3+CloudFront) y la API backend (EC2) de SACC. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +image: atlassian/default-image:5 + +definitions: + steps: + - step: ¬ify-start + name: Notify Start + script: + - export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}" + - export TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}" + - bash ci-cd-commons/telegram_alert.sh "🚀 Iniciando pipeline de proyectosacc (${BITBUCKET_BRANCH})" + + - step: ¬ify-fail + name: Notify Failure + script: + - export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}" + - export TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}" + - bash ci-cd-commons/telegram_alert.sh "❌ Pipeline de proyectosacc falló en el paso ${BITBUCKET_STEP_KEY}" + +pipelines: + default: + - step: + name: 04_build + script: + - set -euo pipefail + - echo "=== Build de proyectosacc (sin deploy) ===" + - npm ci + - npm run build + - ./gradlew clean bootJar + + branches: + develop: + - step: + name: 01_image-setup + script: + - set -euo pipefail + - apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk awscli + - mkdir -p ~/.ssh + - echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.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 + - export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}" + - export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}" + - bash ci-cd-commons/telegram_alert.sh "🚀 Iniciando pipeline DEV de proyectosacc" + + - step: + name: 02_repo-config + script: + - set -euo pipefail + - git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-commons.git" ci-cd-commons + - git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-saac4.git" ci-cd-saac4 + + - step: + name: 03_dependencies + script: + - set -euo pipefail + - npm ci + - ./gradlew dependencies + + - step: + name: 04_build + script: + - set -euo pipefail + - npm ci + - npm run build + - ./gradlew clean bootJar + artifacts: + - build/** + - build/libs/*.jar + + - step: + name: 05_publish + script: + - set -euo pipefail + - aws s3 sync build/ "s3://${S3_FRONTEND_BUCKET}/" --delete + - aws s3 cp build/libs/*.jar "s3://${S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar" + + - step: + name: 06_install + script: + - set -euo pipefail + - echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key + - chmod 600 ~/.ssh/sacc4_key + - | + 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/deploy/artifacts/current && aws s3 cp s3://${S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'" + + - step: + name: 07_deploy + script: + - set -euo pipefail + - echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key + - chmod 600 ~/.ssh/sacc4_key + - | + ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \ + -i ~/.ssh/sacc4_key \ + -o StrictHostKeyChecking=no \ + "${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \ + "bash /home/thoth/deploy/setup/deploy.sh" + - aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*" + - bash ci-cd-commons/telegram_alert.sh "✅ Deploy DEV de proyectosacc completado exitosamente" + + main: + - step: + name: 01_image-setup + script: + - set -euo pipefail + - apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk awscli + - mkdir -p ~/.ssh + - echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.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 + - export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}" + - export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}" + - bash ci-cd-commons/telegram_alert.sh "🚀 Iniciando pipeline PROD de proyectosacc" + + - step: + name: 02_repo-config + script: + - set -euo pipefail + - git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-commons.git" ci-cd-commons + - git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-saac4.git" ci-cd-saac4 + + - step: + name: 03_dependencies + script: + - set -euo pipefail + - npm ci + - ./gradlew dependencies + + - step: + name: 04_build + script: + - set -euo pipefail + - npm ci + - npm run build + - ./gradlew clean bootJar + artifacts: + - build/** + - build/libs/*.jar + + - step: + name: 05_publish + script: + - set -euo pipefail + - aws s3 sync build/ "s3://${S3_FRONTEND_BUCKET}/" --delete + - aws s3 cp build/libs/*.jar "s3://${S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar" + + - step: + name: 06_install + script: + - set -euo pipefail + - echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key + - chmod 600 ~/.ssh/sacc4_key + - | + 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/deploy/artifacts/current && aws s3 cp s3://${S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'" + + - step: + name: 07_deploy + script: + - set -euo pipefail + - echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key + - chmod 600 ~/.ssh/sacc4_key + - | + ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \ + -i ~/.ssh/sacc4_key \ + -o StrictHostKeyChecking=no \ + "${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \ + "bash /home/thoth/deploy/setup/deploy.sh" + - aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*" + - bash ci-cd-commons/telegram_alert.sh "✅ Deploy PROD de proyectosacc completado exitosamente" diff --git a/docs/01-que-es-proyectosacc.md b/docs/01-que-es-proyectosacc.md new file mode 100644 index 0000000..6615d9f --- /dev/null +++ b/docs/01-que-es-proyectosacc.md @@ -0,0 +1,56 @@ +# 01 - ¿Qué es proyectosacc? + +> Guía básica para entender el proyecto `proyectosacc` de Cómputo Contable Soft. + +--- + +## 1. ¿Qué es este proyecto? + +**proyectosacc** es el sistema de despliegue automático para la aplicación **SACC** (Sistema de Administración Contable y Comercial) de Cómputo Contable Soft. + +En palabras simples: es el "puente" que lleva el código de los programadores desde **Bitbucket** hasta el servidor de AWS donde los usuarios pueden usarlo. + +El proyecto tiene tres partes principales: +1. **Infraestructura en AWS**: el servidor de la API, la base de datos, el bucket S3 para el frontend, CloudFront y el dominio. +2. **Pipeline de Bitbucket**: el proceso automático que compila y publica tanto el frontend React como la API backend. +3. **Scripts de despliegue**: los archivos que instalan la API en el servidor y suben el frontend a S3. + +--- + +## 2. ¿Qué problema resuelve? + +Antes de `proyectosacc`, desplegar la aplicación SACC era un proceso manual y lento: +- Los desarrolladores tenían que copiar archivos a mano. +- Había errores porque olvidaban pasos. +- Era difícil saber qué versión estaba corriendo en producción. + +**proyectosacc** resuelve esto automatizando todo: +- Cada vez que un programador sube código a Bitbucket, el pipeline se encarga de compilar, probar y desplegar. +- Si algo falla, se envía una alerta por Telegram. +- La infraestructura se crea con **Terraform**, así que siempre es la misma y no hay "magia" escondida. + +--- + +## 3. ¿Quién lo usa? + +| Rol | Cómo interactúa con el proyecto | +|-----|--------------------------------| +| **Desarrolladores** | Suben código a Bitbucket. El pipeline hace el resto. | +| **DevOps / SysAdmin** | Configuran el pipeline, revisan logs y arreglan problemas de infraestructura. | +| **Usuarios finales** | Acceden a `https://sacc.ccsoft.mx` para usar la aplicación. No ven el pipeline, pero se benefician de que siempre hay una versión estable. | + +--- + +## 4. Dato clave + +El dominio oficial de la aplicación es: + +``` +https://sacc.ccsoft.mx +``` + +Esto significa que cuando el despliegue termina exitosamente, la aplicación está disponible en esa dirección. + +--- + +*Siguiente: [`02-arquitectura-general.md`](02-arquitectura-general.md)* diff --git a/docs/02-arquitectura-general.md b/docs/02-arquitectura-general.md new file mode 100644 index 0000000..a85e206 --- /dev/null +++ b/docs/02-arquitectura-general.md @@ -0,0 +1,151 @@ +# 02 - Arquitectura General + +> Cómo se conectan todas las piezas de `proyectosacc`. + +--- + +## 1. Diagrama general + +Aquí tienes un diagrama en texto que muestra el flujo completo: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ INTERNET (usuarios) │ +│ ▼ │ +│ https://sacc.ccsoft.mx │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ AWS Route 53 │ +│ (traduce el dominio al destino correcto) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ AWS CloudFront + ACM (SSL) │ +│ (distribuye el frontend globalmente y pone el candadito verde HTTPS) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ S3 Static Website Hosting │ +│ (almacena y sirve el frontend React compilado) │ +└─────────────────────────────────────────────────────────────────────────┘ + + +FLUJO DE LA API (desde el navegador o la app) +============================================= + +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Navegador/app │─────▶│ EC2 T3.small │─────▶│ RDS MariaDB │ +│ (llama a /api) │ │ (API backend) │ │ (base de datos)│ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + │ Nginx Proxy (solo API, puerto 8080) + ▼ + Servicio systemd + (usuario osiris) + + +OTRO FLUJO: El pipeline de despliegue +===================================== + +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ Bitbucket │────▶│ Pipeline │────▶│ S3 Buckets │────▶│ EC2 T3 │ +│ (código) │ │ (7 pasos) │ │ (frontend + │ │ (API) │ +│ │ │ │ │ artefactos) │ │ │ +└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ + │ ▲ + │ │ + ▼ │ + CloudFront invalidation │ + (actualiza cache global) │ +``` + +--- + +## 2. Explicación de cada pieza + +> 💡 **Dato clave**: aunque CloudFront sirve principalmente el frontend desde S3, también tiene una regla especial (`/api/*`) que envía las peticiones de la API directamente a la EC2. Así todo el tráfico de `sacc.ccsoft.mx` pasa por un solo punto. + +### 🔷 Bitbucket +Es el "banco" donde se guarda el código fuente de SACC. +- Los desarrolladores suben sus cambios aquí. +- Cuando hay un cambio nuevo, Bitbucket "despierta" el pipeline. + +### 🔷 Pipeline de Bitbucket (7 pasos) +Es el robot que hace todo el trabajo: +1. Prepara la imagen de compilación. +2. Descarga los repositorios. +3. Instala dependencias. +4. **Compila el frontend React (`npm run build`) y la API backend (`./gradlew bootJar`).** +5. **Sube el frontend al bucket S3 y el artefacto de la API a S3.** +6. Se conecta por SSH al servidor EC2 para desplegar la API. +7. **Invalida la caché de CloudFront, reinicia la API y verifica que todo esté saludable.** + +### 🔷 S3 Bucket +Es un "almacén" de AWS con dos trabajos en `proyectosacc`: +1. **Sirve el frontend React** como un sitio web estático. +2. **Guarda los artefactos** de la API generados por el pipeline. +- También sirve como respaldo de versiones anteriores. + +### 🔷 CloudFront +Es la red de distribución de contenido (CDN) de AWS. +- **Rol**: Toma los archivos del frontend desde S3 y los distribuye por todo el mundo de forma rápida y segura. +- También maneja el HTTPS con el certificado de ACM. + +### 🔷 EC2 T3.small +Es el servidor virtual de AWS donde **solo vive la API/backend**. +- **Tipo**: `t3.small` (2 vCPU, 2 GB RAM). +- **Sistema operativo**: Ubuntu 22.04 LTS. +- **Rol**: Ejecuta el servicio de la API (Java) en el puerto `8080`. +- Nginx en la EC2 solo actúa como proxy hacia la API, **no sirve el frontend React**. + +### 🔷 RDS MariaDB +Es la base de datos gestionada por AWS. +- **Tipo**: `db.t3.micro`. +- **Rol**: Guarda toda la información de la aplicación (clientes, facturas, usuarios, etc.). +- La EC2 se conecta a ella por la red interna de AWS. + +### 🔷 Route 53 +Es el servicio de DNS de AWS. +- **Rol**: Cuando un usuario escribe `sacc.ccsoft.mx`, Route 53 le dice al navegador que debe ir a **CloudFront** (no directamente a la EC2). +- Es como la "guía telefónica" de internet. + +### 🔷 ACM (AWS Certificate Manager) +Es el servicio que gestiona los certificados SSL. +- **Rol**: Permite que `https://sacc.ccsoft.mx` funcione con el candadito verde. +- El certificado se adjunta a **CloudFront**, no a Nginx en la EC2. + +--- + +## 3. Integración con proyectos CI/CD compartidos + +`proyectosacc` se conecta con otros repositorios de CI/CD de Cómputo Contable Soft que le prestan "herramientas y ladrillos" ya hechos: + +- **`ci-cd-commons`**: scripts base, utilerías como `telegram_alert.sh` y `logger_bash.sh`, y buenas prácticas de pipeline. +- **`ci-cd-saac4`**: módulos de Terraform, patrones de infraestructura AWS y scripts de despliegue adaptados para la familia de aplicaciones SACC. + +Esto evita reinventar la rueda en cada proyecto y mantiene todo alineado con los estándares de la empresa. + +--- + +## 4. ¿Por qué cada pieza existe? + +| Pieza | ¿Por qué la necesitamos? | +|-------|--------------------------| +| Bitbucket | Para guardar el código y activar el pipeline automáticamente. | +| Pipeline | Para que nadie tenga que copiar archivos a mano. | +| S3 | Para servir el frontend React y guardar los artefactos de la API de forma segura. | +| CloudFront | Para distribuir el frontend rápidamente por todo el mundo con HTTPS. | +| EC2 T3 | Es el servidor donde corre la API backend. Sin él, no hay dónde procesar las peticiones de datos. | +| RDS | Es mejor que instalar la base de datos en la misma EC2 porque AWS la respalda, actualiza y monitorea automáticamente. | +| Route 53 | Para que los usuarios no tengan que memorizar una IP numérica. | +| ACM | Para que la conexión sea segura (HTTPS) y proteger los datos de los usuarios. | +| Nginx | Para recibir las peticiones a la API y pasarlas al servicio backend en el puerto 8080. | + +--- + +*Anterior: [`01-que-es-proyectosacc.md`](01-que-es-proyectosacc.md)* +*Siguiente: [`03-infraestructura-aws.md`](03-infraestructura-aws.md)* diff --git a/docs/03-infraestructura-aws.md b/docs/03-infraestructura-aws.md new file mode 100644 index 0000000..ada830a --- /dev/null +++ b/docs/03-infraestructura-aws.md @@ -0,0 +1,167 @@ +# 03 - Infraestructura AWS + +> Descripción detallada de cada recurso de AWS que usa `proyectosacc`. + +--- + +## 1. EC2 T3.small + +La **EC2** (Elastic Compute Cloud) es el servidor virtual donde corre la aplicación SACC. + +### Especificaciones +- **Tipo de instancia**: `t3.small` +- **vCPU**: 2 +- **RAM**: 2 GB +- **Sistema operativo**: Ubuntu 22.04 LTS +- **Disco raíz**: 20 GB SSD (`gp3`), con cifrado obligatorio + +### ¿Qué hace? +- **Ejecuta la API/backend de SACC** como un servicio de `systemd` (puerto `8080`). +- Corre **Nginx Proxy** solo para redirigir las peticiones de la API hacia el backend. +- **NO sirve el frontend React** (eso lo hace S3 + CloudFront). +- Se conecta a la base de datos RDS para leer y guardar datos. +- **Solo acepta conexiones SSH con llaves dedicadas**: el pipeline y los administradores usan un par de llaves SSH generado específicamente para `proyectosacc`. No se permite acceso con contraseña. + +### Dato importante +En Cómputo Contable Soft hay una regla obligatoria: **todas las instancias EC2 deben ser de la familia T3**. Esto garantiza el mejor balance entre costo y rendimiento. + +--- + +## 2. RDS MariaDB + +La **RDS** (Relational Database Service) es la base de datos gestionada por AWS. + +### Especificaciones +- **Motor**: MariaDB +- **Tipo de instancia**: `db.t3.micro` +- **vCPU**: 2 +- **RAM**: 1 GB +- **Uso**: Base de datos de la aplicación SACC + +### ¿Qué hace? +- Guarda todos los datos de la aplicación: usuarios, clientes, productos, facturas, etc. +- AWS se encarga de hacer respaldos automáticos, aplicar parches de seguridad y monitorear el rendimiento. + +### Ventaja principal +No tenemos que instalar ni mantener MariaDB nosotros mismos en el servidor. AWS lo hace por nosotros. + +--- + +## 3. S3 Bucket + +El **S3** (Simple Storage Service) es el almacén de archivos de AWS. + +### ¿Qué guardamos aquí? +1. **El frontend React compilado** (`build/`) como un sitio web estático. CloudFront lee estos archivos para servirlos a los usuarios. +2. Los archivos compilados (artefactos `.jar`) de la API backend. +3. Versiones anteriores por si necesitamos hacer un *rollback*. +4. Logs y respaldos temporales. + +### Ejemplo de rutas dentro del bucket +``` +s3://ccsoft-proyectosacc-frontend/index.html +s3://ccsoft-artifacts-sacc4/develop/proyectosacc-app-1.0.0.jar +``` + +### ¿Por qué S3? +- Es muy económico. +- Es duradero (AWS garantiza que no se pierdan los archivos). +- El pipeline puede subir y descargar archivos fácilmente. + +--- + +## 4. CloudFront + +**CloudFront** es la red de distribución de contenido (CDN) de AWS. + +### ¿Qué hace? +- Distribuye el frontend React desde el bucket S3 a los usuarios de todo el mundo. +- Reduce la latencia porque los archivos se sirven desde el "edge location" más cercano al usuario. +- Termina el cifrado SSL/TLS (HTTPS) usando el certificado de ACM. +- Se conecta a S3 mediante **Origin Access Control (OAC)** para que el bucket no sea público directamente. + +### Relación con S3 y la API +CloudFront lee los archivos estáticos (`index.html`, CSS, JS) desde S3. Además, tiene un behavior `/api/*` que redirige las peticiones de la API hacia la EC2. De esta forma, tanto el frontend como el backend comparten el mismo dominio `https://sacc.ccsoft.mx`, pero los usuarios nunca acceden directamente al bucket S3. + +--- + +## 5. Route 53 + +**Route 53** es el servicio de DNS de AWS. + +### Registro configurado +- **Nombre**: `sacc.ccsoft.mx` +- **Tipo**: A (Alias) +- **Destino**: **CloudFront Distribution** (no la IP de la EC2) + +### ¿Qué hace? +Cuando un usuario escribe `https://sacc.ccsoft.mx` en su navegador, Route 53 le dice: "ve a CloudFront". Sin Route 53, los usuarios tendrían que escribir una URL larga de CloudFront. + +--- + +## 6. ACM (AWS Certificate Manager) + +**ACM** es el servicio que gestiona certificados SSL/TLS. + +### ¿Qué es un certificado SSL? +Es un documento digital que permite que la conexión entre el usuario y el servidor esté cifrada. Se ve como el candadito verde 🔒 al lado de la dirección web. + +### ¿Cómo lo usamos? +1. Solicitamos un certificado en ACM para el dominio `sacc.ccsoft.mx`. +2. ACM valida que el dominio nos pertenece. +3. El certificado se adjunta a la **distribución de CloudFront**. +4. Los usuarios acceden por **HTTPS** de forma segura. El certificado ya no vive en Nginx de la EC2. + +--- + +## 7. Security Groups + +Un **Security Group** es como un "guardia de seguridad" virtual que controla quién puede entrar y salir de la EC2. + +### Reglas de entrada (inbound) + +| Puerto | Origen | ¿Para qué? | +|--------|--------|-----------| +| `22` | IP del pipeline / VPN | Conexión SSH para despliegues de la API | +| `80` | `0.0.0.0/0` (todo internet) | Tráfico HTTP hacia la API (puede redirigir a HTTPS) | +| `443` | `0.0.0.0/0` (todo internet) | Tráfico HTTPS hacia la API | +| `8080` | IPs de la VPC / CloudFront | Acceso directo a la API backend | + +> 💡 **Nota**: los puertos `80` y `443` en la EC2 ya no sirven el frontend React. Solo reciben peticiones a la API. El frontend se sirve desde CloudFront. + +### Reglas de salida (outbound) + +| Puerto | Destino | ¿Para qué? | +|--------|---------|-----------| +| Todo (`0-65535`) | `0.0.0.0/0` | La EC2 puede salir a internet (descargar paquetes, consultar APIs, etc.) | + +### Regla de seguridad importante +El puerto `22` (SSH) **solo debe abrirse desde IPs confiables**. Nunca dejarlo abierto a todo el mundo (`0.0.0.0/0`). + +--- + +## 8. Nginx Proxy en la EC2 + +**Nginx** es un servidor web que en este proyecto actúa como **proxy inverso exclusivo para la API**. + +### ¿Qué hace Nginx? +1. Escucha en los puertos `80` y/o `443`. +2. Recibe las peticiones dirigidas a la API (por ejemplo, `sacc.ccsoft.mx/api`). +3. Redirige el tráfico al backend que corre en el puerto interno `8080`. +4. **NO sirve archivos estáticos del frontend React** (eso es trabajo de S3 + CloudFront). + +### Ejemplo mental simple +``` +Navegador/app ──▶ Nginx (443) ──▶ API SACC (8080) +``` + +### ¿Por qué Nginx y no Apache? +En `proyectosacc` se decidió usar **Nginx** porque: +- Consume menos memoria RAM. +- Es más rápido manejando muchas conexiones simultáneas. +- La configuración de proxy inverso es más sencilla. + +--- + +*Anterior: [`02-arquitectura-general.md`](02-arquitectura-general.md)* +*Siguiente: [`04-usuarios-y-permisos.md`](04-usuarios-y-permisos.md)* diff --git a/docs/04-usuarios-y-permisos.md b/docs/04-usuarios-y-permisos.md new file mode 100644 index 0000000..6bb9b4f --- /dev/null +++ b/docs/04-usuarios-y-permisos.md @@ -0,0 +1,134 @@ +# 04 - Usuarios y Permisos + +> Quién puede hacer qué en el servidor de `proyectosacc`. + +--- + +## 1. Usuarios del sistema + +En el servidor EC2 de `proyectosacc` existen tres usuarios principales. Cada uno tiene un trabajo específico y **no se mezclan los roles**. + +--- + +### 👤 `ubuntu` + +Este es el usuario administrador por defecto de Ubuntu en AWS. + +#### ¿Qué puede hacer? +- Todo. Es el usuario con permisos de `sudo` (superusuario). +- Se usa para configurar el servidor por primera vez. +- Puede instalar programas, crear otros usuarios y revisar logs del sistema. + +#### ¿Cuándo se usa? +- Durante el setup inicial del servidor. +- Para tareas de mantenimiento que requieren privilegios elevados. +- **No se usa para despliegues automáticos ni para ejecutar la aplicación.** + +--- + +### 👤 `thoth` + +Este es el usuario de **CI/CD** (Integración Continua / Despliegue Continuo). + +#### ¿Qué puede hacer? +- ✅ Descargar actualizaciones de los repositorios de código (solo lectura). +- ✅ Subir el frontend compilado al bucket S3 (`aws s3 sync`). +- ✅ Mover archivos compilados de la API (binarios, JARs, etc.) a los directorios correctos en la EC2. +- ✅ Hacer copias de seguridad (backups) de los binarios antes de actualizarlos. +- ✅ Ejecutar los scripts de despliegue de la API. + +#### ¿Qué NO puede hacer? +- ❌ Ejecutar la aplicación final en producción. Ese trabajo es de `osiris`. + +#### ¿Por qué existe? +Para que el pipeline de Bitbucket se conecte por SSH al servidor y haga el despliegue sin necesidad de usar el usuario administrador. + +--- + +### 👤 `osiris` + +Este es el usuario de **ejecución** de la aplicación. + +#### ¿Qué puede hacer? +- ✅ Iniciar y detener la aplicación SACC. +- ✅ Ejecutar los binarios finales (por ejemplo, archivos `.jar` de Java). +- ✅ Escribir logs en los directorios asignados. + +#### ¿Qué NO puede hacer? +- ❌ Realizar despliegues. +- ❌ Mover archivos de instalación. +- ❌ Acceder a repositorios de código. + +#### ¿Por qué existe? +Por seguridad. Si alguien compromete la aplicación, el atacante solo tendría los permisos de `osiris`, que son muy limitados. + +--- + +## 2. Separación de privilegios + +Este modelo se llama **SoD** (Segregation of Duties) o "Separación de Funciones". + +La idea es simple: **una sola persona (o usuario) no debe poder hacer todo**. + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ thoth │ │ osiris │ │ ubuntu │ +│ (CI/CD) │ │ (ejecución) │ │ (admin) │ +│ │ │ │ │ │ +│ Despliega │ ──▶ │ Ejecuta │ ◀── │ Administra │ +│ la app │ │ la app │ │ el server │ +└─────────────┘ └─────────────┘ └─────────────┘ +``` + +--- + +## 3. Llaves SSH + +Las **llaves SSH** son como un "carnet de identidad digital" que permite conectarse al servidor sin escribir una contraseña cada vez. +En `proyectosacc` usamos un **par de llaves SSH dedicado**, generado específicamente para este proyecto. No se usa contraseña ni herramientas como `sshpass`. + +### ¿Cómo funcionan? + +Una llave SSH siempre viene en **par**: +1. **Llave privada**: es secreta. Solo la tiene quien se va a conectar. +2. **Llave pública**: se puede compartir. Se coloca en el servidor para decirle "confía en quien tenga la llave privada que corresponde a esta pública". + +### Analogía simple +Imagina un candado con dos llaves: +- La **pública** es el candado mismo. Lo pones en la puerta del servidor. +- La **privada** es la llave que abre el candado. Solo tú la tienes. + +--- + +## 4. Dónde viven las llaves + +### Llave privada +- **Vive en**: las variables seguras de **Bitbucket Pipelines**. +- **Nombre de variable**: `PROYECTOSACC_SSH_KEY` +- **Formato**: codificada en **base64** (esto evita que Bitbucket modifique el formato). +- **Quién la usa**: el pipeline de Bitbucket cuando se conecta por SSH al servidor. + +### Llave pública +- **Vive en**: el archivo `~/.ssh/authorized_keys` del usuario `thoth` en la EC2. +- **Ruta completa**: `/home/thoth/.ssh/authorized_keys` +- **Función**: permite que el pipeline se conecte sin contraseña. + +### Llaves del servidor hacia Bitbucket +- El usuario `thoth` también tiene sus propias llaves SSH para clonar repositorios desde Bitbucket. +- **Ubicación**: `/home/thoth/.ssh/` +- **Ejemplo de nombre**: `proyectosacc_server_thoth_develop_TO_bitbucket_pipeline_ci_cd` + +--- + +## 5. Resumen rápido + +| Usuario | Rol principal | Llave SSH asociada | +|---------|---------------|-------------------| +| `ubuntu` | Administrador del servidor | Llave de acceso inicial a la EC2 (creada al lanzarla en AWS) | +| `thoth` | Despliegue automático (CI/CD) | `PROYECTOSACC_SSH_KEY` en Bitbucket Variables | +| `osiris` | Ejecutar la aplicación | No necesita llave SSH para conexiones externas | + +--- + +*Anterior: [`03-infraestructura-aws.md`](03-infraestructura-aws.md)* +*Siguiente: [`05-pipeline-bitbucket.md`](05-pipeline-bitbucket.md)* diff --git a/docs/05-pipeline-bitbucket.md b/docs/05-pipeline-bitbucket.md new file mode 100644 index 0000000..7fc7008 --- /dev/null +++ b/docs/05-pipeline-bitbucket.md @@ -0,0 +1,199 @@ +# 05 - Pipeline Bitbucket + +> Cómo funciona el pipeline de 7 pasos que despliega `proyectosacc`. + +--- + +## 1. ¿Qué es un pipeline? + +Un **pipeline** es una serie de pasos automáticos que se ejecutan cada vez que alguien sube código al repositorio. + +En Cómputo Contable Soft, todos los pipelines web usan **7 pasos** estandarizados. Esto significa que, sin importar el proyecto, siempre hay los mismos pasos con los mismos nombres. + +--- + +## 2. Los 7 pasos del pipeline + +### Paso 1: `01_image-setup` +**Prepara la computadora virtual donde se va a compilar todo.** + +Qué hace: +1. Actualiza los paquetes del sistema. +2. Instala herramientas necesarias: `openssh-client`, `openjdk-21-jdk`, `aws-cli`. +3. Configura las llaves SSH y los `known_hosts`. +4. Carga los scripts de utilería (`logger_bash.sh`, `telegram_alert.sh`). + +> 💡 **En palabras simples**: como cuando llegas a una cocina nueva y primero lavas los trastes, prendes la estufa y sacas los utensilios. + +--- + +### Paso 2: `02_repo-config` +**Descarga todos los repositorios que necesita la aplicación.** + +Qué hace: +1. Clona el repositorio principal de la aplicación. +2. Clona los repositorios de componentes compartidos (por ejemplo, `cmp-commons`, `cmp-security`). +3. Clona `ci-cd-commons` (scripts base de CCsoft). + +> 💡 **En palabras simples**: bajas todos los ingredientes de la receta antes de empezar a cocinar. + +--- + +### Paso 3: `03_dependencies` +**Compila o descarga todo lo que la aplicación necesita para funcionar.** + +Qué hace: +1. Compila las librerías compartidas. +2. Descarga dependencias de Maven, Gradle, npm, etc. +3. Publica las librerías locales si es necesario. + +> 💡 **En palabras simples**: preparas la masa, cortas las verduras y sacas los condimentos antes de armar el platillo final. + +--- + +### Paso 4: `04_build` +**Compila el frontend React y la API backend.** + +Qué hace: +1. **Frontend**: ejecuta `npm run build` (o `npm ci && npm run build`) para generar la carpeta `build/` con el React estático. +2. **Backend**: ejecuta el comando de build de la API (por ejemplo, `./gradlew clean bootJar` para Java). +3. Genera los archivos finales: la carpeta `build/` para el frontend y el `.jar` para la API. + +> 💡 **En palabras simples**: aquí cocinamos dos platillos: la entrada (frontend) y el plato fuerte (API). + +--- + +### Paso 5: `05_publish` +**Sube el frontend a S3 y el artefacto de la API a Amazon S3.** + +Qué hace: +1. **Frontend**: sincroniza la carpeta `build/` al bucket S3 designado para el sitio estático (`aws s3 sync build/ s3://bucket-name`). +2. **Backend**: sube el `.jar` (o artefacto equivalente) al bucket S3 para que la EC2 lo descargue después. +3. Esto evita transferir archivos grandes directamente por SSH y permite recuperar versiones anteriores fácilmente. + +> 💡 **En palabras simples**: pones la entrada en el mostrador (S3) y el plato fuerte en la bandeja de la cocina (S3) para servirlo después. + +--- + +### Paso 6: `06_install` +**Descarga el artefacto de la API en el servidor EC2.** + +Qué hace: +1. Se conecta por SSH al servidor EC2 como `thoth`. +2. Ejecuta un script de preparación que descarga el `.jar` desde **S3** y lo coloca en `/home/thoth/deploy/artifacts/current/`. +3. También hace una copia de seguridad de la versión anterior. +4. **Aún no reinicia el servicio**: eso ocurre en el paso 7. + +> 💡 **En palabras simples**: pones el plato fuerte en el plato y lo dejas listo, pero aún no lo sirves. + +--- + +### Paso 7: `07_deploy` +**Invalida la caché de CloudFront, despliega la API y verifica que todo esté saludable.** + +Qué hace: +1. **Invalida la caché de CloudFront** (`aws cloudfront create-invalidation`) para que los usuarios vean la nueva versión del frontend inmediatamente. +2. Se conecta por SSH al servidor EC2 como `thoth` y ejecuta `bash /home/thoth/deploy/setup/deploy.sh`. +3. El script `deploy.sh` crea o actualiza el servicio de `systemd`, detiene la versión anterior e inicia la nueva con el usuario `osiris`. +4. Ejecuta un **health check** en la API para verificar que responde correctamente. +5. Envía una notificación por Telegram con el resultado. + +> 💡 **En palabras simples**: actualizas el menú en todos los restaurantes (CloudFront) y sirves el plato fuerte calentito desde la cocina (EC2). + +--- + +## 3. Diagrama del pipeline + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ BITBUCKET PIPELINE │ +│ │ +│ 1. image-setup ▶ Instala herramientas │ +│ │ │ +│ ▼ │ +│ 2. repo-config ▶ Descarga repositorios │ +│ │ │ +│ ▼ │ +│ 3. dependencies ▶ Compila librerías y dependencias │ +│ │ │ +│ ▼ │ +│ 4. build ▶ Compila React frontend + API backend (.jar) │ +│ │ │ +│ ▼ │ +│ 5. publish ▶ Sube frontend a S3 web + API .jar a S3 artifacts │ +│ │ │ +│ ▼ │ +│ 6. install ▶ Prepara la API en el servidor EC2 │ +│ │ │ +│ ▼ │ +│ 7. deploy ▶ Invalida CloudFront, reinicia API y health check │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. ¿Cómo se conecta el pipeline a la EC2? + +La conexión se hace por **SSH** (Secure Shell). + +1. El pipeline tiene guardada la **llave privada** de `thoth` en una variable segura de Bitbucket. +2. Al llegar al paso `05_publish` o `06_install`, el pipeline decodifica la llave y la guarda en `/root/.ssh/sacc4_key`. +3. Ejecuta un comando `ssh` usando esa llave para ejecutar scripts remotos. +4. La **llave pública** correspondiente ya está en el archivo `~/.ssh/authorized_keys` del usuario `thoth` en la EC2. +5. ¡La puerta se abre y el pipeline puede ejecutar comandos en el servidor! + +### Ejemplo del comando que usa el pipeline +```bash +ssh -p 22 \ + -i /root/.ssh/sacc4_key \ + -o StrictHostKeyChecking=no \ + thoth@ \ + "bash /home/thoth/deploy/setup/deploy.sh" +``` + +--- + +## 5. ¿De dónde salen los secrets? + +Los **secrets** (contraseñas, llaves, tokens) viven en las **variables de repositorio** de Bitbucket. + +### Cómo se configuran +1. Entra al repositorio en Bitbucket. +2. Ve a **Repository settings > Pipelines > Repository variables**. +3. Agrega cada variable y márcala como **Secured** 🔒. +4. Las variables marcadas como secured se ofuscan en los logs (aparecen como `***`). + +### Variables principales + +| Variable | Descripción | +|----------|-------------| +| `PROYECTOSACC_SSH_KEY` | Llave privada SSH (en base64) dedicada para conectar al servidor | +| `PROYECTOSACC_SERVER_IP` | IP pública de la EC2 | +| `PROYECTOSACC_SSH_PORT` | Puerto SSH (normalmente `22`) | +| `PROYECTOSACC_SERVER_USER` | Usuario SSH para despliegue (`thoth`) | +| `PROYECTOSACC_TELEGRAM_BOT_TOKEN` | Token del bot de Telegram para alertas | +| `PROYECTOSACC_TELEGRAM_CHAT_ID` | ID del chat donde llegan las alertas | +| `AWS_ACCESS_KEY_ID` | Credencial de AWS para subir a S3 | +| `AWS_SECRET_ACCESS_KEY` | Credencial secreta de AWS | +| `CLOUDFRONT_DISTRIBUTION_ID` | ID de la distribución de CloudFront para invalidaciones | +| `S3_FRONTEND_BUCKET` | Nombre del bucket S3 donde se publica el frontend | + +### ⚠️ Regla de oro +**NUNCA** escribas secrets directamente en el código del pipeline (`bitbucket-pipelines.yml`). Siempre usa variables de entorno. + +--- + +## 6. Integración con proyectos CI/CD compartidos + +`proyectosacc` no trabaja solo. Se conecta con otros proyectos de CI/CD de la empresa que proveen piezas reutilizables: + +- **`ci-cd-commons`**: scripts base compartidos como `telegram_alert.sh` y `logger_bash.sh`. +- **`ci-cd-saac4`**: módulos de Terraform, patrones de infraestructura y scripts de despliegue específicos para la familia SACC. + +Esto significa que parte del pipeline y de los scripts de despliegue vienen de esos repositorios, no de este. Es como armar una casa con ladrillos que ya están hechos en otra fábrica. + +--- + +*Anterior: [`04-usuarios-y-permisos.md`](04-usuarios-y-permisos.md)* +*Siguiente: [`06-scripts-de-despliegue.md`](06-scripts-de-despliegue.md)* diff --git a/docs/06-scripts-de-despliegue.md b/docs/06-scripts-de-despliegue.md new file mode 100644 index 0000000..c157ce5 --- /dev/null +++ b/docs/06-scripts-de-despliegue.md @@ -0,0 +1,197 @@ +# 06 - Scripts de Despliegue + +> Qué hace cada script y cuándo se ejecuta. + +--- + +## 1. ¿Qué son los scripts de despliegue? + +Los **scripts** son archivos de texto con instrucciones que la computadora puede ejecutar automáticamente. + +En `proyectosacc` usamos scripts escritos en **Bash** (el lenguaje de comandos de Linux). Todos comienzan con esta línea mágica: + +```bash +set -euo pipefail +``` + +Esto significa tres cosas: +1. **`set -e`**: Si un comando falla, el script se detiene inmediatamente. +2. **`set -u`**: Si usas una variable que no existe, el script se detiene. +3. **`set -o pipefail`**: Si un comando dentro de una "tubería" (`|`) falla, el script también se detiene. + +> 💡 **En palabras simples**: es como poner el cinturón de seguridad antes de manejar. + +--- + +### 1.1. Scripts provenientes de otros proyectos CI/CD + +`proyectosacc` reutiliza scripts y utilerías que viven en repositorios compartidos: + +- **`ci-cd-commons`**: contiene `telegram_alert.sh`, `logger_bash.sh` y otras utilerías base que usan todos los pipelines de la empresa. +- **`ci-cd-saac4`**: provee módulos de Terraform y scripts de despliegue adaptados para la familia SACC. + +Estos repositorios se clonan dentro de la carpeta `/home/thoth/deploy/` (por ejemplo, `/home/thoth/deploy/ci-cd-commons/`) para que los scripts locales puedan usarlos. + +### 1.2. Nuevo script: `deploy-frontend-s3.sh` + +Este script se ejecuta **desde el pipeline de Bitbucket** (no dentro de la EC2). Su trabajo es subir el frontend React a S3. + +Qué hace: +1. Sincroniza la carpeta `build/` con el bucket S3 (`aws s3 sync build/ s3://bucket-name --delete`). +2. Espera a que la sincronización termine. + +> 💡 **Nota**: la invalidación de la caché de CloudFront se hace en el **paso 7** del pipeline (`07_deploy`), después de confirmar que la API está saludable. Así evitamos refrescar el frontend antes de que el backend esté listo. + +Ejemplo de comando: +```bash +bash /home/thoth/deploy/scripts/deploy-frontend-s3.sh +``` + +--- + +## 2. `deploy.sh` + +### ¿Qué hace? +Este es el script principal de despliegue de la **API backend**. Se ejecuta **dentro del servidor EC2** y se encarga de instalar la nueva versión del servicio. + +### Pasos que sigue +1. **Crear directorios**: verifica que existan las carpetas necesarias (`backup`, `current`, `pids`, `logs`). +2. **Detener servicio anterior**: si la aplicación ya está corriendo, la detiene con `systemctl`. +3. **Configurar systemd**: crea o actualiza el archivo de servicio para que la app inicie automáticamente. +4. **Recargar systemd**: ejecuta `sudo systemctl daemon-reload`. +5. **Iniciar servicio**: ejecuta `sudo systemctl start `. +6. **Health check**: hace peticiones a `http://localhost:8080/actuator/health` para verificar que la app responde. +7. **Guardar PID**: anota el ID del proceso para poder monitorearlo después. + +### ¿Cuándo se llama? +- En el **paso 7** del pipeline (`07_deploy`). +- También puede ejecutarse manualmente si necesitas hacer un despliegue fuera del pipeline. + +### Ejemplo de comando +```bash +bash /home/thoth/deploy/setup/deploy.sh +``` + +--- + +## 3. `health-check.sh` + +### ¿Qué hace? +Verifica que la aplicación esté viva y respondiendo correctamente. + +### Cómo funciona +1. Intenta conectarse a la URL de salud de la aplicación: + ``` + http://localhost:8080/actuator/health + ``` +2. Si la respuesta contiene `"status":"UP"`, todo está bien. +3. Si no responde después de varios intentos, reporta un error. + +### ¿Cuándo se llama? +- **Dentro de `deploy.sh`**, inmediatamente después de iniciar el servicio. +- También puede usarse manualmente para revisar si la app sigue funcionando horas después del despliegue. + +### Ejemplo de comando manual +```bash +bash /home/thoth/deploy/scripts/health-check.sh +``` + +--- + +## 4. `rollback.sh` + +### ¿Qué hace? +Regresa la API backend a la versión anterior si el nuevo despliegue falló. También puede restaurar el frontend en S3 si se habilitó **versionamiento de objetos** en el bucket. + +### Pasos que sigue (API en EC2) +1. Busca el último backup en la carpeta `/home/thoth/deploy/artifacts/backup/`. +2. Detiene el servicio actual. +3. Copia el backup a la carpeta `current`. +4. Reinicia el servicio con la versión anterior. +5. Ejecuta un health check para confirmar que la versión anterior todavía funciona. + +### Pasos que sigue (frontend en S3) +Si el frontend también necesita rollback: +1. Identifica la versión anterior del archivo en S3 usando el versionamiento del bucket. +2. Restaura la versión anterior de `index.html` y los assets estáticos. +3. Invalida la caché de CloudFront para que los usuarios vean la versión anterior. + +### ¿Cuándo se llama? +- Cuando el `deploy.sh` o el `health-check.sh` reportan un fallo en la API. +- Manualmente, si descubres que la nueva versión del frontend o la API tiene un bug grave. + +### Ejemplo de comando +```bash +bash /home/thoth/deploy/scripts/rollback.sh +``` + +--- + +## 5. `notify-telegram.sh` + +### ¿Qué hace? +Envía mensajes a un grupo o chat de Telegram para informar sobre el estado del pipeline. + +### Qué tipo de mensajes envía +- 🟢 **Éxito**: "El despliegue de proyectosacc terminó correctamente." +- 🔴 **Error**: "El pipeline de proyectosacc falló en el paso 4. Revisar logs." +- 🟡 **Advertencia**: "Health check falló. Iniciando rollback automático." + +### ¿Cuándo se llama? +- Al **inicio** del pipeline. +- Al **final** del pipeline (éxito o fracaso). +- Cuando ocurre un error en cualquier paso. + +### Variables necesarias +```bash +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHAT_ID= +``` + +### Ejemplo de comando +```bash +bash /home/thoth/deploy/ci-cd-commons/telegram_alert.sh "Despliegue exitoso" +``` + +--- + +## 6. Resumen de cuándo usar cada script + +| Script | ¿Cuándo se ejecuta? | ¿Dónde corre? | +|--------|---------------------|---------------| +| `deploy-frontend-s3.sh` | Paso 5 del pipeline (subir React a S3) | Pipeline (Bitbucket) | +| `deploy.sh` | Paso 7 del pipeline (desplegar API en la EC2), o manualmente | Dentro de la EC2 | +| `health-check.sh` | Después de `deploy.sh`, o manualmente | Dentro de la EC2 | +| `rollback.sh` | Cuando `deploy.sh` o `health-check.sh` fallan, o manualmente | Pipeline o EC2 | +| `notify-telegram.sh` | Inicio, fin o error del pipeline | Pipeline (Bitbucket) o EC2 | + +--- + +## 7. Dato extra: ¿dónde viven los scripts? + +En el servidor EC2, los scripts se organizan así: + +``` +/home/thoth/deploy/ +├── artifacts/ +│ ├── backup/ ← versiones anteriores de la API +│ ├── current/ ← versión activa de la API +│ ├── logs/ ← logs del pipeline +│ └── pids/ ← archivos con el ID de proceso +├── ci-cd-commons/ +│ ├── telegram_alert.sh +│ └── logger_bash.sh +├── scripts/ +│ ├── deploy-frontend-s3.sh ← sube React a S3 +│ ├── health-check.sh +│ └── rollback.sh +└── setup/ + └── deploy.sh ← script principal del pipeline para la API (llamado vía SSH) +``` + +> 💡 **Nota**: el artefacto de la API que termina en `artifacts/current/` se descarga desde **Amazon S3** durante el paso de instalación del pipeline. No se transfiere directamente por SSH. El frontend React se sube directamente a S3 desde el pipeline. + +--- + +*Anterior: [`05-pipeline-bitbucket.md`](05-pipeline-bitbucket.md)* +*Siguiente: [`07-terraform-iac.md`](07-terraform-iac.md)* diff --git a/docs/07-terraform-iac.md b/docs/07-terraform-iac.md new file mode 100644 index 0000000..309b9b9 --- /dev/null +++ b/docs/07-terraform-iac.md @@ -0,0 +1,322 @@ +# 07 - Terraform (IaC) + +> Cómo creamos la infraestructura de `proyectosacc` con código. + +--- + +## 1. ¿Qué es Terraform? + +**Terraform** es una herramienta que permite crear infraestructura en la nube escribiendo código en lugar de hacer clic en la consola de AWS. + +A esto se le llama **IaC** (Infrastructure as Code) o "Infraestructura como Código". + +### Ventajas de usar Terraform +- ✅ **Repetible**: puedes crear la misma infraestructura 10 veces y siempre será idéntica. +- ✅ **Versionable**: el código se guarda en Git, así que sabes quién hizo qué cambio y cuándo. +- ✅ **Rápido**: un solo comando crea servidor, base de datos, DNS y seguridad. +- ✅ **Sin sorpresas**: todo está documentado en archivos de texto. + +--- + +## 2. ¿Qué crea Terraform en proyectosacc? + +Terraform se encarga de crear todos estos recursos en AWS: + +1. **VPC y subredes**: la red virtual donde vive todo. +2. **Security Groups**: las reglas de firewall. +3. **EC2 T3.small**: el servidor de la **API backend**. +4. **RDS MariaDB**: la base de datos gestionada. +5. **S3 Bucket (artefactos)**: el almacén de archivos de la API. +6. **S3 Bucket (frontend)**: el sitio web estático que sirve el React app. +7. **CloudFront Distribution**: la CDN que distribuye el frontend globalmente con HTTPS. +8. **Route 53 Record**: el dominio `sacc.ccsoft.mx` apunta a CloudFront. +9. **ACM Certificate**: certificado SSL para CloudFront. +10. **IAM Role**: permisos para que la EC2 y CloudFront accedan a S3. + +--- + +## 3. Archivos principales de Terraform + +### `main.tf` +Este es el archivo central. Aquí se definen los recursos que Terraform va a crear. + +Ejemplo de la EC2 para la API: +```hcl +resource "aws_instance" "sacc_api" { + ami = "ami-0c02fb55956c7d316" # Ubuntu 22.04 + instance_type = "t3.small" + + vpc_security_group_ids = [aws_security_group.sacc_sg.id] + + user_data = file("user-data.sh") + + tags = { + Name = "proyectosacc-api" + Environment = "develop" + Project = "proyectosacc" + } +} +``` + +Ejemplo del bucket S3 para el frontend React: +```hcl +resource "aws_s3_bucket" "sacc_frontend" { + bucket = "ccsoft-proyectosacc-frontend" +} + +resource "aws_s3_bucket_website_configuration" "sacc_frontend" { + bucket = aws_s3_bucket.sacc_frontend.id + + index_document { + suffix = "index.html" + } + + error_document { + key = "index.html" + } +} +``` + +Ejemplo de CloudFront: +```hcl +resource "aws_cloudfront_distribution" "sacc_cdn" { + enabled = true + default_root_object = "index.html" + aliases = ["sacc.ccsoft.mx"] + + # Origin 1: S3 bucket para el frontend React + origin { + domain_name = aws_s3_bucket.sacc_frontend.bucket_regional_domain_name + origin_id = "saccS3Origin" + + s3_origin_config { + origin_access_identity = aws_cloudfront_origin_access_identity.sacc_oai.cloudfront_access_identity_path + } + } + + # Origin 2: EC2 para la API backend + origin { + domain_name = aws_instance.sacc_api.public_dns + origin_id = "saccApiOrigin" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["TLSv1.2"] + } + } + + default_cache_behavior { + target_origin_id = "saccS3Origin" + viewer_protocol_policy = "redirect-to-https" + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + } + + # API requests go to the EC2 origin + ordered_cache_behavior { + path_pattern = "/api/*" + target_origin_id = "saccApiOrigin" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD"] + viewer_protocol_policy = "redirect-to-https" + + forwarded_values { + query_string = true + headers = ["Origin", "Access-Control-Request-Headers", "Access-Control-Request-Method"] + cookies { + forward = "all" + } + } + } + + viewer_certificate { + acm_certificate_arn = aws_acm_certificate.sacc_cert.arn + ssl_support_method = "sni-only" + minimum_protocol_version = "TLSv1.2_2021" + } +} +``` + +> 💡 **¿Por qué dos origins?** CloudFront sirve el frontend desde S3 por defecto. Pero cuando el navegador pide algo bajo `/api/*`, CloudFront lo envía a la EC2 donde Nginx lo redirige al backend en el puerto 8080. Así todo comparte el mismo dominio `sacc.ccsoft.mx`. + +--- + +### `variables.tf` +Aquí se definen las variables que personalizan la infraestructura. + +Ejemplo: +```hcl +variable "region" { + description = "Región de AWS" + type = string + default = "us-east-1" +} + +variable "instance_type" { + description = "Tipo de instancia EC2" + type = string + default = "t3.small" +} + +variable "db_password" { + description = "Contraseña de la base de datos" + type = string + sensitive = true +} +``` + +> ⚠️ La variable `db_password` tiene `sensitive = true` para que no aparezca en los logs. + +--- + +### `user-data.sh` +Este archivo contiene los comandos que la EC2 ejecuta la **primera vez** que se enciende. + +Ejemplo de lo que hace: +1. Actualiza el sistema operativo. +2. Instala Nginx. +3. Instala Java (para correr la aplicación SACC). +4. Instala AWS CLI. +5. Crea los usuarios `thoth` y `osiris`. +6. Inyecta la **llave pública SSH dedicada** para el pipeline en `~/.ssh/authorized_keys` del usuario `thoth`. +7. Crea los directorios necesarios para el despliegue. + +```bash +#!/bin/bash +set -euo pipefail + +# Actualizar sistema +apt-get update -y +apt-get install -y nginx openjdk-21-jdk awscli + +# Crear usuarios +useradd -m -s /bin/bash thoth || true +useradd -m -s /bin/bash osiris || true + +# Crear directorios de despliegue +mkdir -p /home/thoth/deploy/artifacts/{backup,current,logs,pids} +chown -R thoth:thoth /home/thoth/deploy + +# Configurar Nginx como proxy SOLO para la API (no para el frontend) +cat > /etc/nginx/sites-available/proyectosacc-api <<'EOF' +server { + listen 80; + server_name _; + + location /api/ { + proxy_pass http://localhost:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +EOF +ln -sf /etc/nginx/sites-available/proyectosacc-api /etc/nginx/sites-enabled/default + +# Iniciar Nginx +systemctl enable nginx +systemctl start nginx +``` + +--- + +### `outputs.tf` +Aquí definimos qué información queremos que Terraform nos muestre al final. + +Ejemplo: +```hcl +output "ec2_public_ip" { + description = "IP pública del servidor de la API" + value = aws_instance.sacc_api.public_ip +} + +output "rds_endpoint" { + description = "Dirección de conexión a la base de datos" + value = aws_db_instance.sacc_db.endpoint +} + +output "s3_artifacts_bucket" { + description = "Nombre del bucket de artefactos de la API" + value = aws_s3_bucket.artifacts.bucket +} + +output "s3_frontend_bucket" { + description = "Nombre del bucket del frontend React" + value = aws_s3_bucket.sacc_frontend.bucket +} + +output "cloudfront_domain" { + description = "Dominio de CloudFront" + value = aws_cloudfront_distribution.sacc_cdn.domain_name +} +``` + +Después de ejecutar `terraform apply`, verás estos valores en la pantalla. + +--- + +## 4. Cómo ejecutar Terraform paso a paso + +### Paso 1: Ir a la carpeta de Terraform +```bash +cd /home/evert/Servidores/Nuve/AWS/proyectosacc/terraform +``` + +### Paso 2: Inicializar Terraform +```bash +terraform init +``` +Esto descarga los plugins necesarios para hablar con AWS. + +### Paso 3: Revisar el plan +```bash +terraform plan +``` +Terraform te muestra **todo lo que va a crear, modificar o destruir**. Revísalo con cuidado. + +### Paso 4: Aplicar los cambios +```bash +terraform apply +``` +Terraform te pedirá que escribas `yes` para confirmar. Luego empezará a crear los recursos en AWS. + +### Paso 5: Ver los outputs +Al finalizar, Terraform mostrará los valores definidos en `outputs.tf`: +- IP pública de la EC2 (API backend) +- Endpoint de la base de datos +- Nombre del bucket S3 de artefactos +- Nombre del bucket S3 del frontend +- Dominio de CloudFront + +--- + +## 5. Comandos útiles adicionales + +| Comando | Para qué sirve | +|---------|----------------| +| `terraform validate` | Revisa que la sintaxis de los archivos esté correcta. | +| `terraform fmt` | Ordena y da formato a los archivos `.tf`. | +| `terraform show` | Muestra el estado actual de la infraestructura. | +| `terraform destroy` | **Elimina** todos los recursos creados. ¡Úsalo con cuidado! | + +--- + +## 6. Checklist antes de ejecutar Terraform + +- [ ] Tienes configuradas las credenciales de AWS (`aws configure`). +- [ ] La variable `db_password` no está escrita en el código, viene de un archivo seguro o variable de entorno. +- [ ] Revisaste el `terraform plan` antes de aplicar. +- [ ] Sabes en qué región de AWS se va a crear todo (`us-east-1`, `us-west-2`, etc.). +- [ ] El bucket S3 para el estado remoto ya existe (si usas backend remoto). +- [ ] El `user_data.sh` incluye la llave pública SSH dedicada para el pipeline. +- [ ] El bucket S3 del frontend tiene habilitado el versionamiento de objetos (para rollback del frontend). +- [ ] CloudFront tiene configurado el Origin Access Identity (OAI) u Origin Access Control (OAC) para leer de S3. +- [ ] Route 53 apunta a CloudFront, no a la IP de la EC2. +- [ ] CloudFront tiene un behavior `/api/*` que apunta a la EC2. +- [ ] La EC2 tiene el Security Group configurado para recibir tráfico HTTP desde CloudFront. + +--- + +*Anterior: [`06-scripts-de-despliegue.md`](06-scripts-de-despliegue.md)* +*Siguiente: [`08-seguridad-y-secretos.md`](08-seguridad-y-secretos.md)* diff --git a/docs/08-seguridad-y-secretos.md b/docs/08-seguridad-y-secretos.md new file mode 100644 index 0000000..89beed2 --- /dev/null +++ b/docs/08-seguridad-y-secretos.md @@ -0,0 +1,205 @@ +# 08 - Seguridad y Secretos + +> Cómo protegemos las contraseñas, llaves y datos sensibles de `proyectosacc`. + +--- + +## 1. ¿Qué es un "secreto"? + +Un **secreto** es cualquier información que no debe ser pública. En `proyectosacc`, los secrets incluyen: + +- Contraseñas de bases de datos. +- Llaves SSH privadas. +- Tokens de Telegram. +- Credenciales de AWS (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`). + +> 💡 **Regla de oro**: si perder esa información causaría un problema de seguridad, es un secreto. + +--- + +## 2. ¿Dónde viven los secrets? + +### Opción 1: Bitbucket Repository Variables (la más usada) + +En Bitbucket Pipelines podemos guardar variables de entorno que los scripts usan sin exponer su valor. + +**Cómo configurarlas:** +1. Ve al repositorio en Bitbucket. +2. Entra a **Repository settings > Pipelines > Repository variables**. +3. Agrega el nombre y el valor. +4. **Marca la casilla "Secured"** 🔒. + +**Ventaja**: cuando el pipeline corre, las variables secured aparecen como `***` en los logs. Nadie puede leerlas. + +### Opción 2: AWS Secrets Manager + +AWS tiene un servicio llamado **Secrets Manager** que guarda secrets de forma segura y permite rotarlos automáticamente. + +**Ejemplo de uso en un pipeline de CodeBuild:** +```yaml +env: + secrets-manager: + DB_PASSWORD: "prod/database/password" +``` + +### Opción 3: AWS Systems Manager Parameter Store + +Similar a Secrets Manager, pero más simple y económico. Se usa para guardar configuraciones sensibles. + +```yaml +env: + parameter-store: + DATABASE_URL: "/app/database/url" +``` + +### Opción 4: Archivo `.env` (solo local) + +En tu computadora personal puedes tener un archivo `.env` con variables locales. **Este archivo NUNCA debe subirse al repositorio.** + +El archivo `.gitignore` del proyecto ya debería incluir `.env` para evitar accidentes. + +--- + +## 3. ¿Qué NUNCA poner en el repositorio? + +### ❌ NUNCA hagas esto + +```yaml +# Mal ejemplo en bitbucket-pipelines.yml +- step: + script: + - export DB_PASSWORD="supersecreta123" + - export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +``` + +```hcl +# Mal ejemplo en Terraform +resource "aws_db_instance" "example" { + password = "mi-contrasena-123" +} +``` + +```bash +# Mal ejemplo en un script +API_KEY="sk-1234567890abcdef" +``` + +### ✅ SIEMPRE haz esto + +```yaml +# Bien: usa variables de entorno +- step: + script: + - export DB_PASSWORD="$DB_PASSWORD" +``` + +```hcl +# Bien: usa variables de Terraform +resource "aws_db_instance" "example" { + password = var.db_password +} +``` + +```bash +# Bien: lee desde una variable de entorno +API_KEY="$API_KEY" +``` + +--- + +## 4. ¿Cómo rotar las llaves SSH? + +**Rotar** una llave significa crear una nueva y dejar de usar la anterior. Es una buena práctica de seguridad, especialmente si alguien con acceso a la llave privada deja la empresa. + +### Paso 1: Generar un nuevo par de llaves +```bash +ssh-keygen -t ed25519 -b 4096 \ + -C "bitbucket.pipeline.ci.cd.proyectosacc.thoth.develop@computocontable.com" \ + -f bitbucket_pipeline_ci_cd_TO_proyectosacc_server_thoth_develop +``` + +### Paso 2: Codificar la llave privada en base64 +```bash +base64 -i bitbucket_pipeline_ci_cd_TO_proyectosacc_server_thoth_develop | tr -d '\n' > nueva_llave.b64 +``` + +### Paso 3: Actualizar la variable en Bitbucket +1. Copia el contenido de `nueva_llave.b64`. +2. Ve a **Repository settings > Pipelines > Repository variables**. +3. Actualiza la variable `PROYECTOSACC_SSH_KEY` con el nuevo valor. + +### Paso 4: Colocar la llave pública en el servidor +```bash +# Copia el contenido del archivo .pub +cat bitbucket_pipeline_ci_cd_TO_proyectosacc_server_thoth_develop.pub + +# Conéctate al servidor y agrégala a authorized_keys +ssh ubuntu@ +sudo su - thoth +nano ~/.ssh/authorized_keys +# Pega la nueva llave pública al final del archivo + +> 💡 **Tip**: en un despliegue automatizado con Terraform, esta llave pública se inyecta directamente desde el archivo `user_data`. No hace falta editar `authorized_keys` a mano. + +### Paso 5: Probar la conexión +Ejecuta un pipeline de prueba o conéctate manualmente con la nueva llave privada para confirmar que funciona. + +### Paso 6: Eliminar la llave anterior +Una vez confirmado que la nueva llave funciona: +1. Borra la llave pública antigua de `~/.ssh/authorized_keys`. +2. Borra la llave privada antigua de tu computadora. +3. Actualiza cualquier otra copia que exista. + +--- + +## 5. Seguridad del frontend en S3 + CloudFront + +### Bucket policy para CloudFront (OAI) +El bucket S3 del frontend NO debe ser público. Solo CloudFront puede leer los objetos: + +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Sid": "AllowCloudFrontOAI", + "Effect": "Allow", + "Principal": { + "CanonicalUser": "" + }, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::ccsoft-proyectosacc-frontend/*" + }] +} +``` + +### CORS en S3 (si el frontend llama a la API) +Como el frontend React y la API backend comparten el mismo dominio `https://sacc.ccsoft.mx` (gracias al behavior `/api/*` de CloudFront), **no hay problema de CORS**. Si en el futuro decides mover la API a un subdominio diferente (por ejemplo, `api.sacc.ccsoft.mx`), ahí sí necesitarás configurar CORS en el backend. + +### Versionamiento de objetos en S3 +Habilita el versionamiento en el bucket del frontend. Así puedes restaurar una versión anterior del sitio si un deploy falla: +```bash +aws s3api put-bucket-versioning \ + --bucket ccsoft-proyectosacc-frontend \ + --versioning-configuration Status=Enabled +``` + +--- + +## 6. Checklist de seguridad básica + +- [ ] El archivo `.env` está en `.gitignore`. +- [ ] Ningún archivo del repositorio contiene contraseñas escritas directamente. +- [ ] Las variables de Bitbucket están marcadas como **Secured**. +- [ ] La llave privada SSH dedicada para `proyectosacc` está en formato **base64** en Bitbucket. +- [ ] No se usa acceso SSH por contraseña ni herramientas como `sshpass`. +- [ ] El puerto SSH (22) de la EC2 solo está abierto a IPs conocidas. +- [ ] La base de datos RDS no es accesible desde internet (solo desde la VPC). +- [ ] Los certificados SSL están configurados correctamente en **CloudFront** (HTTPS obligatorio). +- [ ] El bucket S3 del frontend NO es público; solo CloudFront tiene acceso mediante OAI/OAC. +- [ ] El versionamiento de objetos está habilitado en el bucket S3 del frontend. +- [ ] Se ha programado una rotación de llaves SSH al menos una vez al año. + +--- + +*Anterior: [`07-terraform-iac.md`](07-terraform-iac.md)* +*Siguiente: [`09-runbook-deploy.md`](09-runbook-deploy.md)* diff --git a/docs/09-runbook-deploy.md b/docs/09-runbook-deploy.md new file mode 100644 index 0000000..34dbd39 --- /dev/null +++ b/docs/09-runbook-deploy.md @@ -0,0 +1,269 @@ +# 09 - Runbook: Deploy Manual + +> Qué hacer si el pipeline automático falla y necesitas desplegar a mano. + +--- + +## 1. ¿Cuándo hacer un deploy manual? + +Un **deploy manual** significa que tú, persona humana, ejecutas los pasos que normalmente hace el pipeline automático. + +Haz un deploy manual cuando: +- 🔴 El pipeline de Bitbucket falló y no se puede reintentar. +- 🟡 Necesitas desplegar una corrección urgente fuera de horario. +- 🟠 El servidor EC2 fue recreado y el pipeline no está configurado todavía. + +--- + +## 2. Paso a paso: deploy manual + +Un deploy manual ahora tiene **dos partes**: subir el frontend a S3 + CloudFront, y desplegar la API en la EC2. + +--- + +### PARTE A: Subir el frontend React a S3 e invalidar CloudFront + +Esta parte puedes hacerla desde tu computadora local o desde cualquier máquina con AWS CLI configurada. + +#### Paso A1: Ve a la carpeta del frontend compilado + +```bash +cd /ruta/al/proyecto/frontend/build +``` + +Si no has compilado el frontend aún: +```bash +npm ci +npm run build +``` + +#### Paso A2: Sincroniza la carpeta `build/` con S3 + +```bash +aws s3 sync . s3://ccsoft-proyectosacc-frontend --delete +``` + +#### Paso A3: Invalida la caché de CloudFront + +```bash +aws cloudfront create-invalidation \ + --distribution-id \ + --paths "/*" +``` + +> 💡 **Nota**: el `DISTRIBUTION_ID` lo encuentras en la consola de AWS o en los outputs de Terraform. + +#### Paso A4: Verifica el frontend + +Abre tu navegador y visita: +``` +https://sacc.ccsoft.mx +``` + +Si ves la interfaz de SACC, la parte A fue exitosa. + +--- + +### PARTE B: Desplegar la API backend en la EC2 + +#### Paso B1: Conéctate al servidor EC2 + +Necesitas la IP pública de la instancia y la **llave SSH dedicada** generada para `proyectosacc`. + +```bash +ssh -i ~/.ssh/tu_llave.pem ubuntu@ +``` + +O si ya estás dentro de la red de la empresa: + +```bash +ssh thoth@ +``` + +--- + +#### Paso B2: Conviértete en el usuario thoth + +Si entraste como `ubuntu`, cambia al usuario de despliegue: + +```bash +sudo su - thoth +``` + +--- + +#### Paso B3: Ve al directorio de despliegue + +```bash +cd /home/thoth/deploy +``` + +--- + +#### Paso B4: Descarga el artefacto compilado de la API + +Si el artefacto está en S3, descárgalo: + +```bash +aws s3 cp s3://ccsoft-artifacts-sacc4/develop/proyectosacc-app-1.0.0.jar \ + /home/thoth/deploy/artifacts/current/ +``` + +Si no tienes acceso a S3, pide el archivo `.jar` a un compañero y súbelo con `scp`. + +--- + +#### Paso B5: Ejecuta el script de deploy de la API + +```bash +bash /home/thoth/deploy/setup/deploy.sh +``` + +Este script hará lo siguiente automáticamente: +1. Hará backup de la versión anterior. +2. Detendrá el servicio actual. +3. Copiará el nuevo archivo a su lugar. +4. Configurará y recargará `systemd`. +5. Iniciará el nuevo servicio. +6. Hará un health check. + +--- + +#### Paso B6: Verifica que el servicio esté corriendo + +```bash +sudo systemctl status proyectosacc-app.service +``` + +Deberías ver algo como: +``` +● proyectosacc-app.service - Proyecto SACC App Service + Loaded: loaded (/etc/systemd/system/proyectosacc-app.service; enabled) + Active: active (running) since Mon 2026-04-14 10:30:00 UTC +``` + +Si dice `active (running)`, ¡excelente! + +--- + +#### Paso B7: Verifica el health check de la API + +```bash +curl http://localhost:8080/actuator/health +``` + +La respuesta debe incluir: +```json +{"status":"UP"} +``` + +--- + +#### Paso B8: Verifica la API desde fuera + +Desde tu navegador o con `curl`, prueba un endpoint de la API: +```bash +curl https://sacc.ccsoft.mx/api/actuator/health +``` + +Si responde correctamente, el deploy manual completo fue exitoso. + +--- + +## 3. Cómo revisar si el deploy funcionó + +### Frontend (S3 + CloudFront) +- Abre `https://sacc.ccsoft.mx` en el navegador. +- Revisa la consola de AWS > S3 para ver que los archivos nuevos estén en el bucket. +- Verifica el estado de la invalidación de CloudFront en la consola de AWS. + +### API (EC2) + +#### Revisar los logs del servicio +```bash +sudo journalctl -u proyectosacc-app.service -n 50 --no-pager +``` + +#### Revisar el archivo de log de la aplicación +```bash +tail -n 50 /var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log +``` + +#### Revisar que Nginx esté funcionando +```bash +sudo systemctl status nginx +``` + +#### Revisar que la API responda en el puerto correcto +```bash +ss -tlnp | grep 8080 +``` + +--- + +## 4. Errores comunes y cómo arreglarlos + +### Error 1: "Permission denied" al conectarse por SSH +**Causa**: la llave SSH no tiene los permisos correctos o no está en `authorized_keys`. + +**Solución**: +```bash +# En tu computadora local +chmod 600 ~/.ssh/tu_llave.pem + +# En el servidor, como thoth +chmod 700 ~/.ssh +chmod 600 ~/.ssh/authorized_keys +``` + +--- + +### Error 2: "No such file or directory" al ejecutar deploy.sh +**Causa**: el script no está donde esperas o la ruta es incorrecta. + +**Solución**: +```bash +# Busca el script +find /home/thoth -name "deploy.sh" + +# Luego ejecútalo con la ruta completa +bash /home/thoth/deploy/setup/deploy.sh +``` + +--- + +### Error 3: El servicio no inicia (failed) +**Causa**: puede ser que falta Java, que el puerto 8080 ya está ocupado, o que hay un error en el archivo `.jar`. + +**Solución**: +```bash +# Ver qué dice el log +sudo journalctl -u proyectosacc-app.service -n 100 --no-pager + +# Ver si el puerto 8080 está ocupado +sudo lsof -i :8080 + +# Verificar que Java esté instalado +java -version +``` + +--- + +### Error 4: Health check falla después del deploy +**Causa**: la aplicación inició pero no responde correctamente, o tarda más de lo esperado. + +**Solución**: +```bash +# Espera unos segundos más y vuelve a intentar +curl -v http://localhost:8080/actuator/health + +# Revisa si hay errores en el log de la app +tail -n 100 /var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log +``` + +Si sigue fallando, considera ejecutar el **rollback** (ver [`10-runbook-rollback.md`](10-runbook-rollback.md)). + +--- + +*Anterior: [`08-seguridad-y-secretos.md`](08-seguridad-y-secretos.md)* +*Siguiente: [`10-runbook-rollback.md`](10-runbook-rollback.md)* diff --git a/docs/10-runbook-rollback.md b/docs/10-runbook-rollback.md new file mode 100644 index 0000000..8296519 --- /dev/null +++ b/docs/10-runbook-rollback.md @@ -0,0 +1,235 @@ +# 10 - Runbook: Rollback + +> Cómo regresar a la versión anterior de la aplicación cuando algo sale mal. + +--- + +## 1. ¿Qué es un rollback? + +Un **rollback** es el proceso de "deshacer" un despliegue. Si la nueva versión de la aplicación tiene errores, el rollback vuelve a poner la versión anterior para que los usuarios no se vean afectados. + +> 💡 **Analogía simple**: es como cuando instalas una actualización en tu celular y algo deja de funcionar, así que regresas a la versión anterior del sistema. + +--- + +## 2. ¿Cuándo hacer un rollback? + +Haz rollback **inmediatamente** si ves alguna de estas situaciones: + +- 🔴 El **health check** falla después del despliegue. +- 🔴 La aplicación se cae al poco tiempo de iniciar. +- 🟡 Los usuarios reportan errores graves que no existían antes. +- 🟡 El pipeline detectó un fallo en el paso de deploy. +- 🟠 El servicio de `systemd` no puede arrancar. + +> ⚠️ **Regla de oro**: si dudas, haz rollback. Es mejor tener la versión anterior estable mientras investigas el problema. + +--- + +## 3. Paso a paso: cómo hacer rollback + +El rollback ahora tiene **dos partes**: el frontend en S3 y la API en la EC2. + +--- + +### PARTE A: Rollback del frontend en S3 + +Si el problema es visual (interfaz rota, pantalla en blanco, etc.), restaura el frontend anterior. + +#### Paso A1: Lista las versiones anteriores del archivo `index.html` + +```bash +aws s3api list-object-versions \ + --bucket ccsoft-proyectosacc-frontend \ + --prefix index.html \ + --query 'Versions[?IsLatest==`false`].[VersionId,LastModified]' \ + --output table +``` + +#### Paso A2: Restaura la versión anterior + +Copia el `VersionId` de la versión anterior y restáurala: + +```bash +aws s3api copy-object \ + --bucket ccsoft-proyectosacc-frontend \ + --copy-source ccsoft-proyectosacc-frontend/index.html?versionId= \ + --key index.html +``` + +#### Paso A3: Invalida la caché de CloudFront + +```bash +aws cloudfront create-invalidation \ + --distribution-id \ + --paths "/*" +``` + +#### Paso A4: Verifica el frontend + +Abre `https://sacc.ccsoft.mx` y confirma que la interfaz anterior ha vuelto. + +--- + +### PARTE B: Rollback de la API en la EC2 + +Si el problema es del backend (errores 500, API caída, etc.), restaura la API. + +#### Paso B1: Conéctate al servidor EC2 + +```bash +ssh -i ~/.ssh/tu_llave.pem ubuntu@ +``` + +#### Paso B2: Cambia al usuario thoth + +```bash +sudo su - thoth +``` + +#### Paso B3: Ve al directorio de artefactos + +```bash +cd /home/thoth/deploy/artifacts +``` + +#### Paso B4: Revisa los backups disponibles + +```bash +ls -lah backup/ +``` + +Verás algo como: +``` +proyectosacc-app-20260414_103000.jar +proyectosacc-app-20260413_154500.jar +``` + +Cada archivo tiene una fecha y hora en su nombre. + +#### Paso B5: Identifica cuál es la versión anterior estable + +Normalmente es el backup **más reciente antes del deploy fallido**. + +```bash +# Ordena por fecha para ver el más reciente +ls -lt backup/ | head -5 +``` + +#### Paso B6: Detén el servicio actual + +```bash +sudo systemctl stop proyectosacc-app.service +``` + +#### Paso B7: Reemplaza el archivo actual con el backup + +```bash +# Guarda una copia del archivo fallido (por si necesitas investigarlo después) +mv current/proyectosacc-app.jar current/proyectosacc-app.jar.fallo-$(date +%Y%m%d_%H%M%S) + +# Copia el backup al directorio current +cp backup/proyectosacc-app-20260414_103000.jar current/proyectosacc-app.jar +``` + +> 💡 **Tip**: reemplaza `20260414_103000` con la fecha real del backup que quieres restaurar. + +#### Paso B8: Recarga systemd y reinicia el servicio + +```bash +sudo systemctl daemon-reload +sudo systemctl start proyectosacc-app.service +``` + +#### Paso B9: Verifica que el servicio esté corriendo + +```bash +sudo systemctl status proyectosacc-app.service +``` + +Debe decir `active (running)`. + +#### Paso B10: Ejecuta el health check + +```bash +curl http://localhost:8080/actuator/health +``` + +Debe responder: +```json +{"status":"UP"} +``` + +#### Paso B11: Verifica la API desde fuera + +```bash +curl https://sacc.ccsoft.mx/api/actuator/health +``` + +Si todo funciona, el rollback fue exitoso. + +--- + +## 4. Cómo verificar que el rollback funcionó + +### Checklist de verificación (API) + +- [ ] El servicio `systemd` está en estado `active (running)`. +- [ ] El health check responde `"status":"UP"`. +- [ ] El endpoint `https://sacc.ccsoft.mx/api/actuator/health` responde sin errores. +- [ ] Los usuarios pueden iniciar sesión y usar la aplicación. +- [ ] No hay errores críticos recientes en los logs. + +### Checklist de verificación (Frontend) + +- [ ] La página `https://sacc.ccsoft.mx` carga sin errores. +- [ ] La invalidación de CloudFront se completó. +- [ ] No hay errores de consola del navegador relacionados con assets faltantes. + +### Comandos útiles para verificar + +```bash +# Estado del servicio API +sudo systemctl status proyectosacc-app.service + +# Health check local +curl -s http://localhost:8080/actuator/health | grep '"status"' + +# Health check remoto +curl -s https://sacc.ccsoft.mx/api/actuator/health | grep '"status"' + +# Últimos logs +tail -n 30 /var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log + +# Verificar que Nginx sigue redirigiendo bien la API +sudo systemctl status nginx + +# Verificar invalidación de CloudFront +aws cloudfront list-invalidations --distribution-id +``` + +--- + +## 5. Después del rollback + +Una vez que la versión anterior esté estable: + +1. **Investiga qué falló** en la nueva versión. +2. **Revisa los logs** del intento fallido. +3. **Notifica al equipo** por Telegram o el canal de comunicación que usen. +4. **Corrige el problema** en el código. +5. **Haz un nuevo deploy** solo cuando estés seguro de que el error está corregido. + +### Mensaje de notificación sugerido + +``` +🚨 Rollback ejecutado en proyectosacc +Motivo: [describe brevemente el problema] +Estado actual: Aplicación estable con versión anterior +Próximo paso: Investigación del error en la nueva versión +``` + +--- + +*Anterior: [`09-runbook-deploy.md`](09-runbook-deploy.md)* +*Siguiente: [`11-checklist-primer-deploy.md`](11-checklist-primer-deploy.md)* diff --git a/docs/11-checklist-primer-deploy.md b/docs/11-checklist-primer-deploy.md new file mode 100644 index 0000000..c2b190e --- /dev/null +++ b/docs/11-checklist-primer-deploy.md @@ -0,0 +1,146 @@ +# 11 - Checklist: Primer Deploy + +> Lista de verificación de todo lo que debe estar listo antes de hacer el primer despliegue de `proyectosacc`. + +--- + +## 1. Infraestructura AWS + +1. [ ] La instancia **EC2 T3.small** con **Ubuntu 22.04** está creada y encendida. +2. [ ] La **EC2** tiene un **Security Group** que permite: + - Puerto `22` (SSH) desde IPs confiables. + - Puerto `80` (HTTP) desde cualquier lugar (solo para la API). + - Puerto `443` (HTTPS) desde cualquier lugar (solo para la API). +3. [ ] La **RDS MariaDB** (`db.t3.micro`) está creada y accesible desde la VPC. +4. [ ] El **bucket S3** para artefactos de la API está creado y la EC2 tiene permisos para leer/escribir en él. +5. [ ] El **bucket S3 para el frontend React** está creado con **Static Website Hosting** habilitado. +6. [ ] El bucket S3 del frontend tiene **versionamiento de objetos** habilitado. +7. [ ] La **distribución CloudFront** está creada, apunta al bucket S3 del frontend, tiene HTTPS y un behavior `/api/*` que apunta a la EC2. +8. [ ] El registro **Route 53** `sacc.ccsoft.mx` apunta a **CloudFront** (no a la IP de la EC2). +9. [ ] El certificado **ACM** para `sacc.ccsoft.mx` está emitido, válido y adjunto a **CloudFront**. +10. [ ] El bucket S3 del frontend tiene un **bucket policy** que permite leer objetos solo a CloudFront (OAI/OAC). + +--- + +## 2. Servidor EC2 (configuración interna) + +11. [ ] El usuario `ubuntu` puede conectarse por SSH. +12. [ ] El usuario `thoth` está creado con su directorio `/home/thoth`. +13. [ ] El usuario `osiris` está creado con su directorio `/home/osiris`. +14. [ ] Los directorios de despliegue existen: + ``` + /home/thoth/deploy/artifacts/backup + /home/thoth/deploy/artifacts/current + /home/thoth/deploy/artifacts/logs + /home/thoth/deploy/artifacts/pids + ``` +15. [ ] **Nginx** está instalado, configurado como proxy inverso **solo para la API** (`/api` → `localhost:8080`) y corriendo. +16. [ ] **Java** (`openjdk-21-jdk`) está instalado. +17. [ ] **AWS CLI** está instalado y configurado. +18. [ ] Los permisos de carpetas están correctos (`thoth` y `osiris` pueden acceder donde deben). + +--- + +## 3. Llaves SSH + +15. [ ] Se generó el **par de llaves SSH dedicado** para `proyectosacc` (no se reutilizan llaves de otros proyectos). +16. [ ] La **llave privada** está codificada en base64 y guardada en **Bitbucket Repository Variables** (`PROYECTOSACC_SSH_KEY`). +17. [ ] La **llave pública** está en `/home/thoth/.ssh/authorized_keys` (inyectada vía Terraform `user_data`). +18. [ ] Se generó el par de llaves SSH para que `thoth` clone repositorios desde Bitbucket. +19. [ ] Las **Access Keys** correspondientes están configuradas en los repositorios de Bitbucket (`ci-cd-commons`, `ci-cd-saac4`). +20. [ ] Se probó la conexión SSH desde el pipeline (o manualmente) y funciona. + +--- + +## 4. Base de datos + +21. [ ] La base de datos RDS está accesible desde la EC2. +22. [ ] Se creó el esquema o las tablas necesarias para la aplicación. +23. [ ] La aplicación tiene configurada la URL de conexión a la base de datos. +24. [ ] Las credenciales de la base de datos están guardadas de forma segura (no en el código fuente). + +--- + +## 5. Pipeline de Bitbucket + +30. [ ] El archivo `bitbucket-pipelines.yml` está en la raíz del repositorio. +31. [ ] Los 7 pasos del pipeline están configurados correctamente: + - Paso 4 compila tanto el frontend React (`npm run build`) como la API backend. + - Paso 5 sube el frontend a S3 y el `.jar` de la API a S3. + - Paso 7 invalida la caché de CloudFront y reinicia la API. +32. [ ] Las **variables de entorno** están creadas en Bitbucket: + - `PROYECTOSACC_SSH_KEY` + - `PROYECTOSACC_SERVER_IP` + - `PROYECTOSACC_SSH_PORT` + - `PROYECTOSACC_SERVER_USER` + - `PROYECTOSACC_TELEGRAM_BOT_TOKEN` + - `PROYECTOSACC_TELEGRAM_CHAT_ID` + - `AWS_ACCESS_KEY_ID` + - `AWS_SECRET_ACCESS_KEY` + - `CLOUDFRONT_DISTRIBUTION_ID` + - `S3_FRONTEND_BUCKET` +33. [ ] Todas las variables sensibles están marcadas como **Secured** 🔒. +34. [ ] El pipeline puede clonar los repositorios necesarios (`ci-cd-commons`, `ci-cd-saac4`, etc.). + +--- + +## 6. Scripts de despliegue + +35. [ ] El script `deploy-frontend-s3.sh` existe y puede subir archivos a S3 + invalidar CloudFront. +36. [ ] El script `setup/deploy.sh` existe en el servidor y tiene permisos de ejecución (solo para la API). +37. [ ] El script `health-check.sh` existe y funciona correctamente. +38. [ ] El script `rollback.sh` existe y tiene al menos un backup de prueba de la API. +39. [ ] El script `notify-telegram.sh` envía mensajes de prueba exitosamente. + +--- + +## 7. Aplicación SACC + +40. [ ] El código fuente del **frontend React** compila sin errores (`npm run build`). +41. [ ] El código fuente de la **API backend** compila sin errores localmente. +42. [ ] El pipeline puede generar el artefacto final del frontend (`build/`) y de la API (`.jar`). +43. [ ] El artefacto de la API se puede ejecutar manualmente en la EC2. +44. [ ] La API responde en `http://localhost:8080/actuator/health` cuando corre localmente en la EC2. + +--- + +## 8. Dominio y SSL + +45. [ ] El dominio `sacc.ccsoft.mx` resuelve a **CloudFront** (verifica con `dig` o `nslookup`). +46. [ ] **CloudFront** sirve el frontend React por HTTPS sin errores de certificado. +47. [ ] **Nginx** en la EC2 redirige las peticiones de la API correctamente (`/api` → `localhost:8080`). +48. [ ] El endpoint `https://sacc.ccsoft.mx/api/actuator/health` responde correctamente. +49. [ ] Al entrar a `https://sacc.ccsoft.mx` se ve la interfaz de SACC (o al menos una página de prueba del frontend). + +--- + +## 9. Seguridad + +50. [ ] El archivo `.env` (si existe) está en `.gitignore`. +51. [ ] No hay contraseñas, tokens ni llaves privadas en el código del repositorio. +52. [ ] No se usa `sshpass` ni acceso SSH por contraseña. +53. [ ] El puerto SSH (22) del Security Group no está abierto a `0.0.0.0/0`. +54. [ ] La base de datos RDS no es pública (solo accesible desde la VPC). +55. [ ] El bucket S3 del frontend NO es público directamente; solo CloudFront accede a él (OAI/OAC). +56. [ ] Los logs sensibles no se imprimen en los archivos de configuración del pipeline. + +--- + +## 10. Comunicación y documentación + +57. [ ] El equipo de desarrollo sabe que el primer deploy se va a realizar. +58. [ ] Hay un canal de comunicación activo (Telegram, Slack, etc.) para alertas. +59. [ ] Este checklist ha sido revisado por al menos una persona más. +60. [ ] Se tiene planificado un horario de bajo tráfico para el primer deploy. + +--- + +## ¿Listo? + +Si marcaste **todas** las 60 casillas, estás listo para hacer el primer deploy. 🚀 + +Si te falta alguna, resuélvela antes de continuar. Es mejor tardar un poco más y hacerlo bien, que apurarse y dejar el servicio caído. + +--- + +*Anterior: [`10-runbook-rollback.md`](10-runbook-rollback.md)* diff --git a/nginx/proyectosacc-api.conf b/nginx/proyectosacc-api.conf new file mode 100644 index 0000000..a227d61 --- /dev/null +++ b/nginx/proyectosacc-api.conf @@ -0,0 +1,49 @@ +# =============================================================================================================== +# proyectosacc-api.conf - Configuración de Nginx para API de proyectosacc +# Descripción: +# Proxy inverso EXCLUSIVO para la API backend. Nginx escucha en +# puerto 80 y redirige /api/ hacia localhost:8080. +# NO sirve el frontend React (eso es S3 + CloudFront). +# +# Uso: +# Copiar a /etc/nginx/sites-available/proyectosacc-api y crear +# enlace simbólico en sites-enabled. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + + # Proxy inverso hacia la API backend en localhost:8080 + location /api/ { + proxy_pass http://localhost:8080/; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + proxy_buffering off; + } + + # Cualquier otra ruta en la EC2 debe devolver 404 + location / { + return 404; + } + + # Página de error genérica + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/scripts/deploy-frontend-s3.sh b/scripts/deploy-frontend-s3.sh new file mode 100755 index 0000000..ed80f3d --- /dev/null +++ b/scripts/deploy-frontend-s3.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# =============================================================================================================== +# deploy-frontend-s3.sh - Sincroniza el frontend React con S3 +# Descripción: +# Sube la carpeta build/ al bucket S3 designado para el sitio estático. +# La invalidación de CloudFront se realiza en el pipeline (paso 7). +# +# Uso: +# S3_FRONTEND_BUCKET=ccsoft-proyectosacc-frontend bash deploy-frontend-s3.sh +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------- +# Validación de variables +# ------------------------------------------------------------------------------- +S3_FRONTEND_BUCKET="${S3_FRONTEND_BUCKET:-}" +BUILD_DIR="${BUILD_DIR:-build}" + +if [[ -z "$S3_FRONTEND_BUCKET" ]]; then + echo "[ERROR] Variable S3_FRONTEND_BUCKET no definida" >&2 + exit 1 +fi + +if [[ ! -d "$BUILD_DIR" ]]; then + echo "[ERROR] Directorio ${BUILD_DIR} no encontrado" >&2 + exit 1 +fi + +# ------------------------------------------------------------------------------- +# Sincronización a S3 +# ------------------------------------------------------------------------------- +echo "[INFO] Sincronizando ${BUILD_DIR}/ a s3://${S3_FRONTEND_BUCKET}/ ..." +aws s3 sync "${BUILD_DIR}/" "s3://${S3_FRONTEND_BUCKET}/" --delete +echo "[INFO] Sincronización completada exitosamente" diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..2b0d916 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# =============================================================================================================== +# deploy.sh - Script de despliegue de la API backend en EC2 +# Descripción: +# Despliega el artefacto .jar de la API en la EC2, gestiona backups, +# configura el servicio systemd y ejecuta health check. +# +# Uso: +# bash /home/thoth/deploy/setup/deploy.sh +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------- +# Configuración +# ------------------------------------------------------------------------------- +readonly APP_NAME="proyectosacc-app" +readonly ARTIFACTS_DIR="/home/thoth/deploy/artifacts" +readonly CURRENT_DIR="${ARTIFACTS_DIR}/current" +readonly BACKUP_DIR="${ARTIFACTS_DIR}/backup" +readonly LOGS_DIR="/var/log/proyectosacc/${APP_NAME}" +readonly SYSTEMD_SERVICE="${APP_NAME}.service" +readonly HEALTH_ENDPOINT="http://localhost:8080/actuator/health" +readonly MAX_RETRIES=30 +readonly RETRY_INTERVAL=2 + +# ------------------------------------------------------------------------------- +# Colores y logging +# ------------------------------------------------------------------------------- +readonly CLR_RED='\033[0;31m' +readonly CLR_GREEN='\033[0;32m' +readonly CLR_YELLOW='\033[1;33m' +readonly CLR_BLUE='\033[0;34m' +readonly CLR_NC='\033[0m' + +_log_info() { echo -e "${CLR_GREEN}[INFO]${CLR_NC} $*"; } +_log_warn() { echo -e "${CLR_YELLOW}[WARN]${CLR_NC} $*" >&2; } +_log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; } +_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; } +_fail() { _log_error "$*"; exit 1; } + +# ------------------------------------------------------------------------------- +# Funciones +# ------------------------------------------------------------------------------- +ensure_directories() { + _log_step "Verificando directorios de despliegue" + mkdir -p "${CURRENT_DIR}" "${BACKUP_DIR}" "${ARTIFACTS_DIR}/pids" "${LOGS_DIR}" + chown -R osiris:osiris "${ARTIFACTS_DIR}" + chmod 750 "${ARTIFACTS_DIR}" + _log_info "Directorios listos" +} + +backup_current_version() { + _log_step "Creando backup de la versión actual" + local current_jar="${CURRENT_DIR}/${APP_NAME}.jar" + if [[ -f "$current_jar" ]]; then + local timestamp + timestamp=$(date +%Y%m%d_%H%M%S) + local backup_name="${BACKUP_DIR}/${APP_NAME}-${timestamp}.jar" + cp "$current_jar" "$backup_name" + _log_info "Backup creado: ${backup_name}" + else + _log_warn "No hay versión actual para hacer backup" + fi +} + +stop_service() { + _log_step "Deteniendo servicio actual" + if systemctl is-active --quiet "${SYSTEMD_SERVICE}"; then + sudo systemctl stop "${SYSTEMD_SERVICE}" || true + _log_info "Servicio detenido" + else + _log_warn "El servicio no estaba activo" + fi +} + +create_systemd_service() { + _log_step "Configurando servicio systemd" + sudo tee "/etc/systemd/system/${SYSTEMD_SERVICE}" > /dev/null < "${ARTIFACTS_DIR}/pids/${APP_NAME}.pid" + _log_info "PID guardado: ${pid}" +} + +# ------------------------------------------------------------------------------- +# Main +# ------------------------------------------------------------------------------- +main() { + _log_step "Iniciando despliegue de ${APP_NAME}" + ensure_directories + backup_current_version + stop_service + create_systemd_service + start_service + health_check + save_pid + _log_info "Despliegue de ${APP_NAME} completado exitosamente" +} + +main "$@" diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100755 index 0000000..5b8345b --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# =============================================================================================================== +# health-check.sh - Verifica que la API backend esté saludable +# Descripción: +# Realiza peticiones al endpoint de salud de la API con reintentos. +# Puede ejecutarse desde la EC2 localmente o desde el pipeline. +# +# Uso: +# HEALTH_URL=http://localhost:8080/actuator/health bash health-check.sh +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------- +# Configuración +# ------------------------------------------------------------------------------- +HEALTH_URL="${HEALTH_URL:-http://localhost:8080/actuator/health}" +MAX_RETRIES="${MAX_RETRIES:-30}" +RETRY_INTERVAL="${RETRY_INTERVAL:-2}" + +readonly CLR_RED='\033[0;31m' +readonly CLR_GREEN='\033[0;32m' +readonly CLR_YELLOW='\033[1;33m' +readonly CLR_BLUE='\033[0;34m' +readonly CLR_NC='\033[0m' + +_log_info() { echo -e "${CLR_GREEN}[INFO]${CLR_NC} $*"; } +_log_warn() { echo -e "${CLR_YELLOW}[WARN]${CLR_NC} $*" >&2; } +_log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; } +_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; } + +# ------------------------------------------------------------------------------- +# Health Check +# ------------------------------------------------------------------------------- +main() { + _log_step "Health Check: ${HEALTH_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" + return 0 + fi + + if [[ $attempt -lt $MAX_RETRIES ]]; then + _log_warn "API no responde aún, reintentando en ${RETRY_INTERVAL}s..." + sleep "${RETRY_INTERVAL}" + fi + + ((attempt++)) + done + + _log_error "Health check FALLÓ después de ${MAX_RETRIES} intentos" + return 1 +} + +main "$@" diff --git a/scripts/notify-telegram.sh b/scripts/notify-telegram.sh new file mode 100755 index 0000000..8f5db46 --- /dev/null +++ b/scripts/notify-telegram.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# =============================================================================================================== +# notify-telegram.sh - Envía notificaciones a Telegram +# Descripción: +# Wrapper simple para enviar mensajes a un chat de Telegram. +# Requiere TELEGRAM_BOT_TOKEN y TELEGRAM_CHAT_ID como variables +# de entorno. +# +# Uso: +# bash notify-telegram.sh "Mensaje de prueba" +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------- +# Validación +# ------------------------------------------------------------------------------- +TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}" +TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}" +MESSAGE="${1:-}" + +if [[ -z "$TELEGRAM_BOT_TOKEN" ]]; then + echo "[ERROR] TELEGRAM_BOT_TOKEN no definido" >&2 + exit 1 +fi + +if [[ -z "$TELEGRAM_CHAT_ID" ]]; then + echo "[ERROR] TELEGRAM_CHAT_ID no definido" >&2 + exit 1 +fi + +if [[ -z "$MESSAGE" ]]; then + echo "[ERROR] Debe proporcionar un mensaje como primer argumento" >&2 + exit 1 +fi + +# ------------------------------------------------------------------------------- +# Envío de mensaje +# ------------------------------------------------------------------------------- +curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" \ + -d "text=${MESSAGE}" \ + -d "parse_mode=Markdown" > /dev/null + +echo "[INFO] Mensaje enviado a Telegram" diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100755 index 0000000..5205da4 --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +# =============================================================================================================== +# rollback.sh - Rollback de la API backend y/o frontend de proyectosacc +# Descripción: +# Restaura la versión anterior de la API desde backup local en la EC2. +# Opcionalmente puede restaurar el frontend en S3 usando versionamiento. +# +# Uso: +# bash /home/thoth/deploy/scripts/rollback.sh [api|frontend|both] +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------- +# Configuración +# ------------------------------------------------------------------------------- +ROLLBACK_TARGET="${1:-api}" +readonly APP_NAME="proyectosacc-app" +readonly ARTIFACTS_DIR="/home/thoth/deploy/artifacts" +readonly CURRENT_DIR="${ARTIFACTS_DIR}/current" +readonly BACKUP_DIR="${ARTIFACTS_DIR}/backup" +readonly SYSTEMD_SERVICE="${APP_NAME}.service" + +readonly CLR_RED='\033[0;31m' +readonly CLR_GREEN='\033[0;32m' +readonly CLR_YELLOW='\033[1;33m' +readonly CLR_BLUE='\033[0;34m' +readonly CLR_NC='\033[0m' + +_log_info() { echo -e "${CLR_GREEN}[INFO]${CLR_NC} $*"; } +_log_warn() { echo -e "${CLR_YELLOW}[WARN]${CLR_NC} $*" >&2; } +_log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; } +_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; } +_fail() { _log_error "$*"; exit 1; } + +# ------------------------------------------------------------------------------- +# Rollback API +# ------------------------------------------------------------------------------- +rollback_api() { + _log_step "Iniciando rollback de la API" + + if [[ ! -d "$BACKUP_DIR" ]]; then + _fail "Directorio de backups no encontrado: ${BACKUP_DIR}" + fi + + local latest_backup + latest_backup=$(ls -t "${BACKUP_DIR}/${APP_NAME}"-*.jar 2>/dev/null | head -n 1 || true) + + if [[ -z "$latest_backup" ]]; then + _fail "No se encontraron backups de la API en ${BACKUP_DIR}" + fi + + _log_info "Backup seleccionado: ${latest_backup}" + + # Detener servicio actual + if systemctl is-active --quiet "${SYSTEMD_SERVICE}"; then + sudo systemctl stop "${SYSTEMD_SERVICE}" || true + _log_info "Servicio detenido" + fi + + # Guardar versión fallida + local current_jar="${CURRENT_DIR}/${APP_NAME}.jar" + if [[ -f "$current_jar" ]]; then + mv "$current_jar" "${current_jar}.fallo-$(date +%Y%m%d_%H%M%S)" + fi + + # Restaurar backup + cp "$latest_backup" "$current_jar" + chown osiris:osiris "$current_jar" + _log_info "Backup restaurado a ${current_jar}" + + # Reiniciar servicio + sudo systemctl daemon-reload + sudo systemctl start "${SYSTEMD_SERVICE}" + _log_info "Servicio reiniciado con versión anterior" + + # Health check + local attempt=1 + local max_retries=30 + local retry_interval=2 + + while [[ $attempt -le $max_retries ]]; do + _log_info "Health check intento ${attempt}/${max_retries}..." + if curl -sf http://localhost:8080/actuator/health | grep -q '"status":"UP"'; then + _log_info "Rollback de API exitoso - API saludable" + return 0 + fi + sleep "$retry_interval" + ((attempt++)) + done + + _fail "Rollback de API falló - Health check no pasó" +} + +# ------------------------------------------------------------------------------- +# Rollback Frontend +# ------------------------------------------------------------------------------- +rollback_frontend() { + _log_step "Iniciando rollback del frontend en S3" + + local bucket="${S3_FRONTEND_BUCKET:-}" + local distribution_id="${CLOUDFRONT_DISTRIBUTION_ID:-}" + + if [[ -z "$bucket" ]]; then + _fail "Variable S3_FRONTEND_BUCKET no definida" + fi + + _log_info "Listando versiones anteriores de index.html..." + local version_id + version_id=$(aws s3api list-object-versions \ + --bucket "$bucket" \ + --prefix index.html \ + --query 'Versions[?IsLatest==`false`].VersionId' \ + --output text 2>/dev/null | awk '{print $1}' || true) + + if [[ -z "$version_id" ]]; then + _fail "No se encontró versión anterior de index.html en S3" + fi + + aws s3api copy-object \ + --bucket "$bucket" \ + --copy-source "${bucket}/index.html?versionId=${version_id}" \ + --key index.html + + _log_info "index.html restaurado a versión anterior" + + if [[ -n "$distribution_id" ]]; then + aws cloudfront create-invalidation \ + --distribution-id "$distribution_id" \ + --paths "/*" + _log_info "Invalidación de CloudFront creada" + else + _log_warn "CLOUDFRONT_DISTRIBUTION_ID no definido, omitiendo invalidación" + fi +} + +# ------------------------------------------------------------------------------- +# Main +# ------------------------------------------------------------------------------- +main() { + case "$ROLLBACK_TARGET" in + api) + rollback_api + ;; + frontend) + rollback_frontend + ;; + both) + rollback_api + rollback_frontend + ;; + *) + echo "Uso: $0 [api|frontend|both]" + exit 1 + ;; + esac + + _log_info "Rollback completado" +} + +main "$@" diff --git a/terraform/backend.prod.hcl b/terraform/backend.prod.hcl new file mode 100644 index 0000000..7451ae8 --- /dev/null +++ b/terraform/backend.prod.hcl @@ -0,0 +1,5 @@ +bucket = "ccsoft-terraform-state-739086995772" +key = "proyectosacc/terraform.tfstate" +region = "mx-central-1" +encrypt = true +dynamodb_table = "terraform-locks-proyectosacc-prod" diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..af08fd1 --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,21 @@ +# =============================================================================================================== +# backend.tf - Configuración del backend de estado para proyectosacc +# Descripción: +# Configura el backend remoto de Terraform en S3 para estado compartido. +# +# Uso: +# - DEV (default): terraform init +# - PROD: terraform init -backend-config=backend.prod.hcl +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +terraform { + backend "s3" { + bucket = "ccsoft-terraform-state" + key = "proyectosacc/terraform.tfstate" + region = "mx-central-1" + encrypt = true + dynamodb_table = "terraform-locks" + } +} diff --git a/terraform/environments/dev.tfvars b/terraform/environments/dev.tfvars new file mode 100644 index 0000000..2cd0259 --- /dev/null +++ b/terraform/environments/dev.tfvars @@ -0,0 +1,24 @@ +# =============================================================================================================== +# dev.tfvars - Variables de ambiente develop para proyectosacc +# Descripción: +# Valores representativos para el ambiente de desarrollo. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +# Nota: mx-central-1 es una región opt-in. Debe estar habilitada en la cuenta de AWS antes del despliegue. + +environment = "dev" +aws_region = "mx-central-1" +vpc_cidr = "10.1.0.0/16" +availability_zones = ["mx-central-1a", "mx-central-1b"] +ec2_instance_type = "t3.small" +ec2_key_name = "ccsoft-dev-key" +pipeline_public_key = "ssh-ed25519 AAAAC3NzaC... bitbucket.pipeline.ci.cd.proyectosacc.thoth.develop@computocontable.com" +db_instance_class = "db.t3.micro" +db_name = "sacc_db_dev" +db_username = "sacc_admin_dev" +db_password = "" +s3_frontend_bucket = "ccsoft-proyectosacc-frontend-dev" +s3_artifacts_bucket = "ccsoft-proyectosacc-artifacts-dev" +cloudfront_price_class = "PriceClass_100" diff --git a/terraform/environments/prod.tfvars b/terraform/environments/prod.tfvars new file mode 100644 index 0000000..53994ca --- /dev/null +++ b/terraform/environments/prod.tfvars @@ -0,0 +1,24 @@ +# =============================================================================================================== +# prod.tfvars - Variables de ambiente producción para proyectosacc +# Descripción: +# Valores representativos para el ambiente de producción. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +# Nota: mx-central-1 es una región opt-in. Debe estar habilitada en la cuenta de AWS antes del despliegue. + +environment = "prod" +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 AAAAC3NzaC... bitbucket.pipeline.ci.cd.proyectosacc.thoth.prod@computocontable.com" +db_instance_class = "db.t3.micro" +db_name = "sacc_db_prod" +db_username = "sacc_admin_prod" +db_password = "" +s3_frontend_bucket = "ccsoft-proyectosacc-frontend-prod" +s3_artifacts_bucket = "ccsoft-proyectosacc-artifacts-prod" +cloudfront_price_class = "PriceClass_100" diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..a981b51 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,576 @@ +# =============================================================================================================== +# main.tf - Recursos principales de infraestructura para proyectosacc +# Descripción: +# Crea VPC, EC2, RDS, S3, CloudFront, Route 53, ACM e IAM para la +# arquitectura híbrida S3+CloudFront+EC2 de SACC. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +# ------------------------------------------------------------------------------- +# VPC y Networking +# ------------------------------------------------------------------------------- +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_support = true + enable_dns_hostnames = true + + tags = { + Name = "${var.project_name}-vpc-${var.environment}" + } +} + +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${var.project_name}-igw-${var.environment}" + } +} + +resource "aws_subnet" "public" { + count = length(var.availability_zones) + vpc_id = aws_vpc.main.id + cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) + availability_zone = var.availability_zones[count.index] + map_public_ip_on_launch = true + + tags = { + Name = "${var.project_name}-public-${var.availability_zones[count.index]}" + } +} + +resource "aws_subnet" "private" { + count = length(var.availability_zones) + vpc_id = aws_vpc.main.id + cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10) + availability_zone = var.availability_zones[count.index] + + tags = { + Name = "${var.project_name}-private-${var.availability_zones[count.index]}" + } +} + +resource "aws_eip" "nat" { + domain = "vpc" + + tags = { + Name = "${var.project_name}-nat-eip-${var.environment}" + } +} + +resource "aws_nat_gateway" "main" { + allocation_id = aws_eip.nat.id + subnet_id = aws_subnet.public[0].id + + tags = { + Name = "${var.project_name}-nat-${var.environment}" + } + + depends_on = [aws_internet_gateway.main] +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id + } + + tags = { + Name = "${var.project_name}-public-rt-${var.environment}" + } +} + +resource "aws_route_table_association" "public" { + count = length(aws_subnet.public) + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +resource "aws_route_table" "private" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main.id + } + + tags = { + Name = "${var.project_name}-private-rt-${var.environment}" + } +} + +resource "aws_route_table_association" "private" { + count = length(aws_subnet.private) + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private.id +} + +# ------------------------------------------------------------------------------- +# Security Groups +# ------------------------------------------------------------------------------- +resource "aws_security_group" "ec2_api" { + name_prefix = "${var.project_name}-ec2-api-" + vpc_id = aws_vpc.main.id + description = "Security Group para la API backend de ${var.project_name}" + + ingress { + description = "SSH desde IPs confiables" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] # Ajustar a IP/VPN real del pipeline + } + + ingress { + description = "HTTP API" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTPS API" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "API backend directo" + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = [aws_vpc.main.cidr_block] + } + + egress { + description = "Salida libre" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-sg-ec2-api" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group" "rds" { + name_prefix = "${var.project_name}-rds-" + vpc_id = aws_vpc.main.id + description = "Security Group para RDS MariaDB de ${var.project_name}" + + ingress { + description = "MariaDB desde EC2 API" + from_port = 3306 + to_port = 3306 + protocol = "tcp" + security_groups = [aws_security_group.ec2_api.id] + } + + egress { + description = "Salida libre" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-sg-rds" + } + + lifecycle { + create_before_destroy = true + } +} + +# ------------------------------------------------------------------------------- +# IAM Role / Instance Profile para EC2 +# ------------------------------------------------------------------------------- +resource "aws_iam_role" "ec2_role" { + name = "${var.project_name}-ec2-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + Action = "sts:AssumeRole" + }] + }) + + tags = { + Name = "${var.project_name}-ec2-role" + } +} + +resource "aws_iam_role_policy" "ec2_policy" { + name = "${var.project_name}-ec2-policy-${var.environment}" + role = aws_iam_role.ec2_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:ListBucket" + ] + Resource = [ + aws_s3_bucket.artifacts.arn, + "${aws_s3_bucket.artifacts.arn}/*" + ] + }, + { + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:*:*:*" + }, + { + Effect = "Allow" + Action = [ + "cloudfront:CreateInvalidation" + ] + Resource = "*" + } + ] + }) +} + +resource "aws_iam_instance_profile" "ec2_profile" { + name = "${var.project_name}-ec2-profile-${var.environment}" + role = aws_iam_role.ec2_role.name + + tags = { + Name = "${var.project_name}-ec2-profile" + } +} + +# ------------------------------------------------------------------------------- +# EC2 (API Backend) +# ------------------------------------------------------------------------------- +resource "aws_instance" "api" { + ami = var.ec2_ami + 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 + 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 + } +} + +# ------------------------------------------------------------------------------- +# RDS MariaDB +# ------------------------------------------------------------------------------- +resource "aws_db_subnet_group" "rds" { + name = "${var.project_name}-rds-subnet-group-${var.environment}" + subnet_ids = aws_subnet.private[*].id + + tags = { + Name = "${var.project_name}-rds-subnet-group" + } +} + +resource "aws_db_instance" "main" { + identifier = "${var.project_name}-db-${var.environment}" + engine = var.db_engine + engine_version = var.db_engine_version + instance_class = var.db_instance_class + allocated_storage = var.db_allocated_storage + storage_type = "gp3" + storage_encrypted = true + db_name = var.db_name + username = var.db_username + password = var.db_password + db_subnet_group_name = aws_db_subnet_group.rds.name + vpc_security_group_ids = [aws_security_group.rds.id] + publicly_accessible = false + skip_final_snapshot = true + backup_retention_period = 7 + + tags = { + Name = "${var.project_name}-rds" + Environment = var.environment + } +} + +# ------------------------------------------------------------------------------- +# S3 Buckets +# ------------------------------------------------------------------------------- +resource "aws_s3_bucket" "frontend" { + bucket = var.s3_frontend_bucket + + tags = { + Name = "${var.project_name}-frontend" + } +} + +resource "aws_s3_bucket_versioning" "frontend" { + bucket = aws_s3_bucket.frontend.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_public_access_block" "frontend" { + bucket = aws_s3_bucket.frontend.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_website_configuration" "frontend" { + bucket = aws_s3_bucket.frontend.id + + index_document { + suffix = "index.html" + } + + error_document { + key = "index.html" + } +} + +resource "aws_s3_bucket" "artifacts" { + bucket = var.s3_artifacts_bucket + + tags = { + Name = "${var.project_name}-artifacts" + } +} + +resource "aws_s3_bucket_versioning" "artifacts" { + bucket = aws_s3_bucket.artifacts.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_public_access_block" "artifacts" { + bucket = aws_s3_bucket.artifacts.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +# ------------------------------------------------------------------------------- +# CloudFront Origin Access Control +# ------------------------------------------------------------------------------- +resource "aws_cloudfront_origin_access_control" "frontend" { + name = "${var.project_name}-oac-${var.environment}" + description = "OAC para bucket frontend de ${var.project_name}" + origin_access_control_origin_type = "s3" + signing_behavior = "always" + signing_protocol = "sigv4" +} + +resource "aws_s3_bucket_policy" "frontend" { + bucket = aws_s3_bucket.frontend.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "AllowCloudFrontOAC" + Effect = "Allow" + Principal = { + Service = "cloudfront.amazonaws.com" + } + Action = "s3:GetObject" + Resource = "${aws_s3_bucket.frontend.arn}/*" + Condition = { + StringEquals = { + "AWS:SourceArn" = aws_cloudfront_distribution.main.arn + } + } + }] + }) + + depends_on = [aws_s3_bucket_public_access_block.frontend] +} + +# ------------------------------------------------------------------------------- +# ACM Certificate (us-east-1 obligatorio para CloudFront) +# ------------------------------------------------------------------------------- +resource "aws_acm_certificate" "main" { + provider = aws.us_east_1 + domain_name = var.domain_name + validation_method = "DNS" + + tags = { + Name = "${var.project_name}-cert" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_route53_record" "cert_validation" { + for_each = { + for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + record = dvo.resource_record_value + type = dvo.resource_record_type + } + } + + allow_overwrite = true + name = each.value.name + records = [each.value.record] + ttl = 60 + type = each.value.type + zone_id = data.aws_route53_zone.main.zone_id +} + +resource "aws_acm_certificate_validation" "main" { + provider = aws.us_east_1 + certificate_arn = aws_acm_certificate.main.arn + validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] +} + +# ------------------------------------------------------------------------------- +# Route 53 +# ------------------------------------------------------------------------------- +data "aws_route53_zone" "main" { + name = "ccsoft.mx" + private_zone = false +} + +resource "aws_route53_record" "main" { + zone_id = data.aws_route53_zone.main.zone_id + name = var.domain_name + type = "A" + + alias { + name = aws_cloudfront_distribution.main.domain_name + zone_id = aws_cloudfront_distribution.main.hosted_zone_id + evaluate_target_health = false + } +} + +# ------------------------------------------------------------------------------- +# CloudFront Distribution +# ------------------------------------------------------------------------------- +resource "aws_cloudfront_distribution" "main" { + enabled = true + is_ipv6_enabled = true + comment = "CDN para ${var.project_name}" + default_root_object = "index.html" + aliases = [var.domain_name] + price_class = var.cloudfront_price_class + + origin { + domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name + origin_id = "saccS3Origin" + origin_access_control_id = aws_cloudfront_origin_access_control.frontend.id + } + + origin { + domain_name = aws_instance.api.public_dns + origin_id = "saccApiOrigin" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["TLSv1.2"] + } + } + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "saccS3Origin" + viewer_protocol_policy = "redirect-to-https" + compress = true + + forwarded_values { + query_string = false + cookies { + forward = "none" + } + } + + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 + } + + ordered_cache_behavior { + path_pattern = "/api/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "saccApiOrigin" + viewer_protocol_policy = "redirect-to-https" + compress = true + + forwarded_values { + query_string = true + headers = ["Origin", "Access-Control-Request-Headers", "Access-Control-Request-Method"] + cookies { + forward = "all" + } + } + + min_ttl = 0 + default_ttl = 0 + max_ttl = 0 + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + viewer_certificate { + acm_certificate_arn = aws_acm_certificate_validation.main.certificate_arn + ssl_support_method = "sni-only" + minimum_protocol_version = "TLSv1.2_2021" + } + + depends_on = [aws_acm_certificate_validation.main] + + tags = { + Name = "${var.project_name}-cdn" + } +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..13aa618 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,57 @@ +# =============================================================================================================== +# outputs.tf - Outputs de infraestructura para proyectosacc +# Descripción: +# Expone información útil de los recursos creados por Terraform. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +output "ec2_public_ip" { + description = "IP pública del servidor de la API" + value = aws_instance.api.public_ip +} + +output "ec2_public_dns" { + description = "DNS público del servidor de la API" + value = aws_instance.api.public_dns +} + +output "rds_endpoint" { + description = "Endpoint de conexión a la base de datos" + value = aws_db_instance.main.endpoint +} + +output "s3_frontend_bucket" { + description = "Nombre del bucket S3 del frontend React" + value = aws_s3_bucket.frontend.bucket +} + +output "s3_artifacts_bucket" { + description = "Nombre del bucket S3 de artefactos de la API" + value = aws_s3_bucket.artifacts.bucket +} + +output "cloudfront_domain" { + description = "Dominio de la distribución CloudFront" + value = aws_cloudfront_distribution.main.domain_name +} + +output "cloudfront_distribution_id" { + description = "ID de la distribución CloudFront" + value = aws_cloudfront_distribution.main.id +} + +output "route53_record" { + description = "Registro DNS creado en Route 53" + value = aws_route53_record.main.name +} + +output "acm_certificate_arn" { + description = "ARN del certificado SSL en ACM" + value = aws_acm_certificate.main.arn +} + +output "vpc_id" { + description = "ID de la VPC creada" + value = aws_vpc.main.id +} diff --git a/terraform/provider.tf b/terraform/provider.tf new file mode 100644 index 0000000..2b9ad76 --- /dev/null +++ b/terraform/provider.tf @@ -0,0 +1,47 @@ +# =============================================================================================================== +# provider.tf - Configuración del proveedor AWS para proyectosacc +# Descripción: +# Define la región y versiones del provider AWS para Terraform. +# +# Uso: +# terraform init +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +terraform { + required_version = ">= 1.5.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Project = var.project_name + ManagedBy = "terraform" + Environment = var.environment + } + } +} + +# Provider exclusivo para ACM en us-east-1 (requerido por CloudFront) +provider "aws" { + alias = "us_east_1" + region = "us-east-1" + + default_tags { + tags = { + Project = var.project_name + ManagedBy = "terraform" + Environment = var.environment + } + } +} diff --git a/terraform/user-data.sh b/terraform/user-data.sh new file mode 100755 index 0000000..eb3dc71 --- /dev/null +++ b/terraform/user-data.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# =============================================================================================================== +# user-data.sh - Script de inicialización de la EC2 para proyectosacc +# Descripción: +# Configura la instancia EC2 al primer boot: instala dependencias, +# crea usuarios, configura Nginx como proxy de API, y prepara +# directorios de despliegue. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------- +# Variables +# ------------------------------------------------------------------------------- +PIPELINE_PUBLIC_KEY="${pipeline_public_key}" + +# ------------------------------------------------------------------------------- +# Actualizar sistema e instalar dependencias +# ------------------------------------------------------------------------------- +apt-get update -y +apt-get install -y nginx openjdk-21-jdk awscli curl jq + +# ------------------------------------------------------------------------------- +# Crear usuarios del sistema +# ------------------------------------------------------------------------------- +useradd -m -s /bin/bash thoth || true +useradd -m -s /bin/bash osiris || true + +# ------------------------------------------------------------------------------- +# Configurar SSH para el pipeline (usuario thoth) +# ------------------------------------------------------------------------------- +mkdir -p /home/thoth/.ssh +chmod 700 /home/thoth/.ssh + +echo "$PIPELINE_PUBLIC_KEY" > /home/thoth/.ssh/authorized_keys +chmod 600 /home/thoth/.ssh/authorized_keys +chown -R thoth:thoth /home/thoth/.ssh + +# ------------------------------------------------------------------------------- +# Crear estructura de directorios de despliegue +# ------------------------------------------------------------------------------- +mkdir -p /home/thoth/deploy/artifacts/{backup,current,logs,pids} +mkdir -p /home/thoth/deploy/{scripts,setup} +chown -R thoth:thoth /home/thoth/deploy + +mkdir -p /var/log/proyectosacc/proyectosacc-app +chown -R osiris:osiris /var/log/proyectosacc + +# ------------------------------------------------------------------------------- +# Configurar Nginx como proxy inverso SOLO para la API +# ------------------------------------------------------------------------------- +cat > /etc/nginx/sites-available/proyectosacc-api <<'NGINX_EOF' +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + + location /api/ { + proxy_pass http://localhost:8080/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + location / { + return 404; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} +NGINX_EOF + +rm -f /etc/nginx/sites-enabled/default +ln -sf /etc/nginx/sites-available/proyectosacc-api /etc/nginx/sites-enabled/proyectosacc-api + +nginx -t +systemctl enable nginx +systemctl restart nginx + +# ------------------------------------------------------------------------------- +# Crear servicio systemd template para la API (será sobrescrito por deploy.sh) +# ------------------------------------------------------------------------------- +cat > /etc/systemd/system/proyectosacc-app.service <<'SYSTEMD_EOF' +[Unit] +Description=Proyecto SACC App Service +After=network.target + +[Service] +Type=simple +User=osiris +Group=osiris +WorkingDirectory=/home/thoth/deploy/artifacts/current +ExecStart=/usr/bin/java -jar /home/thoth/deploy/artifacts/current/proyectosacc-app.jar +SuccessExitStatus=143 +Restart=on-failure +RestartSec=10 +StandardOutput=append:/var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log +StandardError=append:/var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log + +[Install] +WantedBy=multi-user.target +SYSTEMD_EOF + +systemctl daemon-reload +systemctl enable proyectosacc-app.service || true + +# ------------------------------------------------------------------------------- +# Ajustar permisos finales +# ------------------------------------------------------------------------------- +usermod -aG osiris thoth || true +chown -R osiris:osiris /home/thoth/deploy/artifacts +chmod 750 /home/thoth/deploy/artifacts diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..20c2022 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,148 @@ +# =============================================================================================================== +# variables.tf - Variables de infraestructura para proyectosacc +# Descripción: +# Define todas las variables parametrizables de la infraestructura AWS. +# +# Autor: Área de Tecnología y Desarrollo - CCsoft +# =============================================================================================================== + +# ------------------------------------------------------------------------------- +# Generales +# ------------------------------------------------------------------------------- +variable "aws_region" { + description = "Región principal de AWS" + type = string + default = "mx-central-1" +} + +variable "project_name" { + description = "Nombre del proyecto" + type = string + default = "proyectosacc" +} + +variable "environment" { + description = "Ambiente de despliegue (dev, uat, prod)" + type = string +} + +variable "domain_name" { + description = "Dominio principal de la aplicación" + type = string + default = "sacc.ccsoft.mx" +} + +# ------------------------------------------------------------------------------- +# Red +# ------------------------------------------------------------------------------- +variable "vpc_cidr" { + description = "CIDR block de la VPC" + type = string + default = "10.0.0.0/16" +} + +variable "availability_zones" { + description = "Zonas de disponibilidad a utilizar" + type = list(string) + default = ["mx-central-1a", "mx-central-1b"] +} + +# ------------------------------------------------------------------------------- +# EC2 (API Backend) +# ------------------------------------------------------------------------------- +variable "ec2_instance_type" { + description = "Tipo de instancia EC2 para la API" + type = string + default = "t3.small" +} + +variable "ec2_ami" { + description = "AMI de Ubuntu 22.04 LTS" + type = string + # AMI oficial de Ubuntu 22.04 LTS en mx-central-1 (validada: 2026-04-10) + default = "ami-09289f290e76061f8" +} + +variable "ec2_root_volume_size" { + description = "Tamaño del volumen raíz en GB" + type = number + default = 20 +} + +variable "ec2_key_name" { + description = "Nombre del Key Pair SSH para acceso inicial (administrado externamente)" + type = string + default = null +} + +variable "pipeline_public_key" { + description = "Llave pública SSH del pipeline CI/CD (usuario thoth)" + type = string +} + +# ------------------------------------------------------------------------------- +# RDS (Base de datos) +# ------------------------------------------------------------------------------- +variable "db_instance_class" { + description = "Clase de instancia RDS" + type = string + default = "db.t3.micro" +} + +variable "db_engine" { + description = "Motor de base de datos" + type = string + default = "mariadb" +} + +variable "db_engine_version" { + description = "Versión del motor de base de datos" + type = string + default = "10.11" +} + +variable "db_name" { + description = "Nombre de la base de datos inicial" + type = string + default = "sacc_db" +} + +variable "db_username" { + description = "Usuario administrador de la base de datos" + type = string + default = "sacc_admin" +} + +variable "db_password" { + description = "Contraseña del usuario administrador de la base de datos" + type = string + sensitive = true +} + +variable "db_allocated_storage" { + description = "Almacenamiento asignado a RDS en GB" + type = number + default = 20 +} + +# ------------------------------------------------------------------------------- +# S3 +# ------------------------------------------------------------------------------- +variable "s3_frontend_bucket" { + description = "Nombre del bucket S3 para el frontend React" + type = string +} + +variable "s3_artifacts_bucket" { + description = "Nombre del bucket S3 para artefactos de la API" + type = string +} + +# ------------------------------------------------------------------------------- +# CloudFront / ACM +# ------------------------------------------------------------------------------- +variable "cloudfront_price_class" { + description = "Clase de precio de CloudFront" + type = string + default = "PriceClass_100" +}