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:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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', [], ''],
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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&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>
|
||||
@@ -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
@@ -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'],
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
+51
@@ -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.")
|
||||
Executable
+62
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.*
|
||||
Reference in New Issue
Block a user