Skip to content

Latest commit

 

History

History
164 lines (129 loc) · 7.18 KB

File metadata and controls

164 lines (129 loc) · 7.18 KB

nsly-sync-server ☁️🔐

Servidor de sincronización para NSLY. Implementa exactamente el protocolo que el cliente ya espera:

PUT {URL}  Authorization: Bearer {token}  <body = blob cifrado>  → guarda
GET {URL}  Authorization: Bearer {token}                        → devuelve

El servidor nunca ve tus contraseñas en claro. Solo guarda y devuelve el blob que NSLY ya cifró con AES-256-GCM antes de subirlo — es un almacén ciego. Aunque alguien comprometiera este servidor por completo, solo se llevaría bytes cifrados.

Es multi-tenant: un solo servidor puede alojar varias bóvedas (vos, tu pareja, tu equipo...), cada una en /vault/{id} con su propio token. Para compatibilidad con el ejemplo mínimo de un solo usuario del README principal, /vault (sin id) equivale a /vault/default.

Cero dependencias externas — todo es librería estándar de Go. Se compila a un único binario estático, igual de portable que el propio NSLY.

Quickstart

cd server-example
go test ./...              # correr los tests (ver qué se cubre más abajo)
make build                 # compila ./nsly-sync-server

./nsly-sync-server adduser andres
# ✅ Bóveda creada: andres
# ⚠️  Este token se muestra UNA sola vez. Anótalo en un lugar seguro:
#    aB3x...   (256 bits, cópialo ahora)
#
# Configura en NSLY → ☁️ Sync → ⚙️ Configurar:
#    URL:   https://tu-servidor/vault/andres
#    Token: (el de arriba)

NSLY_LISTEN=:8080 ./nsly-sync-server serve

En NSLY: Menú → ☁️ Sync → ⚙️ Configurar con esa URL y ese token.

⚠️ El cliente NSLY rechaza URLs que no empiecen con https:// (a propósito, para no viajar el token en texto plano). Para desarrollo local podés generar un certificado autofirmado o simplemente probar el servidor con curl sin pasar por NSLY todavía — ver más abajo.

Subcomandos de administración

Estos corren sin necesidad de tener el servidor levantado (operan directo sobre tokens.json):

Comando Qué hace
adduser <id> Crea una bóveda nueva y genera su token (se muestra una sola vez)
rotate <id> Genera un token nuevo para una bóveda existente; el anterior queda invalidado al instante
rmuser <id> [--delete-data] Revoca el acceso; con --delete-data borra también el blob cifrado
list Lista los ids de bóvedas registradas (nunca muestra tokens)

Todos aceptan --data-dir (default: $NSLY_DATA_DIR o ./data).

Variables de entorno (serve)

Variable Default Descripción
NSLY_LISTEN :8443 Dirección/puerto a escuchar
NSLY_DATA_DIR ./data Dónde viven tokens.json y los blobs .blob
NSLY_TOKENS_FILE <data-dir>/tokens.json Ruta del archivo de tokens
NSLY_MAX_BODY_BYTES 5242880 (5 MiB) Límite de tamaño del blob subido
NSLY_TLS_CERT / NSLY_TLS_KEY (vacío) Si ambas están seteadas, sirve HTTPS directo sin proxy

Qué está probado (go test -race -cover ./...)

  • Round-trip PUT → GET devuelve exactamente los mismos bytes.
  • PUT/GET sin token, o con token incorrecto → 401, sin importar si el id existe.
  • GET de una bóveda que nunca recibió un PUT → 404 (primer sync).
  • Ruta legacy /vault (sin id) → mapea a default, para compatibilidad.
  • Body más grande que el límite configurado → 413.
  • Un id con .. o / (intento de path traversal) → 400, nunca llega al filesystem.
  • Método no soportado (POST, etc.) → 405.
  • Body vacío → 400.
  • Al subir una segunda versión, la anterior queda en <id>.blob.bak (rollback manual barato).
  • rotate invalida el token anterior de inmediato.
  • El store de tokens persiste correctamente a disco y se recarga bien.

