]> git.ipfire.org Git - people/stevee/pakfire.git/commitdiff
client: Perform Kerberos authentication against the hub
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 5 Oct 2022 15:31:37 +0000 (15:31 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 5 Oct 2022 15:31:37 +0000 (15:31 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
configure.ac
contrib/config/client.conf
src/pakfire/client.py
src/pakfire/hub.py
src/scripts/pakfire-client.in

index f33a66b73a38d260a4793c9e6538dcd250623bdd..60a7245d54fbbe28fc713ae888f472c691cf7d27 100644 (file)
@@ -245,6 +245,7 @@ AC_CHECK_FUNCS([ \
 AM_PATH_PYTHON([3.6])
 
 AX_PYTHON_MODULE([cpuinfo], [fatal])
+AX_PYTHON_MODULE([kerberos], [fatal])
 AX_PYTHON_MODULE([psutil], [fatal])
 AX_PYTHON_MODULE([setproctitle], [fatal])
 AX_PYTHON_MODULE([systemd], [fatal])
index 6c59de98cceb9f62858f873e9ce522a3477a32c0..e9d4ae7859d8a57f8529f455cc6b82a5a654fdf9 100644 (file)
@@ -5,6 +5,5 @@
 # The URL of the server to connect to.
 # server = https://pakfirehub.ipfire.org/
 
-# Your credentials to log in on the hub.
-# username = ipfire
-# password = 1234...
+# Your credentials to log in on the hub
+# keytab = /etc/pakfire/pakfire.keytab (defaults to /etc/krb5.keytab)
index d248e7792d2253e4bcd50293a79ba2781e16be4f..9eb4764d4064835ae8273f821efdbcfb1e103cf9 100644 (file)
@@ -41,21 +41,17 @@ class Client(object):
        def connect_to_hub(self):
                huburl = self.config.get("client", "server", PAKFIRE_HUB)
 
-               # User Credentials
-               username = self.config.get("client", "username")
-               password = self.config.get("client", "password")
+               # keytab
+               keytab = self.config.get("client", "keytab")
 
-               if not (username and password):
-                       raise RuntimeError("User credentials are not set")
+               # Create a connection to the hub
+               return hub.Hub(huburl, keytab=keytab)
 
-               # Create connection to the hub.
-               return hub.Hub(huburl, username, password)
-
-       def check_connection(self):
+       async def check_connection(self):
                """
                        Tests the connection to the hub
                """
-               return self.hub.test()
+               return await self.hub.test()
 
        # Builds
 
index d995bc72271194069f620958e2bde3a99ee830cf..cbf116e4885e387df74a033cb973a40f1dadec83 100644 (file)
@@ -23,6 +23,7 @@ import cpuinfo
 import functools
 import hashlib
 import json
+import kerberos
 import logging
 import os.path
 import psutil
@@ -39,6 +40,8 @@ from .i18n import _
 # Setup logging
 log = logging.getLogger("pakfire.hub")
 
+DEFAULT_KEYTAB = "/etc/krb5.keytab"
+
 # Configure some useful defaults for all requests
 tornado.httpclient.AsyncHTTPClient.configure(
        None, defaults = {
@@ -47,18 +50,21 @@ tornado.httpclient.AsyncHTTPClient.configure(
 )
 
 class Hub(object):
-       def __init__(self, url, username, password):
+       def __init__(self, url, keytab=None):
                self.url = url
-               self.username = username
-               self.password = password
+
+               # Store path to keytab
+               self.keytab = keytab or DEFAULT_KEYTAB
 
                # Initialise the HTTP client
                self.client = tornado.httpclient.AsyncHTTPClient()
 
                # XXX support proxies
 
-       async def _request(self, method, path, websocket=False,
+       async def _request(self, method, path, websocket=False, authenticate=True,
                        body=None, body_producer=None, on_message_callback=None, **kwargs):
+               headers = {}
+
                # Make absolute URL
                url = urllib.parse.urljoin(self.url, path)
 
@@ -78,14 +84,21 @@ class Hub(object):
                        if body is None:
                                body = query_args
 
+               # Perform Kerberos authentication
+               if authenticate:
+                       krb5_context = self._setup_krb5_context(url)
+
+                       # Set the Negotiate header
+                       headers |= {
+                               "Authorization" :
+                                       "Negotiate %s" % kerberos.authGSSClientResponse(krb5_context),
+                       }
+
                # Make the request
                req = tornado.httpclient.HTTPRequest(
-                       method=method, url=url, body=body,
+                       method=method, url=url, headers=headers, body=body,
 
-                       # Authentication
-                       auth_username=self.username, auth_password=self.password,
-
-                       # All all the rest
+                       # Add all the rest
                        body_producer=body_producer,
                )
 
@@ -108,18 +121,40 @@ class Hub(object):
                # Empty response
                return {}
 
+       def _setup_krb5_context(self, url):
+               """
+                       Creates the Kerberos context that can be used to perform client
+                       authentication against the server, and mutual authentication for the server.
+               """
+               # Parse the input URL
+               url = urllib.parse.urlparse(url)
+
+               # Create a new client context
+               result, krb5_context = kerberos.authGSSClientInit("HTTP@%s" % url.hostname)
+
+               if not result == kerberos.AUTH_GSS_COMPLETE:
+                       raise RuntimeError("Could not create Kerberos Client context")
+
+               # Next step...
+               try:
+                       result = kerberos.authGSSClientStep(krb5_context, "")
+
+               except kerberos.GSSError as e:
+                       log.error("Kerberos authentication failed: %s" % e)
+                       raise e
+
+               if not result == kerberos.AUTH_GSS_CONTINUE:
+                       raise RuntimeError("Cloud not continue Kerberos authentication")
+
+               return krb5_context
+
        # Test functions
 
-       def test(self):
+       async def test(self):
                """
                        Tests the connection
                """
-               return self._request("/noop", decode="ascii")
-
-       def test_error(self, code):
-               assert code >= 100 and code <= 999
-
-               self._request("/error/test/%s" % code)
+               return await self._request("GET", "/test")
 
        # Build actions
 
index ffd3206d76635c1dbd4aac60df415d0c5fa77859..1fcf261b191c7789eddbb89aa6a0edfb30fb6e36 100644 (file)
@@ -114,10 +114,13 @@ class Cli(object):
                        tmp.cleanup()
 
        async def _check_connection(self, client, ns):
-               success = client.check_connection()
+               response = await client.check_connection()
 
-               if success:
-                       print("%s: %s" % (_("Connection OK"), success))
+               # Print the response from the service
+               try:
+                       print("\n".join(response["message"]))
+               except KeyError:
+                       pass
 
        async def _upload(self, client, ns):
                for file in ns.file: