# Inventario de Secretos

> Última actualización: 2026-04-28 (post-validación contra VPS reales)
> **Este archivo NUNCA contiene valores de secretos.** Solo nombres, ubicaciones y dueños.

## Estructura validada de `/root/scripts/.airtable.env` (en ambos VPS)

```
AIRTABLE_TOKEN=<redacted>
AIRTABLE_BASE_ID=<redacted>
AIRTABLE_TABLE_ID=<redacted>
HOSTINGER_TOKEN=<redacted>
SLACK_WEBHOOK_URL=<redacted>
```

Permisos: `-rw------- root:root` (correcto). Tamaño: 334 bytes en ambos VPS.

## Convención

- **Owner**: persona/cuenta responsable de rotar y custodiar el secreto.
- **Last rotated**: fecha de la última rotación conocida. `unknown` significa "no documentado, asumir nunca rotado".
- **Where**: dónde vive el valor en el sistema. Puede haber **múltiples ubicaciones** del mismo secreto — todas deben rotarse en conjunto.

---

## 1. Acceso a infraestructura

### 1.1 SSH root — VPS1 (Hostinger)

| Campo | Valor |
|---|---|
| Auth method | **Solo SSH keys** (validado 2026-04-28: server advertise `publickey,gssapi-*` — no `password`) |
| sshd config | `PermitRootLogin yes` (en `99-custom.conf`) <br>`PasswordAuthentication no` (en `50-cloud-init.conf` post-hardening) |
| authorized_keys | (1) `bewpro_claude_local_ed25519` (Claude, sin passphrase) <br>(2) `bewpro_admin_coke_ed25519` (Coke, con passphrase) <br>(3) `juan.mercado@bewpro.com` (Juan dev) |
| Owner | Coke / Juan |
| Last rotated | **2026-04-28** ✅ — password root rotada desde panel Hostinger, guardada en gestor. |

### 1.2 SSH root — VPS2 (Donweb)

| Campo | Valor |
|---|---|
| Auth method | **Solo SSH keys + Smallstep CA** (validado 2026-04-28) |
| sshd config | `PermitRootLogin yes`, `PasswordAuthentication no` (en `99-custom.conf` post-hardening) <br>`TrustedUserCAKeys /etc/ssh/ssh-ca.pub` (en `80-step.conf`) <br>`AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u` |
| Puertos abiertos | **22 + 5633** — escucha en ambos (la doc decía solo 5633) |
| Smallstep CA | Configurada — emite certificados SSH con TTL. **VPS1 NO la tiene** (R17 abierto). |
| authorized_keys | (1-53) **53 keys de staff Donweb** (CS, NOC, L2, IaaS, Entregabilidad, Clouds — proveedor managed support) <br>(54) `root@srv1112606.hstgr.cloud` (cross-VPS de VPS1 → VPS2 para provisioning) <br>(55) `bewpro_claude_local_ed25519` (Claude) <br>(56) `bewpro_admin_coke_ed25519` (Coke) <br>(57) `juan.mercado@bewpro.com` (Juan dev) |
| Owner | Coke / Juan + staff Donweb (read-only para nosotros) |
| Last rotated | **2026-04-28** ✅ — password root rotada desde panel Donweb, guardada en gestor. |
| Nota | Las 53 keys Donweb son **expected-attack-surface** del modelo cPanel managed — quitarlas perdería capacidad de soporte del proveedor cuando algo se rompe a bajo nivel. |

### 1.3 SSH key root → cuentas cPanel (deploy automático)

| Campo | Valor |
|---|---|
| Where | `/root/.ssh/id_rsa` y/o `/root/.ssh/id_ed25519` en cada VPS |
| Uso | `setup_cd_project*.sh` step 3 copia esta key a `/home/<user>/.ssh/` para cada nuevo tenant |
| Owner | Sysadmin del VPS |
| Last rotated | unknown |
| Notas | La **misma** key root se distribuye a cada cuenta cPanel creada — no hay separación por tenant. Si se compromete, afecta a todos los tenants. |

### 1.4 SSH key cuentas cPanel → GitHub (`git@github.com:LACOMPANIADIGITAL/cd-system.git`)

| Campo | Valor |
|---|---|
| Where | `/home/<user>/.ssh/` (heredada de root) |
| Owner | LACOMPANIADIGITAL GitHub org |
| Last rotated | unknown |
| Notas | Se usa para `git clone --branch cd-system` del repo. Necesita acceso de lectura al repo privado. |

---

## 2. Airtable

