chore(ci): fix S3 artifacts bucket references in install step and secure terraform tfvars

- Use DEV_S3_ARTIFACTS_BUCKET and PROD_S3_ARTIFACTS_BUCKET in 06_install
  instead of generic S3_ARTIFACTS_BUCKET to prevent cross-env reads
- Add terraform/environments/*.tfvars to .gitignore to prevent secret leaks
- Update prod backend state bucket name to proyectosacc-specific bucket
- Add CI/CD credential policy documentation
This commit is contained in:
Evert Daniel Romero Garrido
2026-04-14 16:01:30 -06:00
parent 2cdeee0b84
commit 3fe8cb1391
5 changed files with 527 additions and 3 deletions
+233
View File
@@ -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` | `<Access key ID del usuario IAM>` |
| `AWS_SECRET_ACCESS_KEY` | `<Secret access key del usuario IAM>` |
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*
+289
View File
@@ -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": "*"
}
]
}