os-release: add new PORTABLE_PREFIXES= field for declaring valid portable service match prefixes

This commit is contained in:
Lennart Poettering 2021-11-19 16:19:19 +01:00
parent 60c5f7002b
commit 8a129c808a
5 changed files with 154 additions and 44 deletions

View file

@ -247,6 +247,20 @@ image. To facilitate 3 and 4 you also need to include a boot loader in the
image. As mentioned, `mkosi -b` takes care of all of that for you, but any
other image generator should work too.
The
[os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html)
file may optionally be extended with a `PORTABLE_PREFIXES=` field listing all
supported portable service prefixes for the image (see above). This is useful
for informational purposes (as it allows recognizing portable service images
from their contents as such), but is also useful to protect the image from
being used under a wrong name and prefix. This is particularly relevant if the
images are cryptographically authenticated (via Verity or a similar mechanism)
as this way the (not necessarily authenticated) image file name can be
validated against the (authenticated) image contents. If the field is not
specified the image will work fine, but is not necessarily recognizable as
portable service image, and any set of units included in the image may be
attached, there are no restrictions enforced.
## Extension Images
Portable services can be delivered as one or multiple images that extend the base

View file

@ -419,6 +419,17 @@
regular systems and to portable service environments, but not to initrd
environments.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>PORTABLE_PREFIXES=</varname></term>
<listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
<ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> logic. This field
serves two purposes: it's informational, identifying portable service images as such (and thus
allowing them to be distinguished from other OS images, such as bootable system images); whenever a
portable service image is attached the specified or implied portable service prefix is checked
against this list, to enforce restrictions how images may be attached to a
system.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>

View file

@ -13,6 +13,7 @@
#include "discover-image.h"
#include "dissect-image.h"
#include "env-file.h"
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
#include "extension-release.h"
@ -509,20 +510,20 @@ static int extract_image_and_extensions(
OrderedHashmap **ret_extension_images,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
char ***ret_valid_prefixes,
sd_bus_error *error) {
_cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL;
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_strv_free_ char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
Image *ext;
int r;
assert(name_or_path);
assert(matches);
assert(ret_image);
assert(ret_extension_images);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0)
@ -553,10 +554,12 @@ static int extract_image_and_extensions(
if (r < 0)
return r;
/* If we are layering extension images on top of a runtime image, check that the os-release and extension-release metadata
* match, otherwise reject it immediately as invalid, or it will fail when the units are started. */
if (validate_sysext) {
/* If we are layering extension images on top of a runtime image, check that the os-release and
* extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
* the units are started. Also, collect valid portable prefixes if caller requested that. */
if (validate_sysext || ret_valid_prefixes) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *prefixes = NULL;
r = take_fdopen_unlocked(&os_release->fd, "r", &f);
if (r < 0)
@ -565,9 +568,16 @@ static int extract_image_and_extensions(
r = parse_env_file(f, os_release->name,
"ID", &id,
"VERSION_ID", &version_id,
"SYSEXT_LEVEL", &sysext_level);
"SYSEXT_LEVEL", &sysext_level,
"PORTABLE_PREFIXES", &prefixes);
if (r < 0)
return r;
if (prefixes) {
valid_prefixes = strv_split(prefixes, WHITESPACE);
if (!valid_prefixes)
return -ENOMEM;
}
}
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
@ -575,6 +585,7 @@ static int extract_image_and_extensions(
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
_cleanup_strv_free_ char **extension_release = NULL;
_cleanup_fclose_ FILE *f = NULL;
const char *e;
r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error);
if (r < 0)
@ -584,7 +595,7 @@ static int extract_image_and_extensions(
if (r < 0)
return r;
if (!validate_sysext)
if (!validate_sysext && !ret_valid_prefixes)
continue;
r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f);
@ -595,19 +606,40 @@ static int extract_image_and_extensions(
if (r < 0)
return r;
r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
if (validate_sysext) {
r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
}
e = strv_env_pairs_get(extension_release, "PORTABLE_PREFIXES");
if (e) {
_cleanup_strv_free_ char **l = NULL;
l = strv_split(e, WHITESPACE);
if (!l)
return -ENOMEM;
r = strv_extend_strv(&valid_prefixes, l, true);
if (r < 0)
return r;
}
}
*ret_image = TAKE_PTR(image);
*ret_extension_images = TAKE_PTR(extension_images);
strv_sort(valid_prefixes);
if (ret_image)
*ret_image = TAKE_PTR(image);
if (ret_extension_images)
*ret_extension_images = TAKE_PTR(extension_images);
if (ret_os_release)
*ret_os_release = TAKE_PTR(os_release);
if (ret_unit_files)
*ret_unit_files = TAKE_PTR(unit_files);
if (ret_valid_prefixes)
*ret_valid_prefixes = TAKE_PTR(valid_prefixes);
return 0;
}
@ -618,23 +650,29 @@ int portable_extract(
char **extension_image_paths,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
char ***ret_valid_prefixes,
sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(strv_freep) char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
int r;
r = extract_image_and_extensions(name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ false,
&image,
&extension_images,
&os_release,
&unit_files,
error);
assert(name_or_path);
r = extract_image_and_extensions(
name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ false,
&image,
&extension_images,
&os_release,
&unit_files,
ret_valid_prefixes ? &valid_prefixes : NULL,
error);
if (r < 0)
return r;
@ -651,8 +689,12 @@ int portable_extract(
isempty(extensions) ? "" : extensions);
}
*ret_os_release = TAKE_PTR(os_release);
*ret_unit_files = TAKE_PTR(unit_files);
if (ret_os_release)
*ret_os_release = TAKE_PTR(os_release);
if (ret_unit_files)
*ret_unit_files = TAKE_PTR(unit_files);
if (ret_valid_prefixes)
*ret_valid_prefixes = TAKE_PTR(valid_prefixes);
return 0;
}
@ -1211,6 +1253,18 @@ static int install_image_and_extensions_symlinks(
return 0;
}
static bool prefix_matches_compatible(char **matches, char **valid_prefixes) {
char **m;
/* Checks if all 'matches' are included in the list of 'valid_prefixes' */
STRV_FOREACH(m, matches)
if (!strv_contains(valid_prefixes, *m))
return false;
return true;
}
int portable_attach(
sd_bus *bus,
const char *name_or_path,
@ -1225,33 +1279,63 @@ int portable_attach(
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_strv_free_ char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
PortableMetadata *item;
int r;
r = extract_image_and_extensions(name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ true,
&image,
&extension_images,
/* os_release= */ NULL,
&unit_files,
error);
r = extract_image_and_extensions(
name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ true,
&image,
&extension_images,
/* os_release= */ NULL,
&unit_files,
&valid_prefixes,
error);
if (r < 0)
return r;
if (hashmap_isempty(unit_files)) {
_cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
if (!extensions)
if (valid_prefixes && !prefix_matches_compatible(matches, valid_prefixes)) {
_cleanup_free_ char *matches_joined = NULL, *extensions_joined = NULL, *valid_prefixes_joined = NULL;
matches_joined = strv_join(matches, "', '");
if (!matches_joined)
return -ENOMEM;
return sd_bus_error_setf(error,
SD_BUS_ERROR_INVALID_ARGS,
"Couldn't find any matching unit files in image '%s%s%s', refusing.",
image->path,
isempty(extensions) ? "" : "' or any of its extensions '",
isempty(extensions) ? "" : extensions);
extensions_joined = strv_join(extension_image_paths, ", ");
if (!extensions_joined)
return -ENOMEM;
valid_prefixes_joined = strv_join(valid_prefixes, ", ");
if (!valid_prefixes_joined)
return -ENOMEM;
return sd_bus_error_setf(
error,
SD_BUS_ERROR_INVALID_ARGS,
"Selected matches '%s' are not compatible with portable service image '%s%s%s', refusing. (Acceptable prefix matches are: %s)",
matches_joined,
image->path,
isempty(extensions_joined) ? "" : "' or any of its extensions '",
strempty(extensions_joined),
valid_prefixes_joined);
}
if (hashmap_isempty(unit_files)) {
_cleanup_free_ char *extensions_joined = strv_join(extension_image_paths, ", ");
if (!extensions_joined)
return -ENOMEM;
return sd_bus_error_setf(
error,
SD_BUS_ERROR_INVALID_ARGS,
"Couldn't find any matching unit files in image '%s%s%s', refusing.",
image->path,
isempty(extensions_joined) ? "" : "' or any of its extensions '",
strempty(extensions_joined));
}
r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL);

View file

@ -65,7 +65,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);

View file

@ -162,6 +162,7 @@ int bus_image_common_get_metadata(
extension_images,
&os_release,
&unit_files,
NULL,
error);
if (r < 0)
return r;