### 2.1 `AIRTABLE_TOKEN` (Personal Access Token)

| Campo | Valor |
|---|---|
| Where | (a) `.env` de cada tenant Laravel (referenciado en `config/services.php:58`) <br>(b) `/root/scripts/.airtable.env` en VPS1 (referenciado en `process-airtable.sh:71`) <br>(c) `.env.example` en repo: **vacío**, OK |
| Scope (esperado) | Read+Write base `appRxvpzqCmNsw2JN`, tablas: Projects, Subscriptions, Shop Products, Shop Products Copy, Resellers |
| Owner | cd.bewpro@gmail.com (Airtable account) |
| Last rotated | unknown |
| Riesgo si compromete | Lectura/escritura de toda la base operativa. Catastrófico. |

### 2.2 IDs de base y tablas (no secretos, pero documentar)

| Variable | Valor | Notas |
|---|---|---|
| `AIRTABLE_BASE_ID` | `appRxvpzqCmNsw2JN` | Base BewPro 2.0 |
| `AIRTABLE_PRODUCTS_TABLE` | `tbleVlExtIu9ONsQd` | Cores |
| `AIRTABLE_SUBSCRIPTIONS_TRACKING_TABLE` | `tblnpr52JhFBBi2Mg` | Subscriptions |
| `AIRTABLE_FORM_RESELLER_TABLE` | `tblzCgJZCbbt5j13Q` | Projects |
| `AIRTABLE_RESELLERS_TABLE` | `tblWxdTDUlaw788Wh` | Resellers |
| `AIRTABLE_SUBSCRIPTIONS_TABLE` | (sin default — must set) | Subscriptions secundaria |

---

## 3. Slack

### 3.1 `SLACK_WEBHOOK_URL` (Laravel — SlackService)

| Campo | Valor |
|---|---|
| Where | (a) `.env` de cada tenant Laravel (referenciado en `app/Services/SlackService.php:12`) <br>(b) `/root/scripts/.airtable.env` en **VPS1 y VPS2** (validado 2026-04-28) <br>(c) Tooling de scripts shell que postea a Slack desde cron |
| Canal target | El canal está embebido en el webhook URL — Slack lo asocia al crear el incoming webhook |
| Owner | Slack workspace admin |
| Last rotated | unknown |
| Riesgo si compromete | Cualquiera puede postear mensajes que parezcan venir del sistema → phishing interno |

### 3.2 7 webhooks documentados en `infraestructura-operativa.md:217-225`

| Canal | Webhook path (truncado) | Uso |
|---|---|---|
| cd-bewpro | `T081UKDS68P/B0AT45M5L0J/...` | Compras, provisioning, billing (Laravel `SlackService`) |
| cd-ventas | `T081UKDS68P/B0AUGUKCW49/...` | Email forwarder (`email_to_slack.php ventas`) |
| cd-proyectos | `T081UKDS68P/B0ATXL99UNP/...` | Email forwarder |
| cd-soporte | `T081UKDS68P/B0AU0KHLC77/...` | Email forwarder |
| cd-contacto | `T081UKDS68P/B0AU7NBC1SQ/...` | Email forwarder |
| juan (privado) | `T081UKDS68P/B0AU0S6EY5B/...` | Email forwarder |
| coke (privado) | `T081UKDS68P/B0AU7S79SR2/...` | Email forwarder |

> **R10 (resuelto)**: las URLs en `infraestructura-operativa.md` ya estaban truncadas — solo IDs públicos, no token.
>
> **R10b (descubierto y mitigado 2026-04-28)**: las URLs **completas con token** estaban en `/home/lacompany/scripts/email_to_slack.php` con permisos `rwxr-xr-x` (world-readable). Cualquier user del sistema podía leerlas. Mitigación aplicada:
> - Script refactorizado para leer URLs desde `/home/lacompany/scripts/.slack-webhooks.env` (perm 640).
> - Script perm endurecido a 750.
> - Backup viejo sanitizado con sed.
> 
> **Pendiente recomendado**: rotar los 6 webhooks (recrearlos en panel Slack y actualizar `.slack-webhooks.env`) por exposición pasada. Sin rotación, alguien que copió el archivo en algún momento aún tiene acceso.

---

## 4. Stripe

| Variable | Where | Uso | Owner |
|---|---|---|---|
| `STRIPE_KEY` | `.env` BewPro principal | Cashier publishable | cd.bewpro@gmail.com |
| `STRIPE_SECRET` | `.env` BewPro principal | Cashier API secret | id. |
| `STRIPE_WEBHOOK_SECRET` | `.env` BewPro principal | Validación webhook signature | id. |

