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.
This commit is contained in:
Lennart Poettering
2026-03-04 15:16:14 +01:00
parent 19a783f4dd
commit 168a7f7770
7 changed files with 321 additions and 1 deletions

8
NEWS
View File

@@ -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:

View 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',

View File

@@ -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>

View 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:

View 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' },

193
src/imds/imds-generator.c Normal file
View File

@@ -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);

View 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(