]> git.ipfire.org Git - pakfire.git/commitdiff
daemon: Configure Kerberos authentication
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 23 Jan 2025 23:05:40 +0000 (23:05 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 23 Jan 2025 23:08:18 +0000 (23:08 +0000)
This will make sure the daemon stays authenticated and it will have its
own credentials cache. We will register a timer so that we will
automatically refresh the credentials whenever it is time.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/pakfire/daemon.c

index fe47b92d7eda78a2b57c9bf8bd16aa6b7cc9edd9..f171881949ae350e6f8f3ff21fe63a39de967aa9 100644 (file)
@@ -23,6 +23,8 @@
 #include <errno.h>
 #include <stdlib.h>
 
+#include <krb5/krb5.h>
+
 #include <systemd/sd-daemon.h>
 #include <systemd/sd-event.h>
 
 #include <pakfire/string.h>
 #include <pakfire/util.h>
 
+// It would be nice store the credentials cache in memory, but on Debian,
+// cURL is compiled with Heimdal which is incompatible with the KRB5 in-memory cache.
+// The on-disk format is however is compatible between two implementations.
+#define KRB5_CREDENTIALS_CACHE "FILE:/tmp/krb5cc_pakfire-daemon"
+
+// The default keytab
+#define KRB5_DEFAULT_KEYTAB "/etc/krb5.keytab"
+
 #define MAX_JOBS 64
 
 struct pakfire_daemon {
@@ -50,6 +60,21 @@ struct pakfire_daemon {
        // Event Loop
        sd_event* loop;
 
+       // Kerberos Authentication
+       struct {
+               // Context
+               krb5_context ctx;
+
+               // Credentials Cache
+               krb5_ccache ccache;
+
+               // Host Princial
+               krb5_principal principal;
+       } krb5;
+
+       // Authentication Timer
+       sd_event_source* auth_timer;
+
        // The control connection
        struct pakfire_xfer* control;
 
@@ -595,6 +620,176 @@ static int pakfire_daemon_SIGCHLD(sd_event_source* s, const struct signalfd_sigi
        return 0;
 }
 
+static int pakfire_daemon_setup_krb5(struct pakfire_daemon* daemon) {
+       char hostname[HOST_NAME_MAX];
+       const char* error = NULL;
+       char* name = NULL;
+       int r;
+
+       // Fetch our own hostname
+       r = gethostname(hostname, sizeof(hostname));
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not fetch hostname: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Setup a Kerberos context
+       r = krb5_init_context(&daemon->krb5.ctx);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not initialize Kerberos: %s\n", error);
+               goto ERROR;
+       }
+
+       // Make the principal
+       r = krb5_sname_to_principal(daemon->krb5.ctx,
+                       hostname, "host", KRB5_NT_SRV_HST, &daemon->krb5.principal);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not generate the host principal: %s\n", error);
+               goto ERROR;
+       }
+
+       // Print the principal name
+       r = krb5_unparse_name(daemon->krb5.ctx, daemon->krb5.principal, &name);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not print the host principal: %s\n", error);
+               goto ERROR;
+       }
+
+       DEBUG(daemon->ctx, "Authenticating as: %s\n", name);
+
+       // Create a new credentials cache
+       r = krb5_cc_resolve(daemon->krb5.ctx, KRB5_CREDENTIALS_CACHE, &daemon->krb5.ccache);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not resolve the credentials cache: %s\n", error);
+               goto ERROR;
+       }
+
+       // Initialize the cache for the principal
+       r = krb5_cc_initialize(daemon->krb5.ctx, daemon->krb5.ccache, daemon->krb5.principal);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not initialize the credentials cache: %s\n", error);
+               goto ERROR;
+       }
+
+       // Set the credentials cache environment variable
+       r = setenv("KRB5CCNAME", KRB5_CREDENTIALS_CACHE, 1);
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not set KRB5CCNAME: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+ERROR:
+       if (error)
+               krb5_free_error_message(daemon->krb5.ctx, error);
+       if (name)
+               krb5_free_unparsed_name(daemon->krb5.ctx, name);
+
+       return r;
+}
+
+static int pakfire_daemon_auth(sd_event_source* s, uint64_t usec, void* data) {
+       struct pakfire_daemon* daemon = data;
+       krb5_get_init_creds_opt* options = NULL;
+       krb5_keytab keytab = NULL;
+       krb5_creds creds = {};
+       const char* error = NULL;
+       const char* path = NULL;
+       char time[128];
+       char* p = NULL;
+       int r;
+
+       DEBUG(daemon->ctx, "Authenticating...\n");
+
+       // XXX We should read this from the configuration
+       path = KRB5_DEFAULT_KEYTAB;
+
+       // Resolve the keytab
+       r = krb5_kt_resolve(daemon->krb5.ctx, path, &keytab);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not resolve the keytab: %s\n", error);
+               goto ERROR;
+       }
+
+       // Fetch the credentials using the keytab
+       r = krb5_get_init_creds_keytab(daemon->krb5.ctx, &creds, daemon->krb5.principal,
+                       keytab, 0, NULL, options);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not fetch credentials: %s\n", error);
+               goto ERROR;
+       }
+
+       // Determine the end time
+       time_t t_end = creds.times.endtime;
+
+       // Format the expiry time
+       p = ctime_r(&t_end, time);
+       if (!p) {
+               ERROR(daemon->ctx, "Could not format the expiry time: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+       DEBUG(daemon->ctx, "Successfully fetched credentials\n");
+       DEBUG(daemon->ctx, "  Expires: %s\n", time);
+
+       // Store the credentials in the cache
+       r = krb5_cc_store_cred(daemon->krb5.ctx, daemon->krb5.ccache, &creds);
+       if (r) {
+               error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+               ERROR(daemon->ctx, "Could not store credentials: %s\n", error);
+               goto ERROR;
+       }
+
+       // Refresh one hour before the expiry time
+       uint64_t t_refresh = (t_end - 3600) * 1000000;
+
+       // Authenticate again just before the credentials expire
+       r = sd_event_source_set_time(daemon->auth_timer, t_refresh);
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not reset the auth timer: %s\n", strerror(-r));
+               goto ERROR;
+       }
+
+       // Turn on the connection timer
+       r = sd_event_source_set_enabled(daemon->connect_timer, SD_EVENT_ONESHOT);
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not enable the connection timer: %s\n", strerror(-r));
+               goto ERROR;
+       }
+
+       /*
+               XXX This function needs some better error handling. In case the communication
+               with the Kerberos server fails, we should reschedule a call very soon (maybe
+               within a minute) and once the credentials have expired, we should stop the
+               (re-)connection timer.
+       */
+
+ERROR:
+       if (error)
+               krb5_free_error_message(daemon->krb5.ctx, error);
+       if (keytab)
+               krb5_kt_close(daemon->krb5.ctx, keytab);
+
+       return r;
+}
+
 static int pakfire_daemon_setup_loop(struct pakfire_daemon* daemon) {
        int r;
 
@@ -636,6 +831,21 @@ static int pakfire_daemon_setup_loop(struct pakfire_daemon* daemon) {
                return r;
        }
 
+       // Setup the authentication timer
+       r = sd_event_add_time_relative(daemon->loop, &daemon->auth_timer,
+               CLOCK_MONOTONIC, 0, 0, pakfire_daemon_auth, daemon);
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not register the authentication timer: %s\n", strerror(-r));
+               return r;
+       }
+
+       // Authenticate continuously
+       r = sd_event_source_set_enabled(daemon->auth_timer, SD_EVENT_ON);
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not activate the auth timer: %s\n", strerror(-r));
+               return r;
+       }
+
        // Setup the reconnection timer
        r = sd_event_add_time_relative(daemon->loop, &daemon->connect_timer,
                CLOCK_MONOTONIC, 0, 0, pakfire_daemon_connect, daemon);
