Skip to content

Commit

Permalink
add an bootc-image-builder test and a related podman library
Browse files Browse the repository at this point in the history
Signed-off-by: Jiri Jaburek <[email protected]>
  • Loading branch information
comps committed Oct 24, 2024
1 parent 285be01 commit f8c5c86
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 0 deletions.
14 changes: 14 additions & 0 deletions conf/remediation.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,18 @@ def excludes():
'enable_fips_mode',
]

# RHEL Image Mode
# TODO: revisit these, see which ones we really need for use with Contest
if test_name.startswith('/hardening/container'):
rules += [
'no_direct_root_logins',
'firewalld_sshd_disabled',
'service_sshd_disabled',
'sshd_disable_root_login',
'mount_option_nodev_nonroot_local_partitions',
'enable_fips_mode',
'configure_crypto_policy',
'accounts_tmout',
]

return rules
105 changes: 105 additions & 0 deletions hardening/container/bootc-image-builder/main.fmf
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
114 changes: 114 additions & 0 deletions hardening/container/bootc-image-builder/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/python3

#import contextlib
import shutil
from pathlib import Path

from lib import results, oscap, osbuild, virt, podman, util
from conf import remediation

#
# TODO: specify --root and --storage-opt for all podman commands,
# have ie. /var/lib/containers/contest-storage to not conflict
# with other images (image names) that might be on the system
# while having a storage share-able across tests
#

#podman.Host.setup()
virt.Host.setup()

_, variant, profile = util.get_test_name().rsplit('/', 2)

oscap.unselect_rules(util.get_datastream(), 'remediation-ds.xml', remediation.excludes())


pull_images = [
'quay.io/centos-bootc/centos-bootc:stream9',
'quay.io/centos-bootc/bootc-image-builder:latest',
]
for img in pull_images:
# --quiet because screen-redrawing progress bars don't work well with logs
podman.podman('image', 'pull', '--quiet', img)


# TODO: Containerfile, needs 'podman' module support for 'class Repository'
# using a locally-hosted HTTP server
# TODO: probably use localhost/ for pulled images ?
containerfile_text = util.dedent(fr'''
FROM quay.io/centos-bootc/centos-bootc:stream9
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"]
''')

Path('Containerfile').write_text(containerfile_text)
podman.podman('image', 'build', '--tag', 'bootc-centos-openscap', '.')


guest = virt.Guest()
guest.wipe()
guest.generate_ssh_keypair()

# TODO: probably move this to class Containerfile, managed by the 'podman' module,
# so sshkey insertion is generic across all container-based workflows
blueprint = osbuild.Blueprint(template='')
blueprint.add_user('root', password=virt.GUEST_LOGIN_PASS, ssh_pubkey=guest.ssh_pubkey)

#c = podman.Container('quay.io/centos-bootc/bootc-image-builder:latest')


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)

#with contextlib.ExitStack() as stack:
#with tempfile.NamedTemporaryFile(mode='w', suffix='.toml') as config_toml:
# Path(config_toml).write_text(blueprint

with blueprint.to_tmpfile() as config_toml:
# TODO: maybe refer to pulled images as localhost/ so they don't get re-queried?
# (and drop --pull never)
podman.podman(
'container', 'run',
'--rm',
'--pull', 'never',
'--privileged',
'--security-opt', 'label=type:unconfined_t',
'--volume', f'{config_toml}:/config.toml:ro',
'--volume', f'{bootc_output_dir}:/output',
'--volume', '/var/lib/containers/storage:/var/lib/containers/storage',
'quay.io/centos-bootc/bootc-image-builder:latest',
'build',
'--type', 'qcow2',
'--local',
'localhost/bootc-centos-openscap:latest',
)
# 'quay.io/centos-bootc/centos-bootc:stream9',

# seems to be hardcoded by bootc-image-builder
qcow2_path = bootc_output_dir / 'qcow2' / 'disk.qcow2'

guest.import_image(qcow2_path, 'qcow2')


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'])
16 changes: 16 additions & 0 deletions lib/podman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Provides utilities and wrappers for creating and manipulating images and
containers using the 'podman' utility.
"""

from lib import util


def podman(*args, **kwargs):
"""
A simple wrapper for the podman(1) CLI, passing python arguments
as shell arguments.
"""
# TODO: make subprocess_run able to pass skip_frames to underlying calls,
# and use it here, to print out our caller, not podman.podman()
util.subprocess_run(['podman', *args], check=True, universal_newlines=True, **kwargs)

0 comments on commit f8c5c86

Please sign in to comment.