feat: move vault ssh signing into a plugin (C5)

This commit is contained in:
LukeGus committed 2026-09-24 20:24:09 -05:00
1 parent b027717c46
commit 6e56c90aac
198 files changed
+19717 -3445

No files matched your search

+41 -11
View File
@@ -185,7 +185,7 @@ artifacts in lockstep: the `schema.ts` declaration, the `index.ts` DDL, and the
regenerated `schema.pg.ts`/`schema.mysql.ts` plus drizzle migrations.
**Sync entities are registered, not hardcoded.** `ctx.sync.registerEntity`
adds an entity to remote sync; core registers its own ten the same way in
adds an entity to remote sync; core registers its own the same way in
`database/routes/sync-entities.ts`. The wire names are unchanged, because
existing tombstones and rows on the far side match on those strings. The
Electron client asks the server for the ordered list and falls back to its
@@ -1008,12 +1008,10 @@ auto-session and trusted devices, plus the base SSH auth types password, key,
stored credential, agent and none. Every other login method, second factor and
SSH auth method is a plugin through `ctx.auth`.
In 2.9.0, Vault still lives in core. It registers through the same
interface from `src/backend/auth/legacy-providers.ts` with
`pluginId: "core"`. Its Phase C step moves its block out of that file; D1
deletes it. **C4** moved Step-CA out and deleted
`src/ui/auth/legacy-auth-ui.tsx`, which had no other editor left.
Nothing else in core branches on those type names.
**C5** moved Vault, the last SSH auth method core still registered, into
its plugin and deleted `src/backend/auth/legacy-providers.ts`. **C4** had
already deleted `src/ui/auth/legacy-auth-ui.tsx`. Core registers only the
base types and branches on no plugin type name.
**C1** moved TOTP and passkeys out:
@@ -1137,7 +1135,7 @@ Nothing else in core branches on those type names.
`requires_browser_sign_in` and continue on `connect-browser-sign-in`.
- `@termix/plugin-sdk/ssh-certs` (`applyCertificateAuth`) replaces core's
`opkssh-cert-auth.ts`: opkssh, step-ca, core key auth with a CA
certificate, and the Vault provider still in core use it.
certificate, and vault use it.
**C4** moved Step-CA out:
@@ -1159,6 +1157,39 @@ Nothing else in core branches on those type names.
- The sign-in dialog is a `terminal.overlay` contribution speaking
`stepca_*`.
**C5** moved HashiCorp Vault out:
- `vault` registers the `vault` SSH auth provider. It adopts
`vault_profiles` and `vault_tokens` as `p_vault_profiles` and
`p_vault_tokens`. Profiles keep their ids and sync ids; cached
certificates are sealed with `ctx.secrets.seal`, and the ones 2.8 wrote
with the user's data key do not unseal, so they are dropped and cost one
sign-in. A fresh install has no foreign key from a token to its profile
(the SDK has no builder for one to a plugin's own table), so deleting a
profile deletes its tokens in code.
- A host's profile is the `profileId` host setting (hidden, edited in the
plugin's `registerSshAuthEditor` editor, which also holds the profile
manager where the host editor always had it).
`vault-settings-migration.ts` copies `ssh_data.vault_profile_id` into it;
the column is out of `schema.ts` and its drizzle drop is a no-op.
- Vault is reached through `ctx.fetch`, with the profile's own Vault host
passed as `allowPrivateHosts`, since Vault servers are usually internal.
- Profile routes are `/plugin-api/vault/profiles`, gated on `vault.use`;
sharing a profile needs `vault.share` (admins by default), which replaces
2.8's admin check. Only the owner edits or deletes one.
- The sync entity keeps its wire name `vaultProfiles` and order 20. Hosts no
longer reference it, so core's host entity lost its `vaultProfileId`
reference and `sync-references.ts` has no Vault case.
- The callback is `/plugin-api/vault/oidc/callback` (public). The boot
migration decides `legacyCallback` once: on when the 2.8 database used
Vault, so Termix keeps sending `<base>/vault/oidc/callback`, the URI Vault
roles allow, which core answers with a 307 to the plugin
(`registerVaultCompatRoutes` in `host-compat-routes.ts`; nginx proxies
only that path now).
- The sign-in dialog is a `terminal.overlay` contribution. It keeps the 2.8
message names (`vault_auth_url`, `vault_completed`, `vault_error`) for
Termix-Mobile.
#### One SSH connect pipeline
Every SSH connection, core's and plugins', goes through
@@ -2298,8 +2329,7 @@ upgrade on all three engines, and the checks in step 15 are green.
(`ctx.disposables.add`). Nothing at module scope. `deactivate` is usually
empty.
4. **When the SDK does not have it, add it to the SDK.** Never work around a
gap with a core import, and never add to
`src/backend/auth/legacy-providers.ts`. A new ctx member is:
gap with a core import. A new ctx member is:
1. the type and a doc comment in `packages/plugin-sdk/src/backend.ts`;
2. the implementation in `src/backend/plugins/ctx.ts`, wrapped in `guarded()`
when it is privileged, so it checks the capability and writes an audit
@@ -2540,7 +2570,7 @@ Then, with the app running:
The bundled plugins predate the SDK, apart from workspaces (A9), snippets
(B2), remote-desktop (B14), docker (B15), host-metrics (B16), automations
(B17), ai (B18), homepage (B19), totp and webauthn (C1), sso and ldap (C2),
opkssh and warpgate (C3), step-ca (C4),
opkssh and warpgate (C3), step-ca (C4), vault (C5),
which import nothing from core. The others still reach core by relative
path (`../../../../src/backend/...`), which an esbuild plugin,
`packages/plugin-sdk/cli/lib/legacy-core-imports.mjs`, keeps out of the bundle
+33
View File
@@ -529,6 +529,31 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
rejects (it then downloads the pinned version instead). Pass
`OPKSSH_VERSION` and the matching `OPKSSH_SHA256` as `ENV` in the final
stage. Owner: D0.
- **C5 (vault):** the Termix docs still describe Vault profiles with the
`/vault/oidc/callback` redirect URI. They need the plugin settings page,
the new redirect URI (`/plugin-api/vault/oidc/callback`) and the
`legacyCallback` setting. Owner: D0 (docs repo).
- **C5 (vault):** a host's Vault profile is the plugin's `profileId` host
setting now, and host-scope plugin settings do not travel over remote sync
(the host entity used to carry `vaultProfileSyncId`). Profiles still sync
as `vaultProfiles`; the host on the other side has to pick its profile
again. Needs a generic way to sync host-scope plugin settings, the same gap
the B7 export/import line describes. Owner: D1.
- **C5 (vault), for D2:** `vaultProfileId` was an owner-private auth field a
shared-host recipient with edit access could not change. As a host setting
it is written through `/plugins/vault/settings/host/:hostId`, which only
checks edit access, so such a recipient can now point the host at another
profile. Same question as the B14 line on per-field sharing levels.
Owner: D2.
- **C5 (vault):** `ssh_data.vault_profile_id` stays in the database,
unused, after `vault-settings-migration.ts` copies it (kept so a downgrade
works, and the pg/mysql foreign key on it now points at
`p_vault_profiles`). Drop it in 3.0.0. Owner: 3.0.0.
- **Pre-existing, found in C5:** `npm run test -w plugins/ssh-terminal`
fails "ssh-terminal activate > serves the terminal socket": it expects
`mock.wsRoutes` to equal `[{ path, raw }]`, but `FakeWsRoute` now also
records `handler` and `options`. Use `toMatchObject` or
`expect.objectContaining`. Owner: D0.
## Manual checks after 2.9.0
@@ -624,3 +649,11 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
and the host says it needs it. A Warpgate host's toggle is on in the host
editor's Plugins group after the upgrade, and the terminal, file manager
and docker each show the sign-in dialog and connect after it.
- Vault: on a 2.8 database every Vault profile is there and each Vault host
still has its profile selected in the host editor; the admin page shows the
old redirect URI; a terminal connect opens Vault's sign-in, signs a
certificate and connects, and a second connect skips the browser until the
certificate expires; create, edit, share and delete a profile from the host
editor (sharing only offered with "Share Vault profiles"); switch to the new
redirect URI after allowing it in the Vault role; disable the vault plugin
and a Vault host says it needs it, then enable it again.