From 465d889bc1f87451b2f8e29936fcb3e20435e8b6 Mon Sep 17 00:00:00 2001 From: David Wronek Date: Fri, 16 Jan 2026 12:23:36 +0100 Subject: [PATCH] nixos/drasl: init module Signed-off-by: David Wronek --- .../manual/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/drasl.nix | 167 ++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 nixos/modules/services/web-apps/drasl.nix diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 534bc7a9a09a..44a799a4fb1a 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -40,6 +40,8 @@ - [Shoko](https://shokoanime.com), an anime management system. Available as [services.shoko](#opt-services.shoko.enable). +- [Drasl](https://github.com/unmojang/drasl), an alternative authentication server for Minecraft. Available as [services.drasl](#opt-services.drasl.enable). + ## Backward Incompatibilities {#sec-release-26.05-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index dc8c2560c94a..6d9f0e25064e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1604,6 +1604,7 @@ ./services/web-apps/docuseal.nix ./services/web-apps/dokuwiki.nix ./services/web-apps/dolibarr.nix + ./services/web-apps/drasl.nix ./services/web-apps/drupal.nix ./services/web-apps/echoip.nix ./services/web-apps/eintopf.nix diff --git a/nixos/modules/services/web-apps/drasl.nix b/nixos/modules/services/web-apps/drasl.nix new file mode 100644 index 000000000000..1c84da7f0a43 --- /dev/null +++ b/nixos/modules/services/web-apps/drasl.nix @@ -0,0 +1,167 @@ +{ + lib, + pkgs, + config, + ... +}: +let + cfg = config.services.drasl; + format = pkgs.formats.toml { }; + filterAttrs = x: lib.filterAttrs (n: v: n != "ClientSecretFile" || v != null) x; + getIndex = + item: + builtins.toString (lib.lists.findFirstIndex (x: x == item) null cfg.settings.RegistrationOIDC); + secretFiles = lib.filter ( + x: lib.hasAttr "ClientSecretFile" (filterAttrs x) + ) cfg.settings.RegistrationOIDC; + settings = format.generate "drasl-config.toml" ( + if cfg.settings.RegistrationOIDC == [ ] then + lib.filterAttrs (n: v: n != "RegistrationOIDC" || v != [ ]) cfg.settings + else + lib.recursiveUpdate cfg.settings { + RegistrationOIDC = map ( + x: + if lib.hasAttr "ClientSecretFile" (filterAttrs x) then + lib.recursiveUpdate (filterAttrs x) { + ClientSecretFile = "$CREDENTIALS_DIRECTORY/${getIndex x}"; + } + else + filterAttrs x + ) cfg.settings.RegistrationOIDC; + } + ); +in +{ + options.services.drasl = { + enable = lib.mkEnableOption "Drasl"; + package = lib.mkPackageOption pkgs "drasl" { }; + enableDebug = lib.mkEnableOption "debugging"; + settings = lib.mkOption { + description = '' + Configuration for Drasl. See the + [Drasl documentation](https://github.com/unmojang/drasl/blob/master/doc/configuration.md) + for possible options. + ''; + type = lib.types.submodule { + freeformType = format.type; + options = { + RegistrationOIDC = lib.mkOption { + default = [ ]; + description = "List of OpenID connect providers."; + type = lib.types.listOf ( + lib.types.submodule { + freeformType = format.type; + options = { + ClientSecretFile = lib.mkOption { + default = null; + description = '' + Path to a file containing the OIDC client secret. + + ::: {.note} + The NixOS module will automatically load this file using + systemd's LoadCredential. Make sure this file is only + readable by the root user. + ::: + ''; + type = lib.types.nullOr lib.types.path; + }; + }; + } + ); + }; + }; + }; + }; + }; + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = lib.allUnique cfg.settings.RegistrationOIDC; + message = "All items in `services.drasl.settings.RegistrationOIDC` must be unique."; + } + { + assertion = lib.all ( + x: (lib.hasAttr "ClientSecretFile" (filterAttrs x)) -> !(lib.hasAttr "ClientSecret" (filterAttrs x)) + ) cfg.settings.RegistrationOIDC; + message = + "Do not set both `services.drasl.settings.RegistrationOIDC.*.ClientSecret` " + + "and `services.drasl.settings.RegistrationOIDC.*.ClientSecretFile`"; + } + ]; + systemd.services.drasl = { + description = "Drasl"; + after = [ + "network-online.target" + "nss-lookup.target" + ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = lib.mkIf cfg.enableDebug { DRASL_DEBUG = "1"; }; + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} -config ${settings}"; + DynamicUser = true; + RuntimeDirectory = "drasl"; + RuntimeDirectoryMode = "0700"; + StateDirectory = "drasl"; + LoadCredential = lib.mkIf (secretFiles != [ ]) ( + map (x: "${getIndex x}:${x.ClientSecretFile}") secretFiles + ); + # Hardening + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RemoveIPC = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + PrivateTmp = "disconnected"; + ProcSubset = "pid"; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = [ + "~cgroup" + "~ipc" + "~mnt" + "~net" + "~pid" + "~user" + "~uts" + ]; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "~@clock" + "~@cpu-emulation" + "~@debug" + "~@module" + "~@mount" + "~@obsolete" + "~@privileged" + "~@raw-io" + "~@reboot" + "~@resources" + "~@swap" + ]; + UMask = "0077"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ + evan-goode + ungeskriptet + ]; +}