OneLinersCommand workbench
Guides
Security / Services & Applications

Run a custom web application under SELinux enforcing

Confine a custom RHEL 9 web service with persistent labels, a dedicated domain, exact port mapping, denial analysis, negative tests and an enforcement-safe rollback.

300 min12 stepsHigh-impact changeRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 12 steps completed
Goal

Keep SELinux enforcing while granting only the files, port and runtime operations a custom web service genuinely requires.

Supported environments
  • Red Hat Enterprise Linux 9.x
  • SELinux targeted policy RHEL 9 current
Prerequisites
  • RHEL 9 enforcing host Maintained RHEL 9, targeted policy, healthy auditd, official policycoreutils and SELinux development packages.getenforce && sestatus && auditctl -s
  • Documented custom service Stable executable/unit and explicit file, port, network, capability, child and secret contract.
  • Matching staging and recovery access Policy packages match production; console, snapshot/config rollback and protected logs exist.
  • Positive and negative test fixtures Synthetic health/content/state plus forbidden secret/static/upload/wrong-port operations.
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • A custom web service on Red Hat Enterprise Linux 9 that remains in SELinux enforcing mode, runs under a dedicated systemd service and confined domain, reads only its packaged configuration and static content, writes only its state/log directories, binds only its assigned HTTP port, and has no unexplained AVC denials.
  • A persistent labeling design using `semanage fcontext` plus `restorecon`, an explicit port type, evaluated booleans, and a small reviewable custom policy module generated from declared application behavior rather than blindly from denial logs.
  • A denial-investigation workflow that correlates application errors, discretionary permissions, SELinux subject/object contexts, policy decision and audit event; fixes the root cause at the narrowest layer and proves negative tests still fail.
Observable outcome
  • `getenforce` remains `Enforcing`; the service process has the expected custom domain; executable, configuration, content, writable state and logs match persistent file-context rules after `restorecon` and reboot.
  • The application starts, serves its health endpoint, binds only the approved port and reaches only explicitly required local resources, while attempts to read secrets, write static content, execute uploaded files or bind other ports are denied.
  • Every policy rule maps to a documented application operation. Standard types/interfaces and a relevant boolean are preferred when they precisely match; custom allow rules are reviewed in source, compiled, versioned and removable.
  • AVC records, application logs and test evidence show why access was denied. Operators never use permissive mode, `setenforce 0`, global relabeling, `chcon`, `audit2allow -M` or an all-powerful boolean as a generic fix.

Architecture

How the parts fit together

A systemd unit executes `/usr/local/libexec/acme-web` as a dedicated Unix account. SELinux transitions the executable type `acme_web_exec_t` into the process domain `acme_web_t`. Persistent file-context mappings label read-only configuration, static content, writable state and logs with separate types. The policy module grants the domain only required file classes/permissions, process/runtime access through reviewed reference-policy interfaces, and binding to `http_port_t` on TCP 8443. DAC ownership/modes, systemd sandboxing and network firewall remain independent controls. Audit events record SELinux AVC decisions. An operator correlates source context, target context, class and requested permission with the documented operation before changing labels, a standard boolean, port mapping or custom policy.

Custom web executable and systemd unitProvide a stable entry point, identity, arguments, environment and lifecycle for domain transition and testing.
SELinux type-enforcement policyConfines `acme_web_t` and grants only operations required by the application contract.
Persistent file-context databaseMaps application paths to intended types so package updates, restorecon and relabel preserve policy identity.
Port type mappingAssociates TCP/8443 with the reviewed network type used by the policy rather than permitting arbitrary bind.
Linux Audit/AVC evidenceRecords denied decisions with subject, object, class and permission for root-cause analysis.
DAC, systemd and firewall controlsConstrain Unix ownership, capabilities, service filesystem/network view and external reachability independently of SELinux.
  1. Inventory enforcing status, policy type, current labels, service identity, executable hash, network and expected application operations.
  2. Design distinct read, write, execute, bind and outbound access paths before generating or editing policy.
  3. Install the application with strict DAC and systemd hardening, then add persistent file and port mappings and apply them with restorecon.
  4. Generate a policy skeleton for the executable, review every source rule/interface, build and install a versioned module.
  5. Run positive and negative tests in enforcing mode, reproduce denials, correlate AVC fields and correct labels/configuration/DAC before adding policy.
  6. Monitor new AVCs and service behavior after rollout; rollback the module/mappings through versioned commands without disabling SELinux.

Assumptions

  • The host runs a maintained Red Hat Enterprise Linux 9 update with the targeted policy, SELinux enforcing, auditd healthy, and official `policycoreutils`/development tooling installed from trusted repositories.
  • The custom application has a stable executable path and documented file, socket, port, capability, child-process, network and secret behavior. Dynamic self-modifying or plugin execution is outside the initial profile.
  • The operator has console/recovery access, a tested configuration rollback, protected audit logs and a staging host matching production policy packages.
  • TCP/8443 is the declared listener; the firewall/reverse proxy controls client ingress. SELinux port labeling does not open a firewall.
  • Secrets are delivered through an approved root/service-readable mechanism and are not embedded in policy source, unit environment visible to other users, commands or audit notes.

Key concepts

Enforcing
SELinux evaluates and blocks denied operations while recording auditable AVC events. This guide never lowers the host to permissive as a repair.
Type enforcement
Policy relates a process domain, object type, object class and permission; Unix paths are mapped to labels separately.
File-context mapping
`semanage fcontext` stores a persistent path-to-type rule. `restorecon` applies the expected label; `chcon` is temporary and masks design errors.
AVC
An Access Vector Cache decision record describing allowed or denied access with source context, target context, class and requested permission.
Boolean
A documented policy switch enabling a predefined behavior. It affects only policy wired to it and may be broader than one application.
Policy interface
A reference-policy abstraction granting a known behavior more safely and compatibly than copying low-level allow rules.
Domain transition
Execution of a labeled entry point causes the process to enter a confined domain instead of remaining unconfined or inheriting another domain.

Before you copy

Values used in this guide

{{serviceName}}

Stable custom systemd service/module prefix.

Example: acme-web
{{executablePath}}

Immutable custom service executable.

Example: /usr/local/libexec/acme-web
{{contentRoot}}

Read-only static content tree.

Example: /srv/acme-web/public
{{configRoot}}

Root-owned read-only application configuration tree.

Example: /etc/acme-web
{{stateRoot}}

Writable application state tree.

Example: /var/lib/acme-web
{{logRoot}}

Dedicated application log tree.

Example: /var/log/acme-web
{{listenPort}}

Approved TCP port mapped to http_port_t.

Example: 8443
{{policyVersion}}

Version of reviewed custom policy source/module.

Example: 1.0.0

Security and production boundaries

  • SELinux supplements rather than replaces DAC, service accounts, systemd sandboxing, firewalling, patching and application authorization. A permissive Unix mode can still expose data to other unconfined processes.
  • Audit logs may include internal paths, usernames, process command names and security labels. Restrict, retain and forward them according to incident/privacy policy.
  • `audit2allow` transforms observed behavior—including attacker or test mistakes—into candidate allows. It is evidence, never an approval engine. Read `.te` source and map every permission to the application contract.
  • Broad types such as `unconfined_t`, generic writable labels or powerful booleans can erase isolation. Prefer a dedicated type and narrow reference-policy interface.
  • Policy module source, binary package, executable and service unit are privileged supply-chain artifacts; sign/review/version and deploy atomically.

Stop before continuing if

  • Stop if SELinux is disabled/permissive, audit events are lost, policy packages differ from staging, or console/rollback access is unavailable.
  • Stop if application behavior, executable identity, dynamic children/plugins, secrets, file paths, port or outbound dependencies are unknown.
  • Do not apply `chcon`, recursive generic labels, `setenforce 0`, a broad boolean, or generated allow module as a shortcut.
  • Stop when an AVC lacks a reproducible legitimate operation, when DAC/configuration is the real cause, or when a proposed rule grants unrelated class/permission/type.
  • Do not release until positive paths pass and secret-read, static-write, uploaded-exec, wrong-port and unapproved-network negative tests remain denied.
01

verification

Verify enforcing mode, policy, audit health, and current domains

read-only

Capture `getenforce`, `sestatus`, policy packages, boot arguments, audit loss/backlog, loaded local modules, custom fcontext/port/boolean changes, and the current service process context before touching labels or policy.

Why this step matters

A custom policy developed while the host is permissive proves only that logging occurred, not that required accesses are complete or negative paths remain blocked. Audit loss makes absence of AVC meaningless. Local modules, file mappings, port types and booleans can silently change the decision surface. Capturing them creates rollback evidence and prevents a new rule from compensating for an undocumented old customization. Boot arguments and policy name confirm enforcement will survive reboot. The service context, executable label and unit path establish whether a transition already exists or the application is actually running unconfined.

What to understand

Record RHEL release and SELinux userspace/policy package versions.

Check kernel boot arguments for enforcing/disabled overrides.

Require audit lost=0 and backlog below threshold before testing.

Export only local module/mapping/port/boolean differences for review.

Capture `ps -eZ`, `ls -Z` and systemd unit properties for the service.

System changes

  • None; creates protected baseline evidence.

Syntax explained

semanage ... -C
Shows local customizations relative to policy defaults.
auditctl -s
Shows kernel audit enabled/failure/lost/backlog state.
Command
getenforce && sestatus && sudo auditctl -s && sudo semodule -lfull | tail -n 20 && sudo semanage fcontext -l -C
Example output / evidence
Enforcing
SELinux status: enabled
Loaded policy name: targeted
Current mode: enforcing
enabled 1
failure 1
lost 0
backlog 0
1.0 acme_web cil

Checkpoint: Enforcement and evidence are trustworthy

test "$(getenforce)" = Enforcing && sudo auditctl -s | grep -E '^lost 0$'

Continue whenEnforcing targeted policy, no audit loss, known local changes, exact executable/unit/process identity and tested rollback.

Stop whenSELinux is not enforcing, audit lost/backlog is unsafe, or existing customizations/process identity are unknown.

If this step fails

getenforce says Permissive.

Likely causeRuntime or boot configuration lowered enforcement.

Safe checks
  • Inspect `sestatus` and kernel command line.

ResolutionRestore enforcing through the approved maintenance process before policy testing.

Audit lost is nonzero.

Likely causeKernel/userspace backlog or disk/plugin pressure dropped records.

Safe checks
  • Inspect auditd state, backlog, rate and disk.

ResolutionRepair audit capacity and repeat the entire test window.

Security notes

  • Baseline exposes security policy/topology; restrict the artifact.

Alternatives

  • Use a signed compliance collector that executes these read-only checks.

Stop conditions

  • SELinux is not enforcing, audit lost/backlog is unsafe, or existing customizations/process identity are unknown.
02

instruction

Write the application's least-privilege access contract

read-only

List every process entry point, child, file tree, read/write/create/execute permission, socket, port, capability, signal, IPC, DNS/database connection and secret operation. Separate startup, normal requests, maintenance and upgrade behavior plus explicit negative cases.

Why this step matters

SELinux rules should implement intended behavior, not replay everything observed during a test. A finite contract makes each allow rule reviewable and identifies mixed-use directories that should be redesigned. The object class matters: directory search, file read, create, rename and execute are distinct. Startup might legitimately read configuration while a request should not alter it. Maintenance tools may deserve a separate domain rather than expanding the web process. Negative cases—secret reads, static writes, uploaded execution, other ports and arbitrary network—are acceptance tests for least privilege.

What to understand

Trace systemd ExecStart and every expected child executable.

Separate immutable package/config/content from writable state/cache/log/upload.

Record Unix owner/mode and systemd sandbox for each path.

Document listener and exact outbound destinations/protocols.

Classify secrets and external side effects; exclude unknown plugins from v1.

System changes

  • None; creates the policy and test specification.

Syntax explained

