| |
@@ -0,0 +1,245 @@
|
| |
+ #!/bin/bash
|
| |
+ # vim: dict+=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
|
| |
+ . /usr/share/beakerlib/beakerlib.sh || exit 1
|
| |
+
|
| |
+ PACKAGE="scap-security-guide"
|
| |
+
|
| |
+ RULE_PREFIX="xccdf_org.ssgproject.content_rule"
|
| |
+ declare -A RULE_RESULTS=(
|
| |
+ ["test_cpe_applicable"]="pass"
|
| |
+ ["test_cpe_applicable_no_platforms"]="pass"
|
| |
+ ["test_cpe_applicable_legacy_platform"]="pass"
|
| |
+ ["test_cpe_applicable_complex"]="pass"
|
| |
+ ["test_cpe_applicable_more_platforms"]="pass"
|
| |
+ ["test_cpe_applicable_os_interval"]="pass"
|
| |
+ ["test_cpe_not_applicable"]="notapplicable"
|
| |
+ ["test_cpe_not_applicable_os"]="notapplicable"
|
| |
+ ["test_cpe_not_applicable_arch"]="notapplicable"
|
| |
+ ["test_cpe_not_applicable_package"]="notapplicable"
|
| |
+ )
|
| |
+
|
| |
+
|
| |
+ function assertStringNotEmpty() {
|
| |
+ local string="$1"
|
| |
+ local assert_msg="$2"
|
| |
+
|
| |
+ if [ -z "$string" ]; then
|
| |
+ rlFail "$assert_msg"
|
| |
+ else
|
| |
+ rlPass "$assert_msg"
|
| |
+ fi
|
| |
+ }
|
| |
+
|
| |
+
|
| |
+ # Function verifies that CPE platforms from rule with ID <RULE_ID> are correctly
|
| |
+ # built into a data stream <DATASTREAM>.
|
| |
+ #
|
| |
+ # Usage:
|
| |
+ # verifyCpePlatformInDS <RULE_ID> <DATASTREAM>
|
| |
+ #
|
| |
+ # Description of a data stream with a rule which has applicability limited by a CPE platform:
|
| |
+ # <xccdf-1.2:rule> element has a subelement <xccdf-1.2:platform idref="#PLATFORM_ID"/>.
|
| |
+ # Then there is a <cpe-lang:platform-specification> element under <xccdf-1.2:Becnhmark>
|
| |
+ # which contains <cpe-lang:platform> elements (referenced from the <xccdf-1.2:platform> elements).
|
| |
+ # Each platform contains logical tests (<cpe-lang:logical-test>) and fact-refs
|
| |
+ # (<cpe-lang:fact-ref>). Fact-refs are, for example, defined like this:
|
| |
+ # <cpe-lang:fact-ref name="cpe:/a:machine"/>
|
| |
+ # so they are pointing to a CPE name in <cpe-dict> component. The CPE item is defined like this
|
| |
+ # <cpe-dict:cpe-item name="cpe:/a:machine">
|
| |
+ # <cpe-dict:title xml:lang="en-us">Bare-metal or Virtual Machine</cpe-dict:title>
|
| |
+ # <cpe-dict:check system="http://oval.mitre.org/XMLSchema/oval-definitions-5" href="ssg-rhel8-cpe-oval.xml">oval:ssg-installed_env_is_a_machine:def:1</cpe-dict:check>
|
| |
+ # </cpe-dict:cpe-item>
|
| |
+ # and it is pointing to an actual OVAL inventory check which is defined in the OVAL
|
| |
+ # component of the data stream.
|
| |
+ function verifyCpePlatformInDS() {
|
| |
+ local rule="$1"
|
| |
+ local ds="$2"
|
| |
+
|
| |
+ local platform_idref=$(xmllint --xpath "string(//*[local-name()=\"Rule\"][@id=\"$rule\"]/*[local-name()=\"platform\"]/@idref)" "$ds" | tr -d '#')
|
| |
+ assertStringNotEmpty "$platform_idref" "Rule '$rule' contains platform with idref '$platform_idref'"
|
| |
+
|
| |
+ rlRun "xmllint --xpath '//*[local-name()=\"platform-specification\"]/*[local-name()=\"platform\"][@id=\"$platform_idref\"]/*[local-name()=\"logical-test\"]' $ds" \
|
| |
+ 0 "Platform '$platform_idref' contains logical-test element"
|
| |
+
|
| |
+ local fact_refs=$(xmllint --xpath "//*[local-name()=\"platform-specification\"]/*[local-name()=\"platform\"][@id=\"$platform_idref\"]/*[local-name()=\"logical-test\"]//*[local-name()=\"fact-ref\"]/@name" "$ds" \
|
| |
+ | sed 's|name="\([^"]\+\)"|\1|g')
|
| |
+ assertStringNotEmpty "$fact_refs" "Platform '$platform_idref' contains fact-ref elements '$fact_refs'"
|
| |
+
|
| |
+ for fact_ref in $fact_refs; do
|
| |
+ oval_def=$(xmllint --xpath "string(//*[local-name()=\"cpe-list\"]/*[local-name()=\"cpe-item\"][@name=\"$fact_ref\"]/*[local-name()=\"check\"])" "$ds")
|
| |
+ assertStringNotEmpty "$fact_ref" "cpe-item '$fact_ref' contains check element '$oval_def'"
|
| |
+
|
| |
+ rlRun "xmllint --xpath '//*[local-name()=\"oval_definitions\"]/*[local-name()=\"definitions\"]/*[local-name()=\"definition\"][@id=\"$oval_def\"]' $ds" \
|
| |
+ 0 "OVAL definition referenced by check element '$oval_def' exists in data stream"
|
| |
+ done
|
| |
+ }
|
| |
+
|
| |
+
|
| |
+ rlJournalStart
|
| |
+
|
| |
+ rlPhaseStartSetup
|
| |
+ rlImport "distribution/Cleanup" || rlDie "Failed to import Cleanup library"
|
| |
+ rlImport "distribution/RpmSnapshot" || rlDie "Failed to import RpmSnapshot library"
|
| |
+ rlImport "scap-common-lib/scap-common" || rlDie "Failed to import scap-common library"
|
| |
+ rlAssertRpm "$PACKAGE"
|
| |
+ rlAssertRpm "openscap-scanner"
|
| |
+
|
| |
+ PKG_NAME="kernel"
|
| |
+ PKG_VERSION="$(rpm -q "$PKG_NAME" --queryformat "%{VERSION}-%{RELEASE}\n" | tail -n1 | sed "s/\(\.fc\|\.el\).*$//")"
|
| |
+
|
| |
+ # TEST_ARCH must be different than architecture of the host running the test.
|
| |
+ if [ "$(rlGetArch)" == "aarch64" ]; then
|
| |
+ TEST_ARCH="s390x"
|
| |
+ else
|
| |
+ TEST_ARCH="aarch64"
|
| |
+ fi
|
| |
+
|
| |
+ if rlIsRHEL || rlIsCentOS; then
|
| |
+ # DISTRO_NAME must be rhel also for centos as it is built as a derivative of rhel.
|
| |
+ DISTRO_NAME="rhel"
|
| |
+ PRODUCT="${DISTRO_NAME}$(rlGetDistroRelease)"
|
| |
+ DISTRO_VERSION=$(grep "^VERSION_ID" /etc/os-release | sed 's|VERSION_ID=["]*\([^"]\+\)["]*|\1|')
|
| |
+ if rlIsCentOS; then
|
| |
+ # CentOS Stream does not have minor version.
|
| |
+ DISTRO_VERSION_FUTURE="$(( DISTRO_VERSION + 1 ))"
|
| |
+ # Only RHEL and Fedora are supported in os_linux platform atm.
|
| |
+ RULE_RESULTS["test_cpe_applicable"]="notapplicable"
|
| |
+ RULE_RESULTS["test_cpe_applicable_os_interval"]="notapplicable"
|
| |
+ else
|
| |
+ DISTRO_VERSION_MAJOR=$(cut -f 1 -d '.' <<< "$DISTRO_VERSION")
|
| |
+ DISTRO_VERSION_MINOR=$(cut -f 2 -d '.' <<< "$DISTRO_VERSION")
|
| |
+ DISTRO_VERSION_FUTURE="${DISTRO_VERSION_MAJOR}.$(( DISTRO_VERSION_MINOR + 1 ))"
|
| |
+ fi
|
| |
+ else
|
| |
+ DISTRO_NAME="fedora"
|
| |
+ PRODUCT="${DISTRO_NAME}"
|
| |
+ DISTRO_VERSION="$(rlGetDistroRelease)"
|
| |
+ DISTRO_VERSION_FUTURE=$(( DISTRO_VERSION + 1 ))
|
| |
+ fi
|
| |
+ DS_NAME="ssg-$(scapCommonGetDistroName)-ds.xml"
|
| |
+
|
| |
+ RpmSnapshotCreate
|
| |
+ CleanupRegister "RpmSnapshotRevert"
|
| |
+ rlRun "TmpDir=\$(mktemp -d)"
|
| |
+ CleanupRegister "rlRun 'rm -r $TmpDir' 0 'Removing tmp directory'"
|
| |
+ rlRun "cp -r test_rules/ $TmpDir"
|
| |
+ rlRun "pushd $TmpDir"
|
| |
+ CleanupRegister "rlRun 'popd'"
|
| |
+
|
| |
+ scapCommonInstallAnsible || rlDie "Failed to install required Ansible packages"
|
| |
+
|
| |
+ rlFetchSrcForInstalled "$PACKAGE"
|
| |
+ if rlIsFedora || rlIsRHEL ">=8" || rlIsCentOS ">=8"; then
|
| |
+ rlRun "dnf builddep -y $PACKAGE*"
|
| |
+ else
|
| |
+ rlRun "yum-builddep -y $PACKAGE*"
|
| |
+ fi
|
| |
+ TOP_DIR=$(rpm --eval %_topdir)
|
| |
+ rlRun "rm -rf $TOP_DIR" 0-255
|
| |
+ CleanupRegister "rlRun 'rm -rf $TOP_DIR'"
|
| |
+ rlRun "rpm -ihv `ls *.rpm`" 0 "Install $PACKAGE source RPM"
|
| |
+ rlPhaseEnd
|
| |
+
|
| |
+ rlPhaseStartSetup "Prepare source tree (%prep stage and patches from the spec file)"
|
| |
+ #rlRun "rpmbuild -v -bp ${TOP_DIR}/SPECS/${PACKAGE}.spec"
|
| |
+ #TODO
|
| |
+ rlRun "git clone https://github.com/ComplianceAsCode/content -b cpe_versioning"
|
| |
+ TOP_DIR="content"
|
| |
+ #TODO
|
| |
+
|
| |
+ #SRC_DIR=$(readlink -f ${TOP_DIR}/BUILD/${PACKAGE}* | tail -n1)
|
| |
+ #TODO
|
| |
+ SRC_DIR=$(readlink -f ${TOP_DIR} | tail -n1)
|
| |
+ #TODO
|
| |
+ RULES_DIR="${SRC_DIR}/linux_os/guide/system"
|
| |
+ PKG_APPLICABILITY_CONF="${SRC_DIR}/shared/applicability/package.yml"
|
| |
+ BUILD_DIR="${SRC_DIR}/build"
|
| |
+ DS="${BUILD_DIR}/${DS_NAME}"
|
| |
+
|
| |
+ rlLog "ENV:"
|
| |
+ rlLog " SRC_DIR : $SRC_DIR"
|
| |
+ rlLog " BUILD_DIR : $BUILD_DIR"
|
| |
+ rlLog " DS : $DS"
|
| |
+ rlLog " OS : $DISTRO_NAME"
|
| |
+ rlLog " OS VERSION : $DISTRO_VERSION"
|
| |
+ rlLog " OS VERSION (FUTURE) : $DISTRO_VERSION_FUTURE"
|
| |
+ rlLog " TEST PKG : $PKG_NAME"
|
| |
+ rlLog " TEST PKG VERSION : $PKG_VERSION"
|
| |
+ rlPhaseEnd
|
| |
+
|
| |
+ rlPhaseStartSetup "Insert test content into the source tree"
|
| |
+ # Extend source tree with our prepared content updated with
|
| |
+ # versions of RHEL and test RPM.
|
| |
+ if ! grep -q "${PKG_NAME}:" "$PKG_APPLICABILITY_CONF"; then
|
| |
+ rlRun "echo ' $PKG_NAME:
|
| |
+ title: \"SSH Server Test\"
|
| |
+ pkgname: $PKG_NAME' >> $PKG_APPLICABILITY_CONF" \
|
| |
+ 0 "Define $PKG_NAME package applicability"
|
| |
+ fi
|
| |
+ for f in $(find test_rules/ -iname "*rule.yml"); do
|
| |
+ rlRun "sed -i \"s/{PRODTYPE}/$PRODUCT/g\" $f"
|
| |
+ rlRun "sed -i \"s/{DISTRO_NAME}/$DISTRO_NAME/g\" $f"
|
| |
+ rlRun "sed -i \"s/{DISTRO_VERSION}/$DISTRO_VERSION/g\" $f"
|
| |
+ rlRun "sed -i \"s/{DISTRO_VERSION_FUTURE}/$DISTRO_VERSION_FUTURE/g\" $f"
|
| |
+ rlRun "sed -i \"s/{TEST_ARCH}/$TEST_ARCH/g\" $f"
|
| |
+ rlRun "sed -i \"s/{PKG_NAME}/$PKG_NAME/g\" $f"
|
| |
+ rlRun "sed -i \"s/{PKG_VERSION}/$PKG_VERSION/g\" $f"
|
| |
+ done
|
| |
+ rlRun "cp -r test_rules/ $RULES_DIR"
|
| |
+ rlRun "cd $SRC_DIR"
|
| |
+ rlRun "mkdir -p build"
|
| |
+ rlRun "./build_product $PRODUCT --derivatives" \
|
| |
+ 0 "Building $PRODUCT data stream" \
|
| |
+ || { CleanupDo; rlDie "Failed to build $PRODUCT data stream"; }
|
| |
+ rlRun "cd -"
|
| |
+ rlPhaseEnd
|
| |
+
|
| |
+ for rule in ${!RULE_RESULTS[@]}; do
|
| |
+ rule_id="${RULE_PREFIX}_${rule}"
|
| |
+ rlPhaseStartTest "$rule - verify CPE is properly built into a data stream"
|
| |
+ rlRun "cat ${RULES_DIR}/test_rules/${rule}/rule.yml" 0 "Print full rule.yml"
|
| |
+ if [ "$rule" == "test_cpe_applicable_no_platforms" ]; then
|
| |
+ rlLogInfo "$rule has no platforms, skiping."
|
| |
+ else
|
| |
+ verifyCpePlatformInDS "$rule_id" "$DS"
|
| |
+ fi
|
| |
+ rlPhaseEnd
|
| |
+ rlPhaseStartTest "$rule - scan result corresponds with its CPE applicability"
|
| |
+ rule_exp_result="${RULE_RESULTS[$rule]}"
|
| |
+ rlRun "oscap xccdf eval --verbose INFO --progress --profile '(all)' --rule $rule_id $DS >stdout 2>stderr"
|
| |
+ rlAssertNotGrep "(^E:|^openscap error:)" stderr -iE
|
| |
+ rlAssertGrep "${rule_id}:${rule_exp_result}" stdout -i
|
| |
+ if [ $? -ne 0 ]; then
|
| |
+ rlRun "cat stdout"
|
| |
+ rlRun "cat stderr"
|
| |
+ fi
|
| |
+ rlPhaseEnd
|
| |
+ rlPhaseStartTest "$rule - verify Ansible remediation CPE applicability"
|
| |
+ ruleDS="$(basename $DS).${rule}"
|
| |
+ rlRun "cp $DS $ruleDS" 0 "Create a copy of data stream to generate Ansible remediation"
|
| |
+ rlRun "sed -i '/<.*Rule.*id=\"${rule_id}\"/s/selected=\"false\"/selected=\"true\"/g' $ruleDS" \
|
| |
+ 0 "Select only single rule in the data stream copy - $rule"
|
| |
+ rlRun "oscap xccdf generate fix --fix-type ansible --output ${rule}.yml $ruleDS" \
|
| |
+ 0 "Generate Ansible remediation for the rule $rule"
|
| |
+ cat "${rule}.yml"
|
| |
+ rlRun "ansible-playbook --syntax-check ${rule}.yml" \
|
| |
+ 0 "Verify generated Ansible remediation has correct syntax"
|
| |
+ rlRun -s "ansible-playbook -i \"localhost,\" -c local ${rule}.yml" \
|
| |
+ 0 "Run generated Ansible remediation for rule $rule"
|
| |
+ rlAssertGrep "failed=0" "$rlRun_LOG"
|
| |
+ rlAssertGrep "ok=[1-9]\+" "$rlRun_LOG"
|
| |
+ if [ "$rule_exp_result" == "pass" ]; then
|
| |
+ rlAssertGrep "skipped=0" "$rlRun_LOG"
|
| |
+ else
|
| |
+ rlAssertGrep "skipped=1" "$rlRun_LOG"
|
| |
+ fi
|
| |
+ rlRun "rm -f $rlRun_LOG $ruleDS"
|
| |
+ rlPhaseEnd
|
| |
+ done
|
| |
+
|
| |
+ rlPhaseStartCleanup
|
| |
+ CleanupDo
|
| |
+ rlPhaseEnd
|
| |
+
|
| |
+ rlJournalPrintText
|
| |
+ rlJournalEnd
|
| |
For testing purposes the test temporarily uses https://github.com/ComplianceAsCode/content/tree/cpe_versioning to build and test the CPE AL feature.