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

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.


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.

HTH


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;