refactor: remove server-global runtime trust

This commit is contained in:
2026-09-08 12:19:39 +09:00
parent fae36d220d
commit 3344d9f8b2
19 changed files with 269 additions and 2714 deletions
@@ -373,14 +373,9 @@ export type RuntimeVerificationEvidenceSummary = {
runtime_identity_revision: number;
};
export type WorkspaceRuntimeAuthenticationMode =
| "legacy_server_issuer"
| "workspace_identity";
export type WorkspaceRuntimeBindingSummary = {
state: WorkspaceRuntimeBindingState;
connection_state: RuntimeConnectionDisplayState;
authentication_mode: WorkspaceRuntimeAuthenticationMode;
revision: number;
workspace_key_id?: string | null;
workspace_key_generation?: number | null;
@@ -446,11 +441,6 @@ export type WorkspaceRuntimeDetail = {
export type RuntimeTrustKeyRevealResponse = { public_key: string };
export type PutRuntimeTrustKeyRequest = {
public_key: string;
expected_revision: number | null;
};
export type RevokeRuntimeTrustKeyRequest = { expected_revision: number };
export type RuntimeTrustConflictKind = "stale_revision" | "fingerprint_in_use";
@@ -1,7 +1,6 @@
import type {
CreateRemoteRuntimeRequest,
Diagnostic,
PutRuntimeTrustKeyRequest,
RevokeRuntimeTrustKeyRequest,
RuntimeConnectionDisplayState,
RuntimeIdentityAuthority,
@@ -17,7 +16,6 @@ import type {
RuntimeTrustKeyState,
RuntimeTrustKeyStatus,
RuntimeVerificationEvidenceSummary,
WorkspaceRuntimeAuthenticationMode,
WorkspaceRuntimeBindingState,
WorkspaceRuntimeBindingSummary,
WorkspaceRuntimeDetail,
@@ -85,11 +83,6 @@ const CONNECTION_STATES = new Set<RuntimeConnectionDisplayState>([
"revoked",
]);
const AUTHENTICATION_MODES = new Set<WorkspaceRuntimeAuthenticationMode>([
"legacy_server_issuer",
"workspace_identity",
]);
const encoder = new TextEncoder();
type JsonObject = Record<string, unknown>;
@@ -388,15 +381,10 @@ function runtimeBinding(
const item = object(value, path);
exactKeys(
item,
["state", "connection_state", "authentication_mode", "revision"],
["state", "connection_state", "revision"],
["workspace_key_id", "workspace_key_generation", "verification"],
path,
);
const authenticationMode = enumValue(
item.authentication_mode,
`${path}.authentication_mode`,
AUTHENTICATION_MODES,
);
const workspaceKeyId = optionalNullableString(
item.workspace_key_id,
`${path}.workspace_key_id`,
@@ -406,22 +394,13 @@ function runtimeBinding(
item.workspace_key_generation,
`${path}.workspace_key_generation`,
);
const state = enumValue(item.state, `${path}.state`, BINDING_STATES);
if (
authenticationMode === "workspace_identity" &&
state !== "revoked" &&
(workspaceKeyId == null || workspaceKeyGeneration == null)
) {
return fail(path, "requires Workspace signing key identity metadata");
}
if (
authenticationMode === "legacy_server_issuer" &&
(workspaceKeyId != null || workspaceKeyGeneration != null)
) {
return fail(
path,
"must not attach Workspace key metadata to legacy authority",
);
}
const state = enumValue(item.state, `${path}.state`, BINDING_STATES);
const connectionState = enumValue(
item.connection_state,
`${path}.connection_state`,
@@ -437,7 +416,6 @@ function runtimeBinding(
return fail(path, "verification must match the current binding revision");
}
if (
authenticationMode === "workspace_identity" &&
connectionState === "verified" &&
(verification === undefined ||
verification.verified_at === null ||
@@ -451,7 +429,6 @@ function runtimeBinding(
return {
state,
connection_state: connectionState,
authentication_mode: authenticationMode,
revision,
...(workspaceKeyId === undefined
? {}
@@ -988,29 +965,6 @@ export async function previewRuntimePublicKeyFingerprint(
return `sha256:${hex}`;
}
export async function putRuntimeTrustKey(
workspaceId: string,
runtimeId: string,
request: PutRuntimeTrustKeyRequest,
fetchImpl: typeof fetch = fetch,
): Promise<WorkspaceRuntimeDetail> {
const response = await fetchImpl(
workspaceApiPath(
workspaceId,
`/runtimes/${encodeURIComponent(runtimeId)}/trust-key`,
),
{
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({
public_key: request.public_key,
expected_revision: revisionForJson(request.expected_revision),
}),
},
);
return await finishMutation(response, workspaceId, runtimeId);
}
export async function revokeRuntimeTrustKey(
workspaceId: string,
runtimeId: string,
@@ -1,14 +1,12 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import type {
PutRuntimeTrustKeyRequest,
RevokeRuntimeTrustKeyRequest,
RuntimeTrustKeyStatus,
} from '$lib/generated/workspace-api';
import {
createRemoteRuntime,
previewRuntimePublicKeyFingerprint,
putRuntimeTrustKey,
revealRuntimeTrustKey,
revokeRuntimeTrustKey,
RuntimeTrustConflictError,
@@ -145,31 +143,24 @@
}
}
const request: PutRuntimeTrustKeyRequest = {
public_key: key,
expected_revision: trust.revision ?? null,
};
const operation = routeFence.capture(data.runtimeId);
busyAction = 'save';
try {
const binding = data.runtimeDetail.runtime.management.binding;
if (binding?.authentication_mode === 'workspace_identity') {
if (!data.runtimeDetail.endpoint) {
throw new RuntimeTrustRequestError('The authoritative Runtime endpoint is unavailable.');
}
await createRemoteRuntime(data.workspaceId, {
public_bundle: {
identity_id: operation.runtimeId,
public_key: key,
},
display_name: data.runtimeDetail.runtime.label,
endpoint: data.runtimeDetail.endpoint,
expected_revision: binding.revision,
});
} else {
await putRuntimeTrustKey(data.workspaceId, operation.runtimeId, request);
if (!binding || !data.runtimeDetail.endpoint) {
throw new RuntimeTrustRequestError(
'The Workspace identity binding and authoritative Runtime endpoint are required.',
);
}
await createRemoteRuntime(data.workspaceId, {
public_bundle: {
identity_id: operation.runtimeId,
public_key: key,
},
display_name: data.runtimeDetail.runtime.label,
endpoint: data.runtimeDetail.endpoint,
expected_revision: binding.revision,
});
if (!isCurrentRoute(operation)) return;
publicKey = '';
fingerprintConfirmation = '';
@@ -333,7 +324,6 @@
<div><dt>Endpoint</dt><dd>{detail.endpoint ?? 'Not configured'}</dd></div>
<div><dt>Status</dt><dd>{runtime.status}</dd></div>
<div><dt>Connection state</dt><dd>{runtime.management.binding?.connection_state ?? 'Not configured'}</dd></div>
<div><dt>Authentication mode</dt><dd>{runtime.management.binding?.authentication_mode ?? '—'}</dd></div>
<div><dt>Workspace signing key</dt><dd><code>{runtime.management.binding?.workspace_key_id ?? '—'}</code></dd></div>
<div><dt>Verified</dt><dd>{formatTimestamp(runtime.management.binding?.verification?.verified_at)}</dd></div>
<div><dt>Verified binding revision</dt><dd>{runtime.management.binding?.verification?.binding_revision?.toString() ?? '—'}</dd></div>
@@ -9,7 +9,6 @@ import {
parseWorkspaceRuntimeDetail,
parseWorkspaceRuntimeList,
previewRuntimePublicKeyFingerprint,
putRuntimeTrustKey,
revokeRuntimeTrustKey,
RuntimeTrustConflictError,
RuntimeTrustRouteFence,
@@ -43,7 +42,6 @@ function runtime() {
binding: {
state: "verified",
connection_state: "verified",
authentication_mode: "workspace_identity",
revision: 3,
workspace_key_id: "WK-1",
workspace_key_generation: 1,
@@ -279,52 +277,6 @@ Deno.test("Runtime public key preview matches the Server fingerprint contract",
);
});
Deno.test("typed trust conflict is validated and preserves authoritative revision", async () => {
let sentBody: unknown = null;
const fetchImpl = ((_: RequestInfo | URL, init?: RequestInit) => {
sentBody = JSON.parse(String(init?.body)) as unknown;
return Promise.resolve(
new Response(
JSON.stringify({
error: "stale_revision",
message: "Runtime trust changed",
current_revision: 4,
current_fingerprint: "SHA256:new",
}),
{ status: 409, headers: { "content-type": "application/json" } },
),
);
}) as typeof fetch;
try {
await putRuntimeTrustKey(
"workspace-a",
"arcadia",
{ public_key: "ssh-ed25519 AAAA-new", expected_revision: 3 },
fetchImpl,
);
throw new Error("expected mutation to reject");
} catch (error) {
assert(
error instanceof RuntimeTrustConflictError,
"expected typed conflict",
);
assert(
error.conflict.current_revision === 4,
"authoritative revision was lost",
);
}
assert(
JSON.stringify(sentBody) ===
JSON.stringify({
public_key: "ssh-ed25519 AAAA-new",
expected_revision: 3,
}),
"request should serialize the generated bigint revision as a safe JSON integer",
);
});
Deno.test("Runtime create surfaces bounded Settings error details", async () => {
const fetchImpl = (() =>
Promise.resolve(