# 14 - Autenticación OIDC entre Bitbucket Pipelines y AWS

> Guía paso a paso para eliminar Access Keys estáticas y usar OpenID Connect (OIDC) en el pipeline de `proyectosacc`.

---

## 1. ¿Por qué OIDC?

En muchos equipos, los pipelines de CI/CD usan **IAM Users** con **Access Keys de larga vida** (`AWS_ACCESS_KEY_ID` y `AWS_SECRET_ACCESS_KEY`). Esto tiene varios problemas:

| Problema | Explicación |
|----------|-------------|
| **Rotación manual** | Las keys deben renovarse periódicamente (cada 90 días recomendado). Si se olvida, el pipeline falla. |
| **Fuga de secretos** | Si las keys se filtran (logs, commits, etc.), un atacante puede usarlas desde cualquier lugar del mundo. |
| **Deny de IAM** | En organizaciones maduras, los administradores bloquean `iam:CreateUser` e `iam:CreateAccessKey`, haciendo imposible este camino. |

**OIDC resuelve todo esto** permitiendo que el pipeline de Bitbucket asuma un **rol IAM temporal** automáticamente, sin guardar credenciales estáticas. Cada ejecución obtiene un token JWT que AWS valida y convierte en credenciales temporales (vencen en 1 hora por defecto).

---

## 2. ¿Cómo funciona el flujo?

El proceso es como mostrar una credencial de invitado en la entrada de un edificio:

```
┌─────────────────┐     1. Solicita token OIDC      ┌─────────────────┐
│  Bitbucket      │ ───────────────────────────────▶│  Bitbucket      │
│  Pipelines      │                                   OIDC Provider   │
│  (runner)       │◀────────────────────────────────│                 │
└─────────────────┘     2. Devuelve JWT firmado     └─────────────────┘
         │
         │ 3. Presenta JWT + Role ARN
         ▼
┌─────────────────┐     4. Valida JWT contra        ┌─────────────────┐
│  AWS STS        │ ───────────────────────────────▶│  AWS IAM        │
│  (sts:AssumeRole│                                   OIDC Provider   │
│   WithWebIdent.)│◀────────────────────────────────│                 │
└─────────────────┘     5. Confía si aud/sub OK     └─────────────────┘
         │
         │ 6. Devuelve credenciales temporales
         ▼
┌─────────────────┐
│  Pipeline       │ 7. Ejecuta Terraform / aws s3 / cloudfront ...
│  con acceso AWS │
└─────────────────┘
```

**Claims importantes del JWT:**

| Claim | Significado | Ejemplo |
|-------|-------------|---------|
| `aud` | Audience (a quién va dirigido el token) | `sts.amazonaws.com` |
| `sub` | Subject (quién solicita el token) | `{REPO_UUID}:{STEP_UUID}` |
| `iss` | Issuer (quién emitió el token) | `https://api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc` |

---

## 3. Configuración aplicada (resumen de esta sesión)

Los siguientes recursos OIDC ya fueron configurados manualmente en la consola AWS durante abril de 2026:

| Cuenta | OIDC Provider | Rol IAM | Permisos |
|--------|---------------|---------|----------|
| **DEV** (`668889063715`) | `api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc` | `BitbucketProyectosaccCICDRoleDev` | `AdministratorAccess` (temporal) |
| **PROD** (`523761210517`) | `api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc` | `BitbucketProyectosaccCICDRoleProd` | `AdministratorAccess` (temporal) |

**Audience configurada:** `sts.amazonaws.com`

**Repository UUID usado en trust policies:** `{3ceb5bec-0805-4bfb-b891-aaf5626ad7a5}`

> **Nota de seguridad:** `AdministratorAccess` fue adjunta solo como punto de partida para desbloquear el pipeline. En una iteración posterior debe reemplazarse por la política mínima de `docs/iam-policy-ci-cd-proyectosacc.json`.

---

## 4. Pre-requisitos

Antes de empezar, asegúrate de tener:

1. **Acceso administrativo a AWS** (consola o CLI) en ambas cuentas:
   - DEV: `668889063715`
   - PROD: `523761210517`
2. **Permiso para crear** `iam:CreateOpenIDConnectProvider` y `iam:CreateRole`.
3. El **Repository UUID** de `ccsoft1/proyectosacc`:
   - `{3ceb5bec-0805-4bfb-b891-aaf5626ad7a5}` (incluye las llaves `{}`)
   - Lo encuentras en: Bitbucket > Repository settings > OpenID Connect > **Repository UUID**

---

## 4. Bootstrap manual: crear OIDC Provider y Roles

> **IMPORTANTE**: El pipeline NO puede crear su propio rol OIDC porque aún no tiene credenciales. La primera vez, un administrador debe crear el `OIDC Provider` y los `IAM Roles` manualmente o ejecutando Terraform desde su laptop con credenciales de admin.

### Opción A: Aplicar con Terraform local (recomendado)

1. Clona el repo en tu máquina:
   ```bash
   git clone https://bitbucket.org/ccsoft1/proyectosacc.git
   cd proyectosacc/terraform
   ```

