Sign inSign up

leganuxservices/llavero-keychain

By leganuxservices

β€’Updated about 1 month ago

Credential-Aware Reverse Proxy / Secret Injection Gateway in Node.js + TypeScript.

Image
0

103

leganuxservices/llavero-keychain repository overview

Llavero

Β Β  by Storitime

Llavero Logo

⁠Llavero

Credential-Aware Reverse Proxy / Secret Injection Gateway in Node.js + TypeScript.

English | Español⁠

πŸš€ New here? Follow the step-by-step tutorial (for dummies): docs/TUTORIAL.md⁠

⁠The problem

Your applications need to call third-party APIs (OpenAI, Stripe, AWS...), which means scattering real credentials across repos, environment variables, and services. Rotating a key means touching everything; auditing who uses it is impossible; leaking it is a disaster.

⁠The solution

Applications call Llavero with an internal token (llv_*). Llavero authenticates the consumer, checks permissions, decrypts the real credential in memory, signs/modifies the request with an auth driver, and forwards it to the provider. The real secret never leaves Llavero: not in responses, not in logs, not in the API.

Application
    β”‚  Authorization: Bearer llv_dev_xxx
    β–Ό
Llavero ── validate token ── policy (deny by default) ── resolve service/environment
    │── decrypt credential (AES-256-GCM) ── auth driver (bearer/oauth2/aws-sigv4...)
    β–Ό
External API          (the real secret only travels to the registered hostname)

⁠Features

  • Reverse proxy with native streaming (SSE, NDJSON, uploads/downloads without buffering).
  • Transparent WebSocket proxy (driver-authenticated handshake + bidirectional tunnel).
  • 6 swappable auth drivers: bearer, api-key, basic, cookie, oauth2 (refresh flow with cache), aws-sigv4 β€” hot enable/disable.
  • AES-256-GCM encrypted credentials; master key only in memory (LLAVERO_MASTER_KEY).
  • Internal tokens llv_<env>_* β€” SHA-256 only in DB; shown once.
  • Deny-by-default policies per client/service/environment/method/path.
  • Environments and aliases: openai-current can retarget from staging to production without touching clients.
  • Plugins (before/after/onError hooks) with an example RateLimitPlugin.
  • Audit for every request (client, service, env, method, path, status, latency).
  • Security: SSRF guard, no redirect-following, header sanitization, log redaction.
  • Admin API + SSR Dashboard to operate without touching the database.

⁠Stack

Node.js 24+ Β· TypeScript Β· Fastify Β· SQLite (better-sqlite3) Β· Drizzle ORM Β· node:crypto Β· Undici Β· Pino Β· Zod Β· ws Β· Docker

⁠Quickstart (local)

Requires: Node.js 22+ (24 recommended).

npm install

# 1. Master key (required in production; ephemeral one is generated in dev)
export LLAVERO_MASTER_KEY=$(openssl rand -hex 32)

npm run dev    # http://localhost:3000
⁠2. Provision (Admin API)
ADMIN=localhost:3000/admin

# Service (upstream)
curl -s -X POST $ADMIN/services -H 'content-type: application/json' -d '{
  "name": "OpenAI", "slug": "openai",
  "baseUrl": "https://api.openai.com", "authDriver": "bearer"
}'
# β†’ keep the returned "id"

# Environment
curl -s -X POST $ADMIN/environments -H 'content-type: application/json' \
  -d '{"serviceId":"<service-id>","name":"staging"}'
# β†’ keep the environment "id"

# Credential (auto-encrypted; never shown again)
curl -s -X POST $ADMIN/credentials -H 'content-type: application/json' \
  -d '{"serviceId":"<service-id>","name":"primary","secret":{"token":"sk-real-openai-key"}}'
# β†’ keep the credential "id"

Attach the credential to the environment from the dashboard (/dashboard/services/<id>), the CLI (llavero environments attach), or the API (POST /admin/environments/:id/credential).

# Client (your consumer application)
curl -s -X POST $ADMIN/clients -H 'content-type: application/json' -d '{"name":"my-backend"}'

# Internal token β€” SHOWN ONLY ONCE
curl -s -X POST $ADMIN/clients/<client-id>/tokens -H 'content-type: application/json' \
  -d '{"environment":"stg"}'
