mirror of
https://github.com/tale/headplane.git
synced 2026-08-10 05:56:52 +00:00
add oidc.enabled flag for helm and config generation
allows defining oidc config without enabling it
This commit is contained in:
+140
-149
@@ -1,206 +1,197 @@
|
||||
import { type } from 'arktype';
|
||||
import log from '~/utils/log';
|
||||
import DockerIntegration from './integration/docker';
|
||||
import KubernetesIntegration from './integration/kubernetes';
|
||||
import ProcIntegration from './integration/proc';
|
||||
import { deprecatedField } from './utils';
|
||||
import { type } from "arktype";
|
||||
|
||||
import log from "~/utils/log";
|
||||
|
||||
import DockerIntegration from "./integration/docker";
|
||||
import KubernetesIntegration from "./integration/kubernetes";
|
||||
import ProcIntegration from "./integration/proc";
|
||||
import { deprecatedField } from "./utils";
|
||||
|
||||
export const pathSupportedKeys = [
|
||||
'server.cookie_secret',
|
||||
'oidc.client_secret',
|
||||
'oidc.headscale_api_key',
|
||||
'integration.agent.pre_authkey',
|
||||
"server.cookie_secret",
|
||||
"oidc.client_secret",
|
||||
"oidc.headscale_api_key",
|
||||
"integration.agent.pre_authkey",
|
||||
] as const;
|
||||
|
||||
const serverConfig = type({
|
||||
host: 'string.ip = "0.0.0.0"',
|
||||
port: 'number.integer = 3000',
|
||||
base_url: 'string.url?',
|
||||
data_path: 'string.lower = "/var/lib/headplane/"',
|
||||
info_secret: 'string?',
|
||||
host: 'string.ip = "0.0.0.0"',
|
||||
port: "number.integer = 3000",
|
||||
base_url: "string.url?",
|
||||
data_path: 'string.lower = "/var/lib/headplane/"',
|
||||
info_secret: "string?",
|
||||
|
||||
cookie_secret: '(32 <= string <= 32)',
|
||||
cookie_secure: 'boolean = true',
|
||||
cookie_domain: 'string.lower?',
|
||||
cookie_max_age: 'number.integer = 86400',
|
||||
cookie_secret: "(32 <= string <= 32)",
|
||||
cookie_secure: "boolean = true",
|
||||
cookie_domain: "string.lower?",
|
||||
cookie_max_age: "number.integer = 86400",
|
||||
});
|
||||
|
||||
const partialServerConfig = type({
|
||||
host: 'string.ip?',
|
||||
port: 'number.integer?',
|
||||
base_url: 'string.url?',
|
||||
data_path: 'string.lower?',
|
||||
info_secret: 'string?',
|
||||
host: "string.ip?",
|
||||
port: "number.integer?",
|
||||
base_url: "string.url?",
|
||||
data_path: "string.lower?",
|
||||
info_secret: "string?",
|
||||
|
||||
cookie_secret: '(32 <= string <= 32)?',
|
||||
cookie_secure: 'boolean?',
|
||||
cookie_domain: 'string.lower?',
|
||||
cookie_max_age: 'number.integer?',
|
||||
cookie_secret: "(32 <= string <= 32)?",
|
||||
cookie_secure: "boolean?",
|
||||
cookie_domain: "string.lower?",
|
||||
cookie_max_age: "number.integer?",
|
||||
});
|
||||
|
||||
const headscaleConfig = type({
|
||||
url: type('string.url').pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v)),
|
||||
public_url: type('string.url')
|
||||
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
|
||||
.optional(),
|
||||
config_path: 'string.lower?',
|
||||
config_strict: 'boolean = true',
|
||||
dns_records_path: 'string.lower?',
|
||||
tls_cert_path: 'string.lower?',
|
||||
url: type("string.url").pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v)),
|
||||
public_url: type("string.url")
|
||||
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||
.optional(),
|
||||
config_path: "string.lower?",
|
||||
config_strict: "boolean = true",
|
||||
dns_records_path: "string.lower?",
|
||||
tls_cert_path: "string.lower?",
|
||||
});
|
||||
|
||||
const partialHeadscaleConfig = type({
|
||||
url: type('string.url')
|
||||
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
|
||||
.optional(),
|
||||
public_url: type('string.url')
|
||||
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
|
||||
.optional(),
|
||||
config_path: 'string.lower?',
|
||||
config_strict: 'boolean?',
|
||||
dns_records_path: 'string.lower?',
|
||||
tls_cert_path: 'string.lower?',
|
||||
url: type("string.url")
|
||||
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||
.optional(),
|
||||
public_url: type("string.url")
|
||||
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||
.optional(),
|
||||
config_path: "string.lower?",
|
||||
config_strict: "boolean?",
|
||||
dns_records_path: "string.lower?",
|
||||
tls_cert_path: "string.lower?",
|
||||
});
|
||||
|
||||
const oidcConfig = type({
|
||||
issuer: 'string.url',
|
||||
client_id: 'string',
|
||||
client_secret: 'string',
|
||||
headscale_api_key: 'string',
|
||||
use_pkce: 'boolean = false',
|
||||
redirect_uri: type('string.url')
|
||||
.pipe((value, ctx) => {
|
||||
log.warn(
|
||||
'config',
|
||||
'%s is deprecated and will be removed in 0.7.0',
|
||||
ctx.propString,
|
||||
);
|
||||
enabled: "boolean = true",
|
||||
issuer: "string.url",
|
||||
client_id: "string",
|
||||
client_secret: "string",
|
||||
headscale_api_key: "string",
|
||||
use_pkce: "boolean = false",
|
||||
redirect_uri: type("string.url")
|
||||
.pipe((value, ctx) => {
|
||||
log.warn("config", "%s is deprecated and will be removed in 0.7.0", ctx.propString);
|
||||
|
||||
const cleanedValue = new URL(value.trim());
|
||||
if (cleanedValue.pathname.endsWith(`${__PREFIX__}/oidc/callback`)) {
|
||||
cleanedValue.pathname = cleanedValue.pathname.replace(
|
||||
`${__PREFIX__}/oidc/callback`,
|
||||
'/',
|
||||
);
|
||||
const cleanedValue = new URL(value.trim());
|
||||
if (cleanedValue.pathname.endsWith(`${__PREFIX__}/oidc/callback`)) {
|
||||
cleanedValue.pathname = cleanedValue.pathname.replace(`${__PREFIX__}/oidc/callback`, "/");
|
||||
|
||||
log.warn(
|
||||
'config',
|
||||
'Please migrate to using `server.base_url` with a value of "%s"',
|
||||
cleanedValue.toString(),
|
||||
);
|
||||
}
|
||||
log.warn(
|
||||
"config",
|
||||
'Please migrate to using `server.base_url` with a value of "%s"',
|
||||
cleanedValue.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return cleanedValue.toString();
|
||||
})
|
||||
.optional(),
|
||||
disable_api_key_login: 'boolean = false',
|
||||
scope: 'string = "openid email profile"',
|
||||
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
|
||||
extra_params: 'Record<string, string>?',
|
||||
return cleanedValue.toString();
|
||||
})
|
||||
.optional(),
|
||||
disable_api_key_login: "boolean = false",
|
||||
scope: 'string = "openid email profile"',
|
||||
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
|
||||
extra_params: "Record<string, string>?",
|
||||
|
||||
authorization_endpoint: 'string.url?',
|
||||
token_endpoint: 'string.url?',
|
||||
userinfo_endpoint: 'string.url?',
|
||||
token_endpoint_auth_method:
|
||||
'"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
||||
authorization_endpoint: "string.url?",
|
||||
token_endpoint: "string.url?",
|
||||
userinfo_endpoint: "string.url?",
|
||||
token_endpoint_auth_method: '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
||||
|
||||
// Old/deprecated options
|
||||
user_storage_file: 'string.lower = "/var/lib/headplane/users.json"',
|
||||
strict_validation: type('unknown').narrow(deprecatedField()).optional(),
|
||||
// Old/deprecated options
|
||||
user_storage_file: 'string.lower = "/var/lib/headplane/users.json"',
|
||||
strict_validation: type("unknown").narrow(deprecatedField()).optional(),
|
||||
});
|
||||
|
||||
const partialOidcConfig = type({
|
||||
issuer: 'string.url?',
|
||||
client_id: 'string?',
|
||||
client_secret: 'string?',
|
||||
use_pkce: 'boolean?',
|
||||
headscale_api_key: 'string?',
|
||||
redirect_uri: 'string.url?',
|
||||
disable_api_key_login: 'boolean?',
|
||||
scope: 'string?',
|
||||
extra_params: 'Record<string, string>?',
|
||||
profile_picture_source: '"oidc" | "gravatar"?',
|
||||
enabled: "boolean?",
|
||||
issuer: "string.url?",
|
||||
client_id: "string?",
|
||||
client_secret: "string?",
|
||||
use_pkce: "boolean?",
|
||||
headscale_api_key: "string?",
|
||||
redirect_uri: "string.url?",
|
||||
disable_api_key_login: "boolean?",
|
||||
scope: "string?",
|
||||
extra_params: "Record<string, string>?",
|
||||
profile_picture_source: '"oidc" | "gravatar"?',
|
||||
|
||||
authorization_endpoint: 'string.url?',
|
||||
token_endpoint: 'string.url?',
|
||||
userinfo_endpoint: 'string.url?',
|
||||
token_endpoint_auth_method:
|
||||
'"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
||||
authorization_endpoint: "string.url?",
|
||||
token_endpoint: "string.url?",
|
||||
userinfo_endpoint: "string.url?",
|
||||
token_endpoint_auth_method: '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
||||
|
||||
// Old/deprecated options
|
||||
user_storage_file: 'string.lower?',
|
||||
strict_validation: type('unknown').narrow(deprecatedField()).optional(),
|
||||
// Old/deprecated options
|
||||
user_storage_file: "string.lower?",
|
||||
strict_validation: type("unknown").narrow(deprecatedField()).optional(),
|
||||
});
|
||||
|
||||
const agentConfig = type({
|
||||
enabled: 'boolean',
|
||||
host_name: 'string = "headplane-agent"',
|
||||
pre_authkey: 'string',
|
||||
cache_ttl: 'number.integer = 180000',
|
||||
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
|
||||
executable_path: 'string = "/usr/libexec/headplane/agent"',
|
||||
work_dir: 'string = "/var/lib/headplane/agent"',
|
||||
enabled: "boolean",
|
||||
host_name: 'string = "headplane-agent"',
|
||||
pre_authkey: "string",
|
||||
cache_ttl: "number.integer = 180000",
|
||||
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
|
||||
executable_path: 'string = "/usr/libexec/headplane/agent"',
|
||||
work_dir: 'string = "/var/lib/headplane/agent"',
|
||||
});
|
||||
|
||||
const partialAgentConfig = type({
|
||||
enabled: 'boolean?',
|
||||
host_name: 'string?',
|
||||
pre_authkey: 'string?',
|
||||
cache_ttl: 'number.integer?',
|
||||
cache_path: 'string?',
|
||||
executable_path: 'string?',
|
||||
work_dir: 'string?',
|
||||
enabled: "boolean?",
|
||||
host_name: "string?",
|
||||
pre_authkey: "string?",
|
||||
cache_ttl: "number.integer?",
|
||||
cache_path: "string?",
|
||||
executable_path: "string?",
|
||||
work_dir: "string?",
|
||||
});
|
||||
|
||||
const integrationConfig = type({
|
||||
docker: DockerIntegration.configSchema.full,
|
||||
kubernetes: KubernetesIntegration.configSchema.full,
|
||||
proc: ProcIntegration.configSchema.full,
|
||||
agent: agentConfig.optional(),
|
||||
docker: DockerIntegration.configSchema.full,
|
||||
kubernetes: KubernetesIntegration.configSchema.full,
|
||||
proc: ProcIntegration.configSchema.full,
|
||||
agent: agentConfig.optional(),
|
||||
}).partial();
|
||||
|
||||
export const partialIntegrationConfig = type({
|
||||
docker: DockerIntegration.configSchema.partial,
|
||||
kubernetes: KubernetesIntegration.configSchema.partial,
|
||||
proc: ProcIntegration.configSchema.partial,
|
||||
agent: partialAgentConfig.optional(),
|
||||
docker: DockerIntegration.configSchema.partial,
|
||||
kubernetes: KubernetesIntegration.configSchema.partial,
|
||||
proc: ProcIntegration.configSchema.partial,
|
||||
agent: partialAgentConfig.optional(),
|
||||
}).partial();
|
||||
|
||||
export const headplaneConfig = type({
|
||||
debug: 'boolean = false',
|
||||
server: serverConfig,
|
||||
headscale: headscaleConfig,
|
||||
oidc: oidcConfig.optional(),
|
||||
integration: integrationConfig.optional(),
|
||||
}).onDeepUndeclaredKey('delete');
|
||||
debug: "boolean = false",
|
||||
server: serverConfig,
|
||||
headscale: headscaleConfig,
|
||||
oidc: oidcConfig.optional(),
|
||||
integration: integrationConfig.optional(),
|
||||
}).onDeepUndeclaredKey("delete");
|
||||
|
||||
export const partialHeadplaneConfig = type({
|
||||
debug: 'boolean?',
|
||||
server: partialServerConfig.optional(),
|
||||
headscale: partialHeadscaleConfig.optional(),
|
||||
oidc: partialOidcConfig.optional(),
|
||||
integration: partialIntegrationConfig.optional(),
|
||||
debug: "boolean?",
|
||||
server: partialServerConfig.optional(),
|
||||
headscale: partialHeadscaleConfig.optional(),
|
||||
oidc: partialOidcConfig.optional(),
|
||||
integration: partialIntegrationConfig.optional(),
|
||||
});
|
||||
|
||||
export type HeadplaneConfig = typeof headplaneConfig.infer;
|
||||
export type PartialHeadplaneConfig = typeof partialHeadplaneConfig.infer;
|
||||
|
||||
type DotNotationToObjects<
|
||||
T extends string,
|
||||
V,
|
||||
> = T extends `${infer K}.${infer Rest}`
|
||||
? { [P in K]?: DotNotationToObjects<Rest, V> }
|
||||
: { [P in `${T}_path`]?: V };
|
||||
type DotNotationToObjects<T extends string, V> = T extends `${infer K}.${infer Rest}`
|
||||
? { [P in K]?: DotNotationToObjects<Rest, V> }
|
||||
: { [P in `${T}_path`]?: V };
|
||||
|
||||
type ObjectDeepMerge<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]: T[K] extends object ? ObjectDeepMerge<T[K]> : T[K];
|
||||
}
|
||||
: T;
|
||||
? {
|
||||
[K in keyof T]: T[K] extends object ? ObjectDeepMerge<T[K]> : T[K];
|
||||
}
|
||||
: T;
|
||||
|
||||
type ConfigWithPathKeys = ObjectDeepMerge<
|
||||
DotNotationToObjects<(typeof pathSupportedKeys)[number], string | undefined>
|
||||
DotNotationToObjects<(typeof pathSupportedKeys)[number], string | undefined>
|
||||
>;
|
||||
|
||||
export type PartialHeadplaneConfigWithPaths = PartialHeadplaneConfig &
|
||||
ConfigWithPathKeys;
|
||||
export type PartialHeadplaneConfigWithPaths = PartialHeadplaneConfig & ConfigWithPathKeys;
|
||||
|
||||
+8
-7
@@ -76,13 +76,14 @@ const appLoadContext = {
|
||||
hsApi,
|
||||
agents,
|
||||
integration: await loadIntegration(config.integration),
|
||||
oidcConnector: config.oidc
|
||||
? createLazyOidcConnector(
|
||||
config.server.base_url,
|
||||
config.oidc,
|
||||
hsApi.getRuntimeClient(config.oidc.headscale_api_key),
|
||||
)
|
||||
: undefined,
|
||||
oidcConnector:
|
||||
config.oidc && config.oidc.enabled !== false
|
||||
? createLazyOidcConnector(
|
||||
config.server.base_url,
|
||||
config.oidc,
|
||||
hsApi.getRuntimeClient(config.oidc.headscale_api_key),
|
||||
)
|
||||
: undefined,
|
||||
db,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user