mirror of
https://github.com/ostreedev/ostree.git
synced 2025-01-21 22:04:15 +03:00
Read composefs configuration from initrd instead of commandline
This drops the `ot-composefs` kernel commandline in favour of a `[composefs]` section in the `prepare-rootfs.conf` file. You can set `composefs.enabled` to `signed`, `yes`, `no` or `maybe`, with `maybe` being the default. You can also set `composefs.keypath` (or rely on the default `/etc/ostree/initramfs-root-binding.key`) to point to ed25519 public keys, one of which which the commit must be signed with, or boot fails. The ostree dracut module adds `/etc/ostree/initramfs-root-binding.key` to the initrd if it exists. NOTE: This drop the option to define a digest in the commandline. However, that was currently unused (i.e. ComposefsConfig.expected_digest was never read). Additionally it very hard to actually store the composefs digest in the initrd, as the initrd is typically part of the commit and thus the composefs. It may be possible to handle this, but lets add it back when we know exactly how that will work.
This commit is contained in:
parent
2cc6b53199
commit
81fa214155
@ -30,21 +30,13 @@ have a `.ostree.cfs` file in the deployment directory which is a mountable
|
||||
composefs metadata file, with a "backing store" directory that is
|
||||
shared with the current `/ostree/repo/objects`.
|
||||
|
||||
### Kernel argument ot-composefs
|
||||
### composefs configuration
|
||||
|
||||
The `ostree-prepare-root` binary will look for a kernel argument called `ot-composefs`.
|
||||
The `ostree-prepare-root` binary will look for `ostree/prepare-root.conf` in `/etc` and
|
||||
`/usr/lib` in the initramfs. Using that configuration file you can enable composefs,
|
||||
and specify an Ed25519 public key to validate the booted commit.
|
||||
|
||||
The default value is `maybe` (this will likely become a build and initramfs-configurable option)
|
||||
in the future too.
|
||||
|
||||
The possible values are:
|
||||
|
||||
- `off`: Never use composefs
|
||||
- `maybe`: Use composefs if supported and there is a composefs image in the deployment directory
|
||||
- `on`: Require composefs
|
||||
- `digest=<sha256>`: Require the mounted composefs image to have a particular digest
|
||||
- `signed=<path>`: Require that the commit is signed as validated by the ed25519 public key specified
|
||||
by `path` (the path is resolved in the initrd).
|
||||
See the manpage for `ostree-prepare-root` for details of how to configure it.
|
||||
|
||||
### Injecting composefs digests
|
||||
|
||||
@ -56,20 +48,20 @@ covering the composefs fsverity digest with a signature.
|
||||
|
||||
### Signatures
|
||||
|
||||
If a commit is signed with a ed25519 private key (see `ostree
|
||||
--sign`), and `signed=/path/to/public.key` is specified on the
|
||||
commandline, then the initrd will find the commit being booted in the
|
||||
system repo and validate its signature against the public key. It will
|
||||
then ensure that the composefs digest being booted has an fs-verity
|
||||
digest matching the one in the commit. This allows a fully trusted
|
||||
read-only /usr.
|
||||
If a commit is signed with an Ed25519 private key (see `ostree
|
||||
--sign`), and `composefs.keyfile` is specified in `prepare-root.conf`,
|
||||
then the initrd will find the commit being booted in the system repo
|
||||
and validate its signature against the public key. It will then ensure
|
||||
that the composefs digest being booted has an fs-verity digest
|
||||
matching the one in the commit. This allows a fully trusted read-only
|
||||
/usr.
|
||||
|
||||
The exact usage of the signature is up to the user, but a common way
|
||||
to use it with transien keys. This is done like this:
|
||||
to use it with transient keys. This is done like this:
|
||||
* Generate a new keypair before each build
|
||||
* Embed the public key in the initrd that is part of the commit.
|
||||
* Ensure the kernel commandline has `ot-signed=/path/to/key`
|
||||
* After commiting, run `ostree --sign` with the private key.
|
||||
* Ensure the initrd has a `prepare-root.conf` with `keyfile=/path/to/key`
|
||||
* After committing, run `ostree --sign` with the private key.
|
||||
* Throw away the private key.
|
||||
|
||||
When a transient key is used this way, that ties the initrd with the
|
||||
|
@ -32,7 +32,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
|
||||
<surname>Walters</surname>
|
||||
<email>walters@verbum.org</email>
|
||||
</author>
|
||||
</authorgroup>g
|
||||
</authorgroup>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
@ -111,8 +111,25 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>sysroot.readonly</varname></term>
|
||||
<listitem><para>A boolean value; the default is false. If this is set to <literal>true</literal>, then the <literal>/sysroot</literal> mount point is mounted read-only.</para></listitem>
|
||||
</varlistentry>
|
||||
<listitem><para>A boolean value; the default is <literal>false</literal>. If this is set to <literal>true</literal>, then the <literal>/sysroot</literal> mount point is mounted read-only.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><varname>composefs.enabled</varname></term>
|
||||
<listitem><para>This can be <literal>yes</literal>, <literal>no</literal>. <literal>maybe</literal> or
|
||||
<literal>signed</literal>. The default is <literal>maybe</literal>. If set to <literal>yes</literal> or
|
||||
<literal>signed</literal>, then composefs is always used, and the boot fails if it is not
|
||||
available. Additionally if set to <literal>signed</literal>, boot will fail if the image cannot be
|
||||
validated by a public key. If set to <literal>maybe</literal>, then composefs is used if supported.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><varname>composefs.keypath</varname></term>
|
||||
<listitem><para>Path to a file with Ed25519 public keys in the initramfs, used if
|
||||
<literal>composefs.enabled</literal> is set to <literal>signed</literal>. The default value for this is
|
||||
<literal>/etc/ostree/initramfs-root-binding.key</literal>. For a valid signed boot the target OSTree
|
||||
commit must be signed by at least one public key in this file, and the commitfs digest listed in the
|
||||
commit must match the target composefs image.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
@ -38,6 +38,9 @@ install() {
|
||||
inst_simple "$r/ostree/prepare-root.conf"
|
||||
fi
|
||||
done
|
||||
if test -f "/etc/ostree/initramfs-root-binding.key"; then
|
||||
inst_simple "/etc/ostree/initramfs-root-binding.key"
|
||||
fi
|
||||
inst_simple "${systemdsystemunitdir}/ostree-prepare-root.service"
|
||||
mkdir -p "${initdir}${systemdsystemconfdir}/initrd-root-fs.target.wants"
|
||||
ln_r "${systemdsystemunitdir}/ostree-prepare-root.service" \
|
||||
|
@ -80,11 +80,14 @@
|
||||
const char *config_roots[] = { "/usr/lib", "/etc" };
|
||||
#define PREPARE_ROOT_CONFIG_PATH "ostree/prepare-root.conf"
|
||||
|
||||
#define DEFAULT_KEYPATH "/etc/ostree/initramfs-root-binding.key"
|
||||
|
||||
#define SYSROOT_KEY "sysroot"
|
||||
#define READONLY_KEY "readonly"
|
||||
|
||||
// The kernel argument we support to configure composefs.
|
||||
#define OT_COMPOSEFS_KARG "ot-composefs"
|
||||
#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)
|
||||
@ -250,21 +253,24 @@ load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GV
|
||||
}
|
||||
|
||||
static gboolean
|
||||
validate_signature (GBytes *data, GVariant *signatures, const guchar *pubkey, size_t pubkey_size)
|
||||
validate_signature (GBytes *data, GVariant *signatures, GList *pubkeys)
|
||||
{
|
||||
g_autoptr (GBytes) pubkey_buf = g_bytes_new_static (pubkey, pubkey_size);
|
||||
|
||||
for (gsize i = 0; i < g_variant_n_children (signatures); i++)
|
||||
for (GList *l = pubkeys; l != NULL; l = l->next)
|
||||
{
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
|
||||
g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
|
||||
bool valid = false;
|
||||
GBytes *pubkey = l->data;
|
||||
|
||||
if (!otcore_validate_ed25519_signature (data, pubkey_buf, signature, &valid, &local_error))
|
||||
errx (EXIT_FAILURE, "signature verification failed: %s", local_error->message);
|
||||
if (valid)
|
||||
return TRUE;
|
||||
for (gsize i = 0; i < g_variant_n_children (signatures); i++)
|
||||
{
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
|
||||
g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
|
||||
bool valid = false;
|
||||
|
||||
if (!otcore_validate_ed25519_signature (data, pubkey, signature, &valid, &local_error))
|
||||
errx (EXIT_FAILURE, "signature verification failed: %s", local_error->message);
|
||||
if (valid)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
@ -274,53 +280,76 @@ validate_signature (GBytes *data, GVariant *signatures, const guchar *pubkey, si
|
||||
typedef struct
|
||||
{
|
||||
OtTristate enabled;
|
||||
gboolean is_signed;
|
||||
char *signature_pubkey;
|
||||
char *expected_digest;
|
||||
GList *pubkeys;
|
||||
} ComposefsConfig;
|
||||
|
||||
static void
|
||||
free_composefs_config (ComposefsConfig *config)
|
||||
{
|
||||
free (config->signature_pubkey);
|
||||
free (config->expected_digest);
|
||||
free (config);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComposefsConfig, free_composefs_config)
|
||||
|
||||
static ComposefsConfig *
|
||||
load_composefs_config (GError **error)
|
||||
load_composefs_config (GKeyFile *config, GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("Loading composefs config", error);
|
||||
g_autoptr (ComposefsConfig) ret = g_new0 (ComposefsConfig, 1);
|
||||
ret->enabled = OT_TRISTATE_MAYBE;
|
||||
|
||||
// TODO: Drop this kernel argument in favor of just the config file in the initramfs
|
||||
autofree char *ot_composefs = read_proc_cmdline_key (OT_COMPOSEFS_KARG);
|
||||
if (ot_composefs)
|
||||
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)
|
||||
{
|
||||
if (strcmp (ot_composefs, "off") == 0)
|
||||
ret->enabled = OT_TRISTATE_NO;
|
||||
else if (strcmp (ot_composefs, "maybe") == 0)
|
||||
ret->enabled = OT_TRISTATE_MAYBE;
|
||||
else if (strcmp (ot_composefs, "on") == 0)
|
||||
ret->enabled = OT_TRISTATE_YES;
|
||||
else if (g_str_has_prefix (ot_composefs, "signed="))
|
||||
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;
|
||||
|
||||
if (!ot_keyfile_get_value_with_default (config, COMPOSEFS_KEY, KEYPATH_KEY, DEFAULT_KEYPATH,
|
||||
&ret->signature_pubkey, error))
|
||||
return NULL;
|
||||
|
||||
if (ret->is_signed)
|
||||
{
|
||||
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);
|
||||
|
||||
/* Raw binary form if right size */
|
||||
if (pubkeys_size == OSTREE_SIGN_ED25519_PUBKEY_SIZE)
|
||||
ret->pubkeys = g_list_append (ret->pubkeys,
|
||||
g_bytes_new_take (g_steal_pointer (&pubkeys), pubkeys_size));
|
||||
else /* otherwise text with base64 key per line */
|
||||
{
|
||||
ret->enabled = OT_TRISTATE_YES;
|
||||
ret->signature_pubkey = g_strdup (ot_composefs + strlen ("signed="));
|
||||
g_auto (GStrv) lines = g_strsplit (pubkeys, "\n", -1);
|
||||
for (char **iter = lines; *iter; iter++)
|
||||
{
|
||||
const char *line = *iter;
|
||||
if (strlen (line) > 0)
|
||||
{
|
||||
g_autofree guchar *pubkey = NULL;
|
||||
gsize pubkey_size;
|
||||
|
||||
pubkey = g_base64_decode (line, &pubkey_size);
|
||||
ret->pubkeys = g_list_append (
|
||||
ret->pubkeys, g_bytes_new_take (g_steal_pointer (&pubkey), pubkey_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g_str_has_prefix (ot_composefs, "digest="))
|
||||
{
|
||||
ret->enabled = OT_TRISTATE_YES;
|
||||
ret->expected_digest = g_strdup (ot_composefs + strlen ("digest="));
|
||||
}
|
||||
else
|
||||
return glnx_null_throw (error, "Unsupported %s option: '%s'", OT_COMPOSEFS_KARG,
|
||||
ot_composefs);
|
||||
// In theory it's OK to have both a signature and an expected digest,
|
||||
// but since there's no valid reason to do both, let's not support it.
|
||||
g_assert (!(ret->signature_pubkey && ret->expected_digest));
|
||||
|
||||
if (ret->pubkeys == NULL)
|
||||
return glnx_null_throw (error, "public key file specified, but no public keys found");
|
||||
}
|
||||
|
||||
return g_steal_pointer (&ret);
|
||||
@ -347,7 +376,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 (&error);
|
||||
g_autoptr (ComposefsConfig) composefs_config = load_composefs_config (config, &error);
|
||||
if (!composefs_config)
|
||||
errx (EXIT_FAILURE, "%s", error->message);
|
||||
|
||||
@ -425,22 +454,15 @@ main (int argc, char *argv[])
|
||||
1,
|
||||
};
|
||||
|
||||
g_autofree char *expected_digest_owned = NULL;
|
||||
const char *expected_digest = expected_digest_owned;
|
||||
if (composefs_config->signature_pubkey)
|
||||
g_autofree char *expected_digest = NULL;
|
||||
|
||||
if (composefs_config->is_signed)
|
||||
{
|
||||
g_assert (expected_digest == NULL);
|
||||
const char *composefs_pubkey = composefs_config->signature_pubkey;
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
g_autofree char *pubkey = NULL;
|
||||
gsize pubkey_size;
|
||||
g_autoptr (GVariant) commit = NULL;
|
||||
g_autoptr (GVariant) commitmeta = NULL;
|
||||
|
||||
if (!g_file_get_contents (composefs_pubkey, &pubkey, &pubkey_size, &local_error))
|
||||
errx (EXIT_FAILURE, "Failed to load public key '%s': %s", composefs_pubkey,
|
||||
local_error->message);
|
||||
|
||||
if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta,
|
||||
&local_error))
|
||||
errx (EXIT_FAILURE, "Error loading signatures from repo: %s", local_error->message);
|
||||
@ -451,7 +473,7 @@ main (int argc, char *argv[])
|
||||
errx (EXIT_FAILURE, "Signature validation requested, but no signatures in commit");
|
||||
|
||||
g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit);
|
||||
if (!validate_signature (commit_data, signatures, (guchar *)pubkey, pubkey_size))
|
||||
if (!validate_signature (commit_data, signatures, composefs_config->pubkeys))
|
||||
errx (EXIT_FAILURE, "No valid signatures found for public key");
|
||||
|
||||
g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey);
|
||||
@ -468,9 +490,8 @@ main (int argc, char *argv[])
|
||||
if (!cfs_digest_buf)
|
||||
errx (EXIT_FAILURE, "Failed to query digest: %s", error->message);
|
||||
|
||||
expected_digest_owned = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
|
||||
ot_bin2hex (expected_digest_owned, cfs_digest_buf, g_variant_get_size (cfs_digest_v));
|
||||
expected_digest = expected_digest_owned;
|
||||
expected_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
|
||||
ot_bin2hex (expected_digest, cfs_digest_buf, g_variant_get_size (cfs_digest_v));
|
||||
}
|
||||
|
||||
cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
|
||||
@ -489,7 +510,7 @@ main (int argc, char *argv[])
|
||||
// If we're not verifying a digest, then we *must* also have signatures disabled.
|
||||
// Or stated in reverse: if signature verification is enabled, then digest verification
|
||||
// must also be.
|
||||
g_assert (!composefs_config->signature_pubkey);
|
||||
g_assert (!composefs_config->is_signed);
|
||||
g_print ("composefs: Mounting with no digest or signature check\n");
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user