# localkdc.spec — Fedora RPM packaging for localkdc # # This spec builds and installs: # # localkdc Shell scripts, config templates, and systemd units for # running a per-machine Kerberos KDC backed by systemd # userdb (systemd-userdbd). # # localkdc-kdb The kurbu5-kdb-userdb KDB plugin (Rust cdylib) that # augments klmdb with systemd userdb records, installed # to the MIT Kerberos KDB plugin directory. # # localkdc-otp The localkdc-preauth-otp kdcpreauth/clpreauth plugin # (Rust cdylib) that implements OTP pre-authentication # via a Unix-domain socket RADIUS server. # Built only when --with otp is passed to rpmbuild; # disabled by default because MIT Kerberos ships its own # OTP preauth plugin (otp.so) that takes priority. # # Source preparation notes # ───────────────────────── # Source0 (main tarball): # git archive --prefix=localkdc-%%{version}/ v%%{version} \ # | gzip > localkdc-%%{version}.tar.gz # # Source1 (vendor tarball): # 'cargo vendor' stdout (git-source redirect stanzas) is saved alongside # the vendor directory as vendor-config.toml; %%prep appends it to # .cargo/config.toml so --locked builds can find git-sourced packages in # vendor/ via source replacement without any network access. # Use 'make sources' (or 'make vendor') to regenerate this tarball. # %global crate localkdc # Path constants — kept as shell variables in %%install rather than RPM macros # so that the same values can be embedded verbatim into installed config files. %global selinuxtype targeted %global modulename localkdc %global localkdc_sysconfdir %{_sysconfdir}/localkdc %global localkdc_datadir %{_datadir}/localkdc %global localkdc_libexecdir %{_libexecdir}/localkdc %global localkdc_runstatedir /run/localkdc %global localkdc_localstatedir %{_localstatedir}/kerberos/localkdc %global localkdc_krb5confdir %{_sysconfdir}/krb5.conf.d # Snapshot release identifiers. # 'make srpm' substitutes the real date (YYYYMMDD) and short commit hash here # before invoking rpmbuild so that the SRPM contains the expanded Release tag. # The placeholder values below are only used when the spec is processed without # going through the Makefile (e.g. rpmlint, manual rpmbuild invocations). %global snapdate 202603281415 %global snapcommit 430e21d # OTP preauth plugin — disabled by default. # MIT Kerberos ships its own otp.so preauth plugin that takes priority; # enable only when testing the localkdc replacement: # rpmbuild --with otp … %bcond_with otp Name: %{crate} Version: 0.1.0 Release: 1.%{snapdate}.git%{snapcommit}%{?dist} Summary: Per-machine Kerberos KDC backed by systemd userdb License: MIT URL: https://codeberg.org/abbra/kurbu5 Source0: %{name}-%{version}.tar.gz Source1: %{name}-%{version}-vendor.tar.gz # Rust build infrastructure BuildRequires: cargo-rpm-macros >= 25 # krb5 headers needed by the kurbu5-sys bindgen crate BuildRequires: krb5-devel # bindgen (used by kurbu5-sys build.rs) requires libclang at build time BuildRequires: clang-libs BuildRequires: clang-devel # libpam linker stub needed by localkdc-pam-auth (localkdc-pam-auth subpackage) BuildRequires: pam-devel # SELinux policy module build toolchain (localkdc-selinux subpackage) BuildRequires: selinux-policy-devel # systemd RPM macros for %%systemd_post / %%systemd_preun / %%systemd_postun BuildRequires: systemd-rpm-macros # Runtime dependencies for the shell scripts Requires: krb5-server Requires: certmonger Requires: systemd # localkdc-kinit wraps kinit from krb5-workstation Requires: krb5-workstation # SSSD providers used by the localkdc domain snippet Requires: sssd-krb5 Requires: sssd-proxy # The KDB plugin is a sub-package; pull it in automatically Requires: %{name}-kdb%{?_isa} = %{version}-%{release} # Pull in SELinux file contexts only on SELinux-enabled systems Requires: (%{name}-selinux = %{version}-%{release} if selinux-policy-%{selinuxtype}) %description localkdc configures and runs a per-machine MIT Kerberos KDC whose principal database is backed by systemd userdb (systemd-userdbd). Each machine gets a unique realm derived from its machine-id. The KDC uses Anonymous PKINIT for certificate issuance via certmonger. This package provides: * localkdc-setup — initialise the KDC database and certificates * localkdc-kadmin — wrapper for kadmin.local that targets the localkdc realm * localkdc-kinit — obtain a Kerberos ticket for a local user * localkdc-useradd — add a systemd userdb user as a Kerberos principal * Config templates installed to %{localkdc_datadir}/templates/ * Systemd socket and service units # ────────────────────────────────────────────────────────────────────────────── # localkdc-kdb — kurbu5-kdb-userdb KDB plugin # ────────────────────────────────────────────────────────────────────────────── %package kdb Summary: MIT Kerberos KDB plugin for localkdc (systemd userdb overlay) License: MIT # krb5-server owns the KDB plugin directory Requires: krb5-server # All external Rust crates are vendored in the source tarball. # Declare the ones not yet packaged in Fedora as bundled. Provides: bundled(crate(futures-core)) = 0.3.32 Provides: bundled(crate(futures-task)) = 0.3.32 Provides: bundled(crate(is_terminal_polyfill)) = 1.70.2 Provides: bundled(crate(kirmes)) = 0.1.0 Provides: bundled(crate(kurbu5-derive)) = 0.1.0 Provides: bundled(crate(kurbu5-kdb-derive)) = 0.1.0 Provides: bundled(crate(kurbu5-kdb-rs)) = 0.1.0 Provides: bundled(crate(kurbu5-kdb-sys)) = 0.1.0 Provides: bundled(crate(kurbu5-rs)) = 0.1.0 Provides: bundled(crate(kurbu5-sys)) = 0.1.0 Provides: bundled(crate(winnow)) = 1.0.0 %description kdb The localkdc-kdb package provides the kurbu5-kdb-userdb shared library, a KDB overlay plugin for MIT Kerberos that augments klmdb with systemd userdb records. When a principal lookup fails in klmdb, the plugin falls back to: 1. Service alias resolution — retries service principals whose host component is a local IP address using the reverse-DNS hostname. 2. Systemd userdb — for single-component (user) principals, queries systemd-userdbd and retries as userdb: in klmdb. The plugin is loaded by krb5kdc when kdc.conf contains: db_library = kdb_userdb.so in the [dbmodules] section, which is set up automatically by localkdc-setup. # ────────────────────────────────────────────────────────────────────────────── # localkdc-pam-auth — Unix-socket RADIUS server for OTP preauth # ────────────────────────────────────────────────────────────────────────────── %package pam-auth Summary: Unix-socket RADIUS server for localkdc OTP pre-authentication License: MIT # localkdc-pam-auth is linked against libpam at build time Requires: pam%{?_isa} # All external Rust crates are vendored in the source tarball. # Crates available in Fedora at the same version are re-linked from the system # Cargo registry in %%prep; only declare those whose vendored version differs. Provides: bundled(crate(block-buffer)) = 0.10.4 Provides: bundled(crate(crypto-common)) = 0.1.7 Provides: bundled(crate(generic-array)) = 0.14.7 %description pam-auth The localkdc-radius package provides localkdc-pam-auth, a minimal Unix-domain socket RADIUS server that the MIT Kerberos OTP preauth plugin (otp.so) can forward authentication requests to. localkdc-pam-auth authenticates (username, OTP) pairs using one of two backends selected at startup: * PAM — delegates each check to a named PAM service (default: system-auth). Use this backend so that real OS user accounts are authenticated with their OTP token via pam_oath or similar modules. * Static — an in-memory table of user:otp pairs supplied on the command line. Intended for offline unit and integration tests that must not touch the system PAM stack. The server listens on a SOCK_STREAM Unix-domain socket and speaks the minimal subset of RFC 2865 RADIUS required for OTP preauth: Access-Request → Access-Accept / Access-Reject. Example invocation (PAM backend, system-auth service): localkdc-pam-auth /run/localkdc/PAM.socket /run/localkdc/pam-radius.secret --pam Example invocation (custom PAM service): localkdc-pam-auth /run/localkdc/PAM.socket /run/localkdc/pam-radius.secret --pam localkdc-otp Example invocation (static credentials, for testing): localkdc-pam-auth /run/localkdc/PAM.socket /run/localkdc/pam-radius.secret alice:123456 # ────────────────────────────────────────────────────────────────────────────── # localkdc-otp — localkdc-preauth-otp OTP preauth plugin (--with otp only) # ────────────────────────────────────────────────────────────────────────────── %if %{with otp} %package otp Summary: MIT Kerberos OTP preauth plugin for localkdc License: MIT # krb5-server owns the preauth plugin directory Requires: krb5-server # All external Rust crates are vendored in the source tarball. # Declare the ones not yet packaged in Fedora as bundled. Provides: bundled(crate(abol-core)) = 0.1.0 Provides: bundled(crate(block-buffer)) = 0.10.4 Provides: bundled(crate(crypto-common)) = 0.1.7 Provides: bundled(crate(generic-array)) = 0.14.7 Provides: bundled(crate(getrandom)) = 0.3.4 Provides: bundled(crate(kurbu5-derive)) = 0.1.0 Provides: bundled(crate(kurbu5-rs)) = 0.1.0 Provides: bundled(crate(kurbu5-sys)) = 0.1.0 Provides: bundled(crate(rand)) = 0.9.2 Provides: bundled(crate(rand_chacha)) = 0.9.0 Provides: bundled(crate(rand_core)) = 0.9.5 %description otp The localkdc-otp package provides the localkdc-preauth-otp shared library, a kdcpreauth/clpreauth plugin for MIT Kerberos that implements OTP (One-Time Password) pre-authentication by delegating to a RADIUS server over a Unix-domain socket. When configured in kdc.conf, the plugin: 1. Sends an OTP challenge (PA-OTP-CHALLENGE) to the client on first AS-REQ. 2. Receives the OTP response encrypted under the FAST armor key. 3. Forwards the (username, OTP) pair to the configured RADIUS server. 4. Accepts or rejects the AS-REQ based on the RADIUS Access-Accept/Reject. Configure kdc.conf and kdc.conf's [otp] section to point at a Unix-socket RADIUS server, e.g.: [otp] DEFAULT = { server = /run/krb5kdc/DEFAULT.socket strip_realm = true } %endif # ────────────────────────────────────────────────────────────────────────────── # localkdc-selinux — SELinux policy module # ────────────────────────────────────────────────────────────────────────────── %package selinux Summary: SELinux policy module for localkdc BuildArch: noarch License: MIT Requires: selinux-policy-%{selinuxtype} Requires(post): selinux-policy-%{selinuxtype} %{?selinux_requires} %description selinux SELinux policy module for localkdc. Assigns the correct file contexts to the localkdc data directory and database files so that the KDC process can be confined under the existing krb5kdc_t domain without generating AVC denials: /var/kerberos/localkdc(/.*)? krb5kdc_conf_t /var/kerberos/localkdc/principal.mdb krb5kdc_principal_t … (lock files → krb5kdc_lock_t) # ══════════════════════════════════════════════════════════════════════════════ # PREP # ══════════════════════════════════════════════════════════════════════════════ %prep %autosetup -n %{name}-%{version} -p1 # Extract the vendor tarball. It contains vendor/, vendor-config.toml, and # the regenerated Cargo.lock consistent with the vendored crates. tar xf %{SOURCE1} # Write .cargo/config.toml pointing all crate lookups at vendor/. %cargo_prep -v vendor # Append any git-source or path-source redirect stanzas emitted by # 'cargo vendor' (saved in vendor-config.toml by the Makefile). This lets # --locked builds find crates whose source is a git URL or a path dependency # (such as kirmes) via source replacement without network access. awk '/^\[source\."(git\+|path\+)/{s=1; print; next} /^\[/{s=0} s{print} /^[[:space:]]*$/{s=0}' \ vendor-config.toml >> .cargo/config.toml || : # Re-link any crates already installed as system packages so that %%cargo_prep # can also find them from the system Cargo registry. for dir in %{cargo_registry}/*/; do [[ -d "$dir" ]] || continue crate="${dir##%{cargo_registry}/}"; crate="${crate%/}" [[ -e "vendor/$crate" ]] || ln -s "$dir" "vendor/$crate" done # ══════════════════════════════════════════════════════════════════════════════ # BUILD # ══════════════════════════════════════════════════════════════════════════════ %build # Build the KDB plugin cdylib. %{cargo_build} -p localkdc-kdb-userdb # Build the Unix-socket RADIUS server (localkdc-pam-auth subpackage). %{cargo_build} -p localkdc-pam-auth # Build the OTP preauth plugin cdylib (only when --with otp is set). %if %{with otp} %{cargo_build} -p localkdc-preauth-otp %endif # Build the SELinux policy module. make -f %{_datadir}/selinux/devel/Makefile -C localkdc/selinux localkdc.pp bzip2 -9 localkdc/selinux/localkdc.pp # ══════════════════════════════════════════════════════════════════════════════ # INSTALL # ══════════════════════════════════════════════════════════════════════════════ %install # ── Helper: substitute @LOCALKDC_*@ placeholders ───────────────────────────── # Using shell variables so that their values land verbatim in the installed # files (RPM macros in the values would be double-expanded by the shell). _SYSCONFDIR="%{localkdc_sysconfdir}" _DATADIR="%{localkdc_datadir}" _LIBEXECDIR="%{localkdc_libexecdir}" _RUNSTATEDIR="%{localkdc_runstatedir}" _LOCALSTATEDIR="%{localkdc_localstatedir}" _KRB5CONFDIR="%{localkdc_krb5confdir}" _subst() { sed \ -e "s|@LOCALKDC_SYSCONFDIR@|${_SYSCONFDIR}|g" \ -e "s|@LOCALKDC_DATADIR@|${_DATADIR}|g" \ -e "s|@LOCALKDC_LIBEXECDIR@|${_LIBEXECDIR}|g" \ -e "s|@LOCALKDC_RUNSTATEDIR@|${_RUNSTATEDIR}|g" \ -e "s|@LOCALKDC_LOCALSTATEDIR@|${_LOCALSTATEDIR}|g" \ -e "s|@LOCALKDC_KRB5CONFIGDIR@|${_KRB5CONFDIR}|g" \ "$1" > "$2" } UNITDIR=localkdc/systemd # ── 1. KDB plugin ───────────────────────────────────────────────────────────── install -Dpm 755 \ target/rpm/libkdb_userdb.so \ %{buildroot}%{_libdir}/krb5/plugins/kdb/kdb_userdb.so # ── 2. Unix-socket RADIUS server + systemd units ───────────────────────────── install -Dpm 755 \ target/rpm/localkdc-pam-auth \ %{buildroot}%{localkdc_libexecdir}/localkdc-pam-auth _subst ${UNITDIR}/localkdc-pam-auth.socket.in _localkdc-pam-auth.socket install -Dpm 644 _localkdc-pam-auth.socket \ %{buildroot}%{_unitdir}/localkdc-pam-auth.socket _subst ${UNITDIR}/localkdc-pam-auth@.service.in _localkdc-pam-auth@.service install -Dpm 644 "_localkdc-pam-auth@.service" \ %{buildroot}%{_unitdir}/localkdc-pam-auth@.service # ── 3. SELinux policy module ────────────────────────────────────────────────── install -Dpm 644 localkdc/selinux/localkdc.pp.bz2 \ %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2 # ── 4. OTP preauth plugin (--with otp only) ─────────────────────────────────── %if %{with otp} install -Dpm 755 \ target/rpm/libotp.so \ %{buildroot}%{_libdir}/krb5/plugins/preauth/otp.so %endif # ── 5. Shell scripts ────────────────────────────────────────────────────────── BINDIR=localkdc/bin # localkdc-kinit → /usr/bin/ _subst ${BINDIR}/localkdc-kinit.sh _localkdc-kinit install -Dpm 755 _localkdc-kinit %{buildroot}%{_bindir}/localkdc-kinit # localkdc-setup, localkdc-kadmin, localkdc-useradd → /usr/sbin/ for script in localkdc-setup localkdc-kadmin localkdc-useradd; do _subst ${BINDIR}/${script}.sh _${script} install -Dpm 755 _${script} %{buildroot}%{_sbindir}/${script} done # localkdc-utils.sh → /usr/share/localkdc/ (sourced by other scripts) _subst ${BINDIR}/localkdc-utils.sh _localkdc-utils.sh install -Dpm 644 _localkdc-utils.sh \ %{buildroot}%{localkdc_datadir}/localkdc-utils.sh # ── 6. Config templates ─────────────────────────────────────────────────────── CONFDIR=localkdc/conf # kdc.conf template (disallow_name_aliases = false for KDC use) _subst ${CONFDIR}/kdc.conf _kdc.conf.tmp sed -e 's|@LOCALKDC_DISALLOW_NAME_ALIASES@|false|g' _kdc.conf.tmp \ > _kdc.conf install -Dpm 644 _kdc.conf \ %{buildroot}%{localkdc_datadir}/templates/kdc.conf # kadmin.conf variant (disallow_name_aliases = true — kadmin is more strict) sed -e 's|@LOCALKDC_DISALLOW_NAME_ALIASES@|true|g' _kdc.conf.tmp \ > _kadmin.conf install -Dpm 644 _kadmin.conf \ %{buildroot}%{localkdc_datadir}/templates/kadmin.conf # krb5.conf.hostname snippet _subst ${CONFDIR}/krb5.conf.hostname _krb5.conf.hostname install -Dpm 644 _krb5.conf.hostname \ %{buildroot}%{localkdc_datadir}/templates/krb5.conf.hostname # krb5.conf.kdc — minimal KDC-only krb5.conf (no [logging], no includedir) _subst ${CONFDIR}/krb5.conf.kdc _krb5.conf.kdc install -Dpm 644 _krb5.conf.kdc \ %{buildroot}%{localkdc_datadir}/templates/krb5.conf.kdc # SSSD main config (static — no @LOCALKDC_*@ placeholders) install -Dpm 644 ${CONFDIR}/00-sssd.conf \ %{buildroot}%{localkdc_datadir}/templates/sssd.conf # SSSD localkdc domain snippet (contains %REALM% substituted at setup time) install -Dpm 644 ${CONFDIR}/00-sssd-conf.d-localkdc.conf \ %{buildroot}%{localkdc_datadir}/templates/sssd-localkdc.conf # ── 7. Systemd units ────────────────────────────────────────────────────────── SYSTEMD_UNIT_DIR=%{_unitdir} _subst ${UNITDIR}/localkdc.service.in _localkdc.service install -Dpm 644 _localkdc.service \ %{buildroot}${SYSTEMD_UNIT_DIR}/localkdc.service _subst ${UNITDIR}/localkdc.socket.in _localkdc.socket install -Dpm 644 _localkdc.socket \ %{buildroot}${SYSTEMD_UNIT_DIR}/localkdc.socket # ── 8. Runtime directories ──────────────────────────────────────────────────── # /etc/localkdc is owned by localkdc; populated by localkdc-setup at runtime. install -d -m 0755 %{buildroot}%{localkdc_sysconfdir} # /var/kerberos/localkdc is owned by localkdc; populated by localkdc-setup. install -d -m 0700 %{buildroot}%{localkdc_localstatedir} # /run/localkdc is managed by tmpfiles.d — write the drop-in. install -d %{buildroot}%{_tmpfilesdir} cat > %{buildroot}%{_tmpfilesdir}/localkdc.conf << 'EOF' d /run/localkdc 0755 root root - EOF # ══════════════════════════════════════════════════════════════════════════════ # CHECK # ══════════════════════════════════════════════════════════════════════════════ %check %cargo_test -- -p localkdc-kdb-userdb --lib %if %{with otp} %cargo_test -- -p localkdc-preauth-otp --lib %endif # ══════════════════════════════════════════════════════════════════════════════ # SCRIPTLETS # ══════════════════════════════════════════════════════════════════════════════ %post %systemd_post localkdc.service localkdc.socket systemd-tmpfiles --create %{_tmpfilesdir}/localkdc.conf &>/dev/null || : %preun %systemd_preun localkdc.service localkdc.socket %postun %systemd_postun_with_restart localkdc.service # ══════════════════════════════════════════════════════════════════════════════ # FILES # ══════════════════════════════════════════════════════════════════════════════ # ── localkdc (main package) ─────────────────────────────────────────────────── %files %license LICENSE %{_bindir}/localkdc-kinit %{_sbindir}/localkdc-setup %{_sbindir}/localkdc-kadmin %{_sbindir}/localkdc-useradd %dir %{localkdc_datadir} %{localkdc_datadir}/localkdc-utils.sh %dir %{localkdc_datadir}/templates %{localkdc_datadir}/templates/kdc.conf %{localkdc_datadir}/templates/kadmin.conf %{localkdc_datadir}/templates/krb5.conf.hostname %{localkdc_datadir}/templates/krb5.conf.kdc %{localkdc_datadir}/templates/sssd.conf %{localkdc_datadir}/templates/sssd-localkdc.conf %{_unitdir}/localkdc.service %{_unitdir}/localkdc.socket %{_tmpfilesdir}/localkdc.conf %dir %{localkdc_sysconfdir} %dir %{localkdc_localstatedir} # ── localkdc-kdb ────────────────────────────────────────────────────────────── %files kdb %license LICENSE %{_libdir}/krb5/plugins/kdb/kdb_userdb.so # ── localkdc-pam-auth ───────────────────────────────────────────────────────── %files pam-auth %license LICENSE %dir %{localkdc_libexecdir} %{localkdc_libexecdir}/localkdc-pam-auth %{_unitdir}/localkdc-pam-auth.socket %{_unitdir}/localkdc-pam-auth@.service %post pam-auth %systemd_post localkdc-pam-auth.socket %preun pam-auth %systemd_preun localkdc-pam-auth.socket %postun pam-auth %systemd_postun localkdc-pam-auth.socket # ── localkdc-selinux ────────────────────────────────────────────────────────── %files selinux %license LICENSE %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.* %ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename} %pre selinux %selinux_relabel_pre -s %{selinuxtype} %post selinux %selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2 %postun selinux if [ $1 -eq 0 ]; then %selinux_modules_uninstall -s %{selinuxtype} %{modulename} fi %posttrans selinux %selinux_relabel_post -s %{selinuxtype} # ── localkdc-otp (--with otp only) ──────────────────────────────────────────── %if %{with otp} %files otp %license LICENSE %{_libdir}/krb5/plugins/preauth/otp.so %endif # ══════════════════════════════════════════════════════════════════════════════ # CHANGELOG # ══════════════════════════════════════════════════════════════════════════════ %changelog * Tue Mar 24 2026 Alexander Bokovoy - 0.1.0-1 - Initial Fedora package; replaces CMake build with Rust-native packaging - kurbu5-kdb-userdb Rust plugin replaces the C kdb_userdb implementation