Packaging SELinux Policy Modules
Fedora Core 5 introduced a new SELinux feature: loadable policy modules. Policy modules allow an SELinux policy for an application to be developed alongside and packaged with the application itself, rather than having to integrate the policy with the monolithic base policy, as was the case for Fedora Core 4 and earlier releases. This page presents guidelines for how to package a policy module alongside your application in the same source RPM package.
Separate Subpackage?
One of the first decisions to be made is whether to have a separate subpackage for your SELinux policy module. There are advantages each way.
- Having everything in one package is easier to maintain and you can always be sure that the SELinux policy installed on a system is a version of the policy appropriate for the version of the main application itself
- Having a separate subpackage for the SELinux policy means that the main application package does not need to have a dependency on the
selinux-policy
package, and installation/upgrades of the main application will be quicker because the scriptlets needed to install the SELinux policy module will not need to be run on systems not using your SELinux policy package
Either approach is OK, choose whichever is most appropriate for you and your package.
For the remainder of this document, the examples will assume that the main application being packaged is called myapp
,
there is a separate myapp-selinux
subpackage for the SELinux policy module,
and the SELinux policy module is called mymodule
.
If you are creating an integrated package rather than using a subpackage for the SELinux policy module,
everything that applies to myapp-selinux
should be applied to myapp
instead, unless otherwise noted.
Separate Package Altogether?
Another possibility is to have an entirely separate package for the SELinux policy module. This has the advantages of keeping the spec files much simpler and easier to read, allows for separate maintainers of the main and -selinux packages, and selinux packages can be updated without pushing new builds of the main package. However, care would have to be taken to make sure that the selinux package is updated with the main package as needed.
On the other hand, policy modules can be thought of as being rather like kernel modules, in that they're things that are useful and usable whilst still under development but that ideally should eventually become unnecessary because they get merged into the main upstream project. So a separate package should be a short-lived package really, and should probably get special permission to be included, as with kernel modules (see Packaging/KernelModules ).
So combining the SELinux policy module with the main package SRPM is preferred for now, though this topic is open for discussion for anyone with strong views on it.
Build Dependencies
Your package will need the following B'uildRequires:
BuildRequires: checkpolicy, selinux-policy-devel
Runtime Dependencies
As the compiled policy module packages (mymodule.pp
) will be installed into these directories:
%{_datadir}/selinux/targeted %{_datadir}/selinux/strict %{_datadir}/selinux/mls
it is necessary for the myapp-selinux
package to have a dependency on selinux-policy
in order to
satisfy directory ownership requirements.
However, a straightforward dependency on that package is not quite enough.
Policy modules compiled using one version of selinux-policy
may not work with older selinux-policy
versions
(see this example for instance).
So the dependency needs to be a versioned dependency, requiring that the version of selinux-policy
on the
target system is at least as new as the version of selinux-policy
that the package was built with.
There is no particularly clean and straighforward way of determining the current policy version at the moment,
but this hack works for all selinux-policy
packages since Fedora Core 5 was released:
%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0) Requires: selinux-policy >= %{selinux_policyver}
If you are not building an integrated package,
then the myapp-selinux
package needs to have a dependency on the myapp
package.
This is necessary to ensure that the packages are installed in the correct order if both packages are installed in the same
rpm
transaction, which in turn is necessary in order to ensure that the post-install scriptlet of the myapp-selinux
package can set the correct file contexts. Whilst this is only strictly necessary if your policy module includes file
context specifications, it's good practice to do this anyway and it might get forgotten if at some time new file contexts
were added to your policy. It also makes sense to make this dependency a fully-versioned dependency, to ensure that the
myapp-selinux
package gets updated whenever the end user updates the main myapp
package.
Requires: %{name} = %{version}-%{release}
Building the Policy Modules
You should build your policy module for all of the base policies supported in the distribution version you are targeting.
For Fedora Core 5 and 6, this means the mls
, strict
, and targeted
policies.
%define selinux_variants mls strict targeted
The source code for your SELinux policy module will include at least a type enforcement rules file (mymodule.te
),
and possibly also a file contexts file (mymodule.fc
) and an interface file (mymodule.if
).
You could include each of these as separate source files:
Source2: mymodule.te Source3: mymodule.fc Source4: mymodule.if
or you might bundle them together as a tarball:
Source2: mymodule-0.1.tar.bz2
Either way is OK but separate source files is probably better for packages aiming to go into Fedora because the policy files themselves can be maintained in Fedora CVS.
To compile the policy files, copy them into an empty directory in %prep
and use /usr/share/selinux/devel/Makefile
in %build
:
Source2: mymodule.te Source3: mymodule.fc Source4: mymodule.if ... %prep ... mkdir SELinux cp -p %{SOURCE2} %{SOURCE3} %{SOURCE4} SELinux %build ... cd SELinux for selinuxvariant in %{selinux_variants} do make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile mv mymodule.pp mymodule.pp.${selinuxvariant} make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean done cd -
Installing the Policy Modules
In %install
, copy the compiled policy module for each base policy into %{_datadir}/selinux/
POLICYNAME:
%install ... for selinuxvariant in %{selinux_variants} do install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant} install -p -m 644 mymodule.pp.${selinuxvariant} \ %{buildroot}%{_datadir}/selinux/${selinuxvariant}/mymodule.pp done
If there is nothing in your policy that is specific to a particular base policy, it is likely
that the compiled policy files will be identical to each other, so in order to save space and
not install duplicate files, you might want to link identical files together at the end of %install
:
BuildRequires: hardlink ... /usr/sbin/hardlink -cv %{buildroot}%{_datadir}/selinux
It's better to use a program like hardlink
to check that the files are identical rather than simply
assuming that they will be be, as macros you use in your policy sources might have base-policy-specific
elements.
The %files list
It's useful to include your policy sources as documentation in the myapp-selinux
package,
not only because it may help users of your package understand the policy, but also to keep rpmlint
quiet about
missing documentation :-)
The %{_datadir}/selinux/*
directories into which the mymodule.pp
files are installed are owned by the
selinux-policy
package, so your package should not own those directories itself; just include the mymodule.pp
files
themselves in the %files
list:
%files selinux %defattr(-,root,root,0755) %doc SELinux/* %{_datadir}/selinux/*/mymodule.pp
Scriptlets
The post-install and post-uninstall scripts of the myapp-selinux
package are responsible for
the linking and unlinking respectively of your policy module with each of the base policies.
This is done using the semodule
command.
Usually, target systems will only have one of the base policies installed, so it's important
that the scriptlets don't produce spurious error messages nor return failure exit codes that
would break an RPM transaction.
In addition to installing the policy module, you will probably need your scriptlets to fix up
some file contexts if your policy module includes any file contexts specifications.
The fixfiles
command can be used to fix the file contexts of all files in a specified package,
and is useful to use in scriptlets. This may not be sufficient in all caess though, as you may want
to set contexts of files not actually included in the myapp
package itself, such as log or cache
files created by myapp
. In these cases, the restorecon
command may be useful.
There may also be other things usefully done in the scriptlets, such as restarting the server
dæmon if myapp
is a server. Doing a condrestart
of the server's initscript after installing the policy module
will ensure that it runs in your confined domain.
So think carefully about what needs to be done in your scriplets as they will usually need more customisation
than any of the other examples in this document.
Don't forget to add scriptlet dependencies for any commands used in the scriptlets.
If you use fixfiles -R myapp
in a scriptlet, there should be a dependency of myapp
for that scriptlet.
%package selinux Requires(post): /usr/sbin/semodule, /sbin/restorecon, /sbin/fixfiles, myapp Requires(postun): /usr/sbin/semodule, /sbin/restorecon, /sbin/fixfiles, myapp ... %post selinux for selinuxvariant in %{selinux_variants} do /usr/sbin/semodule -s ${selinuxvariant} -i \ %{_datadir}/selinux/${selinuxvariant}/mymodule.pp &> /dev/null || : done /sbin/fixfiles -R myapp restore || : /sbin/restorecon -R %{_localstatedir}/cache/myapp || : %postun selinux if [ $1 -eq 0 ] ; then for selinuxvariant in %{selinux_variants} do /usr/sbin/semodule -s ${selinuxvariant} -r mymodule &> /dev/null || : done /sbin/fixfiles -R myapp restore || : [ -d %{_localstatedir}/cache/myapp ] && \ /sbin/restorecon -R %{_localstatedir}/cache/myapp &> /dev/null || : fi
Policy Revisions
Be sure to increase the policy version number in the mymodule.te
file whenever you make changes to the policy sources.
Whilst the use of semodule -i
to install the module means that version number checking isn't done by semodule
at
install-time, it will be useful to know the exact version of the policy being used if you receive any bug reports,
and this can be checked using semodule -l
.
The actual version numbering used for the policy module can be totally independent of the version numbering of the
main application, since either may change without a change being needed for the other.
It is conventional to use a three-part version number like 1.4.7
(see the output of semodule -l
)
but what you use is up to you; just be sure to bump the version number every time there is a change to the polioy.
Version number comparisons are done using strverscmp()
so in theory any character that can be used there
is OK in a version number, but for sanity's sake it's best to stick with [0-9a-z.]
.
Upstream reference policy versioning is x.y.z
, where z
is incremented every
time the module changes, y
is incremented on a upstream release if the
module has changed since the last release (i.e. z
!= 0), and x
is
incremented on major changes to the module.
Policy modules in development should probably have x
= 0, with version 1 reserved for the first appearance
in the upstream policy.
Obsoletes
If your policy module is stable and there are no apparent issues with it, it's a good idea to submit it for
inclusion in the upstream reference policy .
That way, all distributions using SELinux will benefit from your
work, not just the users of your myapp-selinux
package.
Once the policy module is included upstream, there is no longer any need to ship the standalone module
and in fact it is unlikely to be usable since any types it defines would conflict with those in the upstream policy.
In order to maintain an upgrade path in this situation, the first version of the myapp
package designed to
work with the new upstream policy module should obsolete the myapp-selinux
package and conflict with selinux-policy
packages older than the version containing mymodule
.
Let's say for example that mymodule
is included in selinux-policy
version 3.0.1:
Obsoletes: myapp-selinux Conflicts: selinux-policy < 3.0.1
Using Conflicts rather a versioned dependency of selinux-policy >= 3.0.1 is necessary in order to
allow myapp
to be installed without selinux-policy
for those people not using SELinux.
Examples
The following packages in Fedora ["Extras"] include SELinux policy modules:
Templates
Package with Policy Module in Separate Subpackage
%define selinux_variants mls strict targeted %define selinux_policyver %(sed -e 's,.*selinux-policy-\\([^/] *\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp) %define modulename mymodule Name: myapp Version: 0.01 Release: 1%{?dist} Summary: Example application Group: Examples/SELinux License: Whatever URL: http://www.example.com/myapp/ Source0: http://www.example.com/myapp/downloads/myapp-%{version}.tar.gz Source1: %{modulename}.if Source2: %{modulename}.te Source3: %{modulename}.fc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description myapp is an example application that needs a custom SELinux policy module. %package selinux Summary: SELinux policy module supporting myapp Group: System Environment/Base BuildRequires: checkpolicy, selinux-policy-devel, hardlink %if "%{selinux_policyver}" != "" Requires: selinux-policy >= %{selinux_policyver} %endif Requires: %{name} = %{version}-%{release} Requires(post): /usr/sbin/semodule, /sbin/restorecon Requires(postun): /usr/sbin/semodule, /sbin/restorecon %description selinux SELinux policy module supporting myapp %prep %setup -q mkdir SELinux cp -p %{SOURCE1} %{SOURCE2} %{SOURCE3} SELinux %build %configure make cd SELinux for selinuxvariant in %{selinux_variants} do make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile mv %{modulename}.pp %{modulename}.pp.${selinuxvariant} make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean done cd - %install rm -rf %{buildroot} make DESTDIR=%{buildroot} install cd SELinux for selinuxvariant in %{selinux_variants} do install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant} install -p -m 644 %{modulename}.pp.${selinuxvariant} \ %{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp done cd - /usr/sbin/hardlink -cv %{buildroot}%{_datadir}/selinux %clean rm -rf %{buildroot} %post selinux for selinuxvariant in %{selinux_variants} do /usr/sbin/semodule -s ${selinuxvariant} -i \ %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || : done /sbin/restorecon %{_localstatedir}/cache/myapp || : %postun selinux if [ $1 -eq 0 ] ; then for selinuxvariant in %{selinux_variants} do /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || : done [ -d %{_localstatedir}/cache/myapp ] && \ /sbin/restorecon -R %{_localstatedir}/cache/myapp &> /dev/null || : fi %files %defattr(-,root,root,0755) %doc ChangeLog AUTHOR COPYING %{_bindir}/myapp %files selinux %defattr(-,root,root,0755) %doc SELinux/* %{_datadir}/selinux/*/%{modulename}.pp %changelog * Mon Jul 31 2006 John Doe <doe@example.com> 0.01-1 - Initial version
Package with Policy Module Integrated in Main Package
%define selinux_variants mls strict targeted %define selinux_policyver %(sed -e 's,.*selinux-policy-\\([^/] *\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp) %define modulename mymodule Name: myapp Version: 0.01 Release: 1%{?dist} Summary: Example application Group: Examples/SELinux License: Whatever URL: http://www.example.com/myapp/ Source0: http://www.example.com/myapp/downloads/myapp-%{version}.tar.gz Source1: %{modulename}.if Source2: %{modulename}.te Source3: %{modulename}.fc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: checkpolicy, selinux-policy-devel, hardlink %if "%{selinux_policyver}" != "" Requires: selinux-policy >= %{selinux_policyver} %endif Requires(post): /usr/sbin/semodule, /sbin/fixfiles, myapp Requires(postun): /usr/sbin/semodule %prep %setup -q mkdir SELinux cp -p %{SOURCE1} %{SOURCE2} %{SOURCE3} SELinux %build %configure make cd SELinux for selinuxvariant in %{selinux_variants} do make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile mv %{modulename}.pp %{modulename}.pp.${selinuxvariant} make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean done cd - %install rm -rf %{buildroot} make DESTDIR=%{buildroot} install cd SELinux for selinuxvariant in %{selinux_variants} do install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant} install -p -m 644 %{modulename}.pp.${selinuxvariant} \ %{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp done cd - /usr/sbin/hardlink -cv %{buildroot}%{_datadir}/selinux %clean rm -rf %{buildroot} %post for selinuxvariant in %{selinux_variants} do /usr/sbin/semodule -s ${selinuxvariant} -i \ %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || : done /sbin/fixfiles -R myapp restore || : %postun if [ $1 -eq 0 ] ; then for selinuxvariant in %{selinux_variants} do /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || : done fi %files %defattr(-,root,root,0755) %doc ChangeLog AUTHOR COPYING SELinux/* %{_bindir}/myapp %{_datadir}/selinux/*/%{modulename}.pp %changelog * Mon Jul 31 2006 John Doe <doe@example.com> 0.01-1 - Initial version