ccache_sweeper: Add gssproxy service

The usage of the existing gssproxy service(`service/ipa-api`) leads
to undesirable for this case side effects such as auto renew of
expired credentials.

Fixes: https://pagure.io/freeipa/issue/8735
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Stanislav Levin 2021-03-01 17:44:31 +03:00 committed by Alexander Bokovoy
parent 216720af83
commit 271fd162a7
6 changed files with 109 additions and 34 deletions

View File

@ -16,3 +16,10 @@
allow_client_ccache_sync = true
cred_usage = initiate
euid = $IPAAPI_USER
[service/ipa-sweeper]
mechs = krb5
cred_store = keytab:$HTTP_KEYTAB
socket = $SWEEPER_SOCKET
euid = $IPAAPI_USER
cred_usage = initiate

View File

@ -17,31 +17,39 @@ import stat
import sys
import time
from gssapi.raw import acquire_cred_from
from ipalib.krb_utils import get_credentials_if_valid
from ipaplatform.paths import paths
# process file as a ccache and indicate whether it is expired
def should_delete(fname, t, minlife):
"""Process file as a ccache and indicate whether it is expired"""
# 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
# gssproxy inquires input credentials. If they are expired
# then gssproxy acquires creds from cred_store according to
# the configuration of gssproxy's service, which in this case
# hasn't cred_store(besides `keytab:`, used for decryption of
# ccache). If there is no ccache within cred_store then gssproxy
# adds its own one("MEMORY:internal_%d"), which hasn't
# any credentials, thus, scan_ccache fails with KRB5_FCC_NOFILE.
# Since the caller requires INITIATE-ONLY and the client keytab
# is not provided in cred_store the result of gss_acquire_cred_from
# is KRB5_FCC_NOFILE, which is mapped by gssproxy to
# 0x04200000 + KRB5_FCC_NOFILE.
try:
# skip directories and other non-files
st = os.stat(fname)
if not stat.S_ISREG(st.st_mode):
return False
creds = get_credentials_if_valid(ccache_name=fname)
return creds is None
except ValueError:
return True
# 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
return False
if __name__ == "__main__":
@ -52,7 +60,8 @@ if __name__ == "__main__":
args = parser.parse_args()
os.environ["GSS_USE_PROXY"] = "yes"
os.environ["GSSPROXY_BEHAVIOR"] = "REMOTE_FIRST"
os.environ["GSSPROXY_BEHAVIOR"] = "REMOTE_ONLY"
os.environ["GSSPROXY_SOCKET"] = paths.IPA_CCACHE_SWEEPER_GSSPROXY_SOCK
print("Running sweeper...")
@ -60,8 +69,12 @@ if __name__ == "__main__":
os.chdir(paths.IPA_CCACHES)
for fname in os.listdir(paths.IPA_CCACHES):
if should_delete(fname, t, args.minlife):
os.unlink(fname)
try:
if should_delete(fname, t, args.minlife):
os.unlink(fname)
except FileNotFoundError:
# someone else did the work for us
pass
print("Sweeper finished successfully!")
sys.exit(0)

View File

@ -39,6 +39,12 @@ KRB5_FCC_PERM = 2529639106 # Credentials cache permissions inc
KRB5_CC_FORMAT = 2529639111 # Bad format in credentials cache
KRB5_REALM_CANT_RESOLVE = 2529639132 # Cannot resolve network address for KDC in requested realm
# mechglue/gss_plugin.c: #define MAP_ERROR_BASE 0x04200000
GSSPROXY_MAP_ERROR_BASE = 69206016
# GSSProxy error codes
GSSPROXY_KRB5_FCC_NOFILE = GSSPROXY_MAP_ERROR_BASE + KRB5_FCC_NOFILE
krb_ticket_expiration_threshold = 60*5 # number of seconds to accmodate clock skew
krb5_time_fmt = '%m/%d/%y %H:%M:%S'
ccache_name_re = re.compile(r'^((\w+):)?(.+)')
@ -146,7 +152,9 @@ def get_credentials(name=None, ccache_name=None):
try:
return gssapi.Credentials(usage='initiate', name=name, store=store)
except gssapi.exceptions.GSSError as e:
if e.min_code == KRB5_FCC_NOFILE: # pylint: disable=no-member
if e.min_code in ( # pylint: disable=no-member
KRB5_FCC_NOFILE, GSSPROXY_KRB5_FCC_NOFILE,
):
raise ValueError('"%s", ccache="%s"' % (e, ccache_name))
raise

