diff --git a/.gitignore b/.gitignore index 6c8f371..f0661ad 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ terraform/override.tf terraform/override.tf.json terraform/*_override.tf terraform/*_override.tf.json +terraform/environments/*.tfvars +!terraform/environments/*.example.tfvars # Claves SSH y certificados *.pem diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index c904f4c..616f130 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -94,7 +94,7 @@ pipelines: -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'" + "bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp s3://${DEV_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 @@ -168,7 +168,7 @@ pipelines: -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'" + "bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp s3://${PROD_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 diff --git a/docs/12-credenciales-aws-ci-cd.md b/docs/12-credenciales-aws-ci-cd.md new file mode 100644 index 0000000..1238906 --- /dev/null +++ b/docs/12-credenciales-aws-ci-cd.md @@ -0,0 +1,233 @@ +# 12 - Credenciales AWS para CI/CD de proyectosacc + +> Guía para configurar credenciales persistentes de AWS en Bitbucket Pipelines, evitando el uso de tokens de sesión SSO que expiran frecuentemente. + +--- + +## 1. El problema con los tokens de sesión SSO + +Los **tokens de sesión SSO** (Single Sign-On) son credenciales **temporales** que expiran después de un período corto (generalmente entre 1 y 12 horas). + +### ¿Por qué no funcionan bien en CI/CD? + +| Problema | Impacto | +|----------|---------| +| **Expiración frecuente** | El pipeline falla inesperadamente cuando el token vence. | +| **Renovación manual** | Requiere que un usuario humano inicie sesión en SSO y genere nuevos tokens. | +| **Bloqueo de despliegues** | Un despliegue automatizado en medio de la noche puede fallar por un token vencido. | +| **No son idóneos para automatización** | Están diseñados para uso interactivo, no para pipelines desatendidos. | + +**Conclusión**: Para un pipeline CI/CD que debe ejecutarse de forma automática y confiable, se necesitan credenciales **persistentes** o un mecanismo de autenticación **automatizado** como OIDC. + +--- + +## 2. Alternativas recomendadas + +### Opción A (Mejor práctica): OIDC con Bitbucket Pipelines + +Bitbucket Pipelines soporta **OpenID Connect (OIDC)**, que permite que el pipeline asuma un rol de AWS **sin usar credenciales estáticas**. + +**Ventajas:** +- No hay access keys que roten manualmente. +- Cada ejecución del pipeline obtiene credenciales temporales automáticamente. +- Menor superficie de ataque (no hay secretos almacenados en Bitbucket). + +**Desventaja:** +- Requiere configuración inicial más compleja en IAM (OIDC provider, trust policy, rol). + +Para la configuración completa de OIDC, consulta: +- [`docs/04-integracion-bitbucket-aws.md`](../docs/04-integracion-bitbucket-aws.md) (sección "Deploy to AWS with OIDC"). + +### Opción B (Inmediata y práctica): IAM User con Access Keys de larga vida + +Si OIDC no es viable en el corto plazo, la alternativa estándar es crear un **usuario IAM dedicado** con access keys de larga vida, guardadas como variables seguras en Bitbucket. + +**Usuarios recomendados:** +- `bitbucket-pipelines-proyectosacc-dev` (cuenta DEV: `668889063715`) +- `bitbucket-pipelines-proyectosacc-prod` (cuenta PROD: por confirmar) + +**Principios:** +- **Un usuario por ambiente**: nunca compartir credenciales entre DEV y PROD. +- **Solo acceso programático**: sin contraseña de consola AWS. +- **Mínimo privilegio**: solo los permisos estrictamente necesarios para el pipeline. + +--- + +## 3. Política IAM mínima requerida para proyectosacc + +El pipeline de `proyectosacc` realiza las siguientes operaciones en AWS: + +1. **S3**: Sincroniza el frontend React al bucket de sitio estático. +2. **S3**: Sube el artefacto `.jar` de la API al bucket de artefactos. +3. **CloudFront**: Invalida la caché de la distribución tras el despliegue. +4. *(Futuro)* **Terraform/IaC**: Gestiona EC2, RDS, Route 53, ACM, CloudFormation, etc. + +A continuación se presenta una política **compuesta** que cubre tanto el despliegue de aplicación actual como la gestión de infraestructura mediante Terraform/CloudFormation. + +**Archivo de política:** [`iam-policy-ci-cd-proyectosacc.json`](iam-policy-ci-cd-proyectosacc.json) + +### Resumen de permisos incluidos + +| Servicio | Permisos | Justificación | +|----------|----------|---------------| +| **S3** | `PutObject`, `DeleteObject`, `ListBucket`, `GetObject` | `aws s3 sync` frontend y subida/descarga de artefactos `.jar`. | +| **CloudFront** | `CreateInvalidation`, `GetInvalidation`, `ListInvalidations` | Invalidar caché tras deploy. | +| **CloudFront** | `GetDistribution`, `UpdateDistribution`, `CreateDistribution` | Gestión de distribución vía Terraform. | +| **EC2** | `Describe*`, `RunInstances`, `TerminateInstances`, `CreateTags`, etc. | Gestión de instancias, security groups, EIPs, VPC. | +| **RDS** | `Describe*`, `CreateDBInstance`, `ModifyDBInstance`, `DeleteDBInstance` | Gestión de base de datos MariaDB. | +| **Route 53** | `ChangeResourceRecordSets`, `ListHostedZones`, `GetChange` | Gestión de registros DNS. | +| **ACM** | `RequestCertificate`, `DescribeCertificate`, `DeleteCertificate` | Gestión de certificados SSL. | +| **IAM** | `Get*`, `List*`, `SimulatePrincipalPolicy` | Validación de permisos y lectura de roles/políticas. | +| **CloudFormation** | `CreateStack`, `UpdateStack`, `DeleteStack`, `DescribeStacks` | Despliegue de stacks (si aplica). | +| **CloudWatch Logs** | `CreateLogGroup`, `CreateLogStream`, `PutLogEvents` | Logs de despliegue. | + +> **Nota**: Si el pipeline **solo** despliega la aplicación (sin tocar infraestructura), puedes reducir la política a los permisos de **S3** y **CloudFront** únicamente. Consulta el archivo JSON para ver las secciones comentadas. + +--- + +## 4. Buenas prácticas de seguridad + +### 4.1 Almacenamiento de credenciales + +- Guarda `AWS_ACCESS_KEY_ID` y `AWS_SECRET_ACCESS_KEY` como variables **Secured** en Bitbucket Pipelines. +- Nunca escribas credenciales en código, logs ni documentación. +- Considera migrar a **AWS Secrets Manager** o **Parameter Store** como paso intermedio hacia OIDC. + +### 4.2 Rotación de credenciales + +- **Rotar access keys cada 90 días** como máximo. +- Programa recordatorios en el calendario del equipo. +- Documenta el proceso de rotación en un runbook accesible. + +### 4.3 Sin acceso a consola + +- Los usuarios IAM de CI/CD deben tener **solo acceso programático**. +- No asignar contraseña de consola ni políticas de MFA obligatorio al usuario (el pipeline no puede usar MFA). + +### 4.4 Principio de mínimo privilegio + +- Revisa la política IAM cada vez que el pipeline cambie de operaciones. +- Usa ARNs específicos en lugar de `*` siempre que sea posible. +- Considera separar permisos en políticas distintas por servicio. + +### 4.5 Auditoría + +- Habilita **AWS CloudTrail** en ambas cuentas (DEV y PROD) para registrar todas las llamadas a la API. +- Configura alertas en CloudWatch/Security Hub para actividad sospechosa (ej. `CreateAccessKey` fuera de horario). + +### 4.6 Separación de ambientes + +| Ambiente | Cuenta AWS | Usuario IAM | +|----------|------------|-------------| +| DEV | `668889063715` | `bitbucket-pipelines-proyectosacc-dev` | +| PROD | `523761210517` o `739086995772` | `bitbucket-pipelines-proyectosacc-prod` | + +**Nunca reutilices el mismo par de access keys en DEV y PROD.** + +--- + +## 5. Instrucciones paso a paso para crear el usuario IAM + +### Paso 1: Crear el usuario IAM (Consola AWS) + +1. Inicia sesión en la [Consola AWS](https://console.aws.amazon.com/) de la cuenta correspondiente (DEV o PROD). +2. Navega a **IAM > Users > Add users**. +3. **User name**: `bitbucket-pipelines-proyectosacc-dev` (o `-prod`). +4. Selecciona **Access key - Programmatic access**. +5. **NO** selecciones "Password - AWS Management Console access". +6. Haz clic en **Next: Permissions**. + +### Paso 2: Adjuntar la política + +1. Selecciona **Attach policies directly**. +2. Haz clic en **Create policy**. +3. Ve a la pestaña **JSON** y pega el contenido del archivo [`iam-policy-ci-cd-proyectosacc.json`](iam-policy-ci-cd-proyectosacc.json). +4. Reemplaza los placeholders de ARN (`arn:aws:s3:::ccsoft-proyectosacc-*`, etc.) con los recursos reales de tu cuenta. +5. Asigna un nombre a la política: `proyectosacc-ci-cd-deployment-policy`. +6. Guarda la política y adjúntala al usuario. + +### Paso 3: Obtener las access keys + +1. Completa la creación del usuario. +2. En la pantalla de confirmación, copia: + - **Access key ID** + - **Secret access key** +3. **Importante**: Este es el único momento en que verás la secret access key. Guárdala de forma segura (ej. en un password manager). + +### Paso 4: Configurar en Bitbucket Pipelines + +1. Ve al repositorio de `proyectosacc` en Bitbucket. +2. Navega a **Repository settings > Pipelines > Repository variables**. +3. Agrega las siguientes variables y márcalas como **Secured**: + +| Variable | Valor | +|----------|-------| +| `AWS_ACCESS_KEY_ID` | `` | +| `AWS_SECRET_ACCESS_KEY` | `` | + +4. Verifica que el pipeline `bitbucket-pipelines.yml` ya referencia estas variables en los pasos de `publish` y `deploy`. + +### Paso 5: Validar el despliegue + +1. Ejecuta un pipeline de prueba en la rama `developer` (DEV). +2. Verifica que los pasos `05_publish` y `07_deploy` finalicen exitosamente. +3. Revisa los logs de CloudTrail para confirmar que las llamadas a la API provienen del usuario IAM esperado. + +--- + +## 6. Instrucciones vía AWS CLI + +Si prefieres automatizar la creación con la CLI de AWS: + +```bash +# Variables +USER_NAME="bitbucket-pipelines-proyectosacc-dev" +POLICY_NAME="proyectosacc-ci-cd-deployment-policy" +POLICY_DOCUMENT="file://iam-policy-ci-cd-proyectosacc.json" + +# 1. Crear usuario IAM +aws iam create-user --user-name "$USER_NAME" + +# 2. Crear política inline (o managed) y obtener su ARN +POLICY_ARN=$(aws iam create-policy \ + --policy-name "$POLICY_NAME" \ + --policy-document "$POLICY_DOCUMENT" \ + --query 'Policy.Arn' --output text) + +# 3. Adjuntar política al usuario +aws iam attach-user-policy \ + --user-name "$USER_NAME" \ + --policy-arn "$POLICY_ARN" + +# 4. Crear access keys +aws iam create-access-key --user-name "$USER_NAME" +``` + +> **Nota de seguridad**: El comando `create-access-key` devuelve la `SecretAccessKey` en texto plano. Captúrala de forma segura y no la almacenes en el historial de comandos de tu terminal. + +--- + +## 7. Recomendación final + +Aunque los **IAM users con access keys** resuelven el problema inmediato de la expiración de credenciales SSO, la solución **ideal** a mediano plazo para `proyectosacc` es migrar a **OIDC**. + +### Hoja de ruta sugerida + +| Fase | Acción | Prioridad | +|------|--------|-----------| +| **Inmediata** | Crear usuarios IAM con access keys y reemplazar los tokens SSO en Bitbucket. | Alta | +| **Corto plazo** | Implementar rotación automática de access keys (cada 90 días). | Media | +| **Mediano plazo** | Migrar a OIDC para eliminar credenciales estáticas completamente. | Media/Alta | + +--- + +## Referencias + +- [`docs/08-seguridad-buenas-practicas.md`](../docs/08-seguridad-buenas-practicas.md) — Seguridad general de CI/CD en CCsoft. +- [`docs/04-integracion-bitbucket-aws.md`](../docs/04-integracion-bitbucket-aws.md) — Integración de Bitbucket con AWS (incluye OIDC). +- [`05-pipeline-bitbucket.md`](05-pipeline-bitbucket.md) — Funcionamiento del pipeline de `proyectosacc`. +- [`08-seguridad-y-secretos.md`](08-seguridad-y-secretos.md) — Gestión de secrets en `proyectosacc`. + +--- + +*Área de Tecnología y Desarrollo — CCsoft — Abril 2026* diff --git a/docs/iam-policy-ci-cd-proyectosacc.json b/docs/iam-policy-ci-cd-proyectosacc.json new file mode 100644 index 0000000..be995f8 --- /dev/null +++ b/docs/iam-policy-ci-cd-proyectosacc.json @@ -0,0 +1,289 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "S3FrontendDeploy", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:DeleteObject", + "s3:ListBucket", + "s3:GetObject", + "s3:GetBucketLocation" + ], + "Resource": [ + "arn:aws:s3:::ccsoft-proyectosacc-frontend", + "arn:aws:s3:::ccsoft-proyectosacc-frontend/*" + ] + }, + { + "Sid": "S3ArtifactsBucket", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:ListBucket", + "s3:GetObject", + "s3:GetBucketLocation" + ], + "Resource": [ + "arn:aws:s3:::ccsoft-proyectosacc-artifacts", + "arn:aws:s3:::ccsoft-proyectosacc-artifacts/*" + ] + }, + { + "Sid": "CloudFrontInvalidation", + "Effect": "Allow", + "Action": [ + "cloudfront:CreateInvalidation", + "cloudfront:GetInvalidation", + "cloudfront:ListInvalidations" + ], + "Resource": "*" + }, + { + "Sid": "CloudFrontDistributionManagement", + "Effect": "Allow", + "Action": [ + "cloudfront:GetDistribution", + "cloudfront:GetDistributionConfig", + "cloudfront:CreateDistribution", + "cloudfront:UpdateDistribution", + "cloudfront:DeleteDistribution", + "cloudfront:TagResource", + "cloudfront:UntagResource", + "cloudfront:ListTagsForResource", + "cloudfront:ListDistributions", + "cloudfront:CreateOriginAccessControl", + "cloudfront:GetOriginAccessControl", + "cloudfront:UpdateOriginAccessControl", + "cloudfront:DeleteOriginAccessControl", + "cloudfront:ListOriginAccessControls" + ], + "Resource": "*" + }, + { + "Sid": "EC2Management", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeImages", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables", + "ec2:DescribeInternetGateways", + "ec2:DescribeNatGateways", + "ec2:DescribeAddresses", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:DescribeVolumes", + "ec2:RunInstances", + "ec2:TerminateInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:RebootInstances", + "ec2:CreateTags", + "ec2:DeleteTags", + "ec2:ModifyInstanceAttribute", + "ec2:AssociateAddress", + "ec2:DisassociateAddress", + "ec2:AllocateAddress", + "ec2:ReleaseAddress", + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:RevokeSecurityGroupEgress", + "ec2:CreateVpc", + "ec2:DeleteVpc", + "ec2:CreateSubnet", + "ec2:DeleteSubnet", + "ec2:CreateInternetGateway", + "ec2:DeleteInternetGateway", + "ec2:AttachInternetGateway", + "ec2:DetachInternetGateway", + "ec2:CreateRoute", + "ec2:DeleteRoute", + "ec2:CreateRouteTable", + "ec2:DeleteRouteTable", + "ec2:AssociateRouteTable", + "ec2:DisassociateRouteTable", + "ec2:CreateNatGateway", + "ec2:DeleteNatGateway" + ], + "Resource": "*" + }, + { + "Sid": "RDSManagement", + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:DescribeDBSubnetGroups", + "rds:DescribeDBSnapshots", + "rds:DescribeDBParameterGroups", + "rds:CreateDBInstance", + "rds:ModifyDBInstance", + "rds:DeleteDBInstance", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBSubnetGroup", + "rds:CreateDBSnapshot", + "rds:DeleteDBSnapshot", + "rds:RestoreDBInstanceFromDBSnapshot", + "rds:AddTagsToResource", + "rds:RemoveTagsFromResource", + "rds:ListTagsForResource" + ], + "Resource": "*" + }, + { + "Sid": "Route53Management", + "Effect": "Allow", + "Action": [ + "route53:ListHostedZones", + "route53:GetHostedZone", + "route53:ChangeResourceRecordSets", + "route53:GetChange", + "route53:ListResourceRecordSets" + ], + "Resource": "*" + }, + { + "Sid": "ACMCertificateManagement", + "Effect": "Allow", + "Action": [ + "acm:RequestCertificate", + "acm:DescribeCertificate", + "acm:DeleteCertificate", + "acm:ListCertificates", + "acm:GetCertificate", + "acm:AddTagsToCertificate", + "acm:RemoveTagsFromCertificate", + "acm:ListTagsForCertificate" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "us-east-1" + } + } + }, + { + "Sid": "IAMReadOnlyAndRoleManagement", + "Effect": "Allow", + "Action": [ + "iam:GetRole", + "iam:GetRolePolicy", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetInstanceProfile", + "iam:GetUser", + "iam:ListRoles", + "iam:ListRolePolicies", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfiles", + "iam:ListInstanceProfilesForRole", + "iam:ListPolicyVersions", + "iam:SimulatePrincipalPolicy", + "iam:CreateRole", + "iam:DeleteRole", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:CreateInstanceProfile", + "iam:DeleteInstanceProfile", + "iam:AddRoleToInstanceProfile", + "iam:RemoveRoleFromInstanceProfile", + "iam:PassRole", + "iam:CreatePolicy", + "iam:DeletePolicy", + "iam:CreatePolicyVersion", + "iam:DeletePolicyVersion", + "iam:SetDefaultPolicyVersion", + "iam:TagRole", + "iam:UntagRole", + "iam:TagPolicy", + "iam:UntagPolicy" + ], + "Resource": "*" + }, + { + "Sid": "CloudFormationManagement", + "Effect": "Allow", + "Action": [ + "cloudformation:CreateStack", + "cloudformation:UpdateStack", + "cloudformation:DeleteStack", + "cloudformation:DescribeStacks", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStackResources", + "cloudformation:GetTemplate", + "cloudformation:ListStacks", + "cloudformation:ValidateTemplate", + "cloudformation:CreateChangeSet", + "cloudformation:ExecuteChangeSet", + "cloudformation:DeleteChangeSet", + "cloudformation:DescribeChangeSet", + "cloudformation:SetStackPolicy", + "cloudformation:TagResource", + "cloudformation:UntagResource", + "cloudformation:ListStackResources" + ], + "Resource": "*" + }, + { + "Sid": "CloudWatchLogs", + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams" + ], + "Resource": "arn:aws:logs:*:*:log-group:/aws/*" + }, + { + "Sid": "ElasticLoadBalancing", + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": "*" + }, + { + "Sid": "KMSAndSSM", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:DescribeKey", + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:DescribeParameters", + "secretsmanager:GetSecretValue" + ], + "Resource": "*" + } + ] +} diff --git a/terraform/backend.prod.hcl b/terraform/backend.prod.hcl index 7451ae8..9511df4 100644 --- a/terraform/backend.prod.hcl +++ b/terraform/backend.prod.hcl @@ -1,4 +1,4 @@ -bucket = "ccsoft-terraform-state-739086995772" +bucket = "ccsoft-proyectosacc-terraform-state-prod" key = "proyectosacc/terraform.tfstate" region = "mx-central-1" encrypt = true