Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deploy: Honor prepare-root.conf at deploy time for composefs #3165

Merged
merged 4 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile-tests.am
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ _installed_or_uninstalled_test_scripts = \
tests/test-admin-upgrade-systemd-update.sh \
tests/test-admin-deploy-syslinux.sh \
tests/test-admin-deploy-bootprefix.sh \
tests/test-admin-deploy-composefs.sh \
tests/test-admin-deploy-2.sh \
tests/test-admin-deploy-karg.sh \
tests/test-admin-deploy-switch.sh \
Expand Down
36 changes: 34 additions & 2 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,34 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
cancellable, error))
return FALSE;

glnx_autofd int ret_deployment_dfd = -1;
if (!glnx_opendirat (osdeploy_dfd, checkout_target_name, TRUE, &ret_deployment_dfd, error))
return FALSE;

#ifdef HAVE_COMPOSEFS
if (repo->composefs_wanted != OT_TRISTATE_NO)
/* TODO: Consider changing things in the future to parse the deployment config from memory, and
* if composefs is enabled, then we can check out in "user mode" (i.e. only have suid binaries
* enabled in composefs, etc.)
*
* However in practice we should get this for free by going to composefs-native backing
* storage.
*/
g_autoptr (GKeyFile) prepare_root_config
= otcore_load_config (ret_deployment_dfd, PREPARE_ROOT_CONFIG_PATH, error);
if (!prepare_root_config)
return glnx_prefix_error (error, "Parsing prepare-root config");
// We always parse the composefs config, because we want to detect and error
// out if it's enabled, but not supported at compile time.
g_autoptr (ComposefsConfig) composefs_config
= otcore_load_composefs_config (prepare_root_config, error);
if (!composefs_config)
return glnx_prefix_error (error, "Reading composefs config");

OtTristate composefs_enabled = composefs_config->enabled;
g_debug ("composefs enabled by config: %d repo: %d", composefs_enabled, repo->composefs_wanted);
if (repo->composefs_wanted == OT_TRISTATE_YES)
composefs_enabled = repo->composefs_wanted;
if (composefs_enabled == OT_TRISTATE_YES)
{
g_autofree guchar *fsverity_digest = NULL;
g_auto (GLnxTmpfile) tmpf = {
Expand Down Expand Up @@ -691,6 +717,8 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
g_autofree char *composefs_cfs_path
= g_strdup_printf ("%s/" OSTREE_COMPOSEFS_NAME, checkout_target_name);

g_debug ("writing %s", composefs_cfs_path);

if (!glnx_open_tmpfile_linkable_at (osdeploy_dfd, checkout_target_name, O_WRONLY | O_CLOEXEC,
&tmpf, error))
return FALSE;
Expand All @@ -712,9 +740,13 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
error))
return FALSE;
}
else
g_debug ("not using composefs");
#endif

return glnx_opendirat (osdeploy_dfd, checkout_target_name, TRUE, out_deployment_dfd, error);
if (out_deployment_dfd)
*out_deployment_dfd = glnx_steal_fd (&ret_deployment_dfd);
return TRUE;
}

static char *
Expand Down
72 changes: 72 additions & 0 deletions src/libotcore/otcore-prepare-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@

#include "otcore.h"

// This key is used by default if present in the initramfs to verify
// the signature on the target commit object. When composefs is
// in use, the ostree commit metadata will contain the composefs image digest,
// which can be used to fully verify the target filesystem tree.
#define BINDING_KEYPATH "/etc/ostree/initramfs-root-binding.key"

static bool
proc_cmdline_has_key_starting_with (const char *cmdline, const char *key)
{
Expand Down Expand Up @@ -137,3 +143,69 @@ otcore_load_config (int rootfs_fd, const char *filename, GError **error)

return g_steal_pointer (&ret);
}

void
otcore_free_composefs_config (ComposefsConfig *config)
{
g_clear_pointer (&config->pubkeys, g_ptr_array_unref);
g_free (config->signature_pubkey);
g_free (config);
}

// Parse the [composefs] section of the prepare-root.conf.
ComposefsConfig *
otcore_load_composefs_config (GKeyFile *config, GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Loading composefs config", error);

g_autoptr (ComposefsConfig) ret = g_new0 (ComposefsConfig, 1);

g_autofree char *enabled = g_key_file_get_value (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY,
OTCORE_PREPARE_ROOT_ENABLED_KEY, NULL);
if (g_strcmp0 (enabled, "signed") == 0)
{
ret->enabled = OT_TRISTATE_YES;
ret->is_signed = true;
}
else if (!ot_keyfile_get_tristate_with_default (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY,
OTCORE_PREPARE_ROOT_ENABLED_KEY,
OT_TRISTATE_MAYBE, &ret->enabled, error))
return NULL;

// Look for a key - we default to the initramfs binding path.
if (!ot_keyfile_get_value_with_default (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY,
OTCORE_PREPARE_ROOT_KEYPATH_KEY, BINDING_KEYPATH,
&ret->signature_pubkey, error))
return NULL;

if (ret->is_signed)
{
ret->pubkeys = g_ptr_array_new_with_free_func ((GDestroyNotify)g_bytes_unref);

g_autofree char *pubkeys = NULL;
gsize pubkeys_size;

/* Load keys */

if (!g_file_get_contents (ret->signature_pubkey, &pubkeys, &pubkeys_size, error))
return glnx_prefix_error_null (error, "Reading public key file '%s'",
ret->signature_pubkey);

g_auto (GStrv) lines = g_strsplit (pubkeys, "\n", -1);
for (char **iter = lines; *iter; iter++)
{
const char *line = *iter;
if (!*line)
continue;

gsize pubkey_size;
g_autofree guchar *pubkey = g_base64_decode (line, &pubkey_size);
g_ptr_array_add (ret->pubkeys, g_bytes_new_take (g_steal_pointer (&pubkey), pubkey_size));
}

if (ret->pubkeys->len == 0)
return glnx_null_throw (error, "public key file specified, but no public keys found");
}

return g_steal_pointer (&ret);
}
18 changes: 18 additions & 0 deletions src/libotcore/otcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,27 @@ gboolean otcore_get_ostree_target (const char *cmdline, char **out_target, GErro

GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error);

typedef struct
{
OtTristate enabled;
gboolean is_signed;
char *signature_pubkey;
GPtrArray *pubkeys;
} ComposefsConfig;
void otcore_free_composefs_config (ComposefsConfig *config);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComposefsConfig, otcore_free_composefs_config)

ComposefsConfig *otcore_load_composefs_config (GKeyFile *config, GError **error);

// Our directory with transient state (eventually /run/ostree-booted should be a link to
// /run/ostree/booted)
#define OTCORE_RUN_OSTREE "/run/ostree"
// This sub-directory is transient state that should not be visible to other processes in general;
// we make it with mode 0 (which requires CAP_DAC_OVERRIDE to pass through).
#define OTCORE_RUN_OSTREE_PRIVATE "/run/ostree/.private"

#define PREPARE_ROOT_CONFIG_PATH "ostree/prepare-root.conf"

// The directory holding extra/backing data for a deployment, such as overlayfs workdirs
#define OSTREE_DEPLOYMENT_BACKING_DIR "backing"
// The directory holding the root overlayfs
Expand All @@ -70,6 +84,10 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error);
// EROFS mount if we somehow leaked it (but it *should* be unmounted always).
#define OSTREE_COMPOSEFS_LOWERMNT OTCORE_RUN_OSTREE_PRIVATE "/cfsroot-lower"

#define OTCORE_PREPARE_ROOT_COMPOSEFS_KEY "composefs"
#define OTCORE_PREPARE_ROOT_ENABLED_KEY "enabled"
#define OTCORE_PREPARE_ROOT_KEYPATH_KEY "keypath"

// The file written in the initramfs which contains an a{sv} of metadata
// from ostree-prepare-root.
#define OTCORE_RUN_BOOTED "/run/ostree-booted"
Expand Down
87 changes: 1 addition & 86 deletions src/switchroot/ostree-prepare-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,6 @@
#include "ot-keyfile-utils.h"
#include "otcore.h"

#define PREPARE_ROOT_CONFIG_PATH "ostree/prepare-root.conf"

// This key is used by default if present in the initramfs to verify
// the signature on the target commit object. When composefs is
// in use, the ostree commit metadata will contain the composefs image digest,
// which can be used to fully verify the target filesystem tree.
#define BINDING_KEYPATH "/etc/ostree/initramfs-root-binding.key"

#define SYSROOT_KEY "sysroot"
#define READONLY_KEY "readonly"

Expand All @@ -92,10 +84,6 @@
#define ETC_KEY "etc"
#define TRANSIENT_KEY "transient"

#define COMPOSEFS_KEY "composefs"
#define ENABLED_KEY "enabled"
#define KEYPATH_KEY "keypath"

#define OSTREE_PREPARE_ROOT_DEPLOYMENT_MSG \
SD_ID128_MAKE (71, 70, 33, 6a, 73, ba, 46, 01, ba, d3, 1a, f8, 88, aa, 0d, f7)

Expand Down Expand Up @@ -258,79 +246,6 @@ composefs_error_message (int errsv)

#endif

typedef struct
{
OtTristate enabled;
gboolean is_signed;
char *signature_pubkey;
GPtrArray *pubkeys;
} ComposefsConfig;

static void
free_composefs_config (ComposefsConfig *config)
{
g_ptr_array_unref (config->pubkeys);
g_free (config->signature_pubkey);
g_free (config);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComposefsConfig, free_composefs_config)

// Parse the [composefs] section of the prepare-root.conf.
static ComposefsConfig *
load_composefs_config (GKeyFile *config, GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Loading composefs config", error);

g_autoptr (ComposefsConfig) ret = g_new0 (ComposefsConfig, 1);

g_autofree char *enabled = g_key_file_get_value (config, COMPOSEFS_KEY, ENABLED_KEY, NULL);
if (g_strcmp0 (enabled, "signed") == 0)
{
ret->enabled = OT_TRISTATE_YES;
ret->is_signed = true;
}
else if (!ot_keyfile_get_tristate_with_default (config, COMPOSEFS_KEY, ENABLED_KEY,
OT_TRISTATE_MAYBE, &ret->enabled, error))
return NULL;

// Look for a key - we default to the initramfs binding path.
if (!ot_keyfile_get_value_with_default (config, COMPOSEFS_KEY, KEYPATH_KEY, BINDING_KEYPATH,
&ret->signature_pubkey, error))
return NULL;

if (ret->is_signed)
{
ret->pubkeys = g_ptr_array_new_with_free_func ((GDestroyNotify)g_bytes_unref);

g_autofree char *pubkeys = NULL;
gsize pubkeys_size;

/* Load keys */

if (!g_file_get_contents (ret->signature_pubkey, &pubkeys, &pubkeys_size, error))
return glnx_prefix_error_null (error, "Reading public key file '%s'",
ret->signature_pubkey);

g_auto (GStrv) lines = g_strsplit (pubkeys, "\n", -1);
for (char **iter = lines; *iter; iter++)
{
const char *line = *iter;
if (!*line)
continue;

gsize pubkey_size;
g_autofree guchar *pubkey = g_base64_decode (line, &pubkey_size);
g_ptr_array_add (ret->pubkeys, g_bytes_new_take (g_steal_pointer (&pubkey), pubkey_size));
}

if (ret->pubkeys->len == 0)
return glnx_null_throw (error, "public key file specified, but no public keys found");
}

return g_steal_pointer (&ret);
}

int
main (int argc, char *argv[])
{
Expand Down Expand Up @@ -362,7 +277,7 @@ main (int argc, char *argv[])

// We always parse the composefs config, because we want to detect and error
// out if it's enabled, but not supported at compile time.
g_autoptr (ComposefsConfig) composefs_config = load_composefs_config (config, &error);
g_autoptr (ComposefsConfig) composefs_config = otcore_load_composefs_config (config, &error);
if (!composefs_config)
errx (EXIT_FAILURE, "%s", error->message);

Expand Down
5 changes: 5 additions & 0 deletions tests/admin-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ assert_not_file_has_content status.txt "pending"
assert_not_file_has_content status.txt "rollback"
validate_bootloader

# Someday probably soon we'll turn this on by default, but for now
if test -f sysroot/ostree/deploy/testos/deploy/*.0/.ostree.cfs; then
fatal "found composefs unexpectedly"
fi

# Test the bootable and linux keys
${CMD_PREFIX} ostree --repo=sysroot/ostree/repo --print-metadata-key=ostree.linux show testos:testos/buildmain/x86_64-runtime >out.txt
assert_file_has_content_literal out.txt 3.6.0
Expand Down
42 changes: 42 additions & 0 deletions tests/test-admin-deploy-composefs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
#
# Copyright (C) 2024 Red Hat, Inc.
#
# SPDX-License-Identifier: LGPL-2.0+
#
# This library 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 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <https://www.gnu.org/licenses/>.

set -euox pipefail

. $(dirname $0)/libtest.sh

# Exports OSTREE_SYSROOT so --sysroot not needed.
setup_os_repository "archive" "syslinux"

cd osdata
mkdir -p usr/lib/ostree
cat > usr/lib/ostree/prepare-root.conf << 'EOF'
[composefs]
enabled=true
EOF
${CMD_PREFIX} ostree --repo=${test_tmpdir}/testos-repo commit --add-metadata-string version=1.composefs -b testos/buildmain/x86_64-runtime
cd -
${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmain/x86_64-runtime

${CMD_PREFIX} ostree admin deploy --os=testos --karg=root=LABEL=foo --karg=testkarg=1 testos:testos/buildmain/x86_64-runtime
ls sysroot/ostree/deploy/testos/deploy/*.0/.ostree.cfs

tap_ok composefs

tap_end
Loading