From Fedora Project Wiki
No edit summary
 
(48 intermediate revisions by 6 users not shown)
Line 1: Line 1:
= Creating Own Product Policies =
= Creating Custom Product Policies =


In Fedora, there is a lot of applications and daemons which require customized SELinux security policy. The former approach with providing all policies only as a part of the system has been enhanced by the option to create own product policy.
In Fedora, there is a lot of applications and daemons which require customized SELinux security policy. The former approach with providing all policies only as a part of the system has been enhanced by the option to create custom product policy.


This chapter is dedicated to shipping an own SELinux security module as a subpackage for a daemon or an application.
With the possibility to create custom product policy, required changes in a policy can be released immediately, so the product package maintainer does not need to wait for another SELinux policy package release. In other words, a product SELinux policy is always synchronized with the corresponding product (package).


= Benefits =
This chapter is dedicated to shipping custom SELinux security module as a subpackage for a daemon or an application.


Shipping custom SELinux policy together with product bring several benefits:
{{admon/important|Responsibility|SELinux policy maintainers are not responsible for bugs in customized SELinux policies.}}


* Changes in policy can be modified immediately, so product package maintainer don't need to wait why SELinux policy will be updated.
* Policy changes in product SELinux policy can be released together with changes in product package so SELinux policy will be always synchronized with product.
* Product package can follow different timeline deadlines then SELinux policy package, this can cause issues and customer can get new product package version without nessesary changes in SELinux policy and this can block some functionality of the product.


= Independent SELinux Policy =
= Decentralised SELinux Policy =


While considering own product policy, a product maintainer has two options:
While considering custom product policy, a product maintainer has two options:


