From 909fca022c7ca189507b64780da181cd1b6cd821 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Nov 16 2021 04:21:36 +0000 Subject: import openscap-1.3.5-9.el8 --- diff --git a/SOURCES/openscap-1.3.6-PR-1769-local-ds-components.patch b/SOURCES/openscap-1.3.6-PR-1769-local-ds-components.patch new file mode 100644 index 0000000..fd26026 --- /dev/null +++ b/SOURCES/openscap-1.3.6-PR-1769-local-ds-components.patch @@ -0,0 +1,2158 @@ +From 35ae94cd84b7b99845f0d2306924946e66da6d50 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= +Date: Tue, 15 Jun 2021 10:09:44 +0200 +Subject: [PATCH 01/10] Allow providing pre-downloaded components + +OpenSCAP can download remote SCAP source data stream components from the +internet if the --fetch-remote-resources option is set. When this +command line option isn't set, it is possible to download the remote +component separately, save it as file to the filesystem which name is +equal to the name of the remote component and OpenSCAP would use this +local file instead of just skipping the checks. It currently works by +accident, it wasn't intended to work this way or at least I haven't +heard it before, but somobody found out that it works and documented it +as a KSC: https://access.redhat.com/solutions/5185891. However, it +produces warning messages if the approach used in the KSC is used. + +This patch promotes the workaround to a feature. And it changes the +logic so that it won't print the warnings. + +The main difference is that it won't work automatically, but only +on user's demand by providing --use-local-file which is the new +command line option introduced by this commit. + +Fixes: rhbz#1970527 +--- + src/DS/ds_sds_session.c | 14 ++- + src/DS/ds_sds_session_priv.h | 1 + + src/DS/public/ds_sds_session.h | 15 ++- + src/DS/sds.c | 22 +++++ + src/OVAL/oval_session.c | 11 ++- + src/OVAL/public/oval_session.h | 15 ++- + src/XCCDF/public/xccdf_session.h | 15 ++- + src/XCCDF/xccdf_session.c | 36 ++++--- + tests/DS/CMakeLists.txt | 1 + + .../remote.oval.xml | 58 +++++++++++ + .../remote_content_1.3.ds.xml | 96 +++++++++++++++++++ + .../DS/test_ds_use_local_remote_resources.sh | 57 +++++++++++ + utils/oscap-ds.c | 6 +- + utils/oscap-info.c | 4 +- + utils/oscap-oval.c | 6 +- + utils/oscap-tool.h | 1 + + utils/oscap-xccdf.c | 14 ++- + utils/oscap.8 | 26 +++++ + 18 files changed, 369 insertions(+), 29 deletions(-) + create mode 100644 tests/DS/ds_use_local_remote_resources/remote.oval.xml + create mode 100644 tests/DS/ds_use_local_remote_resources/remote_content_1.3.ds.xml + create mode 100755 tests/DS/test_ds_use_local_remote_resources.sh + +diff --git a/src/DS/ds_sds_session.c b/src/DS/ds_sds_session.c +index 8c0072bae0..9d9a9c8b52 100644 +--- a/src/DS/ds_sds_session.c ++++ b/src/DS/ds_sds_session.c +@@ -55,6 +55,7 @@ struct ds_sds_session { + struct oscap_htable *component_uris; ///< maps component refs to component URIs + bool fetch_remote_resources; ///< Allows loading of external components; + download_progress_calllback_t progress; ///< Callback to report progress of download. ++ bool use_local_file; ///< Use a locally downloaded copy of a remote resource if it exists + }; + + /** +@@ -337,12 +338,23 @@ int ds_sds_session_register_component_with_dependencies(struct ds_sds_session *s + return res; + } + +-void ds_sds_session_set_remote_resources(struct ds_sds_session *session, bool allowed, download_progress_calllback_t callback) ++void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback) + { + session->fetch_remote_resources = allowed; ++ session->use_local_file = use_local_file; + session->progress = (callback != NULL) ? callback : download_progress_empty_calllback; + } + ++void ds_sds_session_set_remote_resources(struct ds_sds_session *session, bool allowed, download_progress_calllback_t callback) ++{ ++ ds_sds_session_configure_remote_resources(session, allowed, false, callback); ++} ++ ++bool ds_sds_session_can_use_local_file(struct ds_sds_session *session) ++{ ++ return session->use_local_file; ++} ++ + int ds_sds_session_dump_component_files(struct ds_sds_session *session) + { + return ds_dump_component_sources(session->component_sources, ds_sds_session_get_target_dir(session)); +diff --git a/src/DS/ds_sds_session_priv.h b/src/DS/ds_sds_session_priv.h +index f58231dc48..018cd053ec 100644 +--- a/src/DS/ds_sds_session_priv.h ++++ b/src/DS/ds_sds_session_priv.h +@@ -40,6 +40,7 @@ struct oscap_htable *ds_sds_session_get_component_sources(struct ds_sds_session + struct oscap_htable *ds_sds_session_get_component_uris(struct ds_sds_session *session); + const char *ds_sds_session_get_readable_origin(const struct ds_sds_session *session); + bool ds_sds_session_fetch_remote_resources(struct ds_sds_session *session); ++bool ds_sds_session_can_use_local_file(struct ds_sds_session *session); + download_progress_calllback_t ds_sds_session_remote_resources_progress(struct ds_sds_session *session); + + void download_progress_empty_calllback(bool warning, const char * format, ...); +diff --git a/src/DS/public/ds_sds_session.h b/src/DS/public/ds_sds_session.h +index 20a85146cc..695a0df215 100644 +--- a/src/DS/public/ds_sds_session.h ++++ b/src/DS/public/ds_sds_session.h +@@ -200,7 +200,20 @@ OSCAP_API void ds_sds_session_reset(struct ds_sds_session *session); + * @param callback used to notify user about download proceeds. This might be safely set + * to NULL -- ignoring user notification. + */ +-OSCAP_API void ds_sds_session_set_remote_resources(struct ds_sds_session *session, bool allowed, download_progress_calllback_t callback); ++OSCAP_API OSCAP_DEPRECATED(void ds_sds_session_set_remote_resources(struct ds_sds_session *session, bool allowed, download_progress_calllback_t callback)); ++ ++/** ++ * Set property of remote content. ++ * @memberof ds_sds_session ++ * @param session The source data stream session ++ * @param allowed Whether is download of remote resources allowed in this ++ * session (defaults to false) ++ * @param use_local_file Allows to use a locally downloaded copy of the remote ++ * resource if it exists (defaults to false) ++ * @param callback used to notify user about download proceeds. This might be ++ * safely set to NULL -- ignoring user notification. ++ */ ++OSCAP_API void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback); + + /** + * Returns HTML representation of selected checklist in form of OpenSCAP guide. +diff --git a/src/DS/sds.c b/src/DS/sds.c +index 0f19a81982..b1737aa57f 100644 +--- a/src/DS/sds.c ++++ b/src/DS/sds.c +@@ -413,6 +413,28 @@ static int ds_sds_dump_component_by_href(struct ds_sds_session *session, char* x + } + + if (!ds_sds_session_fetch_remote_resources(session)) { ++ /* ++ * If fetching remote resources isn't allowed by the user let's take ++ * a look whether there exists a file whose file name is equal to ++ * @name attribute of the uri element within the catalog of the ++ * previously processed component-ref which pointed us to the ++ * currently processed component-ref. Note that the @name attribute ++ * value has been passed as relative_filepath in the recursive call ++ * of ds_sds_dump_component_ref_as. If such file exists, we will ++ * assume that it's a local copy of the remote component located at ++ * the URL defined in @xlink:href. This way people can provide the ++ * previously downloaded component which might be useful on systems ++ * with limited internet access. This behavior is allowed only when ++ * --use-local-file is used on the command line. ++ * See: https://bugzilla.redhat.com/show_bug.cgi?id=1970527 ++ * See: https://access.redhat.com/solutions/5185891 ++ */ ++ struct stat sb; ++ if (ds_sds_session_can_use_local_file(session) && stat(relative_filepath, &sb) == 0) { ++ dI("Using local file '%s' instead of '%s'", relative_filepath, xlink_href); ++ return ds_sds_dump_file_component(relative_filepath, *component_id, session, target_filename_dirname, relative_filepath); ++ } ++ + static bool fetch_remote_resources_suggested = false; + + if (!fetch_remote_resources_suggested) { +diff --git a/src/OVAL/oval_session.c b/src/OVAL/oval_session.c +index ebc7e55f71..5adda43f66 100644 +--- a/src/OVAL/oval_session.c ++++ b/src/OVAL/oval_session.c +@@ -84,6 +84,7 @@ struct oval_session { + bool full_validation; + bool fetch_remote_resources; + download_progress_calllback_t progress; ++ bool use_local_file; + }; + + struct oval_session *oval_session_new(const char *filename) +@@ -223,7 +224,7 @@ static int oval_session_load_definitions(struct oval_session *session) + if ((session->sds_session = ds_sds_session_new_from_source(session->source)) == NULL) { + return 1; + } +- ds_sds_session_set_remote_resources(session->sds_session,session->fetch_remote_resources ,session->progress); ++ ds_sds_session_configure_remote_resources(session->sds_session, session->fetch_remote_resources, session->use_local_file, session->progress); + ds_sds_session_set_datastream_id(session->sds_session, session->datastream_id); + if (ds_sds_session_register_component_with_dependencies(session->sds_session, + "checks", session->component_id, "oval.xml") != 0) { +@@ -451,12 +452,18 @@ void oval_session_set_export_system_characteristics(struct oval_session *session + session->export_sys_chars = export; + } + +-void oval_session_set_remote_resources(struct oval_session *session, bool allowed, download_progress_calllback_t callback) ++void oval_session_configure_remote_resources(struct oval_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback) + { + session->fetch_remote_resources = allowed; ++ session->use_local_file = use_local_file; + session->progress = callback; + } + ++void oval_session_set_remote_resources(struct oval_session *session, bool allowed, download_progress_calllback_t callback) ++{ ++ oval_session_configure_remote_resources(session, allowed, false, callback); ++} ++ + void oval_session_free(struct oval_session *session) + { + if (session == NULL) +diff --git a/src/OVAL/public/oval_session.h b/src/OVAL/public/oval_session.h +index ed97cb7768..d485676747 100644 +--- a/src/OVAL/public/oval_session.h ++++ b/src/OVAL/public/oval_session.h +@@ -239,7 +239,20 @@ OSCAP_API void oval_session_set_export_system_characteristics(struct oval_sessio + * @param callback used to notify user about download proceeds. This might be safely set + * to NULL -- ignoring user notification. + */ +-OSCAP_API void oval_session_set_remote_resources(struct oval_session *session, bool allowed, download_progress_calllback_t callback); ++OSCAP_API OSCAP_DEPRECATED(void oval_session_set_remote_resources(struct oval_session *session, bool allowed, download_progress_calllback_t callback)); ++ ++/** ++ * Set property of remote content. ++ * @memberof oval_session ++ * @param session an \ref oval_session ++ * @param allowed Whether is download of remote resources allowed in this ++ * session (defaults to false) ++ * @param use_local_file Allows to use a locally downloaded copy of the remote ++ * resource if it exists (defaults to false) ++ * @param callback used to notify user about download proceeds. This might be ++ * safely set to NULL -- ignoring user notification. ++ */ ++OSCAP_API void oval_session_configure_remote_resources(struct oval_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback); + + /** + * Destructor of an \ref oval_session. +diff --git a/src/XCCDF/public/xccdf_session.h b/src/XCCDF/public/xccdf_session.h +index 70cfc677f3..2eb7128019 100644 +--- a/src/XCCDF/public/xccdf_session.h ++++ b/src/XCCDF/public/xccdf_session.h +@@ -232,7 +232,20 @@ OSCAP_API void xccdf_session_set_user_tailoring_cid(struct xccdf_session *sessio + * @param callback used to notify user about download proceeds. This might be safely set + * to NULL -- ignoring user notification. + */ +-OSCAP_API void xccdf_session_set_remote_resources(struct xccdf_session *session, bool allowed, download_progress_calllback_t callback); ++OSCAP_API OSCAP_DEPRECATED(void xccdf_session_set_remote_resources(struct xccdf_session *session, bool allowed, download_progress_calllback_t callback)); ++ ++/** ++ * Set properties of remote content. ++ * @memberof xccdf_session ++ * @param session XCCDF Session ++ * @param allowed Whether is download od remote resources allowed in this ++ * session (defaults to false) ++ * @param use_local_file Allows to use a locally downloaded copy of the remote ++ * resource if it exists (defaults to false) ++ * @param callback used to notify user about download proceeds. This might be ++ * safely set to NULL -- ignoring user notification. ++ */ ++OSCAP_API void xccdf_session_configure_remote_resources(struct xccdf_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback); + + /** + * Disable or allow loading of depending content (OVAL, SCE, CPE) +diff --git a/src/XCCDF/xccdf_session.c b/src/XCCDF/xccdf_session.c +index 9d8f42c445..85fcc90d23 100644 +--- a/src/XCCDF/xccdf_session.c ++++ b/src/XCCDF/xccdf_session.c +@@ -87,6 +87,7 @@ struct xccdf_session { + } ds; + struct { + bool fetch_remote_resources; ///< Allows download of remote resources (not applicable when user sets custom oval files) ++ bool use_local_file; ///< Use a locally downloaded copy of a remote resource if it exists + download_progress_calllback_t progress; ///< Callback to report progress of download. + struct oval_content_resource **custom_resources;///< OVAL files required by user + struct oval_content_resource **resources;///< OVAL files referenced from XCCDF +@@ -626,7 +627,8 @@ static struct ds_sds_session *xccdf_session_get_ds_sds_session(struct xccdf_sess + return session->ds.session; + } + +-void xccdf_session_set_remote_resources(struct xccdf_session *session, bool allowed, download_progress_calllback_t callback) ++ ++void xccdf_session_configure_remote_resources(struct xccdf_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback) + { + if (callback == NULL) { + // With empty cb we don't have to check for NULL +@@ -635,15 +637,21 @@ void xccdf_session_set_remote_resources(struct xccdf_session *session, bool allo + } + + session->oval.fetch_remote_resources = allowed; ++ session->oval.use_local_file = use_local_file; + session->oval.progress = callback; + + if (xccdf_session_is_sds(session)) { + // We have to propagate this option to allow loading + // of external datastream components +- ds_sds_session_set_remote_resources(xccdf_session_get_ds_sds_session(session), allowed, callback); ++ ds_sds_session_configure_remote_resources(xccdf_session_get_ds_sds_session(session), allowed, use_local_file, callback); + } + } + ++void xccdf_session_set_remote_resources(struct xccdf_session *session, bool allowed, download_progress_calllback_t callback) ++{ ++ xccdf_session_configure_remote_resources(session, allowed, false, callback); ++} ++ + void xccdf_session_set_loading_flags(struct xccdf_session *session, xccdf_session_loading_flags_t flags) + { + session->loading_flags = flags; +@@ -993,6 +1001,7 @@ static int _xccdf_session_get_oval_from_model(struct xccdf_session *session) + while (oscap_file_entry_iterator_has_more(files_it)) { + struct oscap_file_entry *file_entry; + struct stat sb; ++ bool source_owned = false; + + file_entry = (struct oscap_file_entry *) oscap_file_entry_iterator_next(files_it); + +@@ -1002,9 +1011,6 @@ static int _xccdf_session_get_oval_from_model(struct xccdf_session *session) + + const char *file_path = oscap_file_entry_get_file(file_entry); + struct oscap_source *source = NULL; +- if (xccdf_session_get_ds_sds_session(session) != NULL) { +- source = ds_sds_session_get_component_by_href(xccdf_session_get_ds_sds_session(session), file_path); +- } + + tmp_path = malloc(PATH_MAX * sizeof(char)); + if (file_path[0] == '/') { // it's a simple absolute path +@@ -1017,16 +1023,20 @@ static int _xccdf_session_get_oval_from_model(struct xccdf_session *session) + snprintf(tmp_path, PATH_MAX, "%s/%s", dir_path, file_path); + } + +- if (source != NULL || stat(tmp_path, &sb) == 0) { +- resources[idx] = malloc(sizeof(struct oval_content_resource)); +- resources[idx]->href = oscap_strdup(oscap_file_entry_get_file(file_entry)); +- if (source == NULL) { ++ if (xccdf_session_get_ds_sds_session(session) != NULL) { ++ source = ds_sds_session_get_component_by_href(xccdf_session_get_ds_sds_session(session), file_path); ++ source_owned = false; ++ } else { ++ if (stat(tmp_path, &sb) == 0) { + source = oscap_source_new_from_file(tmp_path); +- resources[idx]->source_owned = true; +- } +- else { +- resources[idx]->source_owned = false; ++ source_owned = true; + } ++ } ++ ++ if (source != NULL) { ++ resources[idx] = malloc(sizeof(struct oval_content_resource)); ++ resources[idx]->href = oscap_strdup(oscap_file_entry_get_file(file_entry)); ++ resources[idx]->source_owned = source_owned; + resources[idx]->source = source; + idx++; + void *new_resources = realloc(resources, (idx + 1) * sizeof(struct oval_content_resource *)); +diff --git a/tests/DS/CMakeLists.txt b/tests/DS/CMakeLists.txt +index f239586f8b..aac089cab5 100644 +--- a/tests/DS/CMakeLists.txt ++++ b/tests/DS/CMakeLists.txt +@@ -4,6 +4,7 @@ add_oscap_test("test_sds_compose_split.sh") + add_oscap_test("test_sds_eval.sh") + add_oscap_test("test_sds_fix_from_results.sh") + add_oscap_test("test_sds_fix_from_source.sh") ++add_oscap_test("test_ds_use_local_remote_resources.sh") + + add_subdirectory("ds_sds_index") + add_subdirectory("schematron") +diff --git a/tests/DS/ds_use_local_remote_resources/remote.oval.xml b/tests/DS/ds_use_local_remote_resources/remote.oval.xml +new file mode 100644 +index 0000000000..ed864a462d +--- /dev/null ++++ b/tests/DS/ds_use_local_remote_resources/remote.oval.xml +@@ -0,0 +1,58 @@ ++ ++ ++ ++ 5.10 ++ 0001-01-01T00:00:00+00:00 ++ ++ ++ ++ ++ ++ This definition will pass ++ x ++ ++ x ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ This definition will fail ++ x ++ ++ x ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ oval:x:var:1 ++ ++ ++ ++ ++ ++ x ++ ++ ++ +diff --git a/tests/DS/ds_use_local_remote_resources/remote_content_1.3.ds.xml b/tests/DS/ds_use_local_remote_resources/remote_content_1.3.ds.xml +new file mode 100644 +index 0000000000..ab1a0f1458 +--- /dev/null ++++ b/tests/DS/ds_use_local_remote_resources/remote_content_1.3.ds.xml +@@ -0,0 +1,96 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 5.11 ++ 2009-01-12T10:41:00-05:00 ++ ++ ++ ++ ++ ++ PASS ++ pass ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ oval:x:var:1 ++ ++ ++ ++ ++ ++ 100 ++ ++ ++ ++ ++ ++ ++ ++ ++ accepted ++ 1.0 ++ ++ ++ xccdf_test_profile ++ This profile is for testing. ++ ++ ++ ++ ++ test value ++ foo ++ 50 ++ 100 ++ ++ ++ This rule always pass ++ ++ ++ ++ ++ ++ This rule checks remote resource ++ ++ ++ ++ ++ ++ This rule always pass ++ ++ ++ ++ ++ ++ ++ +diff --git a/tests/DS/test_ds_use_local_remote_resources.sh b/tests/DS/test_ds_use_local_remote_resources.sh +new file mode 100755 +index 0000000000..706f38c2bc +--- /dev/null ++++ b/tests/DS/test_ds_use_local_remote_resources.sh +@@ -0,0 +1,57 @@ ++#!/usr/bin/env bash ++ ++# Author: ++# Jan Černý ++ ++set -e -o pipefail ++set -x ++ ++. $builddir/tests/test_common.sh ++ ++PROFILE="xccdf_com.example.www_profile_test_remote_res" ++result=$(mktemp) ++stderr=$(mktemp) ++tmpdir=$(mktemp -d) ++cp "${srcdir}/ds_use_local_remote_resources/remote_content_1.3.ds.xml" "$tmpdir" ++cp "${srcdir}/ds_use_local_remote_resources/remote.oval.xml" "$tmpdir" ++pushd "$tmpdir" ++ ++$OSCAP xccdf eval --use-local-file --profile "$PROFILE" --results "$result" "remote_content_1.3.ds.xml" 2>"$stderr" || ret=$? ++[ "$ret" = 2 ] ++ ++grep -q "WARNING: Datastream component 'scap_org.open-scap_cref_remote.oval.xml' points out to the remote 'https://www.example.com/security/data/oval/remote.oval.xml'. Use '--fetch-remote-resources' option to download it." "$stderr" && false ++grep -q "WARNING: Skipping 'https://www.example.com/security/data/oval/remote.oval.xml' file which is referenced from datastream" "$stderr" && false ++ ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass"]/result[text()="pass"]' ++# the remote_res rule is a multicheck with 2 oval definitions so it's twice here ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-remote_res"]/result[text()="pass"]' ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-remote_res"]/result[text()="fail"]' ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass2"]/result[text()="pass"]' ++ ++popd ++rm -f "$result" "$stderr" ++rm -rf "$tmpdir" ++ ++ ++# test the same without --use-local-file to make sure the $tmpdir/remote.oval.xml isn't loaded by oscap ++ ++result=$(mktemp) ++stderr=$(mktemp) ++tmpdir=$(mktemp -d) ++cp "${srcdir}/ds_use_local_remote_resources/remote_content_1.3.ds.xml" "$tmpdir" ++cp "${srcdir}/ds_use_local_remote_resources/remote.oval.xml" "$tmpdir" ++pushd "$tmpdir" ++ ++$OSCAP xccdf eval --profile "$PROFILE" --results "$result" "remote_content_1.3.ds.xml" 2>"$stderr" || ret=$? ++[ "$ret" = 2 ] ++ ++grep -q "WARNING: Datastream component 'scap_org.open-scap_cref_remote.oval.xml' points out to the remote 'https://www.example.com/security/data/oval/remote.oval.xml'. Use '--fetch-remote-resources' option to download it." "$stderr" ++grep -q "WARNING: Skipping 'https://www.example.com/security/data/oval/remote.oval.xml' file which is referenced from datastream" "$stderr" ++ ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass"]/result[text()="pass"]' ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-remote_res"]/result[text()="notchecked"]' ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass2"]/result[text()="pass"]' ++ ++popd ++rm -f "$result" "$stderr" ++rm -rf "$tmpdir" +\ No newline at end of file +diff --git a/utils/oscap-ds.c b/utils/oscap-ds.c +index 772c9c3283..6a42724597 100644 +--- a/utils/oscap-ds.c ++++ b/utils/oscap-ds.c +@@ -82,7 +82,8 @@ static struct oscap_module DS_SDS_SPLIT_MODULE = { + " --xccdf-id - ID of XCCDF in the data stream that should be evaluated.\n" + " --skip-valid - Skips validating of given XCCDF.\n" + " --skip-validation\n" +- " --fetch-remote-resources - Download remote content referenced by data stream.\n", ++ " --fetch-remote-resources - Download remote content referenced by data stream.\n" ++ " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n", + .opt_parser = getopt_ds, + .func = app_ds_sds_split + }; +@@ -186,6 +187,7 @@ bool getopt_ds(int argc, char **argv, struct oscap_action *action) { + {"xccdf-id", required_argument, NULL, DS_OPT_XCCDF_ID}, + {"report-id", required_argument, NULL, DS_OPT_REPORT_ID}, + {"fetch-remote-resources", no_argument, &action->remote_resources, 1}, ++ {"use-local-file", no_argument, &action->use_local_file, 1}, + // end + {0, 0, 0, 0} + }; +@@ -308,7 +310,7 @@ int app_ds_sds_split(const struct oscap_action *action) { + } + ds_sds_session_set_datastream_id(session, f_datastream_id); + +- ds_sds_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ ds_sds_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + ds_sds_session_set_target_dir(session, action->ds_action->target); + if (ds_sds_session_register_component_with_dependencies(session, "checklists", f_component_id, NULL) != 0) { + goto cleanup; +diff --git a/utils/oscap-info.c b/utils/oscap-info.c +index 46721eba05..01db5153b3 100644 +--- a/utils/oscap-info.c ++++ b/utils/oscap-info.c +@@ -63,6 +63,7 @@ struct oscap_module OSCAP_INFO_MODULE = { + .usage = "some-file.xml", + .help = "Options:\n" + " --fetch-remote-resources - Download remote content referenced by data stream.\n" ++ " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" + " --profile - Show info of the profile with the given ID.\n" + " --profiles - Show profiles from the input file in the : format, one line per profile.\n", + .opt_parser = getopt_info, +@@ -531,7 +532,7 @@ static int app_info_sds(struct oscap_source *source, const struct oscap_action * + return OSCAP_ERROR; + } + +- ds_sds_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ ds_sds_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + + /* get collection */ + struct ds_sds_index *sds = ds_sds_session_get_sds_idx(session); +@@ -762,6 +763,7 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action) + /* Command-options */ + const struct option long_options[] = { + {"fetch-remote-resources", no_argument, &action->remote_resources, 1}, ++ {"use-local-file", no_argument, &action->use_local_file, 1}, + {"profile", required_argument, 0, 'p'}, + {"profiles", no_argument, 0, 'n'}, + // end +diff --git a/utils/oscap-oval.c b/utils/oscap-oval.c +index 39bcb92edb..582a5695e3 100644 +--- a/utils/oscap-oval.c ++++ b/utils/oscap-oval.c +@@ -115,7 +115,8 @@ static struct oscap_module OVAL_EVAL = { + " --oval-id <id> - ID of the OVAL component ref in the data stream to use.\n" + " (only applicable for source data streams)\n" + " --fetch-remote-resources - Download remote content referenced by OVAL Definitions.\n" +- " (only applicable for source data streams)\n", ++ " (only applicable for source data streams)\n" ++ " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n", + .opt_parser = getopt_oval_eval, + .func = app_evaluate_oval + }; +@@ -344,7 +345,7 @@ int app_evaluate_oval(const struct oscap_action *action) + /* set OVAL Variables */ + oval_session_set_variables(session, action->f_variables); + +- oval_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ oval_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + /* load all necesary OVAL Definitions and bind OVAL Variables if provided */ + if ((oval_session_load(session)) != 0) + goto cleanup; +@@ -520,6 +521,7 @@ bool getopt_oval_eval(int argc, char **argv, struct oscap_action *action) + { "skip-valid", no_argument, &action->validate, 0 }, + { "skip-validation", no_argument, &action->validate, 0 }, + { "fetch-remote-resources", no_argument, &action->remote_resources, 1}, ++ { "use-local-file", no_argument, &action->use_local_file, 1}, + { 0, 0, 0, 0 } + }; + +diff --git a/utils/oscap-tool.h b/utils/oscap-tool.h +index 18cce0fbce..a3b8781b27 100644 +--- a/utils/oscap-tool.h ++++ b/utils/oscap-tool.h +@@ -174,6 +174,7 @@ struct oscap_action { + int list_dynamic; + char *verbosity_level; + char *fix_type; ++ int use_local_file; + }; + + int app_xslt(const char *infile, const char *xsltfile, const char *outfile, const char **params); +diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c +index 801e54fa35..ef2768bdef 100644 +--- a/utils/oscap-xccdf.c ++++ b/utils/oscap-xccdf.c +@@ -129,6 +129,7 @@ static struct oscap_module XCCDF_EXPORT_OVAL_VARIABLES = { + " --skip-valid - Skip validation.\n" + " --skip-validation\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" ++ " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" + " --datastream-id <id> - ID of the data stream in the collection to use.\n" + " (only applicable for source data streams)\n" + " --xccdf-id <id> - ID of component-ref with XCCDF in the data stream that should be evaluated.\n" +@@ -171,6 +172,7 @@ static struct oscap_module XCCDF_EVAL = { + " (only applicable for source data streams)\n" + " --enforce-signature - Process only signed data streams.\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" ++ " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" + " --progress - Switch to sparse output suitable for progress reporting.\n" + " Format is \"$rule_id:$result\\n\".\n" + " --datastream-id <id> - ID of the data stream in the collection to use.\n" +@@ -199,6 +201,7 @@ static struct oscap_module XCCDF_REMEDIATE = { + " --cpe <name> - Use given CPE dictionary or language (autodetected)\n" + " for applicability checks.\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" ++ " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" + " --results <file> - Write XCCDF Results into file.\n" + " --results-arf <file> - Write ARF (result data stream) into file.\n" + " --stig-viewer <file> - Writes XCCDF results into FILE in a format readable by DISA STIG Viewer\n" +@@ -573,7 +576,7 @@ int app_evaluate_xccdf(const struct oscap_action *action) + if (action->tailoring_file != NULL) + xccdf_session_set_user_tailoring_file(session, action->tailoring_file); + xccdf_session_set_user_tailoring_cid(session, action->tailoring_id); +- xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + xccdf_session_set_product_cpe(session, OSCAP_PRODUCTNAME); + xccdf_session_set_rule(session, action->rule); +@@ -678,7 +681,7 @@ static int app_xccdf_export_oval_variables(const struct oscap_action *action) + xccdf_session_set_benchmark_id(session, action->f_benchmark_id); + } + xccdf_session_set_user_cpe(session, action->cpe); +- xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + xccdf_session_set_custom_oval_eval_fn(session, resolve_variables_wrapper); + +@@ -721,7 +724,7 @@ int app_xccdf_remediate(const struct oscap_action *action) + goto cleanup; + xccdf_session_set_validation(session, action->validate, getenv("OSCAP_FULL_VALIDATION") != NULL); + xccdf_session_set_user_cpe(session, action->cpe); +- xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + + if (xccdf_session_load(session) != 0) +@@ -937,7 +940,7 @@ int app_generate_fix(const struct oscap_action *action) + xccdf_session_set_signature_validation(session, action->validate_signature); + xccdf_session_set_signature_enforcement(session, action->enforce_signature); + xccdf_session_set_user_cpe(session, action->cpe); +- xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + xccdf_session_set_user_tailoring_file(session, action->tailoring_file); + xccdf_session_set_user_tailoring_cid(session, action->tailoring_id); +@@ -1012,7 +1015,7 @@ int app_generate_guide(const struct oscap_action *action) + xccdf_session_set_validation(session, action->validate, getenv("OSCAP_FULL_VALIDATION") != NULL); + xccdf_session_set_signature_validation(session, action->validate_signature); + xccdf_session_set_signature_enforcement(session, action->enforce_signature); +- xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); + xccdf_session_set_user_tailoring_file(session, action->tailoring_file); + xccdf_session_set_user_tailoring_cid(session, action->tailoring_id); + if (xccdf_session_is_sds(session)) { +@@ -1166,6 +1169,7 @@ bool getopt_xccdf(int argc, char **argv, struct oscap_action *action) + {"skip-signature-validation", no_argument, &action->validate_signature, 0}, + {"enforce-signature", no_argument, &action->enforce_signature, 1}, + {"fetch-remote-resources", no_argument, &action->remote_resources, 1}, ++ {"use-local-file", no_argument, &action->use_local_file, 1}, + {"progress", no_argument, &action->progress, 1}, + {"remediate", no_argument, &action->remediate, 1}, + {"hide-profile-info", no_argument, &action->hide_profile_info, 1}, +diff --git a/utils/oscap.8 b/utils/oscap.8 +index 6cae0ffe8a..8dcb9ca330 100644 +--- a/utils/oscap.8 ++++ b/utils/oscap.8 +@@ -72,6 +72,11 @@ For XCCDF or SCAP source data stream files, the info module prints out IDs of in + Allow download of remote components referenced from data stream. + .RE + .TP ++\fB\-\-use-local-file\fR ++.RS ++Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++.RE ++.TP + \fB\-\-profile PROFILE\fR + .RS + Show info of the profile with the given ID. +@@ -201,6 +206,11 @@ Process only digitally signed SCAP source data streams. Data streams without a s + Allow download of remote OVAL content referenced from XCCDF by check-content-ref/@href. + .RE + .TP ++\fB\-\-use-local-file\fR ++.RS ++Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++.RE ++.TP + \fB\-\-remediate\fR + .RS + Execute XCCDF remediation in the process of XCCDF evaluation. This option automatically executes content of XCCDF fix elements for failed rules, and thus this shall be avoided unless for trusted content. Use of this option is always at your own risk. +@@ -226,6 +236,11 @@ Do not validate input/output files. + Allow download of remote OVAL content referenced from XCCDF by check-content-ref/@href. + .RE + .TP ++\fB\-\-use-local-file\fR ++.RS ++Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++.RE ++.TP + \fB\-\-cpe CPE_FILE\fR + .RS + Use given CPE dictionary or language (auto-detected) for applicability checks. +@@ -303,6 +318,11 @@ Select a particular profile from XCCDF document. + Allow download of remote OVAL content referenced from XCCDF by check-content-ref/@href. + .RE + .TP ++\fB\-\-use-local-file\fR ++.RS ++Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++.RE ++.TP + \fB\-\-skip-valid\fR, \fB\-\-skip-validation\fR + .RS + Do not validate input/output files. +@@ -477,6 +497,9 @@ Do not validate input/output files. + .TP + \fB\-\-fetch-remote-resources\fR + Allow download of remote components referenced from data stream. ++.TP ++\fB\-\-use-local-file\fR ++Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + + .TP +@@ -638,6 +661,9 @@ Do not validate input/output files. + .TP + \fB\-\-fetch-remote-resources\fR + Allow download of remote components referenced from data stream. ++.TP ++\fB\-\-use-local-file\fR ++Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + .B \fBsds-validate\fR SOURCE_DS + +From 0a24a755b7102b716da7237717aa78290f8efe9e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Tue, 13 Jul 2021 08:41:08 +0200 +Subject: [PATCH 02/10] Describe using local files in user manual + +To increase visiblity it's also added to the FAQs. +--- + docs/manual/manual.adoc | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc +index 409d502a3b..8655f518f0 100644 +--- a/docs/manual/manual.adoc ++++ b/docs/manual/manual.adoc +@@ -1636,6 +1636,25 @@ Rule xccdf_org.ssgproject.content_rule_partition_for_var_log + ... + ---- + ++On systems that don't have a direct internet access or if the user doesn't want OpenSCAP to connect to the network it's possible to download the remote content using other tools and then pass it to OpenSCAP as a file. ++To do that, use `--use-local-file` instead of `--fetch-remote-resources` as argument of the `oscap` command. ++ ++In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to `name` attribute of the `uri` element within the `catalog` element within the `component-ref` element representing a checklist in the data stream if such file exists. ++ ++In the following example, the `ssg-rhel8-ds.xml` is an SCAP source datastream. ++It needs some checks from a remote component. The remote component's `component-ref` ID is `scap_org.open-scap_cref_security-data-oval-com.redhat.rhsa-RHEL8.xml` and the `component-ref` is pointing to `https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml`. ++The checks from the remote component are used in the only checklist in the data stream. ++The `component-ref` of the checklist component contains a `catalog` where one of the `uri` elements maps the remote component's `component-ref` ID in the `uri` attribute to the actual name `security-data-oval-com.redhat.rhsa-RHEL8.xml` which is the value of the `name` attribute. ++Therefore, we can download the remote data from `https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml` and save it as `security-data-oval-com.redhat.rhsa-RHEL8.xml`. ++Then, we can optionally copy the file to the computer which we want to scan. ++Then, we execute `oscap` with `--use-local-file` in that directory. ++It will pick the file and use it instead of the remote data and it won't connect to the network. ++ ++---- ++$ wget -O security-data-oval-com.redhat.rhsa-RHEL8.xml https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml ++... ++$ oscap xccdf eval --use-local-file --profile ospp ssg-rhel8-ds.xml ++---- + + == Practical Examples + This section demonstrates practical usage of certain security content provided +@@ -2133,3 +2152,11 @@ The downloaded guidance contains rule descriptions, but it doesn't contain OVAL + + Make sure that you provide the ID of the customized profile in `--profile` option instead of the ID of the original profile. + If you created the tailoring file using SCAP Workbench, you were prompted to choose the ID of the customized profile. You can display the ID of the customized profile by running `oscap info <your_tailoring_file>`. By default, the ID of the customized profile ends with `_customized` suffix. ++ ++*My SCAP source data stream contains rule `security_patches_up_to_date` which needs to download some data from the internet to work.* ++*But I'm in an air gapped environment so it can't download it.* ++*Can I download it separately and pass it to oscap?* ++ ++Yes, it's possible, you can download the file on other computer that is connected to the internet and then copy the file to the system where you run `oscap`. ++Instead of the `--fetch-remote-resources` option you will use the `--use-local-file` option. ++For more information, please refer to section <<_using_external_or_remote_resources,Using external or remote resources>>. + +From 86ce263d26405d08abdf027e9b71bea69386e51f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Thu, 22 Jul 2021 16:19:36 +0200 +Subject: [PATCH 03/10] Provide a directory path + +This will allow users to pass a directory path where downloaded files +are stored. + +Currently, when it attempts to open a file, the path is resolved +relatively to the data stream file. For example, that means if the data +stream file is located in /usr/share/xml/scap you need to copy the file +there and you can have permission problems. Instead, we will pass the +directory and store the downloaded file to the directory. + +The interface and command line options are updated to accept the +directory path. Moreover, we have realized that there can be multiple +remote components in a single source data stream. Therefore, we have +found a more suitable name for the option which would more fit that the +option accepts an argument and there can be multiple files. +--- + docs/manual/manual.adoc | 16 +++++----- + src/DS/ds_sds_session.c | 10 +++--- + src/DS/ds_sds_session_priv.h | 2 +- + src/DS/public/ds_sds_session.h | 7 +++-- + src/DS/sds.c | 31 ++++++++++++++++--- + src/OVAL/oval_session.c | 10 +++--- + src/OVAL/public/oval_session.h | 7 +++-- + src/XCCDF/public/xccdf_session.h | 7 +++-- + src/XCCDF/xccdf_session.c | 12 +++---- + .../DS/test_ds_use_local_remote_resources.sh | 19 ++++++------ + utils/oscap-ds.c | 10 ++++-- + utils/oscap-info.c | 9 ++++-- + utils/oscap-oval.c | 12 ++++--- + utils/oscap-tool.h | 2 +- + utils/oscap-xccdf.c | 24 ++++++++------ + utils/oscap.8 | 18 +++++------ + 16 files changed, 118 insertions(+), 78 deletions(-) + +diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc +index 8655f518f0..9f0fc38b75 100644 +--- a/docs/manual/manual.adoc ++++ b/docs/manual/manual.adoc +@@ -1636,8 +1636,8 @@ Rule xccdf_org.ssgproject.content_rule_partition_for_var_log + ... + ---- + +-On systems that don't have a direct internet access or if the user doesn't want OpenSCAP to connect to the network it's possible to download the remote content using other tools and then pass it to OpenSCAP as a file. +-To do that, use `--use-local-file` instead of `--fetch-remote-resources` as argument of the `oscap` command. ++On systems that don't have a direct internet access or if the user doesn't want OpenSCAP to connect to the network it's possible to download the remote content using other tools, save it to a directory and then pass it to OpenSCAP as a file. ++To do that, use `--local-files` instead of `--fetch-remote-resources` as argument of the `oscap` command. + + In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to `name` attribute of the `uri` element within the `catalog` element within the `component-ref` element representing a checklist in the data stream if such file exists. + +@@ -1645,15 +1645,15 @@ In the following example, the `ssg-rhel8-ds.xml` is an SCAP source datastream. + It needs some checks from a remote component. The remote component's `component-ref` ID is `scap_org.open-scap_cref_security-data-oval-com.redhat.rhsa-RHEL8.xml` and the `component-ref` is pointing to `https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml`. + The checks from the remote component are used in the only checklist in the data stream. + The `component-ref` of the checklist component contains a `catalog` where one of the `uri` elements maps the remote component's `component-ref` ID in the `uri` attribute to the actual name `security-data-oval-com.redhat.rhsa-RHEL8.xml` which is the value of the `name` attribute. +-Therefore, we can download the remote data from `https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml` and save it as `security-data-oval-com.redhat.rhsa-RHEL8.xml`. +-Then, we can optionally copy the file to the computer which we want to scan. +-Then, we execute `oscap` with `--use-local-file` in that directory. ++Therefore, we can download the remote data from `https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml` and save it as `security-data-oval-com.redhat.rhsa-RHEL8.xml` to some directory. ++Then, we execute `oscap` with `--local-files` and provide a path to the directory where it's located. + It will pick the file and use it instead of the remote data and it won't connect to the network. + + ---- +-$ wget -O security-data-oval-com.redhat.rhsa-RHEL8.xml https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml ++$ mkdir ~/scap-files ++$ wget -O ~/scap-files/security-data-oval-com.redhat.rhsa-RHEL8.xml https://www.redhat.com/security/data/oval/com.redhat.rhsa-RHEL8.xml + ... +-$ oscap xccdf eval --use-local-file --profile ospp ssg-rhel8-ds.xml ++$ oscap xccdf eval --local-files ~/scap-files --profile ospp ssg-rhel8-ds.xml + ---- + + == Practical Examples +@@ -2158,5 +2158,5 @@ If you created the tailoring file using SCAP Workbench, you were prompted to cho + *Can I download it separately and pass it to oscap?* + + Yes, it's possible, you can download the file on other computer that is connected to the internet and then copy the file to the system where you run `oscap`. +-Instead of the `--fetch-remote-resources` option you will use the `--use-local-file` option. ++Instead of the `--fetch-remote-resources` option you will use the `--local-files` option. + For more information, please refer to section <<_using_external_or_remote_resources,Using external or remote resources>>. +diff --git a/src/DS/ds_sds_session.c b/src/DS/ds_sds_session.c +index 9d9a9c8b52..99f4bc20fd 100644 +--- a/src/DS/ds_sds_session.c ++++ b/src/DS/ds_sds_session.c +@@ -55,7 +55,7 @@ struct ds_sds_session { + struct oscap_htable *component_uris; ///< maps component refs to component URIs + bool fetch_remote_resources; ///< Allows loading of external components; + download_progress_calllback_t progress; ///< Callback to report progress of download. +- bool use_local_file; ///< Use a locally downloaded copy of a remote resource if it exists ++ const char *local_files; ///< Path to the directory where local copies of remote components are located + }; + + /** +@@ -338,10 +338,10 @@ int ds_sds_session_register_component_with_dependencies(struct ds_sds_session *s + return res; + } + +-void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback) ++void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, bool allowed, const char *local_files, download_progress_calllback_t callback) + { + session->fetch_remote_resources = allowed; +- session->use_local_file = use_local_file; ++ session->local_files = local_files; + session->progress = (callback != NULL) ? callback : download_progress_empty_calllback; + } + +@@ -350,9 +350,9 @@ void ds_sds_session_set_remote_resources(struct ds_sds_session *session, bool al + ds_sds_session_configure_remote_resources(session, allowed, false, callback); + } + +-bool ds_sds_session_can_use_local_file(struct ds_sds_session *session) ++const char *ds_sds_session_local_files(struct ds_sds_session *session) + { +- return session->use_local_file; ++ return session->local_files; + } + + int ds_sds_session_dump_component_files(struct ds_sds_session *session) +diff --git a/src/DS/ds_sds_session_priv.h b/src/DS/ds_sds_session_priv.h +index 018cd053ec..5e6dfc7672 100644 +--- a/src/DS/ds_sds_session_priv.h ++++ b/src/DS/ds_sds_session_priv.h +@@ -40,7 +40,7 @@ struct oscap_htable *ds_sds_session_get_component_sources(struct ds_sds_session + struct oscap_htable *ds_sds_session_get_component_uris(struct ds_sds_session *session); + const char *ds_sds_session_get_readable_origin(const struct ds_sds_session *session); + bool ds_sds_session_fetch_remote_resources(struct ds_sds_session *session); +-bool ds_sds_session_can_use_local_file(struct ds_sds_session *session); ++const char *ds_sds_session_local_files(struct ds_sds_session *session); + download_progress_calllback_t ds_sds_session_remote_resources_progress(struct ds_sds_session *session); + + void download_progress_empty_calllback(bool warning, const char * format, ...); +diff --git a/src/DS/public/ds_sds_session.h b/src/DS/public/ds_sds_session.h +index 695a0df215..83e7feadba 100644 +--- a/src/DS/public/ds_sds_session.h ++++ b/src/DS/public/ds_sds_session.h +@@ -208,12 +208,13 @@ OSCAP_API OSCAP_DEPRECATED(void ds_sds_session_set_remote_resources(struct ds_sd + * @param session The source data stream session + * @param allowed Whether is download of remote resources allowed in this + * session (defaults to false) +- * @param use_local_file Allows to use a locally downloaded copy of the remote +- * resource if it exists (defaults to false) ++ * @param local_files Allows to use a locally downloaded copy of the remote ++ * resources. Contains a path to a directory where the files are stored ++ * (defaults to NULL). + * @param callback used to notify user about download proceeds. This might be + * safely set to NULL -- ignoring user notification. + */ +-OSCAP_API void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback); ++OSCAP_API void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, bool allowed, const char *local_files, download_progress_calllback_t callback); + + /** + * Returns HTML representation of selected checklist in form of OpenSCAP guide. +diff --git a/src/DS/sds.c b/src/DS/sds.c +index b1737aa57f..365ae96987 100644 +--- a/src/DS/sds.c ++++ b/src/DS/sds.c +@@ -425,14 +425,35 @@ static int ds_sds_dump_component_by_href(struct ds_sds_session *session, char* x + * the URL defined in @xlink:href. This way people can provide the + * previously downloaded component which might be useful on systems + * with limited internet access. This behavior is allowed only when +- * --use-local-file is used on the command line. ++ * --local-files is used on the command line. + * See: https://bugzilla.redhat.com/show_bug.cgi?id=1970527 + * See: https://access.redhat.com/solutions/5185891 + */ +- struct stat sb; +- if (ds_sds_session_can_use_local_file(session) && stat(relative_filepath, &sb) == 0) { +- dI("Using local file '%s' instead of '%s'", relative_filepath, xlink_href); +- return ds_sds_dump_file_component(relative_filepath, *component_id, session, target_filename_dirname, relative_filepath); ++ const char *local_files = ds_sds_session_local_files(session); ++ if (local_files != NULL) { ++ char *local_filepath = oscap_path_join(local_files, relative_filepath); ++ struct stat sb; ++ if (stat(local_filepath, &sb) == 0) { ++ //if (ds_sds_session_can_use_local_file(session)) { ++ dI("Using local file '%s' instead of '%s'", local_filepath, xlink_href); ++ struct oscap_source *source_file = oscap_source_new_from_file(local_filepath); ++ xmlDoc *doc = oscap_source_get_xmlDoc(source_file); ++ if (doc == NULL) { ++ free(local_filepath); ++ return -1; ++ } ++ xmlNodePtr inner_root = ds_sds_get_component_root_by_id(doc, *component_id); ++ ++ if (ds_sds_register_component(session, doc, inner_root, *component_id, target_filename_dirname, relative_filepath) != 0) { ++ free(local_filepath); ++ return -1; ++ } ++ free(local_filepath); ++ return 0; ++ } else { ++ dW("Can't use local file '%s' instead of '%s'", local_filepath, xlink_href); ++ } ++ free(local_filepath); + } + + static bool fetch_remote_resources_suggested = false; +diff --git a/src/OVAL/oval_session.c b/src/OVAL/oval_session.c +index 5adda43f66..bb73543ed8 100644 +--- a/src/OVAL/oval_session.c ++++ b/src/OVAL/oval_session.c +@@ -84,7 +84,7 @@ struct oval_session { + bool full_validation; + bool fetch_remote_resources; + download_progress_calllback_t progress; +- bool use_local_file; ++ const char *local_files; + }; + + struct oval_session *oval_session_new(const char *filename) +@@ -224,7 +224,7 @@ static int oval_session_load_definitions(struct oval_session *session) + if ((session->sds_session = ds_sds_session_new_from_source(session->source)) == NULL) { + return 1; + } +- ds_sds_session_configure_remote_resources(session->sds_session, session->fetch_remote_resources, session->use_local_file, session->progress); ++ ds_sds_session_configure_remote_resources(session->sds_session, session->fetch_remote_resources, session->local_files, session->progress); + ds_sds_session_set_datastream_id(session->sds_session, session->datastream_id); + if (ds_sds_session_register_component_with_dependencies(session->sds_session, + "checks", session->component_id, "oval.xml") != 0) { +@@ -452,16 +452,16 @@ void oval_session_set_export_system_characteristics(struct oval_session *session + session->export_sys_chars = export; + } + +-void oval_session_configure_remote_resources(struct oval_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback) ++void oval_session_configure_remote_resources(struct oval_session *session, bool allowed, const char *local_files, download_progress_calllback_t callback) + { + session->fetch_remote_resources = allowed; +- session->use_local_file = use_local_file; ++ session->local_files = local_files; + session->progress = callback; + } + + void oval_session_set_remote_resources(struct oval_session *session, bool allowed, download_progress_calllback_t callback) + { +- oval_session_configure_remote_resources(session, allowed, false, callback); ++ oval_session_configure_remote_resources(session, allowed, NULL, callback); + } + + void oval_session_free(struct oval_session *session) +diff --git a/src/OVAL/public/oval_session.h b/src/OVAL/public/oval_session.h +index d485676747..378c77c490 100644 +--- a/src/OVAL/public/oval_session.h ++++ b/src/OVAL/public/oval_session.h +@@ -247,12 +247,13 @@ OSCAP_API OSCAP_DEPRECATED(void oval_session_set_remote_resources(struct oval_se + * @param session an \ref oval_session + * @param allowed Whether is download of remote resources allowed in this + * session (defaults to false) +- * @param use_local_file Allows to use a locally downloaded copy of the remote +- * resource if it exists (defaults to false) ++ * @param local_files Allows to use a locally downloaded copy of the remote ++ * resources. Contains a path to a directory where the files are stored ++ * (defaults to NULL). + * @param callback used to notify user about download proceeds. This might be + * safely set to NULL -- ignoring user notification. + */ +-OSCAP_API void oval_session_configure_remote_resources(struct oval_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback); ++OSCAP_API void oval_session_configure_remote_resources(struct oval_session *session, bool allowed, const char *local_files, download_progress_calllback_t callback); + + /** + * Destructor of an \ref oval_session. +diff --git a/src/XCCDF/public/xccdf_session.h b/src/XCCDF/public/xccdf_session.h +index 2eb7128019..466ef2b9d4 100644 +--- a/src/XCCDF/public/xccdf_session.h ++++ b/src/XCCDF/public/xccdf_session.h +@@ -240,12 +240,13 @@ OSCAP_API OSCAP_DEPRECATED(void xccdf_session_set_remote_resources(struct xccdf_ + * @param session XCCDF Session + * @param allowed Whether is download od remote resources allowed in this + * session (defaults to false) +- * @param use_local_file Allows to use a locally downloaded copy of the remote +- * resource if it exists (defaults to false) ++ * @param local_files Allows to use a locally downloaded copy of the remote ++ * resources. Contains a path to a directory where the files are stored ++ * (defaults to NULL). + * @param callback used to notify user about download proceeds. This might be + * safely set to NULL -- ignoring user notification. + */ +-OSCAP_API void xccdf_session_configure_remote_resources(struct xccdf_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback); ++OSCAP_API void xccdf_session_configure_remote_resources(struct xccdf_session *session, bool allowed, const char *local_files, download_progress_calllback_t callback); + + /** + * Disable or allow loading of depending content (OVAL, SCE, CPE) +diff --git a/src/XCCDF/xccdf_session.c b/src/XCCDF/xccdf_session.c +index 85fcc90d23..990e40702b 100644 +--- a/src/XCCDF/xccdf_session.c ++++ b/src/XCCDF/xccdf_session.c +@@ -87,7 +87,7 @@ struct xccdf_session { + } ds; + struct { + bool fetch_remote_resources; ///< Allows download of remote resources (not applicable when user sets custom oval files) +- bool use_local_file; ///< Use a locally downloaded copy of a remote resource if it exists ++ const char *local_files; ///< Path to the directory where local copies of remote components are located + download_progress_calllback_t progress; ///< Callback to report progress of download. + struct oval_content_resource **custom_resources;///< OVAL files required by user + struct oval_content_resource **resources;///< OVAL files referenced from XCCDF +@@ -103,7 +103,7 @@ struct xccdf_session { + char *arf_file; ///< Path to ARF file to export + char *xccdf_file; ///< Path to XCCDF file to export + char *xccdf_stig_viewer_file; ///< Path to STIG Viewer XCCDF file to export +- char *report_file; ///< Path to HTML file to eport ++ char *report_file; ///< Path to HTML file to export + bool oval_results; ///< Shall be the OVAL results files exported? + bool oval_variables; ///< Shall be the OVAL variable files exported? + bool check_engine_plugins_results; ///< Shall the check engine plugins results be exported? +@@ -628,7 +628,7 @@ static struct ds_sds_session *xccdf_session_get_ds_sds_session(struct xccdf_sess + } + + +-void xccdf_session_configure_remote_resources(struct xccdf_session *session, bool allowed, bool use_local_file, download_progress_calllback_t callback) ++void xccdf_session_configure_remote_resources(struct xccdf_session *session, bool allowed, const char *local_files, download_progress_calllback_t callback) + { + if (callback == NULL) { + // With empty cb we don't have to check for NULL +@@ -637,19 +637,19 @@ void xccdf_session_configure_remote_resources(struct xccdf_session *session, boo + } + + session->oval.fetch_remote_resources = allowed; +- session->oval.use_local_file = use_local_file; ++ session->oval.local_files = local_files; + session->oval.progress = callback; + + if (xccdf_session_is_sds(session)) { + // We have to propagate this option to allow loading + // of external datastream components +- ds_sds_session_configure_remote_resources(xccdf_session_get_ds_sds_session(session), allowed, use_local_file, callback); ++ ds_sds_session_configure_remote_resources(xccdf_session_get_ds_sds_session(session), allowed, local_files, callback); + } + } + + void xccdf_session_set_remote_resources(struct xccdf_session *session, bool allowed, download_progress_calllback_t callback) + { +- xccdf_session_configure_remote_resources(session, allowed, false, callback); ++ xccdf_session_configure_remote_resources(session, allowed, NULL, callback); + } + + void xccdf_session_set_loading_flags(struct xccdf_session *session, xccdf_session_loading_flags_t flags) +diff --git a/tests/DS/test_ds_use_local_remote_resources.sh b/tests/DS/test_ds_use_local_remote_resources.sh +index 706f38c2bc..e4a1f0eedd 100755 +--- a/tests/DS/test_ds_use_local_remote_resources.sh ++++ b/tests/DS/test_ds_use_local_remote_resources.sh +@@ -11,12 +11,14 @@ set -x + PROFILE="xccdf_com.example.www_profile_test_remote_res" + result=$(mktemp) + stderr=$(mktemp) +-tmpdir=$(mktemp -d) +-cp "${srcdir}/ds_use_local_remote_resources/remote_content_1.3.ds.xml" "$tmpdir" +-cp "${srcdir}/ds_use_local_remote_resources/remote.oval.xml" "$tmpdir" +-pushd "$tmpdir" +- +-$OSCAP xccdf eval --use-local-file --profile "$PROFILE" --results "$result" "remote_content_1.3.ds.xml" 2>"$stderr" || ret=$? ++tmpdir1=$(mktemp -d) ++tmpdir2=$(mktemp -d) ++tmpdir3=$(mktemp -d) ++cp "${srcdir}/ds_use_local_remote_resources/remote_content_1.3.ds.xml" "$tmpdir2" ++cp "${srcdir}/ds_use_local_remote_resources/remote.oval.xml" "$tmpdir3" ++pushd "$tmpdir1" ++ ++$OSCAP xccdf eval --local-files "$tmpdir3" --profile "$PROFILE" --results "$result" "$tmpdir2/remote_content_1.3.ds.xml" 2>"$stderr" || ret=$? + [ "$ret" = 2 ] + + grep -q "WARNING: Datastream component 'scap_org.open-scap_cref_remote.oval.xml' points out to the remote 'https://www.example.com/security/data/oval/remote.oval.xml'. Use '--fetch-remote-resources' option to download it." "$stderr" && false +@@ -30,10 +32,9 @@ assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass2"]/r + + popd + rm -f "$result" "$stderr" +-rm -rf "$tmpdir" +- ++rm -rf "$tmpdir1" "$tmpdir2" "$tmpdir3" + +-# test the same without --use-local-file to make sure the $tmpdir/remote.oval.xml isn't loaded by oscap ++# test the same without --local-files to make sure the $tmpdir/remote.oval.xml isn't loaded by oscap + + result=$(mktemp) + stderr=$(mktemp) +diff --git a/utils/oscap-ds.c b/utils/oscap-ds.c +index 6a42724597..8207f097ec 100644 +--- a/utils/oscap-ds.c ++++ b/utils/oscap-ds.c +@@ -172,7 +172,8 @@ static struct oscap_module* DS_SUBMODULES[DS_SUBMODULES_NUM] = { + enum ds_opt { + DS_OPT_DATASTREAM_ID = 1, + DS_OPT_XCCDF_ID, +- DS_OPT_REPORT_ID ++ DS_OPT_REPORT_ID, ++ DS_OPT_LOCAL_FILES + }; + + bool getopt_ds(int argc, char **argv, struct oscap_action *action) { +@@ -187,7 +188,7 @@ bool getopt_ds(int argc, char **argv, struct oscap_action *action) { + {"xccdf-id", required_argument, NULL, DS_OPT_XCCDF_ID}, + {"report-id", required_argument, NULL, DS_OPT_REPORT_ID}, + {"fetch-remote-resources", no_argument, &action->remote_resources, 1}, +- {"use-local-file", no_argument, &action->use_local_file, 1}, ++ {"local-files", required_argument, NULL, DS_OPT_LOCAL_FILES}, + // end + {0, 0, 0, 0} + }; +@@ -199,6 +200,9 @@ bool getopt_ds(int argc, char **argv, struct oscap_action *action) { + case DS_OPT_DATASTREAM_ID: action->f_datastream_id = optarg; break; + case DS_OPT_XCCDF_ID: action->f_xccdf_id = optarg; break; + case DS_OPT_REPORT_ID: action->f_report_id = optarg; break; ++ case DS_OPT_LOCAL_FILES: ++ action->local_files = optarg; ++ break; + case 0: break; + default: return oscap_module_usage(action->module, stderr, NULL); + } +@@ -310,7 +314,7 @@ int app_ds_sds_split(const struct oscap_action *action) { + } + ds_sds_session_set_datastream_id(session, f_datastream_id); + +- ds_sds_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ ds_sds_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + ds_sds_session_set_target_dir(session, action->ds_action->target); + if (ds_sds_session_register_component_with_dependencies(session, "checklists", f_component_id, NULL) != 0) { + goto cleanup; +diff --git a/utils/oscap-info.c b/utils/oscap-info.c +index 01db5153b3..2e6f5003aa 100644 +--- a/utils/oscap-info.c ++++ b/utils/oscap-info.c +@@ -63,7 +63,7 @@ struct oscap_module OSCAP_INFO_MODULE = { + .usage = "some-file.xml", + .help = "Options:\n" + " --fetch-remote-resources - Download remote content referenced by data stream.\n" +- " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --profile <id> - Show info of the profile with the given ID.\n" + " --profiles - Show profiles from the input file in the <id>:<title> format, one line per profile.\n", + .opt_parser = getopt_info, +@@ -532,7 +532,7 @@ static int app_info_sds(struct oscap_source *source, const struct oscap_action * + return OSCAP_ERROR; + } + +- ds_sds_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ ds_sds_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + + /* get collection */ + struct ds_sds_index *sds = ds_sds_session_get_sds_idx(session); +@@ -763,7 +763,7 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action) + /* Command-options */ + const struct option long_options[] = { + {"fetch-remote-resources", no_argument, &action->remote_resources, 1}, +- {"use-local-file", no_argument, &action->use_local_file, 1}, ++ {"local-files", required_argument, NULL, 'l'}, + {"profile", required_argument, 0, 'p'}, + {"profiles", no_argument, 0, 'n'}, + // end +@@ -781,6 +781,9 @@ bool getopt_info(int argc, char **argv, struct oscap_action *action) + action->show_profiles_only = 1; + action->provide_machine_readable_output = 1; + break; ++ case 'l': ++ action->local_files = optarg; ++ break; + default: return oscap_module_usage(action->module, stderr, NULL); + } + } +diff --git a/utils/oscap-oval.c b/utils/oscap-oval.c +index 582a5695e3..3a206bb3c4 100644 +--- a/utils/oscap-oval.c ++++ b/utils/oscap-oval.c +@@ -116,7 +116,7 @@ static struct oscap_module OVAL_EVAL = { + " (only applicable for source data streams)\n" + " --fetch-remote-resources - Download remote content referenced by OVAL Definitions.\n" + " (only applicable for source data streams)\n" +- " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n", ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n", + .opt_parser = getopt_oval_eval, + .func = app_evaluate_oval + }; +@@ -345,7 +345,7 @@ int app_evaluate_oval(const struct oscap_action *action) + /* set OVAL Variables */ + oval_session_set_variables(session, action->f_variables); + +- oval_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ oval_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + /* load all necesary OVAL Definitions and bind OVAL Variables if provided */ + if ((oval_session_load(session)) != 0) + goto cleanup; +@@ -500,7 +500,8 @@ enum oval_opt { + OVAL_OPT_DIRECTIVES, + OVAL_OPT_DATASTREAM_ID, + OVAL_OPT_OVAL_ID, +- OVAL_OPT_OUTPUT = 'o' ++ OVAL_OPT_OUTPUT = 'o', ++ OVAL_OPT_LOCAL_FILES + }; + + #if defined(OVAL_PROBES_ENABLED) +@@ -521,7 +522,7 @@ bool getopt_oval_eval(int argc, char **argv, struct oscap_action *action) + { "skip-valid", no_argument, &action->validate, 0 }, + { "skip-validation", no_argument, &action->validate, 0 }, + { "fetch-remote-resources", no_argument, &action->remote_resources, 1}, +- { "use-local-file", no_argument, &action->use_local_file, 1}, ++ { "local-files", required_argument, NULL, OVAL_OPT_LOCAL_FILES}, + { 0, 0, 0, 0 } + }; + +@@ -535,6 +536,9 @@ bool getopt_oval_eval(int argc, char **argv, struct oscap_action *action) + case OVAL_OPT_DIRECTIVES: action->f_directives = optarg; break; + case OVAL_OPT_DATASTREAM_ID: action->f_datastream_id = optarg; break; + case OVAL_OPT_OVAL_ID: action->f_oval_id = optarg; break; ++ case OVAL_OPT_LOCAL_FILES: ++ action->local_files = optarg; ++ break; + case 0: break; + default: return oscap_module_usage(action->module, stderr, NULL); + } +diff --git a/utils/oscap-tool.h b/utils/oscap-tool.h +index a3b8781b27..c0596f3e60 100644 +--- a/utils/oscap-tool.h ++++ b/utils/oscap-tool.h +@@ -174,7 +174,7 @@ struct oscap_action { + int list_dynamic; + char *verbosity_level; + char *fix_type; +- int use_local_file; ++ char *local_files; + }; + + int app_xslt(const char *infile, const char *xsltfile, const char *outfile, const char **params); +diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c +index ef2768bdef..54b8df5467 100644 +--- a/utils/oscap-xccdf.c ++++ b/utils/oscap-xccdf.c +@@ -129,7 +129,7 @@ static struct oscap_module XCCDF_EXPORT_OVAL_VARIABLES = { + " --skip-valid - Skip validation.\n" + " --skip-validation\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" +- " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --datastream-id <id> - ID of the data stream in the collection to use.\n" + " (only applicable for source data streams)\n" + " --xccdf-id <id> - ID of component-ref with XCCDF in the data stream that should be evaluated.\n" +@@ -172,7 +172,7 @@ static struct oscap_module XCCDF_EVAL = { + " (only applicable for source data streams)\n" + " --enforce-signature - Process only signed data streams.\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" +- " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --progress - Switch to sparse output suitable for progress reporting.\n" + " Format is \"$rule_id:$result\\n\".\n" + " --datastream-id <id> - ID of the data stream in the collection to use.\n" +@@ -201,7 +201,7 @@ static struct oscap_module XCCDF_REMEDIATE = { + " --cpe <name> - Use given CPE dictionary or language (autodetected)\n" + " for applicability checks.\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" +- " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --results <file> - Write XCCDF Results into file.\n" + " --results-arf <file> - Write ARF (result data stream) into file.\n" + " --stig-viewer <file> - Writes XCCDF results into FILE in a format readable by DISA STIG Viewer\n" +@@ -576,7 +576,7 @@ int app_evaluate_xccdf(const struct oscap_action *action) + if (action->tailoring_file != NULL) + xccdf_session_set_user_tailoring_file(session, action->tailoring_file); + xccdf_session_set_user_tailoring_cid(session, action->tailoring_id); +- xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + xccdf_session_set_product_cpe(session, OSCAP_PRODUCTNAME); + xccdf_session_set_rule(session, action->rule); +@@ -681,7 +681,7 @@ static int app_xccdf_export_oval_variables(const struct oscap_action *action) + xccdf_session_set_benchmark_id(session, action->f_benchmark_id); + } + xccdf_session_set_user_cpe(session, action->cpe); +- xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + xccdf_session_set_custom_oval_eval_fn(session, resolve_variables_wrapper); + +@@ -724,7 +724,7 @@ int app_xccdf_remediate(const struct oscap_action *action) + goto cleanup; + xccdf_session_set_validation(session, action->validate, getenv("OSCAP_FULL_VALIDATION") != NULL); + xccdf_session_set_user_cpe(session, action->cpe); +- xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + + if (xccdf_session_load(session) != 0) +@@ -940,7 +940,7 @@ int app_generate_fix(const struct oscap_action *action) + xccdf_session_set_signature_validation(session, action->validate_signature); + xccdf_session_set_signature_enforcement(session, action->enforce_signature); + xccdf_session_set_user_cpe(session, action->cpe); +- xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + xccdf_session_set_custom_oval_files(session, action->f_ovals); + xccdf_session_set_user_tailoring_file(session, action->tailoring_file); + xccdf_session_set_user_tailoring_cid(session, action->tailoring_id); +@@ -1015,7 +1015,7 @@ int app_generate_guide(const struct oscap_action *action) + xccdf_session_set_validation(session, action->validate, getenv("OSCAP_FULL_VALIDATION") != NULL); + xccdf_session_set_signature_validation(session, action->validate_signature); + xccdf_session_set_signature_enforcement(session, action->enforce_signature); +- xccdf_session_configure_remote_resources(session, action->remote_resources, action->use_local_file, download_reporting_callback); ++ xccdf_session_configure_remote_resources(session, action->remote_resources, action->local_files, download_reporting_callback); + xccdf_session_set_user_tailoring_file(session, action->tailoring_file); + xccdf_session_set_user_tailoring_cid(session, action->tailoring_id); + if (xccdf_session_is_sds(session)) { +@@ -1128,7 +1128,8 @@ enum oval_opt { + XCCDF_OPT_CPE_DICT, + XCCDF_OPT_OUTPUT = 'o', + XCCDF_OPT_RESULT_ID = 'i', +- XCCDF_OPT_FIX_TYPE ++ XCCDF_OPT_FIX_TYPE, ++ XCCDF_OPT_LOCAL_FILES + }; + + bool getopt_xccdf(int argc, char **argv, struct oscap_action *action) +@@ -1160,6 +1161,7 @@ bool getopt_xccdf(int argc, char **argv, struct oscap_action *action) + {"cpe-dict", required_argument, NULL, XCCDF_OPT_CPE_DICT}, // DEPRECATED! + {"sce-template", required_argument, NULL, XCCDF_OPT_SCE_TEMPLATE}, + {"fix-type", required_argument, NULL, XCCDF_OPT_FIX_TYPE}, ++ {"local-files", required_argument, NULL, XCCDF_OPT_LOCAL_FILES}, + // flags + {"force", no_argument, &action->force, 1}, + {"oval-results", no_argument, &action->oval_results, 1}, +@@ -1169,7 +1171,6 @@ bool getopt_xccdf(int argc, char **argv, struct oscap_action *action) + {"skip-signature-validation", no_argument, &action->validate_signature, 0}, + {"enforce-signature", no_argument, &action->enforce_signature, 1}, + {"fetch-remote-resources", no_argument, &action->remote_resources, 1}, +- {"use-local-file", no_argument, &action->use_local_file, 1}, + {"progress", no_argument, &action->progress, 1}, + {"remediate", no_argument, &action->remediate, 1}, + {"hide-profile-info", no_argument, &action->hide_profile_info, 1}, +@@ -1215,6 +1216,9 @@ bool getopt_xccdf(int argc, char **argv, struct oscap_action *action) + case XCCDF_OPT_FIX_TYPE: + action->fix_type = optarg; + break; ++ case XCCDF_OPT_LOCAL_FILES: ++ action->local_files = optarg; ++ break; + case 0: break; + default: return oscap_module_usage(action->module, stderr, NULL); + } +diff --git a/utils/oscap.8 b/utils/oscap.8 +index 8dcb9ca330..4f5f6259e3 100644 +--- a/utils/oscap.8 ++++ b/utils/oscap.8 +@@ -72,9 +72,9 @@ For XCCDF or SCAP source data stream files, the info module prints out IDs of in + Allow download of remote components referenced from data stream. + .RE + .TP +-\fB\-\-use-local-file\fR ++\fB\-\-local-files DIRECTORY\fR + .RS +-Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + \fB\-\-profile PROFILE\fR +@@ -206,9 +206,9 @@ Process only digitally signed SCAP source data streams. Data streams without a s + Allow download of remote OVAL content referenced from XCCDF by check-content-ref/@href. + .RE + .TP +-\fB\-\-use-local-file\fR ++\fB\-\-local-files DIRECTORY\fR + .RS +-Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + \fB\-\-remediate\fR +@@ -236,9 +236,9 @@ Do not validate input/output files. + Allow download of remote OVAL content referenced from XCCDF by check-content-ref/@href. + .RE + .TP +-\fB\-\-use-local-file\fR ++\fB\-\-local-files DIRECTORY\fR + .RS +-Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + \fB\-\-cpe CPE_FILE\fR +@@ -318,7 +318,7 @@ Select a particular profile from XCCDF document. + Allow download of remote OVAL content referenced from XCCDF by check-content-ref/@href. + .RE + .TP +-\fB\-\-use-local-file\fR ++\fB\-\-local-files DIRECTORY\fR + .RS + Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE +@@ -498,7 +498,7 @@ Do not validate input/output files. + \fB\-\-fetch-remote-resources\fR + Allow download of remote components referenced from data stream. + .TP +-\fB\-\-use-local-file\fR ++\fB\-\-local-files DIRECTORY\fR + Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + +@@ -662,7 +662,7 @@ Do not validate input/output files. + \fB\-\-fetch-remote-resources\fR + Allow download of remote components referenced from data stream. + .TP +-\fB\-\-use-local-file\fR ++\fB\-\-local-files DIRECTORY\fR + Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + +From 6732eb94fb6f4606be388e27c347a882917dfb3f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 23 Jul 2021 12:58:33 +0200 +Subject: [PATCH 04/10] Show a warning + +When --local-files is used but the file which is expected to be +loaded instead of the remote data stream component doesn't exist +we will print a warning so that the user is informed and he would +know what file is expected. + +Also added a simple test to test the aforementioned warning scenario. +--- + src/DS/sds.c | 7 ++++- + .../DS/test_ds_use_local_remote_resources.sh | 26 ++++++++++++++++++- + 2 files changed, 31 insertions(+), 2 deletions(-) + +diff --git a/src/DS/sds.c b/src/DS/sds.c +index 365ae96987..a6e7b27ac9 100644 +--- a/src/DS/sds.c ++++ b/src/DS/sds.c +@@ -451,7 +451,12 @@ static int ds_sds_dump_component_by_href(struct ds_sds_session *session, char* x + free(local_filepath); + return 0; + } else { +- dW("Can't use local file '%s' instead of '%s'", local_filepath, xlink_href); ++ ds_sds_session_remote_resources_progress(session)(true, ++ "WARNING: Data stream component '%s' points out to the remote '%s'. " \ ++ "The option --local-files '%s' has been provided, but the file '%s' can't be used locally: %s.\n", ++ cref_id, url, local_files, local_filepath, strerror(errno)); ++ free(local_filepath); ++ return -2; + } + free(local_filepath); + } +diff --git a/tests/DS/test_ds_use_local_remote_resources.sh b/tests/DS/test_ds_use_local_remote_resources.sh +index e4a1f0eedd..789dc8326c 100755 +--- a/tests/DS/test_ds_use_local_remote_resources.sh ++++ b/tests/DS/test_ds_use_local_remote_resources.sh +@@ -9,6 +9,7 @@ set -x + . $builddir/tests/test_common.sh + + PROFILE="xccdf_com.example.www_profile_test_remote_res" ++ + result=$(mktemp) + stderr=$(mktemp) + tmpdir1=$(mktemp -d) +@@ -55,4 +56,27 @@ assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass2"]/r + + popd + rm -f "$result" "$stderr" +-rm -rf "$tmpdir" +\ No newline at end of file ++rm -rf "$tmpdir" ++ ++# test that a warning is shown when --local-files is provided but the file doesn't exist ++result=$(mktemp) ++stderr=$(mktemp) ++tmpdir1=$(mktemp -d) ++tmpdir2=$(mktemp -d) ++tmpdir3=$(mktemp -d) ++cp "${srcdir}/ds_use_local_remote_resources/remote_content_1.3.ds.xml" "$tmpdir2" ++pushd "$tmpdir1" ++ ++# $tmpdir3 is empty, it doesn't contain any content ++$OSCAP xccdf eval --local-files "$tmpdir3" --profile "$PROFILE" --results "$result" "$tmpdir2/remote_content_1.3.ds.xml" 2>"$stderr" || ret=$? ++[ "$ret" = 2 ] ++ ++grep -q "WARNING: Data stream component 'scap_org.open-scap_cref_remote.oval.xml' points out to the remote 'https://www.example.com/security/data/oval/remote.oval.xml'. The option --local-files '$tmpdir3' has been provided, but the file '$tmpdir3/remote.oval.xml' can't be used locally: No such file or directory." "$stderr" ++ ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass"]/result[text()="pass"]' ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-remote_res"]/result[text()="notchecked"]' ++assert_exists 1 '//rule-result[@idref="xccdf_com.example.www_rule_test-pass2"]/result[text()="pass"]' ++ ++popd ++rm -f "$result" "$stderr" ++rm -rf "$tmpdir1" "$tmpdir2" "$tmpdir3" + +From 34ebf46a84a9bd4d9727739c514faaf0e41858ac Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 23 Jul 2021 17:07:44 +0200 +Subject: [PATCH 05/10] Update bash completion + +with --local-files +--- + dist/bash_completion.d/oscap | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/dist/bash_completion.d/oscap b/dist/bash_completion.d/oscap +index 030c250ebe..329f0736e3 100644 +--- a/dist/bash_completion.d/oscap ++++ b/dist/bash_completion.d/oscap +@@ -27,14 +27,14 @@ function _oscap { + local -A opts=() + opts[oscap]="--version --quiet --help -V -q -h" + opts[oscap:oval:validate]="--version --definitions --variables --syschar --results --directives --skip-schematron" +- opts[oscap:oval:eval]="--datastream-id --oval-id --id --variables --directives --without-syschar --results --report --skip-valid --skip-validation --fetch-remote-resources --verbose --verbose-log-file" ++ opts[oscap:oval:eval]="--datastream-id --oval-id --id --variables --directives --without-syschar --results --report --skip-valid --skip-validation --fetch-remote-resources --local-files --verbose --verbose-log-file" + opts[oscap:oval:analyse]="--variables --directives --verbose --verbose-log-file --skip-valid --skip-validation" + opts[oscap:oval:collect]="--id --syschar --skip-valid --skip-validation --variables --verbose --verbose-log-file" + opts[oscap:oval:generate:report]="-o --output" +- opts[oscap:xccdf:eval]="--benchmark-id --check-engine-results --cpe --datastream-id --enforce-signature --export-variables --fetch-remote-resources --oval-results --profile --progress --remediate --report --results --results-arf --rule --skip-valid --skip-validation --skip-signature-validation --stig-viewer --tailoring-file --tailoring-id --thin-results --verbose --verbose-log-file --without-syschar --xccdf-id" ++ opts[oscap:xccdf:eval]="--benchmark-id --check-engine-results --cpe --datastream-id --enforce-signature --export-variables --fetch-remote-resources --local-files --oval-results --profile --progress --remediate --report --results --results-arf --rule --skip-valid --skip-validation --skip-signature-validation --stig-viewer --tailoring-file --tailoring-id --thin-results --verbose --verbose-log-file --without-syschar --xccdf-id" + opts[oscap:xccdf:validate]="--skip-schematron" +- opts[oscap:xccdf:export-oval-variables]="--datastream-id --xccdf-id --profile --skip-valid --skip-validation --fetch-remote-resources --cpe" +- opts[oscap:xccdf:remediate]="--result-id --skip-valid --skip-validation --fetch-remote-resources --results --results-arf --report --oval-results --export-variables --cpe --check-engine-results --progress" ++ opts[oscap:xccdf:export-oval-variables]="--datastream-id --xccdf-id --profile --skip-valid --skip-validation --fetch-remote-resources --local-files --cpe" ++ opts[oscap:xccdf:remediate]="--result-id --skip-valid --skip-validation --fetch-remote-resources --local-files --results --results-arf --report --oval-results --export-variables --cpe --check-engine-results --progress" + opts[oscap:xccdf:resolve]="-o --output -f --force" + opts[oscap:xccdf:generate]="--profile" + opts[oscap:xccdf:generate:report]="-o --output --result-id --profile --oval-template --sce-template" +@@ -43,12 +43,12 @@ function _oscap { + opts[oscap:xccdf:generate:custom]="-o --output --stylesheet" + opts[oscap:ds:sds-add]="--datastream-id --skip-valid --skip-validation" + opts[oscap:ds:sds-compose]="--skip-valid --skip-validation" +- opts[oscap:ds:sds-split]="--datastream-id --xccdf-id --skip-valid --skip-validation --fetch-remote-resources" ++ opts[oscap:ds:sds-split]="--datastream-id --xccdf-id --skip-valid --skip-validation --fetch-remote-resources --local-files" + opts[oscap:ds:rds-create]="--skip-valid --skip-validation" + opts[oscap:ds:rds-split]="--report-id --skip-valid --skip-validation" + opts[oscap:cvss:score]="" + opts[oscap:cvss:describe]="" +- opts[oscap:info]="--fetch-remote-resources --profile --profiles" ++ opts[oscap:info]="--fetch-remote-resources --local-files --profile --profiles" + + # local variables + local std cmd i prev + +From 66ae271966405596b156bca13e6ca41dc118564b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 23 Jul 2021 17:08:17 +0200 +Subject: [PATCH 06/10] Support local remote files in wrappers + +This adds --local-files to oscap-chroot, oscap-ssh and oscap-vm. + +In oscap-ssh we need to copy the files to the remote machine, which +is a similar thing that we do with tailoring and other files. +--- + utils/oscap-chroot | 1 + + utils/oscap-chroot.8 | 1 + + utils/oscap-ssh | 17 +++++++++++++++++ + utils/oscap-ssh.8 | 1 + + utils/oscap-vm | 1 + + utils/oscap-vm.8 | 1 + + 6 files changed, 22 insertions(+) + +diff --git a/utils/oscap-chroot b/utils/oscap-chroot +index c1e35aa652..57ec66c428 100755 +--- a/utils/oscap-chroot ++++ b/utils/oscap-chroot +@@ -53,6 +53,7 @@ function usage() + echo " --skip-valid" + echo " --skip-validation" + echo " --fetch-remote-resources" ++ echo " --local-files" + echo " --progress" + echo " --datastream-id" + echo " --xccdf-id" +diff --git a/utils/oscap-chroot.8 b/utils/oscap-chroot.8 +index d0c75c68e3..e02dd8dbea 100644 +--- a/utils/oscap-chroot.8 ++++ b/utils/oscap-chroot.8 +@@ -23,6 +23,7 @@ supported oscap xccdf eval options are: + --skip-valid + --skip-validation + --fetch-remote-resources ++ --local-files + --progress + --datastream-id + --xccdf-id +diff --git a/utils/oscap-ssh b/utils/oscap-ssh +index f428f99a8e..120d4ae8c6 100755 +--- a/utils/oscap-ssh ++++ b/utils/oscap-ssh +@@ -54,6 +54,7 @@ function usage() + echo " --skip-valid" + echo " --skip-validation" + echo " --fetch-remote-resources" ++ echo " --local-files" + echo " --progress" + echo " --datastream-id" + echo " --xccdf-id" +@@ -110,6 +111,12 @@ function scp_copy_to_temp_dir { + scp -o ControlPath="$MASTER_SOCKET" -P "$SSH_PORT" $SSH_ADDITIONAL_OPTIONS "$1" "$SSH_HOST:$REMOTE_TEMP_DIR/$2" + } + ++# $1: Local directory name to copy ++# $2: Remote destination ++function scp_copy_dir_to_temp_dir { ++ scp -r -o ControlPath="$MASTER_SOCKET" -P "$SSH_PORT" $SSH_ADDITIONAL_OPTIONS "$1" "$SSH_HOST:$REMOTE_TEMP_DIR/$2" ++} ++ + # $1: Remote filename to get + # $2: Local destination + function scp_retreive_from_temp_dir { +@@ -196,6 +203,7 @@ oscap_args=("$@") + + LOCAL_CONTENT_PATH="" + LOCAL_TAILORING_PATH="" ++LOCAL_LOCAL_FILES_PATH="" + LOCAL_CPE_PATH="" + LOCAL_VARIABLES_PATH="" + LOCAL_DIRECTIVES_PATH="" +@@ -214,6 +222,10 @@ for i in $(seq 0 `expr $# - 1`); do + LOCAL_TAILORING_PATH=${oscap_args[j]} + oscap_args[j]="$REMOTE_TEMP_DIR/tailoring.xml" + ;; ++ ("--local-files") ++ LOCAL_LOCAL_FILES_PATH=${oscap_args[j]} ++ oscap_args[j]="$REMOTE_TEMP_DIR/local_files" ++ ;; + ("--cpe") + LOCAL_CPE_PATH=${oscap_args[j]} + oscap_args[j]="$REMOTE_TEMP_DIR/cpe.xml" +@@ -258,6 +270,7 @@ fi + + [ "$LOCAL_CONTENT_PATH" == "" ] || [ -f "$LOCAL_CONTENT_PATH" ] || die "Expected the last argument to be an input file, '$LOCAL_CONTENT_PATH' isn't a valid file path or the file doesn't exist!" + [ "$LOCAL_TAILORING_PATH" == "" ] || [ -f "$LOCAL_TAILORING_PATH" ] || die "Tailoring file path '$LOCAL_TAILORING_PATH' isn't a valid file path or the file doesn't exist!" ++[ "$LOCAL_LOCAL_FILES_PATH" == "" ] || [ -d "$LOCAL_LOCAL_FILES_PATH" ] || die "Directory '$LOCAL_LOCAL_FILES_PATH' isn't a valid directory path or the directory doesn't exist!" + [ "$LOCAL_CPE_PATH" == "" ] || [ -f "$LOCAL_CPE_PATH" ] || die "CPE file path '$LOCAL_CPE_PATH' isn't a valid file path or the file doesn't exist!" + [ "$LOCAL_VARIABLES_PATH" == "" ] || [ -f "$LOCAL_VARIABLES_PATH" ] || die "OVAL variables file path '$LOCAL_VARIABLES_PATH' isn't a valid file path or the file doesn't exist!" + [ "$LOCAL_DIRECTIVES_PATH" == "" ] || [ -f "$LOCAL_DIRECTIVES_PATH" ] || die "OVAL directives file path '$LOCAL_DIRECTIVES_PATH' isn't a valid file path or the file doesn't exist!" +@@ -270,6 +283,10 @@ if [ "$LOCAL_TAILORING_PATH" != "" ]; then + echo "Copying tailoring file '$LOCAL_TAILORING_PATH' to remote working directory '$REMOTE_TEMP_DIR'..." + scp_copy_to_temp_dir "$LOCAL_TAILORING_PATH" tailoring.xml || die "Failed to copy tailoring file to remote temporary directory!" + fi ++if [ "$LOCAL_LOCAL_FILES_PATH" != "" ]; then ++ echo "Copying directory '$LOCAL_LOCAL_FILES_PATH' to remote working directory '$REMOTE_TEMP_DIR'..." ++ scp_copy_dir_to_temp_dir "$LOCAL_LOCAL_FILES_PATH" local_files || die "Failed to copy directory $LOCAL_LOCAL_FILES_PATH to remote temporary directory!" ++fi + if [ "$LOCAL_CPE_PATH" != "" ]; then + echo "Copying CPE file '$LOCAL_CPE_PATH' to remote working directory '$REMOTE_TEMP_DIR'..." + scp_copy_to_temp_dir "$LOCAL_CPE_PATH" cpe.xml || die "Failed to copy CPE file to remote temporary directory!" +diff --git a/utils/oscap-ssh.8 b/utils/oscap-ssh.8 +index f64855829a..416b1f3e58 100644 +--- a/utils/oscap-ssh.8 ++++ b/utils/oscap-ssh.8 +@@ -29,6 +29,7 @@ Supported options are: + --skip-valid + --skip-validation + --fetch-remote-resources ++ --local-files + --progress + --datastream-id + --xccdf-id +diff --git a/utils/oscap-vm b/utils/oscap-vm +index 1a9d6b3bf6..e7ae6e2600 100755 +--- a/utils/oscap-vm ++++ b/utils/oscap-vm +@@ -51,6 +51,7 @@ function usage() + echo " --skip-valid" + echo " --skip-validation" + echo " --fetch-remote-resources" ++ echo " --local-files" + echo " --progress" + echo " --datastream-id" + echo " --xccdf-id" +diff --git a/utils/oscap-vm.8 b/utils/oscap-vm.8 +index a335725e5e..b7276c1128 100644 +--- a/utils/oscap-vm.8 ++++ b/utils/oscap-vm.8 +@@ -65,6 +65,7 @@ Supported oscap xccdf eval options are: + \-\-skip-valid + \-\-skip-validation + \-\-fetch-remote-resources ++ \-\-local-files + \-\-progress + \-\-datastream-id <id> + \-\-xccdf-id <id> + +From cac289e1d15cd8f3bde64b94563ed1dcf34652bb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 23 Jul 2021 17:18:55 +0200 +Subject: [PATCH 07/10] Fix the value to use a correct type + +--- + src/DS/ds_sds_session.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/DS/ds_sds_session.c b/src/DS/ds_sds_session.c +index 99f4bc20fd..3a720e8131 100644 +--- a/src/DS/ds_sds_session.c ++++ b/src/DS/ds_sds_session.c +@@ -347,7 +347,7 @@ void ds_sds_session_configure_remote_resources(struct ds_sds_session *session, b + + void ds_sds_session_set_remote_resources(struct ds_sds_session *session, bool allowed, download_progress_calllback_t callback) + { +- ds_sds_session_configure_remote_resources(session, allowed, false, callback); ++ ds_sds_session_configure_remote_resources(session, allowed, NULL, callback); + } + + const char *ds_sds_session_local_files(struct ds_sds_session *session) + +From e4e6732ffd23b408c0bf08eea91c406434bd4ec6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 23 Jul 2021 17:36:36 +0200 +Subject: [PATCH 08/10] Refactor: Extract function _handle_disabled_downloads + +--- + src/DS/sds.c | 122 ++++++++++++++++++++++++++------------------------- + 1 file changed, 63 insertions(+), 59 deletions(-) + +diff --git a/src/DS/sds.c b/src/DS/sds.c +index a6e7b27ac9..530e3ad9fa 100644 +--- a/src/DS/sds.c ++++ b/src/DS/sds.c +@@ -371,6 +371,66 @@ static char *compose_target_filename_dirname(const char *relative_filepath, cons + return target_filename_dirname; + } + ++static int _handle_disabled_downloads(struct ds_sds_session *session, const char *relative_filepath, const char *xlink_href, const char *component_id, const char *target_filename_dirname, const char *cref_id, const char *url) ++{ ++ /* ++ * If fetching remote resources isn't allowed by the user let's take a look ++ * whether there exists a file whose file name is equal to @name attribute ++ * of the uri element within the catalog of the previously processed ++ * component-ref which pointed us to the currently processed component-ref. ++ * Note that the @name attribute value has been passed as relative_filepath ++ * in the recursive call of ds_sds_dump_component_ref_as. If such file ++ * exists, we will assume that it's a local copy of the remote component ++ * located at the URL defined in @xlink:href. This way people can provide ++ * the previously downloaded component which might be useful on systems with ++ * limited internet access. This behavior is allowed only when --local-files ++ * is used on the command line. ++ * See: https://bugzilla.redhat.com/show_bug.cgi?id=1970527 ++ * See: https://access.redhat.com/solutions/5185891 ++ */ ++ const char *local_files = ds_sds_session_local_files(session); ++ if (local_files == NULL) { ++ static bool fetch_remote_resources_suggested = false; ++ if (!fetch_remote_resources_suggested) { ++ fetch_remote_resources_suggested = true; ++ ds_sds_session_remote_resources_progress(session)(true, ++ "WARNING: Datastream component '%s' points out to the remote '%s'. Use '--fetch-remote-resources' option to download it.\n", ++ cref_id, url); ++ } ++ ++ ds_sds_session_remote_resources_progress(session)(true, ++ "WARNING: Skipping '%s' file which is referenced from datastream\n", ++ url); ++ // -2 means that remote resources were not downloaded ++ return -2; ++ } ++ char *local_filepath = oscap_path_join(local_files, relative_filepath); ++ struct stat sb; ++ if (stat(local_filepath, &sb) == 0) { ++ dI("Using local file '%s' instead of '%s'", local_filepath, xlink_href); ++ struct oscap_source *source_file = oscap_source_new_from_file(local_filepath); ++ xmlDoc *doc = oscap_source_get_xmlDoc(source_file); ++ if (doc == NULL) { ++ free(local_filepath); ++ return -1; ++ } ++ xmlNodePtr inner_root = ds_sds_get_component_root_by_id(doc, component_id); ++ ++ if (ds_sds_register_component(session, doc, inner_root, component_id, target_filename_dirname, relative_filepath) != 0) { ++ free(local_filepath); ++ return -1; ++ } ++ free(local_filepath); ++ return 0; ++ } ++ ds_sds_session_remote_resources_progress(session)(true, ++ "WARNING: Data stream component '%s' points out to the remote '%s'. " \ ++ "The option --local-files '%s' has been provided, but the file '%s' can't be used locally: %s.\n", ++ cref_id, url, local_files, local_filepath, strerror(errno)); ++ free(local_filepath); ++ return -2; ++} ++ + static int ds_sds_dump_component_by_href(struct ds_sds_session *session, char* xlink_href, char *target_filename_dirname, const char* relative_filepath, char* cref_id, char **component_id) + { + if (!xlink_href || strlen(xlink_href) < 2) +@@ -413,65 +473,9 @@ static int ds_sds_dump_component_by_href(struct ds_sds_session *session, char* x + } + + if (!ds_sds_session_fetch_remote_resources(session)) { +- /* +- * If fetching remote resources isn't allowed by the user let's take +- * a look whether there exists a file whose file name is equal to +- * @name attribute of the uri element within the catalog of the +- * previously processed component-ref which pointed us to the +- * currently processed component-ref. Note that the @name attribute +- * value has been passed as relative_filepath in the recursive call +- * of ds_sds_dump_component_ref_as. If such file exists, we will +- * assume that it's a local copy of the remote component located at +- * the URL defined in @xlink:href. This way people can provide the +- * previously downloaded component which might be useful on systems +- * with limited internet access. This behavior is allowed only when +- * --local-files is used on the command line. +- * See: https://bugzilla.redhat.com/show_bug.cgi?id=1970527 +- * See: https://access.redhat.com/solutions/5185891 +- */ +- const char *local_files = ds_sds_session_local_files(session); +- if (local_files != NULL) { +- char *local_filepath = oscap_path_join(local_files, relative_filepath); +- struct stat sb; +- if (stat(local_filepath, &sb) == 0) { +- //if (ds_sds_session_can_use_local_file(session)) { +- dI("Using local file '%s' instead of '%s'", local_filepath, xlink_href); +- struct oscap_source *source_file = oscap_source_new_from_file(local_filepath); +- xmlDoc *doc = oscap_source_get_xmlDoc(source_file); +- if (doc == NULL) { +- free(local_filepath); +- return -1; +- } +- xmlNodePtr inner_root = ds_sds_get_component_root_by_id(doc, *component_id); +- +- if (ds_sds_register_component(session, doc, inner_root, *component_id, target_filename_dirname, relative_filepath) != 0) { +- free(local_filepath); +- return -1; +- } +- free(local_filepath); +- return 0; +- } else { +- ds_sds_session_remote_resources_progress(session)(true, +- "WARNING: Data stream component '%s' points out to the remote '%s'. " \ +- "The option --local-files '%s' has been provided, but the file '%s' can't be used locally: %s.\n", +- cref_id, url, local_files, local_filepath, strerror(errno)); +- free(local_filepath); +- return -2; +- } +- free(local_filepath); +- } +- +- static bool fetch_remote_resources_suggested = false; +- +- if (!fetch_remote_resources_suggested) { +- fetch_remote_resources_suggested = true; +- ds_sds_session_remote_resources_progress(session)(true, "WARNING: Datastream component '%s' points out to the remote '%s'. " +- "Use '--fetch-remote-resources' option to download it.\n", cref_id, url); +- } +- +- ds_sds_session_remote_resources_progress(session)(true, "WARNING: Skipping '%s' file which is referenced from datastream\n", url); +- // -2 means that remote resources were not downloaded +- return -2; ++ return _handle_disabled_downloads( ++ session, relative_filepath, xlink_href, *component_id, ++ target_filename_dirname, cref_id, url); + } + + return ds_dsd_dump_remote_component(url, *component_id, session, target_filename_dirname, relative_filepath); + +From 9cb39894484e6bdccc235e4138af6447ffc5ee4b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Mon, 26 Jul 2021 16:03:18 +0200 +Subject: [PATCH 09/10] Fix indentation and typos + +--- + utils/oscap-ds.c | 2 +- + utils/oscap-info.c | 2 +- + utils/oscap-oval.c | 2 +- + utils/oscap-xccdf.c | 6 +++--- + 4 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/utils/oscap-ds.c b/utils/oscap-ds.c +index 8207f097ec..5f9863414b 100644 +--- a/utils/oscap-ds.c ++++ b/utils/oscap-ds.c +@@ -83,7 +83,7 @@ static struct oscap_module DS_SDS_SPLIT_MODULE = { + " --skip-valid - Skips validating of given XCCDF.\n" + " --skip-validation\n" + " --fetch-remote-resources - Download remote content referenced by data stream.\n" +- " --use-local-file - Use a locally downloaded copy of the remote resource if it exists.\n", ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n", + .opt_parser = getopt_ds, + .func = app_ds_sds_split + }; +diff --git a/utils/oscap-info.c b/utils/oscap-info.c +index 2e6f5003aa..721596a528 100644 +--- a/utils/oscap-info.c ++++ b/utils/oscap-info.c +@@ -63,7 +63,7 @@ struct oscap_module OSCAP_INFO_MODULE = { + .usage = "some-file.xml", + .help = "Options:\n" + " --fetch-remote-resources - Download remote content referenced by data stream.\n" +- " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --profile <id> - Show info of the profile with the given ID.\n" + " --profiles - Show profiles from the input file in the <id>:<title> format, one line per profile.\n", + .opt_parser = getopt_info, +diff --git a/utils/oscap-oval.c b/utils/oscap-oval.c +index 3a206bb3c4..da1b1aad86 100644 +--- a/utils/oscap-oval.c ++++ b/utils/oscap-oval.c +@@ -116,7 +116,7 @@ static struct oscap_module OVAL_EVAL = { + " (only applicable for source data streams)\n" + " --fetch-remote-resources - Download remote content referenced by OVAL Definitions.\n" + " (only applicable for source data streams)\n" +- " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n", ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n", + .opt_parser = getopt_oval_eval, + .func = app_evaluate_oval + }; +diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c +index 54b8df5467..a7870c4281 100644 +--- a/utils/oscap-xccdf.c ++++ b/utils/oscap-xccdf.c +@@ -129,7 +129,7 @@ static struct oscap_module XCCDF_EXPORT_OVAL_VARIABLES = { + " --skip-valid - Skip validation.\n" + " --skip-validation\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" +- " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --datastream-id <id> - ID of the data stream in the collection to use.\n" + " (only applicable for source data streams)\n" + " --xccdf-id <id> - ID of component-ref with XCCDF in the data stream that should be evaluated.\n" +@@ -172,7 +172,7 @@ static struct oscap_module XCCDF_EVAL = { + " (only applicable for source data streams)\n" + " --enforce-signature - Process only signed data streams.\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" +- " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --progress - Switch to sparse output suitable for progress reporting.\n" + " Format is \"$rule_id:$result\\n\".\n" + " --datastream-id <id> - ID of the data stream in the collection to use.\n" +@@ -201,7 +201,7 @@ static struct oscap_module XCCDF_REMEDIATE = { + " --cpe <name> - Use given CPE dictionary or language (autodetected)\n" + " for applicability checks.\n" + " --fetch-remote-resources - Download remote content referenced by XCCDF.\n" +- " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" ++ " --local-files <dir> - Use locally downloaded copies of remote resources stored in the given directory.\n" + " --results <file> - Write XCCDF Results into file.\n" + " --results-arf <file> - Write ARF (result data stream) into file.\n" + " --stig-viewer <file> - Writes XCCDF results into FILE in a format readable by DISA STIG Viewer\n" + +From 7187e2a1e49927884d80702c2e3e6a796996277b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Mon, 26 Jul 2021 16:19:49 +0200 +Subject: [PATCH 10/10] Update description in man page + +--- + utils/oscap.8 | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/utils/oscap.8 b/utils/oscap.8 +index 4f5f6259e3..dcfa3d0c2f 100644 +--- a/utils/oscap.8 ++++ b/utils/oscap.8 +@@ -320,7 +320,7 @@ Allow download of remote OVAL content referenced from XCCDF by check-content-ref + .TP + \fB\-\-local-files DIRECTORY\fR + .RS +-Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + \fB\-\-skip-valid\fR, \fB\-\-skip-validation\fR +@@ -499,7 +499,7 @@ Do not validate input/output files. + Allow download of remote components referenced from data stream. + .TP + \fB\-\-local-files DIRECTORY\fR +-Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + + .TP +@@ -663,7 +663,7 @@ Do not validate input/output files. + Allow download of remote components referenced from data stream. + .TP + \fB\-\-local-files DIRECTORY\fR +-Instead of downloading remote data stream components from the network, use a data stream component stored locally in a file. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. ++Instead of downloading remote data stream components from the network, use data stream components stored locally as files in the given directory. In place of the remote data stream component OpenSCAP will attempt to use a file whose file name is equal to @name attribute of the uri element within the catalog element within the component-ref element in the data stream if such file exists. + .RE + .TP + .B \fBsds-validate\fR SOURCE_DS diff --git a/SOURCES/openscap-1.3.6-PR-1772-UBI9.patch b/SOURCES/openscap-1.3.6-PR-1772-UBI9.patch new file mode 100644 index 0000000..85311ce --- /dev/null +++ b/SOURCES/openscap-1.3.6-PR-1772-UBI9.patch @@ -0,0 +1,38 @@ +From 80543bc666d648d0251e4c7b675489b8011a548a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 25 Jun 2021 10:19:43 +0200 +Subject: [PATCH] Fix UBI 9 scan + +In offline mode when scanning a cointainer based on UBI 9 the +system_info probe failed because the function `_offline_get_hname` which +reads from `/etc/hostname` returns an empty string which causes +`__sysinfo_saneval(hname)` check to return zero which in turn causes the +probe returns an error. We can prevent this situation by replacing the +empty string by `"Unknown"`, which we already do when the `hname` is +`NULL`. + +Addressing: + +W: oscap: Can't receive message: 125, Operation canceled. +E: oscap: Recv: retry limit (0) reached. +OpenSCAP Error: Probe at sd=32 (system_info) reported an error: Invalid type, value or format [/home/jcerny/work/git/openscap/src/OVAL/oval_probe_ext.c:383] +Unable to receive a message from probe [/home/jcerny/work/git/openscap/src/OVAL/oval_probe_ext.c:572] + +Resolves: rhbz#1953610 +--- + src/OVAL/probes/independent/system_info_probe.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/OVAL/probes/independent/system_info_probe.c b/src/OVAL/probes/independent/system_info_probe.c +index 9bdd73556d..8251e655ed 100644 +--- a/src/OVAL/probes/independent/system_info_probe.c ++++ b/src/OVAL/probes/independent/system_info_probe.c +@@ -732,7 +732,7 @@ int system_info_probe_main(probe_ctx *ctx, void *arg) + if (!architecture) + architecture = strdup(unknown); + +- if (!hname) ++ if (!hname || *hname == '\0') + hname = strdup(unknown); + + if (__sysinfo_saneval(os_name) < 1 || diff --git a/SOURCES/openscap-1.3.6-PR-1786-document-local-files-in-oscap-ssh-man-page.patch b/SOURCES/openscap-1.3.6-PR-1786-document-local-files-in-oscap-ssh-man-page.patch new file mode 100644 index 0000000..a405a67 --- /dev/null +++ b/SOURCES/openscap-1.3.6-PR-1786-document-local-files-in-oscap-ssh-man-page.patch @@ -0,0 +1,25 @@ +From 9f9a322b73e71bb4945a736605eb0515acf9a207 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Wed, 28 Jul 2021 08:36:50 +0200 +Subject: [PATCH] Document problems with --local-files in oscap-ssh + +Related to: https://github.com/OpenSCAP/openscap/pull/1769 +Thanks @ggbecker for pointing this out. +--- + utils/oscap-ssh.8 | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/utils/oscap-ssh.8 b/utils/oscap-ssh.8 +index 416b1f3e5..05c80cd3c 100644 +--- a/utils/oscap-ssh.8 ++++ b/utils/oscap-ssh.8 +@@ -67,6 +67,9 @@ Specific option for oscap-ssh (must be first argument): + oscap-ssh checks out the SSH_ADDITIONAL_OPTIONS environment variable, and pastes its contents into the command-line of ssh to the location where options are expected. + Supply the variable in form of a string that corresponds to a section of the ssh command-line and that consists of options you want to pass. + ++.SS Using --local-files option ++The oscap-ssh command supports the --local-files option, but it isn't possible to pass './' and '../' as an argument. Use a full directory path instead. ++ + .SH EXAMPLE USAGE + .SS Simple XCCDF evaluation + The following command evaluates a remote Fedora machine as root. HTML report is written out as report.html on the local machine. Can be executed from any machine that has ssh, scp and bash. The local machine does not need to have openscap installed. diff --git a/SOURCES/openscap-1.3.6-PR-1806-alternative-hostname.patch b/SOURCES/openscap-1.3.6-PR-1806-alternative-hostname.patch new file mode 100644 index 0000000..1d6b269 --- /dev/null +++ b/SOURCES/openscap-1.3.6-PR-1806-alternative-hostname.patch @@ -0,0 +1,72 @@ +From d97687c12ba6cbca1d732534ff7394bd14547d92 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com> +Date: Fri, 10 Sep 2021 14:53:42 +0200 +Subject: [PATCH] Add an alternative source of hostname + +If /etc/hostname can't be read, we will try to open /proc/sys/kernel/hostname instead. + +Resolves: rhbz#1977668 +--- + src/XCCDF/result.c | 5 ++++ + tests/API/XCCDF/unittests/CMakeLists.txt | 1 + + .../XCCDF/unittests/test_results_hostname.sh | 26 +++++++++++++++++++ + 3 files changed, 32 insertions(+) + create mode 100755 tests/API/XCCDF/unittests/test_results_hostname.sh + +diff --git a/src/XCCDF/result.c b/src/XCCDF/result.c +index 91fcc6041d..c0ad4b926f 100644 +--- a/src/XCCDF/result.c ++++ b/src/XCCDF/result.c +@@ -271,6 +271,11 @@ static char *_get_etc_hostname(const char *oscap_probe_root) + + fp = oscap_fopen_with_prefix(oscap_probe_root, "/etc/hostname"); + ++ if (fp == NULL) { ++ dD("Trying to use /proc/sys/kernel/hostname instead of /etc/hostname"); ++ fp = oscap_fopen_with_prefix(oscap_probe_root, "/proc/sys/kernel/hostname"); ++ } ++ + if (fp == NULL) + goto fail; + +diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt +index 52645834c4..6549538440 100644 +--- a/tests/API/XCCDF/unittests/CMakeLists.txt ++++ b/tests/API/XCCDF/unittests/CMakeLists.txt +@@ -101,3 +101,4 @@ add_oscap_test("test_fix_arf.sh") + add_oscap_test("test_fix_resultid_by_suffix.sh") + add_oscap_test("test_generate_fix_ansible_vars.sh") + add_oscap_test("test_xccdf_requires_conflicts.sh") ++add_oscap_test("test_results_hostname.sh") +diff --git a/tests/API/XCCDF/unittests/test_results_hostname.sh b/tests/API/XCCDF/unittests/test_results_hostname.sh +new file mode 100755 +index 0000000000..c4408affbb +--- /dev/null ++++ b/tests/API/XCCDF/unittests/test_results_hostname.sh +@@ -0,0 +1,26 @@ ++#!/usr/bin/env bash ++. $builddir/tests/test_common.sh ++ ++set -e ++set -o pipefail ++ ++result=$(mktemp) ++tmpdir=$(mktemp -d) ++ ++export OSCAP_PROBE_ROOT="$tmpdir" ++ ++mkdir -p "$tmpdir/etc" ++echo "hostname_defined_in_etc_hostname" > "$tmpdir/etc/hostname" ++$OSCAP xccdf eval --results "$result" "$srcdir/test_single_rule.ds.xml" || ret=$? ++assert_exists 1 '/Benchmark/TestResult/target[text()="hostname_defined_in_etc_hostname"]' ++assert_exists 0 '/Benchmark/TestResult/target[text()="hostname_defined_in_proc_sys_kernel"]' ++ ++rm -rf "$tmpdir/etc/hostname" ++mkdir -p "$tmpdir/proc/sys/kernel/" ++echo "hostname_defined_in_proc_sys_kernel" > "$tmpdir/proc/sys/kernel/hostname" ++$OSCAP xccdf eval --results "$result" "$srcdir/test_single_rule.ds.xml" || ret=$? ++assert_exists 0 '/Benchmark/TestResult/target[text()="hostname_defined_in_etc_hostname"]' ++assert_exists 1 '/Benchmark/TestResult/target[text()="hostname_defined_in_proc_sys_kernel"]' ++ ++rm -f "$result" ++rm -rf "$tmpdir" diff --git a/SOURCES/openscap-1.3.6-PR-1810-blueprint-fix-extra.patch b/SOURCES/openscap-1.3.6-PR-1810-blueprint-fix-extra.patch new file mode 100644 index 0000000..ec31a3d --- /dev/null +++ b/SOURCES/openscap-1.3.6-PR-1810-blueprint-fix-extra.patch @@ -0,0 +1,583 @@ +From b0b7626dca08acd4563ae42c1c27ccc0777b5357 Mon Sep 17 00:00:00 2001 +From: Evgeny Kolesnikov <ekolesni@redhat.com> +Date: Thu, 23 Sep 2021 00:58:29 +0200 +Subject: [PATCH] Add proper Blueprint's remediation snippets handling for + generation of the final TOML document. + +As the final Blueprint could not be created by just gluing up all +the snippets together we have to get a bit more creative. +--- + docs/manual/manual.adoc | 15 ++ + src/XCCDF_POLICY/xccdf_policy_remediate.c | 216 ++++++++++++++++-- + src/common/list.c | 19 ++ + src/common/list.h | 1 + + tests/API/XCCDF/unittests/CMakeLists.txt | 1 + + .../unittests/test_remediation_blueprint.sh | 27 +++ + .../unittests/test_remediation_blueprint.toml | 45 ++++ + .../test_remediation_blueprint.xccdf.xml | 102 +++++++++ + 8 files changed, 405 insertions(+), 21 deletions(-) + create mode 100755 tests/API/XCCDF/unittests/test_remediation_blueprint.sh + create mode 100644 tests/API/XCCDF/unittests/test_remediation_blueprint.toml + create mode 100644 tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml + +diff --git a/docs/manual/manual.adoc b/docs/manual/manual.adoc +index e8664eb920..90e2cc2c63 100644 +--- a/docs/manual/manual.adoc ++++ b/docs/manual/manual.adoc +@@ -1084,6 +1084,21 @@ scanned during this command. If you want to generate remediation only for the + failed rules based on scan results, refer to <<_reviewing_remediations,Reviewing + remediations>>. + ++=== Generating Image Builder Blueprints ++ ++OpenSCAP can also create a remediation in form of Image Builder (OSBuild) Blueprint. This remeditaion ++is intendeded to be used as a bootstrap for image creation and usually it will contain only essential ++elements of the configuration, elements that would be hard or impossible to change after the image ++is created, like partitioning or set of installed packages. ++ ++It is recommended to combine this type of remediation with other types, executed on the running system. ++ ++For example, to generate a blueprint remediation for RHEL 8 OSPP profile, run: ++ ++---- ++$ oscap xccdf generate fix --profile ospp --fix-type blueprint /usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml > blueprint.toml ++---- ++ + == Details on SCAP conformance + + === Check Engines +diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c +index 0b3a037a5f..6033c3b54b 100644 +--- a/src/XCCDF_POLICY/xccdf_policy_remediate.c ++++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c +@@ -656,6 +656,78 @@ static int _write_fix_missing_warning_to_fd(const char *sys, int output_fd, stru + } + } + ++struct blueprint_entries { ++ const char *pattern; ++ struct oscap_list *list; ++ pcre *re; ++}; ++ ++static inline int _parse_blueprint_fix(const char *fix_text, struct oscap_list *generic, struct oscap_list *services_enable, struct oscap_list *services_disable, struct oscap_list *kernel_append) ++{ ++ const char *err; ++ int errofs; ++ int ret = 0; ++ ++ struct blueprint_entries tab[] = { ++ {"\\[customizations\\.services\\]\\s+enabled[=\\s]+\\[([^\\]]+)\\]\\s+", services_enable, NULL}, ++ {"\\[customizations\\.services\\]\\s+disabled[=\\s]+\\[([^\\]]+)\\]\\s+", services_disable, NULL}, ++ {"\\[customizations\\.kernel\\]\\s+append[=\\s\"]+([^\"]+)[\\s\"]+", kernel_append, NULL}, ++ // We do this only to pop the 'distro' entry to the top of the generic list, ++ // effectively placing it to the root of the TOML document. ++ {"\\s+(distro[=\\s\"]+[^\"]+[\\s\"]+)", generic, NULL}, ++ {NULL, NULL, NULL} ++ }; ++ ++ for (int i = 0; tab[i].pattern != NULL; i++) { ++ tab[i].re = pcre_compile(tab[i].pattern, PCRE_UTF8, &err, &errofs, NULL); ++ if (tab[i].re == NULL) { ++ dE("Unable to compile /%s/ regex pattern, pcre_compile() returned error (offset: %d): '%s'.\n", tab[i].pattern, errofs, err); ++ ret = 1; ++ goto exit; ++ } ++ } ++ ++ const size_t fix_text_len = strlen(fix_text); ++ size_t start_offset = 0; ++ int ovector[6] = {0}; ++ ++ for (int i = 0; tab[i].pattern != NULL; i++) { ++ while (true) { ++ const int match = pcre_exec(tab[i].re, NULL, fix_text, fix_text_len, start_offset, ++ 0, ovector, sizeof(ovector) / sizeof(ovector[0])); ++ if (match == -1) ++ break; ++ ++ if (match != 2) { ++ dE("Expected 1 capture group matches per entry. Found %i!", match - 1); ++ ret = 1; ++ goto exit; ++ } ++ ++ char *val = malloc((ovector[3] - ovector[2] + 1) * sizeof(char)); ++ memcpy(val, &fix_text[ovector[2]], ovector[3] - ovector[2]); ++ val[ovector[3] - ovector[2]] = '\0'; ++ ++ if (!oscap_list_contains(kernel_append, val, (oscap_cmp_func) oscap_streq)) { ++ oscap_list_prepend(tab[i].list, val); ++ } else { ++ free(val); ++ } ++ ++ start_offset = ovector[1]; ++ } ++ } ++ ++ if (start_offset < fix_text_len-1) { ++ oscap_list_add(generic, strdup(fix_text + start_offset)); ++ } ++ ++exit: ++ for (int i = 0; tab[i].pattern != NULL; i++) ++ pcre_free(tab[i].re); ++ ++ return ret; ++} + + static inline int _parse_ansible_fix(const char *fix_text, struct oscap_list *variables, struct oscap_list *tasks) + { +@@ -793,6 +865,18 @@ static int _xccdf_policy_rule_generate_fix(struct xccdf_policy *policy, struct x + return ret; + } + ++static int _xccdf_policy_rule_generate_blueprint_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct oscap_list *generic, struct oscap_list *services_enable, struct oscap_list *services_disable, struct oscap_list *kernel_append) ++{ ++ char *fix_text = NULL; ++ int ret = _xccdf_policy_rule_get_fix_text(policy, rule, template, &fix_text); ++ if (fix_text == NULL) { ++ return ret; ++ } ++ ret = _parse_blueprint_fix(fix_text, generic, services_enable, services_disable, kernel_append); ++ free(fix_text); ++ return ret; ++} ++ + static int _xccdf_policy_rule_generate_ansible_fix(struct xccdf_policy *policy, struct xccdf_rule *rule, const char *template, struct oscap_list *variables, struct oscap_list *tasks) + { + char *fix_text = NULL; +@@ -914,25 +998,45 @@ static char *_comment_multiline_text(char *text) + static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_result *result, const char *sys, int output_fd) + { + if (!(oscap_streq(sys, "") || oscap_streq(sys, "urn:xccdf:fix:script:sh") || oscap_streq(sys, "urn:xccdf:fix:commands") || +- oscap_streq(sys, "urn:xccdf:fix:script:ansible"))) ++ oscap_streq(sys, "urn:xccdf:fix:script:ansible") || oscap_streq(sys, "urn:redhat:osbuild:blueprint"))) + return 0; // no header required + +- const bool ansible_script = strcmp(sys, "urn:xccdf:fix:script:ansible") == 0; +- const char *how_to_apply = ansible_script ? +- "# $ ansible-playbook -i \"localhost,\" -c local playbook.yml\n" +- "# $ ansible-playbook -i \"192.168.1.155,\" playbook.yml\n" +- "# $ ansible-playbook -i inventory.ini playbook.yml" : +- "# $ sudo ./remediation-script.sh"; + const char *oscap_version = oscap_get_version(); +- const char *format = ansible_script ? "ansible" : "bash"; +- const char *remediation_type = ansible_script ? "Ansible Playbook" : "Bash Remediation Script"; +- const char *shebang_with_newline = ansible_script ? "" : "#!/usr/bin/env bash\n"; ++ char *how_to_apply = ""; ++ char *format = (char *)sys; ++ char *remediation_type = "Unknown"; ++ char *shebang_with_newline = ""; ++ ++ if (oscap_streq(sys, "urn:xccdf:fix:script:ansible")) { ++ how_to_apply = "# $ ansible-playbook -i \"localhost,\" -c local playbook.yml\n" ++ "# $ ansible-playbook -i \"192.168.1.155,\" playbook.yml\n" ++ "# $ ansible-playbook -i inventory.ini playbook.yml"; ++ format = "ansible"; ++ remediation_type = "Ansible Playbook"; ++ } ++ ++ if (oscap_streq(sys, "urn:redhat:osbuild:blueprint")) { ++ how_to_apply = "# composer-cli blueprints push blueprint.toml"; ++ format = "blueprint"; ++ remediation_type = "Blueprint"; ++ } ++ ++ if (oscap_streq(sys, "") || oscap_streq(sys, "urn:xccdf:fix:script:sh") || oscap_streq(sys, "urn:xccdf:fix:commands")) { ++ how_to_apply = "# $ sudo ./remediation-script.sh"; ++ format = "bash"; ++ remediation_type = "Bash Remediation Script"; ++ shebang_with_newline = "#!/usr/bin/env bash\n"; ++ } + + char *fix_header; + + struct xccdf_profile *profile = xccdf_policy_get_profile(policy); + const char *profile_id = xccdf_profile_get_id(profile); + ++ struct xccdf_benchmark *benchmark = xccdf_policy_get_benchmark(policy); ++ const char *benchmark_version_info = benchmark ? xccdf_benchmark_get_version(benchmark) : "Unknown"; ++ const char *benchmark_id = benchmark ? xccdf_benchmark_get_id(benchmark) : "Unknown"; ++ + // Title + struct oscap_text_iterator *title_iterator = xccdf_profile_get_title(profile); + char *raw_profile_title = oscap_textlist_get_preferred_plaintext(title_iterator, NULL); +@@ -942,11 +1046,6 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ + + if (result == NULL) { + // Profile-based remediation fix +- struct xccdf_benchmark *benchmark = xccdf_policy_get_benchmark(policy); +- if (benchmark == NULL) { +- free(profile_title); +- return 1; +- } + // Description + struct oscap_text_iterator *description_iterator = xccdf_profile_get_description(profile); + char *profile_description = description_iterator != NULL ? +@@ -955,10 +1054,8 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ + char *commented_profile_description = _comment_multiline_text(profile_description); + free(profile_description); + +- const char *benchmark_version_info = xccdf_benchmark_get_version(benchmark); +- const char *benchmark_id = xccdf_benchmark_get_id(benchmark); +- const struct xccdf_version_info *xccdf_version = xccdf_benchmark_get_schema_version(benchmark); +- const char *xccdf_version_name = xccdf_version_info_get_version(xccdf_version); ++ const struct xccdf_version_info *xccdf_version = benchmark ? xccdf_benchmark_get_schema_version(benchmark) : NULL; ++ const char *xccdf_version_name = xccdf_version ? xccdf_version_info_get_version(xccdf_version) : "Unknown"; + + fix_header = oscap_sprintf( + "%s" +@@ -1026,9 +1123,8 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ + result_id, format, remediation_type, remediation_type, how_to_apply + ); + } +- free(profile_title); + +- if (ansible_script) { ++ if (oscap_streq(sys, "urn:xccdf:fix:script:ansible")) { + char *ansible_fix_header = oscap_sprintf( + "---\n" + "%s\n" +@@ -1036,9 +1132,85 @@ static int _write_script_header_to_fd(struct xccdf_policy *policy, struct xccdf_ + fix_header); + free(fix_header); + return _write_text_to_fd_and_free(output_fd, ansible_fix_header); ++ } else if (oscap_streq(sys, "urn:redhat:osbuild:blueprint")) { ++ char *blueprint_fix_header = oscap_sprintf( ++ "%s" ++ "name = \"%s\"\n" ++ "description = \"%s\"\n" ++ "version = \"%s\"\n", ++ fix_header, profile_id, profile_title, benchmark_version_info); ++ free(fix_header); ++ return _write_text_to_fd_and_free(output_fd, blueprint_fix_header); + } else { + return _write_text_to_fd_and_free(output_fd, fix_header); + } ++ ++ free(profile_title); ++} ++ ++static int _xccdf_policy_generate_fix_blueprint(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) ++{ ++ int ret = 0; ++ struct oscap_list *generic = oscap_list_new(); ++ struct oscap_list *services_enable = oscap_list_new(); ++ struct oscap_list *services_disable = oscap_list_new(); ++ struct oscap_list *kernel_append = oscap_list_new(); ++ struct oscap_iterator *rules_to_fix_it = oscap_iterator_new(rules_to_fix); ++ while (oscap_iterator_has_more(rules_to_fix_it)) { ++ struct xccdf_rule *rule = (struct xccdf_rule*)oscap_iterator_next(rules_to_fix_it); ++ ret = _xccdf_policy_rule_generate_blueprint_fix(policy, rule, sys, generic, services_enable, services_disable, kernel_append); ++ if (ret != 0) ++ break; ++ } ++ oscap_iterator_free(rules_to_fix_it); ++ ++ struct oscap_iterator *generic_it = oscap_iterator_new(generic); ++ while(oscap_iterator_has_more(generic_it)) { ++ char *var_line = (char *) oscap_iterator_next(generic_it); ++ _write_text_to_fd(output_fd, var_line); ++ } ++ _write_text_to_fd(output_fd, "\n"); ++ oscap_iterator_free(generic_it); ++ oscap_list_free(generic, free); ++ ++ _write_text_to_fd(output_fd, "[customizations.kernel]\nappend = \""); ++ struct oscap_iterator *kernel_append_it = oscap_iterator_new(kernel_append); ++ while(oscap_iterator_has_more(kernel_append_it)) { ++ char *var_line = (char *) oscap_iterator_next(kernel_append_it); ++ _write_text_to_fd(output_fd, var_line); ++ if (oscap_iterator_has_more(kernel_append_it)) ++ _write_text_to_fd(output_fd, " "); ++ } ++ _write_text_to_fd(output_fd, "\"\n\n"); ++ oscap_iterator_free(kernel_append_it); ++ oscap_list_free(kernel_append, free); ++ ++ _write_text_to_fd(output_fd, "[customizations.services]\n"); ++ _write_text_to_fd(output_fd, "enabled = ["); ++ struct oscap_iterator *services_enable_it = oscap_iterator_new(services_enable); ++ while(oscap_iterator_has_more(services_enable_it)) { ++ char *var_line = (char *) oscap_iterator_next(services_enable_it); ++ _write_text_to_fd(output_fd, var_line); ++ if (oscap_iterator_has_more(services_enable_it)) ++ _write_text_to_fd(output_fd, ","); ++ } ++ _write_text_to_fd(output_fd, "]\n"); ++ oscap_iterator_free(services_enable_it); ++ oscap_list_free(services_enable, free); ++ ++ _write_text_to_fd(output_fd, "disabled = ["); ++ struct oscap_iterator *services_disable_it = oscap_iterator_new(services_disable); ++ while(oscap_iterator_has_more(services_disable_it)) { ++ char *var_line = (char *) oscap_iterator_next(services_disable_it); ++ _write_text_to_fd(output_fd, var_line); ++ if (oscap_iterator_has_more(services_disable_it)) ++ _write_text_to_fd(output_fd, ","); ++ } ++ _write_text_to_fd(output_fd, "]\n\n"); ++ oscap_iterator_free(services_disable_it); ++ oscap_list_free(services_disable, free); ++ ++ return ret; + } + + static int _xccdf_policy_generate_fix_ansible(struct oscap_list *rules_to_fix, struct xccdf_policy *policy, const char *sys, int output_fd) +@@ -1145,6 +1317,8 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * + // in Ansible we have to generate variables first and then tasks + if (strcmp(sys, "urn:xccdf:fix:script:ansible") == 0) { + ret = _xccdf_policy_generate_fix_ansible(rules_to_fix, policy, sys, output_fd); ++ } else if (strcmp(sys, "urn:redhat:osbuild:blueprint") == 0) { ++ ret = _xccdf_policy_generate_fix_blueprint(rules_to_fix, policy, sys, output_fd); + } else { + ret = _xccdf_policy_generate_fix_other(rules_to_fix, policy, sys, output_fd); + } +diff --git a/src/common/list.c b/src/common/list.c +index 2516d0f2f0..90381069f8 100644 +--- a/src/common/list.c ++++ b/src/common/list.c +@@ -66,6 +66,25 @@ bool oscap_list_add(struct oscap_list * list, void *value) + return true; + } + ++bool oscap_list_prepend(struct oscap_list * list, void *value) ++{ ++ __attribute__nonnull__(list); ++ if (value == NULL) return false; ++ ++ struct oscap_list_item *item = malloc(sizeof(struct oscap_list_item)); ++ item->next = NULL; ++ item->data = value; ++ ++list->itemcount; ++ ++ if (list->first == NULL) { ++ list->last = list->first = item; ++ } else { ++ item->next = list->first; ++ list->first = item; ++ } ++ return true; ++} ++ + bool oscap_list_push(struct oscap_list *list, void *value) + { + return oscap_list_add(list,value); +diff --git a/src/common/list.h b/src/common/list.h +index 7a0694dc8a..3179c514f0 100644 +--- a/src/common/list.h ++++ b/src/common/list.h +@@ -62,6 +62,7 @@ struct oscap_list *oscap_list_new(void); + void oscap_create_lists(struct oscap_list **first, ...); + bool oscap_list_add(struct oscap_list *list, void *value); + bool oscap_list_push(struct oscap_list *list, void *value); ++bool oscap_list_prepend(struct oscap_list *list, void *value); + bool oscap_list_pop(struct oscap_list *list, oscap_destruct_func destructor); + bool oscap_list_remove(struct oscap_list *list, void *value, oscap_cmp_func compare, oscap_destruct_func destructor); + struct oscap_list *oscap_list_clone(const struct oscap_list * list, oscap_clone_func cloner); +diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt +index 52645834c4..9c17ebb78a 100644 +--- a/tests/API/XCCDF/unittests/CMakeLists.txt ++++ b/tests/API/XCCDF/unittests/CMakeLists.txt +@@ -75,6 +75,7 @@ add_oscap_test("test_single_rule_stigw.sh") + add_oscap_test("test_remediation_simple.sh") + add_oscap_test("test_remediation_offline.sh") + add_oscap_test("test_remediation_metadata.sh") ++add_oscap_test("test_remediation_blueprint.sh") + add_oscap_test("test_remediation_bad_fix.sh") + add_oscap_test("test_remediation_subs_plain_text.sh") + add_oscap_test("test_remediation_subs_plain_text_empty.sh") +diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.sh b/tests/API/XCCDF/unittests/test_remediation_blueprint.sh +new file mode 100755 +index 0000000000..7c79822529 +--- /dev/null ++++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.sh +@@ -0,0 +1,27 @@ ++#!/usr/bin/env bash ++. $builddir/tests/test_common.sh ++ ++set -e ++set -o pipefail ++ ++name=$(basename $0 .sh) ++result=$(make_temp_file /tmp ${name}.out) ++stderr=$(make_temp_file /tmp ${name}.out) ++ ++ret=0 ++ ++input_xml="$srcdir/${name}.xccdf.xml" ++valid_toml="$srcdir/${name}.toml" ++ ++echo "Stderr file = $stderr" ++echo "Result file = $result" ++[ -f $stderr ]; [ ! -s $stderr ]; :> $stderr ++ ++# The $valid_toml file was generated without ' # This file was generated by OpenSCAP 1.3.5 using:' line ++# to make the test independent from the scanner version. We have to filter this line from the output as well. ++ ++$OSCAP xccdf generate fix --fix-type blueprint --profile 'common' "$input_xml" | grep -v "OpenSCAP" > "$result" ++ ++diff $valid_toml $result ++ ++rm "$result" +diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.toml b/tests/API/XCCDF/unittests/test_remediation_blueprint.toml +new file mode 100644 +index 0000000000..e189adca9d +--- /dev/null ++++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.toml +@@ -0,0 +1,45 @@ ++############################################################################### ++# ++# Blueprint for Profile title on one line ++# ++# Profile Description: ++# Profile description ++# ++# Profile ID: xccdf_moc.elpmaxe.www_profile_common ++# Benchmark ID: xccdf_moc.elpmaxe.www_benchmark_test ++# Benchmark Version: 1.0 ++# XCCDF Version: 1.2 ++# ++# $ oscap xccdf generate fix --profile xccdf_moc.elpmaxe.www_profile_common --fix-type blueprint xccdf-file.xml ++# ++# It attempts to fix every selected rule, even if the system is already compliant. ++# ++# How to apply this Blueprint: ++# composer-cli blueprints push blueprint.toml ++# ++############################################################################### ++ ++name = "xccdf_moc.elpmaxe.www_profile_common" ++description = "Profile title on one line" ++version = "1.0" ++distro = rhel-80 ++ ++[[packages]] ++name = "aide" ++version = "*" ++ ++[[customizations.filesystem]] ++mountpoint = "/home" ++size = 1 ++ ++[[customizations.filesystem]] ++mountpoint = "/tmp" ++size = 2 ++ ++[customizations.kernel] ++append = "foo=bar audit=1" ++ ++[customizations.services] ++enabled = ["sshd","usbguard"] ++disabled = ["kdump"] ++ +diff --git a/tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml b/tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml +new file mode 100644 +index 0000000000..e685620dac +--- /dev/null ++++ b/tests/API/XCCDF/unittests/test_remediation_blueprint.xccdf.xml +@@ -0,0 +1,102 @@ ++<?xml version="1.0" encoding="UTF-8"?> ++<Benchmark xmlns="http://checklists.nist.gov/xccdf/1.2" id="xccdf_moc.elpmaxe.www_benchmark_test"> ++ <status>accepted</status> ++ <version>1.0</version> ++ <Profile id="xccdf_moc.elpmaxe.www_profile_common"> ++ <title>Profile title on one line ++ Profile description ++