@@ -644,6 +854,13 @@ static int pakfire_daemon_setup_loop(struct pakfire_daemon* daemon) {
                return r;
        }
 
+       // Disable the reconnection timer until we are authenticated
+       r = sd_event_source_set_enabled(daemon->connect_timer, SD_EVENT_OFF);
+       if (r < 0) {
+               ERROR(daemon->ctx, "Could not disable the connection timer: %s\n", strerror(-r));
+               return r;
+       }
+
        return 0;
 }
 
@@ -676,6 +893,15 @@ ERROR:
 }
 
 static void pakfire_daemon_free(struct pakfire_daemon* daemon) {
+       if (daemon->krb5.principal)
+               krb5_free_principal(daemon->krb5.ctx, daemon->krb5.principal);
+       if (daemon->krb5.ccache)
+               krb5_cc_close(daemon->krb5.ctx, daemon->krb5.ccache);
+       if (daemon->krb5.ctx)
+               krb5_free_context(daemon->krb5.ctx);
+
+       if (daemon->auth_timer)
+               sd_event_source_unref(daemon->auth_timer);
        if (daemon->connect_timer)
                sd_event_source_unref(daemon->connect_timer);
        if (daemon->stats_timer)
@@ -718,6 +944,11 @@ int pakfire_daemon_create(struct pakfire_daemon** daemon, struct pakfire_ctx* ct
        if (r)
                goto ERROR;
 
+       // Setup Kerberos Authentication
+       r = pakfire_daemon_setup_krb5(d);
+       if (r)
+               goto ERROR;
+
        // Create the cgroup
        r = pakfire_cgroup_create(&d->cgroup, d->ctx, NULL, "pakfire-daemon", 0);
        if (r < 0)