Skip to content

feat: OpenBao-backed secrets for docker-local and vps - #176

Open
BKJN1 wants to merge 10 commits into
mainfrom
feat/openbao-secrets
Open

feat: OpenBao-backed secrets for docker-local and vps#176
BKJN1 wants to merge 10 commits into
mainfrom
feat/openbao-secrets

Conversation

@BKJN1

@BKJN1 BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Generated configs no longer contain a secret's real value. Every secret
field (JWT signing key, session/cookie secrets, Postgres password, admin
bootstrap password) is written as a ${?VAR} HOCON placeholder instead
(see gen-env.scala's secretField/secretKeyField) — versola-cli resolves
each one against an OpenBao server before starting anything: an existing
value wins over regenerating one, so secrets survive being reconfigured
rather than rotating on every deploy. Companion PR in versola-cli:
versolauth/versola-cli#7.

What's in this PR:

  • openbao service added to the local docker-compose stack (KV v2 +
    AppRole), with a persistent external volume so its data survives a
    fresh bundle directory each configure run.
  • docker-local's auth/central/edge configs placeholder every secret field
    instead of writing it literally. This includes the JWT/edge RSA key
    pairs' public halves (JWKS, edge's public-key-jwk), not just the
    private ones — both halves have to resolve from the same source or a
    second configure reuses the old private key from OpenBao while writing
    a freshly generated public one, a mismatch that silently broke edge's
    sync calls to central (401s) before this was fixed.
  • A vps branch in gen-env.scala: same placeholder mechanism, but
    non-interactive host-network defaults (network_mode: host,
    127.0.0.1 addressing, the real id.versola.kz domain) for the one
    real VPS this deploys to, plus a vps-only compose template (no
    postgres/nginx — both native on the VPS) and a TARGET-aware
    entrypoint.sh so one versola-tools image serves both targets.
  • develop.md: the one-time OpenBao setup procedure (init/unseal/enable
    KV+AppRole/policy/role) for both targets, and the vps-specific note on
    seeding the real Postgres password before the first deploy (it isn't
    something this script can generate — the Postgres role already exists).

Verified locally end-to-end, including a real browser login against the
docker-local stack after a fresh configure.

@augmentcode

augmentcode Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor
🤖 Augment PR Summary

Summary: This PR moves docker-local and VPS deployment secret resolution to OpenBao.

Changes:

  • Generates HOCON environment placeholders and companion candidate dotenv secret files.
  • Adds a persistent OpenBao service, storage volume, and target-specific listener configuration.
  • Injects resolved per-service secret dotenv files into auth, central, and edge Compose services.
  • Adds a non-interactive vps target with host networking and native Postgres/nginx assumptions.
  • Sets VPS application configs to production mode and binds service/diagnostic ports to loopback.
  • Preserves generated JWT and edge key-pair identity components through OpenBao-backed resolution.
  • Updates the tools entrypoint and image build to generate both supported deployment targets.
  • Documents OpenBao initialization, AppRole setup, migration seeding, and operational constraints.

Technical Notes: The external OpenBao volume is intended to survive fresh deployment bundle directories; VPS configuration expects existing production secrets to be seeded before its first run.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

}

listener "tcp" {
address = "0.0.0.0:8200"

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This listener binds OpenBao’s unauthenticated/TLS-disabled API to every interface; with the vps fragment’s host network, port 8200 is consequently reachable on the VPS public interfaces, contrary to the deployment contract that nothing binds to 0.0.0.0. The docker-local fragment also publishes it on all host interfaces, so AppRole credentials and secret API traffic can be exposed or observed outside the intended local host.

Severity: high

Other Locations
  • docker/versola-tools/compose.fragment.yml.template:37

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

platform: linux/amd64
container_name: versola-central
restart: unless-stopped
network_mode: host

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At docker/versola-tools/compose.fragment.vps.yml.template:46, host networking makes central use the host namespace, while VersolaApp only calls Server.Config.default.port, whose default bind address is 0.0.0.0; ports 8090/8091 can therefore be reached directly on any network path to the VPS, bypassing nginx and its TLS/access controls. The same exposure applies to auth and edge.

Severity: high

Other Locations
  • docker/versola-tools/compose.fragment.vps.yml.template:70
  • docker/versola-tools/compose.fragment.vps.yml.template:87

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread scripts/gen-env.scala
"SESSION_COOKIE_SECRET" -> sessionCookieSecret,
"USER_AGENT_COOKIE_SECRET" -> userAgentCookieSecret,
"PAR_REQUESTS_SECRET" -> parRequestsSecret,
"JWT_PRIVATE_KEY" -> jwtKey.privateB64,

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When OpenBao is initialized for the already-running VPS, this stores a newly generated private key while the existing jwks table retains its prior key. Bootstrap adds the new JWK, but auth signs with JWT.PublicKeys.active (the first, unordered persisted record), so it can put the old kid on a token signed by this new private key and validators will reject it.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread scripts/gen-env.scala
val centralExtras = if isVps then Seq("POSTGRES_PASSWORD" -> centralPgPass) else Seq.empty
writeGeneratedSecrets(dir, "central.generated-secrets.env", Seq(
"CENTRAL_SECRET_KEY" -> centralSecretKey,
"CLIENT_SECRETS_SECRET" -> clientSecretsSecret,

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A first VPS setup against an empty OpenBao generates a new CLIENT_SECRETS_SECRET even though the existing central resource secret remains encrypted under the prior key. Bootstrap skips that existing resource, so central cannot recover its credential to sync to edge and the edge admin proxy becomes unavailable after this rollout.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread scripts/gen-env.scala

val edgeExtras = if isVps then Seq("POSTGRES_PASSWORD" -> edgePgPass) else Seq.empty
writeGeneratedSecrets(dir, "edge.generated-secrets.env", Seq(
"EDGE_PRIVATE_KEY" -> edgeKey.privateB64,

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDGE_PRIVATE_KEY is persisted without its paired key identity. On the initial migration central retains the existing edge-default public JWK, and on a later run the literal key-id is regenerated from the date while this private key is reused; either case makes the sync token's KID/key differ from central's persisted public key, causing edge-to-central syncs to return 401.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

…ocument seeding real crypto material for vps migration
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread scripts/gen-env.scala
// Unlike docker-local's throwaway Postgres container, the VPS's Postgres
// role already exists outside this script's control -- see the comment
// on pgPassDefault below.
val isVps = env == "vps"

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vps is emitted verbatim as env, but VersolaApp.envName recognizes only "prod" as EnvName.Prod, so this real VPS runs as Test("vps"). That makes OTPs deterministic and not delivered, and enables test-only service endpoints, so the deployment cannot safely serve production traffic.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

…ocument seeding real crypto material for vps migration
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

# stop them. A stable name fixes both: Compose looks containers up by
# project+service, not by which file path happened to describe them this
# time, so any bundle directory's compose file manages the same stack.
name: versola-local

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing docker-local stacks were created under bundle-derived project names, while their containers already use these fixed names; changing the project to versola-local cannot adopt them. The first post-upgrade configure/up will therefore fail with container-name conflicts before this stable name can help. Other locations where this applies: docker/versola-tools/compose.fragment.vps.yml.template:32 (the documented existing VPS stack is an implicit versola project).

Severity: high

Other Locations
  • docker/versola-tools/compose.fragment.vps.yml.template:32

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant