janus-secrets

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0

README

Janus

A self-hosted secrets manager you actually own. One Go binary plus PostgreSQL — no SaaS, no multi-tenancy, no per-seat pricing, and your keys never leave your server in plaintext.

ci release license

Janus stores secrets in a project → environment → config tree, injects them into your process with janus run, and records every read in a hash-chained audit log. It also does the things you would otherwise bolt on later: encryption-as-a-service, scheduled rotation, one-way sync to your CI and clusters, and short-lived database credentials.

janus run -- ./my-service      # secrets arrive as env vars; nothing hits disk

Quickstart

git clone https://github.com/steveokay/janus-secrets.git
cd janus-secrets
make dev-up        # builds, starts Postgres + Janus, then inits and unseals a 1-of-1 dev seal

The UI is now on http://localhost:8210. make dev-up prints a one-time admin password — that is your login.

The dev seal keeps its single share in .dev/janus-share, and that share is the master key. It exists to make local development painless and is not a production posture — see Unseal below.

Then, from any project directory:

janus login --address http://localhost:8210
janus setup                             # binds this directory to a project/env/config
janus secrets set DATABASE_URL=postgres://…
janus run -- ./my-service               # injected as environment variables

New here? The getting-started guide walks the same path with more explanation, including doing it by hand instead of via make dev-up.

What you get

Secrets

  • A project → environment → config tree with per-key secrets.
  • Two-level versioning: each save creates one immutable config version (the unit of diff and rollback), and every key also keeps its own value history. Soft delete with restore; hard destroy is separate and explicit.
  • Inheritance from a base config, and references between secrets (${projects.app.prod.KEY}), resolved at read time with cycle detection.
  • Advisory max-age and unused-key flags, owner/note annotations, and typed values.

Getting secrets to where they run

  • janus run injects into a subprocess — with --watch to restart it when the config changes — and janus render fills a template.
  • Sync one-way to GitHub Actions, GitLab CI, Kubernetes, Cloudflare, Vercel, Netlify, AWS SSM and AWS Secrets Manager, with drift detection that reads the destination back.
  • Import from .env / .properties files, or from Doppler, Vault KV and AWS Secrets Manager — in the CLI or a paste-based wizard in the UI.
  • SDKs for Go, TypeScript and Python, and a Terraform provider.

Access

  • Email + password (Argon2id) with optional TOTP, WebAuthn passkeys as a single-step sign-in, and OIDC login.
  • Scoped service tokens for machines, and OIDC-federated workload identity so GitHub Actions or a Kubernetes pod exchanges its own JWT for a short-lived token — no long-lived secret in CI.
  • Deny-by-default RBAC (viewer ⊂ developer ⊂ admin ⊂ owner) at instance, project or environment scope, plus break-glass time-boxed elevation.
  • Four-eyes approval for protected configs and environment promotion.

Accountability

  • An append-only, hash-chained audit log. Recording is fail-closed: if the audit write fails, the request fails.
  • GET /v1/audit/verify walks the chain; the UI surfaces a "chain verified" badge. Export as JSONL or CSV, or ship it to a webhook or syslog.
  • Signed checkpoints let a long log be pruned without breaking verification.

More than storage

  • Transit — encryption as a service, with named keys, versioning and rewrap. Janus holds the keys, your app holds the ciphertext.
  • Rotation — scheduled rotators for Postgres, MySQL, Redis, AWS IAM, OAuth clients and generic webhooks.
  • Dynamic credentials — short-lived Postgres roles with a lease manager that renews and revokes.

Operating it

  • A Svelte SPA embedded in the binary and served same-origin — no Node in production, and it works on a phone.
  • Prometheus /metrics, a health panel, and a ready-made Grafana dashboard + alerts.
  • Encrypted backups (including scheduled to S3-compatible storage), master-key and project-key rotation, and a native TLS listener with optional ACME.

How it works

Envelope encryption

Three levels, so no single stored value can decrypt anything on its own:

Key Lives Wrapped by
Master key (root KEK) server memory only, after unseal never persisted in plaintext
Project KEK Postgres, wrapped the master key
DEK (one per secret version) Postgres, wrapped that project's KEK

Values are AES-256-GCM with random, never-reused nonces. Every ciphertext's AAD binds it to its exact storage slot — project, config, key, version — so ciphertext moved or swapped between slots fails to decrypt rather than silently decrypting as another secret. The storage layer is crypto-blind: it persists opaque bytes and never holds a key or a plaintext.

Unseal — the server starts locked

The master key is never written down. On boot Janus is sealed and every secret operation returns 503 until an operator unseals it, either by submitting Shamir shares (k-of-n, default 3-of-5, janus init prints them exactly once) or automatically via cloud KMS (AWS KMS, GCP KMS, Azure Key Vault).

Details, including key rotation and the key-check value: docs/crypto.md.

Configuration

The server is configured by environment only. The essentials:

Variable Meaning
JANUS_DATABASE_URL Postgres DSN (required)
JANUS_LISTEN_ADDR listen address, default :8200
JANUS_SEAL_TYPE shamir, awskms, gcpkms or azurekv — set before first init; the stored type wins afterwards
JANUS_ADDR the CLI's default server address

Every variable is listed in docs/operations.md.

Deploying

The binary and a multi-arch container publish automatically on a release tag. The production-deployment guide covers TLS, unseal strategy, sizing, backups and upgrades, and its deployment modes section covers Docker Compose, Kubernetes, Swarm, Argo CD / Flux, Nomad and systemd. There is a Helm chart for Kubernetes.

Verify what you run. Releases are cosign keyless-signed with syft SBOMs and SLSA build-provenance attestations — check them with cosign verify-blob or gh attestation verify.

Documentation

Everything lives under docs/ — start at the documentation index.

Guidesgetting started · injecting secrets · managing secrets · the web UI · service tokens · members & RBAC · groups · passkeys · two-factor auth · SSO & federation · promoting between environments · protected configs · importing & exporting · GitHub Actions · Docker · Kubernetes · observability · backup & restore · break-glass

Referencearchitecture · cryptography · data model & versioning · references & inheritance · CLI · operations · transit · OIDC · CI federation · web UI · OpenAPI spec · threat model

Enginesrotation · sync · dynamic secrets · backup & restore

Security

  • AES-256-GCM everywhere, Argon2id for passwords, HMAC-SHA256 for token hashing — only token hashes are stored, never the tokens. Constant-time comparison for every key-check, token and MAC check.
  • No plaintext secret or key material in logs or errors, enforced by leak tests at the crypto, secrets, HTTP and audit layers. An audit Event has no value field by construction, so a secret cannot enter the audit log even by mistake.
  • The crypto is Go's standard library and golang.org/x/crypto only. Two exceptions are recorded, both for standards work that should not be hand-rolled: JOSE/JWKS verification for OIDC, and COSE/attestation parsing for passkeys. Envelope, transit and unseal crypto remain stdlib.
  • Outbound requests are SSRF-hardened: a shared dialer re-checks the resolved IP on every dial (defeating DNS rebinding), blocks link-local and cloud-metadata ranges, and caps redirects — applied to every operator-configured caller, including OIDC discovery.
  • internal/crypto is held to 100% statement coverage, enforced in CI, including tamper and nonce-reuse cases. CI also runs govulncheck and gosec as build failures.

Full detail in docs/threat-model.md, which is explicit about what Janus does not defend against. To report a vulnerability, see SECURITY.md.

Non-goals

Deliberately out of scope: HA / Raft clustering (run one node with Postgres backups), PKI / certificate authority, SSH signing, HSM / PKCS#11, multi-tenancy and organizations, and FIPS certification claims.

Contributing

Build instructions, the test suite, the CI gates and the crypto and migration rules are in CONTRIBUTING.md. The short version:

make dev-up     # full local stack
make test       # every module, plus the web tests

The current state and remaining work are tracked in docs/roadmap.md and status.md.

Trademarks

Third-party product and company names used in this project — including Doppler, HashiCorp Vault, Amazon Web Services (AWS), Google Cloud, Microsoft Azure, Kubernetes, GitHub, GitLab, and any others — are the trademarks or registered trademarks of their respective owners. Janus is an independent project and is not affiliated with, endorsed by, or sponsored by any of them. Such names appear here solely to identify the third-party systems that Janus interoperates with (for example, the janus import source systems and the cloud KMS providers used for auto-unseal), which is nominative use.

License

Janus is licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.

The vendored internal/crypto/shamir/ package is licensed under MPL-2.0 (see its LICENSE); its per-file headers are retained. MPL-2.0 is file-level copyleft and compatible with Apache-2.0 distribution.

Directories