2. Verifica que `terraform/oidc-bitbucket.tf` tenga el UUID correcto:
   ```hcl
   bitbucket_repo_uuid = "{3ceb5bec-0805-4bfb-b891-aaf5626ad7a5}"
   ```

3. Configura tus credenciales de admin para la cuenta **DEV**:
   ```bash
   export AWS_ACCESS_KEY_ID="AKIA..."
   export AWS_SECRET_ACCESS_KEY="..."
   export AWS_DEFAULT_REGION="mx-central-1"
   ```

4. Inicializa y aplica:
   ```bash
   terraform init -backend-config=backend.dev.hcl
   terraform plan -var-file=environments/dev.tfvars -target=aws_iam_openid_connect_provider.bitbucket -target=aws_iam_role.bitbucket_ci_cd
   terraform apply -var-file=environments/dev.tfvars -target=aws_iam_openid_connect_provider.bitbucket -target=aws_iam_role.bitbucket_ci_cd
   ```

5. Repite para la cuenta **PROD** cambiando el backend:
   ```bash
   terraform init -backend-config=backend.prod.hcl -reconfigure
   terraform plan -var-file=environments/prod.tfvars -target=aws_iam_openid_connect_provider.bitbucket -target=aws_iam_role.bitbucket_ci_cd
   terraform apply -var-file=environments/prod.tfvars -target=aws_iam_openid_connect_provider.bitbucket -target=aws_iam_role.bitbucket_ci_cd
   ```

### Opción B: Crear desde la Consola AWS (paso a paso)

#### Paso 1: Crear el OIDC Provider

Repite esto en **DEV** y **PROD**:

1. Ve a **IAM > Identity Providers > Add Provider**.
2. Selecciona **OpenID Connect**.
3. **Provider URL**: `https://api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc`
4. Haz clic en **Get thumbprint** (AWS lo obtendrá automáticamente).
5. **Audience**: `sts.amazonaws.com`
6. Guarda con el nombre sugerido: `bitbucket-pipelines-oidc`

#### Paso 2: Crear el Rol IAM para DEV

1. Ve a **IAM > Roles > Create Role**.
2. Tipo de entidad confiable: **Web Identity**.
3. Selecciona el provider creado en el paso anterior.
4. **Audience**: `sts.amazonaws.com`.
5. En **Conditions**, agrega una condición personalizada (avanzado) o edita el trust policy después de crearlo.
6. Nombre del rol: `BitbucketProyectosaccCICDRoleDev`
7. Adjunta la política `AdministratorAccess` (solo como punto de partida).
8. Edita el **Trust Policy** para que quede exactamente así:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::668889063715:oidc-provider/api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc:sub": "{3ceb5bec-0805-4bfb-b891-aaf5626ad7a5}:*"
        }
      }
    }
  ]
}
```

#### Paso 3: Crear el Rol IAM para PROD

Repite exactamente el paso 2 pero en la cuenta PROD (`523761210517`) y con el nombre:

```
BitbucketProyectosaccCICDRoleProd
```

El ARN del provider federado cambiará a:
```
arn:aws:iam::523761210517:oidc-provider/api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc
```

---

## 5. Configuración del pipeline

El archivo `bitbucket-pipelines.yml` ya fue actualizado para usar OIDC. Estos son los cambios clave:

### 5.1 Audience global

```yaml
options:
  oidc:
    audiences:
      - sts.amazonaws.com
```

Esto le dice a Bitbucket que el token OIDC incluya `sts.amazonaws.com` en el claim `aud`.

### 5.2 Activar OIDC en los steps

Cada step que interactúa con AWS debe tener `oidc: true`:

```yaml
- step:
    name: 03_terraform
    oidc: true
    script:
      - source scripts/aws-oidc-setup.sh dev
      - ...