Last rotated: unknown.
Riesgo si compromete `STRIPE_SECRET`: cobros fraudulentos, refunds, lectura de PII de clientes. **Catastrófico**.

---

## 5. Hostinger DNS API

| Variable | Where | Uso |
|---|---|---|
| `HOSTINGER_TOKEN` | `/root/scripts/.airtable.env` en VPS1 (referenciado en `setup_cd_project4.sh:59`) | step 9: crear registros A para subdominios `*.bewpro.com` |

Owner: cuenta Hostinger del propietario del dominio.
Last rotated: unknown.
Riesgo: control total sobre la zona DNS de bewpro.com.

---

## 6. cPanel / WHM

| Variable | Where | Uso real (validado 2026-04-29) |
|---|---|---|
| `WHM_API_TOKEN` | `.env` | **Usado** en `ProjectSetupController.php:174` (endpoint HTTP `/project-setup` — provisioning alternativo via WHM API HTTP en lugar del cron Airtable) y en `scripts/rollback-cpanel.sh:108` (rollback automático de cuentas) |
| `WHM_HOST` | `.env` | id. — necesario para que ProjectSetupController llame a la API WHM |
| `WHM_USERNAME` | `.env` (default `root`) | id. |

> El flujo principal de provisioning (Stripe webhook → Airtable → cron `process-airtable.sh` → `setup_cd_project*.sh`) usa `whmapi1`/`uapi` LOCAL en VPS y **no requiere** estas vars. Pero existen 2 paths alternativos que sí las usan:
> 1. `/project-setup` POST endpoint — provisioning desde la app principal sin pasar por Airtable polling. Útil para tests internos o si Airtable cae.
> 2. `rollback-cpanel.sh` — script de rollback que destruye una cuenta cPanel via WHM API HTTP.
>
> **No eliminar**. Mantener seteados con valor real cuando se quiera habilitar esos paths.

---

## 6b. Backblaze B2 — destino off-site de backups

| Campo | Valor |
|---|---|
| Bucket | `bewpro-backup-server` (id `957aea4aaccab6af96d0071d`) |
| Paths | `backups/hostinger/` (VPS1) y `backups/donweb/` (VPS2) |
| Auth | `application_key_id` + `application_key` por VPS (mismas credenciales en ambos) |
| Where (en VPS) | `whmapi1 backup_destination_list` (cPanel managed). NO en `.airtable.env`. |
| Owner | cuenta Backblaze del titular del bucket |
| Last rotated | unknown (creado pre-2026-04-28) |
| Riesgo si compromete | Read+delete de TODOS los backups históricos. **Catastrófico** + cumplimiento de pérdida de datos. |
| Acción pendiente | (1) Validar que la application_key tiene **scope mínimo** (idealmente solo `writeFiles` + `listFiles` al bucket específico, NO `deleteFiles`). (2) Rotación trimestral (R2). |

---

## 7. Mail (SMTP saliente)

| Variable | Where | Last rotated |
|---|---|---|
| `SMTP_PASS` (cuenta cPanel `noreply@bewpro.com`) | `/root/scripts/.airtable.env` en VPS1 (perm 600) | **2026-04-28** ✅ |

> **Estado 2026-04-28**: el password fue **rotado y movido a env**. Los scripts (`send-welcome-email.php`, `setup_cd_project4.sh`) leen ahora desde `.airtable.env`. Repo limpio + `.bak` files del VPS sanitizados con sed.
>
> El password viejo (`6A5cnothb8it7QbHJQJQ`) **sigue presente en el historial git del repo** — si el repo se hace público, hay que rotar de nuevo, dado que en el commit que sanea quedó la versión vieja antes en el árbol. Decisión pendiente: limpiar historial con `git filter-repo` (riesgoso) o dejar como está y aceptar que el password viejo es público (dado que ya rotamos, no es activo).

Other mail vars (no secretos): `SMTP_HOST=127.0.0.1`, `SMTP_PORT=587`, `SMTP_USER=noreply@bewpro.com`. Templates en `infrastructure/env-templates/airtable.env.example`.

**Path real de email saliente**: los tenants Laravel **NO usan SMTP autenticado** (tienen `MAIL_HOST=localhost MAIL_PORT=25 MAIL_PASSWORD=null`). Los emails reales (welcome, suspended, billing alerts) salen del orquestador VPS1 vía PHP `mail()`/exim local o vía `send-welcome-email.php` con autenticación contra exim/dovecot.

---

## 8. Cloudinary

