From: Michael Tremer Date: Thu, 23 Jan 2025 23:05:40 +0000 (+0000) Subject: daemon: Configure Kerberos authentication X-Git-Tag: 0.9.30~391 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=628946e1b6007b4536c803bad2944a859bdec81f;p=pakfire.git daemon: Configure Kerberos authentication 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 --- diff --git a/src/pakfire/daemon.c b/src/pakfire/daemon.c index fe47b92d..f1718819 100644 --- a/src/pakfire/daemon.c +++ b/src/pakfire/daemon.c @@ -23,6 +23,8 @@ #include #include +#include + #include #include @@ -35,6 +37,14 @@ #include #include +// 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)