```

En `proyectosacc`, los steps con OIDC son:
- `03_terraform`
- `05_publish`
- `07_deploy`

### 5.3 Script helper `aws-oidc-setup.sh`

Este script:
1. Lee `BITBUCKET_STEP_OIDC_TOKEN` (lo inyecta Bitbucket automáticamente).
2. Lo guarda en un archivo temporal.
3. Exporta `AWS_WEB_IDENTITY_TOKEN_FILE` y `AWS_ROLE_ARN`.
4. AWS CLI y Terraform usan estas variables para asumir el rol automáticamente.

---

## 6. ¿Cómo probar que funciona?

### Prueba 1: Verificar el token JWT

Crea un pipeline de prueba temporal o agrega este comando al inicio de un step:

```bash
echo "$BITBUCKET_STEP_OIDC_TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
```

Deberías ver algo como:
```json
{
  "sub": "{3ceb5bec-0805-4bfb-b891-aaf5626ad7a5}:{759de0c6-eaee-4eaa-b7a6-c507eec759a7}",
  "aud": "sts.amazonaws.com",
  "iss": "https://api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc",
  ...
}
```

### Prueba 2: Verificar AssumeRole desde el pipeline

Agrega temporalmente esto al step `03_terraform`:

```bash
source scripts/aws-oidc-setup.sh dev
aws sts get-caller-identity
```

Debería devolver el ARN del rol asumido:
```json
{
  "UserId": "AROA...:bot/session",
  "Account": "668889063715",
  "Arn": "arn:aws:sts::668889063715:assumed-role/BitbucketProyectosaccCICDRoleDev/bot/session"
}
```

### Prueba 3: Pipeline completo en DEV

1. Haz push a la rama `developer`.
2. Ve a Bitbucket > Pipelines.
3. Verifica que `03_terraform`, `05_publish` y `07_deploy` terminen en verde.
4. Si falla, revisa la sección de Troubleshooting.

---

## 7. Troubleshooting común

### Error: `BITBUCKET_STEP_OIDC_TOKEN no está definido`

**Causa**: Olvidaste agregar `oidc: true` al step.

**Solución**: Verifica que los steps `03_terraform`, `05_publish` y `07_deploy` tengan:
```yaml
oidc: true
```

---

### Error: `An error occurred (InvalidIdentityToken) when calling the AssumeRoleWithWebIdentity operation`

**Causa**: El `aud` del token no coincide con el `client_id_list` del OIDC Provider.

**Solución**:
1. Verifica que `bitbucket-pipelines.yml` tenga:
   ```yaml
   options:
     oidc:
       audiences:
         - sts.amazonaws.com
   ```
2. Verifica que el OIDC Provider en AWS tenga `sts.amazonaws.com` en `client_id_list`.

---

### Error: `AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity`

**Causa**: El trust policy del rol no coincide con el claim `sub` del token.

**Solución**:
1. Obtén el `sub` real del token (ver Prueba 1).
2. Ajusta el trust policy. Si usas `StringLike`, asegúrate de que el patrón termine en `:*`:
   ```json
   "StringLike": {
     "api.bitbucket.org/2.0/workspaces/ccsoft1/pipelines-config/identity/oidc:sub": "{REPO_UUID}:*"
   }
   ```
3. El UUID configurado es `{3ceb5bec-0805-4bfb-b891-aaf5626ad7a5}`. Si el pipeline sigue fallando, verifica que el `sub` claim del token real coincida con este UUID. Solo en casos de emergencia puedes poner `"*"` temporalmente **solo para pruebas** (menos seguro).

---

### Error: `Unable to locate credentials`

**Causa**: AWS CLI no encuentra el token o el rol.

**Solución**:
1. Confirma que `scripts/aws-oidc-setup.sh` se ejecutó antes de cualquier comando `aws` o `terraform`.
2. Verifica que `AWS_WEB_IDENTITY_TOKEN_FILE` y `AWS_ROLE_ARN` estén exportadas.
3. Asegúrate de usar una versión reciente de AWS CLI (`aws --version` >= 2.x).

---

### Error: `terraform init` falla por permisos al bucket de estado

**Causa**: El rol OIDC no tiene permisos para leer/escribir el bucket S3 de estado ni la tabla DynamoDB de locks.

**Solución**: Como usamos `AdministratorAccess` como punto de partida, esto no debería pasar. Si en el futuro restringes la política, asegúrate de incluir permisos sobre:
- `s3:GetObject`, `s3:PutObject`, `s3:ListBucket` en el bucket de estado
- `dynamodb:GetItem`, `dynamodb:PutItem`, `dynamodb:DeleteItem` en la tabla de locks

---

## 8. Roadmap de hardening

Aunque `AdministratorAccess` hace que todo funcione inmediatamente, es una mala práctica de seguridad dejarlo así a largo plazo.

| Prioridad | Acción | Justificación |
|-----------|--------|---------------|
| **Alta** | Reemplazar `AdministratorAccess` por una política custom basada en `docs/iam-policy-ci-cd-proyectosacc.json`. | Mínimo privilegio. |
| **Media** | Restringir el `sub` claim al `REPO_UUID` exacto. | Evita que otros repos del workspace asuman el rol. |
| **Media** | Separar roles: uno para Terraform (infraestructura) y otro para despliegue de aplicación (S3 + CloudFront). | Reduce el blast radius. |
| **Baja** | Agregar condiciones de IP en el trust policy (si el pipeline siempre corre desde rangos conocidos de Bitbucket). | Capa extra de seguridad. |

---

## Referencias

- [Deploy on AWS using Bitbucket Pipelines OpenID Connect – Atlassian](https://support.atlassian.com/bitbucket-cloud/docs/deploy-on-aws-using-bitbucket-pipelines-openid-connect/)
- [AWS Docs: Creating OpenID Connect identity providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html)
- [docs/12-credenciales-aws-ci-cd.md](12-credenciales-aws-ci-cd.md) — Contexto anterior de credenciales estáticas.
- [docs/iam-policy-ci-cd-proyectosacc.json](iam-policy-ci-cd-proyectosacc.json) — Política mínima de referencia.

---

*Área de Tecnología y Desarrollo — CCsoft — Abril 2026*
