Skip to content

Commit 74482e8

Browse files
committed
utils: Introduce oscap-remediate-offline utility and corresponding
systemd service The service wrapper and activator utility are installed by CMake and then the service is enabled by the package manager during the installation phase.
1 parent 41d2cf5 commit 74482e8

10 files changed

+369
-3
lines changed

.packit.yaml

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
downstream_package_name: openscap
21
jobs:
32
- job: copr_build
43
metadata:
54
targets:
65
- fedora-all-x86_64
6+
- epel-8-x86_64
77
trigger: pull_request
88
- job: tests
99
metadata:
@@ -14,7 +14,11 @@ jobs:
1414
metadata:
1515
dist-git-branch: fedora-all
1616
trigger: release
17-
specfile_path: openscap.spec
17+
actions:
18+
get-current-version:
19+
- bash -c "source release_tools/versions.sh && echo ${version}"
1820
synced_files:
1921
- .packit.yaml
22+
downstream_package_name: openscap
2023
upstream_package_name: openscap
24+
specfile_path: openscap.spec

CMakeLists.txt

+9
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ find_package(OpenDbx)
109109
find_package(PCRE REQUIRED)
110110
find_package(PerlLibs)
111111
find_package(Popt)
112+
find_package(Systemd)
112113

113114
find_package(Procps)
114115
if(PROCPS_FOUND)
@@ -607,6 +608,14 @@ if(NOT WIN32)
607608
${CMAKE_CURRENT_BINARY_DIR}/libopenscap.pc
608609
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
609610
)
611+
if(WITH_SYSTEMD)
612+
# systemd service for offline (boot-time) remediation
613+
configure_file("oscap-remediate.service.in" "oscap-remediate.service" @ONLY)
614+
install(FILES
615+
${CMAKE_CURRENT_BINARY_DIR}/oscap-remediate.service
616+
DESTINATION ${SYSTEMD_UNITDIR}
617+
)
618+
endif()
610619
endif()
611620

612621
# changelog

cmake/FindSystemd.cmake

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# https://raw.githubusercontent.com/ximion/limba/master/data/cmake/systemdservice.cmake
2+
#
3+
# Find systemd service dir
4+
5+
include(LibFindMacros)
6+
7+
# Use pkg-config to get hints about paths
8+
libfind_pkg_check_modules(SYSTEMD systemd)
9+
10+
if(SYSTEMD_FOUND AND "${SYSTEMD_UNITDIR}" STREQUAL "")
11+
execute_process(
12+
COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=systemdsystemunitdir systemd
13+
OUTPUT_VARIABLE SYSTEMD_UNITDIR
14+
)
15+
string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_UNITDIR "${SYSTEMD_UNITDIR}")
16+
elseif(NOT SYSTEMD_FOUND AND SYSTEMD_UNITDIR)
17+
message(FATAL_ERROR "Variable SYSTEMD_UNITDIR is defined, but we can't find systemd using pkg-config")
18+
endif()
19+
20+
if(SYSTEMD_FOUND)
21+
set(WITH_SYSTEMD "ON")
22+
message(STATUS "Found systemd, services install dir: ${SYSTEMD_UNITDIR}")
23+
else()
24+
set(WITH_SYSTEMD "OFF")
25+
endif(SYSTEMD_FOUND)

docs/manual/manual.adoc

+42
Original file line numberDiff line numberDiff line change
@@ -2158,7 +2158,49 @@ by `oscap-docker` or `oscap-vm`, like containers in other
21582158
formats than Docker.
21592159
Again, usage of the tool mimics usage and options of `oscap` tool.
21602160

2161+
== Scanning and remediating the system at boot time
21612162

