2023-08-10 05:39:32 +02:00
|
|
|
{ config, pkgs, lib, ... }:
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.shb.authelia;
|
|
|
|
|
2024-01-12 08:22:46 +01:00
|
|
|
contracts = pkgs.callPackage ../contracts {};
|
|
|
|
|
2023-08-10 05:39:32 +02:00
|
|
|
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
|
|
|
|
|
|
|
autheliaCfg = config.services.authelia.instances.${fqdn};
|
2023-12-11 06:56:31 +01:00
|
|
|
|
|
|
|
template = file: newPath: replacements:
|
|
|
|
let
|
|
|
|
templatePath = newPath + ".template";
|
|
|
|
|
|
|
|
sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "\"s|${from}|${to}|\"") replacements);
|
|
|
|
in
|
|
|
|
''
|
|
|
|
ln -fs ${file} ${templatePath}
|
|
|
|
rm ${newPath} || :
|
|
|
|
sed ${sedPatterns} ${templatePath} > ${newPath}
|
|
|
|
'';
|
2023-08-10 05:39:32 +02:00
|
|
|
in
|
|
|
|
{
|
|
|
|
options.shb.authelia = {
|
|
|
|
enable = lib.mkEnableOption "selfhostblocks.authelia";
|
|
|
|
|
|
|
|
subdomain = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "Subdomain under which Authelia will be served.";
|
2023-11-30 19:38:35 +01:00
|
|
|
example = "auth";
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
domain = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "domain under which Authelia will be served.";
|
|
|
|
example = "mydomain.com";
|
|
|
|
};
|
|
|
|
|
2024-01-12 08:22:46 +01:00
|
|
|
ssl = lib.mkOption {
|
|
|
|
description = "Path to SSL files";
|
|
|
|
type = lib.types.nullOr contracts.ssl.certs;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
|
2023-08-10 05:39:32 +02:00
|
|
|
ldapEndpoint = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "Endpoint for LDAP authentication backend.";
|
|
|
|
example = "ldap.example.com";
|
|
|
|
};
|
|
|
|
|
|
|
|
dcdomain = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "dc domain for ldap.";
|
|
|
|
example = "dc=mydomain,dc=com";
|
|
|
|
};
|
|
|
|
|
2023-11-07 09:35:27 +01:00
|
|
|
autheliaUser = lib.mkOption {
|
2023-12-01 07:49:34 +01:00
|
|
|
type = lib.types.str;
|
|
|
|
description = "System user for this Authelia instance.";
|
|
|
|
default = "authelia";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
secrets = lib.mkOption {
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "Secrets needed by Authelia";
|
2023-11-07 09:35:27 +01:00
|
|
|
type = lib.types.submodule {
|
|
|
|
options = {
|
|
|
|
jwtSecretFile = lib.mkOption {
|
2023-12-11 06:56:31 +01:00
|
|
|
type = lib.types.path;
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "File containing the JWT secret.";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
ldapAdminPasswordFile = lib.mkOption {
|
2023-12-11 06:56:31 +01:00
|
|
|
type = lib.types.path;
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "File containing the LDAP admin user password.";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
sessionSecretFile = lib.mkOption {
|
2023-12-11 06:56:31 +01:00
|
|
|
type = lib.types.path;
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "File containing the session secret.";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
storageEncryptionKeyFile = lib.mkOption {
|
2023-12-11 06:56:31 +01:00
|
|
|
type = lib.types.path;
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "File containing the storage encryption key.";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
identityProvidersOIDCHMACSecretFile = lib.mkOption {
|
2023-12-11 06:56:31 +01:00
|
|
|
type = lib.types.path;
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "File containing the identity provider OIDC HMAC secret.";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
identityProvidersOIDCIssuerPrivateKeyFile = lib.mkOption {
|
2023-12-11 06:56:31 +01:00
|
|
|
type = lib.types.path;
|
2023-12-04 09:33:16 +01:00
|
|
|
description = "File containing the identity provider OIDC issuer private key.";
|
2023-11-07 09:35:27 +01:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
oidcClients = lib.mkOption {
|
|
|
|
type = lib.types.listOf lib.types.anything;
|
|
|
|
description = "OIDC clients";
|
|
|
|
default = [];
|
|
|
|
};
|
|
|
|
|
2023-12-11 06:56:31 +01:00
|
|
|
smtp = lib.mkOption {
|
|
|
|
description = "SMTP options.";
|
|
|
|
default = null;
|
|
|
|
type = lib.types.nullOr (lib.types.submodule {
|
|
|
|
options = {
|
|
|
|
from_address = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "SMTP address from which the emails originate.";
|
|
|
|
example = "authelia@mydomain.com";
|
|
|
|
};
|
|
|
|
from_name = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "SMTP name from which the emails originate.";
|
|
|
|
default = "Authelia";
|
|
|
|
};
|
|
|
|
host = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "SMTP host to send the emails to.";
|
|
|
|
};
|
|
|
|
port = lib.mkOption {
|
|
|
|
type = lib.types.port;
|
|
|
|
description = "SMTP port to send the emails to.";
|
|
|
|
default = 25;
|
|
|
|
};
|
|
|
|
username = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "Username to connect to the SMTP host.";
|
|
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "File containing the password to connect to the SMTP host.";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
2023-08-07 07:36:31 +02:00
|
|
|
|
|
|
|
rules = lib.mkOption {
|
|
|
|
type = lib.types.listOf lib.types.anything;
|
|
|
|
description = "Rule based clients";
|
|
|
|
default = [];
|
|
|
|
};
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
config = lib.mkIf cfg.enable {
|
2023-12-11 06:56:31 +01:00
|
|
|
assertions = [
|
|
|
|
{
|
|
|
|
assertion = builtins.length cfg.oidcClients > 0;
|
|
|
|
message = "Must have at least one oidc client otherwise Authelia refuses to start.";
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2023-08-10 05:39:32 +02:00
|
|
|
# Overriding the user name so we don't allow any weird characters anywhere. For example, postgres users do not accept the '.'.
|
|
|
|
users = {
|
|
|
|
groups.${autheliaCfg.user} = {};
|
|
|
|
users.${autheliaCfg.user} = {
|
|
|
|
isSystemUser = true;
|
|
|
|
group = autheliaCfg.user;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
services.authelia.instances.${fqdn} = {
|
|
|
|
enable = true;
|
2023-12-01 07:49:34 +01:00
|
|
|
user = cfg.autheliaUser;
|
2023-08-10 05:39:32 +02:00
|
|
|
|
|
|
|
secrets = {
|
2023-11-07 09:35:27 +01:00
|
|
|
inherit (cfg.secrets) jwtSecretFile storageEncryptionKeyFile;
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
# See https://www.authelia.com/configuration/methods/secrets/
|
|
|
|
environmentVariables = {
|
2023-12-11 06:56:31 +01:00
|
|
|
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = toString cfg.secrets.ldapAdminPasswordFile;
|
|
|
|
AUTHELIA_SESSION_SECRET_FILE = toString cfg.secrets.sessionSecretFile;
|
2023-08-10 05:39:32 +02:00
|
|
|
# Not needed since we use peer auth.
|
|
|
|
# AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE = "/run/secrets/authelia/postgres_password";
|
2023-12-11 06:56:31 +01:00
|
|
|
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = toString cfg.secrets.storageEncryptionKeyFile;
|
|
|
|
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecretFile;
|
|
|
|
AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = toString cfg.secrets.identityProvidersOIDCIssuerPrivateKeyFile;
|
|
|
|
|
|
|
|
AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(isNull cfg.smtp)) (toString cfg.smtp.passwordFile);
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
settings = {
|
|
|
|
server.host = "127.0.0.1";
|
|
|
|
server.port = 9091;
|
|
|
|
|
|
|
|
# Inspired from https://github.com/lldap/lldap/blob/7d1f5abc137821c500de99c94f7579761fc949d8/example_configs/authelia_config.yml
|
|
|
|
authentication_backend = {
|
|
|
|
refresh_interval = "5m";
|
|
|
|
password_reset = {
|
|
|
|
disable = "false";
|
|
|
|
};
|
|
|
|
ldap = {
|
|
|
|
implementation = "custom";
|
|
|
|
url = cfg.ldapEndpoint;
|
|
|
|
timeout = "5s";
|
|
|
|
start_tls = "false";
|
|
|
|
base_dn = cfg.dcdomain;
|
|
|
|
username_attribute = "uid";
|
|
|
|
additional_users_dn = "ou=people";
|
|
|
|
# Sign in with username or email.
|
|
|
|
users_filter = "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))";
|
|
|
|
additional_groups_dn = "ou=groups";
|
|
|
|
groups_filter = "(member={dn})";
|
|
|
|
group_name_attribute = "cn";
|
|
|
|
mail_attribute = "mail";
|
|
|
|
display_name_attribute = "displayName";
|
|
|
|
user = "uid=admin,ou=people,${cfg.dcdomain}";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
totp = {
|
|
|
|
disable = "false";
|
|
|
|
issuer = fqdn;
|
|
|
|
algorithm = "sha1";
|
|
|
|
digits = "6";
|
|
|
|
period = "30";
|
|
|
|
skew = "1";
|
|
|
|
secret_size = "32";
|
|
|
|
};
|
|
|
|
# Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis
|
|
|
|
session = {
|
|
|
|
name = "authelia_session";
|
|
|
|
domain = cfg.domain;
|
|
|
|
same_site = "lax";
|
|
|
|
expiration = "1h";
|
|
|
|
inactivity = "5m";
|
|
|
|
remember_me_duration = "1M";
|
|
|
|
redis = {
|
|
|
|
host = config.services.redis.servers.authelia.unixSocket;
|
|
|
|
port = 0;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
storage = {
|
|
|
|
postgres = {
|
|
|
|
host = "/run/postgresql";
|
|
|
|
username = autheliaCfg.user;
|
|
|
|
database = autheliaCfg.user;
|
|
|
|
port = config.services.postgresql.port;
|
|
|
|
# Uses peer auth for local users, so we don't need a password.
|
|
|
|
password = "test";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
notifier = {
|
2023-12-11 06:56:31 +01:00
|
|
|
filesystem = lib.mkIf (isNull cfg.smtp) {
|
|
|
|
filename = "/tmp/authelia-notifications";
|
|
|
|
};
|
|
|
|
smtp = lib.mkIf (!(isNull cfg.smtp)) {
|
|
|
|
host = cfg.smtp.host;
|
|
|
|
port = cfg.smtp.port;
|
|
|
|
username = cfg.smtp.username;
|
|
|
|
sender = "${cfg.smtp.from_name} <${cfg.smtp.from_address}>";
|
2023-08-10 05:39:32 +02:00
|
|
|
subject = "[Authelia] {title}";
|
|
|
|
startup_check_address = "test@authelia.com";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
access_control = {
|
|
|
|
default_policy = "deny";
|
|
|
|
networks = [
|
|
|
|
{
|
|
|
|
name = "internal";
|
|
|
|
networks = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/18" ];
|
|
|
|
}
|
|
|
|
];
|
|
|
|
rules = [
|
|
|
|
{
|
2023-08-09 09:09:56 +02:00
|
|
|
domain = fqdn;
|
|
|
|
policy = "bypass";
|
|
|
|
resources = [
|
|
|
|
"^/api/.*"
|
|
|
|
];
|
2023-08-10 05:39:32 +02:00
|
|
|
}
|
2023-08-09 09:09:56 +02:00
|
|
|
] ++ cfg.rules;
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
telemetry = {
|
|
|
|
metrics = {
|
|
|
|
enabled = true;
|
|
|
|
address = "tcp://127.0.0.1:9959";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2023-12-11 06:56:31 +01:00
|
|
|
|
|
|
|
settingsFiles = map (client: "/var/lib/authelia-${fqdn}/oidc_client_${client.id}.yaml") cfg.oidcClients;
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
|
2023-12-11 06:56:31 +01:00
|
|
|
systemd.services."authelia-${fqdn}".preStart =
|
|
|
|
let
|
|
|
|
mkCfg = client:
|
|
|
|
let
|
|
|
|
secretFile = client.secretFile;
|
|
|
|
clientWithTmpl = {
|
|
|
|
identity_providers.oidc.clients = [
|
|
|
|
((lib.attrsets.filterAttrs (name: v: name != "secretFile") client) // {
|
|
|
|
secret = "%SECRET%";
|
|
|
|
})
|
|
|
|
];
|
|
|
|
};
|
|
|
|
tmplFile = pkgs.writeText "oidc_client_${client.id}.yaml" (lib.generators.toYAML {} clientWithTmpl);
|
|
|
|
in
|
|
|
|
template tmplFile "/var/lib/authelia-${fqdn}/oidc_client_${client.id}.yaml" {
|
|
|
|
"%SECRET%" = "$(cat ${toString secretFile})";
|
|
|
|
};
|
|
|
|
in
|
|
|
|
lib.mkBefore (lib.concatStringsSep "\n" (map mkCfg cfg.oidcClients));
|
|
|
|
|
2023-08-10 05:39:32 +02:00
|
|
|
services.nginx.virtualHosts.${fqdn} = {
|
2024-01-12 08:22:46 +01:00
|
|
|
forceSSL = !(isNull cfg.ssl);
|
|
|
|
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
|
|
|
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
2023-09-03 00:00:41 +02:00
|
|
|
# Taken from https://github.com/authelia/authelia/issues/178
|
|
|
|
# TODO: merge with config from https://matwick.ca/authelia-nginx-sso/
|
|
|
|
locations."/".extraConfig = ''
|
|
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
add_header X-Content-Type-Options nosniff;
|
|
|
|
add_header X-Frame-Options "SAMEORIGIN";
|
|
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
|
|
add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";
|
|
|
|
add_header X-Download-Options noopen;
|
|
|
|
add_header X-Permitted-Cross-Domain-Policies none;
|
2023-08-10 05:39:32 +02:00
|
|
|
|
2023-09-03 00:00:41 +02:00
|
|
|
proxy_set_header Host $host;
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
proxy_http_version 1.1;
|
|
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
|
|
proxy_set_header Connection "upgrade";
|
|
|
|
proxy_cache_bypass $http_upgrade;
|
|
|
|
|
|
|
|
proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port};
|
|
|
|
proxy_intercept_errors on;
|
|
|
|
if ($request_method !~ ^(POST)$){
|
|
|
|
error_page 401 = /error/401;
|
|
|
|
error_page 403 = /error/403;
|
|
|
|
error_page 404 = /error/404;
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
|
|
|
|
locations."/api/verify".extraConfig = ''
|
|
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
add_header X-Content-Type-Options nosniff;
|
|
|
|
add_header X-Frame-Options "SAMEORIGIN";
|
|
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
|
|
add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";
|
|
|
|
add_header X-Download-Options noopen;
|
|
|
|
add_header X-Permitted-Cross-Domain-Policies none;
|
|
|
|
|
|
|
|
proxy_set_header Host $http_x_forwarded_host;
|
|
|
|
proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port};
|
|
|
|
'';
|
2023-08-10 05:39:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
services.redis.servers.authelia = {
|
|
|
|
enable = true;
|
|
|
|
user = autheliaCfg.user;
|
|
|
|
};
|
|
|
|
|
2023-11-23 10:03:33 +01:00
|
|
|
shb.postgresql.ensures = [
|
2023-11-06 01:37:50 +01:00
|
|
|
{
|
|
|
|
username = autheliaCfg.user;
|
|
|
|
database = autheliaCfg.user;
|
|
|
|
}
|
|
|
|
];
|
2023-08-10 05:39:32 +02:00
|
|
|
|
|
|
|
services.prometheus.scrapeConfigs = [
|
|
|
|
{
|
|
|
|
job_name = "authelia";
|
|
|
|
static_configs = [
|
|
|
|
{
|
|
|
|
targets = ["127.0.0.1:9959"];
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
];
|
|
|
|
};
|
|
|
|
}
|