Despliegue en producción

Opción A: Docker + Caddy (recomendada, TLS automático)

cp Caddyfile.example Caddyfile
# editá el dominio en Caddyfile
docker compose up -d
docker compose exec nsly-sync /nsly-sync-server adduder andres   # (ver nota abajo)

Nota: la imagen Docker es distroless (sin shell, endurecida), así que docker compose exec ... sh no existe. Para crear usuarios contra los datos del volumen, corré el binario compilado localmente apuntando al mismo NSLY_DATA_DIR montado, o agregá un contenedor "admin" temporal con la imagen golang:alpine que monte el volumen nsly-data.

docker-compose.yml deja el servidor NSLY solo en la red interna (expose, no ports) — únicamente Caddy queda expuesto a internet, terminando TLS y renovando el certificado solo.

Opción B: systemd + nginx/Caddy ya existentes

make release                                  # cross-compila a dist/
sudo useradd --system --home /var/lib/nsly-sync --create-home nsly-sync
sudo cp dist/nsly-sync-server-linux-amd64 /usr/local/bin/nsly-sync-server
sudo cp nsly-sync-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nsly-sync-server
sudo -u nsly-sync nsly-sync-server adduser andres --data-dir /var/lib/nsly-sync/data

Luego apuntá tu nginx (nginx.conf.example) o Caddy existente a 127.0.0.1:8080 (o el puerto que hayas puesto en NSLY_LISTEN).

Opción C: TLS directo, sin reverse proxy

Para un homelab o un uso muy simple, sin nginx/Caddy delante:

NSLY_LISTEN=:8443 \
NSLY_TLS_CERT=/ruta/fullchain.pem \
NSLY_TLS_KEY=/ruta/privkey.pem \
./nsly-sync-server serve

Seguridad — qué hace y qué NO hace este servidor

Hace:

  • Compara tokens en tiempo constante (crypto/subtle) — no filtra el token por timing.
  • Guarda solo sha256(token), nunca el token en claro.
  • Aplica un delay fijo (300ms) ante auth fallida, para encarecer fuerza bruta.
  • Valida los ids de bóveda contra una lista blanca de caracteres — cero path traversal posible.
  • Escribe los blobs de forma atómica (.tmp + rename) y conserva la versión anterior en .bak.
  • Limita el tamaño del body subido (NSLY_MAX_BODY_BYTES).
  • Apagado ordenado ante SIGINT/SIGTERM (no corta un PUT a la mitad).

NO hace (y es intencional):

  • No descifra ni entiende el contenido de tu bóveda — es un almacén ciego.
  • No reemplaza HTTPS: si corrés serve sin NSLY_TLS_CERT/NSLY_TLS_KEY y sin poner un reverse proxy con TLS delante, el tráfico viaja sin cifrar en la red. El cliente NSLY ya rechaza URLs http:// por esta razón — no hay forma de "saltarse" esto sin editar el propio NSLY.
  • No implementa rate-limiting sofisticado por IP (más allá del delay fijo en fallos): a esta escala, un firewall (fail2ban, ufw) delante es más apropiado que reinventarlo acá.
  • No mantiene un historial completo de versiones — solo un .bak (la versión inmediatamente anterior). Si necesitás versionado real, lo más simple es apuntar NSLY_DATA_DIR a una carpeta bajo control de git o restic/borg con snapshots periódicos.

Migrando desde el ejemplo mínimo de Node.js del README principal

Si ya tenías el snippet de Express corriendo: apagalo, montá este binario apuntando a un NSLY_DATA_DIR nuevo, corré adduser default (mismo id que usaba el ejemplo viejo), y actualizá el token en NSLY → Sync → Configurar. La URL puede seguir siendo la misma (/vault) gracias a la compatibilidad legacy.