mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 15:58:14 +00:00
180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
import { parse } from "yaml";
|
|
|
|
import { loadHeadscaleConfig } from "~/server/headscale/config-loader";
|
|
|
|
describe("Headscale config loader", () => {
|
|
let dir: string;
|
|
|
|
beforeEach(async () => {
|
|
dir = await mkdtemp(join(tmpdir(), "headplane-config-loader-"));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("tolerates unknown Headscale keys while reading known defaults", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
await writeFile(
|
|
path,
|
|
[
|
|
"server_url: http://localhost:8080",
|
|
"future_headscale_key:",
|
|
" nested: true",
|
|
"randomize_client_port: false",
|
|
"auto_update:",
|
|
" enabled: true",
|
|
].join("\n"),
|
|
);
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
expect(config.readable()).toBe(true);
|
|
expect(config.getDNSConfig()).toMatchObject({
|
|
magicDns: true,
|
|
baseDomain: "",
|
|
nameservers: [],
|
|
splitDns: {},
|
|
searchDomains: [],
|
|
overrideDns: true,
|
|
});
|
|
});
|
|
|
|
test("falls back for invalid consumed values without rejecting the whole config", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
await writeFile(
|
|
path,
|
|
[
|
|
"server_url: http://localhost:8080",
|
|
"dns:",
|
|
" magic_dns: maybe",
|
|
" base_domain: 1234",
|
|
" override_local_dns: nope",
|
|
" nameservers:",
|
|
" global: 1.1.1.1",
|
|
" split:",
|
|
" example.com: 1.1.1.1",
|
|
" search_domains: example.com",
|
|
"oidc:",
|
|
" issuer: https://issuer.example.com",
|
|
" allowed_domains: example.com",
|
|
].join("\n"),
|
|
);
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
expect(config.readable()).toBe(true);
|
|
expect(config.getDNSConfig()).toMatchObject({
|
|
magicDns: true,
|
|
baseDomain: "",
|
|
nameservers: [],
|
|
splitDns: {},
|
|
searchDomains: [],
|
|
overrideDns: true,
|
|
});
|
|
expect(config.getOIDCConfig()).toMatchObject({
|
|
allowedDomains: [],
|
|
});
|
|
});
|
|
|
|
test("patches only requested paths and does not write effective defaults", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
await writeFile(
|
|
path,
|
|
[
|
|
"server_url: http://localhost:8080",
|
|
"future_headscale_key: keep-me",
|
|
"dns:",
|
|
" base_domain: example.com",
|
|
].join("\n"),
|
|
);
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
await config.patch([{ path: "dns.magic_dns", value: false }]);
|
|
|
|
const written = parse(await readFile(path, "utf8"));
|
|
expect(written.future_headscale_key).toBe("keep-me");
|
|
expect(written.dns).toEqual({
|
|
base_domain: "example.com",
|
|
magic_dns: false,
|
|
});
|
|
});
|
|
|
|
test("patches quoted split DNS domains", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
await writeFile(
|
|
path,
|
|
["server_url: http://localhost:8080", "dns:", " nameservers:", " split: {}"].join("\n"),
|
|
);
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
await config.patch([{ path: 'dns.nameservers.split."corp.example.com"', value: ["1.1.1.1"] }]);
|
|
|
|
const written = parse(await readFile(path, "utf8"));
|
|
expect(written.dns.nameservers.split).toEqual({
|
|
"corp.example.com": ["1.1.1.1"],
|
|
});
|
|
});
|
|
|
|
test("prefers extra records JSON over inline YAML records", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
const recordsPath = join(dir, "extra-records.json");
|
|
await writeFile(recordsPath, JSON.stringify([{ name: "json", type: "A", value: "1.1.1.1" }]));
|
|
await writeFile(
|
|
path,
|
|
[
|
|
"server_url: http://localhost:8080",
|
|
"dns:",
|
|
" extra_records_path: " + recordsPath,
|
|
" extra_records:",
|
|
" - name: yaml",
|
|
" type: A",
|
|
" value: 2.2.2.2",
|
|
].join("\n"),
|
|
);
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
expect(config.dnsRecords()).toEqual([{ name: "json", type: "A", value: "1.1.1.1" }]);
|
|
|
|
await config.addDNS({ name: "new", type: "A", value: "3.3.3.3" });
|
|
|
|
expect(JSON.parse(await readFile(recordsPath, "utf8"))).toEqual([
|
|
{ name: "json", type: "A", value: "1.1.1.1" },
|
|
{ name: "new", type: "A", value: "3.3.3.3" },
|
|
]);
|
|
expect(parse(await readFile(path, "utf8")).dns.extra_records).toEqual([
|
|
{ name: "yaml", type: "A", value: "2.2.2.2" },
|
|
]);
|
|
});
|
|
|
|
test("reads OIDC restrictions as empty arrays when unset", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
await writeFile(
|
|
path,
|
|
["server_url: http://localhost:8080", "oidc:", " issuer: https://issuer.example.com"].join(
|
|
"\n",
|
|
),
|
|
);
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
expect(config.getOIDCConfig()).toEqual({
|
|
issuer: "https://issuer.example.com",
|
|
allowedDomains: [],
|
|
allowedGroups: [],
|
|
allowedUsers: [],
|
|
});
|
|
});
|
|
|
|
test("does not treat an empty OIDC block as configured", async () => {
|
|
const path = join(dir, "config.yaml");
|
|
await writeFile(path, ["server_url: http://localhost:8080", "oidc: {}"].join("\n"));
|
|
|
|
const config = await loadHeadscaleConfig(path);
|
|
expect(config.getOIDCConfig()).toBeUndefined();
|
|
expect(config.hasOIDCConfig()).toBe(false);
|
|
});
|
|
});
|