Skip to content

Commit

Permalink
Merge pull request #3165 from cgwalters/drop-ex-integrity
Browse files Browse the repository at this point in the history
deploy: Honor prepare-root.conf at deploy time for composefs
  • Loading branch information
cgwalters authored Feb 9, 2024
2 parents 751ec90 + cae4ceb commit 1c18bd2
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 88 deletions.
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

0 comments on commit 1c18bd2

Please sign in to comment.