a414f03a59
- Configure tailscale serve automatically for each instance on Windows userspace networking. - Add local UI buttons: start/stop/reset/delete instances (stop/start preserve volumes). - Clean shutdown: stop tailscaled and instances, notify server with instance_stopped. - Restart tailscaled on agent boot using persisted state when pre-auth key is absent. - Sync instance stopped/deleted status to dashboard (server/lib/websocket.ts). - Security: include prior authz/scoping changes across API routes, ephemeral pre-auth keys, ACL policy, internal API key. - Update SUIVI_VPN_ONDEMAND.md and docs/ONBOARDING_CLIENT.md. - Bump agent version to 0.3.5.
76 lines
1.7 KiB
TypeScript
76 lines
1.7 KiB
TypeScript
interface HeadscaleUser {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
interface HeadscalePreAuthKey {
|
|
key: string;
|
|
expiration: string;
|
|
aclTags: string[];
|
|
}
|
|
|
|
export async function getHeadscaleUserId(
|
|
baseUrl: string,
|
|
apiKey: string,
|
|
userName: string
|
|
): Promise<string> {
|
|
const res = await fetch(
|
|
`${baseUrl}/api/v1/user?name=${encodeURIComponent(userName)}`,
|
|
{
|
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
}
|
|
);
|
|
if (!res.ok) {
|
|
throw new Error(
|
|
`Headscale list users failed: ${res.status} ${await res.text()}`
|
|
);
|
|
}
|
|
const data = (await res.json()) as { users: HeadscaleUser[] };
|
|
const user = data.users.find((u) => u.name === userName);
|
|
if (!user) {
|
|
throw new Error(`Headscale user not found: ${userName}`);
|
|
}
|
|
return user.id;
|
|
}
|
|
|
|
export async function createEphemeralPreAuthKey(
|
|
baseUrl: string,
|
|
apiKey: string,
|
|
userId: string,
|
|
options: {
|
|
expirationMinutes?: number;
|
|
aclTags?: string[];
|
|
} = {}
|
|
): Promise<string> {
|
|
const expirationMinutes = options.expirationMinutes ?? 15;
|
|
const aclTags = options.aclTags ?? [];
|
|
|
|
const expiration = new Date(
|
|
Date.now() + expirationMinutes * 60 * 1000
|
|
).toISOString();
|
|
|
|
const res = await fetch(`${baseUrl}/api/v1/preauthkey`, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
user: userId,
|
|
reusable: false,
|
|
ephemeral: false,
|
|
expiration,
|
|
aclTags,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error(
|
|
`Headscale create preauthkey failed: ${res.status} ${await res.text()}`
|
|
);
|
|
}
|
|
|
|
const data = (await res.json()) as { preAuthKey: HeadscalePreAuthKey };
|
|
return data.preAuthKey.key;
|
|
}
|