This repository contains a tool for generating SELinux security profiles for containers. The whole concept is based on "block inheritence" feature inside CIL intermediate language supported by SELinux userspace. The tool creates a policy which combines rules inherited from specified CIL blocks(templates) and rules discovered by inspection of container JSON file, which contains mountpoints and ports definitions.
The name of this tool is derived from the Slovak word "udica" *\[uɟit͡sa\]*, which means "fishing rod". It is a reference to the saying *"Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime."* Here udica is the fishing rod that allows you to get the fish (container policy) yourself, instead of always having to ask your local fisherman (SELinux expert) to catch (create) it for you ;)
This tool is still in early phase of development. Any feedback, ideas, pull requests are welcome. We're still adding new features, parameters and policy blocks which could be used.
## Proof of concept
Tool was created based on following PoC where process of creating policy is described:
It would be great to restrict this access and allow container bind just on tcp port *21* or with the same label.
## Creating SELinux policy for container
To create policy for container, it's necessary to have running container for which a policy will be generated. Container from previous chapter will be used.
Let's find container id using *podman ps* command:
# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
37a3635afb8f docker.io/library/fedora:latest bash 15 minutes ago Up 15 minutes ago heuristic_lewin
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:
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).
# 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.
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.
For example a container using port *21* will also be given access to ports *989* and *990* by *udica*, since all the listed ports share a single label.
# sudo semanage port -l | grep 21
ftp_port_t tcp 21, 989, 990
Similarly, bind mounting a sub-directory of your home directory will result in a container policy allowing access to almost all the data in the home directory, unless a non-default label is used for the mounted path.
# sudo semanage fcontext -l | grep user_home_t
/home/[^/]+/.+ all files unconfined_u:object_r:user_home_t:s0
Udica could run in OpenShift and generate SELinux policies for pods in the same instance.
[SELinux policy helper operator](https://github.com/JAORMX/selinux-policy-helper-operator) is a controller that listens to all pods in the system. It will attempt to generate a policy for pods when the pod is annotated with a specific tag "generate-selinux-policy" and the pod is in a running state. In order to generate the policy, it spawns a pod with the [selinux-k8s](https://github.com/JAORMX/selinux-k8s) tool which uses udica to generate the policy. It will spit out a configmap with the appropriate policy.
* It's not possible to detect capabilities used by container in docker engine, therefore you *have to* use '-c' to specify capabilities for docker container manually.
* It's not possible to generate custom local policy using "audit2allow -M" tool from AVCs where source context was generated by udica. For this purpose please use '--append-rules' option.
* In some situations udica fails to identify which container engine is used, therefore "--container-engine" parameter has to be used to inform udica how JSON inspection file should be parsed.