SCAP scanning is a way to evaluate a system (hardware or software) for compliance with a standard. For example, SCAP can determine if a docker image complies with the PCI/DSS benchmark or, for those in the US federal space, many DoD (Department of Defense) projects require compliance against DISA STIGs (Security Technical Implementation Guides). Even in cases where such compliance isn’t strictly required, scanning against these security standards is beneficial as security hardening is always a good idea.
Performing SCAP scans has traditionally been a very manual process involving obscure tools, complex output requiring security specialists to interpret, and non-repeatable processes. Clearly, that approach is more waterfall than that rapid feedback, automate everything mindset of the modern Agile CI/CD-driven world. I set out to bring SCAP scanning to the fast feedback world by offering a way to run SCAP scans in GitHub Actions or GitLab CI with the output going directly into the familiar GitHub Advanced Security and GitLab Security Dashboard interfaces, respectively. Scan results can be sent to other tools, too, of course; for example, the SARIF generated by this approach can be used by Fortify Software Security Center using the Fortify SARIF Parser Plugin. I created these pipelines using well-supported, government-certified, free and open-source tooling. By lowering the barrier to entry, I’m hoping more projects can adopt SCAP scanning and improve the security baseline for everyone.
I already proved the value of this approach as, during the development and testing of these pipelines, I discovered some security issues, which I then reported and worked on fixing:
- I submitted the PR that fixed paketo buildpacks creates images with world-writable /home/cnb
- I reported that packeto buildpacks sometimes creates images with world-writable files in /workspace
How It Works
First, what tool should do the actual SCAP scan? I set my requirements to run on Linux (so it can run in GitLab CI and GitHub Actions), free and open source, actively maintained, and scan docker images. OpenSCAP is the only tool that meets these requirements, and it also works great in my testing. OpenSCAP is NIST Certified, too.
Other tools I considered include SCC and Evaluate-STIG. SCC is unable to scan docker images and therefore isn’t usable for this effort. Evaluate-STIG is another option, but it’s not publicly available (and definitely is not free/open source), it’s written in PowerShell so it may be challenging to run on Linux, it requires .NET core to be installed on the image being scanned which may be very difficult or impossible, and it cannot scan docker images; therefore, this tool misses a number of key requirements for this effort.
Next, what benchmarks should the scanner evaluate the image against? There are basically two options: STIGS provided by DISA and Compliance as Code. The DISA benchmarks are not container-aware, so evaluating them against a docker image results in a lot of false positives for rules that do not apply to containers. For example, findings are reported for secure boot configuration and single-user mode. Compliance as Code benchmarks are container aware so such findings are (correctly) not reported. Compliance as Code benchmarks also detects prerequisite software for rules, further eliminating false positives (for example, the PAM configuration rules are only evaluated if PAM is installed). Another great benefit of Compliance as Code is that they are very responsive to issue reports and pull requests (I submitted 23 pull requests so far eliminating false positives), something that can’t be said of DISA. Another great feature of Compliance as Code is that it is a collaboration between United States Government agencies and commercial operating system vendors.
The next part is where a bit of complexity seeps in. OpenSCAP outputs XCCDF, but neither GitHub nor GitLab support the import of XCCDF. Therefore, OpenSCAP’s XCCDF output must be converted to an appropriate format: GitLab CodeQuality JSON or GitLab SAST JSON for GitLab, and SARIF for GitHub. However, there are no converters that go directly between XCCDF and any of those GitLab formats or SARIF. Therefore, multiple steps are required to achieve the conversion goal. The resulting pipeline with tools that perform the conversions is:
XCCDF —(Mitre’s SAF)—> Heimdall (HDF) —(Microsoft’s SARIF Multitool)—> SARIF —(Ignis SARIF Converter)—> GitLab JSON
Mitre’s SAF is used to consume XCCDF and output HDF (Heimdall Data Format). HDF seeks to be a common format for exchanging normalized security data between cybersecurity tools. OASIS is currently developing HDF as a standard. This tool is free and open-source software and is well supported by Mitre, an American not-for-profit organization.
Microsoft’s SARIF Multitool consumes the HDF and outputs SARIF. SARIF is an open standard that’s well supported by various tools. GitHub can directly consume SARIF, so this step concludes the GitHub Actions pipeline. This tool is free and open-source software and is well supported by Microsoft, a major vendor.
GitLab (at least currently) cannot consume SARIF so conversion is required. Ignis SARIF Converter converts SARIF to either GitLab CodeQuality JSON or GitLab SAST JSON. It’s maintained by a single individual, but the maintainer is responsive and dedicated. Hopefully, GitLab can support SARIF sooner rather than later, removing the need for this tool. I greatly appreciate this tool’s author doing the work to create, develop, and maintain this software, as otherwise full GitLab integration simply wouldn’t be possible.
The Journey of Getting It All to Work Together
Determining which tools to use and how to glue them all together was the easy part. Getting all the tools to work together, and fixing bugs in them, was the hard part. Along the way, I fixed many issues with most of the tools used. Below are the 14 pull requests and 6 issues I submitted broken down by the 5 projects:
- OpenSCAP
- Pull request: oscap-docker: remove atomic, fix environment variable configuration
- Pull request: apt-devel to BuildRequires; apt-libs to Requires
- Pull request: Always use /var/lib/rpm as rpm dbpath
The issue this PR fixes was a very tricky one. In Fedora 36, the path to RPM database changed resulting in a complex situation where sometimes OpenSCAP scanning worked as expected and sometimes it returned many rules as “not applicable.”
- Alpine Linux
- Issue: openscap: add to community
- Issue: openscap: oscap-docker: ModuleNotFoundError: No module named ‘docker’
- Issue: openscap: can’t execute ‘bash’: No such file or directory
- Issue: openscap-docker: fails with Failed to import “Atomic.mount.DockerMount”. It seems Atomic has not been installed.
- Issue: openscap missing sql probe due to missing dependency on OpenDbxled.
- Issue: Issue openscap: unable to scan debian due to missing libapt dependency
- Fortify Software Security Center (SSC) parser plugin for SARIF input files
- Pull request: feat: handle SARIF “kind”
- GitHub
- Support for SARIF “kind”
I implemented a workaround usingjq
to the GitHub Actions pipeline to work around this issue. Hopefully, GitLab can add support for SARIF “kind” removing the need for this workaround.
- Support for SARIF “kind”
- Mitre’s SAF. Thank you to Amndeep Mann and Aaron Lippold for their help in these efforts. XCCDF and Heimdal are very complex standards necessitating a lot of time spent figuring out exactly how to convert between them. I really appreciate all the hours spent both asynchronously and in meetings to get these efforts done.
- Pull request: Improve xccdf_results_mapper when converting XCCDF Results to HDF Results
This PR more or less rewrote XCCDF->HDF conversion, adding support for nested groups and other XCCDF features used by Compliance as Code (this converter was originally only developed and tested only against XCCDF generated by DISA STIG provided XCCDF). - Pull request: Don’t handle every array item within each array item
This PR reduces the complexity of the key, hot part from O(n^2) to O(n), drastically reducing runtime from the order of hours to seconds. - Pull request: Map XCCDF result value of “notselected” to HDF impact of 0
- Pull request: Improving handling of XCCDF fields that may contain HTML
- Pull request: Improve xccdf_results_mapper when converting XCCDF Results to HDF Results
- Microsoft’s SARIF Multitool. Thank you to Michael C. Fanning for reviewing all of these PRs and making such timely releases, really helping me complete this effort!
- Pull request: HDF Desc in SARIF FullDescription
- Pull request: If HDF CodeDesc is empty, use Desc as Message
- Pull request: HdfConverter: map status to SARIF kind/level/rank correctly (#2695)
- Pull request: HdfConverter improvements
- Pull request: HdfConverter: Map Impact=0 to Kind=Not Applicable
- Pull request: HdfConverter: SARIF location improvements
- Pull request: HdfConverter: Set security-severity property used by GitHub
- Pull request: HdfConverter: Set precision and tags
- Ignis SARIF Converter
- Issue: [Schema] Version 14.0.4 for report type sast is unsupported
Unfortunately, the SAST report generated by this tool won’t work with the current version of GitLab until this issue is resolved.
- Issue: [Schema] Version 14.0.4 for report type sast is unsupported
All told this project took about 6 months from inception to completion. It was a really great journey, giving me exposure to a wide variety of tools, data formats, and concepts, but most importantly, introducing me to some great people too.
Implementations
By adding the following CI code to your project, you can “shift left” security scanning, enjoying all the benefits of early, easy-to-interpret results right from the comfort of the issue-tracking systems you already use.
These examples detect the base operating system by inspecting /etc/os-release
in the image. If that file is not present, the image is likely a distroless image, so the scan fails because there is no platform available for distroless images. Based on the detected platform, an appropriate profile is selected. Then, the first available profile from a prioritized list of profiles (in this example, CIS Level 2 server, CIS, then standard) is selected. Those may not be the appropriate profiles for your use case – perhaps STIG, PCI, or something else is more appropriate for you. You may also not want to autodetect the platform or use a prioritized profile list; you may want to use a fixed baseline and a fixed profile. You’ll need to customize these implementations for your specific requirements.
The example scans only the operating system / platform; it does not scan any applications for compliance. For example, if you scan an Ubuntu 22.04 image that includes PostgreSQL, the scan will scan the Ubuntu 22.04 against the Ubuntu Linux 22.04 CIS Benchmark, but it won’t scan the PostgreSQL installation against the PostgreSQL CIS Benchmark.
The GitHub Actions Code
I developed, tested, and will continue to maintain this pipeline code in Jumpstart – so please see that project to see this code in action and see the latest version of it.
The result of this action is that issues will appear under “Security,” like this:
openscap
is run from within an Alpine image. It would have been easier, with less code to develop and maintain, and potentially faster to run it from within the Ubuntu system that’s already setup. However, GitHub’s ubuntu-latest
uses Ubuntu 22.04 which includes openscap
version 1.2.16, which is too old and has significant problems that prevent its use for this purpose. openscap 1.38 (or later) is required for docker image scanning to work correctly. Therefore, Alpine Linux is used to run openscap, since it includes a later, usable version of openscap
, and Alpine Linux is a small image that starts fast.
Here’s the GitHub Actions pipeline YAML to add to your project:
---
name: Build
permissions: {}
# Run this workflow every time a new commit pushed to your repository
on: # yamllint disable-line rule:truthy
- push
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}:${{ ( github.ref_name == github.event.repository.default_branch ) && 'latest' || format('ci-{0}', github.sha ) }}
jobs:
build:
permissions:
packages: write
runs-on: ubuntu-latest
steps:
# build and push a docker image to $IMAGE_NAME
scap:
needs: build
permissions:
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
runs-on: ubuntu-latest
container:
image: alpine:3.18.3@sha256:7144f7bab3d4c2648d7e59409f15ec52a18006a128c733fcff20d3a4a54ba44a
env:
SCAP_SECURITY_GUIDE_VERSION: "0.1.69"
MICROSOFT_SARIF_MULTITOOL_VERSION: "4.3.5"
MITRE_SAF_VERSION: "1.2.33"
SSG_DIR: "ssg"
steps:
- name: Install prerequisites
run: |
# shellcheck shell=sh
set -eu
apk add curl docker openscap-docker npm gcompat unzip
npm install -g "@microsoft/sarif-multitool@${MICROSOFT_SARIF_MULTITOOL_VERSION}"
npm install -g "@mitre/saf@${MITRE_SAF_VERSION}"
mkdir -p "${SSG_DIR}"
curl "https://github.com/ComplianceAsCode/content/releases/download/v${SCAP_SECURITY_GUIDE_VERSION}/scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}.zip" -Lso "${SSG_DIR}/ssg.zip"
unzip "${SSG_DIR}/ssg.zip" -d "${SSG_DIR}"
- name: Login to GitHub Container Registry
uses: docker/login-action@a9794064588be971151ec5e7144cb535bcb56e36 # v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull the docker image to scan
run: |
# shellcheck shell=sh
set -eu
# oscap-docker requires the image to have been pulled
docker pull "${IMAGE_NAME}"
- name: Run openscap
run: |
# shellcheck shell=sh
set -eu
# extract /etc/os-release
container_id=$(docker create "${IMAGE_NAME}")
if ! docker export "${container_id}" | tar -tvf - | grep -E '\setc/os-release( ->.*)?$' > /dev/null 2>&1 ; then
>&2 echo "The operating system used by ${IMAGE_NAME} could not be detected."
>&2 echo "Images that are not based on an operating system (such as distroless images) cannot be scanned by SCAP."
exit 1
fi
docker cp -L "$container_id:/etc/os-release" .
docker rm "$container_id"
unset container_id
# determine which ssg to use based on /etc/os-release
# see https://www.freedesktop.org/software/systemd/man/os-release.html
version_id=$(awk -F= '$1=="VERSION_ID" { print $2 ;}' os-release | sed 's/"//g')
id=$(awk -F= '$1=="ID" { print $2 ;}' os-release | sed 's/"//g')
if [ "${id}" = "ubuntu" ] && echo "${version_id}" | grep -qE '^18\.04(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ubuntu1804-ds.xml"
elif [ "${id}" = "ubuntu" ] && echo "${version_id}" | grep -qE '^20\.04(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ubuntu2004-ds.xml"
elif [ "${id}" = "ubuntu" ] && echo "${version_id}" | grep -qE '^22\.04(\..*)?$' ; then\
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ubuntu2204-ds.xml"
elif [ "${id}" = "centos" ] && echo "${version_id}" | grep -qE '^7(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-centos7-ds.xml"
elif [ "${id}" = "centos" ] && echo "${version_id}" | grep -qE '^8(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-centos8-ds.xml"
elif [ "${id}" = "ol" ] && echo "${version_id}" | grep -qE '^7(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ol7-ds.xml"
elif [ "${id}" = "ol" ] && echo "${version_id}" | grep -qE '^8(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ol8-ds.xml"
elif [ "${id}" = "ol" ] && echo "${version_id}" | grep -qE '^9(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ol9-ds.xml"
elif [ "${id}" = "rhel" ] && echo "${version_id}" | grep -qE '^7(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-rhel7-ds.xml"
elif [ "${id}" = "rhel" ] && echo "${version_id}" | grep -qE '^8(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-rhel8-ds.xml"
elif [ "${id}" = "rhel" ] && echo "${version_id}" | grep -qE '^9(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-rhel9-ds.xml"
elif [ "${id}" = "sles" ] && echo "${version_id}" | grep -qE '^12(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-sle12-ds.xml"
elif [ "${id}" = "sles" ] && echo "${version_id}" | grep -qE '^15(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-sle15-ds.xml"
else
>&2 echo "There is no configuration available for ${id} ${version_id}"
exit 1
fi
# Select the profile to use. The first profile that exists in the ssg is used.
for profile in xccdf_org.ssgproject.content_profile_cis_level2_server xccdf_org.ssgproject.content_profile_cis xccdf_org.ssgproject.content_profile_standard; do
if oscap info --profiles "${SSG_DIR}/${ssg}" | grep -qF "${profile}:"; then
echo "Selected profile: ${profile}"
break;
fi
done
set +e
oscap-docker image "${IMAGE_NAME}" xccdf eval --verbose ERROR --fetch-remote-resources --profile "${profile}" --results "openscap-report.xml" --report "openscap-report.html" "${SSG_DIR}/${ssg}"
OSCAP_EXIT_CODE=$?
set -e
case "${OSCAP_EXIT_CODE}" in
0)
echo "All rules passed"
;;
1)
>&2 echo "An error occurred during evaluation"
exit 2
;;
2)
echo "There is at least one rule with either fail or unknown result"
;;
*)
>&2 echo "openscap returned an unexpected exit status of $OSCAP_EXIT_CODE"
exit "$OSCAP_EXIT_CODE"
;;
esac
- name: Convert xml to hdf
run: |
# shellcheck shell=sh
set -eu
saf convert xccdf_results2hdf -i "openscap-report.xml" -o openscap-report.hdf
- name: Convert hdf to sarif
run: |
# shellcheck shell=sh
set -eu
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 sarif-multitool convert -t Hdf -o openscap-report.sarif openscap-report.hdf.json
- name: filter results that shouldn't be GitHub security alerts
# Hopefully GitHub adds support for SARIF's "kind" eliminating the need for this step: https://github.com/orgs/community/discussions/65477
run: |
# shellcheck shell=sh
set -eu
jq 'del(.runs[].results[] | select(.kind == "notApplicable" or .kind == "pass" or .kind == "informational" ))' openscap-report.sarif > filtered.sarif
mv filtered.sarif openscap-report.sarif
- name: Upload reports
if: success() || failure() # always run even if the previous step fails
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
with:
name: openscap-reports
path: |
openscap-report.html
openscap-report.xml
openscap-report.hdf.json
openscap-report.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2
# Results are generated only on a success or failure
# this is required since GitHub by default won't run the next step
# when the previous one has failed. Security checks that do not pass will 'fail'.
# An alternative is to add `continue-on-error: true` to the previous step
if: success() || failure()
with:
sarif_file: openscap-report.sarif
To keep the tools up to date using Renovate (it is very important to keep dependencies up to date):
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"regexManagers": [
{
"fileMatch": ["\\.yml$"],
"matchStrings": ["MICROSOFT_SARIF_MULTITOOL_VERSION *: *\"(?<currentValue>.+?)\""],
"depNameTemplate": "@microsoft/sarif-multitool",
"datasourceTemplate": "npm"
},
{
"fileMatch": ["\\.yml$"],
"matchStrings": ["MITRE_SAF_VERSION *: *\"(?<currentValue>.+?)\""],
"depNameTemplate": "@mitre/saf",
"datasourceTemplate": "npm"
}
]
}
The GitLab CI Code
I developed, tested, and will continue to maintain this pipeline code in Jumpstart – so please see that project to see this code in action and see the latest version of it.
The result of this pipeline is that issues will appear under “Security,” like this:
Here’s the GitLab CI pipeline YAML to add to your project:
---
# An earlier job/stage must have build and pushed a docker image to $IMAGE_NAME that the scap job will scan
scap:
stage: test
image:
name: alpine:3.18.3@sha256:7144f7bab3d4c2648d7e59409f15ec52a18006a128c733fcff20d3a4a54ba44a # alpine official image
entrypoint: [""]
services:
- name: docker:24.0.4-dind@sha256:594b3a4dc012e5160fed2a5c47a36ac5af6fa25f0c155bd9cff23d4f8c6fec16
alias: docker
# explicitly disable tls to avoid docker startup interruption as of docker 20.10.9
# there is so security concern because docker is only accessed from within the same system;
# there is no external network communication to worry about.
# See https://github.com/testcontainers/testcontainers-java/pull/4573
command: ["--tls=false"]
variables:
# Instruct Testcontainers to use the daemon of DinD.
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
SCAP_SECURITY_GUIDE_VERSION: "0.1.69"
MICROSOFT_SARIF_MULTITOOL_VERSION: "4.3.5"
MITRE_SAF_VERSION: "1.2.33"
SARIF_CONVERTER_VERSION: "0.7.1"
script:
- |
# shellcheck shell=sh
set -eu
printf "\e[0Ksection_start:%s:prerequisites[collapsed=true]\r\e[0KInstalling prerequisites...\n" "$(date +%s)"
apk add curl docker openscap-docker npm gcompat unzip
npm install -g "@microsoft/sarif-multitool@${MICROSOFT_SARIF_MULTITOOL_VERSION}"
npm install -g "@mitre/saf@${MITRE_SAF_VERSION}"
mkdir ssg
ssgdir="ssg"
curl "https://github.com/ComplianceAsCode/content/releases/download/v${SCAP_SECURITY_GUIDE_VERSION}/scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}.zip" -Lso "${ssgdir}/ssg.zip"
unzip "${ssgdir}/ssg.zip" -d "${ssgdir}"
curl "https://gitlab.com/ignis-build/sarif-converter/-/releases/v${SARIF_CONVERTER_VERSION}/downloads/bin/sarif-converter-linux" -Lso sarif-converter
chmod +x sarif-converter
printf "\e[0Ksection_end:%s:prerequisites\r\e[0K\n" "$(date +%s)"
printf "\e[0Ksection_start:%s:docker[collapsed=true]\r\e[0KPulling the docker image to scan...\n" "$(date +%s)"
# oscap-docker requires the image to have been pulled
docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
docker pull "${IMAGE_NAME}"
printf "\e[0Ksection_end:%s:docker\r\e[0K\n" "$(date +%s)"
printf "\e[0Ksection_start:%s:scan\r\e[0KRunning openscap...\n" "$(date +%s)"
# extract /etc/os-release
container_id=$(docker create "${IMAGE_NAME}")
if ! docker export "${container_id}" | tar -tvf - | grep -E '\setc/os-release( ->.*)$' > /dev/null 2>&1 ; then
>&2 echo "The operating system used by ${IMAGE_NAME} could not be detected."
>&2 echo "Images that are not based on an operating system (such as distroless images) cannot be scanned by SCAP."
exit 1
fi
docker cp -L "$container_id:/etc/os-release" .
docker rm "$container_id"
unset container_id
# determine which ssg to use based on /etc/os-release
# see https://www.freedesktop.org/software/systemd/man/os-release.html
version_id=$(awk -F= '$1=="VERSION_ID" { print $2 ;}' os-release | sed 's/"//g')
id=$(awk -F= '$1=="ID" { print $2 ;}' os-release | sed 's/"//g')
if [ "${id}" = "ubuntu" ] && echo "${version_id}" | grep -qE '^18\.04(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ubuntu1804-ds.xml"
elif [ "${id}" = "ubuntu" ] && echo "${version_id}" | grep -qE '^20\.04(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ubuntu2004-ds.xml"
elif [ "${id}" = "ubuntu" ] && echo "${version_id}" | grep -qE '^22\.04(\..*)?$' ; then\
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ubuntu2204-ds.xml"
elif [ "${id}" = "centos" ] && echo "${version_id}" | grep -qE '^7(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-centos7-ds.xml"
elif [ "${id}" = "centos" ] && echo "${version_id}" | grep -qE '^8(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-centos8-ds.xml"
elif [ "${id}" = "ol" ] && echo "${version_id}" | grep -qE '^7(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ol7-ds.xml"
elif [ "${id}" = "ol" ] && echo "${version_id}" | grep -qE '^8(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ol8-ds.xml"
elif [ "${id}" = "ol" ] && echo "${version_id}" | grep -qE '^9(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-ol9-ds.xml"
elif [ "${id}" = "rhel" ] && echo "${version_id}" | grep -qE '^7(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-rhel7-ds.xml"
elif [ "${id}" = "rhel" ] && echo "${version_id}" | grep -qE '^8(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-rhel8-ds.xml"
elif [ "${id}" = "rhel" ] && echo "${version_id}" | grep -qE '^9(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-rhel9-ds.xml"
elif [ "${id}" = "sles" ] && echo "${version_id}" | grep -qE '^12(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-sle12-ds.xml"
elif [ "${id}" = "sles" ] && echo "${version_id}" | grep -qE '^15(\..*)?$' ; then
ssg="scap-security-guide-${SCAP_SECURITY_GUIDE_VERSION}/ssg-sle15-ds.xml"
else
>&2 echo "There is no configuration available for ${id} ${version_id}"
exit 1
fi
# Select the profile to use. The first profile that exists in the ssg is used.
for profile in xccdf_org.ssgproject.content_profile_cis_level2_server xccdf_org.ssgproject.content_profile_cis xccdf_org.ssgproject.content_profile_standard; do
if oscap info --profiles "${ssgdir}/${ssg}" | grep -qF "${profile}:"; then
echo "Selected profile: ${profile}"
break;
fi
done
set +e
oscap-docker image "${IMAGE_NAME}" xccdf eval --verbose ERROR --fetch-remote-resources --profile "${profile}" --results "openscap-report.xml" --report "openscap-report.html" "${ssgdir}/${ssg}"
OSCAP_EXIT_CODE=$?
set -e
echo "To view the openscap report: ${CI_JOB_URL}/artifacts/external_file/openscap-report.html"
case "${OSCAP_EXIT_CODE}" in
0)
echo "All rules passed"
;;
1)
>&2 echo "An error occurred during evaluation"
exit 2
;;
2)
echo "There is at least one rule with either fail or unknown result"
;;
*)
>&2 echo "openscap returned an unexpected exit status of $OSCAP_EXIT_CODE"
exit "$OSCAP_EXIT_CODE"
;;
esac
printf "\e[0Ksection_end:%s:scan\r\e[0K\n" "$(date +%s)"
printf "\e[0Ksection_start:%s:xml_to_hdf\r\e[0KConverting xml to hdf...\n" "$(date +%s)"
saf convert xccdf_results2hdf -i "openscap-report.xml" -o openscap-report.hdf
printf "\e[0Ksection_end:%s:xml_to_hdf\r\e[0K\n" "$(date +%s)"
printf "\e[0Ksection_start:%s:hdf_to_sarif\r\e[0KConverting hdf to sarif...\n" "$(date +%s)"
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 sarif-multitool convert -t Hdf -o openscap-report.sarif openscap-report.hdf.json
printf "\e[0Ksection_end:%s:hdf_to_sarif\r\e[0K\n" "$(date +%s)"
printf "\e[0Ksection_start:%s:sarif_to_gitlab\r\e[0KConverting sarif to GitLab SAST JSON...\n" "$(date +%s)"
./sarif-converter --type sast openscap-report.sarif gl-sast-report.json
printf "\e[0Ksection_end:%s:sarif_to_gitlab\r\e[0K\n" "$(date +%s)"
artifacts:
when: always
paths:
- "openscap-report.xml"
- "openscap-report.html"
- "openscap-report.sarif"
- "openscap-report.hdf.json"
reports:
sast:
- "gl-sast-report.json"
To keep the tools up to date using Renovate (it is very important to keep dependencies up to date):
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"regexManagers": [
{
"fileMatch": ["\\.yml$"],
"matchStrings": ["SARIF_CONVERTER_VERSION *: *\"(?<currentValue>.+?)\""],
"depNameTemplate": "ignis-build/sarif-converter",
"datasourceTemplate": "gitlab-releases"
},
{
"fileMatch": ["\\.yml$"],
"matchStrings": ["MICROSOFT_SARIF_MULTITOOL_VERSION *: *\"(?<currentValue>.+?)\""],
"depNameTemplate": "@microsoft/sarif-multitool",
"datasourceTemplate": "npm"
},
{
"fileMatch": ["\\.yml$"],
"matchStrings": ["MITRE_SAF_VERSION *: *\"(?<currentValue>.+?)\""],
"depNameTemplate": "@mitre/saf",
"datasourceTemplate": "npm"
}
]
}
SCAP Security and Compliance Scanning of Docker Images in GitHub Actions and GitLab CI by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
I don’t usually leave replies on blog posts but I really enjoyed this read! Thanks a lot for the work.