diff --git a/configuration/default.nix b/configuration/default.nix index b4601b1..60dea4f 100644 --- a/configuration/default.nix +++ b/configuration/default.nix @@ -14,6 +14,7 @@ "${modulesPath}/profiles/minimal.nix" (import ../modules) + ./services/auth.nix ./services/backups.nix ./services/battery-manager.nix ./services/conduit.nix diff --git a/configuration/services/auth.nix b/configuration/services/auth.nix new file mode 100644 index 0000000..eef6983 --- /dev/null +++ b/configuration/services/auth.nix @@ -0,0 +1,105 @@ +{config, ...}: let + user = config.services.authelia.instances.main.user; + domain = "authelia.${config.services.nginx.domain}"; +in { + services.authelia.instances.main = { + enable = true; + settings = { + theme = "auto"; + + access_control.default_policy = "one_factor"; + + authentication_backend = { + password_reset.disable = true; + file.path = "/var/lib/authelia-main/users.yml"; + }; + + notifier.filesystem.filename = "/var/lib/authelia-main/notification.txt"; + + session = { + domain = config.services.nginx.domain; + redis.host = config.services.redis.servers.authelia.unixSocket; + }; + + # server.endpoints.authz.auth-request.implementation = "AuthRequest"; + + storage.postgres = { + host = "/run/postgresql"; + database = user; + username = user; + + password = "unnecessary"; + }; + }; + + secrets = { + storageEncryptionKeyFile = config.sops.secrets."authelia/storageEncryptionKey".path; # Database + sessionSecretFile = config.sops.secrets."authelia/sessionSecret".path; # Redis + jwtSecretFile = config.sops.secrets."authelia/jwtSecret".path; + }; + }; + + systemd.services.authelia-main.after = ["postgresql.service"]; + + services.nginx.virtualHosts."${domain}" = { + forceSSL = true; + enableACME = true; + enableHSTS = true; + + locations = { + "/" = { + proxyPass = "http://127.0.0.1:9091"; + recommendedProxySettings = false; + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-URI $request_uri; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + + proxy_redirect http:// $scheme://; + proxy_cache_bypass $cookie_session; + proxy_no_cache $cookie_session; + + real_ip_header X-Forwarded-For; + real_ip_recursive on; + ''; + }; + + "/api/verify" = { + proxyPass = "http://127.0.0.1:9091"; + recommendedProxySettings = false; + }; + + "/api/authz/" = { + proxyPass = "http://127.0.0.1:9091"; + recommendedProxySettings = false; + }; + }; + }; + + services.redis.servers.authelia = { + inherit user; + enable = true; + }; + + sops.secrets = { + "authelia/storageEncryptionKey" = { + owner = user; + group = user; + }; + + "authelia/sessionSecret" = { + owner = user; + group = user; + }; + + "authelia/jwtSecret" = { + owner = user; + group = user; + }; + }; +} diff --git a/configuration/services/conduit.nix b/configuration/services/conduit.nix index cae2510..3fcadeb 100644 --- a/configuration/services/conduit.nix +++ b/configuration/services/conduit.nix @@ -212,9 +212,9 @@ in { ]; forceSSL = true; + enableHSTS = true; extraConfig = '' merge_slashes off; - access_log /var/log/nginx/${domain}/access.log upstream_time; ''; locations = { diff --git a/configuration/services/foundryvtt.nix b/configuration/services/foundryvtt.nix index 069f336..51b0212 100644 --- a/configuration/services/foundryvtt.nix +++ b/configuration/services/foundryvtt.nix @@ -25,10 +25,7 @@ in { in { forceSSL = true; enableACME = true; - extraConfig = '' - add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; - access_log /var/log/nginx/${domain}/access.log upstream_time; - ''; + enableHSTS = true; locations."/" = { proxyWebsockets = true; diff --git a/configuration/services/gitea.nix b/configuration/services/gitea.nix index 41b8583..ffd21dc 100644 --- a/configuration/services/gitea.nix +++ b/configuration/services/gitea.nix @@ -42,10 +42,7 @@ in { in { forceSSL = true; enableACME = true; - extraConfig = '' - add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; - access_log /var/log/nginx/${domain}/access.log upstream_time; - ''; + enableHSTS = true; locations."/".proxyPass = "http://${httpAddress}:${toString httpPort}"; locations."/metrics" = { diff --git a/configuration/services/metrics/grafana.nix b/configuration/services/metrics/grafana.nix index 8538dc7..446ac21 100644 --- a/configuration/services/metrics/grafana.nix +++ b/configuration/services/metrics/grafana.nix @@ -39,10 +39,8 @@ in { services.nginx.virtualHosts."${domain}" = { forceSSL = true; enableACME = true; - extraConfig = '' - add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; - access_log /var/log/nginx/${domain}/access.log upstream_time; - ''; + enableHSTS = true; + enableAuthorization = true; locations."/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}"; }; } diff --git a/configuration/services/nextcloud.nix b/configuration/services/nextcloud.nix index 3ba967a..73e075e 100644 --- a/configuration/services/nextcloud.nix +++ b/configuration/services/nextcloud.nix @@ -46,9 +46,7 @@ in { services.nginx.virtualHosts."${hostName}" = { forceSSL = true; enableACME = true; - extraConfig = '' - access_log /var/log/nginx/${hostName}/access.log upstream_time; - ''; + # The upstream module already adds HSTS }; # Block repeated failed login attempts diff --git a/configuration/services/postgres.nix b/configuration/services/postgres.nix index 018dc6e..4ec1e83 100644 --- a/configuration/services/postgres.nix +++ b/configuration/services/postgres.nix @@ -1,4 +1,8 @@ -{pkgs, ...}: { +{ + config, + pkgs, + ... +}: { services.postgresql = { package = pkgs.postgresql_14; enable = true; @@ -24,11 +28,16 @@ name = "nextcloud"; ensureDBOwnership = true; } + { + name = config.services.authelia.instances.main.user; + ensureDBOwnership = true; + } ]; ensureDatabases = [ "grafana" "nextcloud" + config.services.authelia.instances.main.user ]; }; } diff --git a/configuration/services/webserver.nix b/configuration/services/webserver.nix index 085b1f7..defcae1 100644 --- a/configuration/services/webserver.nix +++ b/configuration/services/webserver.nix @@ -17,10 +17,7 @@ in { forceSSL = true; enableACME = true; - extraConfig = '' - add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; - access_log /var/log/nginx/${domain}/access.log upstream_time; - ''; + enableHSTS = true; locations."/".proxyPass = "http://${addr}:${toString port}"; }; diff --git a/keys/production.yaml b/keys/production.yaml index 7ed57b3..43b3578 100644 --- a/keys/production.yaml +++ b/keys/production.yaml @@ -1,3 +1,6 @@ +authelia: + storageEncryptionKey: ENC[AES256_GCM,data:OUCC+6Gcr6U7Mub1+DaIyswTV6da1wd1u0WGEm4wpJ8L0mi7WSpEmVjH79YyRhp7AmiZhdFFDXFeEYthBb2AZl+xoS9gqs6rWyfU4ezaCbXBiS/dIhsA5foPg13wq5A33qJWtPTy7DJEgqHaIonnaBuVJIBwH3wzPTHc3bDvBo4=,iv:intiZzngz5cMTtjEI9rTKMW0Xv3KB3ZEgtYN3amwKCE=,tag:AKxfbeZlPs54esHCsVnNCg==,type:str] + sessionSecret: ENC[AES256_GCM,data:GEMWhBltOIOs0g9FsWk3OQGs6dMcbwz3ZuhlyBFYROylsIZb4xTXWLgNwIpHwQukQU3TgvIxbCW/fGRWiALPanE2koSVAHNx0UU0hj1mVNRFQGK4H3EL10tPp7l4PofrcdeCbLPrOwM/xLOuPt+52sKlcbL2Awz5/MmpUVpCKXc=,iv:kWX2ptOpTgW3obBgri0MvVv6gCEPR3o77sldOXFQeks=,tag:je4pqLcEOhuBTQkoZHYNCw==,type:str] battery-manager: email: ENC[AES256_GCM,data:rYLUACXR/n+bLBmZ,iv:sUBEkh2+7qGjHZ5R23e/hoCiyTA7GTL4bJvXmxjZ5Sw=,tag:fdPMllaQQfRgX0WZKIre4g==,type:str] password: ENC[AES256_GCM,data:7cokZa6Q6ahSeiFPz+cV,iv:vz405P0IcG9FsAQXlY7mi78GuushQUKJm2irG6buGzc=,tag:JLHG2jTkJDGbinAq9dXRsQ==,type:str] @@ -31,8 +34,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2024-04-06T15:32:49Z" - mac: ENC[AES256_GCM,data:ShqLJf9b20LdmjK6MMPtI3KicE+fPc0ejzVGEIdgbNs7ueDwdt7jqgpDrpiyf+vW86tr3I1E1VTlh127XlSH/RZDRRHehpX0tnBiF0zMscmt1vdinY4cPhTwhLJ1fdpVpY8ihdOqv0UFyC39HP78aWESX5S/dJZQ6vS7K5VGKTY=,iv:TYE9f9iyrUQxmMeKXApEYsSPcMWK8vndyBm7HtJyJPo=,tag:vSlobwA1R0Go7BYgNVpMkw==,type:str] + lastmodified: "2024-04-11T23:38:56Z" + mac: ENC[AES256_GCM,data:GjIB0EbWsh4o+QoFSyIXgGYnNhRlvfSmue1LyTt6oUlIjNgODhdIB8px8LnRo0rmm/f1YHbDq2MFOxlgdm3PTNaqm/MoKyW3r/wuAeWADsYayQszLNxyhTMXcjWtfm6zCRIuc/+YyM44pXRfVrOZRAin9B6pmJZsRJwBAZpogbU=,iv:r/ZQZvrP0E9dOW5fhBH2I21Z0uv2e3njdEGmadxEALg=,tag:iZvbGTvRJFo80n8aoKSSmQ==,type:str] pgp: - created_at: "2024-03-18T04:02:00Z" enc: |- diff --git a/keys/staging.yaml b/keys/staging.yaml index 029e6ac..ebdd628 100644 --- a/keys/staging.yaml +++ b/keys/staging.yaml @@ -1,3 +1,7 @@ +authelia: + storageEncryptionKey: ENC[AES256_GCM,data:8X6Zvq7ct1MkMH+lc5kTTHXOo6rGyhSzc3aWxGRA5tnBet+TGcENo0RYTYmactsPGVpTIUGGplaG7B7dqRPhkdDHhbCCZCm2nLaYjpVJ241DrpUNKHn8lvg/bMxUQ/Dvw76ByYuWN6bREr3XRaBztBSPzld8zTSYx71I0CKY7vk=,iv:cJSwfuVWO39qqKCGt2Mvw7pN8+hD6kRH9v4c/u4hLuk=,tag:YhdlXuX2ETxjb443RI8MsA==,type:str] + sessionSecret: ENC[AES256_GCM,data:dnoWmc4HND62w3jMXL+akncAEb61c/I70DgRytx55Wxcn4rMiswp6zCkRdsP4CkouTQ1lyAcQrubp5I8M9Kyow/KBMYz9dPkr4+2xJ9w0SEmAVhyPe2DFvYos3x0Uvx5S0B3o1mXoXqbg78e4w5yEIbALiJT8VPGrWK8Cl4nVPo=,iv:FHDXUW2DWUmEZzWUYkYduogdVOtvMlRH4/fVg05cZaI=,tag:u282WQnHpBsZGYJH7mFFKA==,type:str] + jwtSecret: ENC[AES256_GCM,data:0M3AyoMp+orrljl5NsxmthzrHMmu0REcz7+9fpFKbwwqV6KqlpgGddjYZIsTpHEWEq9zhZ2YWLJkMxKdDgROVHUFZGKut28JPSAjjY+1V0wxNBnfSCnxEv5BUw2+cCxcpCwYQyNfRK6SotTt8aqpxvda4oRXpzxV6SW7ogDjc6E=,iv:D57SynZkW2JuFyX6bpZYkxpR2KtkOmKaySg1Bxim0r8=,tag:JCPGZaumdHrtgcH16A7b+g==,type:str] battery-manager: email: ENC[AES256_GCM,data:LM/EGzWHfVQ=,iv:jFaoUQuUfuGoOyj/GFpdI8TerH/c8D9fjvio+IEt2Tc=,tag:IWLiN011JEnHRLIXWQgfmA==,type:str] password: ENC[AES256_GCM,data:SUxjqS7SJHM=,iv:LvdKk88S+nSImh6/ZezbFGLCUBu1Lpdu+neF2xyHdBg=,tag:rcMyZuW4FVNbcbz00wQKBg==,type:str] @@ -31,8 +35,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2024-04-06T15:33:40Z" - mac: ENC[AES256_GCM,data:qB9uDDM5K6+BmeAKyTJ0Sel6Um0Fc9IhdV3wAn13WxpwDtxmMsdqnwaewI/KepsRG3k76x9vkYL+oKkUysqq1r1FkocUDg4DnKnf1KtKo2Zm9MPcVRG833m6oDoTeGnmgrAMTDKy1tUdGkXW40IfbMakbSjSIfLbrymtoHeVbaE=,iv:8P8M4Ueo3Idlgo+Yqj6JUtFfWX949fz6HfRHEOy1/Vg=,tag:ou+IGZSQSfX6gNoxbpAipg==,type:str] + lastmodified: "2024-04-12T01:00:31Z" + mac: ENC[AES256_GCM,data:fVnMwfvGi7vtP1Fg4NLrhGvLF2PcIgZPOcwk4Ssm4iw5iSj0K1npOX3pd5BWzyszqchfYYRHY99GllAump0bZmprVAld9rf70B2HZIVvowBPuUXfc9Cz/5q0z+s8bQ5vCdElW1Bh7h8W/POePdc8cFGAyBS4i1ZVNheIDOHdDjI=,iv:Bi6rekXOx3/dwwPRryF3CoAoQi3D06ABysRF1oBeG5A=,tag:0TCra+AkhBDczj4uvAzKMw==,type:str] pgp: - created_at: "2023-12-29T15:25:27Z" enc: | diff --git a/modules/default.nix b/modules/default.nix index de1c7c2..9341a5a 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,23 +1,5 @@ { - pkgs, - config, - lib, - ... -}: { - options.services.nginx.domain = lib.mkOption { - type = lib.types.str; - description = "The base domain name to append to virtual domain names"; - }; - - config = { - # Don't attempt to run acme if the domain name is not tlater.net - systemd.services = let - confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]''; - in - lib.mapAttrs' (cert: _: - lib.nameValuePair "acme-${cert}" { - serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' ''; - }) - config.security.acme.certs; - }; + imports = [ + ./nginxExtensions.nix + ]; } diff --git a/modules/nginxExtensions.nix b/modules/nginxExtensions.nix new file mode 100644 index 0000000..edc1ddc --- /dev/null +++ b/modules/nginxExtensions.nix @@ -0,0 +1,132 @@ +{ + config, + pkgs, + lib, + ... +}: { + options = { + services.nginx.domain = lib.mkOption { + type = lib.types.str; + description = "The base domain name to append to virtual domain names"; + }; + + services.nginx.virtualHosts = let + extraLocationOptions = {config, ...}: { + options = { + enableAuthorization = lib.mkEnableOption "Enable authorization via authelia"; + }; + + config = { + recommendedProxySettings = lib.mkIf config.enableAuthorization false; + + extraConfig = lib.concatStringsSep "\n" [ + (lib.optionalString config.enableAuthorization '' + proxy_set_header Host $host; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-URI $request_uri; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + + proxy_redirect http:// $scheme://; + proxy_cache_bypass $cookie_session; + proxy_no_cache $cookie_session; + + real_ip_header X-Forwarded-For; + real_ip_recursive on; + '') + (lib.optionalString config.enableAuthorization '' + auth_request /internal/authelia/authz; + + auth_request_set $user $upstream_http_remote_user; + auth_request_set $groups $upstream_http_remote_groups; + auth_request_set $name $upstream_http_remote_name; + auth_request_set $email $upstream_http_remote_email; + + proxy_set_header Remote-User $user; + proxy_set_header Remote-Groups $groups; + proxy_set_header Remote-Email $email; + proxy_set_header Remote-Name $name; + + auth_request_set $redirection_url $upstream_http_location; + error_page 401 =302 $redirection_url; + '') + ]; + }; + }; + + extraVirtualHostOptions = {name, config, ...}: { + options = { + enableAuthorization = lib.mkEnableOption "Enable authorization via authelia"; + enableHSTS = lib.mkEnableOption "Enable HSTS"; + + addAccessLog = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Add special logging to `/var/log/nginx/''${serverName}` + ''; + }; + + locations = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule extraLocationOptions); + }; + }; + + config = { + extraConfig = lib.concatStringsSep "\n" [ + (lib.optionalString config.enableHSTS '' + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + '') + (lib.optionalString config.addAccessLog '' + access_log /var/log/nginx/${name}/access.log upstream_time; + '') + ]; + + locations = lib.mkIf config.enableAuthorization { + "/".enableAuthorization = true; + "/internal/authelia/authz" = { + proxyPass = "http://127.0.0.1:9091/api/authz/auth-request"; + recommendedProxySettings = false; + extraConfig = '' + internal; + + proxy_set_header X-Original-Method $request_method; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Content-Length ""; + proxy_set_header Connection ""; + + proxy_pass_request_body off; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; + proxy_redirect http:// $scheme://; + proxy_http_version 1.1; + proxy_cache_bypass $cookie_session; + proxy_no_cache $cookie_session; + proxy_buffers 4 32k; + client_body_buffer_size 128k; + ''; + }; + }; + }; + }; + in + lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions); + }; + }; + + config = { + # Don't attempt to run acme if the domain name is not tlater.net + systemd.services = let + confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]''; + in + lib.mapAttrs' (cert: _: + lib.nameValuePair "acme-${cert}" { + serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' ''; + }) + config.security.acme.certs; + }; +}