Straypenguins-Tips-Inventory

Create SELinux Policy Module for Your Own Service

Overview

This document explains how to design, implement, test, and maintain custom SELinux policy modules for your own services on RHEL/CentOS 9 systems.
You will learn how to:

Related Documents


Environment & Specification

👉 For SELinux-specific terminology, see the centralized Glossary in the README.md for this folder.


Glossary


Install Prerequisite Packages (RHEL9)

dnf install policycoreutils-devel selinux-policy-devel
# Optional:
dnf install setools-console

Decide Between Predefined Port Type or Creating Your Own

When enabling SELinux access to outbound ports, you have two options:

To check if your desired port is already associated with a type:

semanage port -l | grep -w '700[0-9]' | grep tcp

Example output:

afs3_callback_port_t           tcp      7001
afs_pt_port_t                  tcp      7002
gatekeeper_port_t              tcp      1721, 7000

If your port is not listed, or you wish to assign your own type name, see
Manage SELinux to Allow httpd to Access Port 7003/TCP
for instructions on defining or reassigning a port type.

This document proceeds with two cases:


Create Domain Type Module for Custom Executable

1. Create Policy Module Source .te File

This is the main module, defining Exec type and Domain type, with transition for systemd.
👉 Supplementary type definitions and permissions go into a separate Storage Module.

File: mysvcd.te
Replace http_port_t with httpd_wls_port_t if you use 7001/TCP.

module mysvcd 1.0;

require {
    type init_t;
    type http_port_t;
    type fs_t;
    attribute domain;
    attribute exec_type;
    attribute file_type;
    attribute non_auth_file_type;
    attribute non_security_file_type;
    class filesystem getattr;
    class tcp_socket { create connect name_connect getopt };
    class process { transition };
    class file { open read write append execute create unlink map getattr entrypoint };
    class dir { open read write getattr search add_name remove_name };
    class lnk_file read;
    role system_r;
}

type mysvcd_t, domain;

type mysvcd_exec_t;
typeattribute mysvcd_exec_t exec_type, file_type, non_auth_file_type, non_security_file_type;

type mysvcd_opt_t;
typeattribute mysvcd_opt_t file_type, non_auth_file_type, non_security_file_type;

# Entrypoint & transition
role system_r types mysvcd_t;
allow init_t mysvcd_exec_t:file { read open execute map };
type_transition init_t mysvcd_exec_t:process mysvcd_t;
allow init_t mysvcd_t:process transition;

# Entrypoint and full access to service executable for transition and execution
allow mysvcd_t mysvcd_exec_t:file { entrypoint read open execute map getattr };

# Access to own libraries and symlinks
allow mysvcd_t mysvcd_opt_t:file { read open execute map getattr };
allow mysvcd_t mysvcd_opt_t:lnk_file read;

# Network
allow mysvcd_t http_port_t:tcp_socket name_connect;
allow mysvcd_t self:tcp_socket { create connect getopt };

# Filesystem (e.g., /):
allow mysvcd_t fs_t:filesystem getattr;

# Service package home access
allow mysvcd_t mysvcd_opt_t:dir { read search write add_name remove_name getattr open };
allow mysvcd_t mysvcd_opt_t:file { read write append open create unlink map getattr };

2. Build and Install Domain Module

checkmodule -M -m -o mysvcd.mod mysvcd.te
semodule_package -o mysvcd.pp -m mysvcd.mod
semodule -v -X 300 -i mysvcd.pp
semodule -lfull | grep mysvcd

3. Label Your Executable and Package Home Directory

semanage fcontext -a -t mysvcd_exec_t "/opt/mysvc/mysvcd"
restorecon -Fv /opt/mysvc/mysvcd

semanage fcontext -a -t mysvcd_opt_t "/opt/mysvc(/.*)?"
restorecon -FRv /opt/mysvc/

Create a Supplementary Module (Storage Module)

This guide recommends a modular SELinux policy design:

💡 Best Practice:
Handle access to variable/shared directories in a dedicated supplementary module to keep policy maintainable, flexible, and easier to troubleshoot.

1. Create Policy Module Source .te for Storage

File: mysvcd_storage.te

For accessing files/directories with predefined/shared labels:

module mysvcd_storage 1.0;

require {
    type mysvcd_t;
    type var_log_t;
    class dir { read search write add_name remove_name };
    class file { read write append open create unlink };
}

allow mysvcd_t var_log_t:dir { read search write add_name remove_name };
allow mysvcd_t var_log_t:file { read write append open create unlink };

Or, for dedicated labels for your service’s variable files/directories:

module mysvcd_storage 1.0;

require {
    type mysvcd_t;
    class dir { read search write add_name remove_name };
    class file { read write append open create unlink };
}

# Define a dedicated type for all (or split by purpose, see above)
type mysvcd_var_t;
files_type(mysvcd_var_t)

allow mysvcd_t mysvcd_var_t:dir { read search write add_name remove_name };
allow mysvcd_t mysvcd_var_t:file { read write append open create unlink };

💡 Type Granularity:
The module example above uses one single catch-all type mysvcd_var_t for all variable data. Alternatively if you want stricter access control, you can define more granular types, e.g., mysvcd_var_log_t for logs, mysvcd_var_cache_t for cache, etc.

2. Build and Install Storage Module

checkmodule -M -m -o mysvcd_storage.mod mysvcd_storage.te
semodule_package -o mysvcd_storage.pp -m mysvcd_storage.mod
semodule -v -X 300 -i mysvcd_storage.pp
semodule -lfull | grep mysvcd_storage

3. Label Variable Directories and Files

semanage fcontext -a -t mysvcd_var_t "/var/log/mysvc(/.*)?"
semanage fcontext -a -t mysvcd_var_t "/var/cache/mysvc(/.*)?"
semanage fcontext -a -t mysvcd_var_t "/var/lib/mysvc(/.*)?"
restorecon -FRv /var/log/mysvc/ /var/cache/mysvc/ /var/lib/mysvc/

You can build, install, and update these modules independently as your package requirements evolve.


Start with Systemd and Verify

Start your service via systemd, and check the running process label:

systemctl start mysvcd.service
ps -Z -C mysvcd

Expected: mysvcd_t should appear in the process label.

Check for SELinux denials using ausearch:

ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -su mysvcd_t

For more audit log search options and tips, see the Audit Log Search Cheat Sheet.

If any denials are observed, start diagnostics; consult the related document:
SELinux Policy Troubleshooting: Resolving Audit Denials for a Custom Service


Uninstalling Policy Modules (When Needed)

When you need to remove your custom SELinux policy modules, follow this procedure for a clean uninstall.

Considerations Before Committing Module Removal

1. Stop the Service

Stop and disable the service to ensure no processes are using the policy modules.

systemctl stop mysvcd
systemctl disable mysvcd

2. Remove the Storage Policy Module

In this example, the supplementary module mysvcd_storage depends on the main module mysvcd.
The mysvcd_storage module contains permission rules such as allow mysvcd_t ..., where mysvcd_t is a type/domain defined in the main module.
The main module cannot be safely removed until the storage module is uninstalled.

2-1. Reset File Labels

Remove any custom file context assignments and restore default SELinux labels for data directories.

semanage fcontext -d "/var/log/mysvcd"
semanage fcontext -d "/var/log/mysvc(/.*)?"
restorecon -RFv /var/log/mysvcd

Verify the reset:

ls -ldZ /var/log/mysvcd
ls -lZ /var/log/mysvcd

2-2. Uninstall the Storage Policy Module

Remove the storage-related policy module.

semodule -v -X 300 -r mysvcd_storage
semodule -lfull | grep mysvcd_storage

3. Remove the Main Policy Module

Now proceed to remove your main custom policy module.

3-1. Reset File Labels

Reset custom file types for executables and primary package directories.
This is required if your main policy module defined its own file types.

restorecon -Fv /opt/mysvc/bin/mysvcd
chcon -t bin_t /opt/mysvc/bin/mysvcd

semanage fcontext -d "/opt/mysvc/bin/mysvcd"
semanage fcontext -d "/opt/mysvc(/.*)?"

restorecon -RFv /opt/mysvc

Verify the reset:

ls -ldZ /opt/mysvc
ls -lZ /opt/mysvc

3-2. Uninstall the Main Policy Module

Remove the main policy module.

semodule -v -X 300 -r mysvcd
semodule -lfull | grep mysvcd