mirror of
https://github.com/systemd/systemd
synced 2024-09-06 16:56:43 +00:00
os-release: add new PORTABLE_PREFIXES= field for declaring valid portable service match prefixes
This commit is contained in:
parent
60c5f7002b
commit
8a129c808a
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -162,6 +162,7 @@ int bus_image_common_get_metadata(
|
|||
extension_images,
|
||||
&os_release,
|
||||
&unit_files,
|
||||
NULL,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
|
Loading…
Reference in a new issue