krb5 commit: Add support for systemd socket activation
ghudson at mit.edu
ghudson at mit.edu
Mon Feb 3 18:55:42 EST 2025
https://github.com/krb5/krb5/commit/11b9a8244f1b3f5226f315e998f4e892b262e46e
commit 11b9a8244f1b3f5226f315e998f4e892b262e46e
Author: Andreas Schneider <asn at samba.org>
Date: Wed Nov 20 18:17:29 2024 +0100
Add support for systemd socket activation
If LISTEN_PID and LISTEN_FDS are set in the environment, expect
listener sockets to be present at fds starting at 3. If any match a
configured listener address and port, use it instead of creating a new
socket.
[ghudson at mit.edu: combined two commits; changed sa_compare() to
sa_equal(); restructured changes to fetch listener sockets into a list
once and to match on socket type as well as address; added tests]
ticket: 9157 (new)
.gitignore | 1 +
doc/admin/admin_commands/kadmind.rst | 8 ++
doc/admin/admin_commands/krb5kdc.rst | 7 ++
src/include/socket-utils.h | 35 +++++++++
src/kdc/Makefile.in | 10 ++-
src/kdc/deps | 10 +++
src/kdc/t_sockact.c | 121 +++++++++++++++++++++++++++++
src/kdc/t_sockact.py | 43 +++++++++++
src/lib/apputils/net-server.c | 144 ++++++++++++++++++++++++++++++-----
9 files changed, 358 insertions(+), 21 deletions(-)
diff --git a/.gitignore b/.gitignore
index a7a217a6f..ae6780c37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -258,6 +258,7 @@ local.properties
/src/kdc/rtest
/src/kdc/t_ndr
/src/kdc/t_replay
+/src/kdc/t_sockact
/src/lib/k5sprt32.def
diff --git a/doc/admin/admin_commands/kadmind.rst b/doc/admin/admin_commands/kadmind.rst
index 7e1482635..bc66890de 100644
--- a/doc/admin/admin_commands/kadmind.rst
+++ b/doc/admin/admin_commands/kadmind.rst
@@ -121,6 +121,14 @@ ENVIRONMENT
See :ref:`kerberos(7)` for a description of Kerberos environment
variables.
+As of release 1.22, kadmind supports systemd socket activation via the
+LISTEN_PID and LISTEN_FDS environment variables. Sockets provided by
+the caller must correspond to configured listener addresses (via the
+**kadmind_listen** or **kpasswd_listen** variables or equivalents) or
+they will be ignored. Any configured listener addresses that do not
+correspond to caller-provided sockets will be ignored if socket
+activation is used.
+
SEE ALSO
--------
diff --git a/doc/admin/admin_commands/krb5kdc.rst b/doc/admin/admin_commands/krb5kdc.rst
index 631a0de84..97fbe5ed7 100644
--- a/doc/admin/admin_commands/krb5kdc.rst
+++ b/doc/admin/admin_commands/krb5kdc.rst
@@ -106,6 +106,13 @@ ENVIRONMENT
See :ref:`kerberos(7)` for a description of Kerberos environment
variables.
+As of release 1.22, krb5kdc supports systemd socket activation via the
+LISTEN_PID and LISTEN_FDS environment variables. Sockets provided by
+the caller must correspond to configured listener addresses (via the
+**kdc_listen** variable or equivalent) or they will be ignored. Any
+configured listener addresses that do not correspond to
+caller-provided sockets will be ignored if socket activation is used.
+
SEE ALSO
--------
diff --git a/src/include/socket-utils.h b/src/include/socket-utils.h
index 177662c87..02c10ec02 100644
--- a/src/include/socket-utils.h
+++ b/src/include/socket-utils.h
@@ -43,6 +43,8 @@
#ifndef SOCKET_UTILS_H
#define SOCKET_UTILS_H
+#include <stdbool.h>
+
/* Some useful stuff cross-platform for manipulating socket addresses.
We assume at least ipv4 sockaddr_in support. The sockaddr_storage
stuff comes from the ipv6 socket api enhancements; socklen_t is
@@ -159,4 +161,37 @@ sa_socklen(const struct sockaddr *sa)
abort();
}
+/* Return true if a and b are the same address (and port if applicable). */
+static inline bool
+sa_equal(const struct sockaddr *a, const struct sockaddr *b)
+{
+ if (a == NULL || b == NULL || a->sa_family != b->sa_family)
+ return false;
+
+ if (a->sa_family == AF_INET) {
+ const struct sockaddr_in *x = sa2sin(a);
+ const struct sockaddr_in *y = sa2sin(b);
+
+ if (x->sin_port != y->sin_port)
+ return false;
+ return memcmp(&x->sin_addr, &y->sin_addr, sizeof(x->sin_addr)) == 0;
+ } else if (a->sa_family == AF_INET6) {
+ const struct sockaddr_in6 *x = sa2sin6(a);
+ const struct sockaddr_in6 *y = sa2sin6(b);
+
+ if (x->sin6_port != y->sin6_port)
+ return false;
+ return memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(x->sin6_addr)) == 0;
+#ifndef _WIN32
+ } else if (a->sa_family == AF_UNIX) {
+ const struct sockaddr_un *x = sa2sun(a);
+ const struct sockaddr_un *y = sa2sun(b);
+
+ return strcmp(x->sun_path, y->sun_path) == 0;
+#endif
+ }
+
+ return false;
+}
+
#endif /* SOCKET_UTILS_H */
diff --git a/src/kdc/Makefile.in b/src/kdc/Makefile.in
index 7199b3472..bf4d9b580 100644
--- a/src/kdc/Makefile.in
+++ b/src/kdc/Makefile.in
@@ -31,7 +31,8 @@ SRCS= \
EXTRADEPSRCS= \
$(srcdir)/t_ndr.c \
- $(srcdir)/t_replay.c
+ $(srcdir)/t_replay.c \
+ $(srcdir)/t_sockact.c
OBJS= \
authind.o \
@@ -78,16 +79,21 @@ T_REPLAY_OBJS=t_replay.o
t_replay: $(T_REPLAY_OBJS) replay.o $(KRB5_BASE_DEPLIBS)
$(CC_LINK) -o $@ $(T_REPLAY_OBJS) $(CMOCKA_LIBS) $(KRB5_BASE_LIBS)
+t_sockact: t_sockact.o $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ t_sockact.o $(KRB5_BASE_LIBS)
+
check-cmocka: t_replay
$(RUN_TEST) ./t_replay > /dev/null
-check-pytests:
+check-pytests: t_sockact
$(RUNPYTEST) $(srcdir)/t_workers.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_emptytgt.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_bigreply.py $(PYTESTFLAGS)
+ $(RUNPYTEST) $(srcdir)/t_sockact.py $(PYTESTFLAGS)
install:
$(INSTALL_PROGRAM) krb5kdc ${DESTDIR}$(SERVER_BINDIR)/krb5kdc
clean:
$(RM) krb5kdc rtest.o rtest t_replay.o t_replay t_ndr.o t_ndr
+ $(RM) t_sockact.o t_sockact
diff --git a/src/kdc/deps b/src/kdc/deps
index 2d54fa93d..86be4c475 100644
--- a/src/kdc/deps
+++ b/src/kdc/deps
@@ -427,3 +427,13 @@ $(OUTPRE)t_replay.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
$(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
extern.h kdc_util.h realm_data.h replay.c reqstate.h \
t_replay.c
+$(OUTPRE)t_sockact.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h t_sockact.c
diff --git a/src/kdc/t_sockact.c b/src/kdc/t_sockact.c
new file mode 100644
index 000000000..cb4a4bc7a
--- /dev/null
+++ b/src/kdc/t_sockact.c
@@ -0,0 +1,121 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* kdc/t_sockact.c - socket activation test harness */
+/*
+ * Copyright (C) 2025 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Usage: t_sockact address... -- program args...
+ *
+ * This program simulates systemd socket activation by creating one or more
+ * listener sockets at the specified addresses, setting LISTEN_FDS and
+ * LISTEN_PID in the environment, and executing the specified command. (The
+ * real systemd would not execute the program until there is input on one of
+ * the listener sockets, but we do not need to simulate that, and executing the
+ * command immediately allow easier integration with k5test.py.)
+ */
+
+#include "k5-int.h"
+#include "socket-utils.h"
+
+static int max_fd;
+
+static void
+create_socket(const struct sockaddr *addr)
+{
+ int fd, one = 1;
+
+ fd = socket(addr->sa_family, SOCK_STREAM, 0);
+ if (fd < 0)
+ abort();
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0)
+ abort();
+#ifdef SO_REUSEPORT
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) != 0)
+ abort();
+#endif
+#if defined(IPV6_V6ONLY)
+ if (addr->sa_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) != 0)
+ abort();
+ }
+#endif
+ if (bind(fd, addr, sa_socklen(addr)) != 0)
+ abort();
+ if (listen(fd, 5) != 0)
+ abort();
+ max_fd = fd;
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *addrstr;
+ struct sockaddr_storage ss = { 0 };
+ struct addrinfo hints = { 0 }, *ai_list = NULL, *ai = NULL;
+ char *host, nbuf[128];
+ int i, port;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--") == 0)
+ break;
+ addrstr = argv[i];
+ if (*addrstr == '/') {
+ ss.ss_family = AF_UNIX;
+ strlcpy(ss2sun(&ss)->sun_path, addrstr,
+ sizeof(ss2sun(&ss)->sun_path));
+ create_socket(ss2sa(&ss));
+ } else {
+ if (k5_parse_host_string(addrstr, 0, &host, &port) != 0 || !port)
+ abort();
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+#ifdef AI_NUMERICSERV
+ hints.ai_flags |= AI_NUMERICSERV;
+#endif
+ (void)snprintf(nbuf, sizeof(nbuf), "%d", port);
+ if (getaddrinfo(host, nbuf, &hints, &ai_list) != 0)
+ abort();
+ for (ai = ai_list; ai != NULL; ai = ai->ai_next)
+ create_socket(ai->ai_addr);
+ freeaddrinfo(ai_list);
+ free(host);
+ }
+ }
+ argv += i + 1;
+
+ (void)snprintf(nbuf, sizeof(nbuf), "%d", max_fd - 2);
+ setenv("LISTEN_FDS", nbuf, 1);
+ (void)snprintf(nbuf, sizeof(nbuf), "%lu", (unsigned long)getpid());
+ setenv("LISTEN_PID", nbuf, 1);
+ execv(argv[0], argv);
+ abort();
+ return 1;
+}
diff --git a/src/kdc/t_sockact.py b/src/kdc/t_sockact.py
new file mode 100644
index 000000000..2a4aeb9ed
--- /dev/null
+++ b/src/kdc/t_sockact.py
@@ -0,0 +1,43 @@
+from k5test import *
+
+if not which('systemd-socket-activate'):
+ skip_rest('socket activation tests', 'systemd-socket-activate not found')
+
+# Configure listeners for two UNIX domain sockets and two ports.
+kdc_conf = {'realms': {'$realm': {
+ 'kdc_listen': '$testdir/sock1 $testdir/sock2',
+ 'kdc_tcp_listen': '$port7 $port8'}}}
+realm = K5Realm(kdc_conf=kdc_conf, start_kdc=False)
+
+# Create socket activation fds for just one of the UNIX domain sockets
+# and one of the ports.
+realm.start_server(['./t_sockact', os.path.join(realm.testdir, 'sock1'),
+ str(realm.portbase + 8), '--', krb5kdc, '-n'],
+ 'starting...')
+
+mark('UNIX socket 1')
+cconf1 = {'realms': {'$realm': {'kdc': '$testdir/sock1'}}}
+env1 = realm.special_env('sock1', False, krb5_conf=cconf1)
+realm.kinit(realm.user_princ, password('user'), env=env1)
+
+mark('port8')
+cconf2 = {'realms': {'$realm': {'kdc': '$hostname:$port8'}}}
+env2 = realm.special_env('sock1', False, krb5_conf=cconf2)
+realm.kinit(realm.user_princ, password('user'), env=env2)
+
+# Test that configured listener addresses are ignored if they don't
+# match caller-provided sockets.
+
+mark('UNIX socket 2')
+cconf3 = {'realms': {'$realm': {'kdc': '$testdir/sock2'}}}
+env3 = realm.special_env('sock2', False, krb5_conf=cconf3)
+realm.kinit(realm.user_princ, password('user'), env=env3, expected_code=1,
+ expected_msg='Cannot contact any KDC')
+
+mark('port7')
+cconf4 = {'realms': {'$realm': {'kdc': '$hostname:$port7'}}}
+env4 = realm.special_env('sock1', False, krb5_conf=cconf4)
+realm.kinit(realm.user_princ, password('user'), env=env3, expected_code=1,
+ expected_msg='Cannot contact any KDC')
+
+success('systemd socket activation tests')
diff --git a/src/lib/apputils/net-server.c b/src/lib/apputils/net-server.c
index 6fa8a97e0..c8c83606d 100644
--- a/src/lib/apputils/net-server.c
+++ b/src/lib/apputils/net-server.c
@@ -65,6 +65,19 @@
#include "udppktinfo.h"
+/* List of systemd socket activation addresses and socket types. */
+struct sockact_list {
+ size_t nsockets;
+ struct {
+ struct sockaddr_storage addr;
+ int type;
+ } *fds;
+};
+
+/* When systemd socket activation is used, caller-provided sockets begin at
+ * file descriptor 3. */
+const int SOCKACT_START = 3;
+
/* XXX */
#define KDC5_NONET (-1779992062L)
@@ -694,6 +707,82 @@ static const enum conn_type bind_conn_types[] =
[UNX] = CONN_UNIXSOCK_LISTENER
};
+/* If any systemd socket activation fds are indicated by the environment, set
+ * them close-on-exec and put their addresses and socket types into *list. */
+static void
+init_sockact_list(struct sockact_list *list)
+{
+ const char *v;
+ char *end;
+ long lpid;
+ int fd;
+ size_t nfds, i;
+ socklen_t slen;
+
+ list->nsockets = 0;
+ list->fds = NULL;
+
+ /* Check if LISTEN_FDS is meant for this process. */
+ v = getenv("LISTEN_PID");
+ if (v == NULL)
+ return;
+ lpid = strtol(v, &end, 10);
+ if (end == NULL || end == v || *end != '\0' || lpid != getpid())
+ return;
+
+ /* Get the number of activated sockets. */
+ v = getenv("LISTEN_FDS");
+ if (v == NULL)
+ return;
+ nfds = strtoul(v, &end, 10);
+ if (end == NULL || end == v || *end != '\0')
+ return;
+ if (nfds == 0 || nfds > (size_t)INT_MAX - SOCKACT_START)
+ return;
+
+ list->fds = calloc(nfds, sizeof(*list->fds));
+ if (list->fds == NULL)
+ return;
+
+ for (i = 0; i < nfds; i++) {
+ fd = i + SOCKACT_START;
+ set_cloexec_fd(fd);
+ slen = sizeof(list->fds[i].addr);
+ (void)getsockname(fd, ss2sa(&list->fds[i].addr), &slen);
+ slen = sizeof(list->fds[i].type);
+ (void)getsockopt(fd, SOL_SOCKET, SO_TYPE, &list->fds[i].type, &slen);
+ }
+
+ list->nsockets = nfds;
+}
+
+/* Release any storage used by *list. */
+static void
+fini_sockact_list(struct sockact_list *list)
+{
+ free(list->fds);
+ list->fds = NULL;
+ list->nsockets = 0;
+}
+
+/* If sa matches an address in *list, return the associated file descriptor and
+ * clear the address from *list. Otherwise return -1. */
+static int
+find_sockact(struct sockact_list *list, const struct sockaddr *sa, int type)
+{
+ size_t i;
+
+ for (i = 0; i < list->nsockets; i++) {
+ if (list->fds[i].type == type &&
+ sa_equal(ss2sa(&list->fds[i].addr), sa)) {
+ list->fds[i].type = -1;
+ memset(&list->fds[i].addr, 0, sizeof(list->fds[i].addr));
+ return i + SOCKACT_START;
+ }
+ }
+ return -1;
+}
+
/*
* Set up a listening socket.
*
@@ -708,8 +797,9 @@ static const enum conn_type bind_conn_types[] =
*/
static krb5_error_code
setup_socket(struct bind_address *ba, struct sockaddr *sock_address,
- void *handle, const char *prog, verto_ctx *ctx,
- int listen_backlog, verto_callback vcb, enum conn_type ctype)
+ struct sockact_list *sockacts, void *handle, const char *prog,
+ verto_ctx *ctx, int listen_backlog, verto_callback vcb,
+ enum conn_type ctype)
{
krb5_error_code ret;
struct connection *conn;
@@ -722,20 +812,31 @@ setup_socket(struct bind_address *ba, struct sockaddr *sock_address,
krb5_klog_syslog(LOG_DEBUG, _("Setting up %s socket for address %s"),
bind_type_names[ba->type], addrbuf);
- /* Create the socket. */
- ret = create_server_socket(sock_address, bind_socktypes[ba->type], prog,
- &sock);
- if (ret)
- goto cleanup;
+ if (sockacts->nsockets > 0) {
+ /* Look for a systemd socket activation fd matching sock_address. */
+ sock = find_sockact(sockacts, sock_address, bind_socktypes[ba->type]);
+ if (sock == -1) {
+ /* Ignore configured addresses that don't match any caller-provided
+ * sockets. */
+ ret = 0;
+ goto cleanup;
+ }
+ } else {
+ /* We're not using socket activation; create the socket. */
+ ret = create_server_socket(sock_address, bind_socktypes[ba->type],
+ prog, &sock);
+ if (ret)
+ goto cleanup;
- /* Listen for backlogged connections on stream sockets. (For RPC sockets
- * this will be done by svc_register().) */
- if ((ba->type == TCP || ba->type == UNX) &&
- listen(sock, listen_backlog) != 0) {
- ret = errno;
- com_err(prog, errno, _("Cannot listen on %s server socket on %s"),
- bind_type_names[ba->type], addrbuf);
- goto cleanup;
+ /* Listen for backlogged connections on stream sockets. (For RPC
+ * sockets this will be done by svc_register().) */
+ if ((ba->type == TCP || ba->type == UNX) &&
+ listen(sock, listen_backlog) != 0) {
+ ret = errno;
+ com_err(prog, errno, _("Cannot listen on %s server socket on %s"),
+ bind_type_names[ba->type], addrbuf);
+ goto cleanup;
+ }
}
/* Set non-blocking I/O for non-RPC listener sockets. */
@@ -837,6 +938,7 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog,
struct bind_address addr;
struct sockaddr_un sun;
struct addrinfo hints, *ai_list = NULL, *ai = NULL;
+ struct sockact_list sockacts = { 0 };
verto_callback vcb;
char addrbuf[128];
@@ -855,6 +957,8 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog,
hints.ai_flags |= AI_NUMERICSERV;
#endif
+ init_sockact_list(&sockacts);
+
/* Add all the requested addresses. */
for (i = 0; i < bind_addresses.n; i++) {
addr = bind_addresses.data[i];
@@ -870,8 +974,9 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog,
addr.address);
goto cleanup;
}
- ret = setup_socket(&addr, (struct sockaddr *)&sun, handle, prog,
- ctx, listen_backlog, verto_callbacks[addr.type],
+ ret = setup_socket(&addr, (struct sockaddr *)&sun, &sockacts,
+ handle, prog, ctx, listen_backlog,
+ verto_callbacks[addr.type],
bind_conn_types[addr.type]);
if (ret) {
krb5_klog_syslog(LOG_ERR,
@@ -911,8 +1016,8 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog,
/* Set the real port number. */
sa_setport(ai->ai_addr, addr.port);
- ret = setup_socket(&addr, ai->ai_addr, handle, prog, ctx,
- listen_backlog, verto_callbacks[addr.type],
+ ret = setup_socket(&addr, ai->ai_addr, &sockacts, handle, prog,
+ ctx, listen_backlog, verto_callbacks[addr.type],
bind_conn_types[addr.type]);
if (ret) {
k5_print_addr(ai->ai_addr, addrbuf, sizeof(addrbuf));
@@ -937,6 +1042,7 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog,
cleanup:
if (ai_list != NULL)
freeaddrinfo(ai_list);
+ fini_sockact_list(&sockacts);
return ret;
}
More information about the cvs-krb5
mailing list