Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to generate custom policy for a confined user #137

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,56 @@ SELinux now allows binding to tcp/udp port *21*, but not to *80*:
Ncat: SHA-1 fingerprint: 6EEC 102E 6666 5F96 CC4F E5FA A1BE 4A5E 6C76 B6DC
Ncat: bind to :::80: Permission denied. QUITTING.

## Creating SELinux policy for confined user

Each Linux user on an SELinux-enabled system is mapped to an SELinux user. By default administrators can choose between the following SELinux users when confining a user account: root, staff_u, sysadm_u, user_u, xguest_u, guest_u (and unconfined_u which does not limit the user's actions).

To give administrators more options in confining users, *udica* now provides a way to generate a custom SELinux user (and corresponding roles and types) based on the specified parameters. The new user policy is assembled using a set of predefined policy macros based on use-cases (managing network, administrative tasks, etc.).

To generate a confined user, use the "confined_user" keyword followed by a list of options:

| Option | Use case |
| ------------- | ------------- |
| -a, --admin_commands | Use administrative commands (vipw, passwd, ...) |
| -g, --graphical_login | Use graphical login environment |
| -m, --mozilla_usage | Use mozilla firefox |
| -n, --networking | Manage basic networking (ip, ifconfig, traceroute, tcpdump, ...) |
| -d, --security_advanced | Manage SELinux settings (semanage, semodule, sepolicy, ...) |
| -i, --security_basic | Use read-only security-related tools (seinfo, getsebool, sesearch, ...) |
| -s, --sudo | Run commands as root using sudo |
| -l, --user_login | Basic rules common to all users (tty, pty, ...) |
| -c, --ssh_connect | Connect over SSH |
| -b, --basic_commands | Use basic commands (date, ls, ps, man, systemctl -user, journalctl -user, passwd, ...) |

The new user also needs to be assigned an MLS/MCS level and range. These are set to `s0` and `s0:c0.c1023` respectively by default to work well in *targeted* policy mode.
For more details see [Red Hat Multi-Level Security documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/using_selinux/index#using-multi-level-security-mls_using-selinux).

```
$ udica confined_user -abcdgilmns --level s0 --range "s0:c0" custom_user

Created custom_user.cil
Run the following commands to apply the new policy:
Install the new policy module
# semodule -i custom_user.cil /usr/share/udica/macros/confined_user_macros.cil
Create a default context file for the new user
# sed -e ’s|user|custom_user|g’ /etc/selinux/targeted/contexts/users/user_u > /etc/selinux/targeted/contexts/users/custom_user_u
Map the new selinux user to an existing user account
# semanage login -a -s custom_user_u custom_user
Fix labels in the user's home directory
# restorecon -RvF /home/custom_user
```

As prompted by *udica*, the new user policy needs to be installed into the system along with the *confined_user_macros* file and a *default context* file needs to be created before the policy is ready to be used.

Last step is either assignment to an existing linux user (using `semanage login`), or specifying the new SELinux user when creating a new linux user account (no need to run `restorecon` for a new user home directory).
```
useradd -Z custom_user_u
```

The created policy defines a new SELinux user `<user_name>_u`, a corresponding role `<user_name>_r` and a list of types (varies based on selected options) `<user_name>_t, <user_name>_sudo_t, <user_name>_ssh_agent_t, ...`

See [Red Hat Confined User documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/using_selinux/managing-confined-and-unconfined-users_using-selinux#doc-wrapper) for more details about confined users, their assignment, available roles and access they allow.

## SELinux labels vs. objects they represent

Policies generated by *udica* work with **SELinux labels** as opposed to filesystem paths, port numbers etc. This means that allowing access to given path (e.g. path to a directory mounted to your container), port number, or any other resource may also allow access to other resources you didn't specify, since the same SELinux label can be assigned to multiple resources.
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
data_files=[
("/usr/share/licenses/udica", ["LICENSE"]),
("/usr/share/udica/ansible", ["udica/ansible/deploy-module.yml"]),
("/usr/share/udica/macros", ["udica/macros/confined_user_macros.cil"]),
],
# scripts=["bin/udica"],
entry_points={"console_scripts": ["udica=udica.__main__:main"]},
Expand Down
24 changes: 24 additions & 0 deletions tests/test_confined_abcdgilmns.cil
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(boolean my_container_exec_content true)
(role my_container_r)
(type my_container_dbus_t)
(type my_container_gkeyringd_t)
(type my_container_ssh_agent_t)
(type my_container_sudo_t)
(type my_container_sudo_tmp_t)
(type my_container_t)
(type my_container_userhelper_t)
(user my_container_u)
(userrole my_container_u my_container_r)
(userlevel my_container_u (s0))
(userrange my_container_u ((s0 ) (s0 (c0))))

(call confinedom_admin_commands_macro (my_container_t my_container_r my_container_sudo_t))
(call confinedom_graphical_login_macro (my_container_t my_container_r my_container_dbus_t))
(call confinedom_mozilla_usage_macro (my_container_t my_container_r))
(call confinedom_networking_macro (my_container_t my_container_r))
(call confinedom_security_advanced_macro (my_container_t my_container_r my_container_sudo_t my_container_userhelper_t))
(call confinedom_security_basic_macro (my_container_t my_container_r))
(call confinedom_sudo_macro (my_container_t my_container_r my_container_sudo_t my_container_sudo_tmp_t))
(call confinedom_user_login_macro (my_container_t my_container_r my_container_gkeyringd_t my_container_dbus_t my_container_exec_content))
(call confined_ssh_connect_macro (my_container_t my_container_r my_container_ssh_agent_t))
(call confined_use_basic_commands_macro (my_container_t my_container_r))
15 changes: 15 additions & 0 deletions tests/test_confined_cla.cil
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(boolean my_container_exec_content true)
(role my_container_r)
(type my_container_dbus_t)
(type my_container_gkeyringd_t)
(type my_container_ssh_agent_t)
(type my_container_sudo_t)
(type my_container_t)
(user my_container_u)
(userrole my_container_u my_container_r)
(userlevel my_container_u (s0))
(userrange my_container_u ((s0 ) (s0 (c0))))

(call confinedom_admin_commands_macro (my_container_t my_container_r my_container_sudo_t))
(call confinedom_user_login_macro (my_container_t my_container_r my_container_gkeyringd_t my_container_dbus_t my_container_exec_content))
(call confined_ssh_connect_macro (my_container_t my_container_r my_container_ssh_agent_t))
12 changes: 12 additions & 0 deletions tests/test_confined_lb.cil
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(boolean my_container_exec_content true)
(role my_container_r)
(type my_container_dbus_t)
(type my_container_gkeyringd_t)
(type my_container_t)
(user my_container_u)
(userrole my_container_u my_container_r)
(userlevel my_container_u (s0))
(userrange my_container_u ((s0 ) (s0 (c0))))

(call confinedom_user_login_macro (my_container_t my_container_r my_container_gkeyringd_t my_container_dbus_t my_container_exec_content))
(call confined_use_basic_commands_macro (my_container_t my_container_r))
17 changes: 17 additions & 0 deletions tests/test_confined_lsid.cil
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(boolean my_container_exec_content true)
(role my_container_r)
(type my_container_dbus_t)
(type my_container_gkeyringd_t)
(type my_container_sudo_t)
(type my_container_sudo_tmp_t)
(type my_container_t)
(type my_container_userhelper_t)
(user my_container_u)
(userrole my_container_u my_container_r)
(userlevel my_container_u (s0))
(userrange my_container_u ((s0 ) (s0 (c0))))

(call confinedom_security_advanced_macro (my_container_t my_container_r my_container_sudo_t my_container_userhelper_t))
(call confinedom_security_basic_macro (my_container_t my_container_r))
(call confinedom_sudo_macro (my_container_t my_container_r my_container_sudo_t my_container_sudo_tmp_t))
(call confinedom_user_login_macro (my_container_t my_container_r my_container_gkeyringd_t my_container_dbus_t my_container_exec_content))
35 changes: 30 additions & 5 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,26 @@ def test_device_access_podman(self):
self.assert_templates(output, ["base_container"])
self.assert_policy(test_file("test_devices.podman.cil"))

def run_udica(self, args):
# Confined user tests
def test_confined_user(self):
"""udica confined_user <args> --level s0 --range s0:c0 my_container"""
for arg in ["cla", "lb", "lsid", "abcdgilmns"]:
output = self.run_udica(
[
"udica",
"confined_user",
"-{}".format(arg),
"--level",
"s0",
"--range",
"s0:c0",
"my_container",
],
True,
)
self.assert_policy(test_file("test_confined_{}.cil".format(arg)))

def run_udica(self, args, confined=False):
with patch("sys.argv", args):
with patch("sys.stderr.write") as mock_err, patch(
"sys.stdout.write"
Expand All @@ -383,10 +402,16 @@ def store_output(output):
udica.__main__.main()
mock_err.assert_not_called()

self.assertRegex(mock_out.output, "Policy my_container created")
self.assertRegex(
mock_out.output, "--security-opt label=type:my_container.process"
)
if confined:
self.assertRegex(mock_out.output, "semodule -i my_container.cil")
self.assertRegex(
mock_out.output, "semanage login -a -s my_container_u my_container"
)
else:
self.assertRegex(mock_out.output, "Policy my_container created")
self.assertRegex(
mock_out.output, "--security-opt label=type:my_container.process"
)

return mock_out.output

Expand Down
Loading
Loading