diff --git a/dns/dnsconfig.js b/dns/dnsconfig.js index 5bc5dfb..acb176f 100644 --- a/dns/dnsconfig.js +++ b/dns/dnsconfig.js @@ -226,6 +226,11 @@ D("radicle.network", REG_NONE, DnsProvider(DSP_CLOUDFLARE), NS("bootstrap", "seed.radicle.dev."), + ALIAS("1.eu.dns.iroh", "iris"), + ALIAS("1.us.dns.iroh", "rosa"), + ALIAS("1.eu.relay.iroh", "iris"), + ALIAS("1.us.relay.iroh", "rosa"), + A("blackhole", "192.0.2.1"), ); diff --git a/os/host/network/iris/default.nix b/os/host/network/iris/default.nix index d5eef7e..4bc9a14 100644 --- a/os/host/network/iris/default.nix +++ b/os/host/network/iris/default.nix @@ -17,6 +17,7 @@ ../../../mixin/users.nix ./i2pd.nix + ./iroh.nix ./ssh.nix ./radicle.nix ./tor.nix diff --git a/os/host/network/iris/iroh.nix b/os/host/network/iris/iroh.nix new file mode 100644 index 0000000..25eab19 --- /dev/null +++ b/os/host/network/iris/iroh.nix @@ -0,0 +1,40 @@ +{ + imports = [ + ../../../module/iroh-dns-server.nix + ../../../module/iroh-relay.nix + ]; + + networking.firewall = { + allowedTCPPorts = [ + 53 # dns-server + ]; + allowedUDPPorts = [ + 53 # dns-server + 7824 # relay + ]; + }; + + services.iroh = { + relay = { + enable = true; + domain = "1.eu.relay.iroh.radicle.network"; + nginx.enable = true; + }; + + dns = { + enable = true; + domain = "1.eu.dns.iroh.radicle.network"; + nginx.enable = true; + + settings = { + http.port = 8053; + dns = { + port = 53; + bind_addr = "::"; + default_soa = ". team.radicle.network. 0 10800 3600 604800 3600"; + origins = ["."]; + }; + }; + }; + }; +} diff --git a/os/host/network/rosa/default.nix b/os/host/network/rosa/default.nix index 80b1712..d142c8c 100644 --- a/os/host/network/rosa/default.nix +++ b/os/host/network/rosa/default.nix @@ -17,6 +17,7 @@ ../../../mixin/users.nix ./i2pd.nix + ./iroh.nix ./ssh.nix ./radicle.nix ./tor.nix diff --git a/os/host/network/rosa/iroh.nix b/os/host/network/rosa/iroh.nix new file mode 100644 index 0000000..c5881f2 --- /dev/null +++ b/os/host/network/rosa/iroh.nix @@ -0,0 +1,40 @@ +{ + imports = [ + ../../../module/iroh-dns-server.nix + ../../../module/iroh-relay.nix + ]; + + networking.firewall = { + allowedTCPPorts = [ + 53 # dns-server + ]; + allowedUDPPorts = [ + 53 # dns-server + 7824 # relay + ]; + }; + + services.iroh = { + relay = { + enable = true; + domain = "1.us.relay.iroh.radicle.network"; + nginx.enable = true; + }; + + dns = { + enable = true; + domain = "1.us.dns.iroh.radicle.network"; + nginx.enable = true; + + settings = { + http.port = 8053; + dns = { + port = 53; + bind_addr = "::"; + default_soa = ". team.radicle.network. 0 10800 3600 604800 3600"; + origins = ["."]; + }; + }; + }; + }; +} diff --git a/os/module/iroh-dns-server.nix b/os/module/iroh-dns-server.nix new file mode 100644 index 0000000..cfe3a9b --- /dev/null +++ b/os/module/iroh-dns-server.nix @@ -0,0 +1,148 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.services.iroh.dns; + settingsFormat = pkgs.formats.toml {}; + configFile = settingsFormat.generate "iroh-dns-server.toml" cfg.settings; +in { + options.services.iroh.dns = { + enable = lib.mkEnableOption "iroh DNS Server"; + + package = lib.mkPackageOption pkgs "iroh-dns-server" {}; + + user = lib.mkOption { + type = lib.types.str; + default = "iroh"; + description = "User under which iroh-dns-server runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "iroh"; + description = "Group under which iroh-dns-server runs."; + }; + + domain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "dns.example.org"; + description = "Public hostname used for DNS over HTTPS."; + }; + + settings = lib.mkOption { + type = settingsFormat.type; + default = {}; + description = '' + Configuration written to iroh-dns-server.toml. See the upstream + iroh-dns-server configuration for supported values. + ''; + }; + + nginx = { + enable = lib.mkEnableOption "nginx virtual host for iroh DNS over HTTPS"; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + services.iroh.dns.settings = { + pkarr_put_rate_limit = lib.mkDefault "smart"; + data_dir = lib.mkDefault "/var/lib/iroh-dns-server"; + + http = { + bind_addr = lib.mkDefault "::1"; + port = lib.mkDefault 8080; + }; + + dns = { + bind_addr = lib.mkDefault "::"; + port = lib.mkDefault 53; + default_ttl = lib.mkDefault 30; + origins = lib.mkDefault ["."]; + }; + + mainline.enabled = lib.mkDefault false; + + metrics = { + disabled = lib.mkDefault false; + bind_addr = lib.mkDefault "127.0.0.1:9117"; + }; + }; + + users = { + users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + }; + + groups.${cfg.group} = {}; + }; + + systemd.services.iroh-dns-server = { + description = "iroh DNS Server"; + wantedBy = ["multi-user.target"]; + wants = ["network-online.target"]; + after = ["network-online.target"]; + + serviceConfig = { + Type = "simple"; + ExecStart = "${lib.getExe cfg.package} --config ${configFile}"; + Restart = "on-failure"; + RestartSec = 5; + + User = cfg.user; + Group = cfg.group; + + AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; + CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; + + RuntimeDirectory = "iroh-dns-server"; + StateDirectory = "iroh-dns-server"; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateIPC = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = ["@system-service"]; + + LimitNOFILE = 65536; + }; + }; + } + + (lib.mkIf cfg.nginx.enable { + assertions = [ + { + assertion = cfg.domain != null; + message = "services.iroh.dns.domain must be set when nginx is enabled"; + } + ]; + + services.nginx.virtualHosts.${cfg.domain} = { + enableACME = true; + forceSSL = true; + quic = true; + locations."/".proxyPass = "http://[::1]:${builtins.toString cfg.settings.http.port}"; + }; + }) + ]); +} diff --git a/os/module/iroh-relay.nix b/os/module/iroh-relay.nix new file mode 100644 index 0000000..88cf26f --- /dev/null +++ b/os/module/iroh-relay.nix @@ -0,0 +1,166 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.services.iroh.relay; + settingsFormat = pkgs.formats.toml {}; + configFile = settingsFormat.generate "iroh-relay.toml" cfg.settings; +in { + options.services.iroh.relay = { + enable = lib.mkEnableOption "iroh Relay"; + + package = lib.mkPackageOption pkgs "iroh-relay" {}; + + user = lib.mkOption { + type = lib.types.str; + default = "iroh"; + description = "User under which iroh-relay runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "iroh"; + description = "Group under which iroh-relay runs."; + }; + + domain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "relay.example.org"; + description = "Public hostname of the relay."; + }; + + settings = lib.mkOption { + type = settingsFormat.type; + default = {}; + description = '' + Configuration written to `iroh-relay.toml`. See the upstream + `iroh-relay` configuration for supported values. + ''; + }; + + nginx = { + enable = lib.mkEnableOption "nginx reverse-proxy for iroh-relay"; + + proxyPass = lib.mkOption { + type = lib.types.str; + default = "http://127.0.0.1:3340"; + description = "URL to which nginx proxies relay HTTP and WebSocket traffic."; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + services.iroh.relay.settings = { + http_bind_addr = lib.mkDefault ( + if cfg.nginx.enable + then "127.0.0.1:3340" + else "[::]:3340" + ); + enable_quic_addr_discovery = lib.mkDefault cfg.nginx.enable; + enable_metrics = lib.mkDefault true; + metrics_bind_addr = lib.mkDefault "127.0.0.1:9091"; + }; + + users = { + users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + }; + groups.${cfg.group} = {}; + }; + + systemd.services.iroh-relay = { + description = "iroh Relay"; + wantedBy = ["multi-user.target"]; + wants = ["network-online.target"]; + after = ["network-online.target"]; + + serviceConfig = { + Type = "simple"; + ExecStart = "${lib.getExe cfg.package} --config-path ${configFile}"; + Restart = "on-failure"; + RestartSec = 5; + + User = cfg.user; + Group = cfg.group; + + RuntimeDirectory = "iroh-relay"; + StateDirectory = "iroh-relay"; + + CapabilityBoundingSet = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateIPC = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = ["@system-service"]; + + LimitNOFILE = 65536; + }; + }; + } + + (lib.mkIf cfg.nginx.enable { + assertions = [ + { + assertion = cfg.domain != null; + message = "services.iroh.relay.domain must be set when its nginx frontend is enabled"; + } + ]; + + services.iroh.relay.settings = { + tls = { + cert_mode = lib.mkDefault "Reloading"; + manual_cert_path = lib.mkDefault "/var/lib/acme/${cfg.domain}/fullchain.pem"; + manual_key_path = lib.mkDefault "/var/lib/acme/${cfg.domain}/key.pem"; + dangerous_http_only = lib.mkDefault true; + quic_bind_addr = lib.mkDefault "[::]:7842"; + }; + }; + + users.users.${cfg.user}.extraGroups = [config.services.nginx.group]; + + systemd.services.iroh-relay = { + requires = ["acme-${cfg.domain}.service"]; + after = ["acme-${cfg.domain}.service"]; + }; + + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts.${cfg.domain} = { + enableACME = true; + forceSSL = true; + quic = true; + locations."/" = { + proxyPass = "http://127.0.0.1:3340"; + proxyWebsockets = true; + extraConfig = '' + proxy_buffering off; + proxy_request_buffering off; + ''; + }; + }; + }; + }) + ]); +}