Why do selinux policies apply to commands (e.g: logrotate) running from cronjobs, but not when run directly from command line?

When I run logrotate manually from the command line, it works perfectly. But when it runs from the cronjob, I get an error in the audit.log alerting me that selinux prevented access to www, etc.

Why is that? And how can I simulate it running from the cronjob to test?


When cron runs logrotate, SELinux confines it to a logrotate_t "type". That "type" is restricted from modifying other file types (aka "escaping the confinement").

When you run logrotate, you're (most likely) starting from an "unconfined" type, which means what it says -- the logrotate process is permitted to modify files. You might also want logrotate to restart or signal processes (via postrotate, for example); that activity may also be confined by SELinux.

My suggestion here is to tell SELinux to allow ("permit") the logrotate_t type to escape the confinement, with:

semanage permissive -a logrotate_t

Doing so is a moderate solution, in-between turning SELinux off and fine-tuning a policy that allows exactly the confinement escapes that you need (perhaps with custom labeling). To revert this change, use semanage permissive -d logrotate_t.

The best way to simulate a cron-initiated process is to put the jobs into cron. Alternatively, I'm aware of runcon, although I wasn't able to use it successfully.

  • 3
    The description of the reason for the difference between running from command line or from cron is great. But I don't agree with the recommendation to just put that type in permissive mode. While it's not as bad as turning SELinux off completely, it's still a very coarse measure. IMO, the OP should try to troubleshoot why they're having trouble with those specific files instead. – filbranden May 6 '19 at 14:23
  • 1
    I welcome a better description/solution, or any successful runcon simulation. I'm only moderately SELinux-capable, myself. I do my very best to avoid turning it off, but I lack the PhD it seems to require to create sane policies. – Jeff Schaller May 6 '19 at 14:31
  • I'm mostly pointing out that recommendation on disabling it is somewhat out of scope here... It was never really asked in the first place... – filbranden May 6 '19 at 14:34
  • True; I mention it only to avoid some (other) people's last-ditch effort to "get SELinux working" by getting rid of it. Note that I don't (directly) say how to disable it. I know we have some smart SELinux people around here cough cough ... – Jeff Schaller May 6 '19 at 14:36
  • I'm currently using audit2allow to give proper permissions to logrotate. The issue is that logrotate is being denied 1 permission at a time, so I have had to wait several days, each day adding a new permission that denied logrotate running. I will try putting logrotate into a normal cron and see what happens. – Pat May 6 '19 at 15:29

Just to add on top of the good answer by @JeffSchaller, with regard to the specific question:

how can I simulate it running from the cronjob to test?

I can share a workable alternative to having to run test commands from a cronjob.

To start, it may be worth mentioning a subtle detail:

In a traditional non-SELinux environment, when you operate as root (effective UID = 0) you are normally free to switch to whatever other UID you like, to "gain" that UID's restrictions.

That is quite unlike SELinux's contexts: being root (effective UID = 0) typically does place you in "unconfined" context, but does not automatically allow you to switch to other contexts freely. You rather need an explicit SELinux policy rule that grants the "unconfined" context the clearance to perform that specific "transition" operation. After you have installed such a rule, the runcon command will succeed in putting you in that context.

In practical terms, for your case you probably need a SELinux policy like a simple:

allow unconfined_t crond_t:process transition;

(assuming crond_t is the SELinux domain your crond daemon runs under)

Such rule as a full module to be compiled and installed is:

module unconfined-trans-crond 1.0;

require {
        type unconfined_t;
        type crond_t;
        class process transition;

allow unconfined_t crond_t:process transition;

After having compiled and installed that module, a runcon targeting the crond_t domain from unconfined_t would work. For instance:

# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# runcon $(ps -q $(pgrep crond) -o context --no-header) sh
# id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:crond_t:s0

Note also that you are not required to be UID 0 to use runcon, you "only" need the correct policy rule that allows the transition from your current SELinux context to the target SELinux context.

In fact, the full command I would normally run as root to test a daemon's (such as crond) reach is:

runuser $(ps -q $(pgrep crond) -o user --no-header) -c 'runcon $(ps -q $(pgrep crond) -o context --no-header) /bin/bash'

which gives a shell running with crond's user as well as its SELinux context.

To undo that policy once you've finished your testing just remove the custom module.

This of course does not take into account possible other restrictions such as different namespaces views, resource limits (ulimits), cgroups, or Linux Capabilities. These would require additional commands to be run in sequence in order to recreate these additional restrictions, unless the PAM configuration for runuser is in some way consistent at re-creating them for you.



Inspired by @Jeff_Schaller's and @LL3's answers, here's a way to come up with the right sepolicy module for this process:

:; sudo semanage permissive -a logrotate_t
# turn off selinux for logrotate temporarily to allow the logrotate command to go through, while still logging the denied messages in audit.log

:; sudo runcon -u system_u -r system_r -t logrotate_t logrotate -f /etc/logrotate.conf
:; sudo semanage permissive -d logrotate_t # re-enable selinux

:; sudo grep denied /var/log/audit/audit.log | audit2allow -M logrotate
# create a selinux policy based on the denied messages

:; sudo semodule -i logrotate.pp

The content of logrotate.te ends up looking something like this

module logrotate 1.0;

require {
        type initrc_exec_t;
        type default_t;
        type policykit_t;
        type logrotate_t;
        class dbus send_msg;
        class file { create getattr ioctl open read setattr unlink write };
        class dir { add_name read remove_name write };
        class service reload;

#============= logrotate_t ==============
allow logrotate_t default_t:dir { add_name read remove_name write };

#!!!! WARNING: 'default_t' is a base type.
allow logrotate_t default_t:file { create getattr ioctl open read setattr unlink write };
allow logrotate_t initrc_exec_t:service reload;
allow logrotate_t policykit_t:dbus send_msg;

I dont have deep knowledge in SElinux and I'm not sure if it is the same as the other answer . Here how I've done it : Inside the dir. of the crontab semanage fcontext -a -t 'myCronTab' logrotate_exec_t this make the cronjob and the logrotate in the same context so it should be like it's executed via command line.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.