Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/user/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ List of the documents in alphabetical file name order.
- [AMQ Broker on MicroShift](./howto_amq_broker.md)
- [Cluster-to-Cluster Connectivity (C2CC)](./howto_c2cc.md)
- [Encrypting C2CC Traffic with IPsec](./howto_c2cc_ipsec.md)
- [CIS Level 2 Hardening for MicroShift](./howto_cis_hardening.md)
- [MicroShift Configuration](./howto_config.md)
- [Firewall Configuration](./howto_firewall.md)
- [Gateway API](./howto_gateway_api.md)
Expand Down
107 changes: 107 additions & 0 deletions docs/user/howto_cis_hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# CIS Level 2 Hardening for MicroShift

This guide describes how to run MicroShift on a CIS Level 2 hardened RHEL system. It covers which CIS rules MicroShift affects, the required hardening playbook overrides, and how to verify compliance.

## MicroShift's CIS Impact

MicroShift introduces changes to a hardened system in three categories. These rules will fail on any system running Kubernetes containers and cannot be avoided without disabling core functionality.

### IP Forwarding

OVN-Kubernetes requires IP forwarding for pod networking. MicroShift enables `net.ipv4.ip_forward` and `net.ipv6.conf.all.forwarding` at startup.

| Rule ID | RHEL 9 | RHEL 10 |
|:--------|:------:|:-------:|
| `sysctl_net_ipv4_ip_forward` | x | x |
| `sysctl_net_ipv6_conf_all_forwarding` | x | x |
| `sysctl_net_ipv4_conf_all_forwarding` | | x |
| `sysctl_net_ipv4_conf_default_forwarding` | | x |
| `sysctl_net_ipv6_conf_default_forwarding` | | x |

### Container File Ownership

Container images contain files owned by UIDs and GIDs that do not exist in the host's `/etc/passwd` and `/etc/group`. These are stored in `/var/lib/containers/storage/overlay/` and are an inherent characteristic of any container runtime.

| Rule ID | RHEL 9 | RHEL 10 |
|:--------|:------:|:-------:|
| `file_permissions_ungroupowned` | x | |
| `no_files_unowned_by_user` | x | |
| `no_files_or_dirs_ungroupowned` | | x |
| `no_files_or_dirs_unowned_by_user` | | x |

### Kubelet Volume Permissions

Kubelet creates 0777 directories for ConfigMap and EmptyDir volumes so that pods running as any UID can access their projected configuration. It also creates 0666 container termination log files.

| Rule ID | RHEL 9 | RHEL 10 |
|:--------|:------:|:-------:|
| `dir_perms_world_writable_sticky_bits` | x | x |
| `file_permissions_unauthorized_world_writable` | x | x |

### Audit Rules

CIS requires audit rules for all setuid/setgid binaries. If MicroShift is installed after running the CIS hardening role, the audit rules will not cover binaries added by MicroShift RPMs.

| Rule ID | RHEL 9 | RHEL 10 |
|:--------|:------:|:-------:|
| `audit_rules_privileged_commands` | x | x |

This rule can be resolved by regenerating audit rules after installing MicroShift:

```bash
sudo find / -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null | \
awk '{print "-a always,exit -F path=" $1 " -F perm=x -F auid>=1000 -F auid!=unset -k privileged"}' | \
sudo tee /etc/audit/rules.d/99-privileged-post-install.rules > /dev/null
sudo augenrules --load
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

## Hardening Playbook Overrides

When using the RedHatOfficial CIS ansible roles, the hardening playbook must override the IP forwarding variables to prevent the role from disabling forwarding before MicroShift starts.

For RHEL 9:

```yaml
---
- name: Apply CIS Level 2 hardening
hosts: localhost
connection: local
become: true
roles:
- role: ansible-role-rhel9-cis
vars:
sysctl_net_ipv4_ip_forward: false
sysctl_net_ipv6_conf_all_forwarding: false
```

For RHEL 10, the CIS benchmark adds per-interface forwarding checks:

```yaml
---
- name: Apply CIS Level 2 hardening
hosts: localhost
connection: local
become: true
roles:
- role: ansible-role-rhel10-cis
vars:
sysctl_net_ipv4_ip_forward: false
sysctl_net_ipv4_conf_all_forwarding: false
sysctl_net_ipv4_conf_default_forwarding: false
sysctl_net_ipv6_conf_all_forwarding: false
sysctl_net_ipv6_conf_default_forwarding: false
```

> Each variable is set to `false` to tell the role **not** to disable forwarding. This does not enable forwarding — MicroShift does that at startup.

## Firewall Configuration

CIS hardening enables `firewalld` and locks down all traffic. MicroShift requires several ports and trusted network sources to be opened as described in [Firewall Configuration](howto_firewall.md).

## References

- [RedHatOfficial ansible-role-rhel9-cis](https://github.com/RedHatOfficial/ansible-role-rhel9-cis)
- [RedHatOfficial ansible-role-rhel10-cis](https://github.com/RedHatOfficial/ansible-role-rhel10-cis)
- [OpenSCAP Project](https://www.open-scap.org/)
- [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks)
- [Firewall Configuration](howto_firewall.md)
16 changes: 16 additions & 0 deletions test/assets/cis/cis-harden-el10.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
- name: Apply CIS Level 2 hardening
hosts: localhost
connection: local
become: true
roles:
- role: ansible-role-rhel10-cis
vars:
# MicroShift OVN networking requires IP forwarding
sysctl_net_ipv4_ip_forward: false
sysctl_net_ipv4_conf_all_forwarding: false
sysctl_net_ipv4_conf_default_forwarding: false
sysctl_net_ipv6_conf_all_forwarding: false
sysctl_net_ipv6_conf_default_forwarding: false
# CI requires passwordless sudo
sudo_remove_nopasswd: false
13 changes: 13 additions & 0 deletions test/assets/cis/cis-harden-el9.yml
Comment thread
copejon marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
- name: Apply CIS Level 2 hardening
hosts: localhost
connection: local
become: true
roles:
- role: ansible-role-rhel9-cis
vars:
# MicroShift OVN networking requires IP forwarding
sysctl_net_ipv4_ip_forward: false
sysctl_net_ipv6_conf_all_forwarding: false
# CI requires passwordless sudo
sudo_remove_nopasswd: false
7 changes: 7 additions & 0 deletions test/assets/cis/cis-requirements-el10.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
roles:
- name: ansible-role-rhel10-cis
src: https://github.com/RedHatOfficial/ansible-role-rhel10-cis
version: "0.1.80"
collections:
- community.general
- ansible.posix
7 changes: 7 additions & 0 deletions test/assets/cis/cis-requirements-el9.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
roles:
- name: ansible-role-rhel9-cis
src: https://github.com/RedHatOfficial/ansible-role-rhel9-cis
Comment thread
ggiguash marked this conversation as resolved.
version: "0.1.80"
collections:
- community.general
- ansible.posix
154 changes: 154 additions & 0 deletions test/scenarios-bootc/el10/periodics/el102-src@cis-lvl2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/bin/bash

# Sourced from scenario.sh and uses functions defined there.

# CIS hardening runs inside the Robot suite and takes ~20 minutes
# shellcheck disable=SC2034 # used elsewhere
TEST_EXECUTION_TIMEOUT=60m

# The RPM-based image used to create the VM for this test does not
# include MicroShift or greenboot, so tell the framework not to wait
# for greenboot to finish when creating the VM.
export SKIP_GREENBOOT=true
Comment thread
coderabbitai[bot] marked this conversation as resolved.

configure_microshift_mirror() {
local -r repo=$1

if [[ -z "${repo}" ]] ; then
return
fi

if [[ ! "${repo}" =~ ^http ]]; then
return
fi

local -r tmp_file=$(mktemp)
tee "${tmp_file}" >/dev/null <<EOF
[microshift-mirror-rpms]
name=MicroShift Mirror
baseurl=${repo}
enabled=1
gpgcheck=0
skip_if_unavailable=0
EOF
copy_file_to_vm host1 "${tmp_file}" "${tmp_file}"
run_command_on_vm host1 "sudo cp ${tmp_file} /etc/yum.repos.d/microshift-mirror-rpms.repo"
rm -f "${tmp_file}"
}

# On RHEL 10, rhocp and fast-datapath repos are not available via
# subscription-manager. Create repo files pointing to the RHEL 9 CDN
# using entitlement certificates as a workaround.
configure_cdn_repo() {
local -r repo_id=$1
local -r repo_name=$2
local -r baseurl=$3

local -r cert=$(run_command_on_vm host1 "ls /etc/pki/entitlement/[0-9]*.pem | grep -v '\-key.pem' | head -n1")
local -r key=$(run_command_on_vm host1 "ls /etc/pki/entitlement/[0-9]*-key.pem | head -n1")
local -r tmp_file=$(mktemp)

tee "${tmp_file}" >/dev/null <<EOF
[${repo_id}]
name=${repo_name}
baseurl=${baseurl}
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
sslverify=1
sslcacert=/etc/rhsm/ca/redhat-uep.pem
sslclientcert=${cert}
sslclientkey=${key}
EOF
copy_file_to_vm host1 "${tmp_file}" "${tmp_file}"
run_command_on_vm host1 "sudo cp ${tmp_file} /etc/yum.repos.d/${repo_id}.repo"
rm -f "${tmp_file}"
}

configure_rhocp_repo() {
local -r rhocp=$1
local -r major=$2
local -r minor=$3
local -r arch=${4:-$(uname -m)}

if [[ -z "${rhocp}" ]] ; then
return
fi

if [[ "${rhocp}" =~ ^[0-9]{1,2}$ ]]; then
configure_cdn_repo \
"rhocp-${major}.${rhocp}" \
"Red Hat OpenShift ${major}.${rhocp} for RHEL 9" \
"https://cdn.redhat.com/content/dist/layered/rhel9/${arch}/rhocp/${major}.${rhocp}/os"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
elif [[ "${rhocp}" =~ ^http ]]; then
local -r ocp_repo_name="rhocp-${major}.${minor}-for-rhel-9-mirrorbeta-rpms"
local -r tmp_file=$(mktemp)

tee "${tmp_file}" >/dev/null <<EOF
[${ocp_repo_name}]
name=Beta rhocp RPMs for RHEL 9
baseurl=${rhocp}
enabled=1
gpgcheck=0
skip_if_unavailable=0
EOF
copy_file_to_vm host1 "${tmp_file}" "${tmp_file}"
run_command_on_vm host1 "sudo cp ${tmp_file} /etc/yum.repos.d/${ocp_repo_name}.repo"
rm -f "${tmp_file}"
fi
}

scenario_create_vms() {
prepare_kickstart host1 kickstart-liveimg.ks.template ""
launch_vm rhel102-installer

configure_vm_firewall host1
subscription_manager_register host1

# Configure repositories for MicroShift and its dependencies.
# MicroShift is NOT installed here — the Robot suite installs it
# after CIS hardening so the scan reveals what MicroShift changes.
local -r source_reponame=$(basename "${LOCAL_REPO}")
local -r source_repo_url="${WEB_SERVER_URL}/rpm-repos/${source_reponame}"
local -r arch=$(run_command_on_vm host1 "uname -m")

configure_rhocp_repo "${RHOCP_MINOR_Y}" "${MAJOR_VERSION}" "${MINOR_VERSION}" "${arch}"
configure_rhocp_repo "${RHOCP_MINOR_Y_BETA}" "${MAJOR_VERSION}" "${MINOR_VERSION}" "${arch}"
configure_rhocp_repo "${RHOCP_MINOR_Y1}" "${PREVIOUS_MAJOR_VERSION}" "${PREVIOUS_MINOR_VERSION}" "${arch}"
configure_rhocp_repo "${RHOCP_MINOR_Y1_BETA}" "${PREVIOUS_MAJOR_VERSION}" "${PREVIOUS_MINOR_VERSION}" "${arch}"
configure_microshift_mirror "${PREVIOUS_RELEASE_REPO}"
run_command_on_vm host1 "sudo subscription-manager release --set 10.2"
configure_cdn_repo \
"fast-datapath" \
"Red Hat Fast Datapath for RHEL 9" \
"https://cdn.redhat.com/content/dist/layered/rhel9/${arch}/fast-datapath/os"

# Add the source RPM repo for locally-built MicroShift packages.
local -r tmp_file=$(mktemp)
tee "${tmp_file}" >/dev/null <<EOF
[microshift-local]
name=MicroShift Local
baseurl=${source_repo_url}
enabled=1
gpgcheck=0
skip_if_unavailable=0
EOF
copy_file_to_vm host1 "${tmp_file}" "${tmp_file}"
run_command_on_vm host1 "sudo cp ${tmp_file} /etc/yum.repos.d/microshift-local.repo"
rm -f "${tmp_file}"

}

scenario_remove_vms() {
remove_vm host1
}

scenario_run_tests() {
# shellcheck disable=SC2034 # used elsewhere
TEST_RANDOMIZATION=none
run_tests host1 \
--variable "SCAP_DS_FILE:/usr/share/xml/scap/ssg/content/ssg-rhel10-ds.xml" \
--variable "CIS_REQUIREMENTS_FILE:cis-requirements-el10.yml" \
--variable "CIS_HARDEN_FILE:cis-harden-el10.yml" \
suites/cis/
}
Loading