Path Synopsis
cmd
janus command
Command janus is the Janus server and its operator CLI.
Command janus is the Janus server and its operator CLI.
internal
api
Package api is Janus's HTTP surface: the chi router, the /v1/sys/* seal lifecycle endpoints, the RequireUnsealed middleware, and the project-wide JSON error envelope.
Package api is Janus's HTTP surface: the chi router, the /v1/sys/* seal lifecycle endpoints, the RequireUnsealed middleware, and the project-wide JSON error envelope.
auditship
Package auditship streams the (value-free) audit log to an external SIEM destination — a webhook or a syslog collector — as newline-delimited JSON.
Package auditship streams the (value-free) audit log to an external SIEM destination — a webhook or a syslog collector — as newline-delimited JSON.
auth
Package auth is Janus's identity layer: Argon2id passwords, Postgres-backed sessions, and janus_svc_ service tokens, all HMAC-hashed at rest with a master-key-wrapped key.
Package auth is Janus's identity layer: Argon2id passwords, Postgres-backed sessions, and janus_svc_ service tokens, all HMAC-hashed at rest with a master-key-wrapped key.
backupsched
Package backupsched is Janus's scheduled encrypted-backup engine: on a configured interval it runs the existing key-preserving instance dump and uploads the resulting sealed artifact to S3-compatible object storage, applies retention (keep N most recent, prune the rest), records each attempt in backup_runs (value-free), and offers a restore-rehearsal that verifies the latest (or a named) backup restores WITHOUT touching the live instance.
Package backupsched is Janus's scheduled encrypted-backup engine: on a configured interval it runs the existing key-preserving instance dump and uploads the resulting sealed artifact to S3-compatible object storage, applies retention (keep N most recent, prune the rest), records each attempt in backup_runs (value-free), and offers a restore-rehearsal that verifies the latest (or a named) backup restores WITHOUT touching the live instance.
crypto
Package crypto implements Janus's envelope encryption: AES-256-GCM primitives, key wrapping with AAD binding, the in-memory keyring, and the Shamir and AWS KMS unseal mechanisms.
Package crypto implements Janus's envelope encryption: AES-256-GCM primitives, key wrapping with AAD binding, the in-memory keyring, and the Shamir and AWS KMS unseal mechanisms.
dynamic
Package dynamic is Janus's dynamic Postgres credentials engine: on-demand, short-lived database roles issued from admin-authored SQL templates, with a lease manager that revokes them on expiry (and reclaims crash-orphaned leases after unseal).
Package dynamic is Janus's dynamic Postgres credentials engine: on-demand, short-lived database roles issued from admin-authored SQL templates, with a lease manager that revokes them on expiry (and reclaims crash-orphaned leases after unseal).
editreq
Package editreq implements the four-eyes approval flow for edits to a protected config (configs.require_approval = true).
Package editreq implements the four-eyes approval flow for edits to a protected config (configs.require_approval = true).
masterkeys
Package masterkeys orchestrates rotation of the root master key.
Package masterkeys orchestrates rotation of the root master key.
metrics
Package metrics is a tiny, dependency-free Prometheus text-exposition layer.
Package metrics is a tiny, dependency-free Prometheus text-exposition layer.
nethard
Package nethard provides SSRF hardening for Janus's operator-configured outbound clients (notification webhooks, rotation webhooks/DB dials, secret sync providers).
Package nethard provides SSRF hardening for Janus's operator-configured outbound clients (notification webhooks, rotation webhooks/DB dials, secret sync providers).
notification
Package notification delivers outbound alerts (generic webhook, Slack) by tailing the value-free audit log and fanning matching events out to configured channels through a crash-safe delivery outbox.
Package notification delivers outbound alerts (generic webhook, Slack) by tailing the value-free audit log and fanning matching events out to configured channels through a crash-safe delivery outbox.
projectkeys
Package projectkeys rotates a project's KEK and lazily re-wraps its DEKs; never decrypts a secret value; zeroes key material after use.
Package projectkeys rotates a project's KEK and lazily re-wraps its DEKs; never decrypts a secret value; zeroes key material after use.
promote
Package promote moves selected secrets forward along a project's release pipeline.
Package promote moves selected secrets forward along a project's release pipeline.
resolve
Package resolve composes config inheritance and read-time secret references over two ports (RawReader, Authorizer).
Package resolve composes config inheritance and read-time secret references over two ports (RawReader, Authorizer).
rotation
Package rotation is Janus's static-rotation engine: scheduled rotation of an existing secret's value via a Postgres single-role reset or a generic HMAC-signed webhook, with crash-safe apply and optional notify webhooks.
Package rotation is Janus's static-rotation engine: scheduled rotation of an existing secret's value via a Postgres single-role reset or a generic HMAC-signed webhook, with crash-safe apply and optional notify webhooks.
secrets
Package secrets orchestrates internal/crypto and internal/store: it is the only component that holds an unsealed key or sees plaintext, and only transiently within a call.
Package secrets orchestrates internal/crypto and internal/store: it is the only component that holds an unsealed key or sees plaintext, and only transiently within a call.
secretsync
Package secretsync is Janus's outbound sync engine: scheduled one-way replication of a config's resolved secrets to external stores (GitHub Actions secrets, Kubernetes Secrets).
Package secretsync is Janus's outbound sync engine: scheduled one-way replication of a config's resolved secrets to external stores (GitHub Actions secrets, Kubernetes Secrets).
store
Package store is Janus's crypto-blind PostgreSQL persistence layer.
Package store is Janus's crypto-blind PostgreSQL persistence layer.
version
Package version carries build metadata injected at release time via -ldflags "-X github.com/steveokay/janus-secrets/internal/version.Version=…".
Package version carries build metadata injected at release time via -ldflags "-X github.com/steveokay/janus-secrets/internal/version.Version=…".
web
Package web embeds the built React SPA and serves it same-origin from the Go binary.
Package web embeds the built React SPA and serves it same-origin from the Go binary.
Package migrations embeds the SQL migration files so they ship inside the binary.
Package migrations embeds the SQL migration files so they ship inside the binary.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL