stages: add oscap-remediation stage

Add a new stage to handle openscap first boot
remediation. The openscap-remediation.service
looks for a `/system-update` symlink which
points to an openscap config file. This stage
creates both the necessary configuration and
the `/system-update` symlink.
This commit is contained in:
Gianluca Zuccarelli 2022-05-06 12:18:59 +01:00 committed by Christian Kellner
parent ce9253788e
commit 66d2cf6df5

View file

@ -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)