URL: https://github.com/freeipa/freeipa/pull/5307
Author: rcritten
 Title: #5307: Generate a unique cache for each connection
Action: opened

PR body:
"""
Generate a unique cache for each connection

Rather than having a shared ccache per user, configure
mod_auth_gssapi to create a unique one. This requires cleanup
to remove expired caches. A new script is added,
ipa-ccache-sweeper to do this. It will be invoked by a
new service, ipa-ccache-sweep, which will be executed every
12 hours by an equally-named timer.

https://pagure.io/freeipa/issue/8589
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/5307/head:pr5307
git checkout pr5307
From e13a091ea9c67ef817612b5205cab61ffeb9f667 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 20 Nov 2020 10:07:27 -0500
Subject: [PATCH 1/4] Convert reset_to_default_policy into a pytest fixture

This ensures that the ticket policy will be reset even on
failure.

https://pagure.io/freeipa/issue/8589

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 ipatests/test_integration/test_krbtpolicy.py | 24 ++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/ipatests/test_integration/test_krbtpolicy.py b/ipatests/test_integration/test_krbtpolicy.py
index 787568ed0d9..4644accfd6c 100644
--- a/ipatests/test_integration/test_krbtpolicy.py
+++ b/ipatests/test_integration/test_krbtpolicy.py
@@ -12,6 +12,8 @@
 import time
 from datetime import datetime
 
+import pytest
+
 from ipatests.test_integration.base import IntegrationTest
 from ipatests.test_integration.test_otp import add_otptoken, del_otptoken
 from ipatests.pytest_ipa.integration import tasks
@@ -43,11 +45,25 @@ def maxlife_within_policy(input, maxlife, slush=3600):
     return maxlife >= diff >= maxlife - slush
 
 
-def reset_to_default_policy(host, user):
+@pytest.fixture
+def reset_to_default_policy():
     """Reset default user authentication and user authentication type"""
+
+    state = dict()
+
+    def _reset_to_default_policy(host, user=None):
+        state['host'] = host
+        state['user'] = user
+
+    yield _reset_to_default_policy
+
+    host = state['host']
+    user = state['user']
     tasks.kinit_admin(host)
-    host.run_command(['ipa', 'user-mod', user, '--user-auth-type='])
     host.run_command(['ipa', 'krbtpolicy-reset', user])
+    if user:
+        host.run_command(['ipa', 'user-mod', user, '--user-auth-type='])
+        host.run_command(['ipa', 'krbtpolicy-reset', user])
 
 
 def kinit_check_life(master, user):
@@ -137,7 +153,7 @@ def test_krbtpolicy_reset(self):
         result = master.run_command('klist | grep krbtgt')
         assert maxlife_within_policy(result.stdout_text, MAXLIFE) is True
 
-    def test_krbtpolicy_otp(self):
+    def test_krbtpolicy_otp(self, reset_to_default_policy):
         """Test otp ticket policy"""
         master = self.master
         master.run_command(['ipa', 'user-mod', USER1,
@@ -149,6 +165,7 @@ def test_krbtpolicy_otp(self):
         armor = tasks.create_temp_file(self.master, create_file=False)
         otpuid, totp = add_otptoken(master, USER1, otptype="totp")
         otpvalue = totp.generate(int(time.time())).decode("ascii")
+        reset_to_default_policy(master, USER1)
         try:
             tasks.kdestroy_all(master)
             # create armor for FAST
@@ -174,7 +191,6 @@ def test_krbtpolicy_otp(self):
                                ok_returncode=1)
         finally:
             del_otptoken(master, otpuid)
-            reset_to_default_policy(master, USER1)
             self.master.run_command(['rm', '-f', armor])
             master.run_command(['ipa', 'config-mod', '--user-auth-type='])
 

From 84a52d55e47c8da1545c02fc2bf916855d852d62 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Thu, 19 Nov 2020 16:29:05 -0500
Subject: [PATCH 2/4] Generate a unique cache for each connection

Rather than having a shared ccache per user, configure
mod_auth_gssapi to create a unique one. This requires cleanup
to remove expired caches. A new script is added,
ipa-ccache-sweeper to do this. It will be invoked by a
new service, ipa-ccache-sweep, which will be executed every
12 hours by an equally-named timer.

https://pagure.io/freeipa/issue/8589

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 freeipa.spec.in                          |  3 ++
 init/systemd/Makefile.am                 |  4 ++
 init/systemd/ipa-ccache-sweep.service.in | 12 +++++
 init/systemd/ipa-ccache-sweep.timer.in   |  8 +++
 install/share/ipa.conf.template          |  4 +-
 install/tools/Makefile.am                |  2 +
 install/tools/ipa-ccache-sweeper.in      | 67 ++++++++++++++++++++++++
 7 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 init/systemd/ipa-ccache-sweep.service.in
 create mode 100644 init/systemd/ipa-ccache-sweep.timer.in
 create mode 100644 install/tools/ipa-ccache-sweeper.in

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 13b569abb6b..da36f79632f 100755
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -1239,6 +1239,7 @@ fi
 %{_sbindir}/ipa-acme-manage
 %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit
 %{_libexecdir}/certmonger/ipa-server-guard
+%{_libexecdir}/ipa/ipa-ccache-sweeper
 %{_libexecdir}/ipa/ipa-custodia
 %{_libexecdir}/ipa/ipa-custodia-check
 %{_libexecdir}/ipa/ipa-httpd-kdcproxy
@@ -1263,6 +1264,8 @@ fi
 %attr(644,root,root) %{_unitdir}/ipa.service
 %attr(644,root,root) %{_unitdir}/ipa-otpd.socket
 %attr(644,root,root) %{_unitdir}/ipa-otpd@.service
+%attr(644,root,root) %{_unitdir}/ipa-ccache-sweep.service
+%attr(644,root,root) %{_unitdir}/ipa-ccache-sweep.timer
 # END
 %attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so
 %attr(755,root,root) %{plugin_dir}/libipa_enrollment_extop.so
diff --git a/init/systemd/Makefile.am b/init/systemd/Makefile.am
index 175178787a8..8236075a210 100644
--- a/init/systemd/Makefile.am
+++ b/init/systemd/Makefile.am
@@ -7,11 +7,15 @@ NULL =
 dist_noinst_DATA = 			\
 	ipa-custodia.service.in		\
 	ipa.service.in			\
+	ipa-ccache-sweep.service.in	\
+	ipa-ccache-sweep.timer.in	\
 	$(NULL)
 
 systemdsystemunit_DATA = 	\
 	ipa-custodia.service	\
 	ipa.service				\
+	ipa-ccache-sweep.service	\
+	ipa-ccache-sweep.timer	\
 	$(NULL)
 
 CLEANFILES = $(systemdsystemunit_DATA)
diff --git a/init/systemd/ipa-ccache-sweep.service.in b/init/systemd/ipa-ccache-sweep.service.in
new file mode 100644
index 00000000000..0ba2661f99c
--- /dev/null
+++ b/init/systemd/ipa-ccache-sweep.service.in
@@ -0,0 +1,12 @@
+[Unit]
+Description=IPA Kerberos Ccache Sweeper Service
+Wants=gssproxy.service
+
+[Service]
+Type=simple
+ExecStart=@libexecdir@/ipa/ipa-ccache-sweeper
+PrivateTmp=yes
+User=ipaapi
+
+[Install]
+WantedBy=multi-user.target
diff --git a/init/systemd/ipa-ccache-sweep.timer.in b/init/systemd/ipa-ccache-sweep.timer.in
new file mode 100644
index 00000000000..84fe6e38389
--- /dev/null
+++ b/init/systemd/ipa-ccache-sweep.timer.in
@@ -0,0 +1,8 @@
+[Unit]
+Description=Remove Expired Kerberos Credential Caches
+
+[Timer]
+OnUnitActiveSec=12h
+
+[Install]
+WantedBy=timers.target
diff --git a/install/share/ipa.conf.template b/install/share/ipa.conf.template
index 07f39516de8..ca51d2ea304 100644
--- a/install/share/ipa.conf.template
+++ b/install/share/ipa.conf.template
@@ -1,5 +1,5 @@
 #
-# VERSION 31 - DO NOT REMOVE THIS LINE
+# VERSION 32 - DO NOT REMOVE THIS LINE
 #
 # This file may be overwritten on upgrades.
 #
@@ -76,6 +76,7 @@ WSGIScriptReloading Off
   GssapiImpersonate On
   GssapiDelegCcacheDir $IPA_CCACHES
   GssapiDelegCcachePerms mode:0660 gid:ipaapi
+  GssapiDelegCcacheUnique On
   GssapiUseS4U2Proxy on
   GssapiAllowedMech krb5
   Require valid-user
@@ -117,6 +118,7 @@ Alias /ipa/session/cookie "/usr/share/ipa/gssapi.login"
   AuthType none
   GssapiDelegCcacheDir $IPA_CCACHES
   GssapiDelegCcachePerms mode:0660 gid:ipaapi
+  GssapiDelegCcacheUnique On
   SSLVerifyClient require
   SSLUserName SSL_CLIENT_CERT
   LookupUserByCertificate On
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index bcf45fd206e..d6fbf9e3bc8 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -6,6 +6,7 @@ SUBDIRS = 			\
 
 dist_noinst_DATA =		\
 	ipa-ca-install.in		\
+	ipa-ccache-sweeper.in	\
 	ipa-dns-install.in		\
 	ipa-kra-install.in		\
 	ipa-server-install.in	\
@@ -70,6 +71,7 @@ nodist_sbin_SCRIPTS =		\
 
 appdir = $(libexecdir)/ipa/
 nodist_app_SCRIPTS =		\
+	ipa-ccache-sweeper	\
 	ipa-custodia		\
 	ipa-custodia-check	\
 	ipa-httpd-kdcproxy	\
diff --git a/install/tools/ipa-ccache-sweeper.in b/install/tools/ipa-ccache-sweeper.in
new file mode 100644
index 00000000000..33bd5f7fe5d
--- /dev/null
+++ b/install/tools/ipa-ccache-sweeper.in
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+
+# Based heavily on
+# https://github.com/gssapi/mod_auth_gssapi/blob/master/contrib/sweeper.py
+
+# Copyright (C) 2016 mod_auth_gssapi contributors - See COPYING for (C) terms
+
+# If one uses both sessions and unique ccache names, then the filesystem will
+# become littered with ccache files unless the accessed application cleans
+# them up itself.  This script will minimize ccache file proliferation by
+# removing any ccaches that have expired from the filesystem, and serves as an
+# example of how this cleaning can be performed.
+
+import argparse
+import os
+import stat
+import sys
+import time
+
+from gssapi.raw import acquire_cred_from
+from ipaplatform.paths import paths
+
+
+# process file as a ccache and indicate whether it is expired
+def should_delete(fname, t, minlife):
+    try:
+        # skip directories and other non-files
+        st = os.stat(fname)
+        if not stat.S_ISREG(st.st_mode):
+            return False
+
+        # ignore files that are newer than minlife minutes
+        if t - st.st_mtime < minlife * 60:
+            return False
+
+        creds = acquire_cred_from({b"ccache": fname.encode("UTF-8")})
+    except FileNotFoundError:
+        # someone else did the work for us
+        return False
+    except Exception as e:
+        print("Not deleting %s due to error %s" % (fname, e))
+        return False
+
+    return creds.lifetime == 0
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Sweep expired ccaches")
+    parser.add_argument("-m", dest="minlife", type=int,
+                        help="ignore newer files than this (default: 30)",
+                        default=30)
+    args = parser.parse_args()
+
+    os.environ["GSS_USE_PROXY"] = "yes"
+    os.environ["GSSPROXY_BEHAVIOR"] = "REMOTE_FIRST"
+
+    print("Running sweeper...")
+
+    t = time.time()
+
+    os.chdir(paths.IPA_CCACHES)
+    for fname in os.listdir(paths.IPA_CCACHES):
+        if should_delete(fname, t, args.minlife):
+            os.unlink(fname)
+
+    print("Sweeper finished successfully!")
+    sys.exit(0)

From 8d10fa23ce817266942be57ca1f22b5964369857 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 20 Nov 2020 10:09:16 -0500
Subject: [PATCH 3/4] ipatests: test that stale caches are removed using the
 sweeper

- Force wipe all existing ccaches
- Set the ticket policy to a short value, 30 seconds.
- Do a series of kinit, ipa command, kdestroy to generate ccaches
- sleep(30)
- Run the sweeper
- Verify that all ccaches are gone

https://pagure.io/freeipa/issue/8589

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 ipatests/test_integration/test_krbtpolicy.py | 38 +++++++++++++++++++-
 1 file changed, 37 insertions(+), 1 deletion(-)

diff --git a/ipatests/test_integration/test_krbtpolicy.py b/ipatests/test_integration/test_krbtpolicy.py
index 4644accfd6c..d212d1b509d 100644
--- a/ipatests/test_integration/test_krbtpolicy.py
+++ b/ipatests/test_integration/test_krbtpolicy.py
@@ -11,9 +11,10 @@
 import pytest
 import time
 from datetime import datetime
-
 import pytest
 
+from ipaplatform.paths import paths
+
 from ipatests.test_integration.base import IntegrationTest
 from ipatests.test_integration.test_otp import add_otptoken, del_otptoken
 from ipatests.pytest_ipa.integration import tasks
@@ -204,3 +205,38 @@ def test_krbtpolicy_jitter_otp(self):
                                  "--user-auth-type", "otp"])
         kinit_check_life(self.master, USER1)
         reset_to_default_policy(self.master, USER1)
+
+    def test_ccache_sweep(self, reset_to_default_policy):
+        """Test that the ccache sweeper works
+
+           - Force wipe all existing ccaches
+           - Set the ticket policy to a short value, 30 seconds.
+           - Do a series of kinit, ipa command, kdestroy to generate ccaches
+           - sleep()
+           - Run the sweeper
+           - Verify that all ccaches are gone
+        """
+        MAXLIFE = 20
+        reset_to_default_policy(self.master)  # this will reset at END of test
+        tasks.kinit_admin(self.master)
+        self.master.run_command(
+            ['ipa', 'krbtpolicy-mod', '--maxlife', str(MAXLIFE)]
+        )
+        tasks.kdestroy_all(self.master)
+        self.master.run_command(
+            ['find', paths.IPA_CCACHES, '-type', 'f', '-delete']
+        )
+        for _i in range(5):
+            tasks.kdestroy_all(self.master)
+            tasks.kinit_admin(self.master)
+            self.master.run_command(['ipa', 'user-show', 'admin'])
+        tasks.kdestroy_all(self.master)
+        time.sleep(MAXLIFE)
+        self.master.run_command(
+            ['/usr/libexec/ipa/ipa-ccache-sweeper', '-m', '0']
+        )
+        time.sleep(5)
+        result = self.master.run_command(
+            "ls -1 {0} | wc -l".format(paths.IPA_CCACHES)
+        )
+        assert int(result.stdout_text.strip()) == 0

From 8022fec986dc09b715c40b765cd06eb729f72058 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Thu, 19 Nov 2020 21:03:20 -0500
Subject: [PATCH 4/4] Enable the ccache sweep systemd timer

The associated service doesn't need to be enabled. Enabling the
timer is suffient for it to execute. It requires the timers
service so will be ready automatically to run on the configured
period.

https://pagure.io/freeipa/issue/8589

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 freeipa.spec.in | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index da36f79632f..c315b22b976 100755
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -993,6 +993,11 @@ if [  $? -eq 0 ]; then
     if [  $? -eq 0 ]; then
         /bin/systemctl restart ipa.service >/dev/null
     fi
+
+    /bin/systemctl is-enabled ipa-ccache-sweep.timer >/dev/null 2>&1
+    if [  $? -eq 1 ]; then
+        /bin/systemctl enable ipa-ccache-sweep.service >/dev/null
+    fi
 fi
 # END
 
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/freeipa-devel@lists.fedorahosted.org

Reply via email to