-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add an bootc-image-builder test and a related podman library
Signed-off-by: Jiri Jaburek <[email protected]>
- Loading branch information
Showing
6 changed files
with
508 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
summary: Creates an OS using Anaconda's ostreecontainer and scans it | ||
test: python3 -m lib.runtest ./test.py | ||
result: custom | ||
environment+: | ||
PYTHONPATH: ../../.. | ||
duration: 1h | ||
require+: | ||
# virt library dependencies | ||
- libvirt-daemon | ||
- libvirt-daemon-driver-qemu | ||
- libvirt-daemon-driver-storage-core | ||
- libvirt-daemon-driver-network | ||
- firewalld | ||
- qemu-kvm | ||
- libvirt-client | ||
- virt-install | ||
- rpm-build | ||
- createrepo | ||
# podman library dependencies | ||
- podman | ||
extra-hardware: | | ||
keyvalue = HVM=1 | ||
hostrequire = memory>=3720 | ||
adjust: | ||
- when: arch != x86_64 | ||
enabled: false | ||
because: we want to run virtualization on x86_64 only | ||
- when: distro ~< rhel-8.10 or distro ~< rhel-9.5 | ||
enabled: false | ||
because: TODO - what is ostreecontainer supported on? | ||
|
||
/anssi_bp28_high: | ||
|
||
/anssi_bp28_enhanced: | ||
tag+: | ||
- subset-profile | ||
|
||
/anssi_bp28_intermediary: | ||
tag+: | ||
- subset-profile | ||
|
||
/anssi_bp28_minimal: | ||
tag+: | ||
- subset-profile | ||
|
||
/cis: | ||
|
||
/cis_server_l1: | ||
tag+: | ||
- subset-profile | ||
|
||
/cis_workstation_l2: | ||
|
||
/cis_workstation_l1: | ||
tag+: | ||
- subset-profile | ||
|
||
/cui: | ||
adjust+: | ||
- when: distro >= rhel-10 | ||
enabled: false | ||
because: there is no CUI profile on RHEL-10+ | ||
|
||
/e8: | ||
|
||
/hipaa: | ||
|
||
/ism_o: | ||
|
||
/ospp: | ||
adjust+: | ||
- when: distro >= rhel-10 | ||
enabled: false | ||
because: there is no OSPP profile on RHEL-10+ | ||
|
||
/pci-dss: | ||
|
||
/stig: | ||
|
||
/stig_gui: | ||
adjust+: | ||
- enabled: false | ||
because: not supported without GUI, use stig instead | ||
|
||
/ccn_advanced: | ||
adjust+: | ||
- when: distro == rhel-8 or distro == rhel-10 | ||
enabled: false | ||
because: CCN profiles are not present on RHEL-8 and on RHEL-10 | ||
|
||
/ccn_intermediate: | ||
tag+: | ||
- subset-profile | ||
adjust+: | ||
- when: distro == rhel-8 or distro == rhel-10 | ||
enabled: false | ||
because: CCN profiles are not present on RHEL-8 and on RHEL-10 | ||
|
||
/ccn_basic: | ||
tag+: | ||
- subset-profile | ||
adjust+: | ||
- when: distro == rhel-8 or distro == rhel-10 | ||
enabled: false | ||
because: CCN profiles are not present on RHEL-8 and on RHEL-10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#!/usr/bin/python3 | ||
|
||
from lib import results, oscap, versions, virt, podman, util | ||
from conf import remediation | ||
|
||
|
||
virt.Host.setup() | ||
|
||
_, variant, profile = util.get_test_name().rsplit('/', 2) | ||
|
||
oscap.unselect_rules(util.get_datastream(), 'remediation-ds.xml', remediation.excludes()) | ||
|
||
# CentOS Stream image only, for now | ||
src_image = f'quay.io/centos-bootc/centos-bootc:stream{versions.rhel.major}' | ||
|
||
guest = virt.Guest() | ||
guest.generate_ssh_keypair() | ||
|
||
# prepare a Container file for making a hardened image | ||
cfile = podman.Containerfile() | ||
cfile += util.dedent(fr''' | ||
FROM {src_image} | ||
RUN dnf -y install dnf-plugins-core | ||
RUN dnf -y copr enable packit/OpenSCAP-openscap-2170 centos-stream-9-x86_64 | ||
RUN dnf -y install openscap-utils | ||
COPY remediation-ds.xml /root/. | ||
RUN oscap-bootc --profile '{profile}' /root/remediation-ds.xml | ||
''') | ||
cfile.add_ssh_pubkey(guest.ssh_pubkey) | ||
cfile.write_to('Containerfile') | ||
|
||
podman.podman('image', 'build', '--tag', 'contest-hardened', '.') | ||
|
||
ks = virt.Kickstart() | ||
|
||
# install the VM, using a locally-hosted podman registry serving | ||
# the hardened image for Anaconda's ostreecontainer | ||
with podman.Registry(host_addr=virt.NETWORK_HOST) as registry: | ||
image_url = registry.push('contest-hardened') | ||
ks.append(f'ostreecontainer --no-signature-verification --url {image_url}') | ||
# TODO: temporary hack because Anaconda doesn't expose --insecure-skip-tls-verification | ||
raddr, rport = registry.get_listen_addr() | ||
ks.add_pre( | ||
fr'''echo -e '[[registry]]\nlocation = "{raddr}:{rport}"\n''' | ||
r'''insecure = true\n' >> /etc/containers/registries.conf''' | ||
) | ||
# TODO: temporary hack end | ||
guest.install_basic(kickstart=ks) | ||
|
||
# boot up and scan the VM | ||
with guest.booted(): | ||
# copy the original DS to the guest | ||
guest.copy_to(util.get_datastream(), 'scan-ds.xml') | ||
# scan the remediated system | ||
proc, lines = guest.ssh_stream( | ||
f'oscap xccdf eval --profile {profile} --progress --report report.html' | ||
f' --results-arf results-arf.xml scan-ds.xml' | ||
) | ||
oscap.report_from_verbose(lines) | ||
if proc.returncode not in [0,2]: | ||
raise RuntimeError("post-reboot oscap failed unexpectedly") | ||
|
||
guest.copy_from('report.html') | ||
guest.copy_from('results-arf.xml') | ||
|
||
util.subprocess_run(['gzip', '-9', 'results-arf.xml'], check=True) | ||
|
||
results.report_and_exit(logs=['report.html', 'results-arf.xml.gz']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
summary: Runs bootc-image-builder remediation and scan inside VMs | ||
test: python3 -m lib.runtest ./test.py | ||
result: custom | ||
environment+: | ||
PYTHONPATH: ../../.. | ||
duration: 1h | ||
require+: | ||
# virt library dependencies | ||
- libvirt-daemon | ||
- libvirt-daemon-driver-qemu | ||
- libvirt-daemon-driver-storage-core | ||
- libvirt-daemon-driver-network | ||
- firewalld | ||
- qemu-kvm | ||
- libvirt-client | ||
- virt-install | ||
- rpm-build | ||
- createrepo | ||
# podman library dependencies | ||
- podman | ||
extra-hardware: | | ||
keyvalue = HVM=1 | ||
hostrequire = memory>=3720 | ||
adjust: | ||
- when: arch != x86_64 | ||
enabled: false | ||
because: we want to run virtualization on x86_64 only | ||
- when: distro ~< rhel-8.10 or distro ~< rhel-9.5 | ||
enabled: false | ||
because: TODO - what is bootc supported on? | ||
|
||
/anssi_bp28_high: | ||
|
||
/anssi_bp28_enhanced: | ||
tag+: | ||
- subset-profile | ||
|
||
/anssi_bp28_intermediary: | ||
tag+: | ||
- subset-profile | ||
|
||
/anssi_bp28_minimal: | ||
tag+: | ||
- subset-profile | ||
|
||
/cis: | ||
|
||
/cis_server_l1: | ||
tag+: | ||
- subset-profile | ||
|
||
/cis_workstation_l2: | ||
|
||
/cis_workstation_l1: | ||
tag+: | ||
- subset-profile | ||
|
||
/cui: | ||
adjust+: | ||
- when: distro >= rhel-10 | ||
enabled: false | ||
because: there is no CUI profile on RHEL-10+ | ||
|
||
/e8: | ||
|
||
/hipaa: | ||
|
||
/ism_o: | ||
|
||
/ospp: | ||
adjust+: | ||
- when: distro >= rhel-10 | ||
enabled: false | ||
because: there is no OSPP profile on RHEL-10+ | ||
|
||
/pci-dss: | ||
|
||
/stig: | ||
|
||
/stig_gui: | ||
adjust+: | ||
- enabled: false | ||
because: not supported without GUI, use stig instead | ||
|
||
/ccn_advanced: | ||
adjust+: | ||
- when: distro == rhel-8 or distro == rhel-10 | ||
enabled: false | ||
because: CCN profiles are not present on RHEL-8 and on RHEL-10 | ||
|
||
/ccn_intermediate: | ||
tag+: | ||
- subset-profile | ||
adjust+: | ||
- when: distro == rhel-8 or distro == rhel-10 | ||
enabled: false | ||
because: CCN profiles are not present on RHEL-8 and on RHEL-10 | ||
|
||
/ccn_basic: | ||
tag+: | ||
- subset-profile | ||
adjust+: | ||
- when: distro == rhel-8 or distro == rhel-10 | ||
enabled: false | ||
because: CCN profiles are not present on RHEL-8 and on RHEL-10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#!/usr/bin/python3 | ||
|
||
import shutil | ||
from pathlib import Path | ||
|
||
from lib import results, oscap, versions, virt, podman, util | ||
from conf import remediation | ||
|
||
|
||
virt.Host.setup() | ||
|
||
_, variant, profile = util.get_test_name().rsplit('/', 2) | ||
|
||
oscap.unselect_rules(util.get_datastream(), 'remediation-ds.xml', remediation.excludes()) | ||
|
||
# CentOS Stream image only, for now | ||
src_image = f'quay.io/centos-bootc/centos-bootc:stream{versions.rhel.major}' | ||
|
||
# note that the .wipe() is necessary here, as we are not calling any .install() | ||
# function that would normally perform it | ||
guest = virt.Guest() | ||
guest.wipe() | ||
guest.generate_ssh_keypair() | ||
|
||
# prepare a Container file for making a hardened image | ||
cfile = podman.Containerfile() | ||
cfile += util.dedent(fr''' | ||
FROM {src_image} | ||
RUN dnf -y install dnf-plugins-core | ||
RUN dnf -y copr enable packit/OpenSCAP-openscap-2170 centos-stream-9-x86_64 | ||
RUN dnf -y install openscap-utils | ||
COPY remediation-ds.xml /root/. | ||
RUN oscap-bootc --profile '{profile}' /root/remediation-ds.xml | ||
''') | ||
cfile.add_ssh_pubkey(guest.ssh_pubkey) | ||
cfile.write_to('Containerfile') | ||
|
||
podman.podman('image', 'build', '--tag', 'contest-hardened', '.') | ||
|
||
# pre-create a directory (inside GUEST_IMG_DIR) for storing the | ||
# hardened image, built by bootc-image-builder | ||
bootc_output_dir = Path(virt.GUEST_IMG_DIR) / 'bootc-image-builder-output' | ||
if bootc_output_dir.exists(): | ||
shutil.rmtree(bootc_output_dir) | ||
bootc_output_dir.mkdir(parents=True) | ||
|
||
# build the hardened image using a containerized builder, | ||
podman.podman( | ||
'container', 'run', | ||
'--rm', | ||
'--privileged', | ||
'--security-opt', 'label=type:unconfined_t', | ||
'--volume', f'{bootc_output_dir}:/output', | ||
'--volume', '/var/lib/containers/storage:/var/lib/containers/storage', | ||
'quay.io/centos-bootc/bootc-image-builder', | ||
# arguments for the builder itself | ||
'build', | ||
'--type', 'qcow2', | ||
'--local', | ||
# 'localhost/' prefix tells the builder to just use local image storage | ||
'localhost/contest-hardened', | ||
) | ||
|
||
# path inside the output dir seems to be hardcoded in bootc-image-builder | ||
qcow2_path = bootc_output_dir / 'qcow2' / 'disk.qcow2' | ||
guest.import_image(qcow2_path, 'qcow2') | ||
|
||
# boot up and scan the VM | ||
with guest.booted(): | ||
# copy the original DS to the guest | ||
guest.copy_to(util.get_datastream(), 'scan-ds.xml') | ||
# scan the remediated system | ||
proc, lines = guest.ssh_stream( | ||
f'oscap xccdf eval --profile {profile} --progress --report report.html' | ||
f' --results-arf results-arf.xml scan-ds.xml' | ||
) | ||
oscap.report_from_verbose(lines) | ||
if proc.returncode not in [0,2]: | ||
raise RuntimeError("post-reboot oscap failed unexpectedly") | ||
|
||
guest.copy_from('report.html') | ||
guest.copy_from('results-arf.xml') | ||
|
||
util.subprocess_run(['gzip', '-9', 'results-arf.xml'], check=True) | ||
|
||
results.report_and_exit(logs=['report.html', 'results-arf.xml.gz']) |
Oops, something went wrong.