diff --git a/stages/org.osbuild.oscap.remediation b/stages/org.osbuild.oscap.remediation new file mode 100755 index 00000000..c5e9c520 --- /dev/null +++ b/stages/org.osbuild.oscap.remediation @@ -0,0 +1,127 @@ +#!/usr/bin/python3 +""" +Execute oscap remediation on first boot + +The OpenSCAP scanner package uses systemd's `system-update.target` to run an OpenSCAP +remediation at first boot[1]. The `openscap-remediate.service` expects a `/system-update` +symlink that points to an OpenSCAP offline remediation config file. The requirement +of the config file is to have the basename of `oscap-remediate-offline.conf.sh` and +must be accessible at boot time. + +This stage generates the OpenSCAP offline remediation config file with the required +configurations. A `/system-update` symlink is then created which points to the +config file. + +Once executed, the symlink will be removed to avoid an invocation loop. The oscap +offline remediation configuration will be left intact. + +[1] https://github.com/OpenSCAP/openscap/blob/maint-1.3/docs/manual/manual.adoc +""" + + +import os +import sys + +import osbuild.api + + +SCHEMA = """ +"additionalProperties": false, +"required": ["filename", "config"], +"properties": { + "filename": { + "type": "string", + "description": "Filename and location where the OpenSCAP remediation config should be saved" + }, + "config": { + "additionalProperties": false, + "required": ["profile_id", "datastream"], + "type": "object", + "description": "OpenSCAP configuration variables", + "properties": { + "profile_id": { + "type": "string", + "description": "The scap profile id" + }, + "datastream": { + "type": "string", + "description": "The path to the datastream file" + }, + "datastream_id": { + "type": "string", + "description": "The datastream id" + }, + "xccdf_id": { + "type": "string", + "description": "The xccdf id" + }, + "benchmark_id": { + "type": "string", + "description": "The benchmark id" + }, + "tailoring": { + "type": "string", + "description": "The path to the custom tailoring file" + }, + "tailoring_id": { + "type": "string", + "description": "The tailoring id" + }, + "arf_result": { + "type": "string", + "description": "Filename and path for saving the arf results" + }, + "html_report": { + "type": "string", + "description": "Filename and path for saving the html report" + }, + "verbose_log": { + "type": "string", + "description": "Filename and path for verbose error messages" + }, + "verbose_level": { + "type": "string", + "enum": ["DEVEL", "INFO", "ERROR", "WARNING"], + "description": "The verbosity level for the log messages" + } + } + } +} +""" + +# Map containing the translations between +# our configuration names and the OpenScap +# configuration names. +OSCAP_OPTIONS = { + "datastream" : "OSCAP_REMEDIATE_DS", + "profile_id" : "OSCAP_REMEDIATE_PROFILE_ID", + "datastream_id" : "OSCAP_REMEDIATE_DATASTREAM_ID", + "xccdf_id" : "OSCAP_REMEDIATE_XCCDF_ID", + "benchmark_id" : "OSCAP_REMEDIATE_BENCHMARK_ID", + "tailoring" : "OSCAP_REMEDIATE_TAILORING", + "tailoring_id" : "OSCAP_REMEDIATE_TAILORING_ID", + "arf_result" : "OSCAP_REMEDIATE_ARF_RESULT", + "html_report" : "OSCAP_REMEDIATE_HTML_REPORT", + "verbose_log" : "OSCAP_REMEDIATE_VERBOSE_LOG", + "verbose_level" : "OSCAP_REMEDIATE_VERBOSE_LEVEL", +} + +def main(tree, options): + filename = options["filename"] + config = options["config"] + + with open(f"{tree}/{filename}", "w", encoding="utf-8") as f: + for ours, theirs in OSCAP_OPTIONS.items(): + value = config.get(ours) + if value: + f.write(f"{theirs}={value}\n") + + os.symlink(f"{filename}", f"{tree}/system-update") + + return 0 + + +if __name__ == "__main__": + args = osbuild.api.arguments() + r = main(args["tree"], args["options"]) + sys.exit(r)