View File

@ -451,6 +451,9 @@ class BasePathNamespace:
TDBTOOL = '/usr/bin/tdbtool'
SECRETS_TDB = '/var/lib/samba/private/secrets.tdb'
LETS_ENCRYPT_LOG = '/var/log/letsencrypt/letsencrypt.log'
IPA_CCACHE_SWEEPER_GSSPROXY_SOCK = (
"/var/lib/gssproxy/ipa_ccache_sweeper.sock"
)
def check_paths(self):
"""Check paths for missing files

View File

@ -522,6 +522,7 @@ class RedHatTaskNamespace(BaseTaskNamespace):
HTTP_CCACHE=paths.HTTP_CCACHE,
HTTPD_USER=constants.HTTPD_USER,
IPAAPI_USER=ipaapi_user,
SWEEPER_SOCKET=paths.IPA_CCACHE_SWEEPER_GSSPROXY_SOCK,
)
)

View File

@ -12,6 +12,7 @@ import pytest
import time
from datetime import datetime
from ipalib.constants import IPAAPI_USER
from ipaplatform.paths import paths
from ipatests.test_integration.base import IntegrationTest
@ -205,15 +206,15 @@ class TestPWPolicy(IntegrationTest):
"--user-auth-type", "otp"])
kinit_check_life(self.master, USER1)
def test_ccache_sweep(self, reset_to_default_policy):
"""Test that the ccache sweeper works
def test_ccache_sweep_expired(self, reset_to_default_policy):
"""Test that the ccache sweeper works on expired ccaches
- Force wipe all existing ccaches
- Set the ticket policy to a short value, 30 seconds.
- Set the ticket policy to a short value, 20 seconds.
- Do a series of kinit, ipa command, kdestroy to generate ccaches
- sleep()
- sleep() for expiration
- Run the sweeper
- Verify that all ccaches are gone
- Verify that all expired ccaches are gone
"""
MAXLIFE = 20
reset_to_default_policy(self.master) # this will reset at END of test
@ -226,16 +227,58 @@ class TestPWPolicy(IntegrationTest):
['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']
tasks.kdestroy_all(self.master)
result = self.master.run_command(
"ls -1 {0} | wc -l".format(paths.IPA_CCACHES)
)
time.sleep(5)
assert int(result.stdout_text.strip()) == 5
# let ccache expire
time.sleep(MAXLIFE)
ccache_sweep_cmd = ["/usr/libexec/ipa/ipa-ccache-sweeper", "-m", "0"]
# should be run as ipaapi for GSSProxy
self.master.run_command(
["runuser", "-u", IPAAPI_USER, "--"] + ccache_sweep_cmd
)
result = self.master.run_command(
"ls -1 {0} | wc -l".format(paths.IPA_CCACHES)
)
assert int(result.stdout_text.strip()) == 0
def test_ccache_sweep_valid(self):
"""Test that the ccache sweeper doesn't remove valid ccaches
- Force wipe all existing ccaches
- Run the sweeper
- Verify that all valid ccaches weren't removed
Note: assumed that ccache expiration doesn't happen during test
"""
tasks.kdestroy_all(self.master)
self.master.run_command(
["find", paths.IPA_CCACHES, "-type", "f", "-delete"]
)
for _i in range(5):
tasks.kinit_admin(self.master)
self.master.run_command(["ipa", "user-show", "admin"])
tasks.kdestroy_all(self.master)
result = self.master.run_command(
"ls -1 {0} | wc -l".format(paths.IPA_CCACHES)
)
assert int(result.stdout_text.strip()) == 5
ccache_sweep_cmd = ["/usr/libexec/ipa/ipa-ccache-sweeper", "-m", "0"]
# should be run as ipaapi for GSSProxy
self.master.run_command(
["runuser", "-u", IPAAPI_USER, "--"] + ccache_sweep_cmd
)
result = self.master.run_command(
"ls -1 {0} | wc -l".format(paths.IPA_CCACHES)
)
assert int(result.stdout_text.strip()) == 5