From b3bbbd154225e81980546b2c0b5ed98714830696 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 21 Mar 2020 14:48:24 +0000 Subject: [PATCH] repo/commit: Add support for --selinux-policy-from-base The [dev-overlay](https://github.com/coreos/coreos-assembler/blob/332c6ab3b91778d904224c3c960d9cc4739d60bd/src/cmd-dev-overlay) script shipped in coreos-assembler mostly exists to deal with the nontrivial logic around SELinux policy. Let's make the use case of "commit some binaries overlaying a base tree, using the base's selinux policy" just require a magical `--selinux-policy-from-base` argument to `ostree commit`. A new C API was added to implement this in the case of `--tree=ref`; when the base directory is already checked out, we can just reuse the existing logic that `--selinux-policy` was using. Requires: https://github.com/ostreedev/ostree/pull/2039 --- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 2 +- src/libostree/ostree-repo-commit.c | 58 ++++++++++++++++++- src/libostree/ostree-repo-private.h | 1 + src/libostree/ostree-repo.h | 7 +++ src/ostree/ot-builtin-commit.c | 51 +++++++++++----- tests/kola/destructive/itest-label-selinux.sh | 17 ++++++ 7 files changed, 121 insertions(+), 16 deletions(-) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 32cf5228a5..3525d9f288 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -379,6 +379,7 @@ ostree_repo_commit_modifier_new OstreeRepoCommitModifierXattrCallback ostree_repo_commit_modifier_set_xattr_callback ostree_repo_commit_modifier_set_sepolicy +ostree_repo_commit_modifier_set_sepolicy_from_commit ostree_repo_commit_modifier_set_devino_cache ostree_repo_commit_modifier_ref ostree_repo_commit_modifier_unref diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index aa3392ccd9..3d5fd3bcdf 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -20,7 +20,7 @@ /* Add new symbols here. Release commits should copy this section into -released.sym. */ LIBOSTREE_2020.2 { global: - someostree_symbol_deleteme; + ostree_repo_commit_modifier_set_sepolicy_from_commit; } LIBOSTREE_2020.1; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index dac4573ce9..3f2f2bebcd 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -4227,9 +4227,11 @@ ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier) if (modifier->xattr_destroy) modifier->xattr_destroy (modifier->xattr_user_data); - g_clear_object (&modifier->sepolicy); g_clear_pointer (&modifier->devino_cache, (GDestroyNotify)g_hash_table_unref); + g_clear_object (&modifier->sepolicy); + (void) glnx_tmpdir_delete (&modifier->sepolicy_tmpdir, NULL, NULL); + g_free (modifier); return; } @@ -4279,6 +4281,60 @@ ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier modifier->sepolicy = sepolicy ? g_object_ref (sepolicy) : NULL; } +/** + * ostree_repo_commit_modifier_set_sepolicy_from_commit: + * @modifier: Commit modifier + * @repo: OSTree repo containing @rev + * @rev: Find SELinux policy from this base commit + * @cancellable: + * @error: + * + * In many cases, one wants to create a "derived" commit from base commit. + * SELinux policy labels are part of that base commit. This API allows + * one to easily set up SELinux labeling from a base commit. + */ +gboolean +ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier *modifier, + OstreeRepo *repo, + const char *rev, + GCancellable *cancellable, + GError **error) +{ + GLNX_AUTO_PREFIX_ERROR ("setting sepolicy from commit", error); + g_autofree char *commit = NULL; + g_autoptr(GFile) root = NULL; + if (!ostree_repo_read_commit (repo, rev, &root, &commit, cancellable, error)) + return FALSE; + const char policypath[] = "usr/etc/selinux"; + g_autoptr(GFile) policyroot = g_file_get_child (root, policypath); + if (!g_file_query_exists (policyroot, NULL)) + return TRUE; /* No policy, nothing to do */ + + GLnxTmpDir tmpdir = {0,}; + if (!glnx_mkdtemp ("ostree-commit-sepolicy-XXXXXX", 0700, &tmpdir, error)) + return FALSE; + if (!glnx_shutil_mkdir_p_at (tmpdir.fd, "usr/etc", 0755, cancellable, error)) + return FALSE; + + OstreeRepoCheckoutAtOptions coopts = {0,}; + coopts.mode = OSTREE_REPO_CHECKOUT_MODE_USER; + coopts.subpath = glnx_strjoina ("/", policypath); + + if (!ostree_repo_checkout_at (repo, &coopts, tmpdir.fd, policypath, commit, cancellable, error)) + return glnx_prefix_error (error, "policy checkout"); + + g_autoptr(OstreeSePolicy) policy = ostree_sepolicy_new_at (tmpdir.fd, cancellable, error); + if (!policy) + return glnx_prefix_error (error, "reading policy"); + + ostree_repo_commit_modifier_set_sepolicy (modifier, policy); + /* Transfer ownership */ + modifier->sepolicy_tmpdir = tmpdir; + tmpdir.initialized = FALSE; + + return TRUE; +} + /** * ostree_repo_commit_modifier_set_devino_cache: * @modifier: Modifier diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 571cab6b8b..9f722a3945 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -76,6 +76,7 @@ struct OstreeRepoCommitModifier { GDestroyNotify xattr_destroy; gpointer xattr_user_data; + GLnxTmpDir sepolicy_tmpdir; OstreeSePolicy *sepolicy; GHashTable *devino_cache; }; diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 40d3f77379..014afff905 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -691,6 +691,13 @@ _OSTREE_PUBLIC void ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier *modifier, OstreeSePolicy *sepolicy); +_OSTREE_PUBLIC +gboolean ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier *modifier, + OstreeRepo *repo, + const char *commit, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC void ostree_repo_commit_modifier_set_devino_cache (OstreeRepoCommitModifier *modifier, OstreeRepoDevInoCache *cache); diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index 16b07cb9dd..4cca56d026 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -53,6 +53,7 @@ static gboolean opt_tar_autocreate_parents; static char *opt_tar_pathname_filter; static gboolean opt_no_xattrs; static char *opt_selinux_policy; +static gboolean opt_selinux_policy_from_base; static gboolean opt_canonical_permissions; static gboolean opt_consume; static gboolean opt_devino_canonical; @@ -107,6 +108,7 @@ static GOptionEntry options[] = { { "canonical-permissions", 0, 0, G_OPTION_ARG_NONE, &opt_canonical_permissions, "Canonicalize permissions in the same way bare-user does for hardlinked files", NULL }, { "no-xattrs", 0, 0, G_OPTION_ARG_NONE, &opt_no_xattrs, "Do not import extended attributes", NULL }, { "selinux-policy", 0, 0, G_OPTION_ARG_FILENAME, &opt_selinux_policy, "Set SELinux labels based on policy in root filesystem PATH (may be /)", "PATH" }, + { "selinux-policy-from-base", 'P', 0, G_OPTION_ARG_NONE, &opt_selinux_policy_from_base, "Set SELinux labels based on first --tree argument", NULL }, { "link-checkout-speedup", 0, 0, G_OPTION_ARG_NONE, &opt_link_checkout_speedup, "Optimize for commits of trees composed of hardlinks into the repository", NULL }, { "devino-canonical", 'I', 0, G_OPTION_ARG_NONE, &opt_devino_canonical, "Assume hardlinked objects are unmodified. Implies --link-checkout-speedup", NULL }, { "tar-autocreate-parents", 0, 0, G_OPTION_ARG_NONE, &opt_tar_autocreate_parents, "When loading tar archives, automatically create parent directories as needed", NULL }, @@ -550,6 +552,11 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio flags |= OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES; if (opt_disable_fsync) ostree_repo_set_disable_fsync (repo, TRUE); + if (opt_selinux_policy && opt_selinux_policy_from_base) + { + glnx_throw (error, "Cannot specify both --selinux-policy and --selinux-policy-from-base"); + goto out; + } if (flags != 0 || opt_owner_uid >= 0 @@ -557,25 +564,13 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio || opt_statoverride_file != NULL || opt_skiplist_file != NULL || opt_no_xattrs - || opt_selinux_policy) + || opt_selinux_policy + || opt_selinux_policy_from_base) { filter_data.mode_adds = mode_adds; filter_data.skip_list = skip_list; modifier = ostree_repo_commit_modifier_new (flags, commit_filter, &filter_data, NULL); - if (opt_selinux_policy) - { - glnx_autofd int rootfs_dfd = -1; - if (!glnx_opendirat (AT_FDCWD, opt_selinux_policy, TRUE, &rootfs_dfd, error)) - { - g_prefix_error (error, "selinux-policy: "); - goto out; - } - policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error); - if (!policy) - goto out; - ostree_repo_commit_modifier_set_sepolicy (modifier, policy); - } } if (opt_editor) @@ -621,6 +616,7 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio g_assert (opt_trees && *opt_trees); for (tree_iter = (const char *const*)opt_trees; *tree_iter; tree_iter++) { + const gboolean first = (tree_iter == (const char *const*)opt_trees); tree = *tree_iter; eq = strchr (tree, '='); @@ -637,12 +633,33 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio g_clear_object (&object_to_commit); if (strcmp (tree_type, "dir") == 0) { + if (first && opt_selinux_policy_from_base) + { + opt_selinux_policy = g_strdup (tree); + opt_selinux_policy_from_base = FALSE; + } + if (first && opt_selinux_policy) + { + g_assert (modifier); + glnx_autofd int rootfs_dfd = -1; + if (!glnx_opendirat (AT_FDCWD, opt_selinux_policy, TRUE, &rootfs_dfd, error)) + goto out; + policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error); + if (!policy) + goto out; + ostree_repo_commit_modifier_set_sepolicy (modifier, policy); + } if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, tree, mtree, modifier, cancellable, error)) goto out; } else if (strcmp (tree_type, "tar") == 0) { + if (first && opt_selinux_policy_from_base) + { + glnx_throw (error, "Cannot use --selinux-policy-from-base with tar"); + goto out; + } if (!opt_tar_pathname_filter) { if (strcmp (tree, "-") == 0) @@ -707,6 +724,12 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio } else if (strcmp (tree_type, "ref") == 0) { + if (first && opt_selinux_policy_from_base) + { + g_assert (modifier); + if (!ostree_repo_commit_modifier_set_sepolicy_from_commit (modifier, repo, tree, cancellable, error)) + goto out; + } if (!ostree_repo_read_commit (repo, tree, &object_to_commit, NULL, cancellable, error)) goto out; diff --git a/tests/kola/destructive/itest-label-selinux.sh b/tests/kola/destructive/itest-label-selinux.sh index 7bfd235175..d7337124b5 100755 --- a/tests/kola/destructive/itest-label-selinux.sh +++ b/tests/kola/destructive/itest-label-selinux.sh @@ -6,6 +6,7 @@ set -xeuo pipefail . ${KOLA_EXT_DATA}/libinsttest.sh require_writable_sysroot +prepare_tmpdir /var/tmp date cd /ostree/repo/tmp @@ -87,3 +88,19 @@ rm co -rf ostree refs --delete testbranch echo "ok checkout selinux and skip-list" date + +mkdir -p usr/{bin,lib,etc} +echo 'somebinary' > usr/bin/somebinary +ls -Z usr/bin/somebinary > lsz.txt +assert_not_file_has_content lsz.txt ':bin_t:' +rm -f lsz.txt +echo 'somelib' > usr/lib/somelib.so +echo 'someconf' > usr/etc/some.conf +ostree commit -b newbase --selinux-policy-from-base --tree=ref=${host_refspec} --tree=dir=$(pwd) +ostree ls -X newbase /usr/bin/somebinary > newls.txt +assert_file_has_content newls.txt ':bin_t:' +ostree ls -X newbase /usr/lib/somelib.so > newls.txt +assert_file_has_content newls.txt ':lib_t:' +ostree ls -X newbase /usr/etc/some.conf > newls.txt +assert_file_has_content newls.txt ':etc_t:' +echo "ok commit --selinux-policy-from-base"