Basic IMDS support (#40980)

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

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

Why all this?

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

Fixes: #40763
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-03-26 14:12:31 +01:00
committed by GitHub
40 changed files with 5584 additions and 7 deletions
+193
View File
@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-hwdb.h"
#include "dropin.h"
#include "fileio.h"
#include "generator.h"
#include "imds-util.h"
#include "log.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "string-util.h"
#include "virt.h"
static int arg_enabled = -1; /* Whether we shall offer local IMDS APIs */
static bool arg_import = true; /* Whether we shall import IMDS credentials, SSH keys, … into the local system */
static ImdsNetworkMode arg_network_mode =
IMDS_NETWORK_LOCKED_DEFAULT ? IMDS_NETWORK_LOCKED : IMDS_NETWORK_UNLOCKED;
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
assert(key);
if (proc_cmdline_key_streq(key, "systemd.imds")) {
if (proc_cmdline_value_missing(key, value))
return 0;
r = parse_tristate_full(value, "auto", &arg_enabled);
if (r < 0)
return log_warning_errno(r, "Failed to parse systemd.imds= value: %m");
} else if (proc_cmdline_key_streq(key, "systemd.imds.import")) {
if (proc_cmdline_value_missing(key, value))
return 0;
r = parse_boolean(value);
if (r < 0)
return log_warning_errno(r, "Failed to parse systemd.imds.import= value: %m");
arg_import = r;
} else if (proc_cmdline_key_streq(key, "systemd.imds.network")) {
if (proc_cmdline_value_missing(key, value))
return 0;
ImdsNetworkMode m = imds_network_mode_from_string(value);
if (m < 0)
return log_warning_errno(m, "Failed to parse systemd.imds.network= value: %m");
arg_network_mode = m;
}
return 0;
}
static int smbios_get_modalias(char **ret) {
int r;
assert(ret);
_cleanup_free_ char *modalias = NULL;
r = read_virtual_file("/sys/devices/virtual/dmi/id/modalias", SIZE_MAX, &modalias, /* ret_size= */ NULL);
if (r < 0)
return r;
truncate_nl(modalias);
/* To detect Azure we need to check the chassis asset tag. Unfortunately the kernel does not include
* it in the modalias string right now. Let's hence append it manually. This matches similar logic in
* rules.d/60-dmi-id.rules. */
_cleanup_free_ char *cat = NULL;
r = read_virtual_file("/sys/devices/virtual/dmi/id/chassis_asset_tag", SIZE_MAX, &cat, /* ret_size= */ NULL);
if (r < 0)
log_debug_errno(r, "Failed to read chassis asset tag, ignoring: %m");
else {
truncate_nl(cat);
if (!string_has_cc(cat, /* ok= */ NULL) && !isempty(cat) && !strextend(&modalias, "cat", cat, ":"))
return -ENOMEM;
}
log_debug("Constructed SMBIOS modalias string: %s", modalias);
*ret = TAKE_PTR(modalias);
return 0;
}
static int smbios_query(void) {
int r;
/* Let's check whether the DMI device's hwdb data suggests IMDS support is available. Note, we cannot
* ask udev for this, as we typically run long before udev. Hence we'll do the hwdb lookup via
* sd-hwdb directly. */
_cleanup_free_ char *modalias = NULL;
r = smbios_get_modalias(&modalias);
if (r == -ENOENT) {
log_debug("No DMI device found, assuming IMDS is not available.");
return false;
}
if (r < 0)
return log_error_errno(r, "Failed to read DMI modalias: %m");
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
r = sd_hwdb_new(&hwdb);
if (r < 0)
return log_error_errno(r, "Failed to open hwdb: %m");
r = sd_hwdb_seek(hwdb, modalias);
if (r < 0)
return log_error_errno(r, "Failed to seek in hwdb for '%s': %m", modalias);
for (;;) {
const char *key, *value;
r = sd_hwdb_enumerate(hwdb, &key, &value);
if (r < 0)
return log_error_errno(r, "Failed to enumerate hwdb entry for '%s': %m", modalias);
if (r == 0)
break;
if (streq(key, "IMDS_VENDOR"))
return true;
}
log_debug("IMDS_VENDOR= property for DMI device not set, assuming IMDS is not available.");
return false;
}
static int run(const char *dest, const char *dest_early, const char *dest_late) {
int r;
r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
if (arg_enabled < 0) {
Virtualization v = detect_container();
if (v < 0)
log_debug_errno(v, "Container detection failed, ignoring: %m");
if (v > 0) {
log_debug("Running in a container, disabling IMDS logic.");
arg_enabled = false;
} else {
r = smbios_query();
if (r < 0)
return r;
arg_enabled = r > 0;
}
}
if (!arg_enabled) {
log_debug("IMDS not enabled, skipping generator.");
return 0;
}
log_info("IMDS support enabled, pull in IMDS units.");
/* Enable IMDS early networking, so that we can actually reach the IMDS server. */
if (arg_network_mode != IMDS_NETWORK_OFF) {
r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-early-network.service");
if (r < 0)
return log_error_errno(r, "Failed to hook in systemd-imds-early-network.service: %m");
}
/* Enable the IMDS service socket */
r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imdsd.socket");
if (r < 0)
return log_error_errno(r, "Failed to hook in systemd-imdsd.socket: %m");
/* We now know the SMBIOS device exists, hence it's safe now to order the IMDS service after it, so
* that it has all properties properly initialized. */
r = write_drop_in(
dest_early,
"systemd-imdsd@.service",
50, "dmi-id",
"# Automatically generated by systemd-imds-generator\n\n"
"[Unit]\n"
"Wants=sys-devices-virtual-dmi-id.device\n"
"After=sys-devices-virtual-dmi-id.device\n");
if (r < 0)
return log_error_errno(r, "Failed to hook DMI id device before systemd-imdsd@.service: %m");
if (arg_import) {
/* Enable that we import IMDS data */
r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-import.service");
if (r < 0)
return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m");
}
return 0;
}
DEFINE_MAIN_GENERATOR_FUNCTION(run);
+896
View File
@@ -0,0 +1,896 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <getopt.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sd-varlink.h"
#include "alloc-util.h"
#include "build.h"
#include "build-path.h"
#include "creds-util.h"
#include "dns-rr.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "imds-util.h"
#include "in-addr-util.h"
#include "io-util.h"
#include "iovec-util.h"
#include "json-util.h"
#include "log.h"
#include "main-func.h"
#include "parse-argument.h"
#include "pcrextend-util.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "tmpfile-util.h"
static enum {
ACTION_SUMMARY,
ACTION_GET,
ACTION_USERDATA,
ACTION_IMPORT,
_ACTION_INVALID = -EINVAL,
} arg_action = _ACTION_INVALID;
static char *arg_key = NULL;
static ImdsWellKnown arg_well_known = _IMDS_WELL_KNOWN_INVALID;
static int arg_cache = -1;
static usec_t arg_refresh_usec = 0;
static bool arg_refresh_usec_set = false;
STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("systemd-imds", "1", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] [KEY]\n"
"\n%sIMDS data acquisition.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -K --well-known=[hostname|region|zone|ipv4-public|ipv6-public|ssh-key|\n"
" userdata|userdata-base|userdata-base64]\n"
" Select well-known key/base\n"
" --refresh=SEC Set minimum freshness time for returned data\n"
" --cache=no Disable cache use\n"
" -u --userdata Dump user data\n"
" --import Import system credentials from IMDS userdata\n"
" and place them in /run/credstore/\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_REFRESH,
ARG_CACHE,
ARG_IMPORT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "well-known", required_argument, NULL, 'K' },
{ "refresh", required_argument, NULL, ARG_REFRESH },
{ "cache", required_argument, NULL, ARG_CACHE },
{ "userdata", no_argument, NULL, 'u' },
{ "import", no_argument, NULL, ARG_IMPORT },
{}
};
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hK:u", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case 'K': {
if (isempty(optarg)) {
arg_well_known = _IMDS_WELL_KNOWN_INVALID;
break;
}
if (streq(optarg, "help"))
return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX);
ImdsWellKnown wk = imds_well_known_from_string(optarg);
if (wk < 0)
return log_error_errno(wk, "Failed to parse --well-known= argument: %s", optarg);
arg_well_known = wk;
break;
}
case ARG_CACHE:
r = parse_tristate_argument_with_auto("--cache=", optarg, &arg_cache);
if (r < 0)
return r;
break;
case ARG_REFRESH: {
if (isempty(optarg)) {
arg_refresh_usec_set = false;
break;
}
usec_t t;
r = parse_sec(optarg, &t);
if (r < 0)
return log_error_errno(r, "Failed to parse refresh timeout: %s", optarg);
arg_refresh_usec = t;
arg_refresh_usec_set = true;
break;
}
case 'u':
arg_action = ACTION_USERDATA;
break;
case ARG_IMPORT:
arg_action = ACTION_IMPORT;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
}
if (IN_SET(arg_action, ACTION_USERDATA, ACTION_IMPORT)) {
if (argc != optind)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No parameters expected.");
} else {
assert(arg_action < 0);
if (argc > optind + 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "None or one argument expected.");
if (argc == optind && arg_well_known < 0)
arg_action = ACTION_SUMMARY;
else {
if (arg_well_known < 0)
arg_well_known = IMDS_BASE;
if (argc > optind) {
if (!imds_key_is_valid(argv[optind]))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", argv[optind]);
if (!imds_well_known_can_suffix(arg_well_known))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' does not take a key suffix, refusing.", imds_well_known_to_string(arg_well_known));
r = free_and_strdup_warn(&arg_key, argv[optind]);
if (r < 0)
return r;
}
arg_action = ACTION_GET;
}
}
return 1;
}
static int acquire_imds_key(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
struct iovec *ret) {
int r;
assert(link);
assert(wk >= 0);
assert(wk < _IMDS_WELL_KNOWN_MAX);
assert(ret);
const char *error_id = NULL;
sd_json_variant *reply = NULL;
r = sd_varlink_callbo(
link,
"io.systemd.InstanceMetadata.Get",
&reply,
&error_id,
SD_JSON_BUILD_PAIR_CONDITION(wk != IMDS_BASE, "wellKnown", JSON_BUILD_STRING_UNDERSCORIFY(imds_well_known_to_string(wk))),
JSON_BUILD_PAIR_STRING_NON_EMPTY("key", key),
SD_JSON_BUILD_PAIR_CONDITION(arg_refresh_usec_set, "refreshUSec", SD_JSON_BUILD_UNSIGNED(arg_refresh_usec)),
SD_JSON_BUILD_PAIR_CONDITION(arg_cache >= 0, "cache", SD_JSON_BUILD_BOOLEAN(arg_cache)));
if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.Get(): %m");
if (error_id) {
if (STR_IN_SET(error_id, "io.systemd.InstanceMetadata.KeyNotFound", "io.systemd.InstanceMetadata.WellKnownKeyUnset")) {
*ret = (struct iovec) {};
return 0;
}
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.Get(): %s", error_id);
}
_cleanup_(iovec_done) struct iovec data = {};
static const sd_json_dispatch_field dispatch_table[] = {
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, 0, SD_JSON_MANDATORY },
{},
};
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &data);
if (r < 0)
return r;
*ret = TAKE_STRUCT(data);
return 1;
}
static int acquire_imds_key_as_string(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
char **ret) {
int r;
assert(link);
assert(wk >= 0);
assert(wk < _IMDS_WELL_KNOWN_MAX);
assert(ret);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, wk, key, &data);
if (r < 0)
return r;
if (r == 0) {
*ret = NULL;
return 0;
}
_cleanup_free_ char *s = NULL;
r = make_cstring(data.iov_base, data.iov_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &s);
if (r < 0)
return r;
*ret = TAKE_PTR(s);
return 1;
}
static int acquire_imds_key_as_ip_address(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
int family,
union in_addr_union *ret) {
int r;
assert(link);
assert(wk >= 0);
assert(wk < _IMDS_WELL_KNOWN_MAX);
assert(ret);
_cleanup_free_ char *s = NULL;
r = acquire_imds_key_as_string(link, wk, key, &s);
if (r < 0)
return r;
if (r == 0 || isempty(s)) {
*ret = (union in_addr_union) {};
return 0;
}
r = in_addr_from_string(family, s, ret);
if (r < 0)
return r;
return 1;
}
static int action_summary(sd_varlink *link) {
int r;
assert(link);
_cleanup_(table_unrefp) Table *table = table_new_vertical();
if (!table)
return log_oom();
const char *error_id = NULL;
sd_json_variant *reply = NULL;
r = sd_varlink_call(
link,
"io.systemd.InstanceMetadata.GetVendorInfo",
/* parameters= */ NULL,
&reply,
&error_id);
if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m");
if (error_id)
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id);
const char *vendor = NULL;
static const sd_json_dispatch_field dispatch_table[] = {
{ "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
{}
};
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &vendor);
if (r < 0)
return r;
if (vendor) {
r = table_add_many(table,
TABLE_FIELD, "Vendor",
TABLE_SET_JSON_FIELD_NAME, "vendor",
TABLE_STRING, vendor);
if (r < 0)
return table_log_add_error(r);
}
static const struct {
ImdsWellKnown well_known;
const char *field;
} wktable[] = {
{ IMDS_HOSTNAME, "Hostname" },
{ IMDS_REGION, "Region" },
{ IMDS_ZONE, "Zone" },
{ IMDS_IPV4_PUBLIC, "Public IPv4 Address" },
{ IMDS_IPV6_PUBLIC, "Public IPv6 Address" },
};
FOREACH_ELEMENT(i, wktable) {
_cleanup_free_ char *text = NULL;
r = acquire_imds_key_as_string(link, i->well_known, /* key= */ NULL, &text);
if (r < 0)
return r;
if (r == 0 || isempty(text))
continue;
r = table_add_many(table,
TABLE_FIELD, i->field,
TABLE_SET_JSON_FIELD_NAME, imds_well_known_to_string(i->well_known),
TABLE_STRING, text);
if (r < 0)
return table_log_add_error(r);
}
if (table_isempty(table))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No well-known IMDS data available.");
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
return 0;
}
static const char *detect_json_object(const char *text) {
assert(text);
/* Checks if the provided text looks like a JSON object. It checks if the first non-whitespace
* characters are {" or {}. */
text += strspn(text, WHITESPACE);
if (*text != '{')
return NULL;
const char *e = text + 1;
e += strspn(e, WHITESPACE);
if (!IN_SET(*e, '"', '}'))
return NULL;
return text;
}
static int write_credential(const char *dir, const char *name, const struct iovec *data) {
int r;
assert(dir);
assert(name);
_cleanup_close_ int dfd = open_mkdir(dir, O_CLOEXEC|O_PATH, 0700);
if (dfd < 0)
return log_error_errno(dfd, "Failed to open credential directory '%s': %m", dir);
if (faccessat(dfd, name, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to check if '%s' exists in credential directory '%s': %m", name, dir);
} else {
log_notice("Skipping importing of credential '%s', it already exists locally in '%s'.", name, dir);
return 0;
}
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = open_tmpfile_linkable_at(dfd, name, O_WRONLY|O_CLOEXEC, &t);
if (fd < 0)
return log_error_errno(fd, "Failed to create credential file '%s/%s': %m", dir, name);
CLEANUP_TMPFILE_AT(dfd, t);
r = loop_write(fd, data->iov_base, data->iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write credential file '%s/%s': %m", dir, name);
if (fchmod(fd, 0400) < 0)
return log_error_errno(errno, "Failed to set access mode on credential file '%s/%s': %m", dir, name);
r = link_tmpfile_at(fd, dfd, t, name, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to move credential file '%s/%s' into place: %m", dir, name);
t = mfree(t); /* Disarm auto-cleanup */
return 1;
}
typedef struct CredentialData {
const char *name;
const char *text;
struct iovec data, encrypted;
} CredentialData;
static void credential_data_done(CredentialData *d) {
assert(d);
iovec_done(&d->data);
iovec_done(&d->encrypted);
}
static int import_credential_one(CredentialData *d) {
int r;
assert(d);
assert(d->name);
log_debug("Importing credential '%s' from IMDS.", d->name);
const char *dir = "/run/credstore";
struct iovec *v, _v;
if (d->text) {
_v = IOVEC_MAKE_STRING(d->text);
v = &_v;
} else if (iovec_is_set(&d->data))
v = &d->data;
else if (iovec_is_set(&d->encrypted)) {
dir = "/run/credstore.encrypted";
v = &d->encrypted;
} else
assert_not_reached();
r = write_credential(dir, d->name, v);
if (r <= 0)
return r;
log_info("Imported credential '%s' from IMDS (%s).", d->name, FORMAT_BYTES(v->iov_len));
return 1;
}
static int import_credentials(const char *text) {
int r;
assert(text);
/* We cannot be sure if the data is actually intended for us. Hence let's be somewhat defensive, and
* accept data in two ways: either immediately as a JSON object, or alternatively marked with a first
* line of "#systemd-userdata". The latter mimics the markers cloud-init employs. */
const char *e = startswith(text, "#systemd-userdata\n");
if (!e) {
e = detect_json_object(text);
if (!e) {
log_info("IMDS user data does not look like JSON or systemd userdata, not processing.");
return 0;
}
}
log_debug("Detected JSON userdata");
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
unsigned line = 0, column = 0;
r = sd_json_parse(e, /* flags= */ 0, &j, &line, &column);
if (r < 0) {
if (line > 0)
log_syntax(/* unit= */ NULL, LOG_WARNING, /* filename= */ NULL, line, r, "JSON parse failure.");
else
log_error_errno(r, "Failed to parse IMDS userdata JSON: %m");
return 0;
}
static const sd_json_dispatch_field top_table[] = {
{ "systemd.credentials", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, 0, 0 },
{},
};
sd_json_variant *creds = NULL;
r = sd_json_dispatch(j, top_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &creds);
if (r < 0)
return r;
unsigned n_imported = 0;
int ret = 0;
if (creds) {
log_debug("Found 'systemd.credentials' field");
sd_json_variant *c;
JSON_VARIANT_ARRAY_FOREACH(c, creds) {
static const sd_json_dispatch_field credential_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, name), SD_JSON_MANDATORY },
{ "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, text), 0 },
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, data), 0 },
{ "encrypted", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, encrypted), 0 },
{},
};
_cleanup_(credential_data_done) CredentialData d = {};
r = sd_json_dispatch(c, credential_table, SD_JSON_LOG|SD_JSON_WARNING, &d);
if (r < 0) {
RET_GATHER(ret, r);
continue;
}
if (!credential_name_valid(d.name)) {
RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential name '%s' is not valid, refusing.", d.name));
continue;
}
if ((!!d.text + !!iovec_is_set(&d.data) + !!iovec_is_set(&d.encrypted)) != 1) {
RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Exactly one of 'text', 'data', 'encrypted' must be set for credential '%s', refusing.", d.name));
continue;
}
r = import_credential_one(&d);
if (r < 0)
RET_GATHER(ret, r);
else if (r > 0)
n_imported++;
}
}
log_full(n_imported == 0 ? LOG_DEBUG : LOG_INFO, "Imported %u credentials from IMDS.", n_imported);
return ret;
}
static int add_public_address_to_json_array(sd_json_variant **array, int family, const union in_addr_union *addr) {
int r;
assert(array);
assert(IN_SET(family, AF_INET, AF_INET6));
assert(addr);
if (in_addr_is_null(family, addr))
return 0;
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
if (dns_resource_record_new_address(&rr, family, addr, "_public") < 0)
return log_oom();
_cleanup_(sd_json_variant_unrefp) sd_json_variant *rrj = NULL;
r = dns_resource_record_to_json(rr, &rrj);
if (r < 0)
return log_error_errno(r, "Failed to convert A RR to JSON: %m");
r = sd_json_variant_append_array(array, rrj);
if (r < 0)
return log_error_errno(r, "Failed to append A RR to JSON array: %m");
log_debug("Writing IMDS RR for: %s", dns_resource_record_to_string(rr));
return 1;
}
static int import_imds_public_addresses(sd_varlink *link) {
int r, ret = 0;
assert(link);
/* Creates local RRs (honoured by systemd-resolved) for our public addresses. */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
union in_addr_union u = {};
r = acquire_imds_key_as_ip_address(link, IMDS_IPV4_PUBLIC, /* key= */ NULL, AF_INET, &u);
if (r < 0)
RET_GATHER(ret, r);
else if (r > 0) {
r = add_public_address_to_json_array(&aj, AF_INET, &u);
if (r < 0)
return r;
}
u = (union in_addr_union) {};
r = acquire_imds_key_as_ip_address(link, IMDS_IPV6_PUBLIC, /* key= */ NULL, AF_INET6, &u);
if (r < 0)
RET_GATHER(ret, r);
else if (r > 0) {
r = add_public_address_to_json_array(&aj, AF_INET6, &u);
if (r < 0)
return r;
}
if (sd_json_variant_elements(aj) == 0) {
log_debug("No IMDS public addresses known, not writing our RRs.");
return 0;
}
_cleanup_free_ char *text = NULL;
r = sd_json_variant_format(aj, SD_JSON_FORMAT_NEWLINE, &text);
if (r < 0)
return log_error_errno(r, "Failed to format JSON text: %m");
r = write_string_file("/run/systemd/resolve/static.d/imds-public.rr", text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
if (r < 0)
return log_error_errno(r, "Failed to write IMDS RR data: %m");
log_debug("IMDS public addresses written out.");
return 1;
}
static int import_imds_ssh_key(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, IMDS_SSH_KEY, /* key= */ NULL, &data);
if (r < 0)
return r;
if (r == 0 || !iovec_is_set(&data)) {
log_debug("No SSH key supplied via IMDS, not importing.");
return 0;
}
r = write_credential("/run/credstore", "ssh.authorized_keys.root", &data);
if (r <= 0)
return r;
log_info("Imported SSH key as credential 'ssh.authorized_keys.root'.");
return 0;
}
static int import_imds_hostname(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, IMDS_HOSTNAME, /* key= */ NULL, &data);
if (r < 0)
return r;
if (r == 0 || !iovec_is_set(&data)) {
log_debug("No hostname supplied via IMDS, not importing.");
return 0;
}
r = write_credential("/run/credstore", "firstboot.hostname", &data);
if (r <= 0)
return r;
log_info("Imported hostname as credential 'firstboot.hostname'.");
return 0;
}
static int acquire_imds_userdata(sd_varlink *link, struct iovec *ret) {
int r;
assert(link);
assert(ret);
/* First try our private namespace, if the concept exists, and then fall back to the singleton */
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, IMDS_USERDATA_BASE, "/systemd-userdata", &data);
if (r == 0)
r = acquire_imds_key(link, IMDS_USERDATA, /* key= */ NULL, &data);
if (r < 0)
return r;
if (r > 0) {
if (!iovec_is_set(&data)) { /* Treat empty user data like empty */
*ret = (struct iovec) {};
return 0;
}
*ret = TAKE_STRUCT(data);
return 1;
}
r = acquire_imds_key(link, IMDS_USERDATA_BASE64, /* key= */ NULL, &data);
if (r < 0)
return r;
_cleanup_(iovec_done) struct iovec decoded = {};
if (r > 0) {
r = unbase64mem_full(data.iov_base, data.iov_len, /* secure= */ false, &decoded.iov_base, &decoded.iov_len);
if (r < 0)
return r;
}
if (!iovec_is_set(&decoded)) { /* Treat empty user data like empty */
*ret = (struct iovec) {};
return 0;
}
*ret = TAKE_STRUCT(decoded);
return 1;
}
static int action_get(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_key(link, arg_well_known, arg_key, &data);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Key not available.");
r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write data to standard output: %m");
return 0;
}
static int action_userdata(sd_varlink *link) {
int r;
assert(link);
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_userdata(link, &data);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "User data not available.");
r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write data to standard output: %m");
return 0;
}
static int remove_userdata(const char *path) {
assert(path);
if (unlink(path) < 0) {
if (errno != ENOENT)
log_debug_errno(errno, "Failed to remove '%s', ignoring: %m", path);
return 0;
}
log_debug("Removed '%s'.", path);
return 1;
}
static int save_userdata(const struct iovec *data, const char *path) {
int r;
assert(data);
assert(path);
if (!iovec_is_set(data))
return remove_userdata(path);
r = write_data_file_atomic_at(AT_FDCWD, path, data, WRITE_DATA_FILE_MKDIR_0755);
if (r < 0)
return log_error_errno(r, "Failed to save userdata to '%s': %m", path);
log_debug("Saved userdata to '%s'.", path);
return 1;
}
static int action_import(sd_varlink *link) {
int r;
assert(link);
int ret = 0;
RET_GATHER(ret, import_imds_public_addresses(link));
RET_GATHER(ret, import_imds_hostname(link));
RET_GATHER(ret, import_imds_ssh_key(link));
_cleanup_(iovec_done) struct iovec data = {};
r = acquire_imds_userdata(link, &data);
if (r < 0)
return RET_GATHER(ret, r);
if (r == 0) {
log_info("No IMDS data available, not importing credentials.");
(void) remove_userdata("/run/systemd/imds/userdata");
return ret;
}
/* Measure the userdata before we use it */
(void) pcrextend_imds_userdata_now(&data);
/* Keep a pristine copy of the userdata we actually applied. (Note that this data is typically also
* kept as cached item on systemd-imdsd, but that one is possibly subject to cache invalidation,
* while this one is supposed to pin the data actually in effect.) */
(void) save_userdata(&data, "/run/systemd/imds/userdata");
/* Ensure no inner NUL byte */
if (memchr(data.iov_base, 0, data.iov_len)) {
log_info("IMDS user data contains NUL byte, not processing.");
return ret;
}
/* Turn this into a proper C string */
if (!iovec_append(&data, &IOVEC_MAKE_BYTE(0)))
return log_oom();
return RET_GATHER(ret, import_credentials(data.iov_base));
}
static int run(int argc, char* argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
_cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata");
if (r < 0) {
if (r != -ENOENT && !ERRNO_IS_NEG_DISCONNECT(r))
return log_error_errno(r, "Failed to connect to systemd-imdsd: %m");
log_debug_errno(r, "Couldn't connect to /run/systemd/io.systemd.InstanceMetadata, will try to fork off systemd-imdsd as child now.");
/* Try to fork off systemd-imdsd as a child as a fallback. If we have privileges and the
* SO_FWMARK trickery is not necessary, then this might just work. */
_cleanup_free_ char *p = NULL;
_cleanup_close_ int pin_fd =
pin_callout_binary(LIBEXECDIR "/systemd-imdsd", &p);
if (pin_fd < 0)
return log_error_errno(pin_fd, "Failed to pick up imdsd binary: %m");
r = sd_varlink_connect_exec(&link, p, /* argv[]= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to connect to imdsd service: %m");
}
switch (arg_action) {
case ACTION_SUMMARY:
return action_summary(link);
case ACTION_GET:
return action_get(link);
case ACTION_USERDATA:
return action_userdata(link);
case ACTION_IMPORT:
return action_import(link);
default:
assert_not_reached();
}
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
+50
View File
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <assert.h>
#include "imds-util.h"
#include "string-table.h"
#include "string-util.h"
#include "utf8.h"
bool imds_key_is_valid(const char *key) {
/* Just some pretty superficial validation. */
if (!key)
return false;
if (!startswith(key, "/"))
return false;
if (!ascii_is_valid(key))
return false;
if (string_has_cc(key, /* ok= */ NULL))
return false;
return true;
}
static const char* const imds_well_known_table[_IMDS_WELL_KNOWN_MAX] = {
[IMDS_BASE] = "base",
[IMDS_HOSTNAME] = "hostname",
[IMDS_REGION] = "region",
[IMDS_ZONE] = "zone",
[IMDS_IPV4_PUBLIC] = "ipv4-public",
[IMDS_IPV6_PUBLIC] = "ipv6-public",
[IMDS_SSH_KEY] = "ssh-key",
[IMDS_USERDATA] = "userdata",
[IMDS_USERDATA_BASE] = "userdata-base",
[IMDS_USERDATA_BASE64] = "userdata-base64",
};
DEFINE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown);
static const char* const imds_network_mode_table[_IMDS_NETWORK_MODE_MAX] = {
[IMDS_NETWORK_OFF] = "off",
[IMDS_NETWORK_LOCKED] = "locked",
[IMDS_NETWORK_UNLOCKED] = "unlocked",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(imds_network_mode, ImdsNetworkMode, IMDS_NETWORK_LOCKED);
+38
View File
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "macro.h"
#include "string-table.h" /* IWYU pragma: keep */
typedef enum ImdsNetworkMode {
IMDS_NETWORK_OFF, /* No automatic pre-IMDS network configuration, something else has to do this. (Also: no "prohibit" route) */
IMDS_NETWORK_LOCKED, /* "Prohibit" route for the IMDS server, unless you have SO_MARK set to 0x7FFF0815 */
IMDS_NETWORK_UNLOCKED, /* No "prohibit" route for the IMDS server */
_IMDS_NETWORK_MODE_MAX,
_IMDS_NETWORK_MODE_INVALID = -EINVAL,
} ImdsNetworkMode;
/* Various well-known keys */
typedef enum ImdsWellKnown {
IMDS_BASE, /* The same as "/", typically suffixed */
IMDS_HOSTNAME,
IMDS_REGION,
IMDS_ZONE,
IMDS_IPV4_PUBLIC,
IMDS_IPV6_PUBLIC,
IMDS_SSH_KEY,
IMDS_USERDATA,
IMDS_USERDATA_BASE, /* typically suffixed */
IMDS_USERDATA_BASE64,
_IMDS_WELL_KNOWN_MAX,
_IMDS_WELL_KNOWN_INVALID = -EINVAL,
} ImdsWellKnown;
static inline bool imds_well_known_can_suffix(ImdsWellKnown wk) {
return IN_SET(wk, IMDS_BASE, IMDS_USERDATA_BASE);
}
bool imds_key_is_valid(const char *key);
DECLARE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown);
DECLARE_STRING_TABLE_LOOKUP(imds_network_mode, ImdsNetworkMode);
+3163
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1-or-later
This file is part of systemd.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
-->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>https://systemd.io</vendor_url>
<action id="io.systemd.imds.get">
<description gettext-domain="systemd">Acquire IMDS instance metadata.</description>
<message gettext-domain="systemd">Authentication is required for an application to acquire IMDS instance metadata.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
+36
View File
@@ -0,0 +1,36 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
if conf.get('ENABLE_IMDS') != 1
subdir_done()
endif
executables += [
libexec_template + {
'name' : 'systemd-imdsd',
'public' : true,
'sources' : files(
'imdsd.c',
'imds-util.c'
) + import_curl_util_c,
'dependencies' : [ libcurl ],
},
libexec_template + {
'name' : 'systemd-imds',
'public' : true,
'sources' : files(
'imds-tool.c',
'imds-util.c'
),
},
generator_template + {
'name' : 'systemd-imds-generator',
'sources' : files(
'imds-generator.c',
'imds-util.c'
),
},
]
install_data(
'io.systemd.imds.policy',
install_dir : polkitpolicydir)
+3 -2
View File
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
import_curl_util_c = files('curl-util.c')
if conf.get('ENABLE_IMPORTD') != 1
subdir_done()
endif
@@ -32,14 +34,13 @@ executables += [
'name' : 'systemd-pull',
'public' : true,
'sources' : files(
'curl-util.c',
'pull.c',
'pull-common.c',
'pull-job.c',
'pull-oci.c',
'pull-raw.c',
'pull-tar.c',
),
) + import_curl_util_c,
'objects' : ['systemd-importd'],
'dependencies' : common_deps + [
libopenssl,
+1
View File
@@ -207,6 +207,7 @@ shared_sources = files(
'varlink-io.systemd.FactoryReset.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Import.c',
'varlink-io.systemd.InstanceMetadata.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.JournalAccess.c',
'varlink-io.systemd.Login.c',
+69
View File
@@ -18,8 +18,10 @@
#include "mountpoint-util.h"
#include "pcrextend-util.h"
#include "pkcs7-util.h"
#include "sha256.h"
#include "string-util.h"
#include "strv.h"
#include "tpm2-pcr.h"
static int device_get_file_system_word(
sd_device *d,
@@ -291,3 +293,70 @@ int pcrextend_verity_now(
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures.");
#endif
}
#define IMDS_USERDATA_TRUNCATED_MAX 256U
int pcrextend_imds_userdata_word(const struct iovec *data, char **ret) {
assert(iovec_is_set(data));
assert(ret);
/* We include both a hash of the complete user data, and a truncated version of the data in the word
* we measure. The former protects the actual data, the latter is useful for debugging. */
_cleanup_free_ char *hash = hexmem(SHA256_DIRECT(data->iov_base, data->iov_len), SHA256_DIGEST_SIZE);
if (!hash)
return log_oom();
_cleanup_free_ char *data_encoded = NULL;
if (base64mem_full(data->iov_base, MIN(data->iov_len, IMDS_USERDATA_TRUNCATED_MAX), /* line_break= */ SIZE_MAX, &data_encoded) < 0)
return log_oom();
_cleanup_free_ char *word = strjoin("imds-userdata:", hash, ":", data_encoded);
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_imds_userdata_now(const struct iovec *data) {
#if HAVE_TPM2
int r;
_cleanup_free_ char *word = NULL;
r = pcrextend_imds_userdata_word(data, &word);
if (r < 0)
return r;
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend");
if (r < 0)
return r;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
const char *error_id = NULL;
r = sd_varlink_callbo(
vl,
"io.systemd.PCRExtend.Extend",
/* ret_reply= */ NULL,
&error_id,
SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_CONFIG),
SD_JSON_BUILD_PAIR_STRING("text", word),
SD_JSON_BUILD_PAIR_STRING("eventType", "imds_userdata"));
if (r < 0)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
if (error_id) {
r = sd_varlink_error_to_errno(error_id, reply);
if (r != -EBADR)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id);
}
log_debug("Measurement of '%s' into PCR 12 completed.", word);
return 1;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring IMDS userdata.");
#endif
}
+5 -1
View File
@@ -1,9 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/uio.h>
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
int pcrextend_machine_id_word(char **ret);
int pcrextend_product_id_word(char **ret);
int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret);
int pcrextend_imds_userdata_word(const struct iovec *data, char **ret);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig);
int pcrextend_imds_userdata_now(const struct iovec *data);
+1
View File
@@ -6675,6 +6675,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_NVPCR_INIT] = "nvpcr-init",
[TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator",
[TPM2_EVENT_DM_VERITY] = "dm-verity",
[TPM2_EVENT_IMDS_USERDATA] = "imds-userdata",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
+1
View File
@@ -148,6 +148,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_NVPCR_INIT,
TPM2_EVENT_NVPCR_SEPARATOR,
TPM2_EVENT_DM_VERITY,
TPM2_EVENT_IMDS_USERDATA,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "bus-polkit.h"
#include "varlink-io.systemd.InstanceMetadata.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
WellKnown,
SD_VARLINK_DEFINE_ENUM_VALUE(base),
SD_VARLINK_DEFINE_ENUM_VALUE(hostname),
SD_VARLINK_DEFINE_ENUM_VALUE(region),
SD_VARLINK_DEFINE_ENUM_VALUE(zone),
SD_VARLINK_DEFINE_ENUM_VALUE(ipv4_public),
SD_VARLINK_DEFINE_ENUM_VALUE(ipv6_public),
SD_VARLINK_DEFINE_ENUM_VALUE(ssh_key),
SD_VARLINK_DEFINE_ENUM_VALUE(userdata),
SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base),
SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base64));
static SD_VARLINK_DEFINE_METHOD(
Get,
SD_VARLINK_FIELD_COMMENT("The key to retrieve"),
SD_VARLINK_DEFINE_INPUT(key, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Start with a well-known key"),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(wellKnown, WellKnown, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The network interface to use"),
SD_VARLINK_DEFINE_INPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Refresh cached data if older (CLOCK_BOOTTIME, µs)"),
SD_VARLINK_DEFINE_INPUT(refreshUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether to accept cached data"),
SD_VARLINK_DEFINE_INPUT(cache, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The firewall mark value to use"),
SD_VARLINK_DEFINE_INPUT(firewallMark, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Controls whether to wait for connectivity"),
SD_VARLINK_DEFINE_INPUT(wait, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("The data in Base64 encoding."),
SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The interface the data was found on."),
SD_VARLINK_DEFINE_OUTPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
GetVendorInfo,
SD_VARLINK_FIELD_COMMENT("The detected cloud vendor"),
SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The URL to acquire the token from"),
SD_VARLINK_DEFINE_OUTPUT(tokenUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The HTTP header to configure the refresh timeout for the token in"),
SD_VARLINK_DEFINE_OUTPUT(refreshHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The base URL to acquire the data from"),
SD_VARLINK_DEFINE_OUTPUT(dataUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("A suffix to append to the data URL"),
SD_VARLINK_DEFINE_OUTPUT(dataUrlSuffix, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The HTTP header to pass the token in when requesting data"),
SD_VARLINK_DEFINE_OUTPUT(tokenHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Additional HTTP headers to pass when acquiring data"),
SD_VARLINK_DEFINE_OUTPUT(extraHeader, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("IPv4 address of IMDS server"),
SD_VARLINK_DEFINE_OUTPUT(addressIPv4, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("IPv6 address of IMDS server"),
SD_VARLINK_DEFINE_OUTPUT(addressIPv6, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Well-known fields"),
SD_VARLINK_DEFINE_OUTPUT(wellKnown, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(
KeyNotFound);
static SD_VARLINK_DEFINE_ERROR(
WellKnownKeyUnset);
static SD_VARLINK_DEFINE_ERROR(
NotAvailable);
static SD_VARLINK_DEFINE_ERROR(
NotSupported);
static SD_VARLINK_DEFINE_ERROR(
CommunicationFailure);
static SD_VARLINK_DEFINE_ERROR(
Timeout);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_InstanceMetadata,
"io.systemd.InstanceMetadata",
SD_VARLINK_INTERFACE_COMMENT("APIs for acquiring cloud IMDS information."),
SD_VARLINK_SYMBOL_COMMENT("Well known data fields"),
&vl_type_WellKnown,
SD_VARLINK_SYMBOL_COMMENT("Acquire data."),
&vl_method_Get,
SD_VARLINK_SYMBOL_COMMENT("Get information about cloud vendor and IMDS connectivity."),
&vl_method_GetVendorInfo,
SD_VARLINK_SYMBOL_COMMENT("The requested key is not found on the IMDS server."),
&vl_error_KeyNotFound,
SD_VARLINK_SYMBOL_COMMENT("IMDS is disabled or otherwise not available."),
&vl_error_NotAvailable,
SD_VARLINK_SYMBOL_COMMENT("IMDS is not supported."),
&vl_error_NotSupported,
SD_VARLINK_SYMBOL_COMMENT("Well-known key is not set."),
&vl_error_WellKnownKeyUnset,
SD_VARLINK_SYMBOL_COMMENT("Communication with IMDS failed."),
&vl_error_CommunicationFailure,
SD_VARLINK_SYMBOL_COMMENT("Timeout reached"),
&vl_error_Timeout);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-varlink-idl.h"
extern const sd_varlink_interface vl_interface_io_systemd_InstanceMetadata;
+2 -1
View File
@@ -12,7 +12,8 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_VALUE(keyslot),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator),
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity));
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity),
SD_VARLINK_DEFINE_ENUM_VALUE(imds_userdata));
static SD_VARLINK_DEFINE_METHOD(
Extend,
+2
View File
@@ -24,6 +24,7 @@
#include "varlink-io.systemd.FactoryReset.h"
#include "varlink-io.systemd.Hostname.h"
#include "varlink-io.systemd.Import.h"
#include "varlink-io.systemd.InstanceMetadata.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.JournalAccess.h"
#include "varlink-io.systemd.Login.h"
@@ -190,6 +191,7 @@ TEST(parse_format) {
&vl_interface_io_systemd_FactoryReset,
&vl_interface_io_systemd_Hostname,
&vl_interface_io_systemd_Import,
&vl_interface_io_systemd_InstanceMetadata,
&vl_interface_io_systemd_Journal,
&vl_interface_io_systemd_JournalAccess,
&vl_interface_io_systemd_Login,