* Write own SELinux policy from scratch and ask SELinux team for policy review. Note that a guide how to write an SELinux policy from the scratch is not a part of this chapter (See <code>sepolicy generate</code> tool) .
* Write his own SELinux policy from scratch and ask SELinux team for policy review. Note that a guide how to write an SELinux policy from scratch is not a part of this chapter (See <code>sepolicy generate</code> tool).
* Extract an SELinux policy from a distribution policy package. The Git repository with distribution policies is located on [https://github.com/fedora-selinux/selinux-policy github.com/fedora-selinux/selinux-policy] and [https://github.com/fedora-selinux/selinux-policy-contrib github.com/fedora-selinux/selinux-policy-contrib].
* Extract an SELinux policy from a distribution policy package. The Git repository with distribution policies is located on [https://github.com/fedora-selinux/selinux-policy/tree/rawhide/policy/modules/contrib https://github.com/fedora-selinux/selinux-policy].


<blockquote>'''Note'''
{{admon/important|License|Distribution policies have GPL license, so any policy extracted from Distribution policy must have a GPL compatible license.}}


Red Hat is not responsible for bugs in customized SELinux policies.
= Agreement workflow =
</blockquote>
= Extraction Process =


== Agreement workflow ==
Before you start with shipping custom product policies, let the SELinux team know about your intentions. To do this, use SELinux Fedora mailing list or contact SELinux policy maintainer:


Before you start with shipping own product policies, let the Red Hat SELinux team know about your intentions. To do this, use Fedora mailing list or contact SELinux policy maintainer:
* [mailto:selinux-policy-maintainers@fedoraproject.org SELinux Policy maintainer]
* [mailto:selinux@lists.fedoraproject.org selinux@lists.fedoraproject.org]


* [mailto:lvrabec@redhat.com SELinux Maintainer ]
= Preparing sources for the Policy repository =
* [mailto:selinux@lists.fedoraproject.org  selinux@lists.fedoraproject.org ]


== Git Repository setup ==
It is recommended to create a Git repository for the SELinux policy sources.


A product maintainer should prepare a Git repository for SELinux policy sources. Red Hat recommends, to set up custom Git repository for the policy.
Corresponding policy module can than be extracted from [https://github.com/fedora-selinux/selinux-policy/tree/rawhide/policy/modules/contrib selinux-policy repository]. If there is no policy for the product, new policy should be created in this step and added to the repository.


<pre># Create directory to contain the project
Please make sure the policy sources follow [https://github.com/TresysTechnology/refpolicy/wiki/StyleGuide SELinux policy Style Guide].
$ mkdir myapp-selinux
$ cd myapp-selinux
# initialize git repository
$ git init
# Push git repository to remote e.g. to github.com
$ git remote add origin git@github.com:username/myapp-selinux
$ git push -u origin master
            </pre>
After created Git repository for custom policy sources, SELinux policy maintainer makes a pull request with the customized policy. If there is no policy for a product, a new policy should be created in this step and added to the mentioned repository.


<blockquote>'''Note'''
When the custom policy is ready, the product maintainer should create a build script (e.g. Makefile), attach a license file and make sure the policy compiles properly.
 
With writing a new SELinux policy, please contact the Red Hat SELinux team for reviews. Currently, the contact person for SELinux policy reviews is [mailto: lvrabec@redhat.com lvrabec@redhat.com].
</blockquote>
When a git repository with already contains a product SELinux policy, the product maintainer should create a subpackage for it. See [[#sec-preparing_git_repo|section_title]]
 
The final step of an extraction process is removing the product policy from the distribution Git repository. This should be done when the independent SELinux subpackage for the product is ready. See [[#sec-removing_product_policy|section_title]]
 
= Preparing sources for the Policy Git Repository =


== License ==
== License ==


A Git repository should not contain only SELinux policy source files, but also a <code>license</code>. For more information how to add an open source license in your repository, see the [https://help.github.com/articles/adding-a-license-to-a-repository/ Adding a license to a repository] article on the GitHub Help. Distribution policies have GPL license, so any policy extracted from Distribution policy must have GPL compatible license.
A Git repository should not contain only SELinux policy source files, but also a <code>license</code>. For more information how to add an open source license in your repository, see the [https://help.github.com/articles/adding-a-license-to-a-repository/ Adding a license to a repository] article on the GitHub Help. Distribution policies have GPL license, so any policy extracted from Distribution policy must have a GPL compatible license.


== Makefile ==
== Makefile ==


To compile a product policy, you also need a <code>makefile</code>, for example:
To compile a product policy, you can use a <code>makefile</code>, for example (automatically generated by <code>sepolicy generate</code>):


<pre>TARGET?=myapp
<pre>TARGET?=myapp
MODULES?=${TARGET:=.pp.bz2}
MODULES?=${TARGET:=.pp.bz2}
SHAREDIR?=/usr/share
SHAREDIR?=/usr/share
SELINUXTYPE?=targeted


all: ${TARGET:=.pp.bz2}
all: ${TARGET:=.pp.bz2}
Line 90: Line 69:


install: man
install: man
     install -D -m 644 ${TARGET}.pp.bz2 ${DESTDIR}${SHAREDIR}/selinux/packages/${TARGET}.pp.bz2
     install -D -m 644 ${TARGET}.pp.bz2 ${DESTDIR}${SHAREDIR}/selinux/packages/${SELINUXTYPE}/${TARGET}.pp.bz2
    install -D -m 644 ${TARGET}.if ${DESTDIR}${SHAREDIR}/selinux/devel/include/contrib/${TARGET}.if
     install -D -m 644 ${TARGET}_selinux.8 ${DESTDIR}${SHAREDIR}/man/man8/</pre>
     install -D -m 644 ${TARGET}_selinux.8 ${DESTDIR}${SHAREDIR}/man/man8/</pre>
<blockquote>'''Note'''


TARGET should contain a name of your product policy.
If you choose not to use a Makefile, replace the <code>make</code> command in spec file with the following:
</blockquote>
 
<pre>make -f %{_datadir}/selinux/devel/Makefile %{modulename}.pp
bzip2 -9 %{modulename}.pp</pre>
 
== Policy source examples ==
== Policy source examples ==


Line 103: Line 83:
<pre>$ cat myapp.te
<pre>$ cat myapp.te
policy_module(myapp,1.0)
policy_module(myapp,1.0)
 
type myapp_t;
type myapp_t;
type myapp_exec_t;
type myapp_exec_t;
init_daemon_domain(myapp_t, myapp_exec_t)
init_daemon_domain(myapp_t, myapp_exec_t)
 
# Grant myapp_t the signal privilege
# Grant myapp_t the signal privilege
allow myapp_t self:process { signal };
allow myapp_t self:process { signal };
 
$ cat myapp.fc
$ cat myapp.fc
/sbin/myapp --  gen_context(system_u:object_r:myapp_exec_t,s0)
/sbin/myapp --  gen_context(system_u:object_r:myapp_exec_t,s0)
 
$ cat myapp.if
$ cat myapp.if
##
##
My app service.</pre>
My app service.</pre>
<blockquote>'''Note'''


Every name of an interface defined in a third-party module should have a prefix of given module name (e.g. interface allowing read of myapp data would be called ''myapp_''read_logs()).
</blockquote>
<blockquote>'''Note'''
All context definitions should be in policy file context (*.fc) file, not added using <code>semange fcontext</code> command.
</blockquote>
<blockquote>'''Note'''
A third-party module (a module shipped by a product team) must not change existing interfaces.
</blockquote>
The SELinux policy Git repository should contain the following files (replace myapp with a name of your product):
The SELinux policy Git repository should contain the following files (replace myapp with a name of your product):


<pre>$ ls
<pre>$ ls
Makefile  myapp.fc  myapp.if  myapp.te LICENSE</pre>
Makefile  myapp.fc  myapp.if  myapp.te COPYING</pre>
 
== Compiling custom policy ==
== Compiling custom policy ==


To compile an own and already-prepared policy, use the <code>make</code> command:
To compile finished policy, use the <code>make</code> command:


<pre>$ make
<pre>$ make
Line 149: Line 119:
Compressing myapp.pp -&gt; myapp.pp.bz2
Compressing myapp.pp -&gt; myapp.pp.bz2
bzip2 -9 myapp.pp</pre>
bzip2 -9 myapp.pp</pre>
After a succesful compilation, make an archive containing your policy:
After a succesful compilation, make an archive containing your policy:


<pre>$ cd ../
<pre>$ cd ../
$ tar -czf myapp-selinux.tar.gz myapp-selinux/</pre>
$ tar -czf myapp-selinux.tar.gz myapp-selinux/</pre>
== Using custom interfaces ==
== Using custom interfaces ==


It's important to note that distribution policies ''should not'' use interfaces from removable policy modules, and ''stub'' interfaces should be used instead. Stub interface is defined in distribution module and requires types from the removable module as opposed to the normal approach which would cause the distribution module to be dependant on the removable module.
{{admon/warning|Custom interface naming|All custom interfaces ''must'' be prefixed with "ipp_" not to be confused with distribution interfaces.}}
 
Interfaces in your policy module (.if file) allow other policy modules to gain access to your package's resources when needed. Whenever you define a new interface, it is necessary to ship your interface file as part of your policy package (rpm) so that other policy packages can use it.
 
<pre>%install
install -D -p -m 644 selinux/%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
 
%files
%{_datadir}/selinux/devel/include/distributed/%{modulename}.if</pre>
 
All custom interfaces (that is, interfaces that are not part of distribution policy) ''must'' be prefixed with &quot;ipp_&quot; not to be confused with distribution interfaces.
 
Changes to interfaces of the original module can only be delivered via distribution selinux-policy-* packages. If such a change is necessary, please contact the SELinux team, or submit a pull request. Please bear in mind that such changes will influence other policy modules that use given interface.
 
== Backwards compatibility ==
 
The most common problem with using custom policies on older distributions is undefined interfaces.
<pre>
Compiling targeted nagios module
selinux/nagios.te:374:ERROR 'syntax error' at token 'sssd_signull' on line 19406:
    sssd_signull(nrpe_t)
</pre>
 
This issue can be resolved by conditionally defining the missing interface. To do this, find definition of the missing interface in [https://github.com/fedora-selinux/selinux-policy-contrib SELinux-policy-contrib] or [https://github.com/fedora-selinux/selinux-policy SELinux-policy] repository, copy it to your interface file and enclose in an ''ifndef'' statement.
 
Example using ''sssd_signull'' (necessary to use this interface in epel8):
<pre>
########################################
#
# Interface compatibility blocks
#
# The following definitions ensure compatibility with distribution policy
# versions that do not contain given interfaces (epel, or older Fedora
# releases).
# Each block tests for existence of given interface and defines it if needed.
#
 
########################################
## <summary>
##    Allow caller to signull sssd.
##    Backport from RHEL8
## </summary>
## <param name="domain">
##    <summary>
##    Domain allowed access.
##    </summary>
## </param>
#
ifndef(`sssd_signull',`
  interface(`sssd_signull',`
      gen_require(`
          type sssd_t;
      ')
 
      allow $1 sssd_t:process signull;
  ')
')
</pre>
 
The same syntax can be used to define an interface from another custom policy module you rely on. Make sure to also add a BuildRequires if that is the case (the "ifndef" syntax should only be used in case you cannot ensure presence of the interface using BuildRequires alone).
 
== Moving type/attribute/alias definitions ==
{{admon/warning|Contact SELinux team| Moving definitions between modules is usually not advised and you should consult any such changes with the SELinux team beforehand.}}
 
Whenever a type,attribute or alias definition is moved between modules (this is usually done when two modules are merged together, or some distinct part of a policy is moved to a separate module) it is necessary to include the following steps in your custom policy installation:
 
* Disable the distribution version of affected module(s) before calling <code>%selinux_modules_install</code>
** <code>semodule -d <module_name> &> /dev/null || true;</code>
* Re-enable the original policy modules after <code>%selinux_modules_uninstall</code>
** <code>semodule -e <module_name> &> /dev/null || true;</code>
 
{{admon/important|<code>&> /dev/null &#124;&#124; true</code>|This part is needed because the commands are expected to fail in some situations (e.g. during update of the custom policy package, or when the changes where also adopted in the distribution policy).}}
 
These steps are necessary to avoid type, attribute or alias redefinition errors, which may cause the custom package installation to fail.
Example of such error:
 
<pre>
Running scriptlet: freeipa-selinux-4.8.6-1.fc33.noarch                            2/4
Re-declaration of type ipa_custodia_t
Failed to create node
Bad type declaration at /var/lib/selinux/targeted/tmp/modules/100/ipa_custodia/cil:1
/usr/sbin/semodule:  Failed!
</pre>
 
== File contexts and equivalency rules ==
 
File context can be specified either by mapping labels to specific paths
in ".fc" policy files
(eg. <code>/usr/bin/cdcc -- gen_context(system_u:object_r:cdcc_exec_t,s0)</code>),
or by setting file path equivalency
(eg. <code>semanage fcontext -a -e /home /export/home</code>).
The latter approach mirrors labelling structure of the source directory to the target.
There is only a handful of file path equivalencies in the distribution policy,
but it is important to take them into consideration
whenever a file context rule is edited or added.
 
File context rules must not reference paths
that are labelled according to an equivalency
(the new context must be assigned to the original path --
source of the equivalency, not to the target).
 
== Custom policy modules and distribution policy ==
 
It’s important to note that distribution policies ''should not'' use interfaces from removable policy modules.
 
When using types from custom policy modules ''stub'' interfaces should be used instead of directly requiring given type. Stub interface is defined and used in distribution module as follows.


<pre>$ cat distribution_module.if
<pre>$ cat distribution_module.if
Line 182: Line 259:
...
...
optional_policy(`
optional_policy(`
     distro_stup()
     distro_stub()
     allow distro_t myapp_log_t:file read_file_perms;
     allow distro_t myapp_log_t:file read_file_perms;
')
')
...
...
...
...</pre>
    </pre>
 
As with any type defined outside of ''SELinux policy base modules'', ''optional_policy'' block must be used when using types from removable modules in distribution policy.
 
= Creating the Spec File =
= Creating the Spec File =


When a Git repository with SELinux policy sources is ready, create your product .spec file (rpmbuild configuration file).
When a Git repository with SELinux policy sources is ready, create your product .spec file (rpmbuild configuration file).
Please note that this section focuses on ''targeted'' SELinux mode. In case you intend to use the custom policy in multiple SELinux modes, please see [https://fedoraproject.org/wiki/SELinux/IndependentPolicy_MLS Custom Product Policies targeting multiple SELinux modes]


== The Preamble ==
== The Preamble ==
Line 198: Line 279:
<pre># defining macros needed by SELinux
<pre># defining macros needed by SELinux
%global selinuxtype targeted
%global selinuxtype targeted
%global selinux_policyver VERSION # e.g: 3.13.1-212
%global moduletype contrib
%global modulename myapp</pre>
%global modulename myapp</pre>
Then it is necessary to fill in all the information about the subpackage such as a name, a version, a license, and so on.
Then it is necessary to fill in all the information about the subpackage such as a name, a version, a license, and so on.


Line 210: Line 290:
Summary: SELinux policies for product
Summary: SELinux policies for product
Source0: # archive with SELinux policy sources. e.g: myapp-selinux.tar
Source0: # archive with SELinux policy sources. e.g: myapp-selinux.tar
Requires: selinux-policy-%{selinuxtype}
Requires(post): selinux-policy-%{selinuxtype}
BuildRequires: selinux-policy-devel
BuildArch: noarch
BuildArch: noarch
Requires: selinux-policy &gt;= %{selinux_policyver}
%{?selinux_requires}
BuildRequires: git
BuildRequires: pkgconfig(systemd)
BuildRequires: selinux-policy
BuildRequires: selinux-policy-devel
Requires(post): selinux-policy-base &gt;= %{selinux_policyver}
Requires(post): libselinux-utils
Requires(post): policycoreutils
%if 0%{?fedora}
Requires(post): policycoreutils-python-utils
%else
Requires(post): policycoreutils-python
%endif
 
%description
%description
SELinux policy modules for product.</pre>
SELinux policy modules for product.</pre>
== The %prep and %install Section ==
 
= The %prep and %install Section =


The following part of the .spec file describes the way a product policy is compiled and installed:
The following part of the .spec file describes the way a product policy is compiled and installed:
Line 242: Line 313:
%install
%install
# install policy modules
# install policy modules
install -d %{buildroot}%{_datadir}/selinux/packages
install -D -m 0644 %{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
install -d -p %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}
install -D -p -m 0644 selinux/%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
install -p -m 644 %{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}
%check</pre>
install -m 0644 %{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages


%check</pre>
After this step, a product policy is installed on your system.
After this step, a product policy is installed on your system.


Line 255: Line 324:


<pre>%post
<pre>%post
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{modulename}.pp.bz2
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2


%postun
%postun
Line 263: Line 332:


%posttrans
%posttrans
%selinux_relabel_post -s %{selinuxtype}</pre>
In case you are shipping a daemon or service that uses the custom policy,
relabelling needs to take place sooner (%post/%postun sections),
to ensure the custom labels are in place before the daemon/service starts.
<pre>%post
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%selinux_relabel_post -s %{selinuxtype}
%selinux_relabel_post -s %{selinuxtype}
</pre>
 
if [ "$1" -le "1" ]; then # First install
  # the daemon needs to be restarted for the custom label to be applied
  %systemd_postun_with_restart %{modulename}.service
fi
 
%postun
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
    %selinux_relabel_post -s %{selinuxtype}
fi</pre>
 
== The %files Section ==
== The %files Section ==


The end of the .spec file contains the <code>%files</code> section. This section declares which files and directories are owned by the package. Last part of spec file is changelog.
The end of the .spec file contains the <code>%files</code> section. This section declares which files and directories are owned by the package. The last part of the spec file is changelog.


<pre>%files
<pre>%files
%defattr(-,root,root,0755)
%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.*
%attr(0644,root,root) %{_datadir}/selinux/packages/%{modulename}.pp.bz2
%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%attr(0644,root,root) %{_datadir}/selinux/devel/include/%{moduletype}/%{modulename}.if
%ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename}
%license COPYING


%changelog
%changelog
* Mon Jan 01 2017 Author Name &lt;Author@redhat.com&gt; - 0.1.0-1
* Mon Jan 01 2017 Author Name &lt;Author@mail-example.com&gt; - 0.1.0-1
- First Build</pre>
- First Build</pre>
== Adding dependency to the spec file of corresponding package ==
The *-selinux package should only be required on SELinux enabled systems. Therefore the following rich dependency syntax should be used:
<pre>Requires: (%{name}-selinux if selinux-policy-%{selinuxtype})</pre>
This ensures that the *-selinux package and all its dependencies are not pulled into containers and other systems that do not use SELinux.
== SELinux Policy module installation ==
SELinux modules are not installed by the spec file ''install'' command,
but using the ''semodule'' tool.
Maintainers should use <code>%selinux_modules_install</code> macro,
which calls ''semodule'' with all the necessary parameters.
The actual module data are then stored in
<code>%{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename}</code>
directory (you can see it specified in the ''files'' section of the spec file as <code>%ghost</code>).
The 200 refers to the priority at which the module is installed
(see <<selinux-policy-module-priorities>> for more deatils).
The only binary file used in this process is the ''.pp'' archive,
which is architecture independent.
Therfore the resulting subpackage should be created as ''noarch'' (''BuildArch: noarch'').
== SELinux Policy module priorities ==
Policy modules can be installed with different priorities. When multiple modules of the same name exist in the system, only the module with the highest priority takes effect.
Distribution policy modules are installed with priority of 100. Custom policy should always be shipped with priority of 200 to override distribution policy. This value is contained inside the ''selinux_modules_install'' macro and should not be changed.
Note that ''semodule'' installs policy modules with priority of 400 by default.
See [https://plautrba.fedorapeople.org/selinux-modules-and-priority.html SELinux modules and priority] for more details about module priority.
== Example spec file changes to incorporate -selinux subpackage ==
This example shows all the code that should be added to an existing spec file
to start building an SELinux subpackage.
<pre>
%global with_selinux 1
%global modulename mypolicy
%global selinuxtype targeted
----------------------------------------
Source2:      %{modulename}.te
Source3:      %{modulename}.if
Source4:      %{modulename}.fc
----------------------------------------
%if 0%{?with_selinux}
# This ensures that the *-selinux package and all its dependencies are not pulled
# into containers and other systems that do not use SELinux
Requires:        (%{name}-selinux if selinux-policy-%{selinuxtype})
%endif
----------------------------------------
%if 0%{?with_selinux}
# SELinux subpackage
%package selinux
Summary:            Myapp SELinux policy
BuildArch:          noarch
Requires:            selinux-policy-%{selinuxtype}
Requires(post):      selinux-policy-%{selinuxtype}
BuildRequires:      selinux-policy-devel
%{?selinux_requires}
%description selinux
Custom SELinux policy module
%endif
-------- %build section ----------------
%if 0%{?with_selinux}
# SELinux policy (originally from selinux-policy-contrib)
# this policy module will override the production module
mkdir selinux
cp -p %{SOURCE2} selinux/
cp -p %{SOURCE3} selinux/
cp -p %{SOURCE4} selinux/
make -f %{_datadir}/selinux/devel/Makefile %{modulename}.pp
bzip2 -9 %{modulename}.pp
%endif
-------- %install section --------------
%if 0%{?with_selinux}
install -D -m 0644 %{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
install -D -p -m 0644 selinux/%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%endif
----------------------------------------
%if 0%{?with_selinux}
# SELinux contexts are saved so that only affected files can be
# relabeled after the policy module installation
%pre selinux
%selinux_relabel_pre -s %{selinuxtype}
%post selinux
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%postun selinux
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
fi
%posttrans selinux
%selinux_relabel_post -s %{selinuxtype}
# if with_selinux
%endif
-------- version for daemons/services --------
%if 0%{?with_selinux}
# SELinux contexts are saved so that only affected files can be
# relabeled after the policy module installation
%pre selinux
%selinux_relabel_pre -s %{selinuxtype}
%post selinux
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%selinux_relabel_post -s %{selinuxtype}
if [ "$1" -le "1" ]; then # First install
  # the daemon needs to be restarted for the custom label to be applied
  %systemd_postun_with_restart %{modulename}.service
fi
%postun selinux
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
    %selinux_relabel_post -s %{selinuxtype}
fi
%endif
----------------------------------------
%if 0%{?with_selinux}
%files selinux
%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.*
%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename}
%endif
</pre>
= Building a Package with an SELinux Product Policy =
= Building a Package with an SELinux Product Policy =


== Setting Booleans During an Product Policy Installation ==
== Setting Booleans During Installation ==
 
In some cases, it is nescessary to enable or disable some booleans defined in a system security policy. This change should be done during an installation of an SELinux product package and it should also follow a couple of rules.


<blockquote>'''Warning'''
{{admon/warning|Warning!|Setting generic booleans can open security holes in the system.}}


Setting some generic booleans can open security holes in the system.
In some cases, it is necessary to enable or disable booleans defined in the system security policy.
</blockquote>
Maintainers should use the following steps to do so:
To change system booleans, use the following steps:


<ul>
<ul>
<li><p>Find a boolean that fits your needs best. Try to avoid generic booleans, which allow many things and their change could bring security holes to the system.</p></li>
<li><p>Find a boolean that best fits your needs while avoiding generic booleans if possible (additional access in the custom policy module is preferred to switching a boolean that impacts other policy modules).
</p></li>
<li><p>Specify booleans in the following format in the .spec file:</p>
<li><p>Specify booleans in the following format in the .spec file:</p>
<pre># default boolean values need to be changed due to product policy
<pre># default boolean values need to be changed due to product policy
# the change is performed by &quot;%selinux_set_booleans&quot; macro in %post phase
# the change is performed by &quot;%selinux_set_booleans&quot; macro in %post phase
%global selinuxbooleans booleanname=1 booleanname2=0
%global selinuxbooleans booleanname=1 booleanname2=0</pre>
                                    </pre></li>
 
<li><p>In Preamble section ''policycoreutils-python'' package should be required:</p>
<li><p>It is necessary to use special macro _%selinux_set_booleans during &quot;%post&quot; phase of rpmbuild to make sure that the specified boolean values are set.</p></li></ul>
<pre>%if 0%{?fedora}
 
Requires(post): policycoreutils-python-utils
See the following example:
%else
 
Requires(post): policycoreutils-python
%endif</pre></li>
<li><p>It is necessary to use special macro ''%selinux_set_booleans during'' &quot;%post&quot; phase of rpmbuild to make sure that the specified boolean values are set.See following example:</p>
<pre>%post
<pre>%post
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{modulename}.pp.bz2
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%selinux_set_booleans -s %{selinuxtype} %{selinuxbooleans}
# first install
if [ $1 -eq 1 ]; then
    %selinux_set_booleans -s %{selinuxtype} %{selinuxbooleans}
fi


%postun
%postun
%selinux_modules_uninstall -s %{selinuxtype} %{modulename}
if [ $1 -eq 0 ]; then
%selinux_unset_booleans -s %{selinuxtype} %{selinuxbooleans}
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
                                    </pre></li></ul>
    %selinux_unset_booleans -s %{selinuxtype} %{selinuxbooleans}
fi</pre>


Usage of booleans in a .spec file follows these rules:
The boolean macros mentioned above behave as follows:


* If a boolean mentioned in the product .spec file is not set by user previously, it will be changed in the %post install phase and during the %post uninstall phase will be reverted.
* The value of each boolean set using &quot;%selinux_set_booleans&quot; is recorded and will be reset to the original value when &quot;%selinux_unset_booleans&quot; is called
* If a boolean mentioned in the product .spec file was set by user previously, it will be changed to a value from this file. However, during the uninstallation of a product SELinux subpackage, it will not be reverted.
* Number of calls to &quot;%selinux_set_booleans&quot; and &quot;%selinux_unset_booleans&quot; has to match in order for this mechanism to work properly


== Port Labeling ==
== Port Labeling ==
Line 321: Line 560:
If your product policy does not define port labels (such as &quot;product_port_t&quot;), you can skip this section.
If your product policy does not define port labels (such as &quot;product_port_t&quot;), you can skip this section.


You should assign a port number and a port type for every port label. Assigning a port label should be done in %post install phase. For example, for the TCP 1111 port, the <code>semanage port -a -t product_port_t -p tcp 1111</code> command should be added to the if statement in the .spec file:
Assigning a port label should be done in %post install phase. For example, for the TCP 1111 port, the <code>semanage port -a -t product_port_t -p tcp 1111</code> command should be added to the .spec file:


<pre>if %{_sbindir}/selinuxenabled ; then
<pre>
    %{_sbindir}/load_policy
%post
    %relabel_files
# first install
    %{_sbindir}/semanage port -a -t product_port_t -p tcp 1111
if [ $1 -eq 1 ]; then
    %{_sbindir}/semanage port -a -t product_port_t -p tcp 1111 || true
fi</pre>
fi</pre>
Where the <code>a</code>, <code>t</code>, and <code>p</code> of the <code>semanage</code> command mean the following:
Where the <code>a</code>, <code>t</code>, and <code>p</code> of the <code>semanage</code> command mean the following:


<pre>-a  Add a record of the specified object type
<pre>-a  Add a record of the specified object type
-t  SELinux type for the object from product policy
-t  SELinux type for the port
-p  Protocol  for  the  specified  port  (tcp|udp)</pre>
-p  Protocol  for  the  specified  port  (tcp|udp)</pre>
For the %post uninstall phase, the port assignment should be removed. To do this, add the <code>semanage port -d -t &lt;PORT&gt;</code> command in your .spec file, for example:


<pre>if %{_sbindir}/selinuxenabled ; then
The port labeling definition should be removed when the package is unistalled (%postun phase). To do this, add the <code>semanage port -d -t &lt;PORT&gt;</code> command in your .spec file. Note that %postun phase is executed not only when package is removed, but also during update. Use the following condition to only apply the changes on package removal:
    %{_sbindir}/load_policy
 
    %relabel_files
<pre>
     %{_sbindir}/semanage port -d  -p tcp -t product_port_t
%postun
# Delete port labeling when the package is removed
if [ $1 -eq 0 ]; then
     %{_sbindir}/semanage port -d  -p tcp -t product_port_t || true
fi</pre>
fi</pre>
== Building a Package with an Own Product SELinux Policy ==


Move your SELinux product policy sources to the proper destination:
= Testing =
 
Place a copy of [https://pagure.io/DSP_test/blob/master/f/example_tests-DSP.yml tests-DSP.yml] into the resulting package distgit ''tests'' repository to test for potentially dangerous policy issues (we recommend leaving the name at ''tests-DSP.yml'', but anything that fits ''tests*.yml'' will work).
The ''environment'' section needs to be configured to your package and the package also needs to be added to required_packages.
 
Example ''environment'' configurations:
 
[https://src.fedoraproject.org/rpms/zabbix/pull-request/4 Zabbix] - policy sources are placed directly in [https://src.fedoraproject.org/rpms/zabbix/tree/master distgit] (hence <code>POLICY_TAR: &#39;&#39; </code>)
<pre>
TEST_RPM: zabbix-selinux
TEST_POLICY: zabbix
POLICY_TAR: ''
POLICY_PATH: .
</pre>
 
[https://src.fedoraproject.org/rpms/freeipa/tree/master Freeipa] - policy sources live in [https://github.com/freeipa/freeipa/tree/master/selinux freeipa upstream] (in 'selinux' directory)
<pre>
TEST_RPM: freeipa-selinux
TEST_POLICY: ipa
POLICY_TAR: 'freeipa-*.tar.gz'
POLICY_PATH: 'freeipa-*/selinux'
</pre>
 
[https://src.fedoraproject.org/rpms/usbguard/tree/master USBGuard] - policy sources are stored in [https://github.com/USBGuard/usbguard-selinux separate repository] (separate tar.gz)
<pre>
TEST_RPM: usbguard-selinux
TEST_POLICY: usbguard
POLICY_TAR: 'usbguard-selinux*.tar.gz'
POLICY_PATH: 'usbguard-selinux*'
</pre>
 
== Debugging test results ==
 
"''Unsound/dangerous policy practices''" section is based on
[https://fedoraproject.org/wiki/SELinux/Unsound_or_dangerous_SELinux_policy_practices SELinux/Unsound_or_dangerous_SELinux_policy_practices],
where you can find more details about each issue and suggestions for possible causes.
Policy rules flagged by this part of the test can be waived using ''IGNORE_RULES'' variable in ''testing-DSP.yml'' file
(this has to be agreed upon with the SELinux team).
 
Example:
<pre>
IGNORE_RULES: >-
  nagios_script_t:system_map_t:file
  nagios_script_t:boot_t:dir
</pre>
 
For more details about "''SELint static analysis''" section, see
[https://github.com/TresysTechnology/selint SELint github page].
Most issues generated by ''SELint'' can be fixed by tweaking the custom policy sources,
but in rare cases it may be necessary to also update the distribution policy
(e.g. add a new interface).
In such case you can temporarily
(until the necessary change in distribution policy is merged)
disable given check by including the following comment at the end of the line causing the issue:
<pre>
#selint-disable:<ISSUE-ID>
</pre>


<pre>$ cp myapp-selinux.tar.gz ~/rpmbuild/SOURCES/</pre>
You can find the ''ISSUE-ID'' at the end of each SELint message.
Build your product (sub)package with an own SELinux policy:


<pre># rpmbuild -ba myapp-selinux.spec</pre>
After a successful build, your package is ready in the <code>~/rpmbuild/RPMS/noarch/</code> directory:


= Removing an Own Product Policy from the System Policy =
= Removing your Product Policy from the System Policy =


When is your own product SELinux subpackage ready for a release, contact the SELinux policy maintainer. He should remove a product policy from the SELinux distribution policy and update the package. A product maintainer should add dependency for the selinux-policy package:
When your SELinux subpackage is ready for a release, contact the SELinux policy maintainer. He should remove the corresponding policy module from the SELinux distribution policy and update the package. You should then add a dependency on the new selinux-policy package:


<pre># Version of selinux-policy when product policy was removed
<pre># Version of selinux-policy when custom policy was removed
%global selinux_policyver POLICY_VERSION
%global selinux_policyver POLICY_VERSION
Requires: selinux-policy &gt;= %{selinux_policyver}</pre>
Requires: selinux-policy &gt;= %{selinux_policyver}</pre>
If the released policy was not part of the distribution policy, there is no need to add version dependency to your .spec file.


Now is your SELinux subpackage ready to release. It is recommended to create a group update together with selinux-policy package to ensure that the updating process will be successful.
If the released policy was not part of the distribution policy, there is no need to add a version dependency to your .spec file.


== Resources ==
Now your SELinux subpackage is ready for release. It is recommended to create a group update together with selinux-policy package to ensure that the updating process will be successful.


=== SELinux in general ===
= Resources =
 
== SELinux in general ==
* https://docs.fedoraproject.org/en-US/Fedora/25/html/SELinux_Users_and_Administrators_Guide/index.html
* https://docs.fedoraproject.org/en-US/Fedora/25/html/SELinux_Users_and_Administrators_Guide/index.html
* https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/
* https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/
Line 370: Line 666:
* https://mgrepl.wordpress.com/2016/12/13/selinux-security-policy-part-3-lables-in-action/
* https://mgrepl.wordpress.com/2016/12/13/selinux-security-policy-part-3-lables-in-action/


=== Why is SELinux useful ===
== Why is SELinux useful ==
* https://lvrabec-selinux.rhcloud.com/wp-content/uploads/2016/08/slides-deck-6-7-9.html#/
* https://lvrabec-selinux.rhcloud.com/wp-content/uploads/2016/08/slides-deck-6-7-9.html#/
* http://danwalsh.livejournal.com/71396.html
* http://danwalsh.livejournal.com/71396.html
Line 377: Line 673:
* http://lvrabec-selinux.rhcloud.com/wp-content/uploads/2016/11/LinuxDays.pdf
* http://lvrabec-selinux.rhcloud.com/wp-content/uploads/2016/11/LinuxDays.pdf
* https://mgrepl.wordpress.com/2015/11/04/cve-2015-5602-and-selinux/
* https://mgrepl.wordpress.com/2015/11/04/cve-2015-5602-and-selinux/
=== Writing own Policy module ===
* https://mgrepl.fedorapeople.org/Presentations/WritingSELinuxPolicy.pdf
* https://mgrepl.wordpress.com/2015/05/20/how-to-create-a-new-initial-policy-using-sepolicy-generate-tool/
=== Shipping own SELinux module ===
* https://lvrabec-selinux.rhcloud.com/2016/09/19/creating-local-module-quickly-in-cil/
* https://lvrabec-selinux.rhcloud.com/2016/08/17/how-to-quickly-modify-selinux-module-from-distro-policy/
* https://lvrabec-selinux.rhcloud.com/2015/12/01/troubles-with-custom-selinux-modules/
* https://lvrabec-selinux.rhcloud.com/2015/07/07/how-to-create-selinux-product-policy/
* https://plautrba.fedorapeople.org/blok/Fedora-SELinux-module-packaging.html
* https://mgrepl.wordpress.com/2015/07/31/cil-part2-module-priorities/

Latest revision as of 13:55, 22 May 2023

Creating Custom Product Policies

In Fedora, there is a lot of applications and daemons which require customized SELinux security policy. The former approach with providing all policies only as a part of the system has been enhanced by the option to create custom product policy.

With the possibility to create custom product policy, required changes in a policy can be released immediately, so the product package maintainer does not need to wait for another SELinux policy package release. In other words, a product SELinux policy is always synchronized with the corresponding product (package).

This chapter is dedicated to shipping custom SELinux security module as a subpackage for a daemon or an application.

Responsibility
SELinux policy maintainers are not responsible for bugs in customized SELinux policies.


Decentralised SELinux Policy

While considering custom product policy, a product maintainer has two options:

  • Write his own SELinux policy from scratch and ask SELinux team for policy review. Note that a guide how to write an SELinux policy from scratch is not a part of this chapter (See sepolicy generate tool).
  • Extract an SELinux policy from a distribution policy package. The Git repository with distribution policies is located on https://github.com/fedora-selinux/selinux-policy.
License
Distribution policies have GPL license, so any policy extracted from Distribution policy must have a GPL compatible license.

Agreement workflow

Before you start with shipping custom product policies, let the SELinux team know about your intentions. To do this, use SELinux Fedora mailing list or contact SELinux policy maintainer:

Preparing sources for the Policy repository

It is recommended to create a Git repository for the SELinux policy sources.

Corresponding policy module can than be extracted from selinux-policy repository. If there is no policy for the product, new policy should be created in this step and added to the repository.

Please make sure the policy sources follow SELinux policy Style Guide.

When the custom policy is ready, the product maintainer should create a build script (e.g. Makefile), attach a license file and make sure the policy compiles properly.

License

A Git repository should not contain only SELinux policy source files, but also a license. For more information how to add an open source license in your repository, see the Adding a license to a repository article on the GitHub Help. Distribution policies have GPL license, so any policy extracted from Distribution policy must have a GPL compatible license.

Makefile

To compile a product policy, you can use a makefile, for example (automatically generated by sepolicy generate):

TARGET?=myapp
MODULES?=${TARGET:=.pp.bz2}
SHAREDIR?=/usr/share
SELINUXTYPE?=targeted

all: ${TARGET:=.pp.bz2}

%.pp.bz2: %.pp
    @echo Compressing $^ -\> $@
    bzip2 -9 $^

%.pp: %.te
    make -f ${SHAREDIR}/selinux/devel/Makefile $@

clean:
    rm -f *~  *.tc *.pp *.pp.bz2
    rm -rf tmp *.tar.gz

man: install-policy
    sepolicy manpage --path . --domain ${TARGET}_t

install-policy: all
    semodule -i ${TARGET}.pp.bz2

install: man
    install -D -m 644 ${TARGET}.pp.bz2 ${DESTDIR}${SHAREDIR}/selinux/packages/${SELINUXTYPE}/${TARGET}.pp.bz2
    install -D -m 644 ${TARGET}_selinux.8 ${DESTDIR}${SHAREDIR}/man/man8/

If you choose not to use a Makefile, replace the make command in spec file with the following:

make -f %{_datadir}/selinux/devel/Makefile %{modulename}.pp
bzip2 -9 %{modulename}.pp

Policy source examples

For the purpose of this example, we create a policy named myapp:

$ cat myapp.te
policy_module(myapp,1.0)

type myapp_t;
type myapp_exec_t;
init_daemon_domain(myapp_t, myapp_exec_t)

# Grant myapp_t the signal privilege
allow myapp_t self:process { signal };

$ cat myapp.fc
/sbin/myapp --  gen_context(system_u:object_r:myapp_exec_t,s0)

$ cat myapp.if
##
My app service.

The SELinux policy Git repository should contain the following files (replace myapp with a name of your product):

$ ls
Makefile  myapp.fc  myapp.if  myapp.te COPYING

Compiling custom policy

To compile finished policy, use the make command:

$ make
make -f /usr/share/selinux/devel/Makefile myapp.pp
make[1]: Entering directory '/home/lvrabec/devel/documentations/examples'
Compiling targeted myapp module
/usr/bin/checkmodule:  loading policy configuration from tmp/myapp.tmp
/usr/bin/checkmodule:  policy configuration loaded
/usr/bin/checkmodule:  writing binary representation (version 17) to tmp/myapp.mod
Creating targeted myapp.pp policy package
rm tmp/myapp.mod.fc tmp/myapp.mod
make[1]: Leaving directory '/home/lvrabec/devel/documentations/examples'
Compressing myapp.pp -> myapp.pp.bz2
bzip2 -9 myapp.pp

After a succesful compilation, make an archive containing your policy:

$ cd ../
$ tar -czf myapp-selinux.tar.gz myapp-selinux/

Using custom interfaces

Custom interface naming
All custom interfaces must be prefixed with "ipp_" not to be confused with distribution interfaces.

Interfaces in your policy module (.if file) allow other policy modules to gain access to your package's resources when needed. Whenever you define a new interface, it is necessary to ship your interface file as part of your policy package (rpm) so that other policy packages can use it.

%install
install -D -p -m 644 selinux/%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if

%files
%{_datadir}/selinux/devel/include/distributed/%{modulename}.if

All custom interfaces (that is, interfaces that are not part of distribution policy) must be prefixed with "ipp_" not to be confused with distribution interfaces.

Changes to interfaces of the original module can only be delivered via distribution selinux-policy-* packages. If such a change is necessary, please contact the SELinux team, or submit a pull request. Please bear in mind that such changes will influence other policy modules that use given interface.

Backwards compatibility

The most common problem with using custom policies on older distributions is undefined interfaces.

Compiling targeted nagios module
selinux/nagios.te:374:ERROR 'syntax error' at token 'sssd_signull' on line 19406:
    sssd_signull(nrpe_t)

This issue can be resolved by conditionally defining the missing interface. To do this, find definition of the missing interface in SELinux-policy-contrib or SELinux-policy repository, copy it to your interface file and enclose in an ifndef statement.

Example using sssd_signull (necessary to use this interface in epel8):

########################################
#
# Interface compatibility blocks
#
# The following definitions ensure compatibility with distribution policy
# versions that do not contain given interfaces (epel, or older Fedora
# releases).
# Each block tests for existence of given interface and defines it if needed.
#

########################################
## <summary>
##    Allow caller to signull sssd.
##    Backport from RHEL8
## </summary>
## <param name="domain">
##    <summary>
##    Domain allowed access.
##    </summary>
## </param>
#
ifndef(`sssd_signull',`
  interface(`sssd_signull',`
      gen_require(`
          type sssd_t;
      ')

      allow $1 sssd_t:process signull;
  ')
')

The same syntax can be used to define an interface from another custom policy module you rely on. Make sure to also add a BuildRequires if that is the case (the "ifndef" syntax should only be used in case you cannot ensure presence of the interface using BuildRequires alone).

Moving type/attribute/alias definitions

Contact SELinux team
Moving definitions between modules is usually not advised and you should consult any such changes with the SELinux team beforehand.

Whenever a type,attribute or alias definition is moved between modules (this is usually done when two modules are merged together, or some distinct part of a policy is moved to a separate module) it is necessary to include the following steps in your custom policy installation:

  • Disable the distribution version of affected module(s) before calling %selinux_modules_install
    • semodule -d <module_name> &> /dev/null || true;
  • Re-enable the original policy modules after %selinux_modules_uninstall
    • semodule -e <module_name> &> /dev/null || true;
&> /dev/null || true
This part is needed because the commands are expected to fail in some situations (e.g. during update of the custom policy package, or when the changes where also adopted in the distribution policy).

These steps are necessary to avoid type, attribute or alias redefinition errors, which may cause the custom package installation to fail. Example of such error:

Running scriptlet: freeipa-selinux-4.8.6-1.fc33.noarch                            2/4
Re-declaration of type ipa_custodia_t
Failed to create node
Bad type declaration at /var/lib/selinux/targeted/tmp/modules/100/ipa_custodia/cil:1
/usr/sbin/semodule:  Failed!

File contexts and equivalency rules

File context can be specified either by mapping labels to specific paths in ".fc" policy files (eg. /usr/bin/cdcc -- gen_context(system_u:object_r:cdcc_exec_t,s0)), or by setting file path equivalency (eg. semanage fcontext -a -e /home /export/home). The latter approach mirrors labelling structure of the source directory to the target. There is only a handful of file path equivalencies in the distribution policy, but it is important to take them into consideration whenever a file context rule is edited or added.

File context rules must not reference paths that are labelled according to an equivalency (the new context must be assigned to the original path -- source of the equivalency, not to the target).

Custom policy modules and distribution policy

It’s important to note that distribution policies should not use interfaces from removable policy modules.

When using types from custom policy modules stub interfaces should be used instead of directly requiring given type. Stub interface is defined and used in distribution module as follows.

$ cat distribution_module.if
...
...
########################################
## <summary>
##  DBUS stub interface.  No access allowed.
## </summary>
## <param name="domain" unused="true">
## <summary>
##  Domain allowed access
## </summary>
## </param>
#
interface(`distro_stub',`
    gen_require(`
        type dystro_t;
    ')
')
...
...

$ cat myapp.te
...
...
optional_policy(`
    distro_stub()
    allow distro_t myapp_log_t:file read_file_perms;
')
...
...

As with any type defined outside of SELinux policy base modules, optional_policy block must be used when using types from removable modules in distribution policy.

Creating the Spec File

When a Git repository with SELinux policy sources is ready, create your product .spec file (rpmbuild configuration file).

Please note that this section focuses on targeted SELinux mode. In case you intend to use the custom policy in multiple SELinux modes, please see Custom Product Policies targeting multiple SELinux modes

The Preamble

First of all an SELinux policy type, a module type, and a module name should be defined:

# defining macros needed by SELinux
%global selinuxtype targeted
%global modulename myapp

Then it is necessary to fill in all the information about the subpackage such as a name, a version, a license, and so on.

Name: myapp-selinux
Version: 1.0
Release: 1%{?dist}
License: GPLv2
URL: # URL to git repository with policy source files
Summary: SELinux policies for product
Source0: # archive with SELinux policy sources. e.g: myapp-selinux.tar
Requires: selinux-policy-%{selinuxtype}
Requires(post): selinux-policy-%{selinuxtype}
BuildRequires: selinux-policy-devel
BuildArch: noarch
%{?selinux_requires}
%description
SELinux policy modules for product.

The %prep and %install Section

The following part of the .spec file describes the way a product policy is compiled and installed:

%prep
%setup -q

%build
make

%pre
%selinux_relabel_pre -s %{selinuxtype}

%install
# install policy modules
install -D -m 0644 %{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
install -D -p -m 0644 selinux/%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%check

After this step, a product policy is installed on your system.

The %post Section

Next step is loading a product policy into the kernel in the RPM post-install process. This step also contains the post-uninstall process to remove a product policy properly during a product uninstallation.

%post
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2

%postun
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
fi

%posttrans
%selinux_relabel_post -s %{selinuxtype}

In case you are shipping a daemon or service that uses the custom policy, relabelling needs to take place sooner (%post/%postun sections), to ensure the custom labels are in place before the daemon/service starts.

%post
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%selinux_relabel_post -s %{selinuxtype}

if [ "$1" -le "1" ]; then # First install
   # the daemon needs to be restarted for the custom label to be applied
   %systemd_postun_with_restart %{modulename}.service
fi

%postun
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
    %selinux_relabel_post -s %{selinuxtype}
fi

The %files Section

The end of the .spec file contains the %files section. This section declares which files and directories are owned by the package. The last part of the spec file is changelog.

%files
%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.*
%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename}
%license COPYING

%changelog
* Mon Jan 01 2017 Author Name <Author@mail-example.com> - 0.1.0-1
- First Build

Adding dependency to the spec file of corresponding package

The *-selinux package should only be required on SELinux enabled systems. Therefore the following rich dependency syntax should be used:

Requires: (%{name}-selinux if selinux-policy-%{selinuxtype})

This ensures that the *-selinux package and all its dependencies are not pulled into containers and other systems that do not use SELinux.

SELinux Policy module installation

SELinux modules are not installed by the spec file install command, but using the semodule tool. Maintainers should use %selinux_modules_install macro, which calls semodule with all the necessary parameters.

The actual module data are then stored in %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename} directory (you can see it specified in the files section of the spec file as %ghost). The 200 refers to the priority at which the module is installed (see <<selinux-policy-module-priorities>> for more deatils).

The only binary file used in this process is the .pp archive, which is architecture independent. Therfore the resulting subpackage should be created as noarch (BuildArch: noarch).

SELinux Policy module priorities

Policy modules can be installed with different priorities. When multiple modules of the same name exist in the system, only the module with the highest priority takes effect.

Distribution policy modules are installed with priority of 100. Custom policy should always be shipped with priority of 200 to override distribution policy. This value is contained inside the selinux_modules_install macro and should not be changed.

Note that semodule installs policy modules with priority of 400 by default.

See SELinux modules and priority for more details about module priority.

Example spec file changes to incorporate -selinux subpackage

This example shows all the code that should be added to an existing spec file to start building an SELinux subpackage.

%global with_selinux 1
%global modulename mypolicy
%global selinuxtype targeted

----------------------------------------

Source2:       %{modulename}.te
Source3:       %{modulename}.if
Source4:       %{modulename}.fc

----------------------------------------

%if 0%{?with_selinux}
# This ensures that the *-selinux package and all its dependencies are not pulled
# into containers and other systems that do not use SELinux
Requires:        (%{name}-selinux if selinux-policy-%{selinuxtype})
%endif

----------------------------------------

%if 0%{?with_selinux}
# SELinux subpackage
%package selinux
Summary:             Myapp SELinux policy
BuildArch:           noarch
Requires:            selinux-policy-%{selinuxtype}
Requires(post):      selinux-policy-%{selinuxtype}
BuildRequires:       selinux-policy-devel
%{?selinux_requires}

%description selinux
Custom SELinux policy module
%endif

-------- %build section ----------------

%if 0%{?with_selinux}
# SELinux policy (originally from selinux-policy-contrib)
# this policy module will override the production module
mkdir selinux
cp -p %{SOURCE2} selinux/
cp -p %{SOURCE3} selinux/
cp -p %{SOURCE4} selinux/

make -f %{_datadir}/selinux/devel/Makefile %{modulename}.pp
bzip2 -9 %{modulename}.pp
%endif

-------- %install section --------------

%if 0%{?with_selinux}
install -D -m 0644 %{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
install -D -p -m 0644 selinux/%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%endif

----------------------------------------

%if 0%{?with_selinux}
# SELinux contexts are saved so that only affected files can be
# relabeled after the policy module installation
%pre selinux
%selinux_relabel_pre -s %{selinuxtype}

%post selinux
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2

%postun selinux
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
fi

%posttrans selinux
%selinux_relabel_post -s %{selinuxtype}
# if with_selinux
%endif

-------- version for daemons/services --------

%if 0%{?with_selinux}
# SELinux contexts are saved so that only affected files can be
# relabeled after the policy module installation
%pre selinux
%selinux_relabel_pre -s %{selinuxtype}

%post selinux
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%selinux_relabel_post -s %{selinuxtype}

if [ "$1" -le "1" ]; then # First install
   # the daemon needs to be restarted for the custom label to be applied
   %systemd_postun_with_restart %{modulename}.service
fi

%postun selinux
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
    %selinux_relabel_post -s %{selinuxtype}
fi
%endif

----------------------------------------

%if 0%{?with_selinux}
%files selinux
%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.*
%{_datadir}/selinux/devel/include/distributed/%{modulename}.if
%ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename}
%endif

Building a Package with an SELinux Product Policy

Setting Booleans During Installation

Warning!
Setting generic booleans can open security holes in the system.

In some cases, it is necessary to enable or disable booleans defined in the system security policy. Maintainers should use the following steps to do so:

  • Find a boolean that best fits your needs while avoiding generic booleans if possible (additional access in the custom policy module is preferred to switching a boolean that impacts other policy modules).

  • Specify booleans in the following format in the .spec file:

    # default boolean values need to be changed due to product policy
    # the change is performed by "%selinux_set_booleans" macro in %post phase
    %global selinuxbooleans booleanname=1 booleanname2=0
  • It is necessary to use special macro _%selinux_set_booleans during "%post" phase of rpmbuild to make sure that the specified boolean values are set.

See the following example:

%post
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
# first install
if [ $1 -eq 1 ]; then
    %selinux_set_booleans -s %{selinuxtype} %{selinuxbooleans}
fi

%postun
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
    %selinux_unset_booleans -s %{selinuxtype} %{selinuxbooleans}
fi

The boolean macros mentioned above behave as follows:

  • The value of each boolean set using "%selinux_set_booleans" is recorded and will be reset to the original value when "%selinux_unset_booleans" is called
  • Number of calls to "%selinux_set_booleans" and "%selinux_unset_booleans" has to match in order for this mechanism to work properly

Port Labeling

If your product policy does not define port labels (such as "product_port_t"), you can skip this section.

Assigning a port label should be done in %post install phase. For example, for the TCP 1111 port, the semanage port -a -t product_port_t -p tcp 1111 command should be added to the .spec file:

%post
# first install
if [ $1 -eq 1 ]; then
    %{_sbindir}/semanage port -a -t product_port_t -p tcp 1111 || true
fi

Where the a, t, and p of the semanage command mean the following:

-a   Add a record of the specified object type
-t   SELinux type for the port
-p   Protocol  for  the  specified  port  (tcp|udp)

The port labeling definition should be removed when the package is unistalled (%postun phase). To do this, add the semanage port -d -t <PORT> command in your .spec file. Note that %postun phase is executed not only when package is removed, but also during update. Use the following condition to only apply the changes on package removal:

%postun
# Delete port labeling when the package is removed
if [ $1 -eq 0 ]; then
    %{_sbindir}/semanage port -d  -p tcp -t product_port_t || true
fi

Testing

Place a copy of tests-DSP.yml into the resulting package distgit tests repository to test for potentially dangerous policy issues (we recommend leaving the name at tests-DSP.yml, but anything that fits tests*.yml will work). The environment section needs to be configured to your package and the package also needs to be added to required_packages.

Example environment configurations:

Zabbix - policy sources are placed directly in distgit (hence POLICY_TAR: '' )

TEST_RPM: zabbix-selinux
TEST_POLICY: zabbix
POLICY_TAR: ''
POLICY_PATH: .

Freeipa - policy sources live in freeipa upstream (in 'selinux' directory)

TEST_RPM: freeipa-selinux
TEST_POLICY: ipa
POLICY_TAR: 'freeipa-*.tar.gz'
POLICY_PATH: 'freeipa-*/selinux'

USBGuard - policy sources are stored in separate repository (separate tar.gz)

TEST_RPM: usbguard-selinux
TEST_POLICY: usbguard
POLICY_TAR: 'usbguard-selinux*.tar.gz'
POLICY_PATH: 'usbguard-selinux*'

Debugging test results

"Unsound/dangerous policy practices" section is based on SELinux/Unsound_or_dangerous_SELinux_policy_practices, where you can find more details about each issue and suggestions for possible causes. Policy rules flagged by this part of the test can be waived using IGNORE_RULES variable in testing-DSP.yml file (this has to be agreed upon with the SELinux team).

Example:

IGNORE_RULES: >-
  nagios_script_t:system_map_t:file
  nagios_script_t:boot_t:dir

For more details about "SELint static analysis" section, see SELint github page. Most issues generated by SELint can be fixed by tweaking the custom policy sources, but in rare cases it may be necessary to also update the distribution policy (e.g. add a new interface). In such case you can temporarily (until the necessary change in distribution policy is merged) disable given check by including the following comment at the end of the line causing the issue:

#selint-disable:<ISSUE-ID>

You can find the ISSUE-ID at the end of each SELint message.


Removing your Product Policy from the System Policy

When your SELinux subpackage is ready for a release, contact the SELinux policy maintainer. He should remove the corresponding policy module from the SELinux distribution policy and update the package. You should then add a dependency on the new selinux-policy package:

# Version of selinux-policy when custom policy was removed
%global selinux_policyver POLICY_VERSION
Requires: selinux-policy >= %{selinux_policyver}

If the released policy was not part of the distribution policy, there is no need to add a version dependency to your .spec file.

Now your SELinux subpackage is ready for release. It is recommended to create a group update together with selinux-policy package to ensure that the updating process will be successful.

Resources

SELinux in general

Why is SELinux useful