Skip to content

Add NIST SP 800-171 r3 (CUI) profiles for RHEL 8, 9, and 10 - #15062

Merged
Mab879 merged 9 commits into
ComplianceAsCode:masterfrom
ggbecker:nist-800-171
Sep 9, 2026
Merged

Add NIST SP 800-171 r3 (CUI) profiles for RHEL 8, 9, and 10#15062
Mab879 merged 9 commits into
ComplianceAsCode:masterfrom
ggbecker:nist-800-171

Conversation

@ggbecker

@ggbecker ggbecker commented Aug 26, 2026

Copy link
Copy Markdown
Member

Description

This PR adds NIST SP 800-171 Revision 3 (Controlled Unclassified Information, CUI) profiles for RHEL 8, 9, and 10, built on top of the existing NIST 800-53 control mappings.

What's included

  • Cross-policy control references degrade gracefullycontrols/ resolution now tolerates foreign control references that don't resolve for a given product, so a product-agnostic control file can reference another policy's controls without hard-failing.
  • Product-agnostic control file controls/nist_800_171.yml — derived from NIST 800-53. Each 800-171 requirement references the corresponding nist_800_53 control(s), which resolve at build time against the product's 800-53 policy. Generated via utils/nist_sync/generate_nist_800_171.py.
  • CUI profiles for RHEL 8, 9, and 10 — selecting the rules mapped through the 800-171 → 800-53 chain.
  • cui reference type consolidation — the cui reference now points at the r3 document (https://nvlpubs.nist.gov/nistpubs/SpecialPublications/800-171r3/NIST.SP.800-171r3.html). Rules selected by the control file are auto-stamped with the requirement id as a cui reference. Legacy r2 cui references were dropped from rule files to avoid conflicting/duplicate references.

800-53 mapping refinements

The CUI profile derives its selection entirely from the NIST 800-53 controls (- nist_800_171:all), with no hardcoded profile selections. To make that derivation correct and complete, the underlying mappings were refined:

  • Firewall, SELinux, and X Windows rules mapped to their 800-53 controls — so the CUI profile inherits them by derivation rather than by explicit selection.
  • CUI settings derived from controls, not pinned in the profilepackage_xorg-x11-server-Xwayland_removed is mapped under cm-7 (least functionality), alongside xwayland_disabled; var_selinux_state=enforcing (and var_selinux_policy_name=targeted on RHEL 9) is mapped under ac-3, next to selinux_state, the control that actually brings SELinux into the profile. These were previously reachable only via profile-level pins.
  • RHEL 9 mapping aligned with CIS exclusions — 3 rules that CIS itself excludes were dropped from the RHEL 9 800-53 mapping, keeping the derived CUI selection consistent.

Testing

  • Unit tests, product-stability and profile-stability data updated for all three products.
  • Profile-stability and product-stability tests pass locally.

Known limitations

On the Image Mode (bootc) Day-2 upgrade path, two rules fail on RHEL 9 only under /hardening/container/old-new/cui and are waived in the contest test suite:

  • aide_build_database — on bootc switch the guest /var is not re-provisioned, so the AIDE database built into the new image never lands on disk. Transitional: resolves once a released CUI baseline already includes the rule.
  • ensure_pam_wheel_group_empty — the su/wheel group is added imperatively to /etc/group at build time, but the /etc 3-way merge on bootc switch can drop the entry (confirmed flaky).

Only RHEL 9 is affected: RHEL 8 does not run the Image Mode test, and on RHEL 10 the released (old) datastream has no CUI profile so the old-new comparison is skipped.

@Mab879 Mab879 added this to the 0.1.83 milestone Aug 26, 2026
@jan-cerny jan-cerny added New Profile Issues or pull requests related to new Profiles. Highlight This PR/Issue should make it to the featured changelog. RHEL8 Red Hat Enterprise Linux 8 product related. RHEL9 Red Hat Enterprise Linux 9 product related. RHEL10 Red Hat Enterprise Linux 10 product related. labels Aug 27, 2026
CONTROL_RE = re.compile(r"([A-Z]{2})-(\d{2})(?:\((\d{2})\))?")


def strip_tags(line):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to docs/manual/developer/04_style_guide.md, new Python methods and scripts should have type hints.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this scrip will be a one time thing to this repository and I will remove it from this pull request before we merge it. Sorry for the noise

@@ -0,0 +1,33 @@
---
documentation_complete: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are FMF metadata in Contest updated so that Contest tests will test the new profile?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this needs to land in sync with: RHSecurityCompliance/contest#664

@ggbecker
ggbecker marked this pull request as draft August 27, 2026 10:43
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Used by openshift-ci bot. label Aug 27, 2026
@ggbecker
ggbecker force-pushed the nist-800-171 branch 4 times, most recently from 8c28987 to f83cd70 Compare September 2, 2026 17:09
A control's 'controls:' list can reference controls from another policy
(e.g. 'nist_800_53:ac-2'). Such references resolve against the current
product's policies at build time. Derived frameworks like NIST 800-171
reference NIST 800-53 controls that only exist for some products (rhel8/9/10),
so building any other product would previously fail with 'policy doesn't
exist'.

Cross-policy references now degrade gracefully: if the target policy or
control isn't loaded for the product being built, the reference contributes
no rules instead of aborting the build. Same-policy references are still
resolved strictly so typos remain hard errors.
Add a single, product-agnostic controls/nist_800_171.yml in which each
800-171 requirement references (via cross-policy 'controls:' entries) the
NIST 800-53 controls it was tailored from. At build time these resolve
against the current product's nist_800_53 policy, so each requirement
inherits the union of rules mapped to its source controls for that
product; products without nist_800_53 resolve to no rules.

Register the nist-800-171 reference type so rules selected under a
requirement automatically receive that requirement id as a reference,
mirroring the STIG stigid auto-reference behavior.

The control file is derived from the official NIST SP 800-171 r3 publication.
Redefine the cui profile on each RHEL product to select all rules from
the generic nist_800_171 control file (nist_800_171:all). Each 800-171
requirement resolves against the product's own nist_800_53 policy, so the
profile pulls in the union of rules mapped to the source 800-53 controls
for that product.

RHEL 8 and RHEL 9 previously defined cui by inheriting ospp with a couple
of variable overrides; that inheritance is dropped in favor of the
control-file-driven selection. RHEL 10 gains a cui profile for the first
time.
Consolidate the two NIST 800-171 reference types into one. The repo
already had a 'cui' reference (Controlled Unclassified Information)
pointing at NIST 800-171 Revision 2. Rather than maintain a separate
'nist-800-171' reference type for Revision 3, repurpose 'cui' as the
single, current NIST 800-171 reference:

- Point the cui reference URL at the r3 publication and remove the
  separate nist-800-171 reference type from constants.
- Set reference_type: cui on the generic nist_800_171 control file (and
  the generator), so rules selected under a requirement receive that
  requirement id as a 'cui' reference automatically.

The cui profile now maps to the cui reference end to end: cui profile ->
nist_800_171 control file -> product nist_800_53 -> rules, with each rule
stamped with its r3 requirement id.
Remove the 304 superseded manual r2 'cui:' references from rule files
now that r2 is superseded by r3 (see previous commit repurposing the
cui reference type). Drop now-empty references blocks left behind by
the removal.
These rules are selected by the CIS profile but were missing from the
CUI (NIST 800-171 r3) profile, because they were listed under NIST
800-53 controls that 800-171 does not reference, even though each rule's
own nist reference points to a control that 800-171 does reference:

- selinux_state -> ac-3 (Access Enforcement), alongside its already-mapped
  companions selinux_not_disabled and selinux_policytype
- package_firewalld_installed -> sc-7 (Boundary Protection), alongside
  service_firewalld_enabled
- package_xorg-x11-server-common_removed, xwindows_runlevel_target -> cm-7
  (Least Functionality) on RHEL 9, alongside the other package removals

This closes the CUI-vs-CIS parity gaps that were genuine 800-53 mapping
inconsistencies; the remaining CIS-only rules map to controls outside the
800-171 subset and are correctly excluded.
products/rhel9/profiles/cis.profile (and cis_server_l1/cis_workstation_l1/l2)
explicitly unselect file_owner_at_allow, file_ownership_home_directories,
and group_unique_name via '!rule_id', with the comment:
'Following rules once had a prodtype incompatible with the rhel9 product'.

That exclusion lives at the profile level, not in cis_rhel9.yml's control
structure, so these 3 rules were still listed under RHEL9's nist_800_53
ac-3/cm-1/cm-6 controls and got pulled into the cui profile via
nist_800_171:all, making cui select 3 rules CIS RHEL9 does not.

RHEL8 and RHEL10's CIS profiles have no such exclusion and legitimately
select all three rules, so this change is scoped to RHEL9 only, matching
RHEL9 CIS's actual current selections whether or not the underlying
prodtype-era exclusion is itself still warranted (a separate, existing
CIS RHEL9 profile question, out of scope here).

Also add a PRODUCT_RULE_EXCEPTIONS mechanism to
utils/nist_sync/sync_nist_split.py so a future re-sync of the RHEL9
NIST 800-53 mapping does not silently reintroduce these 3 rules, since
load_all_cis_items_from_control_files() reads cis_rhel9.yml's control
structure (which still lists them) rather than the profile's
'!rule_id' exclusions.
Rebasing onto upstream master picked up f6c42ec ('Add
ensure_journald_and_rsyslog_not_active_together to NIST 800-53 AU-2
(rhel9)'), an independent, correctly-scoped mapping fix (au-2 is
referenced by 800-171 requirement 03.03.01) that legitimately adds this
rule to the cui profile. Update the stability reference to match.
Rather than pinning extra selections in the cui profile to reach CIS
parity, map the settings cui genuinely needs onto the NIST 800-53
controls it derives from, so they are inherited rather than hardcoded:

- Add package_xorg-x11-server-Xwayland_removed to cm-7 (least
  functionality), alongside xwayland_disabled, for RHEL 10.
- Add var_selinux_state=enforcing (and var_selinux_policy_name=targeted
  on RHEL 9) to ac-3, next to selinux_state, the control that brings
  SELinux into cui. These were previously present only via profile pins;
  the sc.yml control that also carries them is not pulled by 800-171.

The remaining CIS-only hardening (firewalld zone/loopback rules,
journald_compress, package_nftables_installed, sysctl_kernel_dmesg_restrict)
is dropped: it does not trace to an 800-53 control referenced by 800-171
and cui does not need to match CIS. var_authselect_profile=local is also
dropped as a no-op, since its only consumer, enable_authselect, is not
part of cui on RHEL 10.
@ggbecker
ggbecker marked this pull request as ready for review September 7, 2026 23:32
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Used by openshift-ci bot. label Sep 7, 2026
@ggbecker

ggbecker commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

This should be now ready for review.

@Mab879 Mab879 self-assigned this Sep 8, 2026

@dodys dodys left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from the ubuntu side, the change looks good, thanks!

@teacup-on-rockingchair teacup-on-rockingchair left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@mrkanon mrkanon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks

@Mab879
Mab879 merged commit 72b9384 into ComplianceAsCode:master Sep 9, 2026
73 of 75 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Highlight This PR/Issue should make it to the featured changelog. New Profile Issues or pull requests related to new Profiles. RHEL8 Red Hat Enterprise Linux 8 product related. RHEL9 Red Hat Enterprise Linux 9 product related. RHEL10 Red Hat Enterprise Linux 10 product related.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants