From 368b22d09b1511189f941f8976bfab41dcd651e9 Mon Sep 17 00:00:00 2001 From: Flakebi Date: Fri, 17 Dec 2021 10:33:40 +0100 Subject: [PATCH] powerdns-admin: fix and add module - Add the migrations directory to the package - Add postgres support to the package - Add a service for powerdns-admin Co-authored-by: Zhaofeng Li --- .../from_md/release-notes/rl-2205.section.xml | 7 + .../manual/release-notes/rl-2205.section.md | 2 + nixos/modules/module-list.nix | 1 + .../services/web-apps/powerdns-admin.nix | 149 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/powerdns-admin.nix | 117 ++++++++++++++ .../networking/powerdns-admin/default.nix | 15 +- 7 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 nixos/modules/services/web-apps/powerdns-admin.nix create mode 100644 nixos/tests/powerdns-admin.nix diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 8897febc2023..f0da210f6580 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -36,6 +36,13 @@ services.filebeat. + + + PowerDNS-Admin, + a web interface for the PowerDNS server. Available at + services.powerdns-admin. + +
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 211c64c02899..2c008a67dd72 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -12,6 +12,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html), a lightweight shipper for forwarding and centralizing log data. Available as [services.filebeat](#opt-services.filebeat.enable). +- [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](options.html#opt-services.powerdns-admin.enable). + ## Backward Incompatibilities {#sec-release-22.05-incompatibilities} - `pkgs.ghc` now refers to `pkgs.targetPackages.haskellPackages.ghc`. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index cb2dd530de15..30ad0db459ef 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1026,6 +1026,7 @@ ./services/web-apps/plantuml-server.nix ./services/web-apps/plausible.nix ./services/web-apps/pgpkeyserver-lite.nix + ./services/web-apps/powerdns-admin.nix ./services/web-apps/matomo.nix ./services/web-apps/moinmoin.nix ./services/web-apps/openwebrx.nix diff --git a/nixos/modules/services/web-apps/powerdns-admin.nix b/nixos/modules/services/web-apps/powerdns-admin.nix new file mode 100644 index 000000000000..ce99b606c318 --- /dev/null +++ b/nixos/modules/services/web-apps/powerdns-admin.nix @@ -0,0 +1,149 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.powerdns-admin; + + configText = '' + ${cfg.config} + '' + + optionalString (cfg.secretKeyFile != null) '' + with open('${cfg.secretKeyFile}') as file: + SECRET_KEY = file.read() + '' + + optionalString (cfg.saltFile != null) '' + with open('${cfg.saltFile}') as file: + SALT = file.read() + ''; +in +{ + options.services.powerdns-admin = { + enable = mkEnableOption "the PowerDNS web interface"; + + extraArgs = mkOption { + type = types.listOf types.str; + default = [ ]; + example = literalExpression '' + [ "-b" "127.0.0.1:8000" ] + ''; + description = '' + Extra arguments passed to powerdns-admin. + ''; + }; + + config = mkOption { + type = types.str; + default = ""; + example = '' + BIND_ADDRESS = '127.0.0.1' + PORT = 8000 + SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql' + ''; + description = '' + Configuration python file. + See the example configuration + for options. + ''; + }; + + secretKeyFile = mkOption { + type = types.nullOr types.path; + example = "/etc/powerdns-admin/secret"; + description = '' + The secret used to create cookies. + This needs to be set, otherwise the default is used and everyone can forge valid login cookies. + Set this to null to ignore this setting and configure it through another way. + ''; + }; + + saltFile = mkOption { + type = types.nullOr types.path; + example = "/etc/powerdns-admin/salt"; + description = '' + The salt used for serialization. + This should be set, otherwise the default is used. + Set this to null to ignore this setting and configure it through another way. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.powerdns-admin = { + description = "PowerDNS web interface"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + + environment.FLASK_CONF = builtins.toFile "powerdns-admin-config.py" configText; + environment.PYTHONPATH = pkgs.powerdns-admin.pythonPath; + serviceConfig = { + ExecStart = "${pkgs.powerdns-admin}/bin/powerdns-admin --pid /run/powerdns-admin/pid ${escapeShellArgs cfg.extraArgs}"; + ExecStartPre = "${pkgs.coreutils}/bin/env FLASK_APP=${pkgs.powerdns-admin}/share/powerdnsadmin/__init__.py ${pkgs.python3Packages.flask}/bin/flask db upgrade -d ${pkgs.powerdns-admin}/share/migrations"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStop = "${pkgs.coreutils}/bin/kill -TERM $MAINPID"; + PIDFile = "/run/powerdns-admin/pid"; + RuntimeDirectory = "powerdns-admin"; + User = "powerdnsadmin"; + Group = "powerdnsadmin"; + + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + BindReadOnlyPaths = [ + "/nix/store" + "-/etc/resolv.conf" + "-/etc/nsswitch.conf" + "-/etc/hosts" + "-/etc/localtime" + ] + ++ (optional (cfg.secretKeyFile != null) cfg.secretKeyFile) + ++ (optional (cfg.saltFile != null) cfg.saltFile); + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; + # ProtectClock= adds DeviceAllow=char-rtc r + DeviceAllow = ""; + # Implies ProtectSystem=strict, which re-mounts all paths + #DynamicUser = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + # Needs to start a server + #PrivateNetwork = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectHome = true; + ProtectHostname = true; + # Would re-mount paths ignored by temporary root + #ProtectSystem = "strict"; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + # gunicorn needs setuid + SystemCallFilter = [ + "@system-service" + "~@privileged @resources @keyring" + # These got removed by the line above but are needed + "@setuid @chown" + ]; + TemporaryFileSystem = "/:ro"; + # Does not work well with the temporary root + #UMask = "0066"; + }; + }; + + users.groups.powerdnsadmin = { }; + users.users.powerdnsadmin = { + description = "PowerDNS web interface user"; + isSystemUser = true; + group = "powerdnsadmin"; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f86cc2544dab..5c8342f0bb7b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -381,6 +381,7 @@ in postgresql = handleTest ./postgresql.nix {}; postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {}; powerdns = handleTest ./powerdns.nix {}; + powerdns-admin = handleTest ./powerdns-admin.nix {}; power-profiles-daemon = handleTest ./power-profiles-daemon.nix {}; pppd = handleTest ./pppd.nix {}; predictable-interface-names = handleTest ./predictable-interface-names.nix {}; diff --git a/nixos/tests/powerdns-admin.nix b/nixos/tests/powerdns-admin.nix new file mode 100644 index 000000000000..4d763c9c6f6e --- /dev/null +++ b/nixos/tests/powerdns-admin.nix @@ -0,0 +1,117 @@ +# Test powerdns-admin +{ system ? builtins.currentSystem +, config ? { } +, pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; +let + defaultConfig = '' + BIND_ADDRESS = '127.0.0.1' + PORT = 8000 + ''; + + makeAppTest = name: configs: makeTest { + name = "powerdns-admin-${name}"; + meta = with pkgs.lib.maintainers; { + maintainers = [ Flakebi zhaofengli ]; + }; + + nodes.server = { pkgs, config, ... }: mkMerge ([ + { + services.powerdns-admin = { + enable = true; + secretKeyFile = "/etc/powerdns-admin/secret"; + saltFile = "/etc/powerdns-admin/salt"; + }; + # It's insecure to have secrets in the world-readable nix store, but this is just a test + environment.etc."powerdns-admin/secret".text = "secret key"; + environment.etc."powerdns-admin/salt".text = "salt"; + environment.systemPackages = [ + (pkgs.writeShellScriptBin "run-test" config.system.build.testScript) + ]; + } + ] ++ configs); + + testScript = '' + server.wait_for_unit("powerdns-admin.service") + server.wait_until_succeeds("run-test", timeout=10) + ''; + }; + + matrix = { + backend = { + mysql = { + services.powerdns-admin = { + config = '' + ${defaultConfig} + SQLALCHEMY_DATABASE_URI = 'mysql://powerdnsadmin@/powerdnsadmin?unix_socket=/run/mysqld/mysqld.sock' + ''; + }; + systemd.services.powerdns-admin = { + after = [ "mysql.service" ]; + serviceConfig.BindPaths = "/run/mysqld"; + }; + + services.mysql = { + enable = true; + package = pkgs.mariadb; + ensureDatabases = [ "powerdnsadmin" ]; + ensureUsers = [ + { + name = "powerdnsadmin"; + ensurePermissions = { + "powerdnsadmin.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + }; + postgresql = { + services.powerdns-admin = { + config = '' + ${defaultConfig} + SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql' + ''; + }; + systemd.services.powerdns-admin = { + after = [ "postgresql.service" ]; + serviceConfig.BindPaths = "/run/postgresql"; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "powerdnsadmin" ]; + ensureUsers = [ + { + name = "powerdnsadmin"; + ensurePermissions = { + "DATABASE powerdnsadmin" = "ALL PRIVILEGES"; + }; + } + ]; + }; + }; + }; + listen = { + tcp = { + services.powerdns-admin.extraArgs = [ "-b" "127.0.0.1:8000" ]; + system.build.testScript = '' + curl -sSf http://127.0.0.1:8000/ + ''; + }; + unix = { + services.powerdns-admin.extraArgs = [ "-b" "unix:/run/powerdns-admin/http.sock" ]; + system.build.testScript = '' + curl -sSf --unix-socket /run/powerdns-admin/http.sock http://somehost/ + ''; + }; + }; + }; +in +with matrix; { + postgresql = makeAppTest "postgresql" [ backend.postgresql listen.tcp ]; + mysql = makeAppTest "mysql" [ backend.mysql listen.tcp ]; + unix-listener = makeAppTest "unix-listener" [ backend.postgresql listen.unix ]; +} diff --git a/pkgs/applications/networking/powerdns-admin/default.nix b/pkgs/applications/networking/powerdns-admin/default.nix index 9dba30e3d884..8cc2ec83cd50 100644 --- a/pkgs/applications/networking/powerdns-admin/default.nix +++ b/pkgs/applications/networking/powerdns-admin/default.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchFromGitHub, mkYarnPackage, writeText, python3 }: +{ lib, stdenv, fetchFromGitHub, mkYarnPackage, nixosTests, writeText, python3 }: let version = "0.2.3"; @@ -23,8 +23,8 @@ let pythonDeps = with python.pkgs; [ flask flask_assets flask_login flask_sqlalchemy flask_migrate flask-seasurf flask_mail flask-sslify - mysqlclient sqlalchemy - configobj bcrypt requests ldap pyotp qrcode dnspython + mysqlclient psycopg2 sqlalchemy + cffi configobj cryptography bcrypt requests ldap pyotp qrcode dnspython gunicorn python3-saml pyopenssl pytz cssmin jsmin authlib bravado-core lima pytimeparse pyyaml ]; @@ -91,6 +91,7 @@ in stdenv.mkDerivation rec { postPatch = '' rm -r powerdnsadmin/static powerdnsadmin/assets.py + sed -i "s/id:/'id':/" migrations/versions/787bdba9e147_init_db.py ''; installPhase = '' @@ -100,7 +101,7 @@ in stdenv.mkDerivation rec { wrapPythonPrograms mkdir -p $out/share $out/bin - cp -r powerdnsadmin $out/share/powerdnsadmin + cp -r migrations powerdnsadmin $out/share/ ln -s ${assets} $out/share/powerdnsadmin/static ln -s ${assetsPy} $out/share/powerdnsadmin/assets.py @@ -114,6 +115,12 @@ in stdenv.mkDerivation rec { runHook postInstall ''; + passthru = { + # PYTHONPATH of all dependencies used by the package + pythonPath = python3.pkgs.makePythonPath pythonDeps; + tests = nixosTests.powerdns-admin; + }; + meta = with lib; { description = "A PowerDNS web interface with advanced features"; homepage = "https://github.com/ngoduykhanh/PowerDNS-Admin";