|
|
0b494d |
From 240617674f83305b2a27899aa83f6af0caa69c9c Mon Sep 17 00:00:00 2001
|
|
|
0b494d |
From: Christian Heimes <cheimes@redhat.com>
|
|
|
0b494d |
Date: Wed, 12 Jun 2019 22:02:52 +0200
|
|
|
0b494d |
Subject: [PATCH] Fix CustodiaClient ccache handling
|
|
|
0b494d |
|
|
|
0b494d |
A CustodiaClient object has to the process environment a bit, e.g. set
|
|
|
0b494d |
up GSSAPI credentials. To reuse the credentials in libldap connections,
|
|
|
0b494d |
it is also necessary to set up a custom ccache store and to set the
|
|
|
0b494d |
environment variable KRBCCNAME temporarily.
|
|
|
0b494d |
|
|
|
0b494d |
Fixes: https://pagure.io/freeipa/issue/7964
|
|
|
0b494d |
Co-Authored-By: Fraser Tweedale <ftweedal@redhat.com>
|
|
|
0b494d |
Signed-off-by: Christian Heimes <cheimes@redhat.com>
|
|
|
0b494d |
Reviewed-By: Christian Heimes <cheimes@redhat.com>
|
|
|
0b494d |
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
|
|
|
0b494d |
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
|
|
|
0b494d |
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
|
|
0b494d |
---
|
|
|
0b494d |
install/tools/ipa-pki-retrieve-key | 33 ++++---
|
|
|
0b494d |
ipaserver/secrets/client.py | 143 ++++++++++++++++-------------
|
|
|
0b494d |
2 files changed, 100 insertions(+), 76 deletions(-)
|
|
|
0b494d |
|
|
|
0b494d |
diff --git a/install/tools/ipa-pki-retrieve-key b/install/tools/ipa-pki-retrieve-key
|
|
|
0b494d |
index 5056682c3cdaa734be2dadcffd7de0b2d80afaf9..192022b9b40f076e88fd95d5cc8cf8305901dcf5 100755
|
|
|
0b494d |
--- a/install/tools/ipa-pki-retrieve-key
|
|
|
0b494d |
+++ b/install/tools/ipa-pki-retrieve-key
|
|
|
0b494d |
@@ -2,9 +2,8 @@
|
|
|
0b494d |
|
|
|
0b494d |
from __future__ import print_function
|
|
|
0b494d |
|
|
|
0b494d |
+import argparse
|
|
|
0b494d |
import os
|
|
|
0b494d |
-import sys
|
|
|
0b494d |
-import traceback
|
|
|
0b494d |
|
|
|
0b494d |
from ipalib import constants
|
|
|
0b494d |
from ipalib.config import Env
|
|
|
0b494d |
@@ -16,27 +15,37 @@ def main():
|
|
|
0b494d |
env = Env()
|
|
|
0b494d |
env._finalize()
|
|
|
0b494d |
|
|
|
0b494d |
- keyname = "ca_wrapped/" + sys.argv[1]
|
|
|
0b494d |
- servername = sys.argv[2]
|
|
|
0b494d |
+ parser = argparse.ArgumentParser("ipa-pki-retrieve-key")
|
|
|
0b494d |
+ parser.add_argument("keyname", type=str)
|
|
|
0b494d |
+ parser.add_argument("servername", type=str)
|
|
|
0b494d |
+
|
|
|
0b494d |
+ args = parser.parse_args()
|
|
|
0b494d |
+ keyname = "ca_wrapped/{}".format(args.keyname)
|
|
|
0b494d |
|
|
|
0b494d |
service = constants.PKI_GSSAPI_SERVICE_NAME
|
|
|
0b494d |
client_keyfile = os.path.join(paths.PKI_TOMCAT, service + '.keys')
|
|
|
0b494d |
client_keytab = os.path.join(paths.PKI_TOMCAT, service + '.keytab')
|
|
|
0b494d |
|
|
|
0b494d |
+ for filename in [client_keyfile, client_keytab]:
|
|
|
0b494d |
+ if not os.access(filename, os.R_OK):
|
|
|
0b494d |
+ parser.error(
|
|
|
0b494d |
+ "File '{}' missing or not readable.\n".format(filename)
|
|
|
0b494d |
+ )
|
|
|
0b494d |
+
|
|
|
0b494d |
# pylint: disable=no-member
|
|
|
0b494d |
client = CustodiaClient(
|
|
|
0b494d |
- client_service='%s@%s' % (service, env.host), server=servername,
|
|
|
0b494d |
- realm=env.realm, ldap_uri="ldaps://" + env.host,
|
|
|
0b494d |
- keyfile=client_keyfile, keytab=client_keytab,
|
|
|
0b494d |
- )
|
|
|
0b494d |
+ client_service="{}@{}".format(service, env.host),
|
|
|
0b494d |
+ server=args.servername,
|
|
|
0b494d |
+ realm=env.realm,
|
|
|
0b494d |
+ ldap_uri="ldaps://" + env.host,
|
|
|
0b494d |
+ keyfile=client_keyfile,
|
|
|
0b494d |
+ keytab=client_keytab,
|
|
|
0b494d |
+ )
|
|
|
0b494d |
|
|
|
0b494d |
# Print the response JSON to stdout; it is already in the format
|
|
|
0b494d |
# that Dogtag's ExternalProcessKeyRetriever expects
|
|
|
0b494d |
print(client.fetch_key(keyname, store=False))
|
|
|
0b494d |
|
|
|
0b494d |
|
|
|
0b494d |
-try:
|
|
|
0b494d |
+if __name__ == '__main__':
|
|
|
0b494d |
main()
|
|
|
0b494d |
-except BaseException:
|
|
|
0b494d |
- traceback.print_exc()
|
|
|
0b494d |
- sys.exit(1)
|
|
|
0b494d |
diff --git a/ipaserver/secrets/client.py b/ipaserver/secrets/client.py
|
|
|
0b494d |
index 16e7856185aa9786007d3b7f8be0652f70fb4518..40df6c4e69cd673dd8e3c36fbf33f2cda8544a67 100644
|
|
|
0b494d |
--- a/ipaserver/secrets/client.py
|
|
|
0b494d |
+++ b/ipaserver/secrets/client.py
|
|
|
0b494d |
@@ -1,93 +1,106 @@
|
|
|
0b494d |
# Copyright (C) 2015 IPA Project Contributors, see COPYING for license
|
|
|
0b494d |
|
|
|
0b494d |
from __future__ import print_function, absolute_import
|
|
|
0b494d |
+
|
|
|
0b494d |
+import contextlib
|
|
|
0b494d |
+import os
|
|
|
0b494d |
+from base64 import b64encode
|
|
|
0b494d |
+
|
|
|
0b494d |
+
|
|
|
0b494d |
# pylint: disable=relative-import
|
|
|
0b494d |
from custodia.message.kem import KEMClient, KEY_USAGE_SIG, KEY_USAGE_ENC
|
|
|
0b494d |
# pylint: enable=relative-import
|
|
|
0b494d |
from jwcrypto.common import json_decode
|
|
|
0b494d |
from jwcrypto.jwk import JWK
|
|
|
0b494d |
+from ipalib.krb_utils import krb5_format_service_principal_name
|
|
|
0b494d |
from ipaserver.secrets.kem import IPAKEMKeys
|
|
|
0b494d |
-from ipaserver.secrets.store import iSecStore
|
|
|
0b494d |
+from ipaserver.secrets.store import IPASecStore
|
|
|
0b494d |
from ipaplatform.paths import paths
|
|
|
0b494d |
-from base64 import b64encode
|
|
|
0b494d |
-import ldapurl
|
|
|
0b494d |
import gssapi
|
|
|
0b494d |
-import os
|
|
|
0b494d |
-import urllib3
|
|
|
0b494d |
import requests
|
|
|
0b494d |
|
|
|
0b494d |
|
|
|
0b494d |
-class CustodiaClient(object):
|
|
|
0b494d |
-
|
|
|
0b494d |
- def _client_keys(self):
|
|
|
0b494d |
- return self.ikk.server_keys
|
|
|
0b494d |
-
|
|
|
0b494d |
- def _server_keys(self, server, realm):
|
|
|
0b494d |
- principal = 'host/%s@%s' % (server, realm)
|
|
|
0b494d |
- sk = JWK(**json_decode(self.ikk.find_key(principal, KEY_USAGE_SIG)))
|
|
|
0b494d |
- ek = JWK(**json_decode(self.ikk.find_key(principal, KEY_USAGE_ENC)))
|
|
|
0b494d |
- return (sk, ek)
|
|
|
0b494d |
-
|
|
|
0b494d |
- def _ldap_uri(self, realm):
|
|
|
0b494d |
- dashrealm = '-'.join(realm.split('.'))
|
|
|
0b494d |
- socketpath = paths.SLAPD_INSTANCE_SOCKET_TEMPLATE % (dashrealm,)
|
|
|
0b494d |
- return 'ldapi://' + ldapurl.ldapUrlEscape(socketpath)
|
|
|
0b494d |
-
|
|
|
0b494d |
- def _keystore(self, realm, ldap_uri, auth_type):
|
|
|
0b494d |
- config = dict()
|
|
|
0b494d |
- if ldap_uri is None:
|
|
|
0b494d |
- config['ldap_uri'] = self._ldap_uri(realm)
|
|
|
0b494d |
- else:
|
|
|
0b494d |
- config['ldap_uri'] = ldap_uri
|
|
|
0b494d |
- if auth_type is not None:
|
|
|
0b494d |
- config['auth_type'] = auth_type
|
|
|
0b494d |
+@contextlib.contextmanager
|
|
|
0b494d |
+def ccache_env(ccache):
|
|
|
0b494d |
+ """Temporarily set KRB5CCNAME environment variable
|
|
|
0b494d |
+ """
|
|
|
0b494d |
+ orig_ccache = os.environ.get('KRB5CCNAME')
|
|
|
0b494d |
+ os.environ['KRB5CCNAME'] = ccache
|
|
|
0b494d |
+ try:
|
|
|
0b494d |
+ yield
|
|
|
0b494d |
+ finally:
|
|
|
0b494d |
+ os.environ.pop('KRB5CCNAME', None)
|
|
|
0b494d |
+ if orig_ccache is not None:
|
|
|
0b494d |
+ os.environ['KRB5CCNAME'] = orig_ccache
|
|
|
0b494d |
|
|
|
0b494d |
- return iSecStore(config)
|
|
|
0b494d |
|
|
|
0b494d |
- def __init__(
|
|
|
0b494d |
- self, client_service, keyfile, keytab, server, realm,
|
|
|
0b494d |
- ldap_uri=None, auth_type=None):
|
|
|
0b494d |
+class CustodiaClient(object):
|
|
|
0b494d |
+ def __init__(self, client_service, keyfile, keytab, server, realm,
|
|
|
0b494d |
+ ldap_uri=None, auth_type=None):
|
|
|
0b494d |
+ if client_service.endswith(realm) or "@" not in client_service:
|
|
|
0b494d |
+ raise ValueError(
|
|
|
0b494d |
+ "Client service name must be a GSS name (service@host), "
|
|
|
0b494d |
+ "not '{}'.".format(client_service)
|
|
|
0b494d |
+ )
|
|
|
0b494d |
self.client_service = client_service
|
|
|
0b494d |
self.keytab = keytab
|
|
|
0b494d |
-
|
|
|
0b494d |
- # Init creds immediately to make sure they are valid. Creds
|
|
|
0b494d |
- # can also be re-inited by _auth_header to avoid expiry.
|
|
|
0b494d |
- #
|
|
|
0b494d |
- self.creds = self.init_creds()
|
|
|
0b494d |
-
|
|
|
0b494d |
- self.service_name = gssapi.Name('HTTP@%s' % (server,),
|
|
|
0b494d |
- gssapi.NameType.hostbased_service)
|
|
|
0b494d |
self.server = server
|
|
|
0b494d |
+ self.realm = realm
|
|
|
0b494d |
+ self.ldap_uri = ldap_uri
|
|
|
0b494d |
+ self.auth_type = auth_type
|
|
|
0b494d |
+ self.service_name = gssapi.Name(
|
|
|
0b494d |
+ 'HTTP@{}'.format(server), gssapi.NameType.hostbased_service
|
|
|
0b494d |
+ )
|
|
|
0b494d |
+ self.keystore = IPASecStore()
|
|
|
0b494d |
+ # use in-process MEMORY ccache. Handler process don't need a TGT.
|
|
|
0b494d |
+ token = b64encode(os.urandom(8)).decode('ascii')
|
|
|
0b494d |
+ self.ccache = 'MEMORY:Custodia_{}'.format(token)
|
|
|
0b494d |
+
|
|
|
0b494d |
+ with ccache_env(self.ccache):
|
|
|
0b494d |
+ # Init creds immediately to make sure they are valid. Creds
|
|
|
0b494d |
+ # can also be re-inited by _auth_header to avoid expiry.
|
|
|
0b494d |
+ self.creds = self._init_creds()
|
|
|
0b494d |
+
|
|
|
0b494d |
+ self.ikk = IPAKEMKeys(
|
|
|
0b494d |
+ {'server_keys': keyfile, 'ldap_uri': ldap_uri}
|
|
|
0b494d |
+ )
|
|
|
0b494d |
+ self.kemcli = KEMClient(
|
|
|
0b494d |
+ self._server_keys(), self._client_keys()
|
|
|
0b494d |
+ )
|
|
|
0b494d |
|
|
|
0b494d |
- self.ikk = IPAKEMKeys({'server_keys': keyfile, 'ldap_uri': ldap_uri})
|
|
|
0b494d |
-
|
|
|
0b494d |
- self.kemcli = KEMClient(self._server_keys(server, realm),
|
|
|
0b494d |
- self._client_keys())
|
|
|
0b494d |
-
|
|
|
0b494d |
- self.keystore = self._keystore(realm, ldap_uri, auth_type)
|
|
|
0b494d |
-
|
|
|
0b494d |
- # FIXME: Remove warnings about missing subjAltName for the
|
|
|
0b494d |
- # requests module
|
|
|
0b494d |
- urllib3.disable_warnings()
|
|
|
0b494d |
+ def _client_keys(self):
|
|
|
0b494d |
+ return self.ikk.server_keys
|
|
|
0b494d |
|
|
|
0b494d |
- def init_creds(self):
|
|
|
0b494d |
- name = gssapi.Name(self.client_service,
|
|
|
0b494d |
- gssapi.NameType.hostbased_service)
|
|
|
0b494d |
- store = {'client_keytab': self.keytab,
|
|
|
0b494d |
- 'ccache': 'MEMORY:Custodia_%s' % b64encode(
|
|
|
0b494d |
- os.urandom(8)).decode('ascii')}
|
|
|
0b494d |
+ def _server_keys(self):
|
|
|
0b494d |
+ principal = krb5_format_service_principal_name(
|
|
|
0b494d |
+ 'host', self.server, self.realm
|
|
|
0b494d |
+ )
|
|
|
0b494d |
+ sk = JWK(**json_decode(self.ikk.find_key(principal, KEY_USAGE_SIG)))
|
|
|
0b494d |
+ ek = JWK(**json_decode(self.ikk.find_key(principal, KEY_USAGE_ENC)))
|
|
|
0b494d |
+ return sk, ek
|
|
|
0b494d |
+
|
|
|
0b494d |
+ def _init_creds(self):
|
|
|
0b494d |
+ name = gssapi.Name(
|
|
|
0b494d |
+ self.client_service, gssapi.NameType.hostbased_service
|
|
|
0b494d |
+ )
|
|
|
0b494d |
+ store = {
|
|
|
0b494d |
+ 'client_keytab': self.keytab,
|
|
|
0b494d |
+ 'ccache': self.ccache
|
|
|
0b494d |
+ }
|
|
|
0b494d |
return gssapi.Credentials(name=name, store=store, usage='initiate')
|
|
|
0b494d |
|
|
|
0b494d |
def _auth_header(self):
|
|
|
0b494d |
- if not self.creds or self.creds.lifetime < 300:
|
|
|
0b494d |
- self.creds = self.init_creds()
|
|
|
0b494d |
- ctx = gssapi.SecurityContext(name=self.service_name, creds=self.creds)
|
|
|
0b494d |
+ if self.creds.lifetime < 300:
|
|
|
0b494d |
+ self.creds = self._init_creds()
|
|
|
0b494d |
+ ctx = gssapi.SecurityContext(
|
|
|
0b494d |
+ name=self.service_name,
|
|
|
0b494d |
+ creds=self.creds
|
|
|
0b494d |
+ )
|
|
|
0b494d |
authtok = ctx.step()
|
|
|
0b494d |
return {'Authorization': 'Negotiate %s' % b64encode(
|
|
|
0b494d |
authtok).decode('ascii')}
|
|
|
0b494d |
|
|
|
0b494d |
def fetch_key(self, keyname, store=True):
|
|
|
0b494d |
-
|
|
|
0b494d |
# Prepare URL
|
|
|
0b494d |
url = 'https://%s/ipa/keys/%s' % (self.server, keyname)
|
|
|
0b494d |
|
|
|
0b494d |
@@ -99,9 +112,11 @@ class CustodiaClient(object):
|
|
|
0b494d |
headers = self._auth_header()
|
|
|
0b494d |
|
|
|
0b494d |
# Perform request
|
|
|
0b494d |
- r = requests.get(url, headers=headers,
|
|
|
0b494d |
- verify=paths.IPA_CA_CRT,
|
|
|
0b494d |
- params={'type': 'kem', 'value': request})
|
|
|
0b494d |
+ r = requests.get(
|
|
|
0b494d |
+ url, headers=headers,
|
|
|
0b494d |
+ verify=paths.IPA_CA_CRT,
|
|
|
0b494d |
+ params={'type': 'kem', 'value': request}
|
|
|
0b494d |
+ )
|
|
|
0b494d |
r.raise_for_status()
|
|
|
0b494d |
reply = r.json()
|
|
|
0b494d |
|
|
|
0b494d |
--
|
|
|
0b494d |
2.20.1
|
|
|
0b494d |
|