Imported from simp/pupmod-simp-selinux (
AGENTS.md). Install upstream withnpx skills add simp/pupmod-simp-selinux. Copyright stays with the author.
AGENTS.md
This file provides guidance to AI agents when working with code in this repository.
What this module does
simp-selinux is a SIMP Puppet module that manages SELinux state on
Enterprise Linux systems. It writes /etc/selinux/config, drives the live and
post-reboot enforcement state through a custom selinux_state type, optionally
pins the SELinux kernel boot parameters, installs the supporting userspace
packages (checkpolicy, and optionally mcstrans / restorecond), manages the
mcstrans and restorecond services, and can create selinux_login mappings.
The heavy lifting of the SELinux policy itself is delegated to the
simp/vox_selinux module, which this class always contains.
The module is split into the classic SIMP install/config/service layering behind
a single public entry class. Because changing SELinux state (especially
disabled ↔ enforcing) requires a relabel and a reboot to fully take effect,
the config class emits a reboot_notify whenever the state resource changes
(manifests/config.pp).
Business logic
The module has four classes — selinux (public entry), and the private
selinux::install, selinux::config, selinux::service — plus a custom
Selinux::State data type and two Ruby types/providers (selinux_state,
selinux_login).
-
selinux(manifests/init.pp) — Public entry class (consumersinclude 'selinux'; notassert_private()'d). Several parameters have no default in the manifest and are supplied from module data (data/common.yaml,data/os/RedHat.yaml) — seeinit.pp:$manage_mcstrans_package,$manage_mcstrans_service,$mcstrans_package_name,$mcstrans_service_name,$manage_restorecond_package,$manage_restorecond_service,$restorecond_package_name. Parameters with in-manifest defaults (init.pp):$ensure(Selinux::State, default'enforcing') — the master switch. ABooleanor one ofenforcing/permissive/disabled.$kernel_enforce(Boolean, defaultfalse) — whether to also pin the SELinux kernel boot parameters.$autorelabel(Boolean, defaultfalse) — relabel the filesystem on the next boot.$manage_utils_package(Boolean, defaulttrue).$package_ensure(String) — defaults tosimplib::lookup('simp_options::package_ensure', { 'default_value' => 'present' })(init.pp).$mode(Enum['targeted','mls'], default'targeted') — the docstring warnsmlscan render a system inoperable (init.pp).$login_resources(Optional[Hash], defaultundef).
Control flow and resources:
$stateselector (init.pp) normalises theBoolean/enum$ensureinto a bare string:true => 'enforcing',false => 'disabled', otherwise the passed enum.containsselinux::install,selinux::config,selinux::service, andvox_selinux(init.pp), with orderinginstall -> config ~> service(init.pp).selinux_logincreation (init.pp): only when$login_resourcesis set and the factos.selinux.current_modeis present and notdisabled, itcreate_resources('selinux_login', $login_resources).
-
selinux::install(manifests/install.pp) — its own parameters re-derive from the parent viapick(getvar('selinux::manage_utils_package'), true)andsimplib::lookup('selinux::*')(install.pp).$package_ensurehere has a nested default:simplib::lookup('selinux::package_ensure', { 'default_value' => simplib::lookup('simp_options::package_ensure', { 'default_value' => 'present' }) })(install.pp). Itensure_packagesthe utils packages (['checkpolicy'],install.pp), and conditionally themcstransandrestorecondpackages (install.pp). -
selinux::config(manifests/config.pp) —assert_private(). Declaresreboot_notify { 'selinux' }(config.pp) and theselinux_state { 'set_selinux_state' }resource carrying$selinux::ensure/$selinux::autorelabel, notifying the reboot (config.pp). Computes$_enabling/$_disablingbooleans from the liveos.selinux.enabledfact vs. the desired$state(config.pp). When$kernel_enforce(config.pp): setskernel_parameter { 'selinux' }to'0'when disabled, else'1', pluskernel_parameter { 'enforcing' }='0'for permissive /'1'otherwise — each notifying the reboot. Finally writesfile { '/etc/selinux/config' }(mode0644) from the EPP templateselinux/etc/selinux/configwithstateandmode(config.pp). -
selinux::service(manifests/service.pp) —assert_private(). Chooses$_aux_service_ensure:'stopped'when the desired state isdisabledor SELinux is not currently enabled, else'running'(service.pp). When$manage_mcstrans_service, and only on systemd systems (service.pp), if/procis mounted withhidepid > 0and a GID is set, it asserts the optionalpuppet/systemddependency and writes asystemd::dropin_fileadding that GID to the service'sSupplementaryGroups(service.pp) — the mcstrans daemon needs the GID to see hidden/procentries. It then declares themcstransand (if$manage_restorecond_service)restorecondservices at$_aux_service_ensure(service.pp), both requiringClass['selinux::install'].
Gotchas / non-obvious details
- Several
selinuxparameters have no manifest default and rely entirely on module data being present (init.pp;data/common.yaml,data/os/RedHat.yaml). Removing or renaming those Hiera keys breaks compilation with a "no default" error, not a silent fallback. mcstransandrestorecondmanagement is off by default.selinux::manage_mcstrans_package/_servicedefault tofalse(data/common.yaml);selinux::manage_restorecond_package/_servicedefault tofalsetoo — but those latter defaults live indata/os/RedHat.yaml, notcommon.yaml.- A reboot is required to fully apply a state change. The
selinux_stateresource and everykernel_parameternotifyreboot_notify { 'selinux' }(config.pp). This is why the module cannot flip enforcement purely in a singlepuppet apply. selinux_loginresources are silently skipped unless theos.selinux.current_modefact is present and notdisabled(init.pp) — you cannot create login mappings on a host where SELinux is off.- The hidepid drop-in is systemd-only and doubly-guarded. It fires only when
'systemd' in init_systems,/prochidepid > 0, and a/procGID is set (service.pp); only then ispuppet/systemdasserted as an optional dependency. vox_selinuxdoes the real policy work.selinuxalwayscontain 'vox_selinux'(init.pp); this module manages the surrounding state/config/services and the login mappings, not the policy modules themselves.$mode => 'mls'is dangerous — the class docstring explicitly warns it can render a system inoperable (init.pp).simp/simp_optionsis NOT a declared dependency inmetadata.json, yet the manifests consume thesimp_options::package_ensureseam viasimplib::lookup(provided bysimp/simplib).puppet/systemdis an optional dependency, asserted at runtime withsimplib::assert_optional_dependencyonly on the hidepid path (service.pp).
The simp_options / simplib::lookup seam
The module's SIMP feature-toggle seam is simp_options::package_ensure, reached
through simplib::lookup with an explicit default:
| File | Key | default_value |
|---|---|---|
init.pp |
simp_options::package_ensure |
'present' |
install.pp |
selinux::package_ensure → simp_options::package_ensure |
'present' (nested lookup) |
Keep routing package-ensure through simplib::lookup('simp_options::*', { 'default_value' => ... }) with an explicit default rather than assuming
simp_options is included. selinux::install additionally layers a
module-level selinux::package_ensure override on top.
Dependencies
Module dependencies (from metadata.json):
puppetlabs/stdlib>= 8.0.0 < 10.0.0(providesensure_packages(),member())simp/simplib>= 4.9.0 < 5.0.0(providessimplib::lookup,simplib::assert_optional_dependency,reboot_notify,kernel_parameter, and thesimplib__mountpointsfact)simp/vox_selinux>= 3.1.0 < 4.0.0(the SELinux policy module this classcontains)
Optional dependency (from metadata.json simp.optional_dependencies):
puppet/systemd>= 4.0.2 < 9.0.0— used only on the systemd hidepid path, asserted at runtime withsimplib::assert_optional_dependency(manifests/service.pp).
Runtime requirement (from metadata.json requirements): `openvox
= 8.0.0 < 9.0.0
. This module already names **openvox** (notpuppet) as its runtime requirement, reflecting the SIMP Puppet → OpenVox migration; keep this line matchingmetadata.json`.
Supported OS matrix (from metadata.json): CentOS 9/10; RedHat 8/9/10;
OracleLinux 8/9/10; Rocky 8/9/10; AlmaLinux 8/9/10.
Repository layout
-
manifests/init.pp— the publicselinuxclass; parameters,$statenormalisation,containordering,selinux_logincreation. -
manifests/install.pp—selinux::install; the utils / mcstrans / restorecond packages. -
manifests/config.pp—selinux::config(private);/etc/selinux/config,selinux_state, kernel parameters,reboot_notify. -
manifests/service.pp—selinux::service(private); mcstrans / restorecond services and the hidepid systemd drop-in. -
types/state.pp— theSelinux::Statedata type (Variant[Boolean, Enum['enforcing','permissive','disabled']]). -
lib/puppet/type/selinux_state.rb,lib/puppet/provider/selinux_state/selinux_state.rb— custom type/provider driving the live + persistent SELinux state. -
lib/puppet/type/selinux_login.rb,lib/puppet/provider/selinux_login/semanage.rb— custom type/provider for SELinux login mappings viasemanage. -
templates/etc/selinux/config.epp— the/etc/selinux/configtemplate (SELINUX=/SELINUXTYPE=). -
data/common.yaml— default toggles and thelogin_resourcesdeep-mergelookup_options. -
data/os/RedHat.yaml— RedHat-family overrides (mcstrans service name, restorecond package/toggles). -
hiera.yaml— module data hierarchy (v5): OS family+major → OS family → common. -
metadata.json— deps, optional deps, OS matrix, OpenVox requirement. -
spec/classes/init_spec.rb,spec/classes/install_spec.rb— rspec-puppet unit tests. -
spec/unit/puppet/type/,spec/unit/puppet/provider/— unit tests for the custom types and providers. -
spec/acceptance/suites/default/— beaker acceptance suites (00_default,05_kernel_enforce,10_selinux_login,99_proc_hidepid); nodesets underspec/acceptance/nodesets/. -
REFERENCE.md— generated Puppet Strings reference. -
Acceptance does NOT run in CI right now. In
.github/workflows/pr_tests.ymltheacceptancejob is commented out: it targets thedocker_*nodesets but is disabled because the tests require reboots, which Docker cannot perform. The active jobs are six:puppet-syntax,puppet-style,ruby-style,file-checks,releng-checks, andspec-tests. To exercise acceptance you must run beaker locally against a hypervisor-backed nodeset.
Common commands
# Install dependencies
bundle install
# Run all unit tests
bundle exec rake spec
# Run a single class spec
bundle exec rspec spec/classes/init_spec.rb
# Run the type/provider unit specs
bundle exec rspec spec/unit/puppet/type/selinux_login_spec.rb
# Puppet lint
bundle exec rake lint
# Ruby lint
bundle exec rake rubocop
# Regenerate REFERENCE.md from puppet-strings docstrings
puppet strings generate --format markdown --out REFERENCE.md
# Run a beaker acceptance suite locally (needs a reboot-capable hypervisor;
# the CI acceptance job is disabled because Docker cannot reboot)
bundle exec rake beaker:suites[default]
The Gemfile sets puppet_version to ['>= 8', '< 9'] and — per an
in-file comment — installs both the openvox and puppet gems "temporarily
until the puppet dependency is removed from other gems" (openvox_version
defaults to puppet_version; a loop over ['openvox','puppet']). Relevant gem pins: puppetlabs_spec_helper ~> 8.0.0,
simp-rake-helpers ~> 5.25.0 (note: this module pins 5.25.0, not the more
common 5.24.0), simp-beaker-helpers ~> 2.0.0. Rubocop is pinned to
~> 1.88.0. spec/spec_helper.rb requires
puppetlabs_spec_helper/module_spec_helper.
Conventions
- Preserve the
@summary/@parampuppet-strings docstrings on the classes — they driveREFERENCE.md. RegenerateREFERENCE.mdafter changing docs or parameters. - Keep package names, service names, and management toggles in module data
(
data/common.yaml,data/os/*.yaml), not hard-coded in the manifests. The parentselinuxclass deliberately has no manifest defaults for the mcstrans/restorecond parameters — they must resolve from Hiera. - Continue routing package-ensure through
simplib::lookup('simp_options::package_ensure', { 'default_value' => ... })(and the module-levelselinux::package_ensurelayer) rather than assumingsimp_optionsis included. - Guard optional integrations (
puppet/systemd) withsimplib::assert_optional_dependencyand a fact check, as the hidepid path does — don't hard-includeoptional modules. - Keep the private classes private:
selinux::configandselinux::servicecallassert_private()(config.pp,service.pp) — consumers shouldinclude 'selinux', never the sub-classes directly. - Several baseline files carry a puppetsync notice — e.g.
Gemfile,spec/spec_helper.rb,.github/workflows/pr_tests.yml, and the.gitignore/.pdkignoredotfiles — so they are baseline-managed and the next sync overwrites local edits. Check each file's header for the notice rather than treating this list as exhaustive; push changes to any such file upstream to the baseline, not here. - Match the existing 2-space Puppet indentation and aligned-arrow parameter
style used across
manifests/.