2163+
OpenSCAP can scan and remediate the system at boot time using systemd's `system-update.target`.
2164+
The `oscap-remediate.service` is expecting the `/system-update` symlink (universal trigger for all services in system-update's requires list) which points to a file with base name `oscap-remediate-offline.conf.sh`.
2165+
The file itself could be located anywhere, but it should be accessible at boot time. This configuration file is essentially a Bash script with a set of environment variables, loaded with `source` by the service.
2166+
Upon the start the service will immediately remove the symlink to prevent invocation loop but it won't touch the configuration file itself. A helper tool, `oscap-remediate-offline`, can be used to bootstrap the configuration and prime the `/system-update` symlink, but its flexibility is limited and in general it should only be used for debugging.
2167+
2168+
WARNING: The `oscap-remediate-offline` tool should not be considered as a stable API for priming the service. The *only* API of the service is the configuration file and the `/system-update` symlink pointing to it.
2169+
2170+
Configuration variables:
2171+
----
2172+
# Mandatory -----------------------------
2173+
2174+
# The path to the data stream file
2175+
OSCAP_REMEDIATE_DS=/some/data_stream.xml
2176+
2177+
# The ID of the profile to use
2178+
OSCAP_REMEDIATE_PROFILE_ID=some_profile
2179+
2180+
# Optional ------------------------------
2181+
2182+
# Data stream, XCCDF or Benchmark IDs
2183+
# Benchmark ID and DS + XCCDF IDs pair are mutually
2184+
# exclusive. DS + XCCDF IDs will take precedence
2185+
OSCAP_REMEDIATE_DATASTREAM_ID=some_ds_id
2186+
OSCAP_REMEDIATE_XCCDF_ID=some_xccdf_id
2187+
OSCAP_REMEDIATE_BENCHMARK_ID=some_bench_id
2188+
2189+
# Tailoring file and tailoring component ID
2190+
OSCAP_REMEDIATE_TAILORING=/some/tailoring.xml
2191+
OSCAP_REMEDIATE_TAILORING_ID=tailoring_id
2192+
2193+
# Where to write ARF result and HTML report
2194+
# No defaults, they won't be generated if
2195+
# they are not requested explicitly
2196+
OSCAP_REMEDIATE_ARF_RESULT=/some/arf_res.xml
2197+
OSCAP_REMEDIATE_HTML_REPORT=/some/report.html
2198+
2199+
# Log file name and verbosity
2200+
OSCAP_REMEDIATE_VERBOSE_LOG=/var/some_verbose.log
2201+
# Optional even if OSCAP_REMEDIATE_VERBOSE_LOG is provided (default: INFO)
2202+
OSCAP_REMEDIATE_VERBOSE_LEVEL=INFO
2203+
----
21622204

21632205
== Frequently Asked Questions (FAQs)
21642206
*Why do I get "notchecked" results when I use e.g. https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_Red_Hat_Enterprise_Linux_7_V2R3_STIG.zip[STIG checklist]?*

openscap.spec

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# This spec file is not synchronized to the Fedora downstream.
22
# It serves as Fedora CI configuration and as support for downstream updates.
33
Name: openscap
4-
Version: 1.3.4
54
Release: 0%{?dist}
5+
Version: 1.3.0
66
Epoch: 1
77
Summary: Set of open source libraries enabling integration of the SCAP line of standards
88
License: LGPLv2+
@@ -151,6 +151,12 @@ pathfix.py -i %{__python3} -p -n $RPM_BUILD_ROOT%{_bindir}/scap-as-rpm
151151

152152
%ldconfig_scriptlets
153153

154+
# enable oscap-remediate-offline.service here for now
155+
# https://github.com/hughsie/PackageKit/issues/401
156+
# https://bugzilla.redhat.com/show_bug.cgi?id=1833176
157+
mkdir -p %{buildroot}%{_unitdir}/system-update.target.wants/
158+
ln -sf ../oscap-remediate.service %{buildroot}%{_unitdir}/system-update.target.wants/oscap-remediate.service
159+
154160
%files
155161
%doc AUTHORS NEWS README.md
156162
%license COPYING
@@ -183,6 +189,9 @@ pathfix.py -i %{__python3} -p -n $RPM_BUILD_ROOT%{_bindir}/scap-as-rpm
183189
%{_bindir}/oscap
184190
%{_bindir}/oscap-chroot
185191
%{_sysconfdir}/bash_completion.d
192+
%{_libexecdir}/oscap-remediate
193+
%{_unitdir}/oscap-remediate.service
194+
%{_unitdir}/system-update.target.wants/
186195

187196
%files utils
188197
%doc docs/oscap-scan.cron

oscap-remediate.service.in

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[Unit]
2+
Description=Scan and remediate the operating system using OpenSCAP scanner in accordance with the selected profile
3+
4+
DefaultDependencies=no
5+
Requires=sysinit.target dbus.socket
6+
After=sysinit.target dbus.socket systemd-journald.socket system-update-pre.target
7+
Before=shutdown.target system-update.target
8+
9+
[Service]
10+
Type=oneshot
11+
ExecStart=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBEXECDIR@/oscap-remediate
12+
13+
FailureAction=reboot

utils/CMakeLists.txt

+12
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ if(ENABLE_OSCAP_UTIL)
5757
COMMENT "Generating chroot-capable oscap buddy"
5858
DEPENDS oscap-chrootable-nocap
5959
)
60+
61+
if(WITH_SYSTEMD)
62+
install(PROGRAMS "oscap-remediate"
63+
DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}
64+
)
65+
install(PROGRAMS "oscap-remediate-offline"
66+
DESTINATION ${CMAKE_INSTALL_BINDIR}
67+
)
68+
install(FILES "oscap-remediate-offline.8"
69+
DESTINATION "${CMAKE_INSTALL_MANDIR}/man8"
70+
)
71+
endif()
6072
endif()
6173
endif()
6274
if(ENABLE_OSCAP_UTIL_CHROOT)

