Use daemon hostnames for initial Hub slugs (#3172)
* fix(hub): send hostname during daemon enrollment Pass the local hostname as daemon enrollment metadata so Hub can assign useful default slugs without coupling daemon authority to the human CLI login. Document the rollout dependency on Hub accepting the optional field. * test(hub): verify enrollment hostname payload
This commit is contained in:
@@ -1111,6 +1111,7 @@ export async function createPaseoDaemon(
|
||||
});
|
||||
const hubRelationships = new HubRelationshipController({
|
||||
paseoHome: config.paseoHome,
|
||||
hostname: getHostname(),
|
||||
serverId,
|
||||
daemonPublicKey: daemonKeyPair.publicKeyB64,
|
||||
logger,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { platform } from "node:os";
|
||||
import { hostname, platform } from "node:os";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { HubRelationshipHarness } from "./test-utils/relationship-harness.js";
|
||||
|
||||
@@ -88,6 +88,7 @@ describe("Hub relationship", () => {
|
||||
.update(secret ?? "")
|
||||
.digest("base64url"),
|
||||
);
|
||||
expect(enrollment.hostname).toBe(hostname());
|
||||
relationship.completeEnrollment();
|
||||
await connecting.result;
|
||||
await relationship.socketDialed();
|
||||
|
||||
@@ -137,6 +137,7 @@ export interface HubRelationshipRetryPolicy {
|
||||
|
||||
export interface HubRelationshipControllerOptions {
|
||||
paseoHome: string;
|
||||
hostname: string;
|
||||
serverId: string;
|
||||
daemonPublicKey: string;
|
||||
logger: pino.Logger;
|
||||
@@ -324,6 +325,7 @@ export class HubRelationshipController implements HubRelationshipManagement {
|
||||
idempotencyKey: pending.relationship.idempotencyKey,
|
||||
hubOrigin: pending.relationship.hubOrigin,
|
||||
token: pending.enrollment.token,
|
||||
hostname: this.options.hostname,
|
||||
serverId: pending.identity.serverId,
|
||||
daemonPublicKey: pending.identity.daemonPublicKey,
|
||||
credentialVerifier: verifier,
|
||||
|
||||
@@ -61,6 +61,7 @@ test.each([408, 429])("transient enrollment status %s remains retryable", async
|
||||
idempotencyKey: "ceremony-1",
|
||||
hubOrigin,
|
||||
token: "token",
|
||||
hostname: "test-daemon.local",
|
||||
serverId: "server-1",
|
||||
daemonPublicKey: "public-key",
|
||||
credentialVerifier: "verifier",
|
||||
@@ -74,7 +75,10 @@ test.each([408, 429])("transient enrollment status %s remains retryable", async
|
||||
});
|
||||
|
||||
test("enrollment rejects a transport URL that cannot open a WebSocket", async () => {
|
||||
const hubOrigin = await startEnrollmentHub("ftp://hub.test/daemon");
|
||||
let enrolledHostname: string | undefined;
|
||||
const hubOrigin = await startEnrollmentHub("ftp://hub.test/daemon", (enrollment) => {
|
||||
enrolledHostname = enrollment.hostname;
|
||||
});
|
||||
const remote = new DirectHubRelationshipRemote();
|
||||
|
||||
await expect(
|
||||
@@ -83,12 +87,14 @@ test("enrollment rejects a transport URL that cannot open a WebSocket", async ()
|
||||
idempotencyKey: "ceremony-1",
|
||||
hubOrigin,
|
||||
token: "token",
|
||||
hostname: "test-daemon.local",
|
||||
serverId: "server-1",
|
||||
daemonPublicKey: "public-key",
|
||||
credentialVerifier: "verifier",
|
||||
scopes: ["hub.execution.*"],
|
||||
}),
|
||||
).rejects.toThrow("Hub WebSocket URL must use ws or wss");
|
||||
expect(enrolledHostname).toBe("test-daemon.local");
|
||||
});
|
||||
|
||||
test("enrollment rejects a WebSocket URL with a fragment", async () => {
|
||||
@@ -101,6 +107,7 @@ test("enrollment rejects a WebSocket URL with a fragment", async () => {
|
||||
idempotencyKey: "ceremony-1",
|
||||
hubOrigin,
|
||||
token: "token",
|
||||
hostname: "test-daemon.local",
|
||||
serverId: "server-1",
|
||||
daemonPublicKey: "public-key",
|
||||
credentialVerifier: "verifier",
|
||||
@@ -119,6 +126,7 @@ test("enrollment rejects a WebSocket outside the enrolled Hub authority", async
|
||||
idempotencyKey: "ceremony-1",
|
||||
hubOrigin,
|
||||
token: "token",
|
||||
hostname: "test-daemon.local",
|
||||
serverId: "server-1",
|
||||
daemonPublicKey: "public-key",
|
||||
credentialVerifier: "verifier",
|
||||
@@ -138,6 +146,7 @@ test.each(["enrollment", "revocation"])("%s HTTP calls are bounded", async (oper
|
||||
idempotencyKey: "ceremony-1",
|
||||
hubOrigin,
|
||||
token: "token",
|
||||
hostname: "test-daemon.local",
|
||||
serverId: "server-1",
|
||||
daemonPublicKey: "public-key",
|
||||
credentialVerifier: "verifier",
|
||||
@@ -290,11 +299,15 @@ async function startHubReturning(status: number): Promise<string> {
|
||||
return `http://127.0.0.1:${address.port}`;
|
||||
}
|
||||
|
||||
async function startEnrollmentHub(webSocketUrl: string): Promise<string> {
|
||||
async function startEnrollmentHub(
|
||||
webSocketUrl: string,
|
||||
onEnrollment?: (enrollment: { daemonId: string; hostname: string }) => void,
|
||||
): Promise<string> {
|
||||
const server = createServer(async (request, response) => {
|
||||
let body = "";
|
||||
for await (const chunk of request) body += chunk;
|
||||
const enrollment = JSON.parse(body) as { daemonId: string };
|
||||
const enrollment = JSON.parse(body) as { daemonId: string; hostname: string };
|
||||
onEnrollment?.(enrollment);
|
||||
response.writeHead(200, { "content-type": "application/json" }).end(
|
||||
JSON.stringify({
|
||||
daemonId: enrollment.daemonId,
|
||||
@@ -523,6 +536,7 @@ async function connectController(
|
||||
openPaseoHomes.push(paseoHome);
|
||||
const controller = new HubRelationshipController({
|
||||
paseoHome,
|
||||
hostname: "test-daemon.local",
|
||||
serverId: "server-1",
|
||||
daemonPublicKey: "daemon-public-key",
|
||||
logger: pino({ level: "silent" }),
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface HubEnrollment {
|
||||
idempotencyKey: string;
|
||||
hubOrigin: string;
|
||||
token: string;
|
||||
hostname: string;
|
||||
serverId: string;
|
||||
daemonPublicKey: string;
|
||||
credentialVerifier: string;
|
||||
@@ -96,6 +97,7 @@ export class DirectHubRelationshipRemote implements HubRelationshipRemote {
|
||||
body: JSON.stringify({
|
||||
daemonId: input.daemonId,
|
||||
idempotencyKey: input.idempotencyKey,
|
||||
hostname: input.hostname,
|
||||
serverId: input.serverId,
|
||||
daemonPublicKey: input.daemonPublicKey,
|
||||
credentialVerifier: input.credentialVerifier,
|
||||
|
||||
Reference in New Issue
Block a user