From: Lennart Poettering Date: Wed, 4 Mar 2026 14:16:14 +0000 (+0100) Subject: imds: add generator that hooks in IMDS logic on cloud guests X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=168a7f7770bee0b4e5f7237a5fbd63a9e5ea068f;p=thirdparty%2Fsystemd.git imds: add generator that hooks in IMDS logic on cloud guests The infrastructure added in the previous commits added support for IMDS client functionality, but didn't really to enable the logic by default on suitable hosts. This commit adds a generator that automatically hooks the IMDS functionality into the boot process if it detects that the system is running on a compliant cloud system. it enables both the imds daemon and the client. --- diff --git a/NEWS b/NEWS index cdf3c3a0ad9..c34c55603e4 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,14 @@ CHANGES WITH 261 in spe: attestation environments which use hardware CC registers and not the TPM quote. + * By default networking to cloud IMDS services is now locked down, for + recognized clouds. This is recommended for secure installations, but + typically conflicts with traditional IMDS clients such as cloud-init, + which require direct IMDS access currently. The new meson option + "imds-network" can be used to change the default networking mode to + "unlocked" at build-time, for compatibility. This is probably what + general purpose distributions should set for now. + CHANGES WITH 260: Feature Removals and Incompatible Changes: diff --git a/man/rules/meson.build b/man/rules/meson.build index 0ecf0db5d69..5c14fb626bb 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1045,6 +1045,7 @@ manpages = [ ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'], ['systemd-hwdb', '8', [], 'ENABLE_HWDB'], ['systemd-id128', '1', [], ''], + ['systemd-imds-generator', '8', [], 'ENABLE_IMDS'], ['systemd-imds', '1', ['systemd-imds-import.service'], 'ENABLE_IMDS'], ['systemd-imdsd@.service', '8', diff --git a/man/systemd-imds-generator.xml b/man/systemd-imds-generator.xml new file mode 100644 index 00000000000..d8e1f1aa05b --- /dev/null +++ b/man/systemd-imds-generator.xml @@ -0,0 +1,107 @@ + + + + + + + + systemd-imds-generator + systemd + + + + systemd-imds-generator + 8 + + + + systemd-imds-generator + Generator to automatically enable IMDS on supporting environments + + + + /usr/lib/systemd/system-generators/systemd-imds-generator + + + + Description + + systemd-imds-generator is a generator that enables IMDS (Instance Metadata + Service) functionality at boot on systems that support it. Specifically it does three things: + + + It pulls the systemd-imdsd.socket unit (which activates + systemd-imdsd@.service8) + into the initial transaction, which provides IMDS access to local applications via Varlink + IPC. + + It pulls the systemd-imds-early-network.service unit into the + initial transaction, which generates a suitable + systemd.network5 + network configuration file that allows early-boot network access to the IMDS + functionality. + + It pulls the systemd-imds-import.service unit into the initial + transaction, which automatically imports various credentials from IMDS into the local system, storing + them in /run/credstore/. + + + By default, whether to pull in these services or not is decided based on + hwdb7 information, + that detects various IMDS environments automatically. However, this logic may be overridden via + systemd.imds=, see below. + + systemd-imds-generator implements + systemd.generator7. + + + + Kernel Command Line + + systemd-imds-generator understands the following kernel command line + parameters: + + + + + systemd.imds= + + Takes a boolean argument or the special value auto, and may be used to + enable or disable the IMDS logic. Note that this controls only whether the relevant services (as + listed above) are automatically pulled into the initial transaction, it has no effect if some other + unit or the user explicitly activate the relevant units. If this option is not used (or set to + auto) automatic detection of IMDS is used, see above. + + + + + + + + + systemd.imds.import= + + Takes a boolean argument. If false the systemd-imds-import.service (see + above) is not pulled into the initial transaction, i.e. no credentials are imported from + IMDS. Defaults to true. + + + + + + + + + + See Also + + systemd1 + systemd-imds1 + systemd-imdsd@.service8 + systemd.system-credentials7 + + + + diff --git a/meson.build b/meson.build index e35237e452b..2893bea332f 100644 --- a/meson.build +++ b/meson.build @@ -1544,6 +1544,7 @@ have = get_option('imds').require( conf.get('HAVE_LIBCURL') == 1, error_message : 'curl required').allowed() conf.set10('ENABLE_IMDS', have) +conf.set10('IMDS_NETWORK_LOCKED_DEFAULT', get_option('imds-network') == 'locked') have = get_option('importd').require( conf.get('HAVE_LIBCURL') == 1 and @@ -3087,7 +3088,8 @@ summary({ 'default user $PATH' : default_user_path != '' ? default_user_path : '(same as system services)', 'systemd service watchdog' : service_watchdog == '' ? 'disabled' : service_watchdog, 'time epoch' : f'@time_epoch@ (@alt_time_epoch@)', - 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout() + 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout(), + 'IMDS networking' : get_option('imds-network'), }) # TODO: diff --git a/meson_options.txt b/meson_options.txt index 7835f716662..30c5fd3ab67 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -144,6 +144,8 @@ option('timesyncd', type : 'boolean', description : 'install the systemd-timesyncd daemon') option('imds', type : 'feature', description : 'install the systemd-imds stack') +option('imds-network', type : 'combo', choices : [ 'locked', 'unlocked' ], + description : 'whether to default to locked/unlocked IMDS network mode') option('journal-storage-default', type : 'combo', choices : ['persistent', 'auto', 'volatile', 'none'], description : 'default storage mode for journald (main namespace)') option('remote', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, diff --git a/src/imds/imds-generator.c b/src/imds/imds-generator.c new file mode 100644 index 00000000000..d33e63bddd3 --- /dev/null +++ b/src/imds/imds-generator.c @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-hwdb.h" + +#include "dropin.h" +#include "fileio.h" +#include "generator.h" +#include "imds-util.h" +#include "log.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "string-util.h" +#include "virt.h" + +static int arg_enabled = -1; /* Whether we shall offer local IMDS APIs */ +static bool arg_import = true; /* Whether we shall import IMDS credentials, SSH keys, … into the local system */ +static ImdsNetworkMode arg_network_mode = + IMDS_NETWORK_LOCKED_DEFAULT ? IMDS_NETWORK_LOCKED : IMDS_NETWORK_UNLOCKED; + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (proc_cmdline_key_streq(key, "systemd.imds")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_tristate_full(value, "auto", &arg_enabled); + if (r < 0) + return log_warning_errno(r, "Failed to parse systemd.imds= value: %m"); + + } else if (proc_cmdline_key_streq(key, "systemd.imds.import")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_boolean(value); + if (r < 0) + return log_warning_errno(r, "Failed to parse systemd.imds.import= value: %m"); + + arg_import = r; + } else if (proc_cmdline_key_streq(key, "systemd.imds.network")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + ImdsNetworkMode m = imds_network_mode_from_string(value); + if (m < 0) + return log_warning_errno(m, "Failed to parse systemd.imds.network= value: %m"); + + arg_network_mode = m; + } + + return 0; +} + +static int smbios_get_modalias(char **ret) { + int r; + + assert(ret); + + _cleanup_free_ char *modalias = NULL; + r = read_virtual_file("/sys/devices/virtual/dmi/id/modalias", SIZE_MAX, &modalias, /* ret_size= */ NULL); + if (r < 0) + return r; + + truncate_nl(modalias); + + /* To detect Azure we need to check the chassis asset tag. Unfortunately the kernel does not include + * it in the modalias string right now. Let's hence append it manually. This matches similar logic in + * rules.d/60-dmi-id.rules. */ + _cleanup_free_ char *cat = NULL; + r = read_virtual_file("/sys/devices/virtual/dmi/id/chassis_asset_tag", SIZE_MAX, &cat, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read chassis asset tag, ignoring: %m"); + else { + truncate_nl(cat); + + if (!string_has_cc(cat, /* ok= */ NULL) && !isempty(cat) && !strextend(&modalias, "cat", cat, ":")) + return -ENOMEM; + } + + log_debug("Constructed SMBIOS modalias string: %s", modalias); + *ret = TAKE_PTR(modalias); + return 0; +} + +static int smbios_query(void) { + int r; + + /* Let's check whether the DMI device's hwdb data suggests IMDS support is available. Note, we cannot + * ask udev for this, as we typically run long before udev. Hence we'll do the hwdb lookup via + * sd-hwdb directly. */ + + _cleanup_free_ char *modalias = NULL; + r = smbios_get_modalias(&modalias); + if (r == -ENOENT) { + log_debug("No DMI device found, assuming IMDS is not available."); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to read DMI modalias: %m"); + + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + r = sd_hwdb_new(&hwdb); + if (r < 0) + return log_error_errno(r, "Failed to open hwdb: %m"); + + r = sd_hwdb_seek(hwdb, modalias); + if (r < 0) + return log_error_errno(r, "Failed to seek in hwdb for '%s': %m", modalias); + + for (;;) { + const char *key, *value; + r = sd_hwdb_enumerate(hwdb, &key, &value); + if (r < 0) + return log_error_errno(r, "Failed to enumerate hwdb entry for '%s': %m", modalias); + if (r == 0) + break; + + if (streq(key, "IMDS_VENDOR")) + return true; + } + + log_debug("IMDS_VENDOR= property for DMI device not set, assuming IMDS is not available."); + return false; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + if (arg_enabled < 0) { + Virtualization v = detect_container(); + if (v < 0) + log_debug_errno(v, "Container detection failed, ignoring: %m"); + if (v > 0) { + log_debug("Running in a container, disabling IMDS logic."); + arg_enabled = false; + } else { + r = smbios_query(); + if (r < 0) + return r; + arg_enabled = r > 0; + } + } + + if (!arg_enabled) { + log_debug("IMDS not enabled, skipping generator."); + return 0; + } + + log_info("IMDS support enabled, pull in IMDS units."); + + /* Enable IMDS early networking, so that we can actually reach the IMDS server. */ + if (arg_network_mode != IMDS_NETWORK_OFF) { + r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-early-network.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-early-network.service: %m"); + } + + /* Enable the IMDS service socket */ + r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imdsd.socket"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imdsd.socket: %m"); + + /* We now know the SMBIOS device exists, hence it's safe now to order the IMDS service after it, so + * that it has all properties properly initialized. */ + r = write_drop_in( + dest_early, + "systemd-imdsd@.service", + 50, "dmi-id", + "# Automatically generated by systemd-imds-generator\n\n" + "[Unit]\n" + "Wants=sys-devices-virtual-dmi-id.device\n" + "After=sys-devices-virtual-dmi-id.device\n"); + if (r < 0) + return log_error_errno(r, "Failed to hook DMI id device before systemd-imdsd@.service: %m"); + + if (arg_import) { + /* Enable that we import IMDS data */ + r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-import.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m"); + } + + return 0; +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/imds/meson.build b/src/imds/meson.build index a28dd0ca3a5..f9fa9b5f0fb 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -22,6 +22,13 @@ executables += [ 'imds-util.c' ), }, + generator_template + { + 'name' : 'systemd-imds-generator', + 'sources' : files( + 'imds-generator.c', + 'imds-util.c' + ), + }, ] install_data(