mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
feat: switch to a generalized api key config
This commit is contained in:
@@ -56,7 +56,7 @@ function mapOidcErrorsToMessages(errors: OidcConnectorError[]) {
|
|||||||
node: (
|
node: (
|
||||||
<Card.Text className="inline">
|
<Card.Text className="inline">
|
||||||
The provided API key for OIDC authentication is invalid. Ensure that{" "}
|
The provided API key for OIDC authentication is invalid. Ensure that{" "}
|
||||||
<Code>oidc.headscale_api_key</Code> is a valid API key.
|
<Code>headscale.api_key</Code> is a valid API key.
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { deprecatedField } from "./utils";
|
|||||||
|
|
||||||
export const pathSupportedKeys = [
|
export const pathSupportedKeys = [
|
||||||
"server.cookie_secret",
|
"server.cookie_secret",
|
||||||
|
"headscale.api_key",
|
||||||
"oidc.client_secret",
|
"oidc.client_secret",
|
||||||
"oidc.headscale_api_key",
|
"oidc.headscale_api_key",
|
||||||
"integration.agent.pre_authkey",
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const serverConfig = type({
|
const serverConfig = type({
|
||||||
@@ -45,6 +45,7 @@ const headscaleConfig = type({
|
|||||||
public_url: type("string.url")
|
public_url: type("string.url")
|
||||||
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||||
.optional(),
|
.optional(),
|
||||||
|
api_key: "string?",
|
||||||
config_path: "string.lower?",
|
config_path: "string.lower?",
|
||||||
config_strict: "boolean = true",
|
config_strict: "boolean = true",
|
||||||
dns_records_path: "string.lower?",
|
dns_records_path: "string.lower?",
|
||||||
@@ -58,6 +59,7 @@ const partialHeadscaleConfig = type({
|
|||||||
public_url: type("string.url")
|
public_url: type("string.url")
|
||||||
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||||
.optional(),
|
.optional(),
|
||||||
|
api_key: "string?",
|
||||||
config_path: "string.lower?",
|
config_path: "string.lower?",
|
||||||
config_strict: "boolean?",
|
config_strict: "boolean?",
|
||||||
dns_records_path: "string.lower?",
|
dns_records_path: "string.lower?",
|
||||||
@@ -69,7 +71,12 @@ const oidcConfig = type({
|
|||||||
issuer: "string.url",
|
issuer: "string.url",
|
||||||
client_id: "string",
|
client_id: "string",
|
||||||
client_secret: "string",
|
client_secret: "string",
|
||||||
headscale_api_key: "string",
|
headscale_api_key: type("string")
|
||||||
|
.pipe((value, ctx) => {
|
||||||
|
log.warn("config", "%s is deprecated, use headscale.api_key instead", ctx.propString);
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
use_pkce: "boolean = false",
|
use_pkce: "boolean = false",
|
||||||
redirect_uri: type("string.url")
|
redirect_uri: type("string.url")
|
||||||
.pipe((value, ctx) => {
|
.pipe((value, ctx) => {
|
||||||
@@ -128,21 +135,21 @@ const partialOidcConfig = type({
|
|||||||
const agentConfig = type({
|
const agentConfig = type({
|
||||||
enabled: "boolean",
|
enabled: "boolean",
|
||||||
host_name: 'string = "headplane-agent"',
|
host_name: 'string = "headplane-agent"',
|
||||||
pre_authkey: "string",
|
|
||||||
cache_ttl: "number.integer = 180000",
|
cache_ttl: "number.integer = 180000",
|
||||||
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
|
|
||||||
executable_path: 'string = "/usr/libexec/headplane/agent"',
|
executable_path: 'string = "/usr/libexec/headplane/agent"',
|
||||||
work_dir: 'string = "/var/lib/headplane/agent"',
|
work_dir: 'string = "/var/lib/headplane/agent"',
|
||||||
|
pre_authkey: type("unknown").narrow(deprecatedField()).optional(),
|
||||||
|
cache_path: type("unknown").narrow(deprecatedField()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const partialAgentConfig = type({
|
const partialAgentConfig = type({
|
||||||
enabled: "boolean?",
|
enabled: "boolean?",
|
||||||
host_name: "string?",
|
host_name: "string?",
|
||||||
pre_authkey: "string?",
|
|
||||||
cache_ttl: "number.integer?",
|
cache_ttl: "number.integer?",
|
||||||
cache_path: "string?",
|
|
||||||
executable_path: "string?",
|
executable_path: "string?",
|
||||||
work_dir: "string?",
|
work_dir: "string?",
|
||||||
|
pre_authkey: type("unknown").narrow(deprecatedField()).optional(),
|
||||||
|
cache_path: type("unknown").narrow(deprecatedField()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationConfig = type({
|
const integrationConfig = type({
|
||||||
|
|||||||
+27
-6
@@ -11,7 +11,7 @@ import { createDbClient } from "./db/client.server";
|
|||||||
import { createHeadscaleInterface } from "./headscale/api";
|
import { createHeadscaleInterface } from "./headscale/api";
|
||||||
import { loadHeadscaleConfig } from "./headscale/config-loader";
|
import { loadHeadscaleConfig } from "./headscale/config-loader";
|
||||||
import { createLiveStore, nodesResource, usersResource } from "./headscale/live-store";
|
import { createLiveStore, nodesResource, usersResource } from "./headscale/live-store";
|
||||||
import { createHeadplaneAgent } from "./hp-agent";
|
import { createAgentManager } from "./hp-agent";
|
||||||
import { createAuthService } from "./web/auth";
|
import { createAuthService } from "./web/auth";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -36,10 +36,31 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const db = await createDbClient(join(config.server.data_path, "hp_persist.db"));
|
const db = await createDbClient(join(config.server.data_path, "hp_persist.db"));
|
||||||
const agents = await createHeadplaneAgent(config.integration?.agent, config.headscale.url, db);
|
|
||||||
|
|
||||||
const hsApi = await createHeadscaleInterface(config.headscale.url, config.headscale.tls_cert_path);
|
const hsApi = await createHeadscaleInterface(config.headscale.url, config.headscale.tls_cert_path);
|
||||||
|
|
||||||
|
// Resolve the Headscale API key: headscale.api_key takes precedence,
|
||||||
|
// falling back to the deprecated oidc.headscale_api_key for compatibility.
|
||||||
|
const headscaleApiKey = config.headscale.api_key ?? config.oidc?.headscale_api_key;
|
||||||
|
|
||||||
|
const agents = headscaleApiKey
|
||||||
|
? await createAgentManager(
|
||||||
|
config.integration?.agent,
|
||||||
|
config.headscale.url,
|
||||||
|
headscaleApiKey,
|
||||||
|
(user, ephemeral, reusable, expiration, aclTags) =>
|
||||||
|
hsApi
|
||||||
|
.getRuntimeClient(headscaleApiKey)
|
||||||
|
.createPreAuthKey(user, ephemeral, reusable, expiration, aclTags),
|
||||||
|
() => hsApi.getRuntimeClient(headscaleApiKey).getUsers(),
|
||||||
|
db,
|
||||||
|
)
|
||||||
|
: (() => {
|
||||||
|
if (config.integration?.agent?.enabled) {
|
||||||
|
log.warn("agent", "Agent is enabled but no headscale.api_key is configured");
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})();
|
||||||
|
|
||||||
// We also use this file to load anything needed by the react router code.
|
// We also use this file to load anything needed by the react router code.
|
||||||
// These are usually per-request things that we need access to, like the
|
// These are usually per-request things that we need access to, like the
|
||||||
// helper that can issue and revoke cookies.
|
// helper that can issue and revoke cookies.
|
||||||
@@ -80,13 +101,13 @@ const appLoadContext = {
|
|||||||
agents,
|
agents,
|
||||||
integration: await loadIntegration(config.integration),
|
integration: await loadIntegration(config.integration),
|
||||||
oidc:
|
oidc:
|
||||||
config.oidc && config.oidc.enabled !== false
|
config.oidc && config.oidc.enabled !== false && headscaleApiKey
|
||||||
? {
|
? {
|
||||||
apiKey: config.oidc.headscale_api_key,
|
apiKey: headscaleApiKey,
|
||||||
connector: createLazyOidcConnector(
|
connector: createLazyOidcConnector(
|
||||||
config.server.base_url,
|
config.server.base_url,
|
||||||
config.oidc,
|
config.oidc,
|
||||||
hsApi.getRuntimeClient(config.oidc.headscale_api_key),
|
hsApi.getRuntimeClient(headscaleApiKey),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
+15
-19
@@ -70,6 +70,11 @@ headscale:
|
|||||||
# in the Web UI, but they cannot be changed.
|
# in the Web UI, but they cannot be changed.
|
||||||
config_path: "/etc/headscale/config.yaml"
|
config_path: "/etc/headscale/config.yaml"
|
||||||
|
|
||||||
|
# The API key used by Headplane for server-side operations.
|
||||||
|
# This is required for OIDC authentication and the Headplane agent.
|
||||||
|
# Generate one with: headscale apikeys create
|
||||||
|
# api_key: "<your-api-key>"
|
||||||
|
|
||||||
# Whether the Headscale configuration should be strictly validated
|
# Whether the Headscale configuration should be strictly validated
|
||||||
# when reading from `config_path`. If true, Headplane will not interact
|
# when reading from `config_path`. If true, Headplane will not interact
|
||||||
# with Headscale if there are any issues with the configuration file.
|
# with Headscale if there are any issues with the configuration file.
|
||||||
@@ -91,26 +96,19 @@ headscale:
|
|||||||
|
|
||||||
# Integration configurations for Headplane to interact with Headscale
|
# Integration configurations for Headplane to interact with Headscale
|
||||||
integration:
|
integration:
|
||||||
# The Headplane agent allows retrieving information about nodes
|
# The Headplane agent periodically syncs node information (version, OS, etc.)
|
||||||
# This allows the UI to display version, OS, and connectivity data
|
# from your Tailnet. It auto-generates ephemeral pre-auth keys using the
|
||||||
# You will see the Headplane agent in your Tailnet as a node when
|
# OIDC headscale_api_key, so no manual key configuration is needed.
|
||||||
# it connects.
|
|
||||||
agent:
|
agent:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
# To connect to your Tailnet, you need to generate a pre-auth key
|
# The name of the Headscale user to create pre-auth keys under.
|
||||||
# This can be done via the web UI or through the `headscale` CLI.
|
# A user with this name must exist in Headscale.
|
||||||
pre_authkey: "<your-preauth-key>"
|
|
||||||
|
|
||||||
# Optionally change the name of the agent in the Tailnet.
|
|
||||||
# host_name: "headplane-agent"
|
# host_name: "headplane-agent"
|
||||||
|
|
||||||
# Configure different caching settings. By default, the agent will store
|
# How often to sync node information (in milliseconds).
|
||||||
# caches in the path below for a maximum of 1 minute. If you want data
|
# Default: 180000 (3 minutes)
|
||||||
# to update faster, reduce the TTL, but this will increase the frequency
|
# cache_ttl: 180000
|
||||||
# of requests to Headscale.
|
|
||||||
# cache_ttl: 60
|
|
||||||
# cache_path: /var/lib/headplane/agent_cache.json
|
|
||||||
|
|
||||||
# The work_dir represents where the agent will store its data to be able
|
# The work_dir represents where the agent will store its data to be able
|
||||||
# to automatically reauthenticate with your Tailnet. It needs to be
|
# to automatically reauthenticate with your Tailnet. It needs to be
|
||||||
@@ -174,10 +172,8 @@ integration:
|
|||||||
# The OIDC issuer URL
|
# The OIDC issuer URL
|
||||||
# issuer: "https://accounts.google.com"
|
# issuer: "https://accounts.google.com"
|
||||||
|
|
||||||
# If you are using OIDC, you need to generate an API key
|
# DEPRECATED: Use headscale.api_key instead.
|
||||||
# that can be used to authenticate other sessions when signing in.
|
# If set, this will be used as a fallback for headscale.api_key.
|
||||||
#
|
|
||||||
# This can be done with `headscale apikeys create --expiration 999d`
|
|
||||||
# headscale_api_key: "<your-headscale-api-key>"
|
# headscale_api_key: "<your-headscale-api-key>"
|
||||||
|
|
||||||
# If your OIDC provider does not support discovery (does not have the URL at
|
# If your OIDC provider does not support discovery (does not have the URL at
|
||||||
|
|||||||
Reference in New Issue
Block a user