RHEL

Security-Enhanced Linux (SELinux) is a robust security framework that enforces mandatory fine-grained access controls on Red Hat Enterprise Linux systems. When managing server fleets and running containers, the deployment of customized SELinux policies becomes essential for maintaining a strong security posture.

Custom SELinux policies empower organizations to customize access controls according to their unique requirements. Although Linux distributions come with default SELinux policies, they may not address all the use cases or applications within an organization's environment. Deploying custom policies allows you to establish precise rules that align with your infrastructure and applications.

This is the second article in our SELinux series. The first article described SELinux basics. This article demonstrates the creation and deployment of custom SELinux policies across server fleets and containerized environments, emphasizing the associated benefits and best practices.

How to implement a custom policy

Prerequisites:

The following example describes the steps to create custom policies for granting dedicated directory and file access to users and how to modify a policy file in the file's permissions and labels or context.

1. Install the necessary dependency packages: policycoreutils, policycoreutils-devel, setools-console, setroubleshoot. Additionally, create a private_files directory to consolidate all content in one location.

$mkdir private_files && cd private_files

$sudo yum install policycoreutils policycoreutils-devel setools-console setroubleshoot -y
Package policycoreutils-2.9-24.el8.x86_64 is already installed.
Package policycoreutils-devel-2.9-24.el8.x86_64 is already installed.
Package setools-console-4.3.0-3.el8.x86_64 is already installed.
Package setroubleshoot-3.3.26-5.el8.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!

2. Create a policy module file called private_files.te, where .te signifies type enforcement. Copy and paste the following content into the terminal to create the private_files.te file.

cat <<'EOF' >> private_files.te
policy_module(private_files, 1.0)
require {
type unconfined_t;
type setroubleshootd_t;
}
type private_files_t;
fs_associate(private_files_t);
allow unconfined_t private_files_t:{dir file} relabelto;
allow setroubleshootd_t private_files_t:{ dir file } getattr;
EOF
  • policy_module: Module name.
  • require: Specify the domains that must be defined within the policy.
  • type: Declares new type.
  • fs_associate: Associate type with filesystem.
  • allow: Define specific allow rules for permitted actions while blocking all other actions.

Establish a symbolic link to the makefile using these commands. This will ensure that the make command references the correct file.

# ln -s /usr/share/selinux/devel/Makefile.
# ls -l
total 4
lrwxrwxrwx. 1 root root  33 Aug 30 11:04 Makefile -> /usr/share/selinux/devel/Makefile
-rwxrwxrwx. 1 root root 283 Aug 30 10:59 private_files.te

3. Compile the policy files using the make command. This will produce a private_files.pp output file, which reads private_files.te and generates all required dependency files.

# make private_files.pp
Compiling targeted private_files module
Creating targeted private_files.pp policy package
rm tmp/private_files.mod tmp/private_files.mod.fc

# ls
Makefile  private_files.fc  private_files.if  private_files.pp  private_files.te  tmp

4. Install the compiled file using the semodule -i command:

# semodule -i private_files.pp

5. Create a file to test the policies and assign full read and write permissions to it.

# mkdir -m 777 /private

6. Impose the label to a private directory using the chcon command:

[root@SElinux private_files]# chcon -t private_files_t /private

[root@SElinux private_files]# ls -Z /private
ls: cannot access '/private': Permission denied

Your statement is clear and effectively conveys the expected behavior of the custom policy.

Fix the permissions

7. Make changes in the policy file to grant access to the file and directory using the getattr (get attribute) syntax.

policy_module(private_files, 1.0)
require {
type unconfined_t;
type setroubleshootd_t;
}

type private_files_t;
fs_associate(private_files_t);
allow unconfined_t private_files_t:{dir file} { relabelto getattr };
allow setroubleshootd_t private_files_t:{ dir file } getattr;

8. As shown below, compile a new policy file with the make command.

[root@SElinux private_files]# make private_files.pp
Compiling targeted private_files module
Creating targeted private_files.pp policy package
rm tmp/private_files.mod tmp/private_files.mod.fc

9. Uninstall the previous modules using the following command:

[root@SElinux private_files]# semodule -r private_files
libsemanage.semanage_direct_remove_key: Removing last private_files module (no other private_files module exists at another priority).