# β†’ {"token":"llv_stg_...", ...}  save it

# Policy (without an explicit policy EVERYTHING is rejected: deny by default)
curl -s -X POST $ADMIN/policies -H 'content-type: application/json' -d '{
  "clientId":"<client-id>", "serviceId":"<service-id>",
  "environments":["staging"], "methods":["POST"], "paths":["/v1/*"]
}'
⁠3. First request through the gateway
curl -X POST localhost:3000/gateway/openai/v1/responses \
  -H 'Authorization: Bearer llv_stg_...' \
  -H 'x-llavero-env: staging' \
  -H 'content-type: application/json' \
  -d '{"model":"gpt-5","input":"hello"}'

Llavero turns it into https://api.openai.com/v1/responses with Authorization: Bearer sk-real-openai-key β€” your app never knows that key.

Alternative to the header: create an alias (openai-current β†’ openai/staging) and call /gateway/openai-current/v1/responses with no x-llavero-env.

⁠Docker

export LLAVERO_MASTER_KEY=$(openssl rand -hex 32)
export LLAVERO_ADMIN_KEY=$(openssl rand -hex 24)   # enables /admin and /dashboard in prod
docker compose up -d

SQLite persists in the llavero-data volume (/data/llavero.sqlite). Migrations are applied automatically on startup.

⁠Dashboard

http://localhost:3000/dashboard β€” minimal SSR over the same managers as the API: Services (create, environments, credentials), Aliases (create/retarget), Clients (tokens), Policies, Audit.

Dashboard access: login form with LLAVERO_ADMIN_KEY or an llv_* token from a client marked as admin (isAdmin). Successful login sets an HttpOnly cookie (the credential never travels in URLs). A token from a non-admin client is rejected. In production without LLAVERO_ADMIN_KEY the whole control plane returns 503.

⁠SDK + CLI

  • sdk/⁠: LlaveroClient to operate the full control plane (Admin API) and execute through the gateway. Zero dependencies.
  • cli/⁠: Commander-based CLI on top of the SDK. llavero config (saves url + admin key in ~/.llavero/config.json), commands services|environments| credentials|aliases|clients|policies|audit|drivers|plugins and call (gateway, streaming to stdout).
cd cli && npm install
node cli.mjs config
node cli.mjs call openai /v1/models --env dev --token llv_dev_...

⁠Client demo (examples/client-demo)

Minimal project consuming the gateway with an llv_* token (zero dependencies, native fetch):

cd examples/client-demo
npm run provision        # creates services/clients/policies/token via Admin API
npm run demo:openai      # OpenAI (bearer)
npm run demo:mercadopago # MercadoPago (bearer)
npm run demo:google      # Firebase/Identity Toolkit + userinfo (oauth2)
npm run demo:stripe      # Stripe (bearer)
npm run demo:aws-s3      # PUT/GET object signed (aws-sigv4)
npm run demo:api-key     # httpbin reflects the injected header (api-key)

See examples/client-demo/README.md⁠.

⁠Configuration

VariableDescriptionDefault
LLAVERO_MASTER_KEYAES-256-GCM key, 64 hex (openssl rand -hex 32). Never persistedephemeral in dev/test, required in prod
LLAVERO_ADMIN_KEYAdmin API/Dashboard key (min 16). Empty β†’ open in dev/test, 503 in prodβ€”
LLAVERO_PORTHTTP port3000
LLAVERO_HOSTListen host0.0.0.0
DATABASE_URLSQLite path (:memory: for tests)./data/llavero.sqlite
LOG_LEVELPino levelinfo
NODE_ENVdevelopment | test | productiondevelopment

⁠Credentials per driver (secret field)

DriverauthDriversecret
Bearerbearer{ "token": "sk-..." }
API Keyapi-key{ "apiKey": "..." } + driverConfig { "location": "header|query", "name": "X-API-Key" }
Basicbasic{ "username": "...", "password": "..." }
Cookiecookie{ "cookie": "session=..." }
OAuth2oauth2{ "clientId", "clientSecret", "refreshToken", "tokenEndpoint" }
AWS SigV4aws-sigv4{ "accessKeyId", "secretAccessKey", "region", "service" }

⁠Example seeds

