]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
imds: add generator that hooks in IMDS logic on cloud guests
authorLennart Poettering <lennart@amutable.com>
Wed, 4 Mar 2026 14:16:14 +0000 (15:16 +0100)
committerLennart Poettering <lennart@amutable.com>
Thu, 26 Mar 2026 09:54:15 +0000 (10:54 +0100)
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.

NEWS
man/rules/meson.build
man/systemd-imds-generator.xml [new file with mode: 0644]
meson.build
meson_options.txt
src/imds/imds-generator.c [new file with mode: 0644]
src/imds/meson.build

diff --git a/NEWS b/NEWS
index cdf3c3a0ad91881b316901cbc88bddb5fc022ac4..c34c55603e46a8b61446833539911aec06f17855 100644 (file)
--- 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:
index 0ecf0db5d6957125e1e2969a43cc33cb69a28d73..5c14fb626bb42ed95799d0787d64726a08859d9b 100644 (file)
@@ -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 (file)
index 0000000..d8e1f1a
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd-imds-generator" conditional='ENABLE_IMDS'
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>systemd-imds-generator</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-imds-generator</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-imds-generator</refname>
+    <refpurpose>Generator to automatically enable IMDS on supporting environments</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para><filename>/usr/lib/systemd/system-generators/systemd-imds-generator</filename></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-imds-generator</command> is a generator that enables IMDS (Instance Metadata
+    Service) functionality at boot on systems that support it. Specifically it does three things:</para>
+
+    <itemizedlist>
+      <listitem><para>It pulls the <filename>systemd-imdsd.socket</filename> unit (which activates
+      <citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
+      into the initial transaction, which provides IMDS access to local applications via Varlink
+      IPC.</para></listitem>
+
+      <listitem><para>It pulls the <filename>systemd-imds-early-network.service</filename> unit into the
+      initial transaction, which generates a suitable
+      <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+      network configuration file that allows early-boot network access to the IMDS
+      functionality.</para></listitem>
+
+      <listitem><para>It pulls the <filename>systemd-imds-import.service</filename> unit into the initial
+      transaction, which automatically imports various credentials from IMDS into the local system, storing
+      them in <filename>/run/credstore/</filename>.</para></listitem>
+    </itemizedlist>
+
+    <para>By default, whether to pull in these services or not is decided based on
+    <citerefentry><refentrytitle>hwdb</refentrytitle><manvolnum>7</manvolnum></citerefentry> information,
+    that detects various IMDS environments automatically. However, this logic may be overridden via
+    <varname>systemd.imds=</varname>, see below.</para>
+
+    <para><command>systemd-imds-generator</command> implements
+    <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Kernel Command Line</title>
+
+    <para><command>systemd-imds-generator</command> understands the following kernel command line
+    parameters:</para>
+
+    <variablelist class='kernel-commandline-options'>
+
+      <varlistentry>
+        <term><varname>systemd.imds=</varname></term>
+        <listitem>
+          <para>Takes a boolean argument or the special value <literal>auto</literal>, 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
+          <literal>auto</literal>) automatic detection of IMDS is used, see above.</para>
+
+          <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
+
+      <xi:include href="systemd-imdsd@.service.xml" xpointer="kernel-cmdline-imds-network"/>
+
+      <varlistentry>
+        <term><varname>systemd.imds.import=</varname></term>
+        <listitem>
+          <para>Takes a boolean argument. If false the <filename>systemd-imds-import.service</filename> (see
+          above) is not pulled into the initial transaction, i.e. no credentials are imported from
+          IMDS. Defaults to true.</para>
+
+          <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para><simplelist type="inline">
+      <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd-imds</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+    </simplelist></para>
+  </refsect1>
+
+</refentry>
index e35237e452be0878049a8c17843d1ef42200b584..2893bea332f29b21f6ced5164d717bd91395c8fe 100644 (file)
@@ -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:
index 7835f716662d9e56f00aafffe3754b8c4d1f2013..30c5fd3ab67fe7b7540482111975728de431342f 100644 (file)
@@ -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 (file)
index 0000000..d33e63b
--- /dev/null
@@ -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);
index a28dd0ca3a5103b35109139c25e1044e46335be6..f9fa9b5f0fb1a2ed074105f3c97a88f2f9e7b28d 100644 (file)
@@ -22,6 +22,13 @@ executables += [
                         'imds-util.c'
                 ),
         },
+        generator_template + {
+                'name' : 'systemd-imds-generator',
+                'sources' : files(
+                        'imds-generator.c',
+                        'imds-util.c'
+                ),
+        },
 ]
 
 install_data(