Skip to content

Creating an OIDC client (PocketID)

Cómo registrar una aplicación que soporta OIDC nativo (e.g. Grafana) en PocketID, el IdP que usa el homelab para SSO LAN (ver ADR-0007 §F5).

PocketID corre en VM 208 (stacks/pocket-id, puerto host 1411). Datos en /home/monxas/stacks/pocket-id/data/pocket-id.db (SQLite).

Workflow normal (via UI)

  1. Abrir https://pass.monxas.casa/ (PocketID UI).
  2. Login como admin.
  3. OIDC Clients → New.
  4. Rellenar:
  5. Name: Grafana
  6. Callback URLs: https://grafana.monxas.casa/login/generic_oauth
  7. Logo URL (opcional): icono del servicio
  8. Guardar. Anotar:
  9. client_id
  10. client_secret (solo se muestra una vez)
  11. Configurar el servicio destino (Grafana, Immich, etc.) con esos valores + los endpoints OIDC de PocketID:
  12. Authorization URL: https://pass.monxas.casa/authorize
  13. Token URL: https://pass.monxas.casa/api/oidc/token
  14. Userinfo URL: https://pass.monxas.casa/api/oidc/userinfo
  15. JWKS URL: https://pass.monxas.casa/.well-known/jwks.json
  16. Guardar secrets en SOPS (ver Rotating secrets): bash sops secrets/pocketid.sops.yaml # añadir: # grafana: # client_id: <id> # client_secret: <secret>

Workflow alternativo: SQL directo

Si la UI de PocketID no es accesible (e.g. recovery, bootstrap inicial), se puede INSERT directamente en la DB SQLite.

Solo en caso necesario

Tocar la DB a mano es frágil. La UI de PocketID valida cosas (FK, esquema) que INSERT directo no. Toma snapshot ZFS antes.

Bcrypt gotcha

PocketID almacena el client_secret hasheado con bcrypt (cost 10, $2a$ o $2b$). Hay un gotcha crítico al generarlo:

NO generes bcrypt vía SSH desde la Mac

Comando como este se rompe: bash ssh [email protected] 'python3 -c "..."' El shell remoto interpreta $2a$10$... como variable shell vacía y mutila el hash. Resultado: el INSERT mete un hash inválido, login silenciosamente roto, y debugging muy doloroso.

Solución: SSH primero, generar el hash en sesión interactiva en VM 208, copiar a mano, luego INSERT.

Procedimiento correcto

```bash

1) SSH a VM 208

ssh [email protected]

2) Generar el secret plano + su bcrypt EN la VM

python3 <<'EOF' import secrets, crypt plain = secrets.token_urlsafe(32) hashed = crypt.crypt(plain, crypt.mksalt(crypt.METHOD_BLOWFISH, rounds=10)) print("PLAIN: " + plain) print("HASH: " + hashed) EOF

Salida ejemplo:

PLAIN: xK9_aP3F...

HASH: $2b$10$abcdef...

```

Guarda el PLAIN en SOPS (es lo que el cliente final usa); el HASH es lo que va a PocketID.

INSERT pattern

```bash

3) (todavía en VM 208) INSERT en la DB

docker compose -f ~/stacks/pocket-id/docker-compose.yml exec pocket-id sqlite3 /app/data/pocket-id.db <<EOF INSERT INTO oidc_clients ( id, name, secret, callback_urls, logo_url, is_public, pkce_enabled, created_at, updated_at ) VALUES ( '', 'Grafana', '\$2b\$10\$abcdef...', -- el HASH bcrypt completo, con \$ escapados 'https://grafana.monxas.casa/login/generic_oauth', NULL, 0, 1, datetime('now'), datetime('now') ); EOF

4) Restart PocketID para recargar

docker compose -f ~/stacks/pocket-id/docker-compose.yml restart ```

El client-id-uuid lo generas con python3 -c "import uuid; print(uuid.uuid4())".

Verificar

```bash

desde VM 208

sqlite3 /home/monxas/stacks/pocket-id/data/pocket-id.db \ 'SELECT id, name, length(secret) FROM oidc_clients;'

length(secret) debe ser 60 (bcrypt $2b$10$22charsalt53charshash). Si es <60, mal.

```

Test end-to-end:

```bash curl -sS https://pass.monxas.casa/.well-known/openid-configuration | jq

Pegar luego el client_id/client_secret en Grafana → probar login

```

Apps que NO soportan OIDC nativo

Para apps sin soporte OIDC (Pi-hole, qBittorrent, dozzle, etc.) usamos forward_auth de Caddy contra PocketID, no un OIDC client. Eso se configura en managed.caddy con:

caddy forward_auth pass.monxas.casa { uri /verify copy_headers X-Forwarded-User }

Y se activa con la label tunnel.auth=pocketid (ver Adding a service). No necesita registro como OIDC client.

Rotación del client secret

```bash

1) Generar nuevo plain+hash (en VM 208, no via SSH inline)

2) UPDATE en la DB:

sqlite3 /home/monxas/stacks/pocket-id/data/pocket-id.db \ "UPDATE oidc_clients SET secret = '', updated_at = datetime('now') WHERE name = 'Grafana';"

3) Update el plain en SOPS + push

4) Update Grafana config con el nuevo plain

5) Restart Grafana

```