object class
Kernel object kind whose permissions SELinux evaluates.
negative case
Operation that must remain denied after the policy is installed.
Command
column -t -s, security/acme-web-access-contract.csv
Example output / evidence
subject     object                         class/operation     required
acme_web_t  /etc/acme-web/*.toml           file read/open      yes
acme_web_t  /srv/acme-web/public/**         file read/open      yes
acme_web_t  /var/lib/acme-web/**            create/write       yes
acme_web_t  /etc/shadow                     file read           no
acme_web_t  tcp/8443 http_port_t            name_bind           yes
acme_web_t  uploaded content                execute             no

Checkpoint: Every future allow has a named operation

test -s security/acme-web-access-contract.csv && grep -E 'shadow|uploaded|8443' security/acme-web-access-contract.csv

Continue whenComplete positive/negative matrix with owners, DAC, paths, ports, network, children, secrets and upgrade behavior.

Stop whenThe application dynamically executes unknown code, uses undocumented paths/network/capabilities, or negative boundaries are missing.

If this step fails

Developers request full filesystem access.

Likely causeApplication writes configuration/content/runtime into one tree or behavior is undocumented.

Safe checks
  • Trace normal operations in staging and inventory file opens.

ResolutionRedesign directories and document operations before policy.

Required outbound destinations change per tenant.

Likely causeApplication architecture conflicts with host-level least-privilege egress.

Safe checks
  • Classify protocol/resolver/proxy behavior.

ResolutionUse a controlled local proxy or separate service boundary instead of arbitrary network permission.

Security notes

  • Do not place secret values in the contract.

Alternatives

  • Split maintenance/migration workers into separate services and SELinux domains.

Stop conditions

  • The application dynamically executes unknown code, uses undocumented paths/network/capabilities, or negative boundaries are missing.
03

config

Install files with strict DAC and a hardened systemd unit

caution

Create a dedicated system user, install the immutable executable and configuration, separate read-only content from writable state/logs, and declare systemd hardening. Validate ownership and modes before SELinux policy so an AVC does not hide a DAC mistake.

Why this step matters

SELinux denials and Unix permission denials both surface as EACCES. Fix DAC and service sandboxing first so the policy models the real service. An executable writable by the service would let a compromise replace its trusted entry point. Read-only configuration/content and separate writable state reduce both SELinux rule complexity and application attack surface. systemd restrictions are independent defense; if `ProtectSystem` blocks a path, adding SELinux allow will not help. The unit must be versioned and validated alongside policy because changing ExecStart can bypass domain transition.

What to understand

Use a non-login service account with no broad groups.

Keep executable/config owned by root and non-writable by service.

Use systemd-analyze security as review input, not an automatic score target.

Declare only actual writable paths and capabilities.

Keep secrets outside command arguments and world-readable environment.

System changes

  • Installs service files, account/directories and systemd unit.

Syntax explained

NoNewPrivileges
Prevents gaining privilege through exec mechanisms such as setuid/file capabilities.
ProtectSystem=strict
Makes most filesystem read-only in the service mount namespace.
File /etc/systemd/system/acme-web.service
Configuration
[Service]
User=acme-web
Group=acme-web
ExecStart=/usr/local/libexec/acme-web --config /etc/acme-web/app.toml
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/acme-web /var/log/acme-web
Command
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

sudo install -o root -g root -m 0755 build/acme-web {{executablePath}} && sudo install -d -o root -g acme-web -m 0750 {{configRoot}} {{contentRoot}} && sudo install -d -o acme-web -g acme-web -m 0750 {{stateRoot}} {{logRoot}}
Example output / evidence
-rwxr-xr-x root root /usr/local/libexec/acme-web
drwxr-x--- acme-web acme-web /var/lib/acme-web
drwxr-x--- acme-web acme-web /var/log/acme-web
ProtectSystem=strict
NoNewPrivileges=yes
ReadWritePaths=/var/lib/acme-web /var/log/acme-web

Checkpoint: DAC and systemd permit only the contract

namei -l {{executablePath}} {{configRoot}} {{contentRoot}} {{stateRoot}} {{logRoot}} && systemd-analyze verify /etc/systemd/system/acme-web.service

Continue whenRoot-owned immutable code/config, service-owned state/logs, valid unit and no hidden broad groups/capabilities/writable system paths.

Stop whenService can modify executable/config/static content, needs root, or systemd unit/path differs from contract.

If this step fails

Service gets EACCES with no AVC.

Likely causeDAC or systemd mount namespace denies it.

Safe checks
  • Use namei, systemctl show and journal.

ResolutionCorrect narrow ownership/mode/unit setting rather than SELinux.

App insists on writing beside executable.

Likely causeLayout mixes immutable code and runtime state.

Safe checks
  • Inspect write path and configuration option.

ResolutionReconfigure to stateRoot; do not make code tree writable.

Security notes

  • Do not use chmod 777 as a diagnostic or fix.

Alternatives

  • Use systemd StateDirectory/LogsDirectory to create owned runtime paths automatically.

Stop conditions

  • Service can modify executable/config/static content, needs root, or systemd unit/path differs from contract.
04

config

Inventory current labels and review the persistent mapping plan

read-only

Capture current and policy-default labels, then review anchored mappings for the executable, static content, state and logs. Do not apply mappings for custom types until the reviewed module that defines those types is installed; never use recursive `chcon` as the deployment mechanism.

Why this step matters

SELinux uses labels, not pathname strings, in runtime decisions. `semanage fcontext` registers how paths should be labeled persistently; `restorecon` applies that definition and repairs drift after copying, restore or full relabel. `chcon` changes only current xattrs and is commonly erased, creating fragile production behavior. Separate types let the domain read static content while writing only state. Regex scope must be anchored to the exact tree; a broad `/srv(/.*)?` mapping can affect unrelated services. Types referenced here must exist in the reviewed module before application.

What to understand

Record current labels and define types in policy source before adding mappings.

Use exact file rule for executable and anchored subtree expressions.

Map config, static, state and logs separately.

Review local precedence with semanage fcontext -l -C.

Install the reviewed module before applying mappings and running restorecon.

System changes

  • None; this step records the current state and the reviewed mapping plan.

Syntax explained

matchpathcon
Shows the policy-default context for a path without changing its current label.
semanage fcontext -l -C
Lists existing local mapping overrides for conflict review.
File policy/acme_web/fcontext-plan.txt
Configuration
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

{{executablePath}} -> acme_web_exec_t
{{configRoot}}(/.*)? -> acme_web_etc_t
{{contentRoot}}(/.*)? -> acme_web_content_t
{{stateRoot}}(/.*)? -> acme_web_var_lib_t
{{logRoot}}(/.*)? -> acme_web_log_t
Command
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

ls -Zd {{executablePath}} {{configRoot}} {{contentRoot}} {{stateRoot}} {{logRoot}} && matchpathcon {{executablePath}} {{configRoot}} {{contentRoot}} {{stateRoot}} {{logRoot}} && sudo semanage fcontext -l -C
Example output / evidence
system_u:object_r:bin_t:s0 /usr/local/libexec/acme-web
unconfined_u:object_r:var_t:s0 /srv/acme-web/public
unconfined_u:object_r:var_lib_t:s0 /var/lib/acme-web
unconfined_u:object_r:var_log_t:s0 /var/log/acme-web
No local custom mappings are installed yet.
Reviewed plan: executable=acme_web_exec_t, config=acme_web_etc_t, static=acme_web_content_t, state=acme_web_var_lib_t, logs=acme_web_log_t.

Checkpoint: The mapping plan is conflict-free and installable

ls -Zd {{executablePath}} {{configRoot}} {{contentRoot}} {{stateRoot}} {{logRoot}} && sudo semanage fcontext -l -C

Continue whenCurrent/default labels are recorded, exact mappings affect only intended paths, and no custom type is referenced in the live policy store before module installation.

Stop whenA regex overlaps unrelated paths, an existing local mapping conflicts, or the deployment owner cannot guarantee module-before-mapping order.

If this step fails

The proposed mapping would override an existing local rule.

Likely causeAnother application or prior release owns a more specific or overlapping expression.

Safe checks
  • Inspect all local mappings and test neighboring paths with matchpathcon.

ResolutionResolve ownership and precedence before installing either mapping.

Current labels differ across otherwise identical hosts.

Likely causeA previous chcon, local mapping, package script or restore introduced drift.

Safe checks
  • Compare ls -Z, matchpathcon, semanage local mappings and package history.

ResolutionExplain the drift and choose the authoritative mapping before relabeling.

Security notes

  • Label changes can expose/deny data across domains; review scope like filesystem permissions.

Alternatives

  • Use established standard types only when their policy semantics exactly match the contract.

Stop conditions

  • A regex overlaps unrelated paths, an existing local mapping conflicts, or the deployment owner cannot guarantee module-before-mapping order.
05

config

Map only the approved listener port to the policy port type

caution

Inspect existing port mappings and listeners, then add or modify only TCP/8443 as `http_port_t`. Verify the custom domain policy uses the reference-policy bind interface and that the firewall independently restricts clients.

Why this step matters

SELinux port types mediate bind/connect permissions independently of firewall reachability. Mapping 8443 to `http_port_t` says it belongs to the HTTP port class; the custom domain still needs the appropriate policy interface. Relabeling a port already owned by another policy can break that service. Mapping a wide range or generic unreserved ports makes policy less meaningful. Binding to loopback behind a reverse proxy further limits exposure; the firewall controls which remote clients can reach a non-loopback listener.

What to understand

Check current port type and listener ownership before adding.

Use add only for unmapped ports and modify only with documented prior owner.

Bind application to exact address/port from configuration.

Use `corenet_tcp_bind_http_port(acme_web_t)` or equivalent reviewed interface.

Test wrong-port bind denial and firewall ingress separately.

System changes

  • Adds a persistent SELinux port mapping; service later binds to it.

Syntax explained

http_port_t
Standard SELinux port type for HTTP-family listeners.
semanage port
Persistent port-type mapping; does not open firewall or start listener.
Configuration
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

mapping="$(sudo semanage port -l | grep -w '{{listenPort}}' || true)"; if [ -n "$mapping" ]; then printf '%s
' "$mapping"; printf '%s
' "$mapping" | grep -q '^http_port_t[[:space:]]' || { echo 'STOP: port belongs to another SELinux type' >&2; exit 1; }; else sudo semanage port -a -t http_port_t -p tcp {{listenPort}}; fi; sudo ss -ltnp '( sport = :{{listenPort}} )'
Example output / evidence
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:8443 0.0.0.0:* users:(("acme-web",pid=4118,fd=7))

Checkpoint: Only TCP/8443 is the service's permitted listener

sudo semanage port -l | grep -w 8443 && sudo ss -ltnp | grep ':8443'

Continue whenExact mapping, exact bind address, expected process, firewall rule and negative wrong-port test.

Stop whenPort belongs to another service/type, application requires arbitrary ports, listener exposes unintended address, or firewall boundary is unknown.

If this step fails

Port already defined for another type.

Likely causeAnother policy/service owns 8443.

Safe checks
  • Inspect semanage mapping and service inventory.

ResolutionChoose a new approved port or coordinate exact type change; never overwrite blindly.

Bind denied despite mapping.

Likely causeDomain lacks bind permission or DAC/systemd/socket ownership differs.

Safe checks
  • Correlate AVC, process domain, port type and listener conflict.

ResolutionFix the precise layer and policy interface, not global network permission.

Security notes

  • Port type is not network access control; retain firewall/reverse proxy restrictions.

Alternatives

  • Use systemd socket activation with a separately reviewed socket/service transition.

Stop conditions

  • Port belongs to another service/type, application requires arbitrary ports, listener exposes unintended address, or firewall boundary is unknown.
06

decision

Evaluate existing SELinux booleans before custom allows

read-only

Search boolean descriptions for the required operation, inspect current and persistent values, identify every domain affected, and enable a boolean persistently only when its documented scope exactly matches the contract. Otherwise keep it off and author a narrower policy.

Why this step matters

Booleans are maintained policy switches and are preferable to custom low-level rules when they exactly model a standard domain operation. But they can affect every process in a domain and grant a family of access broader than one custom service. A boolean named for `httpd` does not automatically affect `acme_web_t`; policy must reference it. `setsebool -P` writes persistent policy state and can take time. Do not toggle booleans until success appears and leave them undocumented. Record current and persistent state, affected rules/domains, business need and rollback.

What to understand

Read semanage description and policy source/conditional rules.

Confirm whether the custom domain participates in the boolean.

Compare granted network/file classes with exact contract.

Use a staging test and negative cases before persistent change.

Record and monitor local boolean customizations.

System changes

  • None in this evaluation step; an approved persistent toggle would change policy state.

Syntax explained

getsebool
Shows runtime boolean values.
semanage boolean -l
Shows current/default and descriptive policy intent.
Command
sudo semanage boolean -l | grep -Ei 'httpd|network|database' | head -n 20 && getsebool -a | grep '^httpd_'
Example output / evidence
httpd_can_network_connect_db (off, off) Determine whether HTTP scripts and modules can connect to databases.
httpd_can_network_connect    --> off
httpd_can_network_connect_db --> off

Checkpoint: Boolean choice is explicit and bounded

sudo semanage boolean -l -C

Continue whenEvery customized boolean has an owner, documented affected domains/permissions, positive/negative tests and rollback; irrelevant broad booleans remain off.

Stop whenScope cannot be explained, boolean does not govern custom domain, or it grants generic outbound/write capability beyond contract.

If this step fails

Enabling a suggested boolean changes nothing.

Likely causeThe denial's domain is not governed by that conditional policy.

Safe checks
  • Inspect scontext and conditional policy.

ResolutionRevert the boolean and address label/custom-domain policy.

Boolean fixes app but expands unrelated HTTP services.

Likely causeShared standard domain makes scope broader than intended.

Safe checks
  • Inventory processes in affected domain and test new access.

ResolutionRevert and use dedicated domain/rule for the custom app.

Security notes

  • Boolean changes are policy changes and should be audited like module deployment.

Alternatives

  • Use a dedicated type/interface allow when no standard conditional exactly matches.

Stop conditions

  • Scope cannot be explained, boolean does not govern custom domain, or it grants generic outbound/write capability beyond contract.
07

verification

Reproduce one operation and correlate its complete AVC event

read-only

Run one named test in enforcing mode, capture the application error and UTC interval, search AVC/USER_AVC by service executable or process, group all records by audit event ID, and interpret source context, target context, object class and requested permission before proposing any change.

Why this step matters

An AVC is a policy decision, while an application error may have several causes. All audit records sharing the serial form one event: SYSCALL provides executable, identity, arguments and result; AVC provides policy tuple. `permissive=0` confirms enforcement. Interpret the requested permission in the context of the test contract: a denied write to static content is a successful negative test, not a problem. Searching an overly broad day of events can mix unrelated services and attacker behavior. Narrow by time, executable/comm, PID and event serial and retain raw protected evidence plus a redacted summary.

What to understand

Record exact test ID, expected outcome and UTC start/end.

Use ausearch raw grouping before `-i` interpretation.

Confirm source domain and target type/classes.

Match request to positive/negative access contract.

Check audit lost/backlog before concluding no AVC exists.

System changes

  • Executes one bounded application test; expected denial changes no data.

Syntax explained

scontext/tcontext/tclass
Subject domain, target type and kernel object class evaluated by SELinux.
permissive=0
Decision was enforced, not merely logged.
Command
sudo ausearch -m AVC,USER_AVC -ts recent -c acme-web -i | tail -n 40
Example output / evidence
type=AVC msg=audit(1785259801.422:1882): avc: denied { write } for pid=4118 comm="acme-web" name="index.html" dev="dm-0" ino=28103 scontext=system_u:system_r:acme_web_t:s0 tcontext=system_u:object_r:acme_web_content_t:s0 tclass=file permissive=0
type=SYSCALL msg=audit(1785259801.422:1882): arch=x86_64 syscall=openat success=no exit=EACCES exe=/usr/local/libexec/acme-web

Checkpoint: The denial is understood as a policy tuple and operation

sudo ausearch -a 1882 --raw | grep -E 'type=(AVC|SYSCALL)'

Continue whenOne complete event maps to test, executable/domain, object/type/class/permission and legitimate or negative contract outcome.

Stop whenEvent/time/process mapping is uncertain, audit loss exists, target is mislabeled, or denied operation is intentionally forbidden.

If this step fails

ausearch returns multiple unrelated AVCs.

Likely causeTime/key/process filter is too broad.

Safe checks
  • Filter by event serial, executable, comm, PID and test timestamp.

ResolutionAnalyze one event/operation at a time.

AVC shows permissive=1.

Likely causeA domain or host is permissive.

Safe checks
  • Check `semanage permissive -l`, getenforce and module flags.

ResolutionRemove unauthorized permissive exception and repeat enforcing tests.

Security notes

  • Raw events can expose paths and users; redact external reports.

Alternatives

  • Use `journalctl _COMM=acme-web` plus audit serial when audit logs are forwarded centrally.

Stop conditions

  • Event/time/process mapping is uncertain, audit loss exists, target is mislabeled, or denied operation is intentionally forbidden.
08

decision

Classify denial as labeling, DAC/configuration, boolean, or policy gap

read-only

Use `matchpathcon`, `ls -Z`, `namei`, `audit2why` as an explanation aid, service configuration and the access contract to decide whether the target has the wrong label, the app is using the wrong path, Unix/systemd denies it, a precise standard boolean/interface exists, or a narrow custom rule is truly missing.

Why this step matters

`audit2why` can describe why current policy denied a tuple, but cannot know business intent. Many denials are caused by copying files with wrong labels, using `/home` or `/tmp` unexpectedly, pointing configuration to a read-only tree, systemd sandboxing, or ordinary DAC. Adding policy can hide these faults. `matchpathcon` compares current and persistent expected labels; `namei` reveals every DAC path component. If the operation is forbidden by the contract, the correct fix is application/configuration and a regression test. Only an evidenced legitimate operation with correct label/DAC/configuration becomes a policy gap.

What to understand

Verify expected label and regex provenance.

Check every DAC path component and mount/systemd restrictions.

Compare configured path with designed read/write tree.

Evaluate standard policy interface/boolean semantics.

Document why no broader existing type is appropriate before custom allow.

System changes

  • None; records cause and chosen layer.

Syntax explained

audit2why
Explains current denial reasons; it does not approve permissions.
matchpathcon -V
Checks current label against persistent expected mapping.
Command
matchpathcon -V /srv/acme-web/public/index.html && namei -l /srv/acme-web/public/index.html && sudo ausearch -a 1882 --raw | audit2why
Example output / evidence
/srv/acme-web/public/index.html verified.
type=AVC ... denied { write } ...
Was caused by:
  Missing type enforcement (TE) allow rule.
Contract decision: DENY — static content is intentionally read-only.
Action: fix application output path; no policy change.

Checkpoint: The fix targets the actual layer

grep -E 'Cause:|Contract decision:|Action:' security/avc-1882-decision.md

Continue whenCause is one of negative-test/config/DAC/systemd/label/boolean/policy with evidence and narrow remediation.

Stop whenDecision relies only on generated allow text or the operation lacks a contract owner.

If this step fails

restorecon fixes the denial only until next deployment.

Likely causeDeployment recreates files with wrong location/label behavior.

Safe checks
  • Inspect package/install process and fcontext mapping.

ResolutionMake deployment run restorecon/package labeling and test it.

DAC is correct but systemd denies write.

Likely causeReadWritePaths/ProtectSystem mount namespace excludes it.

Safe checks
  • Inspect unit properties and /proc/PID/mountinfo.

ResolutionAdjust exact systemd path if contract permits; do not add SELinux rule.

Security notes

  • Keep forbidden-denial evidence as a regression test rather than suppressing it.

Alternatives

  • Redesign file placement or split services instead of expanding a powerful process domain.

Stop conditions

  • Decision relies only on generated allow text or the operation lacks a contract owner.
09

command

Generate a source skeleton from the executable, not a blanket allow log

caution

On staging, use `sepolicy generate --init` for the stable executable to create `.te`, `.if`, `.fc` and setup artifacts. Treat them as scaffolding: rename types consistently, remove unused permissions, replace low-level allows with reference-policy interfaces, and keep packaging/service changes under separate review.

Why this step matters

A generated skeleton supplies conventional domain/entry types, interfaces and packaging structure, but it cannot know least privilege or application architecture. Generating from a stable executable is safer than feeding an uncontrolled AVC stream to `audit2allow -M`. Review every generated macro and permission; remove behavior not in the contract. `.fc` can carry file mappings into a policy package, while local semanage mappings may be appropriate for local deployment—choose one ownership model and avoid drift. Version source, not only the compiled `.pp`, so future policy changes are reviewable.

What to understand

Generate only on matching staging policy/tool versions.

Diff every file and remove generic home/tmp/network/capability rules.

Define separate content/state/log types and public interfaces only when other domains need access.

Use reference policy interfaces for standard runtime/library/network behaviors.

Add comments linking each rule to contract/test ID.

System changes

  • Creates policy source files in protected staging; does not install them.

Syntax explained

--init
Generates policy scaffolding for an init/systemd-style daemon.
.te/.if/.fc
Type enforcement, interfaces and file-context source artifacts.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

mkdir -m 0700 policy/acme_web && cd policy/acme_web && sepolicy generate --init {{executablePath}}
Example output / evidence
Created the following files:
/secure/policy/acme_web/acme_web.te
/secure/policy/acme_web/acme_web.if
/secure/policy/acme_web/acme_web.fc
/secure/policy/acme_web/acme_web_selinux.spec
/secure/policy/acme_web/acme_web.sh

Checkpoint: Generated source contains only reviewable scaffolding

grep -Rne 'allow\|interface\|type ' policy/acme_web

Continue whenVersioned source with stable types, narrow interfaces, explicit mappings and contract references; no compiled-only or log-derived blanket policy.

Stop whenGenerator adds unknown capabilities/network/home/tmp access, executable path is mutable, or source cannot be rebuilt from trusted tooling.

If this step fails

Generated policy is enormous.

Likely causeObserved/generated behavior or application architecture is too broad.

Safe checks
  • Compare source to access contract and split responsibilities.

ResolutionDiscard and author minimal module/split service domains.

Generated file contexts conflict with semanage locals.

Likely causeTwo deployment ownership models define same paths.

Safe checks
  • Compare `.fc`, semanage -C and matchpathcon precedence.

ResolutionChoose package-owned or local mapping and remove duplicate safely.

Security notes

  • Generated scripts may perform privileged install/relabel operations; review, do not run blindly.

Alternatives

  • Author a minimal reference-policy module directly with an experienced SELinux reviewer.

Stop conditions

  • Generator adds unknown capabilities/network/home/tmp access, executable path is mutable, or source cannot be rebuilt from trusted tooling.
10

config

Review, compile, and inspect the minimal custom module

caution

Write domain/type declarations and only contract-backed interfaces/allows, compile using the RHEL SELinux development Makefile, inspect source and resulting package metadata, run policy analysis and peer review, install the module on staging, and only then apply its persistent mappings and relabel the intended paths.

Why this step matters

Reference-policy macros express stable intent and often include directory traversal/manage semantics correctly, but they still grant real permissions and must be expanded/reviewed where risk warrants. `manage_files_pattern` is appropriate only for the dedicated state type, never static content. Port binding uses the standard HTTP port type and exact semanage mapping. The module example is deliberately small; a real daemon needs runtime/library/log/systemd interfaces discovered from the contract and controlled tests. Installing a module changes kernel policy immediately, so stage it, record priority/version/hash and retain a removable previous package.

What to understand

Review expanded permissions and type/class wildcards.

Keep write/manage only on dedicated state/log types.

Use standard logging/name-service/runtime interfaces as needed.

Run source lint/analysis and peer review on matching policy package.

Sign/hash compiled artifact and install only staging before production.

System changes

  • Compiles and installs a custom SELinux module, persistent file-context mappings and intended labels on staging.

Syntax explained

init_daemon_domain
Defines transition from executable entry type into daemon domain.
manage_files_pattern
Grants create/read/write/rename/unlink for files of one dedicated type.
File policy/acme_web/acme_web.te
Configuration
policy_module(acme_web, 1.0.0)
type acme_web_t;
type acme_web_exec_t;
init_daemon_domain(acme_web_t, acme_web_exec_t)
type acme_web_etc_t;
files_config_file(acme_web_etc_t)
type acme_web_content_t;
files_type(acme_web_content_t)
type acme_web_var_lib_t;
files_type(acme_web_var_lib_t)
type acme_web_log_t;
logging_log_file(acme_web_log_t)
read_files_pattern(acme_web_t, acme_web_etc_t, acme_web_etc_t)
read_files_pattern(acme_web_t, acme_web_content_t, acme_web_content_t)
manage_files_pattern(acme_web_t, acme_web_var_lib_t, acme_web_var_lib_t)
manage_files_pattern(acme_web_t, acme_web_log_t, acme_web_log_t)
allow acme_web_t self:tcp_socket create_stream_socket_perms;
corenet_tcp_bind_http_port(acme_web_t)
Command
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

make -f /usr/share/selinux/devel/Makefile acme_web.pp && sudo semodule -i acme_web.pp && sudo semanage fcontext -a -t acme_web_exec_t '{{executablePath}}' && sudo semanage fcontext -a -t acme_web_etc_t '{{configRoot}}(/.*)?' && sudo semanage fcontext -a -t acme_web_content_t '{{contentRoot}}(/.*)?' && sudo semanage fcontext -a -t acme_web_var_lib_t '{{stateRoot}}(/.*)?' && sudo semanage fcontext -a -t acme_web_log_t '{{logRoot}}(/.*)?' && sudo restorecon -RFv {{executablePath}} {{configRoot}} {{contentRoot}} {{stateRoot}} {{logRoot}} && sudo semodule -lfull | grep acme_web
Example output / evidence
Compiling targeted acme_web module
Creating targeted acme_web.pp policy package
Relabeled /usr/local/libexec/acme-web from bin_t to acme_web_exec_t
Relabeled /etc/acme-web/app.toml from etc_t to acme_web_etc_t
Relabeled /srv/acme-web/public/index.html from var_t to acme_web_content_t
Relabeled /var/lib/acme-web/state.db from var_lib_t to acme_web_var_lib_t
Relabeled /var/log/acme-web/service.log from var_log_t to acme_web_log_t
400 acme_web 1.0.0 pp
Policy review: every expanded interface/allow maps to the access contract; 0 wildcard types/classes/permissions

Checkpoint: Every expanded permission maps to contract and test

sudo semodule -lfull | grep acme_web && matchpathcon -V {{executablePath}} {{configRoot}} {{contentRoot}} {{stateRoot}} {{logRoot}} && sudo semanage fcontext -l -C | grep acme_web && sha256sum policy/acme_web/acme_web.te acme_web.pp

Continue whenReviewed version/hash installed, domain transition/types/interfaces exact, no wildcard/broad write/network/capability, prior artifact and removal procedure retained.

Stop whenAny rule cannot be justified, compiled artifact differs from reviewed source, module shadows another type, or rollback package is absent.

If this step fails

Module compiles but service still unconfined.

Likely causeEntry mapping/transition/ExecStart mismatch.

Safe checks
  • Check ls -Z, unit ExecStart, ps -Z and module.

ResolutionCorrect exact entry transition rather than granting unconfined domain.

Policy macro expands more broadly than expected.

Likely causeInterface semantics exceed custom need.

Safe checks
  • Inspect interface definition/expanded policy.

ResolutionUse a narrower interface or explicit minimal rule with documentation.

Security notes

  • Treat `.pp` as privileged code and verify it comes from reviewed source.

Alternatives

  • Package policy as an RPM with source, mappings and upgrade scripts for fleet consistency.

Stop conditions

  • Any rule cannot be justified, compiled artifact differs from reviewed source, module shadows another type, or rollback package is absent.
11

verification

Run positive and negative tests in enforcing mode

caution

Start the service, confirm domain, execute startup/health/static/state/log/rotation/upgrade cases, then attempt secret reads, static writes, upload execution, wrong-port bind, unrelated signals and unapproved network. Correlate every expected denial and require zero unexplained AVCs.

Why this step matters

Positive-only testing rewards broad policy: an unconfined service passes every positive case. Negative tests prove containment. Keep the host enforcing and audit healthy, exercise one case at a time and map event IDs. Test service lifecycle and upgrade because new executable paths/temporary files can break transitions. A denied attack simulation should produce the expected application failure/AVC without data change. An explicit `dontaudit` can hide denials, so test observable outcomes as well as log presence. Re-run after restorecon and reboot to prove persistence.

What to understand

Confirm process domain after each restart/upgrade.

Exercise all contract phases with realistic concurrency.

Use synthetic non-sensitive targets for negative tests.

Require audit lost=0 and classify every AVC.

Repeat after restorecon, policy reload and reboot.

System changes

  • Runs the service and controlled tests; positive state writes use synthetic fixtures.

Syntax explained

positive tests
Prove required business operations under confinement.
negative tests
Prove forbidden boundaries remain enforced.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo systemctl restart {{serviceName}} && ps -eZ | grep acme_web_t && ./tests/selinux-contract --service {{serviceName}} --positive --negative
Example output / evidence
system_u:system_r:acme_web_t:s0 4118 ? acme-web
PASS health/static/config read
PASS state create/update and log append
PASS deny /etc/shadow read
PASS deny static content write
PASS deny uploaded file execute
PASS deny TCP/9443 bind
PASS no unexplained AVC; audit lost=0

Checkpoint: The service works because policy is narrow, not disabled

test "$(getenforce)" = Enforcing && ./tests/selinux-contract --assert-report security/selinux-test.json

Continue whenAll required operations pass, all forbidden operations deny, service domain exact, no unexplained AVC, audit healthy after restorecon/reboot.

Stop whenAny negative succeeds, domain is unconfined/permissive, audit loses records, or an AVC is accepted without contract mapping.

If this step fails

Positive test passes but no domain is shown.

Likely causeService exited/restarted or test hit another backend.

Safe checks
  • Check PID/cgroup/endpoint and unit logs.

ResolutionBind test to exact local instance and prove domain.

Negative deny has no AVC.

Likely causeAnother layer denied first or dontaudit/log loss exists.

Safe checks
  • Check app errno, DAC/systemd/firewall and audit health.

ResolutionRecord actual enforcing layer; use narrow diagnostic only when SELinux evidence is required.

Security notes

  • Do not use real secrets or production destructive paths in negative tests.

Alternatives

  • Run the test matrix in an isolated VM snapshot before fleet rollout.

Stop conditions

  • Any negative succeeds, domain is unconfined/permissive, audit loses records, or an AVC is accepted without contract mapping.
12

verification

Deploy atomically, monitor AVCs, and retain an enforcement-safe rollback

caution

Package policy, mappings, unit and application with hashes; canary one host; verify labels, port, boolean and domain before traffic; monitor AVC/audit loss and application health; then roll out gradually. Rollback removes the exact module/mappings or restores prior version and relabels—never disables SELinux.

Why this step matters

Policy, labels, port mapping, booleans, unit and executable form one security release. Partial ordering can start an unconfined or broken service. A canary catches differences in policy packages, local customizations and paths. Monitor AVC rate and audit health: a quiet log with dropped events is not success. Rollback depends on whether the previous module types still label files; remove mappings and restorecon in a reviewed order so objects do not retain orphan/custom labels. Lowering enforcement hides both regression and attack behavior and is never rollback.

What to understand

Record source/artifact hashes, versions and policy package baseline.

Install policy/types before mappings and service start.

Canary with complete positive/negative/reboot tests.

Alert on new AVC, module/fcontext/port/boolean changes and audit loss.

Test prior-module reinstall/removal and label restoration in staging.

System changes

  • Deploys security/application artifacts across hosts and may roll them back.

Syntax explained

semodule -r
Removes exact custom module; must coordinate labels/service state.
restorecon
Reapplies remaining expected labels after mapping/module rollback.
Command
sudo semodule -lfull | grep acme_web && sudo semanage fcontext -l -C | grep acme_web && sudo ausearch -m AVC -ts recent -c acme-web -i
Example output / evidence
400 acme_web 1.0.0 pp
/srv/acme-web/public(/.*)? all files system_u:object_r:acme_web_content_t:s0
<no matches>
Canary health PASS; audit lost=0; negative tests PASS

Checkpoint: Fleet converges with enforcement and reversible policy

test "$(getenforce)" = Enforcing && sudo auditctl -s | grep '^lost 0' && ./tests/selinux-contract --positive --negative

Continue whenAll hosts run signed version/domain/labels/port/boolean, no unexplained AVC/loss, health and negative tests pass, rollback rehearsal succeeds.

Stop whenArtifact/hash/local policy differs, canary AVC or negative test fails, audit loss/backlog rises, or rollback would require permissive mode.

If this step fails

Rollback leaves files with unknown custom types.

Likely causeMappings/module removed in wrong order.

Safe checks
  • Keep service stopped and inspect semanage/matchpathcon/ls -Z.

ResolutionRestore prior module or remove exact mapping and restorecon to known types.

AVC rate spikes only under traffic.

Likely causeTest missed a legitimate path or attack/malformed requests exercise forbidden behavior.

Safe checks
  • Correlate event IDs with routes and contract, preserving negative security intent.

ResolutionPause rollout; fix app/config/label or narrowly review policy.

Security notes

  • Forward module/mapping/boolean/enforcement changes to tamper-resistant audit storage.

Alternatives

  • Use signed RPM policy packages and configuration management for ordered fleet deployment.

Stop conditions

  • Artifact/hash/local policy differs, canary AVC or negative test fails, audit loss/backlog rises, or rollback would require permissive mode.

Finish line

Verification checklist

Enforcement and domaingetenforce && ps -eZ | grep acme_web_tEnforcing and exact custom process domain.
Persistent labelsmatchpathcon -V {{executablePath}} {{contentRoot}} {{stateRoot}}Every path matches persistent expected type after restorecon/reboot.
Policy behavior./tests/selinux-contract --positive --negativeAll required operations pass and all forbidden operations deny.
Audit healthsudo auditctl -s && sudo ausearch -m AVC -ts recent -c acme-web -ilost=0, safe backlog and no unexplained AVC.

Recovery guidance

Common problems and safe checks

Service reports permission denied but no AVC exists.

Likely causeDAC, systemd sandbox, mount flags, application logic or a non-SELinux LSM caused the failure.

Safe checks
  • Check `ausearch -m AVC`, Unix ownership/mode, unit sandbox and mount options.

ResolutionFix the actual layer; do not add SELinux policy without an AVC/policy explanation.

Files revert to the wrong label after restorecon.

Likely causeOnly `chcon` changed the current xattr or the persistent fcontext regex is wrong/overridden.

Safe checks
  • Run `matchpathcon -V`, `semanage fcontext -l -C` and inspect regex precedence.

ResolutionCorrect the persistent mapping and apply `restorecon`; remove temporary labels.

The service process remains unconfined.

Likely causeExecutable entry type, unit path, module installation or domain transition rule does not match.

Safe checks
  • Inspect `ps -eZ`, `ls -Z` executable, unit ExecStart and `semodule -lfull`.

ResolutionCorrect entry-point mapping/module and restart only after source review.

The app cannot bind TCP/8443.

Likely causePort lacks `http_port_t`, policy lacks the bind interface, another process owns it, or DAC capability/systemd socket design differs.

Safe checks
  • Use `semanage port -l`, `ss -ltnp`, AVC and policy source.

ResolutionMap exact port and grant the matching interface only if the application contract requires it.

audit2why reports a boolean could permit access.

Likely causeA standard policy switch covers the denial but its broader behavior may exceed the need.

Safe checks
  • Read `semanage boolean -l` description and inspect affected policy/other services.

ResolutionEnable persistently only after scope review; otherwise write a dedicated narrow policy.

A generated module includes write access to the entire /var tree.

Likely causeLearning traffic or mislabeled targets caused overbroad generated rules.

Safe checks
  • Review `.te`, target contexts and application test plan.

ResolutionDiscard generation, fix labels, and author narrow types/interfaces from the contract.

No AVC appears during repeated denial.

Likely causeAudit suppression/rate/backlog loss, dontaudit rule or wrong time/host filter hides evidence.

Safe checks
  • Check `auditctl -s`, lost/backlog, journal and targeted temporary diagnostic procedure.

ResolutionRestore audit health and use the documented narrow diagnostic method; never leave dontaudit disabled globally.

Policy works in staging but not production.

Likely causePolicy package, file layout, executable hash, unit, booleans, ports or local customizations differ.

Safe checks
  • Diff `sestatus`, `semodule -lfull`, fcontext/port/boolean customizations and deployment artifacts.

ResolutionConverge the environment or update the reviewed policy for the documented difference.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use an existing vendor policy and supported standard labels/booleans when the application matches it exactly; custom policy adds maintenance responsibility.
  • Run the workload in an additional container boundary with an SELinux container type, while still preserving host enforcement and reviewed volume labels.
  • Place writable uploads outside static/executable trees and serve them through a non-executing application path rather than granting mixed read/write/execute permissions.

Operate it safely

  • Package the policy source and persistent mappings with the application release and test them in CI against the current RHEL 9 policy.
  • Add regression tests for every allowed operation and every deliberate denial, including application upgrades and new plugins.
  • Forward AVC and policy-change audit events to protected monitoring with alert ownership and privacy-aware retention.

Recovery

Rollback

Keep enforcement active. Stop and fence the service, restore the previous signed application/unit/policy package, restore prior fcontext/port/boolean customizations, run restorecon on only owned paths, restart, and repeat positive/negative/audit checks. If removing the custom module, coordinate label mappings first so no orphan types remain.

  1. Pause rollout and preserve AVC, module, mapping, port, boolean, unit and artifact hashes.
  2. Stop the exact service; do not change global enforcing mode.
  3. Reinstall the previous reviewed module or restore the previous source-built package.
  4. Restore exact previous fcontext and port mappings and boolean states.
  5. Run restorecon only on application-owned paths and verify matchpathcon.
  6. Restore previous application/unit, daemon-reload and restart canary.
  7. Run full positive/negative/audit-loss checks before reopening traffic.
  8. Remove failed module artifacts only after no process/object depends on their types.

Evidence

Sources and review

Verified 2026-07-24Review due 2027-01-20
RHEL 9 Using SELinuxofficialRHEL 9 custom application SELinux policyofficialRHEL 9 non-standard SELinux configurationsofficialRHEL 9 Security hardening audit systemofficial