David Moreau Simard

4 minute read

The problem

Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!

Uh oh, have you seen that before ?

I bumped into an interesting issue recently when running Ansible from a RHEL derivative host.

It turns out that Ansible requires the package libselinux-python to be installed on hosts where Ansible is running file modules.

If you have SELinux enabled on remote nodes, you will also want to install libselinux-python on them before using any copy/file/template related functions in Ansible. You can of course still use the yum module in Ansible to install this package on remote systems that do not have it.

Easy, right, you just make sure it’s installed before running so you don’t run into the dreaded error.

What happens

libselinux-python was installed on both the control node and the remote node(s) but I kept getting that error.

Here’s what happens:

  • I run Ansible from a virtual environment
  • From a control host where selinux is enabled
  • A playbook runs file operations on the remote nodes just fine
  • A delegate_to task to localhost (the control node) that creates a file fails with the selinux error.

I google a bit and stumble on this not-so-related issue on github talking about selinux problems from a chroot. chroots not being exactly far off from a virtual environment I see that they ensure that the selinux python module is there and usable. In a perfect world, this is what should happen and is expected by Ansible:

$ python
Python 2.7.10 (default, Sep  8 2015, 17:20:17) 
[GCC 5.1.1 20150618 (Red Hat 5.1.1-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import selinux
>>> selinux.is_selinux_enabled()
1

In that issue, the user complains how the selinux module can be imported but is apparently unable to find some required files.

At this point the issue became obvious and I’ll have to blame lack of caffeine for not figuring it out sooner. Although my control host did have libselinux-python installed, since my virtual environments did not inherit system site packages, they did not contain the selinux package:

$ python
Python 2.7.10 (default, Sep  8 2015, 17:20:17) 
[GCC 5.1.1 20150618 (Red Hat 5.1.1-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import selinux
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named selinux

Okay, so if you inherit site packages, that solves the problem - you’ll get the selinux module in your virtual environment - along with the rest of the globally installed python modules that are installed. That’s a problem for me, I don’t want to pollute my virtual environments with site packages as they are used in jenkins jobs and could potentially have an effect on the jobs.

The hack

libselinux-python isn’t exactly available through pypi but it’s fairly self-contained and we can probably assume the dependencies are always installed:

$ repoquery -l libselinux-python
/usr/lib64/python2.7/site-packages/selinux
/usr/lib64/python2.7/site-packages/selinux/__init__.py
/usr/lib64/python2.7/site-packages/selinux/__init__.pyc
/usr/lib64/python2.7/site-packages/selinux/__init__.pyo
/usr/lib64/python2.7/site-packages/selinux/_selinux.so
/usr/lib64/python2.7/site-packages/selinux/audit2why.so

$ yum deplist libselinux-python
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.centos.org
 * epel: mirror.cogentco.com
 * extras: mirror.centos.org
 * updates: mirror.centos.org
package: libselinux-python.x86_64 2.2.2-6.el7
  dependency: libc.so.6(GLIBC_2.14)(64bit)
   provider: glibc.x86_64 2.17-106.el7_2.1
  dependency: libselinux = 2.2.2-6.el7
   provider: libselinux.x86_64 2.2.2-6.el7
   provider: libselinux.i686 2.2.2-6.el7
  dependency: libselinux.so.1()(64bit)
   provider: libselinux.x86_64 2.2.2-6.el7
  dependency: python(abi) = 2.7
   provider: python.x86_64 2.7.5-34.el7
  dependency: rtld(GNU_HASH)
   provider: glibc.x86_64 2.17-106.el7_2.1
   provider: glibc.i686 2.17-106.el7_2.1

I told myself this wouldn’t work but apparently it does - I just copied /usr/lib64/python2.7/site-packages/selinux to $VIRTUAL_ENV/lib/python2.7/site-packages.

I’ll have to live with this hack for now but I plan moving the job execution to ephemeral nodes where I will be able to inherit site packages to avoid having to do this.

Have you bumped into this as well ? Did you end up with a more elegant solution ? Do let me know !

Edit: Found a bug ?

This really bothered me more than it should have (hence the blog post) and I ended up spending more time trying to hunt down the issue.

When running Ansible in verbose mode, I noticed that when running the delegate_to: localhost tasks, it would use the python binary from the virtualenv (that doesn’t have libselinux-python) instead of /usr/bin/python (that has libselinux-python). If tasks run explicitely on localhost (without delegate_to), tasks will complete successfully.

This is kind of an edge case and I’m not sure if it’s a bug or not. I opened an issue against Ansible to see what they have to say about it.

Edit #2: Not a bug

So it turns out that Ansible has a built-in definition for the host localhost and it will fork from sys.executable and this is the virtualenv python if you are running from a virtualenv.

If you want to override this, you will need to declare localhost in your inventory file explicitely like so:

localhost ansible_python_interpreter=/usr/bin/python