npm run seed   # creates openai, stripe, mercadopago, google (oauth2) and aws-s3
               # with dev/staging/production environments and PLACEHOLDER credentials
               # (idempotent; replace placeholders with real secrets)

⁠Security (invariants)

  • Encryption at rest (AES-256-GCM) and master key only in memory.
  • Internal tokens as SHA-256 hash; never in plaintext.
  • Upstream allowlist: the target URL ALWAYS derives from service.baseUrl; paths pointing to another origin β†’ 400.
  • Redirect-following disabled: a 3xx is returned to the client unfollowed (credential never travels to another hostname).
  • Deny-by-default policies.
  • Control plane auth: dashboard login (admin key or admin-client llv_* token) with HttpOnly cookie; Admin API via x-llavero-admin-key header. No credentials in URLs.
  • Redaction of authorization, cookie, set-cookie, x-api-key, *_secret, *_token, password in all logs.
  • No endpoint returns credential material (metadata only).

⁠Project status

PhaseContentStatus
1Bootstrap: TypeScript, Fastify, Drizzle, SQLite, Pino, Zod, Vitest, Docker, /health /ready, migrationsβœ…
2Services, Environments, Credentials, AES-256-GCM, Repositoriesβœ…
3Clients, llv_* tokens, Policy Engineβœ…
4AuthDriver interface, Registry, Bearer/ApiKey/Basicβœ…
5HTTP Gateway, Route Resolver, Header Sanitizer, Upstream Client, Streamingβœ…
6WebSocket proxyβœ…
7Cookie/OAuth2/AWS SigV4 driversβœ…
8Plugin interface, PluginManagerβœ…
9Admin API, minimal Dashboardβœ…
10Docs, seeds, Docker Compose β€” 0.1.0 releaseβœ…

Full design and rules in docs/master-prompt.md⁠ (Spanish).

⁠Structure

src/
  config/ConfigService.ts        # Centralized config (env + Zod)
  core/
    logger.ts                    # Pino with secret redaction
    errors/index.ts              # Domain errors (AppError, NotFound, 401/403/429/5xx)
  auth-drivers/                  # Interface + Registry + 6 swappable drivers
  gateway/
    HttpGateway.ts               # auth β†’ policy β†’ route β†’ credential β†’ driver β†’ upstream β†’ audit
    WebSocketGateway.ts          # Driver handshake + transparent tunnel
    RouteResolver.ts             # Alias or slug + x-llavero-env
    UpstreamUrlBuilder.ts        # SSRF guard (origin = registered baseUrl)
    HeaderSanitizer.ts           # Drop hop-by-hop, auth, cookie, host
    UpstreamClient.ts            # Interface (+ UndiciUpstreamClient, no redirects)
  services/                      # ServiceManager, EnvironmentManager, AliasManager
  credentials/                   # CredentialManager + EncryptionProvider (AES-GCM)
  clients/                       # ClientManager, TokenManager (llv_*)
  policies/PolicyEngine.ts       # deny-by-default, '*' and prefixes
  plugins/                       # PluginManager + RateLimitPlugin
  audit/AuditService.ts          # Per-request metadata; never secrets
  admin/                         # Admin REST API + SSR Dashboard (same manager layer)
  database/                      # DatabaseProvider, schema, repositories, migrations
  bootstrap.ts                   # AppContainer (manual DI)
  app.ts / server.ts
scripts/seed-examples.ts         # Example seeds (npm run seed)
sdk/                             # Client SDK (LlaveroClient)
cli/                             # CLI (Commander + SDK)
examples/client-demo/            # Gateway consumption demo
tests/                           # Vitest (79 tests)

⁠Scripts

CommandDescription
npm run devDev with watch (tsx)
npm run buildCompile to dist/ + copy SQL migrations
npm startRun dist/server.js
npm testVitest (79 tests)
npm run typecheckTypeScript, no emit
npm run db:generateGenerate migration after schema changes
npm run seedExample seeds (idempotent)

⁠Health checks

curl localhost:3000/health   # {"status":"ok"} β€” process alive
curl localhost:3000/ready    # checks: database, masterKey, core β†’ 200 or 503

⁠License

MIT

Tag summary

Content type

Image

Digest

sha256:b0b824a1f…

Size

84.7 MB

Last updated

about 1 month ago

docker pull leganuxservices/llavero-keychain