Skip to content

Commit c15d254

Browse files
authored
Merge pull request #14625 from jan-cerny/hummingbird_remediations
Add hummingbird remediations type
2 parents af93f70 + ad92b52 commit c15d254

File tree

12 files changed

+722
-21
lines changed

12 files changed

+722
-21
lines changed

build-scripts/generate_profile_remediations.py

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
import collections
55
import os
66
import re
7+
import textwrap
78
import xml.etree.ElementTree as ET
8-
import yaml
99

1010
import ssg.ansible
1111
import ssg.yaml
1212
from ssg.constants import (
1313
ansible_system,
1414
bash_system,
15+
hummingbird_system,
1516
datastream_namespace,
1617
OSCAP_PROFILE,
1718
OSCAP_RULE,
@@ -21,9 +22,12 @@
2122

2223
DEFAULT_SELECTOR = "__DEFAULT"
2324
HASH_ROW = "#" * 79
24-
LANGUAGE_TO_SYSTEM = {"ansible": ansible_system, "bash": bash_system}
25-
LANGUAGE_TO_TARGET = {"ansible": "playbook", "bash": "script"}
26-
LANGUAGE_TO_EXTENSION = {"ansible": "yml", "bash": "sh"}
25+
LANGUAGE_TO_SYSTEM = {
26+
"ansible": ansible_system,
27+
"bash": bash_system,
28+
"hummingbird": hummingbird_system}
29+
LANGUAGE_TO_TARGET = {"ansible": "playbook", "bash": "script", "hummingbird": "script"}
30+
LANGUAGE_TO_EXTENSION = {"ansible": "yml", "bash": "sh", "hummingbird": "sh"}
2731
ANSIBLE_VAR_PATTERN = re.compile(
2832
"- name: XCCDF Value [^ ]+ # promote to variable\n set_fact:\n"
2933
" ([^:]+): !!str (.+)\n tags:\n - always\n")
@@ -44,8 +48,8 @@ def parse_args():
4448
help="Product ID, eg. 'rhel9'"
4549
)
4650
parser.add_argument(
47-
"--language", required=True, choices=["bash", "ansible"],
48-
help="Remediation language, either 'bash' or 'ansible'"
51+
"--language", required=True, choices=["bash", "ansible", "hummingbird"],
52+
help="Remediation language, either 'bash' or 'ansible' or 'hummingbird'"
4953
)
5054
args = parser.parse_args()
5155
return args
@@ -125,7 +129,7 @@ def get_variable_values(variable):
125129
if selector is None:
126130
selector = DEFAULT_SELECTOR
127131
if value.text is None:
128-
values["selector"] = ""
132+
values[selector] = ""
129133
else:
130134
values[selector] = value.text
131135
return values
@@ -206,8 +210,10 @@ def load_all_remediations(self, benchmark):
206210
def generate_profile_remediation_script(self, profile_el):
207211
if self.language == "ansible":
208212
output = self.create_output_ansible(profile_el)
209-
elif self.language == "bash":
210-
output = self.create_output_bash(profile_el)
213+
elif self.language in ("bash", "hummingbird"):
214+
output = self.create_output_linear(profile_el)
215+
else:
216+
raise ValueError("Unknown language %s" % self.language)
211217
file_path = self.get_output_file_path(profile_el)
212218
with open(file_path, "wb") as f:
213219
f.write(output.encode("utf-8"))
@@ -254,20 +260,38 @@ def collect_ansible_vars_and_tasks(self, profile_el):
254260
all_tasks.extend(rule_tasks)
255261
return (all_vars, all_tasks)
256262

