mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: move step-ca into a plugin (C4)
This commit is contained in:
1 parent
98d367d7e2
commit
b027717c46
128 files changed
+3805
-1781
No files matched your search
@@ -1008,11 +1008,11 @@ 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, Step-CA and Vault still live in core.
|
||||
They register through the same interfaces from
|
||||
`src/backend/auth/legacy-providers.ts` and `src/ui/auth/legacy-auth-ui.tsx`
|
||||
with `pluginId: "core"`. Phase C moves each one into its plugin by moving its
|
||||
block out of those two files; D1 deletes them.
|
||||
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.
|
||||
|
||||
**C1** moved TOTP and passkeys out:
|
||||
@@ -1136,11 +1136,28 @@ Nothing else in core branches on those type names.
|
||||
2.8 message names for Termix-Mobile; the file manager and docker answer
|
||||
`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, core key auth with a CA certificate, and the
|
||||
Step-CA and Vault providers still in core use it.
|
||||
- Step-CA no longer shares anything with OPKSSH: its interaction is
|
||||
`stepca`, its messages `stepca_*`, and it keeps issued certificates in
|
||||
memory until it becomes a plugin.
|
||||
`opkssh-cert-auth.ts`: opkssh, step-ca, core key auth with a CA
|
||||
certificate, and the Vault provider still in core use it.
|
||||
|
||||
**C4** moved Step-CA out:
|
||||
|
||||
- `step-ca` registers the `stepca` SSH auth provider. Termix never runs the
|
||||
`step` binary: the plugin talks to the CA's HTTPS API and the identity
|
||||
provider through `ctx.fetch` (`network:outbound`), using its `tls` option
|
||||
to bootstrap the CA root by fingerprint and pin it afterwards.
|
||||
- Issued certificates live in its own table, `p_step_ca_certs`, sealed with
|
||||
`ctx.secrets.seal`, until the certificate's `validBefore`. 2.8 cached them
|
||||
in `opkssh_tokens`; those are left to expire.
|
||||
- The CA URL, fingerprint, provisioner and private endpoint list are admin
|
||||
settings. `step-ca-settings-migration.ts` copies the 2.8 `step_ca_*`
|
||||
settings rows in and turns on `legacyCallback` when a CA was configured,
|
||||
so an upgraded install keeps sending `<base>/host/step-ca-callback`, which
|
||||
core answers with a 307 to `/plugin-api/step-ca/callback`.
|
||||
- With `REDIS_URL` set, a callback that lands on another instance is handed
|
||||
to the one that started the sign-in through Redis
|
||||
(`TERMIX_STEP_CA_REDIS_PREFIX`), as in 2.8.
|
||||
- The sign-in dialog is a `terminal.overlay` contribution speaking
|
||||
`stepca_*`.
|
||||
|
||||
#### One SSH connect pipeline
|
||||
|
||||
@@ -1682,44 +1699,44 @@ deciding, not the mechanism.
|
||||
|
||||
Built per plugin in `src/backend/plugins/ctx.ts` and passed to `activate`.
|
||||
|
||||
| Member | Capability | Status |
|
||||
| ------------------------------------------------ | -------------------------------------------------- | ----------------------- |
|
||||
| `ctx.pluginId`, `ctx.manifest` | none | **A1** |
|
||||
| `ctx.log.*` | none | **A1** |
|
||||
| `ctx.events.emit` / `.on` | `events:core` for core topics | **A1** |
|
||||
| `ctx.kv.get/set/delete/list` | `kv:own` | **A1** |
|
||||
| `ctx.files.dataDir` | `files:own` | **B13** |
|
||||
| `ctx.registry.*` | none | **A1** |
|
||||
| `ctx.services.provide` / `.get` / `.providers` | per-service RBAC | **A1**, named **B12** |
|
||||
| `ctx.secrets.offer` / `.withdraw` / `.getShared` | per-secret RBAC | **A1** |
|
||||
| `ctx.secrets.get` / `.set` / `.delete` | `secrets:own` | **B18** |
|
||||
| `ctx.secrets.seal` / `.unseal` | `secrets:own` | **C1** |
|
||||
| `ctx.disposables.add` | none | **A1** |
|
||||
| `ctx.asUser(userId, fn)` | none, always audited | **A1** |
|
||||
| `ctx.currentActor()` | none | **A1** |
|
||||
| `ctx.db.define` / `.client` / `.refs` | `db:own` | **A3** |
|
||||
| `ctx.db.persist` / `.dialect` | `db:own` (persist only) | **A9**, lazy **B16** |
|
||||
| `ctx.sync.registerEntity` | none | **A3** |
|
||||
| `ctx.http.router` / `ctx.ws.route` / `.upgrade` | `network:serve` | **A4** |
|
||||
| `ctx.http.baseUrl(req)` | none | **C2** |
|
||||
| `ctx.rbac.has` / `.hasFor` / `.require` | own permissions only | **A5** |
|
||||
| `ctx.capabilities.has` / `.require` | the capability itself | **B11** |
|
||||
| `ctx.hosts.*` | `hosts:read` / `hosts:write` | **B4**, **B5**, **B18** |
|
||||
| `ctx.hosts.status.*` | `hosts:read` | **B16** |
|
||||
| `ctx.ssh.*` | `ssh:connect`, `credentials:use` | **A8** |
|
||||
| `ctx.settings.*` | `settings:read-core` (readCore only) | **A6** |
|
||||
| `ctx.notify.channels` / `.send` | `notify:send` | **B17** |
|
||||
| `ctx.auth.*` | `auth:provide` | **A8** |
|
||||
| `ctx.auth.completeRedirectLogin` and the rest | `auth:provide` | **C2** |
|
||||
| `ctx.desktop.openIsolatedWindow` | `desktop:window` | **B8** |
|
||||
| `ctx.desktop.launchNativeRdp` / `.available` | `desktop:window` (launch only) | **B14** |
|
||||
| `ctx.credentials.resolveHostProtocol` | `credentials:read` | **B14** |
|
||||
| `ctx.credentials.registerSecretResolver` | `auth:provide` | **B20** |
|
||||
| `ctx.audit.record` | none, the actor is the runtime's | **B9** |
|
||||
| `ctx.schedule.every` / `.after` | none | **B16** |
|
||||
| `ctx.fetch` | `network:outbound` | **B17**, signal **B18** |
|
||||
| `ctx.process.run` / `.ensureBinary` | `process:spawn` (+ `network:outbound` to download) | **C3** |
|
||||
| `ctx.auth.registerKeyboardInteractiveHandler` | `auth:provide` | **C3** |
|
||||
| Member | Capability | Status |
|
||||
| ------------------------------------------------ | -------------------------------------------------- | ----------------------------------- |
|
||||
| `ctx.pluginId`, `ctx.manifest` | none | **A1** |
|
||||
| `ctx.log.*` | none | **A1** |
|
||||
| `ctx.events.emit` / `.on` | `events:core` for core topics | **A1** |
|
||||
| `ctx.kv.get/set/delete/list` | `kv:own` | **A1** |
|
||||
| `ctx.files.dataDir` | `files:own` | **B13** |
|
||||
| `ctx.registry.*` | none | **A1** |
|
||||
| `ctx.services.provide` / `.get` / `.providers` | per-service RBAC | **A1**, named **B12** |
|
||||
| `ctx.secrets.offer` / `.withdraw` / `.getShared` | per-secret RBAC | **A1** |
|
||||
| `ctx.secrets.get` / `.set` / `.delete` | `secrets:own` | **B18** |
|
||||
| `ctx.secrets.seal` / `.unseal` | `secrets:own` | **C1** |
|
||||
| `ctx.disposables.add` | none | **A1** |
|
||||
| `ctx.asUser(userId, fn)` | none, always audited | **A1** |
|
||||
| `ctx.currentActor()` | none | **A1** |
|
||||
| `ctx.db.define` / `.client` / `.refs` | `db:own` | **A3** |
|
||||
| `ctx.db.persist` / `.dialect` | `db:own` (persist only) | **A9**, lazy **B16** |
|
||||
| `ctx.sync.registerEntity` | none | **A3** |
|
||||
| `ctx.http.router` / `ctx.ws.route` / `.upgrade` | `network:serve` | **A4** |
|
||||
| `ctx.http.baseUrl(req)` | none | **C2** |
|
||||
| `ctx.rbac.has` / `.hasFor` / `.require` | own permissions only | **A5** |
|
||||
| `ctx.capabilities.has` / `.require` | the capability itself | **B11** |
|
||||
| `ctx.hosts.*` | `hosts:read` / `hosts:write` | **B4**, **B5**, **B18** |
|
||||
| `ctx.hosts.status.*` | `hosts:read` | **B16** |
|
||||
| `ctx.ssh.*` | `ssh:connect`, `credentials:use` | **A8** |
|
||||
| `ctx.settings.*` | `settings:read-core` (readCore only) | **A6** |
|
||||
| `ctx.notify.channels` / `.send` | `notify:send` | **B17** |
|
||||
| `ctx.auth.*` | `auth:provide` | **A8** |
|
||||
| `ctx.auth.completeRedirectLogin` and the rest | `auth:provide` | **C2** |
|
||||
| `ctx.desktop.openIsolatedWindow` | `desktop:window` | **B8** |
|
||||
| `ctx.desktop.launchNativeRdp` / `.available` | `desktop:window` (launch only) | **B14** |
|
||||
| `ctx.credentials.resolveHostProtocol` | `credentials:read` | **B14** |
|
||||
| `ctx.credentials.registerSecretResolver` | `auth:provide` | **B20** |
|
||||
| `ctx.audit.record` | none, the actor is the runtime's | **B9** |
|
||||
| `ctx.schedule.every` / `.after` | none | **B16** |
|
||||
| `ctx.fetch` | `network:outbound` | **B17**, signal **B18**, tls **C4** |
|
||||
| `ctx.process.run` / `.ensureBinary` | `process:spawn` (+ `network:outbound` to download) | **C3** |
|
||||
| `ctx.auth.registerKeyboardInteractiveHandler` | `auth:provide` | **C3** |
|
||||
|
||||
**B11** added `ctx.capabilities.has(capability)` / `.require(capability)`, a
|
||||
generic check for a capability no other ctx member wraps. Unlike every other
|
||||
@@ -1956,6 +1973,10 @@ notification)` delivers to the ones among them that are theirs and
|
||||
`network:outbound`, audited as `plugin_fetch`. Automations' HTTP step
|
||||
passes the admin allowlist it reads through `ctx.settings.readCore`
|
||||
(`notification_private_endpoint_allowlist` joined `CORE_SETTINGS_ALLOWLIST`).
|
||||
**C4** added `tls: { ca, rejectUnauthorized }` for a private CA: `ca`
|
||||
trusts a PEM bundle, and `rejectUnauthorized: false` is for the one request
|
||||
that fetches a CA root by fingerprint, which the caller then checks
|
||||
(step-ca, like `step ca bootstrap`).
|
||||
|
||||
**B18** added three members for the AI assistant:
|
||||
|
||||
@@ -2519,7 +2540,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),
|
||||
opkssh and warpgate (C3), step-ca (C4),
|
||||
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
|
||||
|
||||
@@ -483,21 +483,13 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
|
||||
(`h.pluginSettings?.["web-endpoint"]`). The file is untouched since before
|
||||
C2. Owner: D0.
|
||||
|
||||
- **C3 (opkssh), for the Step-CA step:** Step-CA is still core and no longer
|
||||
shares OPKSSH's pieces. Its interaction is `stepca` and its socket messages
|
||||
`stepca_*`, but nothing draws them yet: until its plugin contributes a
|
||||
`terminal.overlay` (the opkssh plugin's `OpksshOverlay` and `OpksshDialog`
|
||||
are the model), a Step-CA host asks for a sign-in the terminal cannot show.
|
||||
Its issued certificates are cached in memory (`step-ca-auth.ts`), so they
|
||||
are lost on restart; its plugin should own a table the way opkssh owns
|
||||
`p_opkssh_tokens`. Step-CA certificates 2.8 cached in `opkssh_tokens` stay
|
||||
in the adopted `p_opkssh_tokens`, unused, until they expire. Owner: the
|
||||
Phase C step that moves Step-CA.
|
||||
- **C3 (opkssh):** `database/routes/host-compat-routes.ts` answers the 2.8
|
||||
redirect URI `/host/opkssh-callback` with a 307 to the plugin and so names
|
||||
the plugin's path. Keep it until identity providers have moved to
|
||||
`/plugin-api/opkssh/callback` (the admin page shows it, and the
|
||||
`legacyCallback` setting switches over). Owner: 3.0.0.
|
||||
- **C3 (opkssh), C4 (step-ca):** `database/routes/host-compat-routes.ts`
|
||||
answers the 2.8 redirect URIs `/host/opkssh-callback` and
|
||||
`/host/step-ca-callback` with a 307 to the plugins and so names their
|
||||
paths. Keep it until identity providers have moved to
|
||||
`/plugin-api/opkssh/callback` and `/plugin-api/step-ca/callback` (each
|
||||
plugin's admin page shows it, and its `legacyCallback` setting switches
|
||||
over). Owner: 3.0.0.
|
||||
- **C3 (opkssh/warpgate):** `ssh_data.use_warpgate` and the `.opk` folder
|
||||
under `DATA_DIR` stay on disk, unused (the drizzle drop is `SELECT 1;`, the
|
||||
config is copied, not moved). Remove them in 3.0.0. Owner: 3.0.0.
|
||||
@@ -517,6 +509,20 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
|
||||
`useWarpgate` field inline (the host editor sends plugin settings). The
|
||||
bulk list should come from the registered providers plus
|
||||
`contributes.auth.sshAuthTypes`. Owner: D1.
|
||||
- **C4 (step-ca):** the 2.8 `step_ca_url`, `step_ca_fingerprint`,
|
||||
`step_ca_provisioner` and `step_ca_private_endpoint_allowlist` rows stay in
|
||||
core `settings`, unused, after `step-ca-settings-migration.ts` copies them
|
||||
(kept for one release so a downgrade works). Delete them in 3.0.0.
|
||||
Owner: 3.0.0.
|
||||
- **C4 (step-ca):** the admin fields are plain settings now, so a bad CA URL
|
||||
or fingerprint is reported when someone signs in (in the terminal dialog)
|
||||
instead of when the admin saves, as the 2.8 route did. A settings
|
||||
validation hook for plugins would bring the save-time check back.
|
||||
Owner: D0.
|
||||
- **C4 (step-ca):** the Termix docs still describe Step CA under Admin
|
||||
Settings and the `/host/step-ca-callback` redirect URI. They need the
|
||||
plugin settings page, the new redirect URI and the `legacyCallback`
|
||||
setting. Owner: D0 (docs repo).
|
||||
- **Pre-existing, found in C3:** the Docker `opkssh-downloader` stage's
|
||||
`OPKSSH_VERSION` build arg is not passed on at runtime, so building with
|
||||
another version gives a prebaked binary the plugin's pinned checksum
|
||||
|
||||
@@ -1490,6 +1490,12 @@ export interface PluginFetchInit {
|
||||
allowPrivateHosts?: readonly string[];
|
||||
/** Aborts the request, and a streamed body, when the caller gives up. */
|
||||
signal?: AbortSignal;
|
||||
/**
|
||||
* TLS for a private CA. `ca` trusts this PEM bundle instead of the system
|
||||
* store. `rejectUnauthorized: false` skips verification, only for fetching
|
||||
* a CA root by fingerprint, where the caller checks the answer itself.
|
||||
*/
|
||||
tls?: { ca?: string; rejectUnauthorized?: boolean };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -878,7 +878,7 @@ export function createFakeContext(
|
||||
requiresSecret: (authType) =>
|
||||
["password", "key", "credential", "agent"].includes(authType),
|
||||
supportsBackground: (authType) =>
|
||||
!["none", "stepca"].includes(authType) &&
|
||||
authType !== "none" &&
|
||||
!auth.sshAuthProviders.some(
|
||||
(provider) =>
|
||||
provider.type === authType && provider.supportsBackground === false,
|
||||
|
||||
Reference in new issue
Block a user