| Variable | Where |
|---|---|
| `CLOUDINARY_CLOUD_NAME` | `.env` |
| `CLOUDINARY_API_KEY` | `.env` |
| `CLOUDINARY_API_SECRET` | `.env` |
| `CLOUDINARY_URL` | (alternativa, formato unificado) |

Owner: cuenta Cloudinary del proyecto.
Riesgo si compromete `CLOUDINARY_API_SECRET`: borrar/sustituir todos los assets de todos los tenants.

---

## 9. Google / OAuth / Analytics

| Variable | Uso |
|---|---|
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | OAuth login |
| `FACEBOOK_CLIENT_ID` / `FACEBOOK_CLIENT_SECRET` | OAuth login |
| `GA_PROPERTY_ID` | Google Analytics 4 property |
| `GOOGLE_ANALYTICS_ID` | Tracking ID legacy |
| `GOOGLE_ADS_CONVERSION_ID` | Google Ads conversion tracking |
| `storage/app/google-credentials.json` | Service account para Analytics API |

---

## 10. Otros

### 10.0 `SENTRY_LARAVEL_DSN` (Sprint 5 / G.1)

| Campo | Valor |
|---|---|
| Where | `.env` de **bewpro22 únicamente** (otros tenants lo dejan vacío) |
| Uso | `app/Exceptions/Handler.php` envía excepciones reportables al panel de Sentry. Activación condicional: solo si `app()->bound('sentry') && config('sentry.dsn')`. |
| Free tier | 5,000 errors/mes — buffer cómodo hasta ~500 tenants en condición normal |
| Owner | (cuenta Sentry a crear — dueño Coke) |
| Setup | (1) Crear cuenta en sentry.io → New Project → PHP/Laravel. (2) Copiar DSN. (3) `ssh vps1 "vim /home/bewpro22/public_html/bewpro/.env"` y setear `SENTRY_LARAVEL_DSN=https://...@sentry.io/...`. (4) `php artisan config:clear`. |
| Risk si compromete | Bajo — solo permite postear eventos a tu propio proyecto Sentry. No expone datos de la app. |
| Related env | `SENTRY_TRACES_SAMPLE_RATE=0.1` (sample de performance traces, default 10%) |

---

### 10.1 `PROVISION_SECRET`

| Where | (a) `.env` (Laravel) <br>(b) `/root/scripts/.provision_secret` en VPS1 (validado, 64 bytes, perm `-rw------- root:root`) |
|---|---|
| Uso | Validación de webhook entrante a `/provision-webhook` (handshake con orquestador externo) |
| Riesgo | Si compromete, cualquiera puede disparar provisiones |

### 10.2 MySQL root local

| Where | `.env` |
|---|---|
| Variables | `MYSQL_ROOT_USERNAME`, `MYSQL_ROOT_PASSWORD` |
| Uso | Crear bases para tenants nuevos en local (XAMPP). En VPS lo hace cPanel/uapi. |

---

## Plan de rotación recomendado

Prioridad por riesgo + facilidad:

| Prio | Secreto | Acción |
|---|---|---|
| **P0** | SSH passwords root VPS1 + VPS2 | Migrar a key + rotar pw (Fase 2) |
| **P0** | `MAIL_PASSWORD` hardcoded en setup_cd_project4.sh:252 | Rotar + mover a env. **Limpiar historial git** o asumir compromiso. |
| **P0** | `SLACK_WEBHOOK_URL` (los 7 con paths completos en `infraestructura-operativa.md`) | Sanitizar el doc (mostrar solo prefijo) o regenerar webhooks |
| P1 | `AIRTABLE_TOKEN` | Rotar + restringir scope si el actual es full |
| P1 | `HOSTINGER_TOKEN` | Rotar |
| P1 | `STRIPE_*` | Rotar webhook secret + API secret en panel Stripe |
| P2 | SSH key root → cPanel + GitHub | Generar key dedicada per-tenant en lugar de compartir root |
| P2 | `CLOUDINARY_API_SECRET` | Rotar |
| P3 | OAuth (Google/FB) | Rotar (afecta solo login social) |
| P3 | `WHM_API_TOKEN` | **Confirmar si se usa** — eliminar si no |

## Después de rotar

1. Actualizar `.env` de **cada tenant** vivo con el nuevo valor.
2. Actualizar `/root/scripts/.airtable.env` en VPS1 (y VPS2 si aplica).
3. Registrar en este archivo: actualizar campo "Last rotated".
4. Avisar al equipo en Slack `cd-soporte`.