257-
def create_output_bash(self, profile):
263+
def create_output_linear(self, profile):
258264
output = []
259265
selected_rules = get_selected_rules(profile)
260266
refinements = get_value_refinenements(profile)
261267
header = self.create_header(profile)
262268
output.append(header)
263269
total = len(selected_rules)
270+
if self.language == "hummingbird":
271+
newroot_assign = textwrap.dedent(
272+
"""
273+
# The first argument is the root directory of the system
274+
NEWROOT="$1"
275+
if [[ -z "$NEWROOT" ]] ; then
276+
echo "Missing required NEWROOT argument" >&2
277+
exit 1
278+
fi
279+
"""
280+
)
281+
output.append(newroot_assign)
264282
current = 1
265283
for rule_id in self.remediations:
266284
if rule_id not in selected_rules:
267285
continue
268286
status = (current, total)
269-
rule_remediation = self.generate_bash_rule_remediation(
270-
rule_id, status, refinements)
287+
if self.language == "bash":
288+
rule_remediation = self.generate_bash_rule_remediation(
289+
rule_id, status, refinements)
290+
elif self.language == "hummingbird":
291+
rule_remediation = self.generate_hummingbird_rule_remediation(
292+
rule_id, refinements)
293+
else:
294+
raise ValueError("Unknown language %s" % self.language)
271295
output.append(rule_remediation)
272296
current += 1
273297
return "".join(output)
@@ -286,11 +310,26 @@ def create_header(self, profile):
286310
shebang_with_newline = "#!/usr/bin/env bash\n"
287311
remediation_type = "Bash Remediation Script"
288312
how_to_apply = "# $ sudo ./remediation-script.sh\n"
313+
elif self.language == "hummingbird":
314+
shebang_with_newline = "#!/usr/bin/env bash\n"
315+
remediation_type = (
316+
"Bash Remediation Script for building Project Hummingbird "
317+
"container images")
318+
how_to_apply = "# RUN remediation-script.sh ${NEWROOT}\n"
319+
else:
320+
raise ValueError("Unknown language %s" % self.language)
289321
profile_title = profile.find("./{%s}title" % XCCDF12_NS).text
290322
description = profile.find("./{%s}description" % XCCDF12_NS).text
291323
commented_profile_description = comment(description)
292324
xccdf_version_name = "1.2"
293325
profile_id = profile.get("id")
326+
if self.language == "bash":
327+
generation_text = (
328+
"# This file can be generated by OpenSCAP using:\n"
329+
"# $ oscap xccdf generate fix --profile %s --fix-type %s %s\n"
330+
"#\n" % (profile_id, self.language, self.ds_file_name))
331+
else:
332+
generation_text = ""
294333
fix_header = (
295334
"%s"
296335
"%s\n"
@@ -305,9 +344,7 @@ def create_header(self, profile):
305344
"# Benchmark Version: %s\n"
306345
"# XCCDF Version: %s\n"
307346
"#\n"
308-
"# This file can be generated by OpenSCAP using:\n"
309-
"# $ oscap xccdf generate fix --profile %s --fix-type %s %s\n"
310-
"#\n"
347+
"%s"
311348
"# This %s is generated from an XCCDF profile without"
312349
" preliminary evaluation.\n"
313350
"# It attempts to fix every selected rule, even if the system is"
@@ -320,7 +357,7 @@ def create_header(self, profile):
320357
shebang_with_newline, HASH_ROW, remediation_type,
321358
profile_title, commented_profile_description, profile_id,
322359
self.benchmark_id, self.benchmark_version, xccdf_version_name,
323-
profile_id, self.language, self.ds_file_name,
360+
generation_text,
324361
remediation_type, remediation_type, how_to_apply, HASH_ROW))
325362
return fix_header
326363

@@ -351,6 +388,29 @@ def generate_bash_rule_remediation(self, rule_id, status, refinements):
351388
output.append(end_msg)
352389
return "".join(output)
353390

391+
def generate_hummingbird_rule_remediation(self, rule_id, refinements):
392+
fix_el = self.remediations[rule_id]
393+
if fix_el is None:
394+
return ""
395+
expanded_remediation = expand_variables(
396+
fix_el, refinements, self.variables)
397+
# For Hummingbird we intentionally don't add any warning if the
398+
# rule Hummingbird remediation is missing because we expect that
399+
# it will be normal that most of rules won't have any Hummingbird
400+
# remediation
401+
if expanded_remediation is None:
402+
return ""
403+
output = []
404+
header = (
405+
"%s\n"
406+
"# BEGIN fix for '%s'\n"
407+
"%s\n" % (HASH_ROW, rule_id, HASH_ROW))
408+
output.append(header)
409+
output.append(expanded_remediation)
410+
end_msg = "\n# END fix for '%s'\n\n" % (rule_id)
411+
output.append(end_msg)
412+
return "".join(output)
413+
354414
def generate_ansible_rule_remediation(self, fix_el, refinements):
355415
rule_vars = {}
356416
tasks = []

cmake/SSGCommon.cmake

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ macro(ssg_build_ansible_playbooks PRODUCT)
258258
endmacro()
259259

260260
macro(ssg_build_remediations PRODUCT)
261-
message(STATUS "Scanning for dependencies of ${PRODUCT} fixes (bash, ansible, puppet, anaconda, ignition, kubernetes and blueprint)...")
261+
message(STATUS "Scanning for dependencies of ${PRODUCT} fixes (${PRODUCT_REMEDIATION_LANGUAGES})...")
262262

263263
ssg_collect_remediations(${PRODUCT} "${PRODUCT_REMEDIATION_LANGUAGES}")
264264

@@ -575,6 +575,20 @@ macro(ssg_build_profile_bash_scripts PRODUCT)
575575
DEPENDS "${CMAKE_BINARY_DIR}/bash/all-profile-bash-scripts-${PRODUCT}"
576576
)
577577
endmacro()
578+
macro(ssg_build_profile_hummingbird_scripts PRODUCT)
579+
add_custom_command(
580+
OUTPUT "${CMAKE_BINARY_DIR}/hummingbird_scripts/all-profile-hummingbird-scripts-${PRODUCT}"
581+
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/hummingbird_scripts"
582+
COMMAND env "PYTHONPATH=$ENV{PYTHONPATH}" "${Python_EXECUTABLE}" "${SSG_BUILD_SCRIPTS}/generate_profile_remediations.py" --language hummingbird --data-stream "${CMAKE_BINARY_DIR}/ssg-${PRODUCT}-ds.xml" --output-dir "${CMAKE_BINARY_DIR}/hummingbird_scripts" --product "${PRODUCT}"
583+
COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_BINARY_DIR}/hummingbird_scripts/all-profile-hummingbird-scripts-${PRODUCT}"
584+
DEPENDS generate-ssg-${PRODUCT}-ds.xml "${CMAKE_BINARY_DIR}/ssg-${PRODUCT}-ds.xml"
585+
COMMENT "[${PRODUCT}-hummingbird-scripts] generating hummingbird remediation scripts for all profiles in ssg-${PRODUCT}-ds.xml"
586+
)
587+
add_custom_target(
588+
generate-all-profile-hummingbird-scripts-${PRODUCT}
589+
DEPENDS "${CMAKE_BINARY_DIR}/hummingbird_scripts/all-profile-hummingbird-scripts-${PRODUCT}"
590+
)
591+
endmacro()
578592

579593
# Build per-profile Ansible remediation scripts that can be used independently
580594
# of OpenSCAP execution.
@@ -784,6 +798,16 @@ macro(ssg_build_product PRODUCT)
784798
add_dependencies(zipfile ${PRODUCT}-profile-bash-scripts)
785799
endif()
786800

801+
if("${PRODUCT_HUMMINGBIRD_REMEDIATION_ENABLED}")
802+
ssg_build_profile_hummingbird_scripts(${PRODUCT})
803+
add_custom_target(
804+
${PRODUCT}-profile-hummingbird-scripts
805+
DEPENDS generate-all-profile-hummingbird-scripts-${PRODUCT} "${CMAKE_BINARY_DIR}/hummingbird_scripts/all-profile-hummingbird-scripts-${PRODUCT}"
806+
)
807+
add_dependencies(${PRODUCT} ${PRODUCT}-profile-hummingbird-scripts)
808+
add_dependencies(zipfile ${PRODUCT}-profile-hummingbird-scripts)
809+
endif()
810+
787811
ssg_build_html_guides(${PRODUCT})
788812

789813
add_custom_target(

docs/manual/developer/06_contributing_with_content.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,8 @@ then contain the following subdirectories:
518518
519519
- `bootc` - for remediation content used in the `oscap-im` tool internally, ending in `.bo`
520520
521+
- `hummingbird` - for remediation content used during the build of Project Hummingbird container images, ending in `.sh`
522+
521523
In each of these subdirectories, a file named `shared.ext` will apply to
522524
all products and be included in all builds, but `{{{ product }}}.ext`
523525
will only get included in the build for `{{{ product }}}` (e.g.,

docs/manual/developer/07_understanding_build_system.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ refer to their help text for more information and usage:
114114
framework) to expand Jinja in test scripts.
115115
- `generate_guides.py` -- Generate HTML guides and HTML index for every profile in the built SCAP source data stream.
116116
- `generate_man_page.py` -- generates the ComplianceAsCode man page.
117-
- `generate_profile_remediations.py` -- Generate profile oriented Bash remediation scripts or profile oriented Ansible Playbooks from the built SCAP source data stream. The output is similar to the output of the `oscap xccdf generate fix` command, but the tool `generate_profile_remediations.py` generates the scripts or Playbooks for all profiles in the given SCAP source data stream at once.
117+
- `generate_profile_remediations.py` -- Generate profile oriented Bash remediations (Bash scripts or Ansible Playbooks or Bash scripts for Hummingbird) from the built SCAP source data stream. The output is similar to the output of the `oscap xccdf generate fix` command, but the tool `generate_profile_remediations.py` generates the output for all profiles in the given SCAP source data stream at once.
118118
- `profile_tool.py` -- utility script to generate statistics about profiles
119119
in a specific XCCDF/data stream file.
120120
- `verify_references.py` -- used by the test system to verify cross-linkage
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# platform = multi_platform_all
2+
find "$NEWROOT" -type f -name "shosts.equiv" -exec rm -f {} \;

products/hummingbird/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
33
message(FATAL_ERROR "cmake has to be used on the root CMakeLists.txt, see the Building ComplianceAsCode section in the Developer Guide!")
44
endif()
55

6+
set(PRODUCT_REMEDIATION_LANGUAGES "hummingbird")
7+
68
ssg_build_product("hummingbird")

ssg/build_remediations.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
'kubernetes': '.yml',
2727
'blueprint': '.toml',
2828
'kickstart': '.cfg',
29-
'bootc': '.bo'
29+
'bootc': '.bo',
30+
'hummingbird': '.sh',
3031
}
3132

3233

@@ -515,6 +516,15 @@ def __init__(self, file_path):
515516
file_path, "bootc")
516517

517518

519+
class HummingbirdRemediation(Remediation):
520+
"""
521+
This provides class for Hummingbird remediations
522+
"""
523+
def __init__(self, file_path):
524+
super(HummingbirdRemediation, self).__init__(
525+
file_path, "hummingbird")
526+
527+
518528
REMEDIATION_TO_CLASS = {
519529
'anaconda': AnacondaRemediation,
520530
'ansible': AnsibleRemediation,
@@ -525,6 +535,7 @@ def __init__(self, file_path):
525535
'blueprint': BlueprintRemediation,
526536
'kickstart': KickstartRemediation,
527537
'bootc': BootcRemediation,
538+
'hummingbird': HummingbirdRemediation,
528539
}
529540

530541

@@ -666,6 +677,8 @@ def expand_xccdf_subs(fix, remediation_type):
666677
pattern = r'\(kickstart-populate\s*(\S+)\)'
667678
elif remediation_type == "bootc":
668679
pattern = r'\(bootc-populate\s*(\S+)\)'
680+
elif remediation_type == "hummingbird":
681+
pattern = r'\(bash-populate\s*(\S+)\)'
669682
else:
670683
sys.stderr.write("Unknown remediation type '%s'\n" % (remediation_type))
671684
sys.exit(1)

ssg/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
anaconda_system = "urn:redhat:anaconda:pre"
9090
kickstart_system = "urn:xccdf:fix:script:kickstart"
9191
bootc_system = "urn:xccdf:fix:script:bootc"
92+
hummingbird_system = "urn:xccdf:fix:script:hummingbird"
9293
cce_uri = "https://ncp.nist.gov/cce"
9394
stig_ns = "https://www.cyber.mil/stigs/srg-stig-tools/"
9495
ccn_ns = "https://www.ccn-cert.cni.es/pdf/guias/series-ccn-stic/guias-de-acceso-publico-ccn-stic/6768-ccn-stic-610a22-perfilado-de-seguridad-red-hat-enterprise-linux-9-0/file.html"
@@ -157,6 +158,7 @@
157158
"anaconda": anaconda_system,
158159
"kickstart": kickstart_system,
159160
"bootc": bootc_system,
161+
"hummingbird": hummingbird_system,
160162
}
161163

162164
for prefix, url_part in OVAL_SUB_NS.items():

ssg/templates.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"puppet": TemplatingLang("puppet", ".pp", TemplateType.REMEDIATION, "puppet"),
3636
"sce-bash": TemplatingLang("sce-bash", ".sh", TemplateType.CHECK, "sce"),
3737
"kickstart": TemplatingLang("kickstart", ".cfg", TemplateType.REMEDIATION, "kickstart"),
38-
"bootc": TemplatingLang("bootc", ".bo", TemplateType.REMEDIATION, "bootc")
38+
"bootc": TemplatingLang("bootc", ".bo", TemplateType.REMEDIATION, "bootc"),
39+
"hummingbird": TemplatingLang("hummingbird", ".sh", TemplateType.REMEDIATION, "hummingbird")
3940
}
4041

4142
PREPROCESSING_FILE_NAME = "template.py"

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ endmacro()
2222
if(PY_PYTEST)
2323
ssg_python_unit_tests("utils" "utils" "oscal/")
2424
ssg_python_unit_tests("ssg-module" "." "")
25+
ssg_python_unit_tests("build-scripts" "." "")
2526
ssg_python_unit_tests("ssg_test_suite" "tests" "")
2627
if(Python_VERSION_MAJOR GREATER 2 AND Python_VERSION_MINOR GREATER 7 AND PY_TRESTLE AND PY_LXML)
2728
ssg_python_unit_tests("utils/oscal" "utils/oscal" "")

0 commit comments

Comments
 (0)