nix-configs/modules/services/oauth2Proxy/integration.nix
Lucy Hochkamp 28dc0896b9
Some checks failed
ci/woodpecker/push/build-cache Pipeline failed
ci/woodpecker/cron/dependency-pr Pipeline was successful
navidrome
2025-12-04 00:21:41 +01:00

208 lines
6 KiB
Nix

{
lib,
config,
...
}:
let
inherit (lib)
mkIf
mkOption
mkMerge
attrNames
mapAttrsToList
;
inherit (lib.types)
str
nullOr
submodule
listOf
attrsOf
;
cfg = config.xyno.services.oauth2Proxy;
kanidmCfg = config.xyno.services.kanidm;
oauth2ProxyInternalHostPort = "127.0.0.4:4180";
oauth2ProxyInternalHostPortMetrics = "127.0.0.4:4181";
in
{
options.xyno.services.oauth2Proxy = {
domain = mkOption {
default = "oauth.xyno.systems";
type = str;
};
hosts = mkOption {
type = attrsOf (
submodule (
{ name, ... }:
{
options.allowedGroups = mkOption {
type = nullOr (listOf str);
default = null;
};
options.allowed_email_domains = mkOption {
type = nullOr (listOf str);
default = null;
};
options.allowed_emails = mkOption {
type = nullOr (listOf str);
default = null;
};
options.middlewares = mkOption {
type = listOf str;
description = "add to your service";
default = [
"oauth-errors"
"oauth-host-${name}"
];
};
}
)
);
example = {
"navidrome.xyno.systems" = {
allowedGroups = [ "navidrome_access@idm.xyno.systems" ];
};
};
default = { };
};
};
config = mkIf (cfg.enable && config.xyno.services.kanidm.enable) {
services.kanidm.provision = {
groups = {
proxy_users.members = [ "application_admins" ];
};
systems.oauth2.oauth2_proxy = {
displayName = "oauth2 proxy";
originUrl = [
"https://${cfg.domain}/oauth2/callback"
]
++ (mapAttrsToList (n: v: "https://${n}/oauth2/callback") cfg.hosts);
originLanding = "https://${cfg.domain}/oauth2/sign_in";
preferShortUsername = true;
claimMaps = {
"proxy_group" = {
joinType = "array";
valuesByGroup = {
"proxy_users" = [
"proxy_users"
];
};
};
};
scopeMaps."proxy_users" = [
"email"
"openid"
];
};
};
xyno.services.kanidm.templates.oauth2_proxy = {
wantedBy = [
"oauth2-proxy.service"
];
text = p: ''
OAUTH2_PROXY_CLIENT_ID=${p.clientId}
OAUTH2_PROXY_CLIENT_SECRET=${p.basicSecret}
OAUTH2_PROXY_COOKIE_SECRET=${p.env "COOKIE_SECRET"}
OAUTH2_PROXY_OIDC_ISSUER_URL=https://${kanidmCfg.domain}/oauth2/openid/${p.clientId}
'';
environmentFiles = [ config.sops.templates.oauth2ProxyEnv.path ];
};
sops.secrets."oauth2Proxy/cookieSecret" = {
sopsFile = ../../../instances/${config.networking.hostName}/secrets/kanidm.yaml;
};
sops.templates."oauth2ProxyEnv" = {
restartUnits = [ "generate-kanidm-template-oauth2_proxy.service" ];
content = ''
COOKIE_SECRET=${config.sops.placeholder."oauth2Proxy/cookieSecret"}
'';
};
xyno.services.monitoring.exporters.oauth2Proxy = "http://${oauth2ProxyInternalHostPortMetrics}";
systemd.services.oauth2Proxy.after = [ "traefik.service" ];
xyno.services.oauth2Proxy = {
environmentFiles = [ kanidmCfg.templates.oauth2_proxy.path ];
settings = mkMerge [
{
provider = "oidc";
scope = "openid email";
oidc_groups_claim = "proxy_group";
allowed_groups = [ "proxy_users" ];
http_address = "${oauth2ProxyInternalHostPort}";
https_address = "";
whitelist_domains = attrNames cfg.hosts;
email_domains = "*";
skip_provider_button = true;
code_challenge_method = "S256";
set_xauthrequest = true;
}
(mkIf config.xyno.services.monitoring.enable {
metrics_address = "http://${oauth2ProxyInternalHostPortMetrics}";
})
];
};
xyno.services.traefik.simpleProxy = mkMerge (
[
{
oauth = {
rule = "Host(`${cfg.domain}`) && PathPrefix(`/oauth2`)";
internal = "http://${oauth2ProxyInternalHostPort}";
middlewares = [ "auth-headers" ];
host = cfg.domain;
};
}
]
++ (mapAttrsToList (n: v: {
"oauth-host-${n}" = {
rule = "Host(`${n}`) && PathPrefix(`/oauth2`)";
internal = "http://${oauth2ProxyInternalHostPort}";
middlewares = [ "auth-headers" ];
host = n;
};
}) cfg.hosts)
);
services.traefik.dynamicConfigOptions.http.middlewares = mkMerge (
(mapAttrsToList (n: v: {
"oauth-host-${n}" =
let
maybeQueryArg =
name: value:
if name == "middlewares" || value == null then
null
else
"${name}=${lib.concatStringsSep "," (builtins.map lib.escapeURL value)}";
allArgs = lib.mapAttrsToList maybeQueryArg v;
cleanArgs = builtins.filter (x: x != null) allArgs;
cleanArgsStr = lib.concatStringsSep "&" cleanArgs;
in
{
forwardAuth = {
address = "https://${cfg.domain}/oauth2/auth?${cleanArgsStr}";
authResponseHeaders = [
"X-Auth-Request-User"
"X-Auth-Request-Groups"
"X-Auth-Request-Email"
"X-Auth-Request-Preferred-Username"
];
trustForwardHeader = true;
};
};
}) cfg.hosts)
++ [
{
auth-headers.headers = {
frameDeny = true;
contentTypeNosniff = true;
};
oauth-errors.errors = {
status = [ "401-403" ];
service = config.xyno.services.traefik.simpleProxy.oauth.serviceName;
query = "/oauth2/sign_in?rd={url}";
};
}
]
);
};
}