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.