diff --git a/modules/services/authentik.nix b/modules/services/authentik.nix index 158122d..5a3cd8a 100644 --- a/modules/services/authentik.nix +++ b/modules/services/authentik.nix @@ -6,6 +6,10 @@ }: let cfg = config.my.services.authentik; + + # https://github.com/goauthentik/authentik/issues/5440#issuecomment-1682856454 + # Needed for Tailscale + rfc-7033 = pkgs.callPackage ../../packages/authentik-rfc7033/default.nix { }; in { @@ -101,14 +105,48 @@ in }; }; - networking.firewall.allowedTCPPorts = [ 9000 ]; + # Add your package to system packages + environment.systemPackages = [ rfc-7033 ]; + + # Systemd service configuration + systemd.services.authentik-rfc-7033 = { + description = "Pyhton webserver to implement RFC 7033"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.python3}/bin/python ${rfc-7033}/bin/rfc-7033.py"; + Restart = "on-failure"; + User = "nobody"; # Or specify a user + Group = "nogroup"; + # Optional: Log output to journalctl + StandardOutput = "journal"; + StandardError = "journal"; + }; + }; + + # Port 8000 is for RFC 7033 + networking.firewall.allowedTCPPorts = [ + 8000 + 9000 + ]; }) (lib.mkIf cfg.proxy.enable { services.caddy = with cfg.proxy; { virtualHosts."${subdomain}.${domain}".extraConfig = '' - reverse_proxy http://${host}:9000 + # Other reverse proxies go here + handle { + reverse_proxy http://${host}:9000 + } + + import cloudflare_${domain} + ''; + virtualHosts."${domain}".extraConfig = '' + handle /.well-known/webfinger { + # This should point to the port that the Python script is running on, default is 8000 + reverse_proxy http://${host}:8000 + } + import cloudflare_${domain} ''; }; diff --git a/packages/authentik-rfc7033/default.nix b/packages/authentik-rfc7033/default.nix new file mode 100644 index 0000000..d59ce6b --- /dev/null +++ b/packages/authentik-rfc7033/default.nix @@ -0,0 +1,15 @@ +{ + pkgs ? import { }, +}: + +pkgs.stdenv.mkDerivation rec { + name = "authentik-rfc7033"; + src = ./src; + + buildInputs = [ pkgs.python3 ]; + + installPhase = '' + install -Dm755 rfc-7033.py $out/bin/rfc-7033.py + patchShebangs $out/bin/rfc-7033.py + ''; +} diff --git a/packages/authentik-rfc7033/src/rfc-7033.py b/packages/authentik-rfc7033/src/rfc-7033.py new file mode 100644 index 0000000..8ca5e73 --- /dev/null +++ b/packages/authentik-rfc7033/src/rfc-7033.py @@ -0,0 +1,61 @@ + +#!/usr/bin/env python3 +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import urlparse, parse_qs +import json + +class WebFingerHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path.startswith('/.well-known/webfinger'): + parsed_url = urlparse(self.path) + query_params = parse_qs(parsed_url.query) + + if 'resource' in query_params: + resource = query_params['resource'][0] + + if resource.startswith('acct:'): + email = resource[5:] + issuer_url = "https://auth.pasetto.me/application/o/tailscale/" + response_data = { + "subject": resource, + "links": [ + { + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": issuer_url + }, + { + "rel": "authorization_endpoint", + "href": issuer_url + "oauth2/authorize" + }, + { + "rel": "token_endpoint", + "href": issuer_url + "oauth2/token" + }, + { + "rel": "userinfo_endpoint", + "href": issuer_url + "userinfo" + }, + { + "rel": "jwks_uri", + "href": issuer_url + "jwks" + } + ] + } + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(json.dumps(response_data).encode()) + return + + self.send_response(404) + self.end_headers() + self.wfile.write(b"Resource not found") + +def run_server(server_class=HTTPServer, handler_class=WebFingerHandler, port=8000): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f"Starting WebFinger server on port {port}") + httpd.serve_forever() + +if __name__ == '__main__': + run_server()