From 2c0bd506219c6ee0b3813101b8f6d9598e2be82d Mon Sep 17 00:00:00 2001 From: Georg Sieber Date: Thu, 28 Nov 2024 23:24:27 +0100 Subject: [PATCH] laps-runner-pam script to avoid blocking PAM logout flow; PAM_SERVICE filter in config file instead of parameter --- installer/deb/build.sh | 1 + installer/rpm/build.sh | 1 + .../rpm/rpmbuild/SPECS/laps4linux-runner.spec | 3 +++ laps-runner/README.md | 6 ++++-- laps-runner/laps-runner-pam | 3 +++ laps-runner/laps-runner.json.example | 3 ++- laps-runner/laps_runner/laps_runner.py | 18 ++++++++++-------- 7 files changed, 24 insertions(+), 11 deletions(-) create mode 100755 laps-runner/laps-runner-pam diff --git a/installer/deb/build.sh b/installer/deb/build.sh index d8d752d..e3578da 100755 --- a/installer/deb/build.sh +++ b/installer/deb/build.sh @@ -41,6 +41,7 @@ fi # copy files in place install -D -m 644 ../../README.md -t $BUILDDIR/$INSTALLDIR install -D -m 755 ../../assets/laps-runner.cron $BUILDDIR/etc/cron.hourly/laps-runner +install -D -m 755 ../../laps-runner/laps-runner-pam -t $BUILDDIR/usr/sbin install -D -m 644 ../../laps-runner/laps_runner/*.py -t $BUILDDIR/$INSTALLDIR/laps_runner install -D -m 644 ../../laps-runner/requirements.txt -t $BUILDDIR/$INSTALLDIR install -D -m 644 ../../laps-runner/setup.py -t $BUILDDIR/$INSTALLDIR diff --git a/installer/rpm/build.sh b/installer/rpm/build.sh index 0ff6fd5..9d6dc57 100755 --- a/installer/rpm/build.sh +++ b/installer/rpm/build.sh @@ -33,6 +33,7 @@ cp ../../README.md laps4linux-client-$VERSION/usr/share/laps4 cp ../../assets/LAPS4LINUX.desktop laps4linux-client-$VERSION/usr/share/applications cp ../../assets/laps.png laps4linux-client-$VERSION/usr/share/pixmaps cp ../../assets/laps-runner.cron laps4linux-runner-$VERSION/etc/cron.hourly/laps-runner +cp ../../laps-runner/laps-runner-pam laps4linux-runner-$VERSION/usr/sbin/laps-runner-pam cp ../../laps-runner/laps_runner/*.py laps4linux-runner-$VERSION/usr/share/laps4linux-runner/laps_runner cp ../../laps-runner/requirements.txt laps4linux-runner-$VERSION/usr/share/laps4linux-runner cp ../../laps-runner/setup.py laps4linux-runner-$VERSION/usr/share/laps4linux-runner diff --git a/installer/rpm/rpmbuild/SPECS/laps4linux-runner.spec b/installer/rpm/rpmbuild/SPECS/laps4linux-runner.spec index 831bddc..d8091c0 100644 --- a/installer/rpm/rpmbuild/SPECS/laps4linux-runner.spec +++ b/installer/rpm/rpmbuild/SPECS/laps4linux-runner.spec @@ -32,6 +32,8 @@ mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir} cp etc/laps-runner.json $RPM_BUILD_ROOT/%{_sysconfdir} mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/cron.hourly/ cp etc/cron.hourly/laps-runner $RPM_BUILD_ROOT/%{_sysconfdir}/cron.hourly/ +mkdir -p $RPM_BUILD_ROOT/%{_sbindir} +cp usr/sbin/laps-runner-pam $RPM_BUILD_ROOT/%{_sbindir}/laps-runner-pam %post DIR=/usr/share/laps4linux-runner @@ -46,6 +48,7 @@ rm -rf $RPM_BUILD_ROOT %files %{_sbindir}/laps-runner +%{_sbindir}/laps-runner-pam %{_sysconfdir}/laps-runner.json %{_sysconfdir}/cron.hourly/laps-runner /usr/share/laps4linux-runner/laps_runner/filetime.py diff --git a/laps-runner/README.md b/laps-runner/README.md index cfbf96a..2f16ac4 100644 --- a/laps-runner/README.md +++ b/laps-runner/README.md @@ -69,12 +69,14 @@ Priority: 0 Session-Type: Additional Session-Interactive-Only: yes Session: - optional pam_exec.so type=close_session seteuid quiet /usr/sbin/laps-runner --pam + optional pam_exec.so type=close_session seteuid quiet /usr/sbin/laps-runner-pam ``` -Use `Session-Interactive-Only: no` if you like to rotate the password on sudo usage too. You can add the parameter `--pam-service login` if you do not want to change the password on `sudo -i` usage. The `--pam-service` parameter can be used multiple times - this allows you to trigger LAPS on multiple, specific PAM service events. +Use `Session-Interactive-Only: no` if you like to rotate the password on sudo usage too. Then, run `pam-auth-update` to automatically generate the files under `/etc/pam.d/` with the necessary line for LAPS. +You can add `login` to the array `pam-services` in the config file if you do not want to change the password on `sudo -i` usage. Since this config option is an array, this allows you to trigger LAPS on multiple, specific PAM service events. + If you want the runner to wait a certain time after logout until the password should be changed, set `pam-grace-period` in the runner config to the desired number of seconds, e.g. 300 for 5 minutes. ### Hostnames Longer Than 15 Characters diff --git a/laps-runner/laps-runner-pam b/laps-runner/laps-runner-pam new file mode 100755 index 0000000..209ca5b --- /dev/null +++ b/laps-runner/laps-runner-pam @@ -0,0 +1,3 @@ +#!/bin/sh + +nohup /usr/sbin/laps-runner --pam & diff --git a/laps-runner/laps-runner.json.example b/laps-runner/laps-runner.json.example index d91aafa..d06bfe9 100644 --- a/laps-runner/laps-runner.json.example +++ b/laps-runner/laps-runner.json.example @@ -38,5 +38,6 @@ "password-length": 15, "password-alphabet": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - "pam-grace-period": 0 + "pam-services-EXAMPLE": ["login"], + "pam-grace-period-EXAMPLE": 300 } diff --git a/laps-runner/laps_runner/laps_runner.py b/laps-runner/laps_runner/laps_runner.py index 968a6cb..dbbfcdf 100755 --- a/laps-runner/laps_runner/laps_runner.py +++ b/laps-runner/laps_runner/laps_runner.py @@ -45,7 +45,6 @@ class LapsRunner(): cfgHostname = None cfgUsername = 'root' # the user, whose password should be changed cfgDaysValid = 30 # how long the new password should be valid - cfgPamGracePeriod = 0 # timeout in seconds to wait before changing the password after logout (PAM mode) cfgLength = 15 # the generated password length cfgAlphabet = string.ascii_letters+string.digits+string.punctuation # allowed chars for the new password @@ -56,6 +55,9 @@ class LapsRunner(): cfgLdapAttributePasswordHistory = 'msLAPS-EncryptedPasswordHistory' cfgLdapAttributePasswordExpiry = 'msLAPS-PasswordExpirationTime' + cfgPamServices = [] # PAM_SERVICE filter + cfgPamGracePeriod = 0 # timeout in seconds to wait before changing the password after logout + tmpDn = '' tmpPassword = None tmpExpiry = '' @@ -312,7 +314,8 @@ def LoadSettings(self): self.cfgLdapAttributePasswordHistory = str(cfgJson.get('ldap-attribute-password-history', self.cfgLdapAttributePasswordHistory)) self.cfgLdapAttributePasswordExpiry = str(cfgJson.get('ldap-attribute-password-expiry', self.cfgLdapAttributePasswordExpiry)) self.cfgHostname = cfgJson.get('hostname', self.cfgHostname) - self.cfgPamGracePeriod = cfgJson.get('pam-grace-period', self.cfgPamGracePeriod) + self.cfgPamServices = cfgJson.get('pam-services', self.cfgPamServices) + self.cfgPamGracePeriod = int(cfgJson.get('pam-grace-period', self.cfgPamGracePeriod)) def main(): runner = LapsRunner() @@ -321,7 +324,6 @@ def main(): parser = argparse.ArgumentParser(epilog=__copyright__+' '+__author__+' - https://georg-sieber.de') parser.add_argument('-f', '--force', action='store_true', help='Force updating password, even if it is not expired') parser.add_argument('-p', '--pam', action='store_true', help='PAM mode - update password if configured user has logged out, even if it is not expired') - parser.add_argument('-s', '--pam-service', default=None, nargs='+', help='Only change password in PAM mode if PAM_SERVICE matches the given value (e.g. "login" or "sudo-i")') parser.add_argument('-c', '--config', default=runner.cfgPath, help='Path to config file ['+str(runner.cfgPath)+']') args = parser.parse_args() if args.config: runner.cfgPath = args.config @@ -343,10 +345,10 @@ def main(): runner.updatePassword() elif args.pam: - if 'PAM_TYPE' not in os.environ or 'PAM_USER' not in os.environ: - raise Exception('PAM_TYPE or PAM_USER missing!') - if args.pam_service and os.environ['PAM_SERVICE'] not in args.pam_service: - runner.logger.debug(__title__+': PAM_SERVICE "'+os.environ['PAM_SERVICE']+'" is not one of '+str(args.pam_service)+', exiting.') + if 'PAM_SERVICE' not in os.environ or 'PAM_USER' not in os.environ: + raise Exception('PAM_SERVICE or PAM_USER missing!') + if runner.cfgPamServices and os.environ['PAM_SERVICE'] not in runner.cfgPamServices: + runner.logger.debug(__title__+': PAM_SERVICE "'+os.environ['PAM_SERVICE']+'" is not one of '+str(runner.cfgPamServices)+', exiting.') sys.exit(0) if os.environ['PAM_USER'] != runner.cfgUsername: runner.logger.debug(__title__+': PAM_USER does not match the configured user, exiting.') @@ -354,7 +356,7 @@ def main(): if runner.cfgPamGracePeriod: runner.logger.debug(__title__+': PAM timeout - waiting '+str(runner.cfgPamGracePeriod)+' seconds...') time.sleep(runner.cfgPamGracePeriod) - print('Updating password (forced update by PAM logout)...') + print('Updating password (forced update by PAM)...') runner.updatePassword() else: