05-07-2019, 02:22 AM
Use udica to build SELinux policy for containers
<div><p>While modern IT environments move towards Linux containers, the need to secure these environments is as relevant as ever. Containers are a process isolation technology. While containers can be a defense mechanism, they only excel when combined with SELinux.</p>
<p>Fedora SELinux engineering built a new standalone tool, <strong>udica</strong>, to generate SELinux policy profiles for containers by automatically inspecting them. This article focuses on why <em>udica</em> is needed in the container world, and how it makes SELinux and containers work better together. You’ll find examples of SELinux separation for containers that let you avoid turning protection off because the generic SELinux type <em>container_t</em> is too tight. With <em>udica</em> you can easily customize the policy with limited SELinux policy writing skills. </p>
<p> <span id="more-27556"></span> </p>
<h2>SELinux technology</h2>
<p>SELinux is a security technology that brings proactive security to Linux systems. It’s a labeling system that assigns a label to all <em>subjects</em> (processes and users) and <em>objects</em> (files, directories, sockets, etc.). These labels are then used in a security policy that controls access throughout the system. It’s important to mention that what’s not allowed in an SELinux security policy is denied by default. The policy rules are enforced by the kernel. This security technology has been in use on Fedora for several years. A real example of such a rule is:</p>
<pre class="wp-block-preformatted"><em>allow httpd_t httpd_log_t: file { append create getattr ioctl lock open read setattr };</em></pre>
<p>The rule allows any process labeled as <em>httpd_t</em><strong> </strong>to create, append, read and lock files labeled as <em>httpd_log_t</em>. Using the <em>ps</em> command, you can list all processes with their labels:</p>
<pre class="wp-block-preformatted"><em>$ ps -efZ | grep httpd</em><br /><em>system_u:system_r:httpd_t:s0 root 13911 1 0 Apr14 ? 00:05:14 /usr/sbin/httpd -DFOREGROUND</em><br /><em>...</em></pre>
<p>To see which objects are labeled as httpd_log_t, use <em>semanage</em>:</p>
<pre class="wp-block-preformatted"><em># semanage fcontext -l | grep httpd_log_t<br />/var/log/httpd(/.)? all files system_u:object_r:httpd_log_t:s0 <br />/var/log/nginx(/.)? all files system_u:object_r:httpd_log_t:s0 <br />...</em></pre>
<p>The SELinux security policy for Fedora is shipped in the <em>selinux-policy</em>RPM package.</p>
<h2>SELinux vs. containers</h2>
<p>In Fedora, the <em>container-selinux</em> RPM package provides a generic SELinux policy for all containers started by engines like<em> podman</em> or <em>docker</em>. Its main purposes are to protect the host system against a container process, and to separate containers from each other. For instance, containers confined by SELinux with the process type <em>container_t</em> can only read/execute files in<em> /usr </em>and write to <em>container_file_t</em><strong> </strong>files type on host file system. To prevent attacks by containers on each other, Multi-Category Security (MCS) is used. </p>
<p>Using only one generic policy for containers is problematic, because of the huge variety of container usage. On one hand, the default container type (<em>container_t</em>) is often too strict. For example:</p>
<ul>
<li><a href="https://silverblue.fedoraproject.org">Fedora SilverBlue</a> needs containers to read/write a user’s home directory</li>
<li><a href="https://www.fluentd.org">Fluentd</a> project needs containers to be able to read logs in the <em>/var/log</em> directory</li>
</ul>
<p>On the other hand, the default container type could be too loose for certain use cases:</p>
<ul>
<li>It has no SELinux network controls — all container processes can bind to any network port</li>
<li>It has no SELinux control on <a href="http://man7.org/linux/man-pages/man7/capabilities.7.html">Linux capabilities</a> — all container processes can use all capabilities</li>
</ul>
<p>There is one solution to handle both use cases: write a custom SELinux security policy for the container. This can be tricky, because SELinux expertise is required. For this purpose, the <em>udica</em> tool was created. </p>
<h2>Introducing udica</h2>
<p>Udica generates SELinux security profiles for containers. Its concept is based on the “block inheritance” feature inside the <a href="https://en.wikipedia.org/wiki/Common_Intermediate_Language">common intermediate language</a> (CIL) supported by SELinux userspace. The tool creates a policy that combines:</p>
<ul>
<li>Rules inherited from specified CIL blocks (templates), and </li>
<li>Rules discovered by inspection of container JSON file, which contains mountpoints and ports definitions</li>
</ul>
<p>You can load the final policy immediately, or move it to another system to load into the kernel. Here’s an example, using a container that:</p>
<ul>
<li>Mounts <em>/home</em> as read only</li>
<li>Mounts <em>/var/spool</em> as read/write</li>
<li>Exposes port <em>tcp/21</em></li>
</ul>
<p>The container starts with this command:</p>
<pre class="wp-block-preformatted"># <strong>podman run -v /home:/home:ro -v /var/spool:/var/spool:rw -p 21:21 -it fedora bash</strong></pre>
<p>The default container type (<em>container_t</em>) doesn’t allow any of these three actions. To prove it, you could use the <em>sesearch</em> tool to query that the <em>allow</em> rules are present on system:</p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s container_t -t home_root_t -c dir -p read</strong> <br /></pre>
<p>There’s no <em>allow</em> rule present that lets a process labeled as <em>container_t</em> access a directory labeled <em>home_root_t</em> (like the <em>/home</em> directory). The same situation occurs with <em>/var/spool</em>, which is labeled <em>var_spool_t:</em></p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s container_t -t var_spool_t -c dir -p read</strong><br /></pre>
<p>On the other hand, the default policy completely allows network access.</p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s container_t -t port_type -c tcp_socket</strong><br />allow container_net_domain port_type:tcp_socket { name_bind name_connect recv_msg send_msg };<br />allow sandbox_net_domain port_type:tcp_socket { name_bind name_connect recv_msg send_msg };</pre>
<h2>Securing the container</h2>
<p>It would be great to restrict this access and allow the container to bind just on TCP port <em>21</em> or with the same label. Imagine you find an example container using <em>podman ps</em> whose ID is <em>37a3635afb8f</em>:</p>
<pre class="wp-block-preformatted"># <strong>podman ps -q</strong><br />37a3635afb8f</pre>
<p>You can now inspect the container and pass the inspection file to the <em>udica</em> tool. The name for the new policy is <em>my_container</em>.</p>
<pre class="wp-block-preformatted"># <strong>podman inspect 37a3635afb8f > container.json</strong><br /># <strong>udica -j container.json my_container</strong><br />Policy my_container with container id 37a3635afb8f created!<br /><br />Please load these modules using:<br /> # semodule -i my_container.cil /usr/share/udica/templates/{base_container.cil,net_container.cil,home_container.cil}<br /><br />Restart the container with: "--security-opt label=type:my_container.process" parameter</pre>
<p>That’s it! You just created a custom SELinux security policy for the example container. Now you can load this policy into the kernel and make it active. The <em>udica</em> output above even tells you the command to use:</p>
<pre class="wp-block-preformatted"># <strong>semodule -i my_container.cil /usr/share/udica/templates/{base_container.cil,net_container.cil,home_container.cil}</strong><em><br /></em></pre>
<p>Now you must restart the container to allow the container engine to use the new custom policy:</p>
<pre class="wp-block-preformatted"># <strong>podman run --security-opt label=type:my_container.process -v /home:/home:ro -v /var/spool:/var/spool:rw -p 21:21 -it fedora bash</strong></pre>
<p>The example container is now running in the newly created <em>my_container.process</em> SELinux process type:</p>
<pre class="wp-block-preformatted"># <strong>ps -efZ | grep my_container.process</strong><br />unconfined_u:system_r:container_runtime_t:s0-s0:c0.c1023 root 2275 434 1 13:49 pts/1 00:00:00 podman run --security-opt label=type:my_container.process -v /home:/home:ro -v /var/spool:/var/spool:rw -p 21:21 -it fedora bash<br />system_u:system_r:my_container.process:s0:c270,c963 root 2317 2305 0 13:49 pts/0 00:00:00 bash</pre>
<h2>Seeing the results</h2>
<p>The command <em>sesearch </em>now shows <em>allow</em> rules for accessing <em>/home</em> and <em>/var/spool:</em></p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s my_container.process -t home_root_t -c dir -p read</strong><br />allow my_container.process home_root_t:dir { getattr ioctl lock open read search };<br /># <strong>sesearch -A -s my_container.process -t var_spool_t -c dir -p read</strong><br />allow my_container.process var_spool_t:dir { add_name getattr ioctl lock open read remove_name search write }</pre>
<p>The new custom SELinux policy also allows <em>my_container.process</em> to bind only to TCP/UDP ports labeled the same as TCP port 21:</p>
<pre class="wp-block-preformatted"># <strong>semanage port -l | grep 21 | grep ftp</strong><br /> ftp_port_t tcp 21, 989, 990<br /># <strong>sesearch -A -s my_container.process -c tcp_socket -p name_bind</strong><br /> allow my_container.process ftp_port_t:tcp_socket name_bind;</pre>
<h2>Conclusion</h2>
<p>The <em>udica</em> tool helps you create SELinux policies for containers based on an inspection file without any SELinux expertise required. Now you can increase the security of containerized environments. Sources are available on <a href="https://github.com/containers/udica">GitHub</a>, and an RPM package is available in Fedora repositories for Fedora 28 and later.</p>
<hr class="wp-block-separator" />
<p><em>Photo by </em><a href="https://unsplash.com/photos/KVG-XMOs6tw?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"><em>Samuel Zeller</em></a><em> on </em><a href="https://unsplash.com/search/photos/lockers?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"><em>Unsplash</em>.</a></p>
</div>
<div><p>While modern IT environments move towards Linux containers, the need to secure these environments is as relevant as ever. Containers are a process isolation technology. While containers can be a defense mechanism, they only excel when combined with SELinux.</p>
<p>Fedora SELinux engineering built a new standalone tool, <strong>udica</strong>, to generate SELinux policy profiles for containers by automatically inspecting them. This article focuses on why <em>udica</em> is needed in the container world, and how it makes SELinux and containers work better together. You’ll find examples of SELinux separation for containers that let you avoid turning protection off because the generic SELinux type <em>container_t</em> is too tight. With <em>udica</em> you can easily customize the policy with limited SELinux policy writing skills. </p>
<p> <span id="more-27556"></span> </p>
<h2>SELinux technology</h2>
<p>SELinux is a security technology that brings proactive security to Linux systems. It’s a labeling system that assigns a label to all <em>subjects</em> (processes and users) and <em>objects</em> (files, directories, sockets, etc.). These labels are then used in a security policy that controls access throughout the system. It’s important to mention that what’s not allowed in an SELinux security policy is denied by default. The policy rules are enforced by the kernel. This security technology has been in use on Fedora for several years. A real example of such a rule is:</p>
<pre class="wp-block-preformatted"><em>allow httpd_t httpd_log_t: file { append create getattr ioctl lock open read setattr };</em></pre>
<p>The rule allows any process labeled as <em>httpd_t</em><strong> </strong>to create, append, read and lock files labeled as <em>httpd_log_t</em>. Using the <em>ps</em> command, you can list all processes with their labels:</p>
<pre class="wp-block-preformatted"><em>$ ps -efZ | grep httpd</em><br /><em>system_u:system_r:httpd_t:s0 root 13911 1 0 Apr14 ? 00:05:14 /usr/sbin/httpd -DFOREGROUND</em><br /><em>...</em></pre>
<p>To see which objects are labeled as httpd_log_t, use <em>semanage</em>:</p>
<pre class="wp-block-preformatted"><em># semanage fcontext -l | grep httpd_log_t<br />/var/log/httpd(/.)? all files system_u:object_r:httpd_log_t:s0 <br />/var/log/nginx(/.)? all files system_u:object_r:httpd_log_t:s0 <br />...</em></pre>
<p>The SELinux security policy for Fedora is shipped in the <em>selinux-policy</em>RPM package.</p>
<h2>SELinux vs. containers</h2>
<p>In Fedora, the <em>container-selinux</em> RPM package provides a generic SELinux policy for all containers started by engines like<em> podman</em> or <em>docker</em>. Its main purposes are to protect the host system against a container process, and to separate containers from each other. For instance, containers confined by SELinux with the process type <em>container_t</em> can only read/execute files in<em> /usr </em>and write to <em>container_file_t</em><strong> </strong>files type on host file system. To prevent attacks by containers on each other, Multi-Category Security (MCS) is used. </p>
<p>Using only one generic policy for containers is problematic, because of the huge variety of container usage. On one hand, the default container type (<em>container_t</em>) is often too strict. For example:</p>
<ul>
<li><a href="https://silverblue.fedoraproject.org">Fedora SilverBlue</a> needs containers to read/write a user’s home directory</li>
<li><a href="https://www.fluentd.org">Fluentd</a> project needs containers to be able to read logs in the <em>/var/log</em> directory</li>
</ul>
<p>On the other hand, the default container type could be too loose for certain use cases:</p>
<ul>
<li>It has no SELinux network controls — all container processes can bind to any network port</li>
<li>It has no SELinux control on <a href="http://man7.org/linux/man-pages/man7/capabilities.7.html">Linux capabilities</a> — all container processes can use all capabilities</li>
</ul>
<p>There is one solution to handle both use cases: write a custom SELinux security policy for the container. This can be tricky, because SELinux expertise is required. For this purpose, the <em>udica</em> tool was created. </p>
<h2>Introducing udica</h2>
<p>Udica generates SELinux security profiles for containers. Its concept is based on the “block inheritance” feature inside the <a href="https://en.wikipedia.org/wiki/Common_Intermediate_Language">common intermediate language</a> (CIL) supported by SELinux userspace. The tool creates a policy that combines:</p>
<ul>
<li>Rules inherited from specified CIL blocks (templates), and </li>
<li>Rules discovered by inspection of container JSON file, which contains mountpoints and ports definitions</li>
</ul>
<p>You can load the final policy immediately, or move it to another system to load into the kernel. Here’s an example, using a container that:</p>
<ul>
<li>Mounts <em>/home</em> as read only</li>
<li>Mounts <em>/var/spool</em> as read/write</li>
<li>Exposes port <em>tcp/21</em></li>
</ul>
<p>The container starts with this command:</p>
<pre class="wp-block-preformatted"># <strong>podman run -v /home:/home:ro -v /var/spool:/var/spool:rw -p 21:21 -it fedora bash</strong></pre>
<p>The default container type (<em>container_t</em>) doesn’t allow any of these three actions. To prove it, you could use the <em>sesearch</em> tool to query that the <em>allow</em> rules are present on system:</p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s container_t -t home_root_t -c dir -p read</strong> <br /></pre>
<p>There’s no <em>allow</em> rule present that lets a process labeled as <em>container_t</em> access a directory labeled <em>home_root_t</em> (like the <em>/home</em> directory). The same situation occurs with <em>/var/spool</em>, which is labeled <em>var_spool_t:</em></p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s container_t -t var_spool_t -c dir -p read</strong><br /></pre>
<p>On the other hand, the default policy completely allows network access.</p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s container_t -t port_type -c tcp_socket</strong><br />allow container_net_domain port_type:tcp_socket { name_bind name_connect recv_msg send_msg };<br />allow sandbox_net_domain port_type:tcp_socket { name_bind name_connect recv_msg send_msg };</pre>
<h2>Securing the container</h2>
<p>It would be great to restrict this access and allow the container to bind just on TCP port <em>21</em> or with the same label. Imagine you find an example container using <em>podman ps</em> whose ID is <em>37a3635afb8f</em>:</p>
<pre class="wp-block-preformatted"># <strong>podman ps -q</strong><br />37a3635afb8f</pre>
<p>You can now inspect the container and pass the inspection file to the <em>udica</em> tool. The name for the new policy is <em>my_container</em>.</p>
<pre class="wp-block-preformatted"># <strong>podman inspect 37a3635afb8f > container.json</strong><br /># <strong>udica -j container.json my_container</strong><br />Policy my_container with container id 37a3635afb8f created!<br /><br />Please load these modules using:<br /> # semodule -i my_container.cil /usr/share/udica/templates/{base_container.cil,net_container.cil,home_container.cil}<br /><br />Restart the container with: "--security-opt label=type:my_container.process" parameter</pre>
<p>That’s it! You just created a custom SELinux security policy for the example container. Now you can load this policy into the kernel and make it active. The <em>udica</em> output above even tells you the command to use:</p>
<pre class="wp-block-preformatted"># <strong>semodule -i my_container.cil /usr/share/udica/templates/{base_container.cil,net_container.cil,home_container.cil}</strong><em><br /></em></pre>
<p>Now you must restart the container to allow the container engine to use the new custom policy:</p>
<pre class="wp-block-preformatted"># <strong>podman run --security-opt label=type:my_container.process -v /home:/home:ro -v /var/spool:/var/spool:rw -p 21:21 -it fedora bash</strong></pre>
<p>The example container is now running in the newly created <em>my_container.process</em> SELinux process type:</p>
<pre class="wp-block-preformatted"># <strong>ps -efZ | grep my_container.process</strong><br />unconfined_u:system_r:container_runtime_t:s0-s0:c0.c1023 root 2275 434 1 13:49 pts/1 00:00:00 podman run --security-opt label=type:my_container.process -v /home:/home:ro -v /var/spool:/var/spool:rw -p 21:21 -it fedora bash<br />system_u:system_r:my_container.process:s0:c270,c963 root 2317 2305 0 13:49 pts/0 00:00:00 bash</pre>
<h2>Seeing the results</h2>
<p>The command <em>sesearch </em>now shows <em>allow</em> rules for accessing <em>/home</em> and <em>/var/spool:</em></p>
<pre class="wp-block-preformatted"># <strong>sesearch -A -s my_container.process -t home_root_t -c dir -p read</strong><br />allow my_container.process home_root_t:dir { getattr ioctl lock open read search };<br /># <strong>sesearch -A -s my_container.process -t var_spool_t -c dir -p read</strong><br />allow my_container.process var_spool_t:dir { add_name getattr ioctl lock open read remove_name search write }</pre>
<p>The new custom SELinux policy also allows <em>my_container.process</em> to bind only to TCP/UDP ports labeled the same as TCP port 21:</p>
<pre class="wp-block-preformatted"># <strong>semanage port -l | grep 21 | grep ftp</strong><br /> ftp_port_t tcp 21, 989, 990<br /># <strong>sesearch -A -s my_container.process -c tcp_socket -p name_bind</strong><br /> allow my_container.process ftp_port_t:tcp_socket name_bind;</pre>
<h2>Conclusion</h2>
<p>The <em>udica</em> tool helps you create SELinux policies for containers based on an inspection file without any SELinux expertise required. Now you can increase the security of containerized environments. Sources are available on <a href="https://github.com/containers/udica">GitHub</a>, and an RPM package is available in Fedora repositories for Fedora 28 and later.</p>
<hr class="wp-block-separator" />
<p><em>Photo by </em><a href="https://unsplash.com/photos/KVG-XMOs6tw?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"><em>Samuel Zeller</em></a><em> on </em><a href="https://unsplash.com/search/photos/lockers?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"><em>Unsplash</em>.</a></p>
</div>