Using rpm macros in product SELinux subpackages

Some time ago, I published a post about shipping custom SELinux modules together with product as rpm subpackage. One of the steps  in shipping custom SELinux module is creating a  spec file for SELinux package, which can be messy, especially %post and %postun phases.

In order to simplify writing spec files, we have created several macros.

Loading custom SELinux modules

First improvement concerns loading and removing custom SELinux modules. You can see the difference in the following example:

Without macros:

%post
#
# Install all modules in a single transaction
#
%_format MODULES %{_datadir}/selinux/packages/$x.pp.bz2
%{_sbindir}/semodule -n -s %{selinuxtype} -i $MODULES
if %{_sbindir}/selinuxenabled ; then
    %{_sbindir}/load_policy
    %relabel_files
fi
 
 
%postun
if [ $1 -eq 0 ]; then
	%{_sbindir}/semodule -n -r %{modulenames} &> /dev/null || :
	if %{_sbindir}/selinuxenabled ; then
		%{_sbindir}/load_policy
		%relabel_files
	fi
fi


With macros:

%post
#
# Install all modules in a single transaction
#
%_format MODULES %{_datadir}/selinux/packages/$x.pp.bz2
%selinux_modules_install -s %{selinuxtype} $MODULES
 
%postun
if [ $1 -eq 0 ]; then
    %selinux_modules_uninstall -s %{selinuxtype} $MODULES
fi

As you can see, module loading is done by one macro instead of 5 lines of code.

Labeling objects from custom policy

Next improvement in spec file is a pair of macros connected to labeling files coming from the custom policy. There is no need to specify all objects in spec file as we did in previously.

# Relabel files
%global relabel_files() \ # ADD files in *.fc file

Instead of specifying objects labeled by custom policy, following macros can be used:

%pre
%selinux_relabel_pre -s %{selinuxtype}

%posttrans
%selinux_relabel_post -s %{selinuxtype}

These macros will handle labeling objects coming from the custom SELinux module and after successfull package installation, all new objects should have correct SELinux label.

Handling SELinux booleans

Last improvement is setting booleans by macros. These macros provide solution for handling booleans in spec files and behave as follows:

  • If a boolean mentioned in the product .spec file was not set by user previously, it will be changed in the %post install phase and reverted during the %post uninstall phase will.
  • 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, it will not be reverted during the uninstallation of a product SELinux subpackage.

Usage of booleans macros:

# In preamble define booleans and values which should be used
%global selinuxbooleans booleanname=1 booleanname2=0

# Also some new packages will be needed:
%if 0%{?fedora}
Requires(post): policycoreutils-python-utils
%else
Requires(post): policycoreutils-python
%endif

# use selinux_set_booleans after custom SELinux module is loaded.

%post
%selinux_modules_install -s %{selinuxtype} $MODULES
%selinux_set_booleans -s %{selinuxtype} %{selinuxbooleans}

%postun
%selinux_modules_uninstall -s %{selinuxtype} $MODULES
%selinux_unset_booleans -s %{selinuxtype} %{selinuxbooleans}

All these improvements can be used in Fedora Rawhide and Fedora 26. Repository with sources can be found here.

Shipping custom module using SELinux priorities

Hello everyone!

Some time ago I introduced first part of shipping own custom module with rpm package of your application. This solution allows you to maintain your own SELinux policy module which brings lot of benefits like:

  • changes in policy can be modified immediately
    • no need to wait while selinux maintainer will fix it
  • independent from selinux-policy distro updates
    • policy changes will be updates together with your application
  • own policy module can reflect lates features inside application
    • policy and application will be synchronized

However technical solution had some troubles introduced in second part. This issues disappeared with new release of SELinux userspace (>=2.4).  New userspace brings feature call module priorities.  Benefits of this feature are described in following example:

$ rpm -q docker-selinux
package docker-selinux is not installed

# semodule -lfull | grep docker
100 docker            pp

 

How to modify SELinux module from distro policy?

Hi!

Today, I’ll show you how to modify SELinux module from distro policy without rebuilding whole selinux-policy rpm package. This can be useful during testing new features in your application or debugging SELinux policy. All current Fedora stable releases (Fedora 23, Fedora 24) support this feature.

For example, we have openwsman SELinux module containing following macro:

optional_policy(`
     unconfined_domain(openwsman_t)
')

That means, openwsman_t domain is part of unconfined_domain_type attribute:

$ seinfo -xtopenwsman_t | grep unconfined_domain_type
unconfined_domain_type

This macro makes SELinux domain unconfined. Let’s say, we want openwsman_t domain confined, so we need to remove rule above.
First step is download openwsman policy source files from our repo.

$ ls
openwsman.fc  openwsman.if  openwsman.te

We can edit openwsman.te file:

$ diff -u openwsman.te.old openwsman.te
--- openwsman.te.old	2016-08-17 19:38:30.617111430 +0200
+++ openwsman.te	2016-08-17 19:38:41.105155383 +0200
@@ -68,7 +68,3 @@
     sblim_getattr_exec_sfcbd(openwsman_t)
 ')
 
-optional_policy(`
-    unconfined_domain(openwsman_t)
-')
-

Modified policy needs to be compiled:

$ make -f /usr/share/selinux/devel/Makefile openwsman.pp

Now, we have compiled policy in .pp format:

$ ls
openwsman.fc  openwsman.if  openwsman.pp  openwsman.te  openwsman.te.old  tmp/

Last step, we load modified policy to kernel:

# semodule -i openwsman.pp 
libsemanage.semanage_direct_install_info: Overriding openwsman module at lower priority 100 with module at priority 400.

We can check if modified policy is loaded into kernel and openwsman_t domain is part of unconfined_domain_type:

# semodule -lfull | grep openwsman
400 openwsman                pp         
100 openwsman                pp 

$ seinfo -xtopenwsman_t | grep unconfined_domain_type
$

Two SELinux modules called openwsman are loaded. Firt one, with priority 400 is our local modified module. Second, with priority 100, coming from distro policy. Only SELinux module with the highest priority is active on system.

This is pretty easy and quick solution for modifying distro policy modules, isn’t it? 😉

Trouble with custom SELinux modules

Hello Everyone!

I’m here with second part of series on how to create a custom SELinux policy rpm package. In this part I’ll show disadvantages of this concept and explain a possible solution for future.

 

The concept of using a custom SELinux policy for your service works fine, but it’s limited by the m4 macro language used to write SELinux policies. The limitation lies in the fact that you can’t use a macro definition from your custom policy in the distribution policy. A nice example of this problem is the following bug.

 

When you try to build the selinux-policy rpm package you’ll end up with:

 

/usr/bin/checkmodule -M -m tmp/abrt.tmp -o tmp/abrt.mod
policy/modules/contrib/abrt.te:256:ERROR 'syntax error' at token 'docker_stream_connect' on line 12012:
	docker_stream_connect(abrt_t)
#line 256
/usr/bin/checkmodule:  error(s) encountered while parsing configuration
/usr/bin/checkmodule:  loading policy configuration from tmp/abrt.tmp
Rules.modular:74: recipe for target 'tmp/abrt.mod' failed
make: *** [tmp/abrt.mod] Error 1
error: Bad exit status from /var/tmp/rpm-tmp.B2uAfi (%install)

ABRT daemon adds support for collecting information about containers. For examples it runs:

#docker inspect $container_id
to detect a new crash in a docker container. From the SELinux point of view,  the policy for ABRT daemon needs to be changed. This is expected behaviour. The solution here is pretty simple, we just need to add this rule:

 

docker_stream_connect(abrtd_t)
This allows ABRT daemon to connect with docker over a unix stream socket. The rule needs to be included in the abrt SELinux module of distribution policy. However, the definition of

 

docker_stream_connect()
is in the docker-selinux rpm package. That is very similar to the situation when you are compiling some code written in C and you forgot to include a required header file. In this situation, the rpm package of selinux-policy cannot be build due to a missing definition.
We found a solution! Before every distribution policy package build, we download the docker policy files from github. We archive these files and distribute them along with the distribution policy source files. During build, these files are extracted and the docker interface file (*.if) is merged with all the other interface files. Using this method, we could add the rule mentioned above in the distribution policy.

 

Now, I’ll show you how to make all changes to fix this issue:

We need to install these rpm packages:

# dnf install fedpkg

We create an archive with all SELinux custom module source files:

# tar -czf /tmp/myapp-selinux.tgz myapp.if myapp.te myapp.fc
Then we clone the selinux-policy rpm sources and move it to the current working directory:

 

# fedpkg clone selinux-policy
# cd selinux-policy
# mv /tmp/myapp-selinux.tgz ./
In selinux-policy.spec file we need to add the new source file and extract this archive in the %prep phase of a build:

 

 Source36: myapp-selinux.tgz
 tar -xf %{SOURCE36}

Diff looks like this:

$ git diff
diff --git a/selinux-policy.spec b/selinux-policy.spec
index f052550..9b932cf 100644
--- a/selinux-policy.spec
+++ b/selinux-policy.spec
@@ -19,7 +19,7 @@
 Summary: SELinux policy configuration
 Name: selinux-policy
 Version: 3.13.1
-Release: 161%{?dist}
+Release: 162%{?dist}
 License: GPLv2+
 Group: System Environment/Base
 Source: serefpolicy-%{version}.tgz
@@ -57,6 +57,7 @@ Source30: booleans.subs_dist
 Source33: manpages_html.tgz
 Source34: manpages_man.tgz
 Source35: docker-selinux.tgz
+Source36: myapp-selinux.tgz
 Url: http://github.com/TresysTechnology/refpolicy/wiki
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 BuildArch: noarch
@@ -310,6 +311,7 @@ done
 %setup -n serefpolicy-contrib-%{version} -q -b 29
 %patch1 -p1
 tar -xf %{SOURCE35}
+tar -xf %{SOURCE36}
 contrib_path=`pwd`
 %setup -n serefpolicy-%{version} -q
 %patch -p1

You increment release of build and run:

 # fedpkg mockbuild
It will build a new SELinux package including the interface file from our custom module called myapp. From now on, you can add interfaces from your custom module to the distribution policy, like in abrtd example above.
This  series of blogs showed how to easily create a custom SELinux module, ship it as rpm package or subpackage and dealt with issues of current SELinux technologies.

How to create SELinux custom policy rpm package

Hi Everyone! 
This tutorial guides you on how to create SELinux custom policy rpm package for Fedora system. I will focus on creating rpm package with policy files, not on process how to create the SELinux policy. Whole process will be described in a few easy steps. This blog is output of separation of SELinux policy for docker as rpm subpackage. I’ll use basic SELinux policy as example, but this process can be used for any SELinux policy.  At the end of this tutorial you should have working rpm package with SELinux policy usable for Fedora 22.

Before start, I install following packages:

# dnf install selinux-policy-devel rpm-build

Let’s start!
I create directory with policy files.

$ mkdir myapp-selinux-0.1
$ cd myapp-selinux-0.1/

Then I create basic SELinux policy, something like this:

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

Now, we can see following files in myapp-selinux-0.1 directory:

$ ls
myapp.fc  myapp.if  myapp.te

We also need Makefile to build this policy:

TARGETS?= myapp
MODULES?=${TARGETS:=.pp.bz2}

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

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

%.pp: %.te
	make -f /usr/share/selinux/devel/Makefile $@

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

Run:

$ make

This should create following file myapp.pp.bz2.

$ ls
Makefile  myapp.fc  myapp.if  myapp.pp.bz2  myapp.te  tmp

Create archive from directory with SELinux policy files:

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

Create spec file for rpm package.

%global selinuxtype	targeted
%global moduletype	services
%global modulenames	myapp

# Usage: _format var format
#   Expand 'modulenames' into various formats as needed
#   Format must contain '$x' somewhere to do anything useful
%global _format() export %1=""; for x in %{modulenames}; do %1+=%2; %1+=" "; done;

# Relabel files
%global relabel_files() \ # ADD files in *.fc file


# Version of distribution SELinux policy package 
%global selinux_policyver 3.13.1-128.6.fc22

# Package information
Name:			myapp-selinux
Version:		0.1
Release:		1%{?dist}
License:		GPLv2
Group:			System Environment/Base
Summary:		SELinux Policies for Docker
BuildArch:		noarch
URL:			https://HOSTNAME
Requires(post):		selinux-policy-base >= %{selinux_policyver}, selinux-policy-targeted >= %{selinux_policyver}, policycoreutils, policycoreutils-python libselinux-utils
BuildRequires:		selinux-policy selinux-policy-devel

Source:			%{name}-%{version}.tar.gz

%description
SELinux policy modules for use with myapp

%prep
%setup -q

%build
make SHARE="%{_datadir}" TARGETS="%{modulenames}"

%install

# Install SELinux interfaces
%_format INTERFACES $x.if
install -d %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}
install -p -m 644 $INTERFACES \
	%{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}

