feat: move opkssh and warpgate into plugins (C3)

This commit is contained in:
LukeGus committed 2026-09-24 19:28:19 -05:00
1 parent 925da4a661
commit 98d367d7e2
336 files changed
+22380 -4746

No files matched your search

+68 -134
View File
@@ -8,112 +8,86 @@
import { getErrorMessage } from "../utils/error-message.js";
import { sshLogger } from "../utils/logger.js";
import {
registerKeyboardInteractiveInterceptor,
registerSshAuthProvider,
} from "../hosts/connect/auth-provider-registry.js";
import { applyCertificateAuth } from "@termix/plugin-sdk/ssh-certs";
import { registerSshAuthProvider } from "../hosts/connect/auth-provider-registry.js";
import type {
SshAuthProvider,
SshConnectHost,
} from "../hosts/connect/types.js";
const OPKSSH_REQUIRED_MESSAGE =
"OPKSSH authentication required. Please open a Terminal connection to this host first to complete browser-based authentication. Your session will be cached for 24 hours.";
const STEPCA_REQUIRED_MESSAGE =
"Step CA authentication required. Please open a Terminal connection to this host first to complete browser-based authentication.";
const VAULT_REQUIRED_MESSAGE =
"Vault SSH signer authentication required. Please open a Terminal connection first.";
const AUTH_FAILED_PATTERN = /All configured authentication methods failed/i;
function issuedCertificateProvider(
type: "opkssh" | "stepca",
labelKey: string,
): SshAuthProvider {
return {
type,
pluginId: "core",
labelKey,
needsUserInteraction: true,
supportsBackground: false,
interaction: "opkssh",
prepare: async (config, host, env) => {
const { getOPKSSHToken } = await import("../hosts/opkssh-auth.js");
const token = await getOPKSSHToken(env.userId, env.hostId);
if (!token) {
env.log("info", "No valid certificate found, requesting sign-in");
return {
status: "interaction-required",
interaction: "opkssh",
message: OPKSSH_REQUIRED_MESSAGE,
flag: "requiresOPKSSHAuth",
};
}
try {
const { setupOPKSSHCertAuth } =
await import("../hosts/opkssh-cert-auth.js");
await setupOPKSSHCertAuth(config, env.client, token, host.username);
} catch (error) {
return {
status: "error",
code: "failed",
message: "OPKSSH authentication failed: " + getErrorMessage(error),
};
}
env.log("info", "Using cached SSH certificate");
return { status: "ready" };
},
onAuthFailed: (_host, env, context) => {
if (!AUTH_FAILED_PATTERN.test(context.error.message)) return;
sshLogger.warn("OPKSSH authentication failed - invalidating token", {
operation: "opkssh_auth_failed",
hostId: env.hostId,
userId: env.userId,
});
void import("../hosts/opkssh-auth.js")
.then(({ invalidateOPKSSHToken }) =>
invalidateOPKSSHToken(env.userId, env.hostId, "SSH auth failed"),
)
.catch(() => {});
const stepCaProvider: SshAuthProvider = {
type: "stepca",
pluginId: "core",
labelKey: "hosts.filterAuthStepca",
needsUserInteraction: true,
supportsBackground: false,
interaction: "stepca",
prepare: async (config, host, env) => {
const { getStepCaCert } = await import("../hosts/step-ca-auth.js");
const cert = getStepCaCert(env.userId, env.hostId);
if (!cert) {
env.log("info", "No valid certificate found, requesting sign-in");
return {
status: "interaction-required",
interaction: "opkssh",
message:
"OPKSSH authentication failed or expired. Please authenticate again.",
interaction: "stepca",
message: STEPCA_REQUIRED_MESSAGE,
};
},
startInteraction: async (request) => {
if (type === "stepca") {
const { startStepCaAuth } = await import("../hosts/step-ca-auth.js");
await startStepCaAuth(
request.userId,
request.hostId,
request.host.username,
request.socket,
request.requestOrigin,
);
return;
}
const { startOPKSSHAuth } = await import("../hosts/opkssh-auth.js");
await startOPKSSHAuth(
request.userId,
request.hostId,
request.host.name || request.host.ip,
request.socket,
request.requestOrigin,
}
try {
await applyCertificateAuth(
config,
env.client,
{ privateKey: cert.privateKey, certificate: cert.sshCert },
host.username,
);
},
cancelInteraction: async (request) => {
if (!request.requestId) return;
if (type === "stepca") {
const { cancelStepCaAuth } = await import("../hosts/step-ca-auth.js");
cancelStepCaAuth(request.requestId);
return;
}
const { cancelAuthSession } = await import("../hosts/opkssh-auth.js");
cancelAuthSession(request.requestId);
},
};
}
} catch (error) {
return {
status: "error",
code: "failed",
message: "Step CA authentication failed: " + getErrorMessage(error),
};
}
env.log("info", "Using cached SSH certificate");
return { status: "ready" };
},
onAuthFailed: (_host, env, context) => {
if (!AUTH_FAILED_PATTERN.test(context.error.message)) return;
void import("../hosts/step-ca-auth.js")
.then(({ invalidateStepCaCert }) =>
invalidateStepCaCert(env.userId, env.hostId),
)
.catch(() => {});
return {
status: "interaction-required",
interaction: "stepca",
message:
"Step CA authentication failed or expired. Please authenticate again.",
};
},
startInteraction: async (request) => {
const { startStepCaAuth } = await import("../hosts/step-ca-auth.js");
await startStepCaAuth(
request.userId,
request.hostId,
request.host.username,
request.socket,
request.requestOrigin,
);
},
cancelInteraction: async (request) => {
if (!request.requestId) return;
const { cancelStepCaAuth } = await import("../hosts/step-ca-auth.js");
cancelStepCaAuth(request.requestId);
},
};
function vaultProfileId(host: SshConnectHost): number | undefined {
return host.vaultProfile?.id ?? undefined;
@@ -149,12 +123,10 @@ const vaultProvider: SshAuthProvider = {
};
}
try {
const { setupOPKSSHCertAuth } =
await import("../hosts/opkssh-cert-auth.js");
await setupOPKSSHCertAuth(
await applyCertificateAuth(
config,
env.client,
{ privateKey: cert.privateKey, sshCert: cert.sshCert },
{ privateKey: cert.privateKey, certificate: cert.sshCert },
host.username,
);
} catch (error) {
@@ -214,52 +186,14 @@ const vaultProvider: SshAuthProvider = {
},
};
const WARPGATE_PATTERN = /warpgate\s+authentication/i;
let sshRegistered = false;
export function registerLegacySshAuthProviders(): void {
if (sshRegistered) return;
sshRegistered = true;
registerSshAuthProvider(
issuedCertificateProvider("opkssh", "hosts.filterAuthOpkssh"),
);
registerSshAuthProvider(
issuedCertificateProvider("stepca", "hosts.filterAuthStepca"),
);
registerSshAuthProvider(stepCaProvider);
registerSshAuthProvider(vaultProvider);
// Warpgate is a flag on a host, not an auth type: it shows up as a
// keyboard-interactive round with a sign-in URL.
registerKeyboardInteractiveInterceptor({
id: "warpgate",
pluginId: "core",
detect: ({ name, instructions, prompts }) => {
const texts = prompts.map((p) => p.prompt);
const isWarpgate =
WARPGATE_PATTERN.test(name) ||
WARPGATE_PATTERN.test(instructions) ||
texts.some((p) => WARPGATE_PATTERN.test(p));
if (!isWarpgate) return null;
const fullText = `${name}\n${instructions}\n${texts.join("\n")}`;
const urlMatch = fullText.match(/https?:\/\/[^\s\n]+/i);
if (!urlMatch) return null;
const keyMatch = fullText.match(
/security key[:\s]+([a-z0-9](?:\s+[a-z0-9]){3}|[a-z0-9]{4})/i,
);
return {
kind: "warpgate",
url: urlMatch[0],
securityKey: keyMatch ? keyMatch[1] : "N/A",
instructions,
};
},
// Warpgate asks for the password before its browser round; answer it
// silently so the only thing the user sees is the Warpgate dialog.
autoAnswerPasswords: (host) => host.useWarpgate === true,
});
}
/** Test helper. */