Hi,
I’m currently trying to create a SELinux module using Ansible.
Here’s the command I’m using interactively in the shell:
# cd /var/tmp
# ausearch -c 'dnsmasq' --raw | audit2allow -M my_dnsmasq
# ls -l my_dnsmasq.*
-rw-r--r--. 1 root root 945 May 23 07:57 my_dnsmasq.pp
-rw-r--r--. 1 root root 179 May 23 07:57 my_dnsmasq.te
Here’s how I translated this in Ansible (I’ll worry about idempotency later):
- name: Generate SELinux module for Dnsmasq
ansible.builtin.shell: |
ausearch -c 'dnsmasq' --raw | audit2allow -M my_dnsmasq
args:
chdir: /var/tmp/
executable: /bin/bash
Curiously enough, when I run this task it looks like something has changed and I get no error message. On the other hand, there’s no resulting my_dnsmasq*
file anywhere on the system.
Any idea what’s going on here?
OK now here’s something very weird. It looks like Ansible’s shell module can’t handle commands like ausearch
or audit2allow
.
I just tested this:
- name: Test ausearch
ansible.builtin.shell:
cmd: /usr/sbin/ausearch -c dnsmasq --raw
chdir: /var/tmp/
executable: /bin/bash
And here’s what I get:
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "/usr/sbin/ausearch -c dnsmasq --raw", "delta": "0:
00:00.023751", "end": "2025-05-23 08:53:20.364510", "msg": "non-zero return code", "rc": 1, "start": "2025-
05-23 08:53:20.340759", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
I’m beginning to understand why folks choose to disable SELinux.
That’s because ansible assumes that a return value of 0 would be a success, and perhaps in this case 1 is success (the rc value), so you can do:
register: ausearch
failed_when:
- ausearch.rc != 1
the ausearch
can be any text you like, as long as it’s followed by the .rc
or even set:
changed_when: ausearch.rc == 1
instead of failed_when. You may need a combo of both.
Is the *.te
(ASCII) file system-specific, or would the same rule/policy apply to all systems?
If it is not system-specific, then you don’t need to create it in every host.
Long ago, I did use crude (for cups – I have no issues with dnsmasq):
- name: Remove SELinux policy package
command: semodule -r my-cupspkhelper
failed_when: false
- name: Copy SELinux type enforcement file
copy:
src: my-cupspkhelper.te
dest: /tmp/
- name: Compile SELinux module file
command: checkmodule -M -m -o /tmp/my-cupspkhelper.mod /tmp/my-cupspkhelper.te
- name: Build SELinux policy package
command: semodule_package -o /tmp/my-cupspkhelper.pp -m /tmp/my-cupspkhelper.mod
- name: Load SELinux policy package
command: semodule -i /tmp/my-cupspkhelper.pp
- name: Remove temporary files
file:
path: /tmp/my-cupspkhelper.*
state: absent
iwalker:
That’s because ansible assumes that a return value of 0 would be a success, and perhaps in this case 1 is success (the rc value), so you can do:
register: ausearch
failed_when:
- ausearch.rc != 1
the ausearch
can be any text you like, as long as it’s followed by the .rc
or even set:
changed_when: ausearch.rc == 1
instead of failed_when. You may need a combo of both.
Now here’s the thing. The ausearch
command does return 0 when invoked directly in a shell:
# ausearch -c dnsmasq --raw
type=AVC msg=audit(1747976451.939:101): avc: denied { getattr } for pid=2309 comm="dnsmasq" path="/etc/resolv.conf" dev="dm-2" ino=67884332 scontext=system_u:system_r:dnsmasq_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0
...
# echo $?
0
But when I try to launch the same (!) command using Ansible’s shell
module, it fails.
I’m confused.
Best way instead of comparing it to the console output when you do it manually, is to use the ansible register function, and then use that in a following task with debug to see all the information. Then use that with the changed_when or failed_when options. I have had to do this when using dnf commands in certain situations.
Yeah, I know. But it doesn’t even get to that. The command fails, plain and simple. Output or not. Just try it out and see for yourself.
Well I don’t have your problem with dnsmasq because I don’t use it. And even if I run ausearch -c dnsmasq --raw
, I don’t have anything to work with to replicate your problem.
Does the following possibly work? Ignores error codes.
(ausearch -c 'dnsmasq' --raw | audit2allow -M my_dnsmasq) || true
Even ignoring error codes doesn’t work. Here’s the thing:
- name: Generate SELinux module for Dnsmasq
ansible.builtin.shell:
cmd: /usr/sbin/ausearch -c dnsmasq --raw
chdir: /var/tmp/
executable: /bin/bash
register: ausearch_output
failed_when: false
- name: Display the output of the ausearch command
ansible.builtin.debug:
var: ausearch_output.stdout_lines
The ausearch
command works perfectly directly in a shell and displays a few lines of output. But when run from Ansible, there’s no output.
At this point I’m giving up. I simply build the SELinux module by hand and put this stuff in Ansible’s README file.
Thanks everybody for your input.