Modify MBS to use a separate Kerberos context per thread so both threads can use the thread keyring to store the Kerberos cache

This commit includes the backport of the changes to `krb_login` in
https://pagure.io/koji/pull-request/1187. This change is required
for our separate threads to use a separate Kerberos context per thread.
This commit is contained in:
mprahl
2018-12-13 14:24:32 -05:00
parent e17fdbdb5a
commit 25122cb53e
8 changed files with 141 additions and 36 deletions

View File

@@ -49,6 +49,7 @@ from module_build_service import log, conf, models
import module_build_service.scm
import module_build_service.utils
from module_build_service.builder.utils import execute_cmd
from module_build_service.builder.koji_backports import ClientSession as KojiClientSession
from module_build_service.errors import ProgrammingError
from module_build_service.builder.base import GenericBuilder
@@ -455,7 +456,7 @@ chmod 644 %buildroot/etc/rpm/macros.zz-modules
address = koji_config.server
log.info("Connecting to koji %r.", address)
koji_session = koji.ClientSession(address, opts=koji_config)
koji_session = KojiClientSession(address, opts=koji_config)
if not login:
return koji_session
@@ -463,13 +464,23 @@ chmod 644 %buildroot/etc/rpm/macros.zz-modules
authtype = koji_config.authtype
log.info("Authenticate session with %r.", authtype)
if authtype == "kerberos":
try:
import krbV
except ImportError:
raise RuntimeError(
"python-krbV must be installed to authenticate with Koji using Kerberos")
keytab = getattr(config, "krb_keytab", None)
principal = getattr(config, "krb_principal", None)
if not keytab and principal:
raise ValueError(
"The Kerberos keytab and principal aren't set for Koji authentication")
log.debug(" keytab: %r, principal: %r" % (keytab, principal))
koji_session.krb_login(principal=principal, keytab=keytab)
# We want to create a context per thread to avoid Kerberos cache corruption
ctx = krbV.Context()
# We want to use the thread keyring for the ccache to ensure we have one cache per
# thread to avoid Kerberos cache corruption
ccache = "KEYRING:thread:mbs"
koji_session.krb_login(principal=principal, keytab=keytab, ctx=ctx, ccache=ccache)
elif authtype == "ssl":
koji_session.ssl_login(
os.path.expanduser(koji_config.cert),

View File

@@ -0,0 +1,98 @@
# flake8: noqa
import base64
import traceback
import koji
# Import krbV from here so we don't have to redo the whole try except that Koji does
from koji import krbV, PythonImportError, AuthError, AUTHTYPE_KERB
class ClientSession(koji.ClientSession):
"""The koji.ClientSession class with patches from upstream."""
# This backport comes from https://pagure.io/koji/pull-request/1187
def krb_login(self, principal=None, keytab=None, ccache=None, proxyuser=None, ctx=None):
"""Log in using Kerberos. If principal is not None and keytab is
not None, then get credentials for the given principal from the given keytab.
If both are None, authenticate using existing local credentials (as obtained
from kinit). ccache is the absolute path to use for the credential cache. If
not specified, the default ccache will be used. If proxyuser is specified,
log in the given user instead of the user associated with the Kerberos
principal. The principal must be in the "ProxyPrincipals" list on
the server side. ctx is the Kerberos context to use, and should be unique
per thread. If ctx is not specified, the default context is used."""
try:
# Silently try GSSAPI first
if self.gssapi_login(principal, keytab, ccache, proxyuser=proxyuser):
return True
except Exception as e:
if krbV:
e_str = ''.join(traceback.format_exception_only(type(e), e))
self.logger.debug('gssapi auth failed: %s', e_str)
pass
else:
raise
if not krbV:
raise PythonImportError(
"Please install python-krbV to use kerberos."
)
if not ctx:
ctx = krbV.default_context()
if ccache != None:
ccache = krbV.CCache(name=ccache, context=ctx)
else:
ccache = ctx.default_ccache()
if principal != None:
if keytab != None:
cprinc = krbV.Principal(name=principal, context=ctx)
keytab = krbV.Keytab(name=keytab, context=ctx)
ccache.init(cprinc)
ccache.init_creds_keytab(principal=cprinc, keytab=keytab)
else:
raise AuthError('cannot specify a principal without a keytab')
else:
# We're trying to log ourself in. Connect using existing credentials.
cprinc = ccache.principal()
self.logger.debug('Authenticating as: %s', cprinc.name)
sprinc = krbV.Principal(name=self._serverPrincipal(cprinc), context=ctx)
ac = krbV.AuthContext(context=ctx)
ac.flags = krbV.KRB5_AUTH_CONTEXT_DO_SEQUENCE | krbV.KRB5_AUTH_CONTEXT_DO_TIME
ac.rcache = ctx.default_rcache()
# create and encode the authentication request
(ac, req) = ctx.mk_req(server=sprinc, client=cprinc,
auth_context=ac, ccache=ccache,
options=krbV.AP_OPTS_MUTUAL_REQUIRED)
req_enc = base64.encodestring(req)
# ask the server to authenticate us
(rep_enc, sinfo_enc, addrinfo) = self.callMethod('krbLogin', req_enc, proxyuser)
# Set the addrinfo we received from the server
# (necessary before calling rd_priv())
# addrinfo is in (serveraddr, serverport, clientaddr, clientport)
# format, so swap the pairs because clientaddr is now the local addr
ac.addrs = tuple((addrinfo[2], addrinfo[3], addrinfo[0], addrinfo[1]))
# decode and read the reply from the server
rep = base64.decodestring(rep_enc)
ctx.rd_rep(rep, auth_context=ac)
# decode and decrypt the login info
sinfo_priv = base64.decodestring(sinfo_enc)
sinfo_str = ac.rd_priv(sinfo_priv)
sinfo = dict(zip(['session-id', 'session-key'], sinfo_str.split()))
if not sinfo:
self.logger.warn('No session info received')
return False
self.setSession(sinfo)
self.authtype = AUTHTYPE_KERB
return True