utils/oscap-remediate

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/bin/bash
2+
3+
# SPDX-License-Identifier: GPL-2.0-or-later
4+
# Copyright (C) 2021 Red Hat Inc., Durham, North Carolina.
5+
#
6+
# Evgenii Kolesnikov <[email protected]>
7+
#
8+
# This is OpenSCAP's offline system remediation service wrapper script and
9+
# it is supposed to be executed by systemd. Not sutable for manual invocation.
10+
11+
12+
UCHAR_MAX=255
13+
SYSTEMD_SYSTEM_UPDATE_LINK="/system-update"
14+
OSCAP_REMEDIATE_CONF_BASENAME="oscap-remediate-offline.conf.sh"
15+
16+
17+
display() {
18+
# Plymouth does not like messages longer than UCHAR_MAX
19+
plymouth display-message --text="${1:0:${UCHAR_MAX}}"
20+
}
21+
22+
log() {
23+
echo "${@}"
24+
}
25+
26+
err() {
27+
echo "${@}" >&2
28+
}
29+
30+
die() {
31+
err "${@}"
32+
display "OpenSCAP has failed to evaluate or remediate the system, please check the journal for the details."$'\n'"The system will now restart..."
33+
sleep 5
34+
systemctl reboot
35+
exit 1
36+
}
37+
38+
39+
config=$(readlink -f "${SYSTEMD_SYSTEM_UPDATE_LINK}")
40+
41+
if [[ "$(basename ${config})" != "${OSCAP_REMEDIATE_CONF_BASENAME}" ]]; then
42+
log "The ${SYSTEMD_SYSTEM_UPDATE_LINK} symlink does not point to an oscap offline remediation configuration file, ignoring"
43+
exit 0
44+
fi
45+
46+
rm -f "${SYSTEMD_SYSTEM_UPDATE_LINK}"
47+
log "Removed ${SYSTEMD_SYSTEM_UPDATE_LINK}"
48+
log "Found the config file: ${config}"
49+
50+
source "$config"
51+
52+
53+
log "OSCAP_REMEDIATE_DS: ${OSCAP_REMEDIATE_DS}"
54+
55+
log "OSCAP_REMEDIATE_PROFILE_ID: ${OSCAP_REMEDIATE_PROFILE_ID}"
56+
log "OSCAP_REMEDIATE_DATASTREAM_ID: ${OSCAP_REMEDIATE_DATASTREAM_ID}"
57+
log "OSCAP_REMEDIATE_XCCDF_ID: ${OSCAP_REMEDIATE_XCCDF_ID}"
58+
log "OSCAP_REMEDIATE_BENCHMARK_ID: ${OSCAP_REMEDIATE_BENCHMARK_ID}"
59+
60+
log "OSCAP_REMEDIATE_TAILORING: ${OSCAP_REMEDIATE_TAILORING}"
61+
log "OSCAP_REMEDIATE_TAILORING_ID: ${OSCAP_REMEDIATE_TAILORING_ID}"
62+
63+
log "OSCAP_REMEDIATE_ARF_RESULT: ${OSCAP_REMEDIATE_ARF_RESULT}"
64+
log "OSCAP_REMEDIATE_HTML_REPORT: ${OSCAP_REMEDIATE_HTML_REPORT}"
65+
66+
log "OSCAP_REMEDIATE_VERBOSE_LOG: ${OSCAP_REMEDIATE_VERBOSE_LOG}"
67+
log "OSCAP_REMEDIATE_VERBOSE_LEVEL: ${OSCAP_REMEDIATE_VERBOSE_LEVEL}"
68+
69+
70+
[[ -r "${OSCAP_REMEDIATE_DS}" ]] || {
71+
die "The data stream file does not exists: ${OSCAP_REMEDIATE_DS}"
72+
}
73+
74+
[[ -n "${OSCAP_REMEDIATE_PROFILE_ID}" ]] || {
75+
die "The profile identifier is not defined"
76+
}
77+
78+
[[ -z "${OSCAP_REMEDIATE_TAILORING}" || -r "${OSCAP_REMEDIATE_TAILORING}" ]] || {
79+
die "The tailoring file does not exists: ${OSCAP_REMEDIATE_TAILORING}"
80+
}
81+
82+
oscap xccdf validate --skip-schematron "${OSCAP_REMEDIATE_DS}" || {
83+
die "Invalid data stream file: ${OSCAP_REMEDIATE_DS}"
84+
}
85+
86+
profile=${OSCAP_REMEDIATE_PROFILE_ID}
87+
profile_id_arg="--profile=${OSCAP_REMEDIATE_PROFILE_ID}"
88+
profile_title_line=$(oscap info "${profile_id_arg}" "${OSCAP_REMEDIATE_DS}" | grep Title) || {
89+
die "Can not find the profile: ${profile}"
90+
}
91+
profile_title=${profile_title_line#*Title: }
92+
log "Profile: ${profile} (${profile_title})"
93+
94+
args+=( ${OSCAP_REMEDIATE_VERBOSE_LOG:+"--verbose-log-file=${OSCAP_REMEDIATE_VERBOSE_LOG}"} )
95+
# We don't want to create havok in the output, so no verbose messages unless they are redirected
96+
args+=( ${OSCAP_REMEDIATE_VERBOSE_LOG:+"--verbose=${OSCAP_REMEDIATE_VERBOSE_LEVEL:-INFO}"} )
97+
args+=( "xccdf" )
98+
args+=( "eval" )
99+
args+=( "${profile_id_arg}" )
100+
args+=( ${OSCAP_REMEDIATE_DATASTREAM_ID:+"--datastream-id=${OSCAP_REMEDIATE_DATASTREAM_ID}"} )
101+
args+=( ${OSCAP_REMEDIATE_BENCHMARK_ID:+"--datastream-id=${OSCAP_REMEDIATE_BENCHMARK_ID}"} )
102+
args+=( ${OSCAP_REMEDIATE_XCCDF_ID:+"--xccdf-id=${OSCAP_REMEDIATE_XCCDF_ID}"} )
103+
args+=( ${OSCAP_REMEDIATE_TAILORING:+"--tailoring-file=${OSCAP_REMEDIATE_TAILORING}"} )
104+
args+=( ${OSCAP_REMEDIATE_TAILORING_ID:+"--tailoring-id=${OSCAP_REMEDIATE_TAILORING_ID}"} )
105+
args+=( ${OSCAP_REMEDIATE_ARF_RESULT:+"--results-arf=${OSCAP_REMEDIATE_ARF_RESULT}"} )
106+
args+=( ${OSCAP_REMEDIATE_HTML_REPORT:+"--report=${OSCAP_REMEDIATE_HTML_REPORT}"} )
107+
args+=( "--progress-full" )
108+
args+=( "--remediate" )
109+
args+=( "${OSCAP_REMEDIATE_DS}" )
110+
log "Args: ${args[@]}"
111+
112+
# Now we are good to go
113+
header="OpenSCAP is checking the system for compliance using"$'\n'"${profile_title}"$'\n\n'"Evaluating..."
114+
display "$header"
115+
116+
while read -r line; do
117+
if [[ "${line}" =~ "---evaluation" ]]; then
118+
header="OpenSCAP is checking the system for compliance using"$'\n'"${profile_title}"$'\n\n'
119+
log "Evaluating..."
120+
elif [[ "${line}" =~ "---remediation" ]]; then
121+
header="OpenSCAP is remediating the system using"$'\n'"${profile_title}"$'\n\n'
122+
log "Remediating..."
123+
else
124+
# The line is: "rule_id|Rule Title|result"
125+
IFS="|" read -ra fields <<< "${line}"
126+
mark=$([[ "${fields[2]}" == "pass" ]] && echo "" || echo "")
127+
128+
display "${header}${mark}${fields[1]}"
129+
log "Rule: ${fields[0]} ${fields[2]}"
130+
fi
131+
done < <(oscap "${args[@]}" || {
132+
# Resturn value of 2 and more could mean partial remediation and whatnot,
133+
# no reason to frighten the user in those cases.
134+
if [[ $? == 1 ]]; then
135+
die "Result: 1, processing error"
136+
fi
137+
err "Result: $?"
138+
})
139+
140+
display "The system will now restart..."
141+
log "Done. Rebooting..."
142+
systemctl reboot

0 commit comments

Comments
 (0)