10. Deploy the recently compiled.pp file by utilizing the -i flag.

[root@SElinux private_files]# semodule -i private_files.pp

11. To modify the label or context of a private folder, employ the chcon command.

[root@SElinux private_files]# chcon -t private_files_t /private

Inspect the labels of the following directory by employing the -Z flag.

[root@SElinux private_files]# ls -Z /private
ls: cannot open directory '/private': Permission denied

12. Determine the root cause of the issue using the ausearch command, which conducts audits to identify problems and provide resolutions.

[root@SElinux private_files]# ausearch -m avc -ts recent | audit2allow
#============= setroubleshootd_t ==============
#!!!! This avc is allowed in the current policy
allow setroubleshootd_t private_files_t:dir getattr;

#============= unconfined_t ==============

#!!!! This avc is allowed in the current policy
allow unconfined_t private_files_t:dir getattr;
allow unconfined_t private_files_t:dir read;

13. Transition SELinux to the permissive mode by executing the following command:

[root@SElinux private_files]# setenforce 0

14. Develop a bash script to facilitate all filesystem-related tasks seamlessly.

cat << EOF >> test_script.sh
ls -lZ /private
chmod 1777 /private
touch /private/file1
echo hello > /private/file1
echo hello >> /private/file1
chmod o= /private/file1
mkdir /private/dir1
rmdir /private/dir1
rm -f /private/file1
chcon -t unconfined_t /private
EOF

15. Set permissions of the bash script to execute it using the bash command.

[root@SElinux private_files]# chmod 777 test_script.sh
[root@SElinux private_files]# bash test_script.sh
total 0

16. Conduct another audit of the activity within the private directory and determine the resolution.

[root@SElinux private_files]# ausearch -m avc -ts recent | audit2allow
#============= unconfined_t ==============
allow unconfined_t private_files_t:dir { add_name create open read relabelfrom remove_name rmdir search setattr write };
allow unconfined_t private_files_t:file { append create open setattr unlink write };
allow unconfined_t self:dir relabelto;

17. Apply the policy resolutions to the private_files.te file by executing the following command:

[root@SElinux private_files]# ausearch -m avc -ts recent | audit2allow >> private_files.te

18. After updating private_files.te, it will appear as follows:

[root@SElinux private_files]# cat private_files.te
policy_module(private_files, 1.0)
require {
type unconfined_t;
type setroubleshootd_t;
}

type private_files_t;
fs_associate(private_files_t);
allow unconfined_t private_files_t:{dir file} { relabelto getattr };
allow setroubleshootd_t private_files_t:{ dir file } getattr;
allow unconfined_t private_files_t:dir { add_name create open read relabelfrom remove_name rmdir search setattr write };
allow unconfined_t private_files_t:file { append create open setattr unlink write };
allow unconfined_t self:dir relabelto;

[Note: Remove the #== unconfined_t == line from the private_files.te file. ]

19. Recompile the updated private_file.pp file once more to pull the newly made changes in the system.

[root@SElinux private_files]# make private_files.pp
Compiling targeted private_files module
Creating targeted private_files.pp policy package
rm tmp/private_files.mod tmp/private_files.mod.fc

20. Uninstall the previous modules using the semodule -r command.

[root@SElinux private_files]# semodule -r  private_files
libsemanage.semanage_direct_remove_key: Removing last private_files module (no other private_files module exists at another priority).

21. Apply the latest updates from private_file.pp.

[root@SElinux private_files]# semodule -i private_files.pp

22. Attempt to relabel it once more for a private directory.

[root@SElinux private_files]# chcon -t private_files_t /private

23. Re-enable the enforcing mode and assess the accessibility of directories and files.

[root@SElinux private_files]# setenforce 1

24. Check the file permissions using the following command:

[root@SElinux private_files]# ls -lZd /private
drwxrwxrwt. 2 root root unconfined_u:object_r:private_files_t:s0 6 Aug 30 11:44 /private

25. As anticipated, we can now access the files and directories and make changes as needed. After manipulating the SELinux policies.

[root@SElinux private_files]# touch /private/file1
[root@SElinux private_files]# echo hello > /private/file1
[root@SElinux private_files]# cat /private/file1
hello

Deploy SELinux configurations to multiple systems

To uphold the highest level of security practices, it is essential to keep the server updated and maintain consistent security configurations. However, manually performing this process can be time-consuming and labor-intensive. To address the challenges associated with such scenarios, the semanage utility proves invaluable. This utility enables the effortless import and export of configuration files (.mode), simplifying the management of security configurations.

1. Before proceeding, please install the following dependency packages.

#yum install policycoreutils-python-utils

2. Use semanage to generate the configuration file with a.mod extension.

#semanage export -f./my-selinux-settings.mod

3. Use semanage to generate the configuration file with a.mod extension. To transfer.mod files across fleets of servers, various methods such as FTP, SFTP, SCP, and others can be employed. In this instance, we will utilize the scp command.

#scp./my-selinux-settings.mod new-system-hostname:
Or
#scp -t server_a.pem my-selinux-settings.mod user@servera:/home/user/

4. After transferring the files, access the new server using the SSH command.

#ssh root@server_a
Or
#ssh -i server.pem user@server_a

5. Use the semanage command to import the configuration file onto the new system.

#semanage import -f./my-selinux-settings.mod

[Note: This shared configuration file is compatible only with specific OS versions, such as RHEL 9 to RHEL 9 and RHEL 8 to RHEL 8.]

Apply SELinux to the containers

SELinux policies can be generated for containers using the udica package in the UBI8 image. The udica package enables you to create a customized security policy for precise control over a container's access to host system resources, including storage, devices, and the network. This capability helps prevent security violations and simplifies regulatory compliance when deploying containers.

1. Before proceeding, please install the necessary dependency packages.

#yum install -y udica

2. Install a containerization tool such as Podman or Docker. We will be using Podman as the container tool.

# yum install podman -y

3. Launch the container using a UBI8 image with volume mounts for the /home directory (read-only permissions) and the /var/spool directory (read and write permissions). Additionally, expose port 80.

#podman run --env container=podman -v /home:/home:ro -v /var/spool:/var/spool:rw -p 80:80 -dit ubi8 bash

4. Inspect the running container using the provided Podman command and collect the CONTAINER ID.

#podman ps

5. Gather all running container policies into a .json file.

# podman inspect 567a363etfle > container.json

# udica -j container.json my_container
Policy my_container with container id 567a363etfle created!

6. Load the policy module from the udica output in the previous step.

#semodule -i my_container.cil /usr/share/udica/templates/{base_container.cil,net_container.cil,home_container.cil}

7. Stop the container using the podman stop command, and then start it again with the --security-opt label=type:my_container.process option.

#podman stop 567a363etfle

#podman run --security-opt label=type:my_container.process -v /home:/home:ro -v /var/spool:/var/spool:rw -p 80:80 -dit ubi8 bash

Validating SELinux in containers

8. Verify that the container is running with the my_container.process type, and access the running container using exec.

# ps -efZ | grep my_container.process

# podman exec -it 567a363etfle bash

9. Verify that SELinux is functioning properly. Proceed to perform vulnerability testing activities within the running container.

Install the nmap-ncat package in the container and attempt to redirect port 80 to port 2567.

[root@567a363etfle]# cd /var/spool
[root@567a363etfle]# yum install nmap-ncat
[root@567a363etfle]# nc -lvp 80
...
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
[root@567a363etfle]# nc -lv- 2567
...
Ncat: bind to :::2567: Permission denied. QUITTING.

As expected, SELinux is successfully preventing security risk activities within the containers as well.

Find more resources

For a deeper and practical understanding of Red Hat Enterprise Linux, you can engage in thoughtfully curated hands-on labs by Red Hat. Red Hat Universal Base Images (UBI) are container-based and operating system images with complementary runtime languages and packages. Try Red Hat UBI on curated Red Hat UBI hands-on lab.

In this article, we have meticulously crafted bespoke SELinux policies and seamlessly deployed them across an extensive fleet of servers. Additionally, we have seamlessly integrated these policies into containers leveraging the UBI8 container image.

Furthermore, you have the option to obtain tailored Red Hat Enterprise Linux images designed for AWS, Google Cloud Platform, Microsoft Azure, and VMware, facilitating their seamless deployment on your chosen platform.