STIG Hardening for Rocky Linux 9 Kolla Hypervisors

Apply the DISA STIG profile to a Rocky Linux 9 host, deploy Kolla-Ansible 2025.1 with OVN on it, and keep FIPS mode and SELinux enforcing. Tested on one virtual machine with documented exceptions.

This guide hardens a Rocky Linux 9 host with the DISA STIG profile from the SCAP Security Guide, deploys Kolla-Ansible 2025.1 (Epoxy) on it, and records the few STIG rules that conflict with Kolla, OVN and containers.

Scope as tested (2026-10-21): one Amazon EC2 virtual machine running Rocky Linux 9.8 (8 vCPU, 30 GiB), with an all-in-one Kolla-Ansible 2025.1 deployment using ML2/OVN. Nested virtualization was not available, so Nova used virt_type: qemu software emulation. No GPU and no PCI passthrough. Single node only: no multi-node, HA or TLS, and Cinder, Heat and Horizon were disabled. Anything outside this scope is not yet tested.

Results

Stage XCCDF score Pass Fail
Baseline (stock Rocky 9 image) 42.33 164 259
Hardened (STIG playbook, reboot, FIPS on) 82.49 403 24
Final (Kolla deployed, exceptions in place) 81.07 398 29

The final scan adds 5 failing rules, all caused by Kolla and its containers (exceptions E1, E2 and E6 below). The other 24 failures were already present after remediation and are not caused by Kolla. Most are facts of the cloud image: a single root partition, no GRUB password, GRUB kernel arguments that did not take effect, the vendor-support check and SSSD certificate mapping. They also include sudo_remove_nopasswd (exception E5).

In the final scan, FIPS mode (crypto.fips_enabled = 1), SELinux enforcing, auditd and USBGuard all pass.

Versions

Item Version
OS / kernel Rocky Linux 9.8, 5.14.0-687.15.1.el9_8
openscap-scanner 1.3.14
scap-security-guide 0.1.82 (ssg-rl9-ds.xml)
Profile xccdf_org.ssgproject.content_profile_stig
ansible-core (remediation) 2.14 from the Rocky repositories, with ansible.posix < 2.0 and community.general < 10
kolla-ansible 20.6.0 (2025.1 Epoxy) in a Python 3.12 virtual environment, ansible-core >= 2.16, < 2.18
Images quay.io/openstack.kolla/*:2025.1-rocky-9

1. Baseline scan

sudo dnf -y install openscap-scanner scap-security-guide
DS=/usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml
sudo oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_stig \
  --results-arf baseline-arf.xml --report baseline.html $DS

2. Remediate

sudo oscap xccdf generate fix --fix-type ansible \
  --profile xccdf_org.ssgproject.content_profile_stig --output stig-remediation.yml $DS
sudo dnf -y install ansible-core
sudo ansible-galaxy collection install -p /usr/share/ansible/collections \
  'ansible.posix:<2.0.0' 'community.general:<10.0.0'
sudo ansible-playbook -i localhost, -c local stig-remediation.yml \
  --skip-tags sudo_remove_nopasswd,sudo_remove_no_authenticate,sudo_require_authentication

Before rebooting, give the deploy user an RSA (3072-bit or larger) or ECDSA SSH key: OpenSSH in FIPS mode rejects ed25519 user keys. The playbook sets the FIPS crypto policy but does not switch the kernel into FIPS mode, so enable it separately:

sudo reboot
sudo fips-mode-setup --enable && sudo reboot
cat /proc/sys/crypto/fips_enabled     # expect 1

3. Install Kolla-Ansible 2025.1

sudo dnf -y install python3.12 python3.12-devel git gcc libffi-devel openssl-devel
sudo systemctl stop fapolicyd          # install window only; see E1
python3.12 -m venv ~/kvenv && source ~/kvenv/bin/activate
pip install 'ansible-core>=2.16,<2.18' 'kolla-ansible==20.6.0' python-openstackclient
sudo mkdir -p /etc/kolla && sudo chown $USER /etc/kolla
cp -r ~/kvenv/share/kolla-ansible/etc_examples/kolla/* /etc/kolla/
cp ~/kvenv/share/kolla-ansible/ansible/inventory/all-in-one ~/all-in-one
kolla-ansible install-deps
# FIPS workaround 1: kolla-genpwd uses HMAC-MD5 for two keys; seed them first
for k in designate_rndc_key osprofiler_secret; do
  sed -i "s/^$k:.*/$k: $(openssl rand -hex 16)/" /etc/kolla/passwords.yml; done
kolla-genpwd

globals.yml settings used:

kolla_base_distro: "rocky"
openstack_release: "2025.1"
docker_registry: "quay.io"
docker_namespace: "openstack.kolla"
kolla_internal_vip_address: "<host address>"
network_interface: "<management interface>"
neutron_external_interface: "<external interface>"
neutron_plugin_agent: "ovn"
enable_neutron_provider_networks: "yes"
nova_compute_virt_type: "qemu"
enable_haproxy: "no"
enable_keepalived: "no"
enable_loadbalancer: "no"     # single node: otherwise ProxySQL takes port 3306 and MariaDB cannot start
enable_proxysql: "no"
enable_cinder: "no"
enable_heat: "no"
enable_horizon: "no"
kolla-ansible bootstrap-servers -i ~/all-in-one
sudo chown -R $USER: ~/kvenv     # STIG umask 077 leaves root-installed files unreadable (E4)
kolla-ansible prechecks -i ~/all-in-one
kolla-ansible pull -i ~/all-in-one
kolla-ansible deploy -i ~/all-in-one

FIPS workaround 2: the keystone_ssh and nova_ssh containers exit at start because they run ssh-keygen -t ed25519, which FIPS mode refuses. Place an empty placeholder key in each container, start it, and re-run deploy. sshd skips ed25519 and uses RSA and ECDSA host keys.

for c in keystone_ssh nova_ssh; do
  : > /tmp/empty; sudo docker cp /tmp/empty $c:/etc/ssh/ssh_host_ed25519_key
  sudo docker start $c
done
kolla-ansible deploy -i ~/all-in-one
kolla-ansible post-deploy -i ~/all-in-one

The placeholder is lost when a container is recreated, so re-apply it after every deploy or upgrade.

4. Restore hardening after deploy

Kolla bootstrap-servers sets SELinux to permissive. Set it back to enforcing, and restart fapolicyd in permissive mode (E1):

sudo setenforce 1 && sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
sudo sed -i 's/^permissive.*/permissive = 1/' /etc/fapolicyd/fapolicyd.conf
sudo systemctl enable --now fapolicyd

5. Verify

After deploy, a CirrOS instance on a tenant network behind an OVN router reached ACTIVE, received a DHCP address, and answered 3 of 3 pings from the ovnmeta-* namespace (0% loss), with FIPS mode on. The Nova services and the OVN controller gateway and metadata agents were up. Then run the final scan:

sudo oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_stig \
  --results-arf final-arf.xml --report final.html $DS

Exceptions

# Rule Conflict with Kolla, OVN or containers Setting Compensating control
E1 fapolicy_default_deny Deny mode stops runc from starting containers and blocks the Python virtual environment. A trust entry for the environment did not fix it. fapolicyd running in permissive mode (logs, does not deny); stopped only during install fapolicyd decision logging, auditd, SELinux enforcing, images from one pinned registry
E2 service_firewalld_enabled Kolla bootstrap-servers disables firewalld because Docker and OVN manage netfilter firewalld inactive (Kolla default) Upstream network access rules restricted to administrator addresses, SSH only
E4 umask 077 rules Not an exception: root-installed files in the deploy environment become unreadable umask stays 077; chown the environment after bootstrap —
E5 sudo_remove_nopasswd (and the two related sudo rules, skipped at remediation) Unattended Ansible deploy needs passwordless sudo Deploy user keeps NOPASSWD Key-only SSH from restricted source addresses
E6 no_files_unowned_by_user, file_permissions_ungroupowned, dir_perms_world_writable_sticky_bits Container images and volumes create files with UIDs and GIDs that do not exist on the host Accepted for /var/lib/docker and /var/lib/kolla Root-only parent directories

FIPS mode (E3) is not an exception: it stays enabled with the two workarounds above. SELinux enforcing, umask 077, auditd and USBGuard also stay enabled.

Not yet tested

  • Bare metal with KVM, IOMMU or VFIO
  • GPU and PCI passthrough
  • Multi-node, HA and TLS
  • Cinder, Heat and Horizon
  • fapolicyd in deny mode with a rule set for container storage and the deploy environment
  • firewalld or an explicit nftables ruleset alongside Kolla