From Fedora Project Wiki

Creating Own Product Policies

Warning.png
Not official packaging guidelines
The contents of this document have not been reviewed by the Packaging Committee and do not constitute a set of packaging guidelines.

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.

With the possibility to create own product policy, required changes in a policy can be implemented 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 a product.

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

Independent SELinux Policy

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

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

Extraction Process

Agreement workflow

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

Git Repository setup

A product maintainer should prepare a Git repository for SELinux policy sources (i.e. <policy module name>.{te|fc|if} files). It's recommended, to set up separate Git repository for the policy.

# Create directory to contain the project
$ mkdir myapp-selinux
$ cd myapp-selinux
# initialize git repository
$ git init
# Push git repository to remote e.g. to pagure.io
$ git remote add origin ssh://git@pagure.io/myapp-selinux
$ git push -u origin master
             

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

Idea.png
SELinux policy contact
When writing a new SELinux policy or altering existing one, please contact the SELinux team for reviews. Currently, the contact person for SELinux policy reviews is lvrabec@redhat.com.

When the custom policy is ready, the product maintainer should create a Makefile, attach a license file and make sure the policy compiles properly. See Preparing sources for the Policy Git Repository

The final step of the 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 Removing an Own Product Policy from the System Policy

Preparing sources for the Policy Git Repository

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 GPL compatible license.

Makefile

To compile a product policy, you also need a makefile, for example:

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

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/${TARGET}.pp.bz2
    install -D -m 644 ${TARGET}_selinux.8 ${DESTDIR}${SHAREDIR}/man/man8/
Note.png
Target should be replaced.
TARGET should contain a name of your product policy.

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.
Note.png
Third-party Interfaces
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()).
Note.png
File contexts definition
All context definitions should be in policy file context (*.fc) file, not added using semange fcontext command.
Note.png
Existing Interfaces
A third-party module (a module shipped by a product team) must not change existing interfaces.

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 an own and already-prepared 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

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

The interface file of the custom policy module is not installed in the system because it would conflict with the interface file of the distribution module. Therefore any changes to it will not have effect on other policy modules. In order to use custom interfaces it is necessary to create new interface file with unique name (ipp-<SELinux_module_name>.if) and include it in the new package as follows:

%install
install -d -p %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}
install -p -m 644 ipp-%{modulename}.if %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}

%files
%{_datadir}/selinux/devel/include/%{moduletype}/ipp-%{modulename}.if

All custom interfaces 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.

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).

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 moduletype contrib
%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
BuildRequires: selinux-policy
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 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}
install -m 0644 %{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}

%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}

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. Last part of spec file is changelog.

%files
%{_datadir}/selinux/packages/%{modulename}.pp.bz2
%ghost %{_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 it's dependencies are not pulled into containers and other systems that do not use SELinux.

SELinux Policy module priorities

Policy modules can be installed with different priorities. When multiples 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. When shipping custom policy, it 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.

Building a Package with an SELinux Product Policy

Setting Booleans During an Product Policy Installation

In some cases, it is nescessaryto 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.

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

To change system booleans, use the following steps:

  • 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.

  • 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 following example:

    %post
    %selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
    %selinux_set_booleans -s %{selinuxtype} %{selinuxbooleans}
    
    %postun
    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
    %selinux_unset_booleans -s %{selinuxtype} %{selinuxbooleans}
                                        

Usage of booleans in a .spec file follows these rules:

  • 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.
  • 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.

Port Labeling

If your product policy does not define port labels (such as "product_port_t"), 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 semanage port -a -t product_port_t -p tcp 1111 command should be added to the if statement in the .spec file:

if %{_sbindir}/selinuxenabled ; then
     %{_sbindir}/semanage port -a -t product_port_t -p tcp 1111
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 object from product policy
-p   Protocol  for  the  specified  port  (tcp|udp)

For the %post uninstall phase, the port assignment should be removed. To do this, add the semanage port -d -t <PORT> command in your .spec file, for example:

if %{_sbindir}/selinuxenabled ; then
    %{_sbindir}/semanage port -d  -p tcp -t product_port_t
fi

Removing an Own 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:

# Version of selinux-policy when product 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 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.