From Fedora Project Wiki

(Fix selinux-policy dependency generation for current rawhide (still works on older releases))
No edit summary
 
(18 intermediate revisions by 8 users not shown)
Line 1: Line 1:
{{admon/warning | This document is outdated. Please use https://fedoraproject.org/wiki/SELinux/IndependentPolicy instead.}}
= Packaging SELinux Policy Modules =
= Packaging SELinux Policy Modules =


Fedora Core 5 introduced a few SELinux feature: loadable 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,
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.
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.
This page presents guidelines for how to package a policy module alongside your application in the same source RPM package.
Policy modules can also be used in Red Hat Enterprise Linux 5 and clones.


{{Admon/note | Note that developing the actual SELinux policy for your application is outside the scope of this document.}}
{{Admon/note | Note that developing the actual SELinux policy for your application is outside the scope of this document.}}
Line 37: Line 42:
should eventually become unnecessary because they get merged into the
should eventually become unnecessary because they get merged into the
main upstream project. So a separate package should be a short-lived package really,
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]] ).
and should probably get special permission to be included, as with kernel modules (see [[Packaging:Guidelines#No_External_Kernel_Modules]] ).


So combining the SELinux policy module with the main package SRPM is preferred for now,
So combining the SELinux policy module with the main package SRPM is preferred for now,
Line 44: Line 49:
== Build Dependencies ==
== Build Dependencies ==


Your package will need the following B''''''uildRequires:
Your package will need the following '''BuildRequires''':


<pre>BuildRequires: checkpolicy, selinux-policy-devel
<pre>BuildRequires: checkpolicy, selinux-policy-devel, /usr/share/selinux/devel/policyhelp
</pre>
</pre>
The <code>/usr/share/selinux/devel/policyhelp</code> requirement was necessary to extract
the version number of the <code>selinux-policy</code> package being built against, which
is used to enforce a minimum version requirement on <code>selinux-policy</code> when the
built package is installed. The <code>policyhelp</code> file itself can be found in either
the <code>selinux-policy</code>, <code>selinux-policy-devel</code>, or <code>selinux-policy-doc</code>
package (depending on OS release), which is why we cannot simply use a package name unless
we are prepared to sacrifice spec file portability. From Fedora 20 onwards, this method is
no longer necessary, so if your packaging is not targeting any releases prior to Fedora 20 or
EPEL-5/6, the <code>/usr/share/selinux/devel/policyhelp</code> requirement is not needed.


== Runtime Dependencies ==
== Runtime Dependencies ==


As the compiled policy module packages (<code>mymodule.pp</code>) will be installed into these directories:
As the compiled policy module packages (<code>mymodule.pp</code>) will be installed into these directories:
<pre>%{_datadir}/selinux/targeted
<pre>
%{_datadir}/selinux/strict
%{_datadir}/selinux/targeted
%{_datadir}/selinux/mls</pre>
%{_datadir}/selinux/mls
</pre>
it is necessary for the <code>myapp-selinux</code> package to have a dependency on <code>selinux-policy</code> in order to
it is necessary for the <code>myapp-selinux</code> package to have a dependency on <code>selinux-policy</code> in order to
satisfy directory ownership requirements.
satisfy directory ownership requirements.
Line 60: Line 76:
However, a straightforward dependency on that package is not quite enough.
However, a straightforward dependency on that package is not quite enough.
Policy modules compiled using one version of <code>selinux-policy</code> may not work with older <code>selinux-policy</code> versions
Policy modules compiled using one version of <code>selinux-policy</code> may not work with older <code>selinux-policy</code> versions
(see [http://www.redhat.com/archives/fedora-selinux-list/2006-May/msg00102.html this example] for instance).
(see [http://www.redhat.com/archives/fedora-selinux-list/2006-May/msg00102.html this example] for instance).
So the dependency needs to be a versioned dependency, requiring that the version of <code>selinux-policy</code> on the
So the dependency needs to be a versioned dependency, requiring that the version of <code>selinux-policy</code> on the
target system is at least as new as the version of <code>selinux-policy</code> that the package was built with.
target system is at least as new as the version of <code>selinux-policy</code> that the package was built with.


There is no particularly clean and straighforward way of determining the current policy version at the moment,
There was no particularly clean and straightforward way of determining the current policy version prior to Fedora 20,
but this hack works for all <code>selinux-policy</code> packages since Fedora Core 5 was released:
but this hack works for <code>selinux-policy</code> packages since Fedora Core 5 until Fedora 19 (and EPEL-5/6).
<pre>%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
<pre>
%global selinux_policyver %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
Requires:      selinux-policy >= %{selinux_policyver}
Requires:      selinux-policy >= %{selinux_policyver}
</pre>
From Fedora 20 the <code>selinux-policy</code> package ships with a RPM macro package defining a macro <code>%_selinux_policy_version</code>
that specifies the ''version-release'' of the <code>selinux-policy</code> package, so you can just use:
<pre>
%if "%{_selinux_policy_version}" != ""
Requires:      selinux-policy >= %{_selinux_policy_version}
%endif
</pre>
This became necessary because the old hack was broken by the introduction of
[https://fedoraproject.org/wiki/Changes/UnversionedDocdirs unversioned doc directories] in Fedora 20.
Read [https://lists.fedoraproject.org/pipermail/devel/2013-August/188074.html this thread] and
[https://bugzilla.redhat.com/show_bug.cgi?id=999584 Bug 999584]
for more information.
If you want a single spec file that can support both the new and old methods, use this:
<pre>
%{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null)}
%if "%{_selinux_policy_version}" != ""
Requires:      selinux-policy >= %{_selinux_policy_version}
%endif
</pre>
</pre>


Line 78: Line 116:
were added to your policy. It also makes sense to make this dependency a fully-versioned dependency, to ensure that the
were added to your policy. It also makes sense to make this dependency a fully-versioned dependency, to ensure that the
<code>myapp-selinux</code> package gets updated whenever the end user updates the main <code>myapp</code> package.
<code>myapp-selinux</code> package gets updated whenever the end user updates the main <code>myapp</code> package.
<pre>Requires:      %{name} = %{version}-%{release}
<pre>
Requires:      %{name} = %{version}-%{release}
</pre>
</pre>


Line 84: Line 123:


You should build your policy module for all of the base policies supported in the distribution version you are targeting.
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 <code>mls</code>, <code>strict</code>, and <code>targeted</code> policies.
For Fedora 19, this means the <code>mls</code> and <code>targeted</code> policies.
<pre>%define selinux_variants mls strict targeted
<pre>
%global selinux_variants mls targeted
</pre>
 
If you would rather not hardcode the variants, you could use this approach:
<pre>
%global selinux_types %(%{__awk} '/^#[[:space:]]*SELINUXTYPE=/,/^[^#]/ { if ($3 == "-") printf "%s ", $2 }' /etc/selinux/config 2>/dev/null)
%global selinux_variants %([ -z "%{selinux_types}" ] && echo mls targeted || echo %{selinux_types})
</pre>
</pre>
The source code for your SELinux policy module will include at least a ''type enforcement'' rules file (<code>mymodule.te</code>),
The source code for your SELinux policy module will include at least a ''type enforcement'' rules file (<code>mymodule.te</code>),
and possibly also a ''file contexts'' file (<code>mymodule.fc</code>) and an ''interface'' file (<code></code>mymodule.if<code></code>).
and possibly also a ''file contexts'' file (<code>mymodule.fc</code>) and an ''interface'' file (<code></code>mymodule.if<code></code>).
You could include each of these as separate source files:
You could include each of these as separate source files:
<pre>Source2:        mymodule.te
<pre>
Source2:        mymodule.te
Source3:        mymodule.fc
Source3:        mymodule.fc
Source4:        mymodule.if</pre>
Source4:        mymodule.if
</pre>
or you might bundle them together as a tarball:
or you might bundle them together as a tarball:
<pre>Source2:        mymodule-0.1.tar.bz2
<pre>
Source2:        mymodule-0.1.tar.bz2
</pre>
</pre>
Either way is OK but separate source files is probably better for packages aiming to go into Fedora because the
Either way is OK but separate source files is probably better for packages aiming to go into Fedora because the
Line 100: Line 150:


To compile the policy files, copy them into an empty directory in <code>%prep</code> and use <code>/usr/share/selinux/devel/Makefile</code> in <code>%build</code>:
To compile the policy files, copy them into an empty directory in <code>%prep</code> and use <code>/usr/share/selinux/devel/Makefile</code> in <code>%build</code>:
<pre>Source2:        mymodule.te
<pre>
Source2:        mymodule.te
Source3:        mymodule.fc
Source3:        mymodule.fc
Source4:        mymodule.if
Source4:        mymodule.if
Line 115: Line 166:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile
  make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile
mv mymodule.pp mymodule.pp.${selinuxvariant}
  mv mymodule.pp mymodule.pp.${selinuxvariant}
make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean
  make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean
done
done
cd -</pre>
cd -
</pre>


== Installing the Policy Modules ==
== Installing the Policy Modules ==
Line 125: Line 177:
In <code>%install</code>, copy the compiled policy module for each base policy into <code>%{_datadir}/selinux/</code>''POLICYNAME'':
In <code>%install</code>, copy the compiled policy module for each base policy into <code>%{_datadir}/selinux/</code>''POLICYNAME'':


<pre>%install
<pre>
%install
...
...
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant}
  install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant}
install -p -m 644 mymodule.pp.${selinuxvariant} \
  install -p -m 644 SELinux/mymodule.pp.${selinuxvariant} \
%{buildroot}%{_datadir}/selinux/${selinuxvariant}/mymodule.pp
    %{buildroot}%{_datadir}/selinux/${selinuxvariant}/mymodule.pp
done</pre>
done
</pre>


If there is nothing in your policy that is specific to a particular base policy, it is likely
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
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 <code>%install</code>:
not install duplicate files, you might want to link identical files together at the end of <code>%install</code>:
<pre>BuildRequires: hardlink
<pre>
BuildRequires: hardlink
...
...


Line 156: Line 211:
themselves in the <code>%files</code> list:
themselves in the <code>%files</code> list:


<pre>%files selinux
<pre>
%files selinux
%defattr(-,root,root,0755)
%defattr(-,root,root,0755)
%doc SELinux/*
%doc SELinux/*
%{_datadir}/selinux/*/mymodule.pp</pre>
%{_datadir}/selinux/*/mymodule.pp
</pre>


== Scriptlets ==
== Scriptlets ==
Line 173: Line 230:
some file contexts if your policy module includes any file contexts specifications.
some file contexts if your policy module includes any file contexts specifications.
The <code>fixfiles</code> command can be used to fix the file contexts of all files in a specified package,
The <code>fixfiles</code> 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
and is useful to use in scriptlets. This may not be sufficient in all cases though, as you may want
to set contexts of files not actually included in the <code>myapp</code> package itself, such as log or cache
to set contexts of files not actually included in the <code>myapp</code> package itself, such as log or cache
files created by <code>myapp</code>. In these cases, the <code>restorecon</code> command may be useful.
files created by <code>myapp</code>. In these cases, the <code>restorecon</code> command may be useful.
Line 185: Line 242:
If you use <code>fixfiles -R myapp</code> in a scriptlet, there should be a dependency of <code>myapp</code> for that scriptlet.
If you use <code>fixfiles -R myapp</code> in a scriptlet, there should be a dependency of <code>myapp</code> for that scriptlet.


<pre>%package selinux
<pre>
%package selinux
Requires(post):  /usr/sbin/semodule, /sbin/restorecon, /sbin/fixfiles, myapp
Requires(post):  /usr/sbin/semodule, /sbin/restorecon, /sbin/fixfiles, myapp
Requires(postun): /usr/sbin/semodule, /sbin/restorecon, /sbin/fixfiles, myapp
Requires(postun): /usr/sbin/semodule, /sbin/restorecon, /sbin/fixfiles, myapp
Line 193: Line 251:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
/usr/sbin/semodule -s ${selinuxvariant} -i \
  /usr/sbin/semodule -s ${selinuxvariant} -i \
%{_datadir}/selinux/${selinuxvariant}/mymodule.pp &> /dev/null || :
    %{_datadir}/selinux/${selinuxvariant}/mymodule.pp &> /dev/null || :
done
done
/sbin/fixfiles -R myapp restore || :
/sbin/fixfiles -R myapp restore || :
Line 201: Line 259:
%postun selinux
%postun selinux
if [ $1 -eq 0 ] ; then
if [ $1 -eq 0 ] ; then
for selinuxvariant in %{selinux_variants}
  for selinuxvariant in %{selinux_variants}
do
  do
/usr/sbin/semodule -s ${selinuxvariant} -r mymodule &> /dev/null || :
    /usr/sbin/semodule -s ${selinuxvariant} -r mymodule &> /dev/null || :
done
  done
/sbin/fixfiles -R myapp restore || :
  /sbin/fixfiles -R myapp restore || :
[ -d %{_localstatedir}/cache/myapp ]  && \
  [ -d %{_localstatedir}/cache/myapp ]  && \
/sbin/restorecon -R %{_localstatedir}/cache/myapp &> /dev/null || :
    /sbin/restorecon -R %{_localstatedir}/cache/myapp &> /dev/null || :
fi</pre>
fi
</pre>


{{Admon/note | It is OK to wait until <code>%postun</code> (rather than <code>%preun</code>) to remove policy modules because the  <code>mymodule.pp</code> files are used only at module installation time and are not needed after that point, so it doesn't matter if the files are removed before the policy module is uninstalled.}}
It is OK to wait until <code>%postun</code> (rather than <code>%preun</code>) to remove policy modules because the  <code>mymodule.pp</code> files are used only at module installation time and are not needed after that point, so it doesn't matter if the files are removed before the policy module is uninstalled.


{{Admon/note | Using <code>semodule -i</code> (''install'') rather than <code>semodule -u</code> (''upgrade'') ensures that your policy module will be loaded even if there is an existing module installed with a higher version number; using the ''upgrade'' option in such a situation would result in the module not getting loaded, but <code>semodule -l</code> showing that a module <code>mymodule</code> is loaded, which could lead to some confusing bug reports for <code>myapp</code>.}}
Using <code>semodule -i</code> (''install'') rather than <code>semodule -u</code> (''upgrade'') ensures that your policy module will be loaded even if there is an existing module installed with a higher version number; using the ''upgrade'' option in such a situation would result in the module not getting loaded, but <code>semodule -l</code> showing that a module <code>mymodule</code> is loaded, which could lead to some confusing bug reports for <code>myapp</code>.


{{Admon/warning | Note that you mustn't do a <code>condrestart</code> in <code>%postun</code> to transition a dæmon process back to the unconfined domain '''after''' the module has been unloaded. You first have to transition the dæmon domain, '''then''' remove the policy module. Otherwise the process may end up in an odd state and can't be killed until SELinux is disabled:
Note that you mustn't do a <code>condrestart</code> in <code>%postun</code> to transition a dæmon process back to the unconfined domain '''after''' the module has been unloaded. You first have to transition the dæmon domain, '''then''' remove the policy module. Otherwise the process may end up in an odd state and can't be killed until SELinux is disabled:
<pre>%post selinux
<pre>
%post selinux
/usr/sbin/setsebool myapp_disable_trans 0 &> /dev/null || :
/usr/sbin/setsebool myapp_disable_trans 0 &> /dev/null || :


Line 223: Line 283:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
/usr/sbin/semodule -s ${selinuxvariant} -r mymodule &> /dev/null || :
  /usr/sbin/semodule -s ${selinuxvariant} -r mymodule &> /dev/null || :
done</pre>
done
}}
</pre>
Now that <code>*_disable_trans</code> booleans aren't used any more, this arrangement would have to be replaced with one that checked whether the dæmon was running at the start of <code>%postun</code>, stopped the dæmon, removed the module, then
restarted the dæmon if it had originally been running.
 
== Policy Revisions ==
== Policy Revisions ==


Line 260: Line 323:
packages older than the version containing <code>mymodule</code>.
packages older than the version containing <code>mymodule</code>.
Let's say for example that <code>mymodule</code> is included in <code>selinux-policy</code> version 3.0.1:
Let's say for example that <code>mymodule</code> is included in <code>selinux-policy</code> version 3.0.1:
<pre>Obsoletes: myapp-selinux
<pre>
Conflicts: selinux-policy < 3.0.1</pre>
Obsoletes: myapp-selinux
Conflicts: selinux-policy < 3.0.1
</pre>
Using ''Conflicts'' rather a versioned dependency of ''selinux-policy >= 3.0.1'' is necessary in order to
Using ''Conflicts'' rather a versioned dependency of ''selinux-policy >= 3.0.1'' is necessary in order to
allow <code>myapp</code> to be installed without <code>selinux-policy</code> for those people not using SELinux.
allow <code>myapp</code> to be installed without <code>selinux-policy</code> for those people not using SELinux.
Line 267: Line 332:
== Examples ==
== Examples ==


The following packages in Fedora ["Extras"]  include SELinux policy modules:
The following packages in Fedora include SELinux policy modules:


* [http://cvs.fedora.redhat.com/viewcvs/devel/cyphesis/cyphesis.spec?root=extras&view=markup cyphesis]  
* [http://pkgs.fedoraproject.org/cgit/mod_fcgid.git mod_fcgid]  
* [http://cvs.fedora.redhat.com/viewcvs/devel/mod_fcgid/mod_fcgid.spec?root=extras&view=markup mod_fcgid]  
* [http://pkgs.fedoraproject.org/cgit/mod_selinux.git mod_selinux]
* [http://cvs.fedora.redhat.com/viewcvs/devel/pure-ftpd/pure-ftpd.spec?root=extras&view=markup pure-ftpd]  
* [http://pkgs.fedoraproject.org/cgit/pure-ftpd.git pure-ftpd]  


{{Admon/note | Note that some of the packages listed here pre-date this document.}}
{{Admon/note | Note that some of the packages listed here pre-date this document.}}
Line 279: Line 344:
=== Package with Policy Module in Separate Subpackage ===
=== Package with Policy Module in Separate Subpackage ===


<pre>%define selinux_variants mls strict targeted
<pre>
%define selinux_policyver %(sed -e 's,.*selinux-policy-\\([^/] *\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp)
%global selinux_variants mls targeted
%define modulename mymodule
%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
%global modulename mymodule


Name:          myapp
Name:          myapp
Line 302: Line 368:
Summary:        SELinux policy module supporting myapp
Summary:        SELinux policy module supporting myapp
Group:          System Environment/Base
Group:          System Environment/Base
BuildRequires:  checkpolicy, selinux-policy-devel, hardlink
BuildRequires:  checkpolicy, selinux-policy-devel, /usr/share/selinux/devel/policyhelp, hardlink
%if "%{selinux_policyver}" != ""
%if "%{selinux_policyver}" != ""
Requires:      selinux-policy >= %{selinux_policyver}
Requires:      selinux-policy >= %{selinux_policyver}
Line 325: Line 391:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile
  make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile
mv %{modulename}.pp %{modulename}.pp.${selinuxvariant}
  mv %{modulename}.pp %{modulename}.pp.${selinuxvariant}
make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean
  make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean
done
done
cd -
cd -
Line 338: Line 404:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant}
  install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant}
install -p -m 644 %{modulename}.pp.${selinuxvariant} \
  install -p -m 644 %{modulename}.pp.${selinuxvariant} \
%{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp
    %{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp
done
done
cd -
cd -
Line 352: Line 418:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
/usr/sbin/semodule -s ${selinuxvariant} -i \
  /usr/sbin/semodule -s ${selinuxvariant} -i \
%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || :
    %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || :
done
done
/sbin/restorecon %{_localstatedir}/cache/myapp || :
/sbin/restorecon %{_localstatedir}/cache/myapp || :
Line 359: Line 425:
%postun selinux
%postun selinux
if [ $1 -eq 0 ] ; then
if [ $1 -eq 0 ] ; then
for selinuxvariant in %{selinux_variants}
  for selinuxvariant in %{selinux_variants}
do
  do
/usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || :
    /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || :
done
  done
[ -d %{_localstatedir}/cache/myapp ]  && \
  [ -d %{_localstatedir}/cache/myapp ]  && \
/sbin/restorecon -R %{_localstatedir}/cache/myapp &> /dev/null || :
    /sbin/restorecon -R %{_localstatedir}/cache/myapp &> /dev/null || :
fi
fi


Line 383: Line 449:


=== Package with Policy Module Integrated in Main Package ===
=== Package with Policy Module Integrated in Main Package ===
<pre>%define selinux_variants mls strict targeted
 
%define selinux_policyver %(sed -e 's,.*selinux-policy-\\([^/] *\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp)
<pre>
%define modulename mymodule
%global selinux_variants mls targeted
%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
%global modulename mymodule


Name:          myapp
Name:          myapp
Line 399: Line 467:
Source3:        %{modulename}.fc
Source3:        %{modulename}.fc
BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires:  checkpolicy, selinux-policy-devel, hardlink
BuildRequires:  checkpolicy, selinux-policy-devel, /usr/share/selinux/devel/policyhelp, hardlink
%if "%{selinux_policyver}" != ""
%if "%{selinux_policyver}" != ""
Requires:      selinux-policy >= %{selinux_policyver}
Requires:      selinux-policy >= %{selinux_policyver}
Line 418: Line 486:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile
  make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile
mv %{modulename}.pp %{modulename}.pp.${selinuxvariant}
  mv %{modulename}.pp %{modulename}.pp.${selinuxvariant}
make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean
  make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean
done
done
cd -
cd -
Line 431: Line 499:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant}
  install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant}
install -p -m 644 %{modulename}.pp.${selinuxvariant} \
  install -p -m 644 %{modulename}.pp.${selinuxvariant} \
%{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp
    %{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp
done
done
cd -
cd -
Line 445: Line 513:
for selinuxvariant in %{selinux_variants}
for selinuxvariant in %{selinux_variants}
do
do
/usr/sbin/semodule -s ${selinuxvariant} -i \
  /usr/sbin/semodule -s ${selinuxvariant} -i \
%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || :
    %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || :
done
done
/sbin/fixfiles -R myapp restore || :
/sbin/fixfiles -R myapp restore || :
Line 452: Line 520:
%postun
%postun
if [ $1 -eq 0 ] ; then
if [ $1 -eq 0 ] ; then
for selinuxvariant in %{selinux_variants}
  for selinuxvariant in %{selinux_variants}
do
  do
/usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || :
    /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || :
done
  done
fi
fi


Line 471: Line 539:
{{Admon/note | The use of <code>restorecon</code> in one example's scriptlets and <code>fixfiles</code> in the other example is
{{Admon/note | The use of <code>restorecon</code> in one example's scriptlets and <code>fixfiles</code> in the other example is
to indicate that there's no ''one right way'' of doing things rather than some subtly different requirements between separate and integrated packages.}}
to indicate that there's no ''one right way'' of doing things rather than some subtly different requirements between separate and integrated packages.}}
[[Category:Packaging guidelines drafts]]

Latest revision as of 13:45, 17 May 2023

This document is outdated. Please use https://fedoraproject.org/wiki/SELinux/IndependentPolicy instead.

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.

Policy modules can also be used in Red Hat Enterprise Linux 5 and clones.

Note that developing the actual SELinux policy for your application is outside the scope of this document.

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:Guidelines#No_External_Kernel_Modules ).

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 BuildRequires:

BuildRequires: checkpolicy, selinux-policy-devel, /usr/share/selinux/devel/policyhelp

The /usr/share/selinux/devel/policyhelp requirement was necessary to extract the version number of the selinux-policy package being built against, which is used to enforce a minimum version requirement on selinux-policy when the built package is installed. The policyhelp file itself can be found in either the selinux-policy, selinux-policy-devel, or selinux-policy-doc package (depending on OS release), which is why we cannot simply use a package name unless we are prepared to sacrifice spec file portability. From Fedora 20 onwards, this method is no longer necessary, so if your packaging is not targeting any releases prior to Fedora 20 or EPEL-5/6, the /usr/share/selinux/devel/policyhelp requirement is not needed.

Runtime Dependencies

As the compiled policy module packages (mymodule.pp) will be installed into these directories:

%{_datadir}/selinux/targeted
%{_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 was no particularly clean and straightforward way of determining the current policy version prior to Fedora 20, but this hack works for selinux-policy packages since Fedora Core 5 until Fedora 19 (and EPEL-5/6).

%global selinux_policyver %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
Requires:       selinux-policy >= %{selinux_policyver}

From Fedora 20 the selinux-policy package ships with a RPM macro package defining a macro %_selinux_policy_version that specifies the version-release of the selinux-policy package, so you can just use:

%if "%{_selinux_policy_version}" != ""
Requires:       selinux-policy >= %{_selinux_policy_version}
%endif

This became necessary because the old hack was broken by the introduction of unversioned doc directories in Fedora 20. Read this thread and Bug 999584 for more information.

If you want a single spec file that can support both the new and old methods, use this:

%{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null)}
%if "%{_selinux_policy_version}" != ""
Requires:      selinux-policy >= %{_selinux_policy_version}
%endif

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 19, this means the mls and targeted policies.

%global selinux_variants mls targeted

If you would rather not hardcode the variants, you could use this approach:

%global selinux_types %(%{__awk} '/^#[[:space:]]*SELINUXTYPE=/,/^[^#]/ { if ($3 == "-") printf "%s ", $2 }' /etc/selinux/config 2>/dev/null)
%global selinux_variants %([ -z "%{selinux_types}" ] && echo mls targeted || echo %{selinux_types})

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 SELinux/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 cases 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

It is OK to wait until %postun (rather than %preun) to remove policy modules because the mymodule.pp files are used only at module installation time and are not needed after that point, so it doesn't matter if the files are removed before the policy module is uninstalled.

Using semodule -i (install) rather than semodule -u (upgrade) ensures that your policy module will be loaded even if there is an existing module installed with a higher version number; using the upgrade option in such a situation would result in the module not getting loaded, but semodule -l showing that a module mymodule is loaded, which could lead to some confusing bug reports for myapp.

Note that you mustn't do a condrestart in %postun to transition a dæmon process back to the unconfined domain after the module has been unloaded. You first have to transition the dæmon domain, then remove the policy module. Otherwise the process may end up in an odd state and can't be killed until SELinux is disabled:

%post selinux
/usr/sbin/setsebool myapp_disable_trans 0 &> /dev/null || :

%postun selinux
/usr/sbin/setsebool myapp_disable_trans 1
/sbin/service myapp condrestart &> /dev/null || :
for selinuxvariant in %{selinux_variants}
do
  /usr/sbin/semodule -s ${selinuxvariant} -r mymodule &> /dev/null || :
done

Now that *_disable_trans booleans aren't used any more, this arrangement would have to be replaced with one that checked whether the dæmon was running at the start of %postun, stopped the dæmon, removed the module, then restarted the dæmon if it had originally been running.

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 include SELinux policy modules:

Note that some of the packages listed here pre-date this document.

Templates

Package with Policy Module in Separate Subpackage

%global selinux_variants mls targeted
%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
%global 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, /usr/share/selinux/devel/policyhelp, 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

%global selinux_variants mls targeted
%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
%global 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, /usr/share/selinux/devel/policyhelp, 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
The use of restorecon in one example's scriptlets and fixfiles in the other example is to indicate that there's no one right way of doing things rather than some subtly different requirements between separate and integrated packages.