Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: containers/udica
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 483b98026f224d29f36d23aa333f07d9cc57e13c
Choose a base ref
..
head repository: containers/udica
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 87e86822093934702cd60546f6a20da961692cb9
Choose a head ref
Showing with 4,890 additions and 120 deletions.
  1. +50 −0 README.md
  2. +1 −0 setup.py
  3. +24 −0 tests/test_confined_abcdgilmns.cil
  4. +15 −0 tests/test_confined_cla.cil
  5. +12 −0 tests/test_confined_lb.cil
  6. +17 −0 tests/test_confined_lsid.cil
  7. +30 −5 tests/test_main.py
  8. +227 −115 udica/__main__.py
  9. +134 −0 udica/confined_user.py
  10. +4,380 −0 udica/macros/confined_user_macros.cil
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -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"]},
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
@@ -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"
@@ -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

Loading