# Install policy modules
%_format MODULES $x.pp.bz2
install -d %{buildroot}%{_datadir}/selinux/packages
install -m 0644 $MODULES \
	%{buildroot}%{_datadir}/selinux/packages

%post
#
# Install all modules in a single transaction
#
%_format MODULES %{_datadir}/selinux/packages/$x.pp.bz2
%{_sbindir}/semodule -n -s %{selinuxtype} -i $MODULES
if %{_sbindir}/selinuxenabled ; then
    %{_sbindir}/load_policy
    %relabel_files
fi


%postun
if [ $1 -eq 0 ]; then
	%{_sbindir}/semodule -n -r %{modulenames} &> /dev/null || :
	if %{_sbindir}/selinuxenabled ; then
		%{_sbindir}/load_policy
		%relabel_files
	fi
fi

%files
%defattr(-,root,root,0755)
%attr(0644,root,root) %{_datadir}/selinux/packages/*.pp.bz2
%attr(0644,root,root) %{_datadir}/selinux/devel/include/%{moduletype}/*.if

%changelog
* Fri Mar 06 2015 Lukas Vrabec <lvrabec@redhat.com> - 0.1.0-1
- First Build

Note: If you create custom SELinux policy that is included in distribution SELinux policy package use following spec file:

%global selinuxtype	targeted
%global moduletype	services
%global modulenames	myapp

# Usage: _format var format
#   Expand 'modulenames' into various formats as needed
#   Format must contain '$x' somewhere to do anything useful
%global _format() export %1=""; for x in %{modulenames}; do %1+=%2; %1+=" "; done;

# Relabel files
%global relabel_files() \ # ADD files in *.fc file


# Version of distribution SELinux policy package 
%global selinux_policyver 3.13.1-128.6.fc22

# Package information
Name:			myapp-selinux
Version:		0.1.0
Release:		1%{?dist}
License:		GPLv2
Group:			System Environment/Base
Summary:		SELinux Policies for Docker
BuildArch:		noarch
URL:			https://HOSTNAME
Requires(post):		selinux-policy-base >= %{selinux_policyver}, selinux-policy-targeted >= %{selinux_policyver}, policycoreutils, policycoreutils-python libselinux-utils
BuildRequires:		selinux-policy selinux-policy-devel

Source:			%{name}-%{version}.tar.gz

%description
SELinux policy modules for use with myapp

%prep
%setup -q

%build
make SHARE="%{_datadir}" TARGETS="%{modulenames}"

%install

# Install policy modules
%_format MODULES $x.pp.bz2
install -d %{buildroot}%{_datadir}/selinux/packages
install -m 0644 $MODULES \
	%{buildroot}%{_datadir}/selinux/packages

%post
#
# Install all modules in a single transaction
#
%_format MODULES %{_datadir}/selinux/packages/$x.pp.bz2
%{_sbindir}/semodule -n -s %{selinuxtype} -i $MODULES
if %{_sbindir}/selinuxenabled ; then
    %{_sbindir}/load_policy
    %relabel_files
fi


%postun
if [ $1 -eq 0 ]; then
	%{_sbindir}/semodule -n -r %{modulenames} &> /dev/null || :
	if %{_sbindir}/selinuxenabled ; then
		%{_sbindir}/load_policy
		%relabel_files
	fi
fi

%files
%defattr(-,root,root,0755)
%attr(0644,root,root) %{_datadir}/selinux/packages/*.pp.bz2

%changelog
* Fri Mar 06 2015 Lukas Vrabec <lvrabec@redhat.com> - 0.1.0-1
- First Build

Just change name and add SELinux module name on line 3.

Copy archive with selinux files to ~/rpmbuild/SOURCES like:

$ cp myapp-selinux-0.1.tar.gz ~/rpmbuild/SOURCES/

Finally, run:

$ rpmbuild -ba myapp-selinux.spec

After these steps, you have new rpm package ready for install. Package is stored in:

ls ~/rpmbuild/RPMS/noarch/

This package can be shipped separately or as subpackage. If you have any question or idea, feel free to comment this post.