diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..2232471e1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1742800061, + "narHash": "sha256-oDJGK1UMArK52vcW9S5S2apeec4rbfNELgc50LqiPNs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1750f3c1c89488e2ffdd47cab9d05454dddfb734", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..1c2a8652c --- /dev/null +++ b/flake.nix @@ -0,0 +1,48 @@ +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = { self, nixpkgs }: let + inherit (nixpkgs) lib; + + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + forAllSystems = f: lib.genAttrs systems (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.${system}.default + ]; + }; + in f system pkgs); + in { + devShells = forAllSystems (system: pkgs: { + default = pkgs.callPackage ./nix/shell.nix { }; + }); + + packages = forAllSystems (system: pkgs: with pkgs; { + default = self.packages.${system}.heimdal; + + heimdal = pkgs.callPackage ./nix/heimdal { + src = lib.cleanSource ./.; + inherit (pkgs.darwin.apple_sdk.frameworks) CoreFoundation Security SystemConfiguration; + autoreconfHook = pkgs.buildPackages.autoreconfHook269; + }; + + nixosTest = pkgs.testers.runNixOSTest (import ./nix/nixosTest.nix { inherit nixpkgs; }); + }); + + overlays = forAllSystems (system: pkgs: { + default = final: prev: { + heimdal = self.packages.${system}.heimdal; + }; + }); + + nixosModules = { + default = self.nixosModules.heimdal; + heimdal = ./nix/module; + }; + }; +} diff --git a/nix/heimdal/0001-Define-HAVE_DB_185_H.patch b/nix/heimdal/0001-Define-HAVE_DB_185_H.patch new file mode 100644 index 000000000..f6e2c428c --- /dev/null +++ b/nix/heimdal/0001-Define-HAVE_DB_185_H.patch @@ -0,0 +1,26 @@ +From 08d719e96214f648ae95043acc308deca36e1f7a Mon Sep 17 00:00:00 2001 +From: Ihar Hrachyshka +Date: Tue, 15 Oct 2024 13:52:39 -0400 +Subject: [PATCH] Define HAVE_DB_185_H + +--- + cf/db.m4 | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/cf/db.m4 b/cf/db.m4 +index c0b4510b6..c95a9dee9 100644 +--- a/cf/db.m4 ++++ b/cf/db.m4 +@@ -57,6 +57,9 @@ AS_IF([test "x$with_berkeley_db" != xno], + db.h \ + ])]) + ++dnl detect if compat db_185.h is present ++AC_CHECK_HEADERS([db_185.h]) ++ + dnl db_create is used by db3 and db4 and db5 and db6 + + AC_FIND_FUNC_NO_LIBS(db_create, [$dbheader] db-6 db-5 db4 db3 db, [ +-- +2.46.0 + diff --git a/nix/heimdal/0001-Include-db.h-for-nbdb-compat-mode.patch b/nix/heimdal/0001-Include-db.h-for-nbdb-compat-mode.patch new file mode 100644 index 000000000..e93933278 --- /dev/null +++ b/nix/heimdal/0001-Include-db.h-for-nbdb-compat-mode.patch @@ -0,0 +1,25 @@ +From 749d9451293f9d9f8a3f506401cae369003aeebf Mon Sep 17 00:00:00 2001 +From: Ihar Hrachyshka +Date: Sun, 13 Oct 2024 17:16:13 -0400 +Subject: [PATCH] Include db.h for nbdb compat mode + +--- + lib/otp/otp_db.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/otp/otp_db.c b/lib/otp/otp_db.c +index 036359c1d..32c04bc8c 100644 +--- a/lib/otp/otp_db.c ++++ b/lib/otp/otp_db.c +@@ -39,7 +39,7 @@ RCSID("$Id$"); + #include "otp_locl.h" + + #if defined(HAVE_DB_NDBM) +-# include ++# include + #elif !defined(HAVE_NDBM) + # include "ndbm_wrap.h" + #endif +-- +2.46.0 + diff --git a/nix/heimdal/0001-Link-tests-with-libresolv.patch b/nix/heimdal/0001-Link-tests-with-libresolv.patch new file mode 100644 index 000000000..f20128df9 --- /dev/null +++ b/nix/heimdal/0001-Link-tests-with-libresolv.patch @@ -0,0 +1,51 @@ +From 862900febaec4a2c70257a39374b81138ee9f168 Mon Sep 17 00:00:00 2001 +From: Ihar Hrachyshka +Date: Tue, 15 Oct 2024 16:06:33 -0400 +Subject: [PATCH] Link tests with libresolv + +--- + lib/gssapi/Makefile.am | 1 + + lib/krb5/Makefile.am | 2 ++ + lib/roken/Makefile.am | 1 + + 3 files changed, 4 insertions(+) + +diff --git a/lib/gssapi/Makefile.am b/lib/gssapi/Makefile.am +index 3254866dc..db967e586 100644 +--- a/lib/gssapi/Makefile.am ++++ b/lib/gssapi/Makefile.am +@@ -403,6 +403,7 @@ LDADD = libgssapi.la \ + $(top_builddir)/lib/krb5/libkrb5.la \ + $(LIB_roken) + ++test_names_LDFLAGS = -lresolv + test_names_LDADD = $(LDADD) $(top_builddir)/lib/asn1/libasn1.la + test_context_LDADD = $(LDADD) $(top_builddir)/lib/asn1/libasn1.la $(top_builddir)/lib/wind/libwind.la + +diff --git a/lib/krb5/Makefile.am b/lib/krb5/Makefile.am +index ecce461dd..e22cfe87c 100644 +--- a/lib/krb5/Makefile.am ++++ b/lib/krb5/Makefile.am +@@ -330,6 +330,8 @@ test_rfc3961_LDADD = \ + $(LIB_hcrypto) \ + $(LIB_roken) + ++test_plugin_LDFLAGS = -lresolv ++ + if DEVELOPER_MODE + headerdeps = $(dist_libkrb5_la_SOURCES) + endif +diff --git a/lib/roken/Makefile.am b/lib/roken/Makefile.am +index 1f530c7ae..8350d7034 100644 +--- a/lib/roken/Makefile.am ++++ b/lib/roken/Makefile.am +@@ -54,6 +54,7 @@ libtest_la_CFLAGS = -DTEST_SNPRINTF -DTEST_STRPFTIME + + parse_reply_test_SOURCES = parse_reply-test.c resolve.c + parse_reply_test_CFLAGS = -DTEST_RESOLVE ++parse_reply_test_LDFLAGS = -lresolv + + test_readenv_SOURCES = test-readenv.c test-mem.c + test_auxval_SOURCES = test-auxval.c +-- +2.46.0 + diff --git a/nix/heimdal/default.nix b/nix/heimdal/default.nix new file mode 100644 index 000000000..78e3c2618 --- /dev/null +++ b/nix/heimdal/default.nix @@ -0,0 +1,204 @@ +{ + src, + + lib, + stdenv, + fetchFromGitHub, + autoreconfHook, + pkg-config, + python3, + perl, + bison, + flex, + texinfo, + perlPackages, + + openldap, + libcap_ng, + sqlite, + openssl, + db, + libedit, + pam, + libmicrohttpd, + cjson, + + CoreFoundation, + Security, + SystemConfiguration, + + curl, + jdk_headless, + unzip, + which, + + nixosTests, + + withCJSON ? true, + withCapNG ? stdenv.hostPlatform.isLinux, + # libmicrohttpd should theoretically work for darwin as well, but something is broken. + # It affects tests check-bx509d and check-httpkadmind. + withMicroHTTPD ? stdenv.hostPlatform.isLinux, + withOpenLDAP ? true, + withOpenLDAPAsHDBModule ? false, + withOpenSSL ? true, + withSQLite3 ? true, +}: + +assert lib.assertMsg (withOpenLDAPAsHDBModule -> withOpenLDAP) '' + OpenLDAP needs to be enabled in order to build the OpenLDAP HDB Module. +''; + +stdenv.mkDerivation { + pname = "heimdal"; + version = "7.8.0-unstable-local"; + + inherit src; + + outputs = [ + "out" + "dev" + "man" + "info" + ]; + + nativeBuildInputs = [ + autoreconfHook + pkg-config + python3 + perl + bison + flex + perlPackages.JSON + texinfo + ]; + + buildInputs = + [ + db + libedit + pam + ] + ++ lib.optionals (stdenv.hostPlatform.isDarwin) [ + CoreFoundation + Security + SystemConfiguration + ] + ++ lib.optionals (withCJSON) [ cjson ] + ++ lib.optionals (withCapNG) [ libcap_ng ] + ++ lib.optionals (withMicroHTTPD) [ libmicrohttpd ] + ++ lib.optionals (withOpenLDAP) [ openldap ] + ++ lib.optionals (withOpenSSL) [ openssl ] + ++ lib.optionals (withSQLite3) [ sqlite ]; + + doCheck = true; + nativeCheckInputs = [ + curl + jdk_headless + unzip + which + ]; + + configureFlags = + [ + "--with-hdbdir=/var/lib/heimdal" + + "--with-libedit-include=${libedit.dev}/include" + "--with-libedit-lib=${libedit}/lib" + "--with-berkeley-db-include=${db.dev}/include" + "--with-berkeley-db" + + "--without-x" + "--disable-afs-string-to-key" + ] + ++ lib.optionals (withCapNG) [ + "--with-capng" + ] + ++ lib.optionals (withCJSON) [ + "--with-cjson=${cjson}" + ] + ++ lib.optionals (withOpenLDAP) [ + "--with-openldap=${openldap.dev}" + ] + ++ lib.optionals (withOpenLDAPAsHDBModule) [ + "--enable-hdb-openldap-module" + ] + ++ lib.optionals (withSQLite3) [ + "--with-sqlite3=${sqlite.dev}" + ]; + + patches = [ + # Proposed @ https://github.com/heimdal/heimdal/pull/1262 + ./0001-Include-db.h-for-nbdb-compat-mode.patch + # Proposed @ https://github.com/heimdal/heimdal/pull/1264 + ./0001-Define-HAVE_DB_185_H.patch + # Proposed @ https://github.com/heimdal/heimdal/pull/1265 + ./0001-Link-tests-with-libresolv.patch + ]; + + # (check-ldap) slapd resides within ${openldap}/libexec, + # which is not part of $PATH by default. + # (check-ldap) prepending ${openldap}/bin to the path to avoid + # using the default installation of openldap on unsandboxed darwin systems, + # which does not support the new mdb backend at the moment (2024-01-13). + # (check-ldap) the bdb backend got deprecated in favour of mdb in openldap 2.5.0, + # but the heimdal tests still seem to expect bdb as the openldap backend. + # This might be fixed upstream in a future update. + postPatch = '' + substituteInPlace tests/ldap/slapd-init.in \ + --replace-fail 'SCHEMA_PATHS="' 'SCHEMA_PATHS="${openldap}/etc/schema ' + substituteInPlace tests/ldap/check-ldap.in \ + --replace-fail 'PATH=' 'PATH=${openldap}/libexec:${openldap}/bin:' + substituteInPlace tests/ldap/slapd.conf \ + --replace-fail 'database bdb' 'database mdb' + substituteInPlace tests/kdc/check-iprop.in \ + --replace-fail '/bin/pwd' 'pwd' + ''; + + # (test_cc) heimdal uses librokens implementation of `secure_getenv` on darwin, + # which expects either USER or LOGNAME to be set. + preCheck = lib.optionalString (stdenv.hostPlatform.isDarwin) '' + export USER=nix-builder + ''; + + # We need to build hcrypt for applications like samba + postBuild = '' + (cd include/hcrypto; make -j $NIX_BUILD_CORES) + (cd lib/hcrypto; make -j $NIX_BUILD_CORES) + ''; + + postInstall = '' + # Install hcrypto + (cd include/hcrypto; make -j $NIX_BUILD_CORES install) + (cd lib/hcrypto; make -j $NIX_BUILD_CORES install) + + mkdir -p $dev/bin + mv $out/bin/krb5-config $dev/bin/ + + # asn1 compilers, move them to $dev + mv $out/libexec/heimdal/* $dev/bin + rmdir $out/libexec/heimdal + + # compile_et is needed for cross-compiling this package and samba + mv lib/com_err/.libs/compile_et $dev/bin + ''; + + # Issues with hydra + # In file included from hxtool.c:34:0: + # hx_locl.h:67:25: fatal error: pkcs10_asn1.h: No such file or directory + #enableParallelBuilding = true; + + passthru = { + implementation = "heimdal"; + tests.nixos = nixosTests.kerberos.heimdal; + }; + + meta = with lib; { + homepage = "https://www.heimdal.software"; + changelog = "https://github.com/heimdal/heimdal/releases"; + description = "Implementation of Kerberos 5 (and some more stuff)"; + license = licenses.bsd3; + platforms = platforms.unix; + maintainers = with maintainers; [ h7x4 ]; + }; +} diff --git a/nix/module/default.nix b/nix/module/default.nix new file mode 100644 index 000000000..f132a1530 --- /dev/null +++ b/nix/module/default.nix @@ -0,0 +1,69 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (lib) mkOption types; + cfg = config.services.kerberos_server; + inherit (config.security.krb5) package; + + format = import ./krb5-conf-format.nix { inherit pkgs lib; } { + enableKdcACLEntries = true; + }; +in + +{ + imports = [ + (lib.mkRenamedOptionModule + [ "services" "kerberos_server" "realms" ] + [ "services" "kerberos_server" "settings" "realms" ] + ) + + # ./mit.nix + ./heimdal.nix + ]; + + options = { + services.kerberos_server = { + enable = lib.mkEnableOption "the kerberos authentication server"; + + settings = mkOption { + type = format.type; + description = '' + Settings for the kerberos server of choice. + + See the following documentation: + - Heimdal: {manpage}`kdc.conf(5)` + - MIT Kerberos: + ''; + default = { }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ package ]; + assertions = [ + { + assertion = cfg.settings.realms != { }; + message = "The server needs at least one realm"; + } + { + assertion = lib.length (lib.attrNames cfg.settings.realms) <= 1; + message = "Only one realm per server is currently supported."; + } + ]; + + systemd.slices.system-kerberos-server = { }; + systemd.targets.kerberos-server = { + wantedBy = [ "multi-user.target" ]; + }; + }; + + # meta = { + # doc = ./kerberos-server.md; + # }; +} diff --git a/nix/module/heimdal.nix b/nix/module/heimdal.nix new file mode 100644 index 000000000..0bc9a96d1 --- /dev/null +++ b/nix/module/heimdal.nix @@ -0,0 +1,105 @@ +{ + pkgs, + config, + lib, + ... +}: + +let + inherit (lib) mapAttrs; + cfg = config.services.kerberos_server; + package = config.security.krb5.package; + + aclConfigs = lib.pipe cfg.settings.realms [ + (mapAttrs ( + name: + { acl, ... }: + lib.concatMapStringsSep "\n" ( + { + principal, + access, + target, + ... + }: + "${principal}\t${lib.concatStringsSep "," (lib.toList access)}\t${target}" + ) acl + )) + (lib.mapAttrsToList ( + name: text: { + dbname = "/var/lib/heimdal/heimdal"; + acl_file = pkgs.writeText "${name}.acl" text; + } + )) + ]; + + finalConfig = cfg.settings // { + realms = mapAttrs (_: v: removeAttrs v [ "acl" ]) (cfg.settings.realms or { }); + kdc = (cfg.settings.kdc or { }) // { + database = aclConfigs; + }; + }; + + format = import ./krb5-conf-format.nix { inherit pkgs lib; } { + enableKdcACLEntries = true; + }; + + kdcConfFile = format.generate "kdc.conf" finalConfig; +in + +{ + config = lib.mkIf (cfg.enable && package.passthru.implementation == "heimdal") { + environment.etc."heimdal-kdc/kdc.conf".source = kdcConfFile; + + systemd.tmpfiles.settings."10-heimdal" = + let + databases = lib.pipe finalConfig.kdc.database [ + (map (dbAttrs: dbAttrs.dbname or null)) + (lib.filter (x: x != null)) + lib.unique + ]; + in + lib.genAttrs databases (_: { + d = { + user = "root"; + group = "root"; + mode = "0700"; + }; + }); + + systemd.services.kadmind = { + description = "Kerberos Administration Daemon"; + partOf = [ "kerberos-server.target" ]; + wantedBy = [ "kerberos-server.target" ]; + serviceConfig = { + ExecStart = "${package}/libexec/kadmind --config-file=/etc/heimdal-kdc/kdc.conf"; + Slice = "system-kerberos-server.slice"; + StateDirectory = "heimdal"; + }; + restartTriggers = [ kdcConfFile ]; + }; + + systemd.services.kdc = { + description = "Key Distribution Center daemon"; + partOf = [ "kerberos-server.target" ]; + wantedBy = [ "kerberos-server.target" ]; + serviceConfig = { + ExecStart = "${package}/libexec/kdc --config-file=/etc/heimdal-kdc/kdc.conf"; + Slice = "system-kerberos-server.slice"; + StateDirectory = "heimdal"; + }; + restartTriggers = [ kdcConfFile ]; + }; + + systemd.services.kpasswdd = { + description = "Kerberos Password Changing daemon"; + partOf = [ "kerberos-server.target" ]; + wantedBy = [ "kerberos-server.target" ]; + serviceConfig = { + ExecStart = "${package}/libexec/kpasswdd"; + Slice = "system-kerberos-server.slice"; + StateDirectory = "heimdal"; + }; + restartTriggers = [ kdcConfFile ]; + }; + }; +} diff --git a/nix/module/krb5-conf-format.nix b/nix/module/krb5-conf-format.nix new file mode 100644 index 000000000..274e10242 --- /dev/null +++ b/nix/module/krb5-conf-format.nix @@ -0,0 +1,204 @@ +{ pkgs, lib, ... }: + +# Based on +# - https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html +# - https://manpages.debian.org/unstable/heimdal-docs/krb5.conf.5heimdal.en.html + +let + inherit (lib) + boolToString + concatMapStringsSep + concatStringsSep + filter + isAttrs + isBool + isList + mapAttrsToList + mkOption + singleton + splitString + ; + inherit (lib.types) + attrsOf + bool + coercedTo + either + enum + int + listOf + oneOf + path + str + submodule + ; +in +{ + enableKdcACLEntries ? false, +}: +rec { + sectionType = + let + relation = oneOf [ + (listOf (attrsOf value)) + (attrsOf value) + value + ]; + value = either (listOf atom) atom; + atom = oneOf [ + int + str + bool + ]; + in + attrsOf relation; + + type = + let + aclEntry = submodule { + options = { + principal = mkOption { + type = str; + description = "Which principal the rule applies to"; + }; + access = mkOption { + type = either (listOf (enum [ + "add" + "cpw" + "delete" + "get" + "list" + "modify" + ])) (enum [ "all" ]); + default = "all"; + description = "The changes the principal is allowed to make."; + }; + target = mkOption { + type = str; + default = "*"; + description = "The principals that 'access' applies to."; + }; + }; + }; + + realm = submodule ( + { name, ... }: + { + freeformType = sectionType; + options = { + acl = mkOption { + type = listOf aclEntry; + default = [ + { + principal = "*/admin"; + access = "all"; + } + { + principal = "admin"; + access = "all"; + } + ]; + description = '' + The privileges granted to a user. + ''; + }; + }; + } + ); + in + submodule { + freeformType = attrsOf sectionType; + options = + { + include = mkOption { + default = [ ]; + description = '' + Files to include in the Kerberos configuration. + ''; + type = coercedTo path singleton (listOf path); + }; + includedir = mkOption { + default = [ ]; + description = '' + Directories containing files to include in the Kerberos configuration. + ''; + type = coercedTo path singleton (listOf path); + }; + module = mkOption { + default = [ ]; + description = '' + Modules to obtain Kerberos configuration from. + ''; + type = coercedTo path singleton (listOf path); + }; + + } + // (lib.optionalAttrs enableKdcACLEntries { + realms = mkOption { + type = attrsOf realm; + description = '' + The realm(s) to serve keys for. + ''; + }; + }); + }; + + generate = + let + indent = str: concatMapStringsSep "\n" (line: " " + line) (splitString "\n" str); + + formatToplevel = + args@{ + include ? [ ], + includedir ? [ ], + module ? [ ], + ... + }: + let + sections = removeAttrs args [ + "include" + "includedir" + "module" + ]; + in + concatStringsSep "\n" ( + filter (x: x != "") [ + (concatStringsSep "\n" (mapAttrsToList formatSection sections)) + (concatMapStringsSep "\n" (m: "module ${m}") module) + (concatMapStringsSep "\n" (i: "include ${i}") include) + (concatMapStringsSep "\n" (i: "includedir ${i}") includedir) + ] + ); + + formatSection = name: section: '' + [${name}] + ${indent (concatStringsSep "\n" (mapAttrsToList formatRelation section))} + ''; + + formatRelation = + name: relation: + if isAttrs relation then + '' + ${name} = { + ${indent (concatStringsSep "\n" (mapAttrsToList formatValue relation))} + }'' + else if isList relation then + concatMapStringsSep "\n" (formatRelation name) relation + else + formatValue name relation; + + formatValue = + name: value: + if isList value then concatMapStringsSep "\n" (formatAtom name) value else formatAtom name value; + + formatAtom = + name: atom: + let + v = if isBool atom then boolToString atom else toString atom; + in + "${name} = ${v}"; + in + name: value: + pkgs.writeText name '' + ${formatToplevel value} + ''; +} diff --git a/nix/nixosTest.nix b/nix/nixosTest.nix new file mode 100644 index 000000000..b4ddbc470 --- /dev/null +++ b/nix/nixosTest.nix @@ -0,0 +1,268 @@ + { nixpkgs }: + ( + { pkgs, ... }: + { + name = "kerberos_server-heimdal"; + + nodes = { + server = + { config, pkgs, ... }: + { + disabledModules = [ "services/system/kerberos/default.nix" ]; + imports = [ + "${nixpkgs}/nixos/tests/common/user-account.nix" + ./module + ]; + + users.users.alice.extraGroups = [ "wheel" ]; + + services.getty.autologinUser = "alice"; + + virtualisation.vlans = [ 1 ]; + + time.timeZone = "Etc/UTC"; + + networking = { + domain = "foo.bar"; + useDHCP = false; + firewall.enable = false; + hosts."10.0.0.1" = [ "server.foo.bar" ]; + hosts."10.0.0.2" = [ "client.foo.bar" ]; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.1/24"; + }; + + security.krb5 = { + enable = true; + package = pkgs.heimdal; + settings = { + libdefaults.default_realm = "FOO.BAR"; + + # Enable extra debug output + logging = { + admin_server = "SYSLOG:DEBUG:AUTH"; + default = "SYSLOG:DEBUG:AUTH"; + kdc = "SYSLOG:DEBUG:AUTH"; + }; + + realms = { + "FOO.BAR" = { + admin_server = "server.foo.bar"; + kpasswd_server = "server.foo.bar"; + kdc = [ "server.foo.bar" ]; + }; + }; + }; + }; + + services.kerberos_server = { + enable = true; + settings.realms = { + "FOO.BAR" = { + acl = [ + { + principal = "kadmin/admin@FOO.BAR"; + access = "all"; + } + { + principal = "alice/admin@FOO.BAR"; + access = [ + "add" + "cpw" + "delete" + "get" + "list" + "modify" + ]; + } + ]; + }; + }; + }; + }; + + client = + { config, pkgs, ... }: + { + disabledModules = [ "services/system/kerberos/default.nix" ]; + imports = [ + "${nixpkgs}/nixos/tests/common/user-account.nix" + ./module + ]; + + users.users.alice.extraGroups = [ "wheel" ]; + + services.getty.autologinUser = "alice"; + + virtualisation.vlans = [ 1 ]; + + time.timeZone = "Etc/UTC"; + + networking = { + domain = "foo.bar"; + useDHCP = false; + hosts."10.0.0.1" = [ "server.foo.bar" ]; + hosts."10.0.0.2" = [ "client.foo.bar" ]; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.2/24"; + }; + + security.krb5 = { + enable = true; + package = pkgs.heimdal; + settings = { + libdefaults.default_realm = "FOO.BAR"; + + logging = { + admin_server = "SYSLOG:DEBUG:AUTH"; + default = "SYSLOG:DEBUG:AUTH"; + kdc = "SYSLOG:DEBUG:AUTH"; + }; + + realms = { + "FOO.BAR" = { + admin_server = "server.foo.bar"; + kpasswd_server = "server.foo.bar"; + kdc = [ "server.foo.bar" ]; + }; + }; + }; + }; + }; + }; + + testScript = + { nodes, ... }: + '' + import string + import random + random.seed(0) + + start_all() + + with subtest("Server: initialize realm"): + # for unit in ["kadmind.service", "kdc.socket", "kpasswdd.socket"]: + for unit in ["kadmind.service", "kdc.service", "kpasswdd.service"]: + server.wait_for_unit(unit) + + server.succeed("kadmin -l init --realm-max-ticket-life='8 day' --realm-max-renewable-life='10 day' FOO.BAR") + + for unit in ["kadmind.service", "kdc.service", "kpasswdd.service"]: + server.systemctl(f"restart {unit}") + + alice_krb_pw = "alice_hunter2" + alice_old_krb_pw = "" + alice_krb_admin_pw = "alice_admin_hunter2" + + def random_password(): + password_chars = string.ascii_letters + string.digits + string.punctuation.replace('"', "") + return "".join(random.choice(password_chars) for _ in range(16)) + + with subtest("Server: initialize user principals and keytabs"): + server.succeed(f'kadmin -l add --password="{alice_krb_admin_pw}" --use-defaults alice/admin') + server.succeed("kadmin -l ext_keytab --keytab=admin.keytab alice/admin") + + server.succeed(f'kadmin -p alice/admin -K admin.keytab add --password="{alice_krb_pw}" --use-defaults alice') + server.succeed("kadmin -l ext_keytab --keytab=alice.keytab alice") + + server.wait_for_unit("getty@tty1.service") + server.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + server.wait_for_unit("default.target") + + with subtest("Server: initialize host principal with keytab"): + server.send_chars("sudo ktutil get -p alice/admin host/server.foo.bar\n") + server.wait_until_tty_matches("1", "password for alice:") + server.send_chars("${nodes.server.config.users.users.alice.password}\n") + server.wait_until_tty_matches("1", "alice/admin@FOO.BAR's Password:") + server.send_chars(f'{alice_krb_admin_pw}\n') + server.wait_for_file("/etc/krb5.keytab") + + ktutil_list = server.succeed("sudo ktutil list") + if not "host/server.foo.bar" in ktutil_list: + exit(1) + + server.send_chars("clear\n") + + client.systemctl("start network-online.target") + client.wait_for_unit("network-online.target") + client.wait_for_unit("getty@tty1.service") + client.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + client.wait_for_unit("default.target") + + with subtest("Client: initialize host principal with keytab"): + client.succeed( + f'echo "{alice_krb_admin_pw}" > pw.txt', + "kinit -p --password-file=pw.txt alice/admin", + ) + + client.send_chars("sudo ktutil get -p alice/admin host/client.foo.bar\n") + client.wait_until_tty_matches("1", "password for alice:") + client.send_chars("${nodes.client.config.users.users.alice.password}\n") + client.wait_until_tty_matches("1", "alice/admin@FOO.BAR's Password:") + client.send_chars(f"{alice_krb_admin_pw}\n") + client.wait_for_file("/etc/krb5.keytab") + + ktutil_list = client.succeed("sudo ktutil list") + if not "host/client.foo.bar" in ktutil_list: + exit(1) + + client.send_chars("clear\n") + + with subtest("Client: kinit alice"): + client.succeed( + f"echo '{alice_krb_pw}' > pw.txt", + "kinit -p --password-file=pw.txt alice", + ) + tickets = client.succeed("klist") + assert "Principal: alice@FOO.BAR" in tickets + client.send_chars("clear\n") + + with subtest("Client: kpasswd alice"): + alice_old_krb_pw = alice_krb_pw + alice_krb_pw = random_password() + client.send_chars("kpasswd\n") + client.wait_until_tty_matches("1", "alice@FOO.BAR's Password:") + client.send_chars(f"{alice_old_krb_pw}\n", 0.1) + client.wait_until_tty_matches("1", "New password:") + client.send_chars(f"{alice_krb_pw}\n", 0.1) + client.wait_until_tty_matches("1", "Verify password - New password:") + client.send_chars(f"{alice_krb_pw}\n", 0.1) + + client.wait_until_tty_matches("1", "Success : Password changed") + + client.send_chars("clear\n") + + with subtest("Server: kinit alice"): + server.succeed( + "echo 'alice_pw_2' > pw.txt" + "kinit -p --password-file=pw.txt alice", + ) + tickets = client.succeed("klist") + assert "Principal: alice@FOO.BAR" in tickets + server.send_chars("clear\n") + + with subtest("Server: kpasswd alice"): + alice_old_krb_pw = alice_krb_pw + alice_krb_pw = random_password() + server.send_chars("kpasswd\n") + server.wait_until_tty_matches("1", "alice@FOO.BAR's Password:") + server.send_chars(f"{alice_old_krb_pw}\n", 0.1) + server.wait_until_tty_matches("1", "New password:") + server.send_chars(f"{alice_krb_pw}\n", 0.1) + server.wait_until_tty_matches("1", "Verify password - New password:") + server.send_chars(f"{alice_krb_pw}\n", 0.1) + + server.wait_until_tty_matches("1", "Success : Password changed") + + server.send_chars("clear\n") + ''; + + meta.maintainers = pkgs.heimdal.meta.maintainers; + } +) diff --git a/nix/shell.nix b/nix/shell.nix new file mode 100644 index 000000000..0db62bec6 --- /dev/null +++ b/nix/shell.nix @@ -0,0 +1,51 @@ +{ pkgs, lib }: +pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + autoconf + automake + autogen + autoreconfHook + + pkg-config + python3 + perl + bison + flex + perlPackages.JSON + texinfo + + # check inputs + curl + jdk_headless + unzip + which + ]; + + buildInputs = with pkgs; [ + db + libedit + pam + cjson + libcap_ng + libmicrohttpd + openldap + openssl + sqlite + ]; + + env = { + CFLAGS = lib.concatStringsSep " " [ + # From github workflows + "-Wno-error=shadow" + "-Wno-error=bad-function-cast" + "-Wno-error=unused-function" + "-Wno-error=unused-result" + "-Wno-error=deprecated-declarations" + + # idk, but it complained about these during compilation. + # maybe they come from the nix environment, or too new compiler? + "-Wno-error=maybe-uninitialized" + "-Wno-error=format-overflow" + ]; + }; +}