Basic IMDS support (#40980)

This adds a new cloud IMDS client, that can cover AWS, Azure, GCP,
Hetzner imds to varying degrees. Each cloud has this and it's very basic
functionality, hence I think it makes sense to add this to systemd.

Since the clouds are all different this tries hard to do the abstract
common logic in code, but encode the endpoint details, and well-known
keys in hwdb, attached to the DMI id device.

Why all this?

* Efficiency: we can schedule this in the initrd, at the earliest points
possible, without unnecessary delays
* Robustness: imds is typically slow and/or heavily rate-limited:
systemd-imdsd as single entrypoint can deal with that, and provide a
reliable, cached interface
* Security: the idea is that systemd-imdsd is the only service behing
able to access the IMDS HTTP, and the host carries a blackhole route for
it otherwise. That way sensitive info can be kept away from clients, and
requires polkit auth for access
* Simplicity: extraction of systemd's system credentials from IMDS
userdata happens with systemd's own infra, and for many usecases that
should already be enough.
* Measurements: before accepting the IDMS userdata, it can be measured
into a PCR, as any other configuration input for the system

Fixes: #40763
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-03-26 14:12:31 +01:00
committed by GitHub
40 changed files with 5584 additions and 7 deletions
+8
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:
+10
View File
@@ -125,11 +125,21 @@ Features:
* start making use of the new --graceful switch to util-linux' umount command
* sysusers: allow specifying a path to an inode *and* a literal UID in the UID
column, so that if the inode exists it is used, and if not the literal UID is
used. Use this for services such as the imds one, which run under their own
UID in the initrd, and whose data should survive to the host, properly owned.
* add service file setting to force the fwmark (a la SO_MARK) to some value, so
that we can allowlist certain services for imds this way.
* make systemd work nicely without /bin/sh, logins and associated shell tools around
- make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots
- varlink interface for "systemctl start" and friends
- https://github.com/util-linux/util-linux/issues/4117
* imds: maybe do smarter api version handling
* drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that
it pushes the thing into TPM RAM, but a TPM usually has very little of that,
less than NVRAM. hence setting the flag amplifies space issues. Unsetting the
+105
View File
@@ -0,0 +1,105 @@
# This file is part of systemd
# This provides various properties that declare if and how IMDS is available on
# the local system, i.e. we are running in a major cloud service that provides
# something resembling AWS' or Azure's Instance Metadata Service.
#
# General IMDS endpoint data:
# IMDS_VENDOR= → Indicates IMDS is available, and which vendor it is
# IMDS_TOKEN_URL= → The URL to request an API token from. If not set, no API token is requested.
# IMDS_REFRESH_HEADER_NAME= → The HTTP request header field (everything before the ":") that contains the refresh TTL (in seconds) when requesting a token.
# IMDS_DATA_URL= → The base URL to request actual IMDS data fields from
# IMDS_DATA_URL_SUFFIX= → Parameters to suffix the URLs with
# IMDS_TOKEN_HEADER_NAME= → The HTTP request header field (everything before the ":") used to pass the token
# IMDS_EXTRA_HEADER=, IMDS_EXTRA_HEADER2=, IMDS_EXTRA_HEADER3=, …
# → Additional HTTP headers to pass when requesting a data field (full header, including ":")
# IMDS_ADDRESS_IPV4= → IPv4 address of the IMDS server
# IMDS_ADDRESS_IPV6= → IPv6 address of the IMDS server
#
# Well-known IMDS keys:
# IMDS_KEY_HOSTNAME= → IMDS key for the hostname
# IMDS_KEY_REGION= → IMDS key for the region, if that concept applies
# IMDS_KEY_ZONE= → IMDS key for the zone, if that concept applies
# IMDS_KEY_IPV4_PUBLIC= → IMDS key for the primary public IPv4 address if there is any
# IMDS_KEY_IPV6_PUBLIC= → IMDS key for the primary public IPv6 address if there is any
# IMDS_KEY_SSH_KEY= → IMDS key for an SSH public key to install in the root account
# IMDS_KEY_USERDATA= → IMDS key for arbitrary userdata (if there's only one)
# IMDS_KEY_USERDATA_BASE= → IMDS key for arbitrary userdata (if there are multiple, this is the common prefix)
# IMDS_KEY_USERDATA_BASE64= → IMDS key for arbitrary userdata (if there's only one, but it is base64 encoded)
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
dmi:bvnAmazonEC2:*
IMDS_VENDOR=amazon-ec2
IMDS_TOKEN_URL=http://169.254.169.254/latest/api/token
IMDS_REFRESH_HEADER_NAME=X-aws-ec2-metadata-token-ttl-seconds
IMDS_DATA_URL=http://169.254.169.254/latest
IMDS_TOKEN_HEADER_NAME=X-aws-ec2-metadata-token
IMDS_ADDRESS_IPV4=169.254.169.254
IMDS_ADDRESS_IPV6=fd00:ec2::254
IMDS_KEY_HOSTNAME=/meta-data/hostname
IMDS_KEY_REGION=/meta-data/placement/region
IMDS_KEY_ZONE=/meta-data/placement/availability-zone
IMDS_KEY_IPV4_PUBLIC=/meta-data/public-ipv4
IMDS_KEY_IPV6_PUBLIC=/meta-data/ipv6
IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key
IMDS_KEY_USERDATA=/user-data
# https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service#instance-metadata
dmi:*:cat7783-7084-3265-9085-8269-3286-77:*
IMDS_VENDOR=microsoft-azure
IMDS_DATA_URL=http://169.254.169.254/metadata
IMDS_DATA_URL_SUFFIX=?api-version=2025-04-07&format=text
IMDS_EXTRA_HEADER=Metadata: true
IMDS_ADDRESS_IPV4=169.254.169.254
IMDS_KEY_HOSTNAME=/instance/compute/osProfile/computerName
IMDS_KEY_REGION=/instance/compute/location
IMDS_KEY_ZONE=/instance/compute/physicalZone
IMDS_KEY_IPV4_PUBLIC=/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress
IMDS_KEY_IPV6_PUBLIC=/instance/network/interface/0/ipv6/ipAddress/0/publicIpAddress
IMDS_KEY_SSH_KEY=/instance/compute/publicKeys/0/keyData
IMDS_KEY_USERDATA_BASE64=/instance/compute/userData
# https://docs.cloud.google.com/compute/docs/metadata/predefined-metadata-keys
dmi:*:pnGoogleComputeEngine:*
IMDS_VENDOR=google-gcp
IMDS_DATA_URL=http://169.254.169.254/computeMetadata/v1
IMDS_EXTRA_HEADER=Metadata-Flavor: Google
IMDS_ADDRESS_IPV4=169.254.169.254
IMDS_KEY_HOSTNAME=/instance/hostname
IMDS_KEY_REGION=/instance/region
IMDS_KEY_ZONE=/instance/zone
IMDS_KEY_IPV4_PUBLIC=/instance/network-interfaces/0/access-configs/0/external-ip
IMDS_KEY_USERDATA_BASE=/instance/attributes
# https://docs.hetzner.cloud/reference/cloud#description/server-metadata
dmi:bvnHetzner:*
IMDS_VENDOR=hetzner-cloud
IMDS_DATA_URL=http://169.254.169.254/hetzner/v1/metadata
IMDS_ADDRESS_IPV4=169.254.169.254
IMDS_KEY_HOSTNAME=/hostname
IMDS_KEY_REGION=/region
IMDS_KEY_ZONE=/availability-zone
IMDS_KEY_IPV4_PUBLIC=/public-ipv4
IMDS_KEY_SSH_KEY=/public-keys/0
IMDS_KEY_USERDATA=/userdata
# https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm#metadata-keys
dmi:*:catOracleCloud.com:*
IMDS_VENDOR=oracle-cloud-oci
IMDS_DATA_URL=http://169.254.169.254/opc/v2
IMDS_ADDRESS_IPV4=169.254.169.254
IMDS_ADDRESS_IPV6=fd00:c1::a9fe:a9fe
IMDS_EXTRA_HEADER=Authorization: Bearer Oracle
IMDS_KEY_HOSTNAME=/instance/hostname
IMDS_KEY_REGION=/instance/region
IMDS_KEY_ZONE=/instance/availabilityDomain
IMDS_KEY_SSH_KEY=/instance/metadata/ssh_authorized_keys
IMDS_KEY_USERDATA_BASE64=/metadata/user_data
# https://www.scaleway.com/en/docs/instances/how-to/use-cloud-init/
dmi:*:svnScaleway:*
IMDS_VENDOR=scaleway
IMDS_DATA_URL=http://169.254.42.42
IMDS_ADDRESS_IPV4=169.254.42.42
IMDS_ADDRESS_IPV6=fd00:42::42
IMDS_KEY_USERDATA=/user_data
+1
View File
@@ -19,6 +19,7 @@ hwdb_files_notest = files(
hwdb_files_test = files(
'20-dmi-id.hwdb',
'20-net-ifname.hwdb',
'40-imds.hwdb',
'60-autosuspend.hwdb',
'60-autosuspend-fingerprint-reader.hwdb',
'60-evdev.hwdb',
+19 -1
View File
@@ -125,7 +125,7 @@ def hwdb_grammar():
matchline = (matchline_typed | matchline_general) + EOL
propertyline = (White(' ', exact=1).suppress() +
Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/'))
Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/?&'))
- Optional(pythonStyleComment)) +
EOL)
propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
@@ -215,6 +215,24 @@ def property_grammar():
('ID_NET_NAME_FROM_DATABASE', name_literal),
('ID_NET_NAME_INCLUDE_DOMAIN', zero_one),
('TPM2_BROKEN_NVPCR', zero_one),
('IMDS_VENDOR', name_literal),
('IMDS_TOKEN_URL', name_literal),
('IMDS_REFRESH_HEADER_NAME', name_literal),
('IMDS_DATA_URL', name_literal),
('IMDS_DATA_URL_SUFFIX', name_literal),
('IMDS_TOKEN_HEADER_NAME', name_literal),
('IMDS_EXTRA_HEADER', name_literal),
('IMDS_ADDRESS_IPV4', name_literal),
('IMDS_ADDRESS_IPV6', name_literal),
('IMDS_KEY_HOSTNAME', name_literal),
('IMDS_KEY_REGION', name_literal),
('IMDS_KEY_ZONE', name_literal),
('IMDS_KEY_IPV4_PUBLIC', name_literal),
('IMDS_KEY_IPV6_PUBLIC', name_literal),
('IMDS_KEY_SSH_KEY', name_literal),
('IMDS_KEY_USERDATA', name_literal),
('IMDS_KEY_USERDATA_BASE', name_literal),
('IMDS_KEY_USERDATA_BASE64', name_literal),
)
fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
for name, val in props]
+13
View File
@@ -793,6 +793,19 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds=</varname></term>
<term><varname>systemd.imds.*=</varname></term>
<listitem><para>Controls various Instance Metadata Service (IMDS) cloud aspects, see
<citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
and
<citerefentry><refentrytitle>systemd-imds-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
+8
View File
@@ -1045,6 +1045,14 @@ 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',
['systemd-imdsd',
'systemd-imdsd-early-network.service',
'systemd-imdsd.socket'],
'ENABLE_IMDS'],
['systemd-import-generator', '8', [], ''],
['systemd-importd.service', '8', ['systemd-importd'], 'ENABLE_IMPORTD'],
['systemd-inhibit', '1', [], ''],
+107
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>
+174
View File
@@ -0,0 +1,174 @@
<?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" conditional='ENABLE_IMDS'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-imds</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-imds</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-imds</refname>
<refname>systemd-imds-import.service</refname>
<refpurpose>Cloud IMDS (Instance Metadata Service) tool</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-imds-import.service</filename></para>
<cmdsynopsis>
<command>systemd-imds</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">KEY</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-imds</command> is a tool for acquiring data from IMDS (Instance Metadata Service),
as provided in many cloud environments. It is a client to
<citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
and provides access to IMDS data from shell environments.</para>
<para>The tool can operate in one of five modes:</para>
<itemizedlist>
<listitem><para>Without positional arguments (and without the <option>--well-known=</option> switch)
general IMDS service data and a few well known fields are displayed in human friendly
form.</para></listitem>
<listitem><para>With a positional argument (and without <option>--well-known=</option>) the IMDS data
referenced by the specified key is acquired and written to standard output, in unprocessed form. IMDS
keys are the part of the IMDS acquisition URL that are suffixed to the base URL. IMDS keys must begin
with a slash (<literal>/</literal>). Note that IMDS keys are typically
implementation-specific.</para></listitem>
<listitem><para>With the <option>--well-known=</option> option specified (see below), the indicated
well-known field is written to standard output, in unprocessed form. The concept of well-known fields
abstracts IMDS implementation differences to some level, exposing a unified interface for IMDS fields
that typically exist on many different implementations, but under implementation-specific
keys.</para></listitem>
<listitem><para>With the <option>--userdata</option> option specified (see below) the "userdata"
provided via IMDS is written to standard output. Under the hood this is similar to
<option>--well-known=userdata-base</option>, <option>--well-known=userdata</option> or
<option>--well-known=userdata-base64</option>. Each of the three is tried in turn (in this order), and
the first available is returned. For <option>--well-known=userdata-base</option> the
<literal>systemd-userdata</literal> userdata item is requested. For
<option>--well-known=userdata-base64</option> the returned data is automatically
Base64-decoded.</para></listitem>
<listitem><para>With the <option>--import</option> option specified, various well known and userdata
fields are imported into the local credential store, where they are used to configure and parameterize
the system. For details see below.</para></listitem>
</itemizedlist>
</refsect1>
<refsect1>
<title>Options and Commands</title>
<variablelist>
<varlistentry>
<term><option>--well-known=</option></term>
<term><option>-K</option></term>
<listitem><para>Takes one of <literal>hostname</literal>, <literal>region</literal>,
<literal>zone</literal>, <literal>ipv4-public</literal>, <literal>ipv6-public</literal>,
<literal>ssh-key</literal>, <literal>userdata</literal>, <literal>userdata-base</literal>,
<literal>userdata-base64</literal>. Acquires a specific "well-known" field from IMDS. Many of these
fields are commonly supported by various IMDS implementations, but typically some fields are
not. Note that if <option>--well-known=userdata-base</option> is used an additional subkey should be
specified as positional argument, which encodes the specific userdata item to acquire.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--refresh=</option></term>
<listitem><para>Takes a time in seconds as argument, and indicates the required "freshness" of the
data, in case cached data is used.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--cache=</option></term>
<listitem><para>Takes a boolean. If set to false local caching of IMDS is disabled, and the data is
always acquired fresh from the IMDS endpoint.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--userdata</option></term>
<term><option>-u</option></term>
<listitem><para>Acquire this instance's IMDS user data, if available. See above for
details.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--import</option></term>
<listitem><para>Acquires IMDS data and writes relevant fields as credentials to
<filename>/run/credstore/</filename>. This currently covers:</para>
<itemizedlist>
<listitem><para>If the IMDS user data is a valid JSON object containing a field
<varname>systemd.credentials</varname> (with a JSON array as value) it is processed, importing
arbitrary credentials listed in the array. Each array item must have a <varname>name</varname>
field indicating the credential name. It may have one <varname>text</varname>,
<varname>data</varname> or <varname>encrypted</varname> field, containing the credential data. If
<varname>text</varname> is used the value shall be a literal string of the credential value. If
<varname>data</varname> is used the value may be arbitrary binary data encoded in a Base64
string. If <varname>encrypted</varname> is used the value shall be a Base64 encoded encrypted
credential. See
<citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for information about credentials that may be imported this way.</para></listitem>
<listitem><para>If the well-known <varname>ssh-key</varname> field is available, its value will be
imported into the <varname>ssh.authorized_keys.root</varname> credential.</para></listitem>
<listitem><para>If the well-known <varname>hostname</varname> field is available, its value will be
imported into the <varname>firstboot.hostname</varname> credential.</para></listitem>
</itemizedlist>
<para>This command is invoked by the <filename>systemd-imds-import.service</filename> run at
boot.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-imds-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+269
View File
@@ -0,0 +1,269 @@
<?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-imdsd_.service" conditional='ENABLE_IMDS' xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-imdsd@.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-imdsd@.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-imdsd@.service</refname>
<refname>systemd-imdsd</refname>
<refname>systemd-imdsd.socket</refname>
<refname>systemd-imdsd-early-network.service</refname>
<refpurpose>Cloud IMDS (Instance Metadata Service) client</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-imdsd@.service</filename></para>
<para><filename>systemd-imdsd.socket</filename></para>
<para><filename>systemd-imdsd-early-network.service</filename></para>
<para><filename>/usr/lib/systemd/systemd-imdsd</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-imdsd@.service</command> is a system service that provides local access to IMDS
(Instance Metadata Service; or equivalent) functionality, as provided by many public clouds.</para>
<para>The service provides a Varlink IPC interface via
<filename>/run/systemd/io.systemd.InstanceMetadata</filename> to query IMDS fields.</para>
<para><filename>systemd-imdsd-early-network.service</filename> is a system service that generates a
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
compatible
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> file
for configuring the early-boot network in order to be able to contact the IMDS endpoint.</para>
<para>The
<citerefentry><refentrytitle>systemd-imds</refentrytitle><manvolnum>1</manvolnum></citerefentry> tool may
be used to query information from this service.</para>
</refsect1>
<!-- Note: we do not document the command line switches here for now. The systemd-imdsd binary itself
should not be called by users, but only as a service, hence the command line options shouldn't be user
visible, but exist mostly as debugging tools. People should always use the client side tool
(i.e. systemd-imds; or an equivalent Varlink client) to do actual IMDS operations. -->
<refsect1>
<title>Kernel Command Line Options</title>
<para>The IMDS endpoint is typically determined automatically via
<citerefentry><refentrytitle>hwdb</refentrytitle><manvolnum>7</manvolnum></citerefentry> records, but can
also be configured explicitly via the kernel command line, via the following options:</para>
<variablelist class='kernel-commandline-options'>
<varlistentry id='kernel-cmdline-imds-network'>
<term><varname>systemd.imds.network=</varname></term>
<listitem>
<para>Takes one of <literal>off</literal>, <literal>locked</literal>,
<literal>unlocked</literal>. Controls whether and how to set up networking for IMDS endpoint
access. Unless set to <literal>off</literal> early boot networking is enabled, ensuring that the
IMDS endpoint can be reached. If set to <literal>locked</literal> (the default) direct access to
the IMDS endpoint by regular unprivileged processes is disabled via a "prohibit" route, so that any
access must be done through <filename>systemd-imdsd@.service</filename> or its associated tools. If
set to <literal>unlocked</literal> this "prohibit" route is not created, and regular unprivileged
processes can directly contact IMDS.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.vendor=</varname></term>
<listitem>
<para>A short string identifying the cloud vendor.</para>
<para>Example: <programlisting>systemd.imds.vendor=foobarcloud</programlisting></para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.token_url=</varname></term>
<listitem>
<para>If a bearer token must be acquired to talk to the IMDS service, this is the URL to acquire it
from.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.refresh_header_name=</varname></term>
<listitem>
<para>Takes a HTTP header field name (excluding the <literal>:</literal>) that declares the header
field for passing the TTL value (in seconds) to the HTTP server when acquiring a token. Only
applies if <varname>systemd.imds.token_url=</varname> is set too.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.data_url=</varname></term>
<listitem>
<para>Takes the base URL to acquire the IMDS data from (the IMDS "endpoint"). All data fields are
acquired from below this URL. This URL should typically not end in <literal>/</literal>.</para>
<para>The data URLs are concatenated from this base URL, the IMDS "key" and the suffix configured
via <varname>systemd.imds.data_url_suffix=</varname> below. Well-known IMDS "keys" can be
configured via the <varname>systemd.imds.key=*</varname> options below.</para>
<para>Example: <programlisting>systemd.imds.data_url=http://169.254.169.254/metadata</programlisting></para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.data_url_suffix=</varname></term>
<listitem>
<para>If specified, this field is appended to the end of the data URL (after appending the IMDS
"key" to the data base URL), see above.</para>
<para>Example: <programlisting>systemd.imds.data_url_suffix=?api-version=2025-04-07&amp;format=text</programlisting></para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.token_header_name=</varname></term>
<listitem>
<para>Takes a HTTP header field name (excluding the <literal>:</literal>) that declares the header
field to pass the bearer token acquired from the token URL (see above) in. Only applies if
<varname>systemd.imds.token_url=</varname> is set too.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.extra_header=</varname></term>
<listitem>
<para>Takes a full HTTP header expression (both field name and value, separated by a colon
<literal>:</literal>) to pass to the HTTP server when requesting data. May be used multiple times
to set multiple headers.</para>
<para>Example: <programlisting>systemd.imds.extra_header=Metadata:true</programlisting></para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.address_ipv4=</varname></term>
<listitem>
<para>Configures the IPv4 address the IMDS endpoint is contacted on. This should typically be the
IP address also configured via <varname>systemd.imds.data_url=</varname> (if IPv4 is used) and is
used to set up IP routing.</para>
<para>Example: <programlisting>systemd.imds.address_ipv4=169.254.169.254</programlisting></para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.address_ipv6=</varname></term>
<listitem>
<para>Configures the IPv6 address the IMDS endpoint is contacted on. This should typically be the
IP address also configured via <varname>systemd.imds.data_url=</varname> (if IPv6 is used) and is
used to set up IP routing.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.imds.key.hostname=</varname></term>
<term><varname>systemd.imds.key.region=</varname></term>
<term><varname>systemd.imds.key.zone=</varname></term>
<term><varname>systemd.imds.key.ipv4_public=</varname></term>
<term><varname>systemd.imds.key.ipv6_public=</varname></term>
<term><varname>systemd.imds.key.ssh_key=</varname></term>
<term><varname>systemd.imds.key.userdata=</varname></term>
<term><varname>systemd.imds.key.userdata_base=</varname></term>
<term><varname>systemd.imds.key.userdata_base64=</varname></term>
<listitem>
<para>Configures strings to concatenate to the data base URL (see above) to acquire data for
various "well-known" fields. These strings must begin with a <literal>/</literal>. They should
return the relevant data in plain text.</para>
<para>A special case are the three "userdata" keys: the option
<varname>systemd.imds.key.userdata_base=</varname> should be used if the IMDS service knows a
concept of multiple userdata fields, and a field identifier thus still needs to be appended to the
userdata base URL. The option <varname>systemd.imds.key.userdata=</varname> should be used if only
a single userdata field is supported. The option <varname>systemd.imds.key.userdata_base64=</varname>
should be used in the same case, but only if the userdata field is encoded in Base64.</para>
<para>Example: <programlisting>systemd.imds.key.hostname=/instance/compute/osProfile/computerName</programlisting></para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Credentials</title>
<para><filename>systemd-imdsd@.service</filename> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<varlistentry>
<term><varname>imds.vendor</varname></term>
<term><varname>imds.vendor_token</varname></term>
<term><varname>imds.refresh_header_name</varname></term>
<term><varname>imds.data_url</varname></term>
<term><varname>imds.data_url_suffix</varname></term>
<term><varname>imds.token_header_name</varname></term>
<term><varname>imds.extra_header</varname></term>
<term><varname>imds.extra_header2</varname></term>
<term><varname>imds.extra_header3</varname></term>
<term><varname>imds.extra_header…</varname></term>
<term><varname>imds.address_ipv4</varname></term>
<term><varname>imds.address_ipv6</varname></term>
<term><varname>imds.key_hostname</varname></term>
<term><varname>imds.key_region</varname></term>
<term><varname>imds.key_zone</varname></term>
<term><varname>imds.key_ipv4_public</varname></term>
<term><varname>imds.key_ipv6_public</varname></term>
<term><varname>imds.key_ssh_key</varname></term>
<term><varname>imds.key_userdata</varname></term>
<term><varname>imds.key_userdata_base</varname></term>
<term><varname>imds.key_userdata_base64</varname></term>
<listitem><para>The various IMDS endpoint parameters. The semantics are very close to those configurable
via kernel command line, see above for the matching list.</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-imds-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+10
View File
@@ -580,6 +580,16 @@
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>imds.*</varname></term>
<listitem>
<para>Read by <citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
+11 -1
View File
@@ -906,6 +906,7 @@ foreach option : ['adm-gid',
'video-gid',
'wheel-gid',
'systemd-journal-gid',
'systemd-imds-uid',
'systemd-network-uid',
'systemd-resolve-uid',
'systemd-timesync-uid']
@@ -1539,6 +1540,12 @@ conf.set('DEFAULT_DNSSEC_MODE',
'DNSSEC_' + default_dnssec.underscorify().to_upper())
conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec)
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
conf.get('HAVE_OPENSSL') == 1 and
@@ -2375,6 +2382,7 @@ subdir('src/hostname')
subdir('src/hwdb')
subdir('src/id128')
subdir('src/import')
subdir('src/imds') # Note, we are not alphabetically here, since we want to use a variable from src/import/ here
subdir('src/integritysetup')
subdir('src/journal')
subdir('src/journal-remote')
@@ -3080,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:
@@ -3145,6 +3154,7 @@ foreach tuple : [
['homed'],
['hostnamed'],
['hwdb'],
['imds'],
['importd'],
['initrd'],
['kernel-install'],
+6
View File
@@ -142,6 +142,10 @@ option('timedated', type : 'boolean',
description : 'install the systemd-timedated daemon')
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' },
@@ -334,6 +338,8 @@ option('systemd-resolve-uid', type : 'integer', value : 0,
description : 'soft-static allocation for the systemd-resolve user')
option('systemd-timesync-uid', type : 'integer', value : 0,
description : 'soft-static allocation for the systemd-timesync user')
option('systemd-imds-uid', type : 'integer', value : 0,
description : 'soft-static allocation for the systemd-imds user')
option('dev-kvm-mode', type : 'string', value : '0666',
description : '/dev/kvm access mode')
+1
View File
@@ -44,6 +44,7 @@ wrap=(
/usr/lib/polkit-1/polkitd
/usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py
/usr/libexec/polkit-1/polkitd
/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py
agetty
btrfs
capsh
+193
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);
+896
View File
@@ -0,0 +1,896 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <getopt.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sd-varlink.h"
#include "alloc-util.h"
#include "build.h"
#include "build-path.h"
#include "creds-util.h"
#include "dns-rr.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "imds-util.h"
#include "in-addr-util.h"
#include "io-util.h"
#include "iovec-util.h"
#include "json-util.h"
#include "log.h"
#include "main-func.h"
#include "parse-argument.h"
#include "pcrextend-util.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "tmpfile-util.h"
static enum {
ACTION_SUMMARY,
ACTION_GET,
ACTION_USERDATA,
ACTION_IMPORT,
_ACTION_INVALID = -EINVAL,
} arg_action = _ACTION_INVALID;
static char *arg_key = NULL;
static ImdsWellKnown arg_well_known = _IMDS_WELL_KNOWN_INVALID;
static int arg_cache = -1;
static usec_t arg_refresh_usec = 0;
static bool arg_refresh_usec_set = false;
STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("systemd-imds", "1", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] [KEY]\n"
"\n%sIMDS data acquisition.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -K --well-known=[hostname|region|zone|ipv4-public|ipv6-public|ssh-key|\n"
" userdata|userdata-base|userdata-base64]\n"
" Select well-known key/base\n"
" --refresh=SEC Set minimum freshness time for returned data\n"
" --cache=no Disable cache use\n"
" -u --userdata Dump user data\n"
" --import Import system credentials from IMDS userdata\n"
" and place them in /run/credstore/\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_REFRESH,
ARG_CACHE,
ARG_IMPORT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "well-known", required_argument, NULL, 'K' },
{ "refresh", required_argument, NULL, ARG_REFRESH },
{ "cache", required_argument, NULL, ARG_CACHE },
{ "userdata", no_argument, NULL, 'u' },
{ "import", no_argument, NULL, ARG_IMPORT },
{}
};
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hK:u", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case 'K': {
if (isempty(optarg)) {
arg_well_known = _IMDS_WELL_KNOWN_INVALID;
break;
}
if (streq(optarg, "help"))
return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX);
ImdsWellKnown wk = imds_well_known_from_string(optarg);
if (wk < 0)
return log_error_errno(wk, "Failed to parse --well-known= argument: %s", optarg);
arg_well_known = wk;
break;
}
case ARG_CACHE:
r = parse_tristate_argument_with_auto("--cache=", optarg, &arg_cache);
if (r < 0)
return r;
break;
case ARG_REFRESH: {
if (isempty(optarg)) {
arg_refresh_usec_set = false;
break;
}
usec_t t;
r = parse_sec(optarg, &t);
if (r < 0)
return log_error_errno(r, "Failed to parse refresh timeout: %s", optarg);
arg_refresh_usec = t;
arg_refresh_usec_set = true;
break;
}
case 'u':
arg_action = ACTION_USERDATA;
break;
case ARG_IMPORT:
arg_action = ACTION_IMPORT;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
}
if (IN_SET(arg_action, ACTION_USERDATA, ACTION_IMPORT)) {
if (argc != optind)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No parameters expected.");
} else {
assert(arg_action < 0);
if (argc > optind + 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "None or one argument expected.");
if (argc == optind && arg_well_known < 0)
arg_action = ACTION_SUMMARY;
else {
if (arg_well_known < 0)
arg_well_known = IMDS_BASE;
if (argc > optind) {
if (!imds_key_is_valid(argv[optind]))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", argv[optind]);
if (!imds_well_known_can_suffix(arg_well_known))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' does not take a key suffix, refusing.", imds_well_known_to_string(arg_well_known));
r = free_and_strdup_warn(&arg_key, argv[optind]);
if (r < 0)
return r;
}
arg_action = ACTION_GET;
}
}
return 1;
}
static int acquire_imds_key(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
struct iovec *ret) {
int r;
assert(link);
assert(wk >= 0);
assert(wk < _IMDS_WELL_KNOWN_MAX);
assert(ret);
const char *error_id = NULL;
sd_json_variant *reply = NULL;
r = sd_varlink_callbo(
link,
"io.systemd.InstanceMetadata.Get",
&reply,
&error_id,
SD_JSON_BUILD_PAIR_CONDITION(wk != IMDS_BASE, "wellKnown", JSON_BUILD_STRING_UNDERSCORIFY(imds_well_known_to_string(wk))),
JSON_BUILD_PAIR_STRING_NON_EMPTY("key", key),
SD_JSON_BUILD_PAIR_CONDITION(arg_refresh_usec_set, "refreshUSec", SD_JSON_BUILD_UNSIGNED(arg_refresh_usec)),
SD_JSON_BUILD_PAIR_CONDITION(arg_cache >= 0, "cache", SD_JSON_BUILD_BOOLEAN(arg_cache)));
if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.Get(): %m");
if (error_id) {
if (STR_IN_SET(error_id, "io.systemd.InstanceMetadata.KeyNotFound", "io.systemd.InstanceMetadata.WellKnownKeyUnset")) {
*ret = (struct iovec) {};
return 0;
}
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.Get(): %s", error_id);
}
_cleanup_(iovec_done) struct iovec data = {};
static const sd_json_dispatch_field dispatch_table[] = {
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, 0, SD_JSON_MANDATORY },
{},
};
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &data);
if (r < 0)
return r;
*ret = TAKE_STRUCT(data);
return 1;
}
static int acquire_imds_key_as_string(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
char **ret) {
int r;
assert(link);
assert(wk >= 0);
assert(wk < _IMDS_WELL_KNOWN_MAX);
assert(ret);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, wk, key, &data);
if (r < 0)
return r;
if (r == 0) {
*ret = NULL;
return 0;
}
_cleanup_free_ char *s = NULL;
r = make_cstring(data.iov_base, data.iov_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &s);
if (r < 0)
return r;
*ret = TAKE_PTR(s);
return 1;
}
static int acquire_imds_key_as_ip_address(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
int family,
union in_addr_union *ret) {
int r;
assert(link);
assert(wk >= 0);
assert(wk < _IMDS_WELL_KNOWN_MAX);
assert(ret);
_cleanup_free_ char *s = NULL;
r = acquire_imds_key_as_string(link, wk, key, &s);
if (r < 0)
return r;
if (r == 0 || isempty(s)) {
*ret = (union in_addr_union) {};
return 0;
}
r = in_addr_from_string(family, s, ret);
if (r < 0)
return r;
return 1;
}
static int action_summary(sd_varlink *link) {
int r;
assert(link);
_cleanup_(table_unrefp) Table *table = table_new_vertical();
if (!table)
return log_oom();
const char *error_id = NULL;
sd_json_variant *reply = NULL;
r = sd_varlink_call(
link,
"io.systemd.InstanceMetadata.GetVendorInfo",
/* parameters= */ NULL,
&reply,
&error_id);
if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m");
if (error_id)
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id);
const char *vendor = NULL;
static const sd_json_dispatch_field dispatch_table[] = {
{ "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
{}
};
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &vendor);
if (r < 0)
return r;
if (vendor) {
r = table_add_many(table,
TABLE_FIELD, "Vendor",
TABLE_SET_JSON_FIELD_NAME, "vendor",
TABLE_STRING, vendor);
if (r < 0)
return table_log_add_error(r);
}
static const struct {
ImdsWellKnown well_known;
const char *field;
} wktable[] = {
{ IMDS_HOSTNAME, "Hostname" },
{ IMDS_REGION, "Region" },
{ IMDS_ZONE, "Zone" },
{ IMDS_IPV4_PUBLIC, "Public IPv4 Address" },
{ IMDS_IPV6_PUBLIC, "Public IPv6 Address" },
};
FOREACH_ELEMENT(i, wktable) {
_cleanup_free_ char *text = NULL;
r = acquire_imds_key_as_string(link, i->well_known, /* key= */ NULL, &text);
if (r < 0)
return r;
if (r == 0 || isempty(text))
continue;
r = table_add_many(table,
TABLE_FIELD, i->field,
TABLE_SET_JSON_FIELD_NAME, imds_well_known_to_string(i->well_known),
TABLE_STRING, text);
if (r < 0)
return table_log_add_error(r);
}
if (table_isempty(table))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No well-known IMDS data available.");
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
return 0;
}
static const char *detect_json_object(const char *text) {
assert(text);
/* Checks if the provided text looks like a JSON object. It checks if the first non-whitespace
* characters are {" or {}. */
text += strspn(text, WHITESPACE);
if (*text != '{')
return NULL;
const char *e = text + 1;
e += strspn(e, WHITESPACE);
if (!IN_SET(*e, '"', '}'))
return NULL;
return text;
}
static int write_credential(const char *dir, const char *name, const struct iovec *data) {
int r;
assert(dir);
assert(name);
_cleanup_close_ int dfd = open_mkdir(dir, O_CLOEXEC|O_PATH, 0700);
if (dfd < 0)
return log_error_errno(dfd, "Failed to open credential directory '%s': %m", dir);
if (faccessat(dfd, name, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to check if '%s' exists in credential directory '%s': %m", name, dir);
} else {
log_notice("Skipping importing of credential '%s', it already exists locally in '%s'.", name, dir);
return 0;
}
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = open_tmpfile_linkable_at(dfd, name, O_WRONLY|O_CLOEXEC, &t);
if (fd < 0)
return log_error_errno(fd, "Failed to create credential file '%s/%s': %m", dir, name);
CLEANUP_TMPFILE_AT(dfd, t);
r = loop_write(fd, data->iov_base, data->iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write credential file '%s/%s': %m", dir, name);
if (fchmod(fd, 0400) < 0)
return log_error_errno(errno, "Failed to set access mode on credential file '%s/%s': %m", dir, name);
r = link_tmpfile_at(fd, dfd, t, name, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to move credential file '%s/%s' into place: %m", dir, name);
t = mfree(t); /* Disarm auto-cleanup */
return 1;
}
typedef struct CredentialData {
const char *name;
const char *text;
struct iovec data, encrypted;
} CredentialData;
static void credential_data_done(CredentialData *d) {
assert(d);
iovec_done(&d->data);
iovec_done(&d->encrypted);
}
static int import_credential_one(CredentialData *d) {
int r;
assert(d);
assert(d->name);
log_debug("Importing credential '%s' from IMDS.", d->name);
const char *dir = "/run/credstore";
struct iovec *v, _v;
if (d->text) {
_v = IOVEC_MAKE_STRING(d->text);
v = &_v;
} else if (iovec_is_set(&d->data))
v = &d->data;
else if (iovec_is_set(&d->encrypted)) {
dir = "/run/credstore.encrypted";
v = &d->encrypted;
} else
assert_not_reached();
r = write_credential(dir, d->name, v);
if (r <= 0)
return r;
log_info("Imported credential '%s' from IMDS (%s).", d->name, FORMAT_BYTES(v->iov_len));
return 1;
}
static int import_credentials(const char *text) {
int r;
assert(text);
/* We cannot be sure if the data is actually intended for us. Hence let's be somewhat defensive, and
* accept data in two ways: either immediately as a JSON object, or alternatively marked with a first
* line of "#systemd-userdata". The latter mimics the markers cloud-init employs. */
const char *e = startswith(text, "#systemd-userdata\n");
if (!e) {
e = detect_json_object(text);
if (!e) {
log_info("IMDS user data does not look like JSON or systemd userdata, not processing.");
return 0;
}
}
log_debug("Detected JSON userdata");
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
unsigned line = 0, column = 0;
r = sd_json_parse(e, /* flags= */ 0, &j, &line, &column);
if (r < 0) {
if (line > 0)
log_syntax(/* unit= */ NULL, LOG_WARNING, /* filename= */ NULL, line, r, "JSON parse failure.");
else
log_error_errno(r, "Failed to parse IMDS userdata JSON: %m");
return 0;
}
static const sd_json_dispatch_field top_table[] = {
{ "systemd.credentials", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, 0, 0 },
{},
};
sd_json_variant *creds = NULL;
r = sd_json_dispatch(j, top_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &creds);
if (r < 0)
return r;
unsigned n_imported = 0;
int ret = 0;
if (creds) {
log_debug("Found 'systemd.credentials' field");
sd_json_variant *c;
JSON_VARIANT_ARRAY_FOREACH(c, creds) {
static const sd_json_dispatch_field credential_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, name), SD_JSON_MANDATORY },
{ "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, text), 0 },
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, data), 0 },
{ "encrypted", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, encrypted), 0 },
{},
};
_cleanup_(credential_data_done) CredentialData d = {};
r = sd_json_dispatch(c, credential_table, SD_JSON_LOG|SD_JSON_WARNING, &d);
if (r < 0) {
RET_GATHER(ret, r);
continue;
}
if (!credential_name_valid(d.name)) {
RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential name '%s' is not valid, refusing.", d.name));
continue;
}
if ((!!d.text + !!iovec_is_set(&d.data) + !!iovec_is_set(&d.encrypted)) != 1) {
RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Exactly one of 'text', 'data', 'encrypted' must be set for credential '%s', refusing.", d.name));
continue;
}
r = import_credential_one(&d);
if (r < 0)
RET_GATHER(ret, r);
else if (r > 0)
n_imported++;
}
}
log_full(n_imported == 0 ? LOG_DEBUG : LOG_INFO, "Imported %u credentials from IMDS.", n_imported);
return ret;
}
static int add_public_address_to_json_array(sd_json_variant **array, int family, const union in_addr_union *addr) {
int r;
assert(array);
assert(IN_SET(family, AF_INET, AF_INET6));
assert(addr);
if (in_addr_is_null(family, addr))
return 0;
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
if (dns_resource_record_new_address(&rr, family, addr, "_public") < 0)
return log_oom();
_cleanup_(sd_json_variant_unrefp) sd_json_variant *rrj = NULL;
r = dns_resource_record_to_json(rr, &rrj);
if (r < 0)
return log_error_errno(r, "Failed to convert A RR to JSON: %m");
r = sd_json_variant_append_array(array, rrj);
if (r < 0)
return log_error_errno(r, "Failed to append A RR to JSON array: %m");
log_debug("Writing IMDS RR for: %s", dns_resource_record_to_string(rr));
return 1;
}
static int import_imds_public_addresses(sd_varlink *link) {
int r, ret = 0;
assert(link);
/* Creates local RRs (honoured by systemd-resolved) for our public addresses. */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
union in_addr_union u = {};
r = acquire_imds_key_as_ip_address(link, IMDS_IPV4_PUBLIC, /* key= */ NULL, AF_INET, &u);
if (r < 0)
RET_GATHER(ret, r);
else if (r > 0) {
r = add_public_address_to_json_array(&aj, AF_INET, &u);
if (r < 0)
return r;
}
u = (union in_addr_union) {};
r = acquire_imds_key_as_ip_address(link, IMDS_IPV6_PUBLIC, /* key= */ NULL, AF_INET6, &u);
if (r < 0)
RET_GATHER(ret, r);
else if (r > 0) {
r = add_public_address_to_json_array(&aj, AF_INET6, &u);
if (r < 0)
return r;
}
if (sd_json_variant_elements(aj) == 0) {
log_debug("No IMDS public addresses known, not writing our RRs.");
return 0;
}
_cleanup_free_ char *text = NULL;
r = sd_json_variant_format(aj, SD_JSON_FORMAT_NEWLINE, &text);
if (r < 0)
return log_error_errno(r, "Failed to format JSON text: %m");
r = write_string_file("/run/systemd/resolve/static.d/imds-public.rr", text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
if (r < 0)
return log_error_errno(r, "Failed to write IMDS RR data: %m");
log_debug("IMDS public addresses written out.");
return 1;
}
static int import_imds_ssh_key(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, IMDS_SSH_KEY, /* key= */ NULL, &data);
if (r < 0)
return r;
if (r == 0 || !iovec_is_set(&data)) {
log_debug("No SSH key supplied via IMDS, not importing.");
return 0;
}
r = write_credential("/run/credstore", "ssh.authorized_keys.root", &data);
if (r <= 0)
return r;
log_info("Imported SSH key as credential 'ssh.authorized_keys.root'.");
return 0;
}
static int import_imds_hostname(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, IMDS_HOSTNAME, /* key= */ NULL, &data);
if (r < 0)
return r;
if (r == 0 || !iovec_is_set(&data)) {
log_debug("No hostname supplied via IMDS, not importing.");
return 0;
}
r = write_credential("/run/credstore", "firstboot.hostname", &data);
if (r <= 0)
return r;
log_info("Imported hostname as credential 'firstboot.hostname'.");
return 0;
}
static int acquire_imds_userdata(sd_varlink *link, struct iovec *ret) {
int r;
assert(link);
assert(ret);
/* First try our private namespace, if the concept exists, and then fall back to the singleton */
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, IMDS_USERDATA_BASE, "/systemd-userdata", &data);
if (r == 0)
r = acquire_imds_key(link, IMDS_USERDATA, /* key= */ NULL, &data);
if (r < 0)
return r;
if (r > 0) {
if (!iovec_is_set(&data)) { /* Treat empty user data like empty */
*ret = (struct iovec) {};
return 0;
}
*ret = TAKE_STRUCT(data);
return 1;
}
r = acquire_imds_key(link, IMDS_USERDATA_BASE64, /* key= */ NULL, &data);
if (r < 0)
return r;
_cleanup_(iovec_done) struct iovec decoded = {};
if (r > 0) {
r = unbase64mem_full(data.iov_base, data.iov_len, /* secure= */ false, &decoded.iov_base, &decoded.iov_len);
if (r < 0)
return r;
}
if (!iovec_is_set(&decoded)) { /* Treat empty user data like empty */
*ret = (struct iovec) {};
return 0;
}
*ret = TAKE_STRUCT(decoded);
return 1;
}
static int action_get(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, arg_well_known, arg_key, &data);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Key not available.");
r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write data to standard output: %m");
return 0;
}
static int action_userdata(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_userdata(link, &data);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "User data not available.");
r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write data to standard output: %m");
return 0;
}
static int remove_userdata(const char *path) {
assert(path);
if (unlink(path) < 0) {
if (errno != ENOENT)
log_debug_errno(errno, "Failed to remove '%s', ignoring: %m", path);
return 0;
}
log_debug("Removed '%s'.", path);
return 1;
}
static int save_userdata(const struct iovec *data, const char *path) {
int r;
assert(data);
assert(path);
if (!iovec_is_set(data))
return remove_userdata(path);
r = write_data_file_atomic_at(AT_FDCWD, path, data, WRITE_DATA_FILE_MKDIR_0755);
if (r < 0)
return log_error_errno(r, "Failed to save userdata to '%s': %m", path);
log_debug("Saved userdata to '%s'.", path);
return 1;
}
static int action_import(sd_varlink *link) {
int r;
assert(link);
int ret = 0;
RET_GATHER(ret, import_imds_public_addresses(link));
RET_GATHER(ret, import_imds_hostname(link));
RET_GATHER(ret, import_imds_ssh_key(link));
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_userdata(link, &data);
if (r < 0)
return RET_GATHER(ret, r);
if (r == 0) {
log_info("No IMDS data available, not importing credentials.");
(void) remove_userdata("/run/systemd/imds/userdata");
return ret;
}
/* Measure the userdata before we use it */
(void) pcrextend_imds_userdata_now(&data);
/* Keep a pristine copy of the userdata we actually applied. (Note that this data is typically also
* kept as cached item on systemd-imdsd, but that one is possibly subject to cache invalidation,
* while this one is supposed to pin the data actually in effect.) */
(void) save_userdata(&data, "/run/systemd/imds/userdata");
/* Ensure no inner NUL byte */
if (memchr(data.iov_base, 0, data.iov_len)) {
log_info("IMDS user data contains NUL byte, not processing.");
return ret;
}
/* Turn this into a proper C string */
if (!iovec_append(&data, &IOVEC_MAKE_BYTE(0)))
return log_oom();
return RET_GATHER(ret, import_credentials(data.iov_base));
}
static int run(int argc, char* argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
_cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata");
if (r < 0) {
if (r != -ENOENT && !ERRNO_IS_NEG_DISCONNECT(r))
return log_error_errno(r, "Failed to connect to systemd-imdsd: %m");
log_debug_errno(r, "Couldn't connect to /run/systemd/io.systemd.InstanceMetadata, will try to fork off systemd-imdsd as child now.");
/* Try to fork off systemd-imdsd as a child as a fallback. If we have privileges and the
* SO_FWMARK trickery is not necessary, then this might just work. */
_cleanup_free_ char *p = NULL;
_cleanup_close_ int pin_fd =
pin_callout_binary(LIBEXECDIR "/systemd-imdsd", &p);
if (pin_fd < 0)
return log_error_errno(pin_fd, "Failed to pick up imdsd binary: %m");
r = sd_varlink_connect_exec(&link, p, /* argv[]= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to connect to imdsd service: %m");
}
switch (arg_action) {
case ACTION_SUMMARY:
return action_summary(link);
case ACTION_GET:
return action_get(link);
case ACTION_USERDATA:
return action_userdata(link);
case ACTION_IMPORT:
return action_import(link);
default:
assert_not_reached();
}
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
+50
View File
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <assert.h>
#include "imds-util.h"
#include "string-table.h"
#include "string-util.h"
#include "utf8.h"
bool imds_key_is_valid(const char *key) {
/* Just some pretty superficial validation. */
if (!key)
return false;
if (!startswith(key, "/"))
return false;
if (!ascii_is_valid(key))
return false;
if (string_has_cc(key, /* ok= */ NULL))
return false;
return true;
}
static const char* const imds_well_known_table[_IMDS_WELL_KNOWN_MAX] = {
[IMDS_BASE] = "base",
[IMDS_HOSTNAME] = "hostname",
[IMDS_REGION] = "region",
[IMDS_ZONE] = "zone",
[IMDS_IPV4_PUBLIC] = "ipv4-public",
[IMDS_IPV6_PUBLIC] = "ipv6-public",
[IMDS_SSH_KEY] = "ssh-key",
[IMDS_USERDATA] = "userdata",
[IMDS_USERDATA_BASE] = "userdata-base",
[IMDS_USERDATA_BASE64] = "userdata-base64",
};
DEFINE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown);
static const char* const imds_network_mode_table[_IMDS_NETWORK_MODE_MAX] = {
[IMDS_NETWORK_OFF] = "off",
[IMDS_NETWORK_LOCKED] = "locked",
[IMDS_NETWORK_UNLOCKED] = "unlocked",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(imds_network_mode, ImdsNetworkMode, IMDS_NETWORK_LOCKED);
+38
View File
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "macro.h"
#include "string-table.h" /* IWYU pragma: keep */
typedef enum ImdsNetworkMode {
IMDS_NETWORK_OFF, /* No automatic pre-IMDS network configuration, something else has to do this. (Also: no "prohibit" route) */
IMDS_NETWORK_LOCKED, /* "Prohibit" route for the IMDS server, unless you have SO_MARK set to 0x7FFF0815 */
IMDS_NETWORK_UNLOCKED, /* No "prohibit" route for the IMDS server */
_IMDS_NETWORK_MODE_MAX,
_IMDS_NETWORK_MODE_INVALID = -EINVAL,
} ImdsNetworkMode;
/* Various well-known keys */
typedef enum ImdsWellKnown {
IMDS_BASE, /* The same as "/", typically suffixed */
IMDS_HOSTNAME,
IMDS_REGION,
IMDS_ZONE,
IMDS_IPV4_PUBLIC,
IMDS_IPV6_PUBLIC,
IMDS_SSH_KEY,
IMDS_USERDATA,
IMDS_USERDATA_BASE, /* typically suffixed */
IMDS_USERDATA_BASE64,
_IMDS_WELL_KNOWN_MAX,
_IMDS_WELL_KNOWN_INVALID = -EINVAL,
} ImdsWellKnown;
static inline bool imds_well_known_can_suffix(ImdsWellKnown wk) {
return IN_SET(wk, IMDS_BASE, IMDS_USERDATA_BASE);
}
bool imds_key_is_valid(const char *key);
DECLARE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown);
DECLARE_STRING_TABLE_LOOKUP(imds_network_mode, ImdsNetworkMode);
+3163
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1-or-later
This file is part of systemd.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
-->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>https://systemd.io</vendor_url>
<action id="io.systemd.imds.get">
<description gettext-domain="systemd">Acquire IMDS instance metadata.</description>
<message gettext-domain="systemd">Authentication is required for an application to acquire IMDS instance metadata.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
+36
View File
@@ -0,0 +1,36 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
if conf.get('ENABLE_IMDS') != 1
subdir_done()
endif
executables += [
libexec_template + {
'name' : 'systemd-imdsd',
'public' : true,
'sources' : files(
'imdsd.c',
'imds-util.c'
) + import_curl_util_c,
'dependencies' : [ libcurl ],
},
libexec_template + {
'name' : 'systemd-imds',
'public' : true,
'sources' : files(
'imds-tool.c',
'imds-util.c'
),
},
generator_template + {
'name' : 'systemd-imds-generator',
'sources' : files(
'imds-generator.c',
'imds-util.c'
),
},
]
install_data(
'io.systemd.imds.policy',
install_dir : polkitpolicydir)
+3 -2
View File
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
import_curl_util_c = files('curl-util.c')
if conf.get('ENABLE_IMPORTD') != 1
subdir_done()
endif
@@ -32,14 +34,13 @@ executables += [
'name' : 'systemd-pull',
'public' : true,
'sources' : files(
'curl-util.c',
'pull.c',
'pull-common.c',
'pull-job.c',
'pull-oci.c',
'pull-raw.c',
'pull-tar.c',
),
) + import_curl_util_c,
'objects' : ['systemd-importd'],
'dependencies' : common_deps + [
libopenssl,
+1
View File
@@ -207,6 +207,7 @@ shared_sources = files(
'varlink-io.systemd.FactoryReset.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Import.c',
'varlink-io.systemd.InstanceMetadata.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.JournalAccess.c',
'varlink-io.systemd.Login.c',
+69
View File
@@ -18,8 +18,10 @@
#include "mountpoint-util.h"
#include "pcrextend-util.h"
#include "pkcs7-util.h"
#include "sha256.h"
#include "string-util.h"
#include "strv.h"
#include "tpm2-pcr.h"
static int device_get_file_system_word(
sd_device *d,
@@ -291,3 +293,70 @@ int pcrextend_verity_now(
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures.");
#endif
}
#define IMDS_USERDATA_TRUNCATED_MAX 256U
int pcrextend_imds_userdata_word(const struct iovec *data, char **ret) {
assert(iovec_is_set(data));
assert(ret);
/* We include both a hash of the complete user data, and a truncated version of the data in the word
* we measure. The former protects the actual data, the latter is useful for debugging. */
_cleanup_free_ char *hash = hexmem(SHA256_DIRECT(data->iov_base, data->iov_len), SHA256_DIGEST_SIZE);
if (!hash)
return log_oom();
_cleanup_free_ char *data_encoded = NULL;
if (base64mem_full(data->iov_base, MIN(data->iov_len, IMDS_USERDATA_TRUNCATED_MAX), /* line_break= */ SIZE_MAX, &data_encoded) < 0)
return log_oom();
_cleanup_free_ char *word = strjoin("imds-userdata:", hash, ":", data_encoded);
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_imds_userdata_now(const struct iovec *data) {
#if HAVE_TPM2
int r;
_cleanup_free_ char *word = NULL;
r = pcrextend_imds_userdata_word(data, &word);
if (r < 0)
return r;
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend");
if (r < 0)
return r;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
const char *error_id = NULL;
r = sd_varlink_callbo(
vl,
"io.systemd.PCRExtend.Extend",
/* ret_reply= */ NULL,
&error_id,
SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_CONFIG),
SD_JSON_BUILD_PAIR_STRING("text", word),
SD_JSON_BUILD_PAIR_STRING("eventType", "imds_userdata"));
if (r < 0)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
if (error_id) {
r = sd_varlink_error_to_errno(error_id, reply);
if (r != -EBADR)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id);
}
log_debug("Measurement of '%s' into PCR 12 completed.", word);
return 1;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring IMDS userdata.");
#endif
}
+5 -1
View File
@@ -1,9 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/uio.h>
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
int pcrextend_machine_id_word(char **ret);
int pcrextend_product_id_word(char **ret);
int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret);
int pcrextend_imds_userdata_word(const struct iovec *data, char **ret);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig);
int pcrextend_imds_userdata_now(const struct iovec *data);
+1
View File
@@ -6675,6 +6675,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_NVPCR_INIT] = "nvpcr-init",
[TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator",
[TPM2_EVENT_DM_VERITY] = "dm-verity",
[TPM2_EVENT_IMDS_USERDATA] = "imds-userdata",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
+1
View File
@@ -148,6 +148,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_NVPCR_INIT,
TPM2_EVENT_NVPCR_SEPARATOR,
TPM2_EVENT_DM_VERITY,
TPM2_EVENT_IMDS_USERDATA,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "bus-polkit.h"
#include "varlink-io.systemd.InstanceMetadata.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
WellKnown,
SD_VARLINK_DEFINE_ENUM_VALUE(base),
SD_VARLINK_DEFINE_ENUM_VALUE(hostname),
SD_VARLINK_DEFINE_ENUM_VALUE(region),
SD_VARLINK_DEFINE_ENUM_VALUE(zone),
SD_VARLINK_DEFINE_ENUM_VALUE(ipv4_public),
SD_VARLINK_DEFINE_ENUM_VALUE(ipv6_public),
SD_VARLINK_DEFINE_ENUM_VALUE(ssh_key),
SD_VARLINK_DEFINE_ENUM_VALUE(userdata),
SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base),
SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base64));
static SD_VARLINK_DEFINE_METHOD(
Get,
SD_VARLINK_FIELD_COMMENT("The key to retrieve"),
SD_VARLINK_DEFINE_INPUT(key, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Start with a well-known key"),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(wellKnown, WellKnown, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The network interface to use"),
SD_VARLINK_DEFINE_INPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Refresh cached data if older (CLOCK_BOOTTIME, µs)"),
SD_VARLINK_DEFINE_INPUT(refreshUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether to accept cached data"),
SD_VARLINK_DEFINE_INPUT(cache, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The firewall mark value to use"),
SD_VARLINK_DEFINE_INPUT(firewallMark, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Controls whether to wait for connectivity"),
SD_VARLINK_DEFINE_INPUT(wait, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("The data in Base64 encoding."),
SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The interface the data was found on."),
SD_VARLINK_DEFINE_OUTPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
GetVendorInfo,
SD_VARLINK_FIELD_COMMENT("The detected cloud vendor"),
SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The URL to acquire the token from"),
SD_VARLINK_DEFINE_OUTPUT(tokenUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The HTTP header to configure the refresh timeout for the token in"),
SD_VARLINK_DEFINE_OUTPUT(refreshHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The base URL to acquire the data from"),
SD_VARLINK_DEFINE_OUTPUT(dataUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("A suffix to append to the data URL"),
SD_VARLINK_DEFINE_OUTPUT(dataUrlSuffix, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The HTTP header to pass the token in when requesting data"),
SD_VARLINK_DEFINE_OUTPUT(tokenHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Additional HTTP headers to pass when acquiring data"),
SD_VARLINK_DEFINE_OUTPUT(extraHeader, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("IPv4 address of IMDS server"),
SD_VARLINK_DEFINE_OUTPUT(addressIPv4, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("IPv6 address of IMDS server"),
SD_VARLINK_DEFINE_OUTPUT(addressIPv6, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Well-known fields"),
SD_VARLINK_DEFINE_OUTPUT(wellKnown, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(
KeyNotFound);
static SD_VARLINK_DEFINE_ERROR(
WellKnownKeyUnset);
static SD_VARLINK_DEFINE_ERROR(
NotAvailable);
static SD_VARLINK_DEFINE_ERROR(
NotSupported);
static SD_VARLINK_DEFINE_ERROR(
CommunicationFailure);
static SD_VARLINK_DEFINE_ERROR(
Timeout);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_InstanceMetadata,
"io.systemd.InstanceMetadata",
SD_VARLINK_INTERFACE_COMMENT("APIs for acquiring cloud IMDS information."),
SD_VARLINK_SYMBOL_COMMENT("Well known data fields"),
&vl_type_WellKnown,
SD_VARLINK_SYMBOL_COMMENT("Acquire data."),
&vl_method_Get,
SD_VARLINK_SYMBOL_COMMENT("Get information about cloud vendor and IMDS connectivity."),
&vl_method_GetVendorInfo,
SD_VARLINK_SYMBOL_COMMENT("The requested key is not found on the IMDS server."),
&vl_error_KeyNotFound,
SD_VARLINK_SYMBOL_COMMENT("IMDS is disabled or otherwise not available."),
&vl_error_NotAvailable,
SD_VARLINK_SYMBOL_COMMENT("IMDS is not supported."),
&vl_error_NotSupported,
SD_VARLINK_SYMBOL_COMMENT("Well-known key is not set."),
&vl_error_WellKnownKeyUnset,
SD_VARLINK_SYMBOL_COMMENT("Communication with IMDS failed."),
&vl_error_CommunicationFailure,
SD_VARLINK_SYMBOL_COMMENT("Timeout reached"),
&vl_error_Timeout);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-varlink-idl.h"
extern const sd_varlink_interface vl_interface_io_systemd_InstanceMetadata;
+2 -1
View File
@@ -12,7 +12,8 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_VALUE(keyslot),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator),
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity));
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity),
SD_VARLINK_DEFINE_ENUM_VALUE(imds_userdata));
static SD_VARLINK_DEFINE_METHOD(
Extend,
+2
View File
@@ -24,6 +24,7 @@
#include "varlink-io.systemd.FactoryReset.h"
#include "varlink-io.systemd.Hostname.h"
#include "varlink-io.systemd.Import.h"
#include "varlink-io.systemd.InstanceMetadata.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.JournalAccess.h"
#include "varlink-io.systemd.Login.h"
@@ -190,6 +191,7 @@ TEST(parse_format) {
&vl_interface_io_systemd_FactoryReset,
&vl_interface_io_systemd_Hostname,
&vl_interface_io_systemd_Import,
&vl_interface_io_systemd_InstanceMetadata,
&vl_interface_io_systemd_Journal,
&vl_interface_io_systemd_JournalAccess,
&vl_interface_io_systemd_Login,
+2 -1
View File
@@ -15,7 +15,8 @@ in_files = [['basic.conf', true],
['systemd-journal.conf', true],
['systemd-network.conf', conf.get('ENABLE_NETWORKD') == 1],
['systemd-resolve.conf', conf.get('ENABLE_RESOLVE') == 1],
['systemd-timesync.conf', conf.get('ENABLE_TIMESYNCD') == 1]]
['systemd-timesync.conf', conf.get('ENABLE_TIMESYNCD') == 1],
['systemd-imds.conf', conf.get('ENABLE_IMDS') == 1]]
foreach tuple : in_files
file = tuple[0]
+8
View File
@@ -0,0 +1,8 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u! systemd-imds {{SYSTEMD_IMDS_UID}} "systemd Instance Metadata"
@@ -0,0 +1,51 @@
#!/usr/bin/python3
# SPDX-License-Identifier: LGPL-2.1-or-later
import os, socket
from http.server import BaseHTTPRequestHandler, HTTPServer
def sd_notify(state: str) -> bool:
notify_socket = os.environ.get("NOTIFY_SOCKET")
if not notify_socket:
return False
if notify_socket.startswith("@"):
notify_socket = "\0" + notify_socket[1:]
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock:
sock.sendto(state.encode(), notify_socket)
except OSError:
return False
return True
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/userdata":
body = b"{\"systemd.credentials\":[{\"name\":\"acredtest\",\"text\":\"avalue\"}]}"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", len(body))
self.end_headers()
self.wfile.write(body)
elif self.path == "/hostname":
body = b"piff"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", len(body))
self.end_headers()
self.wfile.write(body)
else:
self.send_error(404)
def log_message(self, fmt, *args):
print(f"{self.address_string()} - {fmt % args}")
PORT=8088
server = HTTPServer(("", PORT), Handler)
print(f"Serving on http://localhost:{PORT}/")
try:
sd_notify("READY=1")
server.serve_forever()
except KeyboardInterrupt:
print("\nStopped.")
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
if ! test -x /usr/lib/systemd/systemd-imdsd ; then
echo "No imdsd installed, skipping test."
exit 0
fi
at_exit() {
set +e
systemctl stop fake-imds systemd-imdsd.socket ||:
ip link del dummy0 ||:
rm -f /run/credstore/firstboot.hostname /run/credstore/acredtest /run/systemd/system/systemd-imdsd@.service.d/50-env.conf
rmdir /run/systemd/system/systemd-imdsd@.service.d ||:
}
trap at_exit EXIT
systemd-run -p Type=notify --unit=fake-imds /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py
systemctl status fake-imds
# Add a fake network interface so that IMDS gets going
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 192.168.47.11/24 dev dummy0
USERDATA='{"systemd.credentials":[{"name":"acredtest","text":"avalue"}]}'
# First try imdsd directly
IMDSD="/usr/lib/systemd/systemd-imdsd --vendor=test --data-url=http://192.168.47.11:8088 --well-known-key=userdata:/userdata --well-known-key=hostname:/hostname"
assert_eq "$($IMDSD --well-known=hostname)" "piff"
assert_eq "$($IMDSD --well-known=userdata)" "$USERDATA"
assert_eq "$($IMDSD /hostname)" "piff"
assert_eq "$($IMDSD /userdata)" "$USERDATA"
# Then, try it as Varlink service
mkdir -p /run/systemd/system/systemd-imdsd@.service.d/
cat >/run/systemd/system/systemd-imdsd@.service.d/50-env.conf <<EOF
[Service]
Environment=SYSTEMD_IMDS_VENDOR=test2
Environment=SYSTEMD_IMDS_DATA_URL=http://192.168.47.11:8088
Environment=SYSTEMD_IMDS_KEY_USERDATA=/userdata
Environment=SYSTEMD_IMDS_KEY_HOSTNAME=/hostname
EOF
systemctl daemon-reload
systemctl start systemd-imdsd.socket
assert_eq "$(/usr/lib/systemd/systemd-imds --well-known=hostname)" "piff"
assert_eq "$(/usr/lib/systemd/systemd-imds --well-known=userdata)" "$USERDATA"
assert_eq "$(/usr/lib/systemd/systemd-imds -u)" "$USERDATA"
/usr/lib/systemd/systemd-imds
/usr/lib/systemd/systemd-imds --import
assert_eq "$(cat /run/credstore/firstboot.hostname)" "piff"
assert_eq "$(cat /run/credstore/acredtest)" "avalue"
+16
View File
@@ -392,6 +392,22 @@ units = [
'file' : 'systemd-hybrid-sleep.service.in',
'conditions' : ['ENABLE_HIBERNATE'],
},
{
'file' : 'systemd-imdsd@.service.in',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-imdsd.socket',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-imds-early-network.service.in',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-imds-import.service.in',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-importd.service.in',
'conditions' : ['ENABLE_IMPORTD'],
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Enable Pre-IMDS Networking
Documentation=man:systemd-imdsd@.service(8)
DefaultDependencies=no
Before=network-pre.target
Wants=network-pre.target
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
After=sys-devices-virtual-dmi-id.device
[Service]
ExecStart={{LIBEXECDIR}}/systemd-imdsd --setup-network
Type=oneshot
RemainAfterExit=yes
+25
View File
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Import System Credentials from IMDS
Documentation=man:systemd-imds(1)
Documentation=man:systemd.system-credentials(7)
DefaultDependencies=no
Wants=systemd-imdsd.socket network-online.target
After=systemd-imdsd.socket network-online.target
Before=sysinit.target systemd-firstboot.service
Conflicts=shutdown.target
Before=shutdown.target
ConditionPathExists=/etc/initrd-release
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-imds --import
+28
View File
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Cloud Instance Metadata Access (IMDS)
Documentation=man:systemd-imdsd@.service(8)
DefaultDependencies=no
Before=sockets.target
[Socket]
ListenStream=/run/systemd/io.systemd.InstanceMetadata
Symlinks=/run/varlink/registry/io.systemd.InstanceMetadata
FileDescriptorName=varlink
SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
RemoveOnStop=yes
# Note that this is typically pulled in automatically by
# systemd-imds-generator, but you can also enable it manually if you like.
[Install]
WantedBy=sockets.target
+28
View File
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Cloud Instance Metadata Access (IMDS)
Documentation=man:systemd-imdsd@.service(8)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
After=sys-devices-virtual-dmi-id.device
[Service]
ExecStart=-{{LIBEXECDIR}}/systemd-imdsd
User=systemd-imds
RuntimeDirectory=systemd/imds
RuntimeDirectoryPreserve=yes
# CAP_NET_ADMIN is required to set SO_FWMARK and bypass the routing restrictions, and CAP_NET_BIND_SERVICE to bind to a low port
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
ImportCredential=imds.*