README.es.md
Rama main · réplica de lectura del repositorio original.
# Physalis
[](LICENSE)
[](https://nextjs.org/)
[](https://www.typescriptlang.org/)
[](https://www.postgresql.org/)
[](https://www.prisma.io/)
[](https://www.docker.com/)
[Français](README.md) · [English](README.en.md) · **Español**
> **Versión 1.7.1** · Versión alojada y gestionada: [physalis.cloud](https://physalis.cloud) · Errores y comentarios: [abrir una issue](https://github.com/physalis-cloud/physalis/issues)
---
Gestor de secretos autoalojado (Next.js + Postgres + AES-256-GCM) que centraliza
las variables de entorno, claves SSH y credenciales de todos tus proyectos, con
autenticación OIDC (GitHub, GitLab, Bitbucket) para los flujos de despliegue.
Multiorganización, registro de auditoría, servicios y cuentas cifrados,
docker-compose servido por entorno, redespliegue CI integrado, intercambio de
claves híbrido post-cuántico (ECDH P-256 + ML-KEM-768) para las solicitudes de
secretos externas.
**Physalis** es un gestor de secretos autoalojado, pensado para centralizar
todas las variables de entorno de una agencia web en sus propios servidores, sin
depender de un servicio en la nube de terceros.
**Documentación**: [guías](https://physalis.cloud/es/docs) · [tutoriales paso a paso](https://physalis.cloud/es/tutos) · [autoalojamiento](https://physalis.cloud/es/self-host)
---
## El problema que resuelve
En una agencia que gestiona varios proyectos en varios VPS, las variables de
entorno (contraseñas de bases de datos, claves de API, tokens) acaban dispersas
en archivos `.env` de cada servidor, en GitHub Secrets, en notas personales.
Cambiar una variable obliga a conectarse manualmente a cada servidor. Cuando una
persona deja el equipo, resulta imposible saber a qué tenía acceso.
---
## Qué hace Physalis
### Centralización cifrada
Todas las variables se almacenan en una base de datos PostgreSQL, cifradas con
AES-256-GCM antes de escribirse. Incluso con acceso directo a la base de datos,
los valores son ilegibles sin la clave de cifrado, que solo vive en las
variables de entorno del servidor.
### Multiorganización y control de acceso
La aplicación admite varias organizaciones aisladas, cada una con sus propios
proyectos y miembros. Los permisos son granulares en tres niveles —
organización, proyecto, entorno — con roles distintos (lector, editor,
propietario). Invitaciones por correo con enlace firmado y revocación automática
de los accesos cuando alguien deja el equipo.
### Dos formas de consumir los secretos
**Para personas** — una interfaz web protegida por contraseña y, opcionalmente,
por doble autenticación TOTP. Los valores de los secretos nunca se muestran en
bloque: cada revelación es una acción explícita, de una en una, registrada en el
registro de auditoría.
**Para máquinas** — autenticación OIDC. En el momento del despliegue, el flujo
de trabajo obtiene un token firmado por el proveedor de CI (sin ningún secreto
almacenado en GitHub Secrets) y lo presenta a Physalis. La bóveda verifica la
firma criptográficamente, comprueba que el repositorio, el flujo de trabajo y la
rama coinciden exactamente con una regla autorizada, y devuelve en una sola
petición todo el paquete de despliegue: variables de entorno descifradas, clave
SSH del servidor de destino, ruta de despliegue, docker-compose y credenciales
del registro Docker.
### Criptografía resistente a la computación cuántica
Los secretos se cifran en reposo con **AES-256-GCM**, un cifrado simétrico ya
considerado resistente a los ordenadores cuánticos: el algoritmo de Grover solo
reduce una clave de 256 bits a 128 bits de seguridad efectiva.
El intercambio de claves de las **solicitudes de secretos externas** — el único
punto donde la criptografía asimétrica protege un valor y, por tanto, el único
realmente expuesto a un futuro ordenador cuántico — es **híbrido**: ECDH P-256
**y** ML-KEM-768 (FIPS 203), combinados mediante HKDF-SHA256 con vinculación de
la transcripción. Romper la clave derivada exige romper ambos. Las solicitudes
creadas antes de este cambio (solo ECDH) siguen siendo descifrables.
Implementación: [lib/hybrid-kem.ts](lib/hybrid-kem.ts) y [lib/pqc.ts](lib/pqc.ts).
### Trazabilidad completa
Cada acción — leer un secreto, modificarlo, iniciar sesión, desplegar, invitar —
queda registrada en un registro de auditoría persistente con el actor, la IP y
la marca de tiempo. Exportable a CSV y consultable por proyecto o por
organización.
---
## Qué cambia en la práctica
| Antes | Después |
|---|---|
| Un archivo `.env` por proyecto y por servidor | Una única interfaz para todos los secretos de la agencia |
| Claves SSH y tokens en GitHub Secrets | Ninguna clave ni token en GitHub |
| Ninguna trazabilidad | Cada acceso registrado con actor, IP y marca de tiempo |
| Imposible saber quién accede a qué | Revocación inmediata cuando alguien se marcha |
| Despliegues manuales o semiautomatizados | Despliegues totalmente automatizados, sin intervención humana |
📖 **Documentación técnica** : [docs/physalis.md](docs/physalis.md) (en francés)
📚 **Documentación de usuario** : [docs/documentation/es/](docs/documentation/es/) (también en `fr` / `en`)
🔒 **Modelo de seguridad** : [docs/security.md](docs/security.md) (en francés)
---
## Inicio rápido
### 1. Local — stack completo (Docker)
```bash
cp .env.example .env
# Rellenar los valores vacíos: ENCRYPTION_KEY, AUTH_SECRET, NEXTAUTH_SECRET,
# DB_PASSWORD, ADMIN_PASSWORD (ADMIN_EMAIL tiene un valor por defecto).
# ENCRYPTION_KEY = openssl rand -hex 32 ; los secretos = openssl rand -base64 32.
docker compose up -d
```
`docker-compose.yml` descarga la imagen publicada de esta versión, fijada por digest.
Para compilar desde el código fuente:
`docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build`.
→ http://localhost:3001 (puerto por defecto; el 3000 suele estar ocupado —
ajustable con `PORT` en `.env`, alineando `NEXTAUTH_URL` con el mismo puerto).
El puerto solo se publica en `127.0.0.1`, es decir, accesible únicamente desde
la máquina anfitriona. En un servidor remoto, coloca Physalis detrás de un
proxy inverso (nginx, Caddy, Traefik) que termine el TLS y hable con el
contenedor en local. Si realmente necesitas publicar el puerto, define
`BIND_IP` en `.env`: preferiblemente la dirección de una interfaz privada (VPN,
red interna), y `0.0.0.0` solo como último recurso. ⚠️ Un puerto publicado sin
dirección elude el cortafuegos del anfitrión: la cadena `DOCKER-USER` está
vacía por defecto.
El primer arranque aplica las migraciones de Prisma y crea la cuenta de
administración definida por `ADMIN_EMAIL` / `ADMIN_PASSWORD`
([scripts/bootstrap-admin.mjs](scripts/bootstrap-admin.mjs)).
### 2. Local — desarrollo nativo (hot reload)
```bash
docker compose -f docker-compose.dev.yml up -d # solo Postgres (puerto 5434)
npm install
npx prisma migrate dev
npm run bootstrap-admin
npm run dev # http://localhost:3000
```
### 3. Producción (VPS detrás de un proxy inverso)
Flujo típico: test → build/push al registro → despliegue SSH + health check.
Plantilla lista para copiar: [docs/deploy.modele.yml](docs/deploy.modele.yml).
Consulta [docs/physalis.md §9](docs/physalis.md) para la instalación completa
(carpeta de despliegue, clave SSH dedicada al flujo de trabajo, contenido del
`.env`, proxy inverso y TLS).
---
## Consumir los secretos desde un proyecto
Tres modos de acceso, del más moderno al más antiguo:
### Modo 1 — OIDC (recomendado)
El runner de CI se autentica con un JWT OIDC firmado por el proveedor (GitHub
Actions, GitLab CI, Bitbucket Pipelines). La bóveda valida el claim contra una
`Policy` estricta — `(repo, workflow, branch) → (project, env)` — y devuelve un
paquete completo.
| Endpoint | Auth | Respuesta |
|---|---|---|
| `POST /api/deploy` | Bearer JWT OIDC | `{ serverIp, serverUser, sshKey, deployPath, secrets, dockerCompose, registry }` |
No se consume ningún secreto de CI. La clave SSH y las credenciales del registro
viven cifradas en la bóveda. Plantillas listas para copiar:
[docs/deploy.modele.yml](docs/deploy.modele.yml) (despliegue con rebuild),
[docs/redeploy.modele.yml](docs/redeploy.modele.yml) (redespliegue sin rebuild),
[docs/deploy.gitlab-ci.modele.yml](docs/deploy.gitlab-ci.modele.yml) y
[docs/deploy.bitbucket-pipelines.modele.yml](docs/deploy.bitbucket-pipelines.modele.yml).
### Modo 2 — token de máquina Bearer (alternativa sin CI OIDC)
Para contextos que no pueden obtener un token OIDC (cron en un VPS, otra CI,
scripts manuales):
| Endpoint | Auth | Respuesta |
|---|---|---|
| `GET /api/secrets/[slug]/[env]` | `Bearer sv_<hex>` | `{ secrets: { KEY: value, … } }` |
| `GET /api/compose/[slug]/[env]` | `Bearer sv_<hex>` | contenido bruto del `docker-compose.yml` configurado |
El token está limitado a un par `(proyecto, entorno)`; cualquier otra
combinación devuelve 403. Se gestiona desde la página del proyecto → pestaña del
entorno → «Machine tokens».
### Modo 3 — script local
[scripts/inject-secrets.sh](scripts/inject-secrets.sh) — un wrapper de bash
sobre el endpoint Bearer, útil cuando la misma lógica se invoca desde varios
scripts en el mismo VPS.
---
## Generar los secretos necesarios al inicializar
```bash
openssl rand -hex 32 # ENCRYPTION_KEY
openssl rand -base64 32 # AUTH_SECRET / NEXTAUTH_SECRET
```
> ⚠️ `ENCRYPTION_KEY`: **nunca en la base de datos ni en el código**, solo en el
> entorno del contenedor. Perderla de forma definitiva significa que los
> secretos son irrecuperables, incluso con un volcado completo de la base de
> datos. Guarda una copia en un gestor de contraseñas compartido (escrow).
---
## Copias de seguridad de tu instancia
**Este repositorio no proporciona copias de seguridad, ni replicación, ni
conmutación por error** — ningún script, ningún cron, ningún mecanismo de
bascula. La copia de seguridad de tu instancia es tu infraestructura y, por
tanto, tu responsabilidad. Como mínimo:
- un **volcado periódico** de la base de datos PostgreSQL (`pg_dump`), cifrado y
almacenado fuera del servidor que lo ha producido;
- una **copia de `ENCRYPTION_KEY`** en escrow (véase la advertencia anterior):
un volcado sin la clave es irrecuperable, y la clave sin volcado no vale nada;
- una **prueba de restauración** periódica; de lo contrario no tienes una copia
de seguridad, tienes archivos.
La oferta alojada [physalis.cloud](https://physalis.cloud) opera su propia
infraestructura de resiliencia (replicación y copias cifradas gestionadas); nada
de eso está incluido ni orquestado por este repositorio.
---
## Stack
Next.js 15 (App Router) · TypeScript · Prisma 6 + PostgreSQL 16 ·
NextAuth v5 (Credentials, JWT) · bcryptjs (salt 12) · AES-256-GCM ·
híbrido ECDH P-256 + ML-KEM-768 (`@noble/post-quantum`) ·
jose 6 (OIDC JWKS) · 2FA TOTP (otplib) · Tailwind 3 · Mailgun (correo
transaccional) · Docker multi-stage (node:22-alpine).
## Tests
```bash
npm test # unitarios (crypto, tokens, rate-limit, validación, totp,
# oidc, categorías, plugin-token)
npm run test:integ # integración (bearer-auth, RBAC, cifrado en BD, cabeceras,
# rate-limit, 2FA, servidores, policies, plugin)
```
Consulta [docs/physalis.md §12](docs/physalis.md) para el detalle.