How to allow apache to rotate logs in user home directory with SELinux enabled

apache-2.2log-rotationselinux

Our development machine has multiple users, and their various sites are stored in /home/username/apache. In these folders are sub-folders like conf containing the virtual host config, logs containing logs, public containing the actual web files, etc.

I want to change the logs so that instead of single log files:

CustomLog "/home/user/apache/domain.tld/logs/web.log" combined

We have logs seperated into daily logs:

CustomLog "|/usr/sbin/rotatelogs -l /home/user/apache/domain.tld/logs/%Y%m%d_web.log 86400" combined

However, when I put this configuration in, restart apache, then reload a page, while tailing audit.log in to ausearch, I'm seeing errors like this:

----
type=SYSCALL msg=audit(06/06/2014 14:16:51.401:406272) : arch=x86_64 syscall=open success=no exit=-13(Permission denied) a0=7fff70a92460 a1=80441 a2=1b6 a3=7fff70a92110 items=0 ppid=64542 pid=64617 auid=root uid=root gid=root euid=root suid=root fsuid=root egid=root sgid=root fsgid=root tty=(none) ses=1193 comm=rotatelogs exe=/usr/sbin/rotatelogs subj=unconfined_u:system_r:httpd_rotatelogs_t:s0 key=(null)
type=AVC msg=audit(06/06/2014 14:16:51.401:406272) : avc:  denied  { search } for  pid=64617 comm=rotatelogs name=home dev=md2 ino=7208961 scontext=unconfined_u:system_r:httpd_rotatelogs_t:s0 tcontext=system_u:object_r:home_root_t:s0 tclass=dir

I thought I'd be able to fix this like I have fixed other SELinux issues (such as allowing apache to write to files) by changing the type, but after trying a few, I still get the same error above. I've tried httpd_log_t, httpd_rotatelogs_exec_t and var_log_t to no avail.

I was doing this with the chcon command.

What am I missing?

Best Answer

The Actual Problem

To translate the second line of the log you provided:

The rotatelogs process with PID 64617 tried to search in the home directory, which is on device md2 and on inode 7208961. Access was denied.

The security context of the rotatelogs process is: unconfined_u:system_r:httpd_rotatelogs_t:s0

The security context of the home directory is: system_u:object_r:home_root_t:s0

You need to allow rotatelogs to read the home directory. Allowing httpd to do so is easy enough (setsebool -P httpd_enable_homedirs=1), but unfortunately rotatelogs does not inherit its type from httpd. To make matters worse, there is no setsebool parameter for rotatelogs.

And that's not all! You also need to allow rotatelogs to search all the way down to the desired directory, as well as allow it to perform all the operations necessary for its task -- both on the files themselves and the directories that contain them.

In short, you need to write a local policy.

Step-by-Step Solution

Create a type enforcement file anywhere, named homelogs.te, with the following contents:

module homelogs 1.0;

require {
        type httpd_rotatelogs_t;
        type home_root_t;
        type user_home_t;
        type user_home_dir_t;
        class dir { add_name getattr open read remove_name search write };
        class file { create getattr open read rename unlink write };
}

#============= httpd_rotatelogs_t =============
allow httpd_rotatelogs_t home_root_t:dir search;
allow httpd_rotatelogs_t user_home_dir_t:dir search;
allow httpd_rotatelogs_t user_home_t:dir { add_name getattr open read remove_name search write };
allow httpd_rotatelogs_t user_home_t:file { create getattr open read rename unlink write };

Now compile it:

make -f /usr/share/selinux/devel/Makefile homelogs.pp

And install it:

semodule -i homelogs.pp

Now rotatelogs should have all the permissions needed to perform its task in the subdirectories of user homes.


How to Write a Local Policy

This is for those who want more details on how I wrote the local policy. You have two options to write the policy.

audit2allow

If you're completely new to writing local policies, one option is to use the audit2allow tool, provided in the policycoreutils-python package, to help you write one.

To fix SELinux permission denials when running rotatelogs, you could run the command

grep rotatelogs /var/log/audit/audit.log | audit2allow -M homelogs

to feed the audit logs that include 'rotatelogs' into audit2allow. The tool will automatically analyze the SELinux permission denials and create a Type Enforcement file, as well as the compiled Policy Package (.pp) file. At that point, you could simply install the created Policy Package by running:

semodule -i  homelogs.pp

And just like that, you've fixed all the SELinux problems related to rotatelogs... right?

The problem with this approach is that audit2allow cannot predict what permissions are needed; it can only analyze what permissions have been denied already.

So far, only a search denial in the home (literally /home) directory has been encountered, so audit2allow will only fix that. Think how many times you will have to re-run the tool each time you encounter the next denied permission. And depending on the rotatelogs configuration, it could take weeks for a particular permission to be denied and logged. For example, how long would it take for rotatelogs to attempt to delete an old log file and hit an unlink permission denial?

Michael Hampton pointed out in the comments that this problem can be mitigated by changing SELinux to Permissive Mode:

setenforce 0

In this mode, SELinux will warn and log actions, but not enforce the policy. This allows rotatelogs to perform all of its tasks while SELinux still logs all of the permissions denials. One could then run audit2allow on the audit log, install the automatically created policy, setenforce 1 to return to Enforcing mode, and be done.

Having not known earlier that Permissiive mode can be used this way, I wrote the local policy manually, as described below. It's not necessary to do it by hand, but hopefully the read is somewhat educational.

Manual Creation

With some understanding of the Type Enforcement file, you can solve permissions problems before they arise by writing it by hand. I created a Type Enforcement file (with a .te extension). It doesn't matter where the Type Enforcement file is.

There are three sections to a Type Enforcement file.

The module section

module homelogs 1.0;

The module section lists the name and version of the module you will be creating with your local policy. The name should be unique, or else you will replace an already-existing module of the same name. You can check the names of installed modules with semodule -l.

The require section

require {
        type httpd_rotatelogs_t;
        type home_root_t;
        type user_home_t;
        type user_home_dir_t;
        class dir { add_name getattr open read remove_name search write };
        class file { create getattr open read rename unlink write };
}

As mentioned here:

This [section] informs the policy loader which types, classes and roles are required in the system policy before this module can be installed. If any of these fields are undefined, the semodule command will fail.

In your situation, rotatelogs will interact with several types. After running ls -Z on the rotatelogs executable file and the directories of your desired path, I found the following types would be required:

  • httpd_rotatelogs_t
  • home_root_t
  • user_home_t
  • user_home_dir_t

You also need to interact with some classes. The complete list of classes and their permissions is here. For your situation, we will only be working with class file and class dir. You also have to list the permissions of each class that you will be interacting with. Based on the complete list, I chose to require these classes and permissions (a couple probably are not necessary, but I erred on the side of allowing):

class dir:

  • add_name - Add a file to the directory
  • getattr - Get file attributes for file, such as access mode. (e.g. stat, some ioctls. ...)
  • open - Open a directory
  • read - Read file contents
  • remove_name - Remove a file from the directory
  • search - Search access
  • write - General write access; required for adding or removing

class file:

  • create -
  • getattr - Get file attributes for file, such as access mode. (e.g. stat, some ioctls. ...)
  • open - Open a file
  • read - Read file contents
  • rename - Rename a file
  • unlink - Remove hard link (delete)
  • write - Write to a file

The rules section

allow httpd_rotatelogs_t home_root_t:dir search;
allow httpd_rotatelogs_t user_home_dir_t:dir search;
allow httpd_rotatelogs_t user_home_t:dir { add_name getattr open read remove_name search write };
allow httpd_rotatelogs_t user_home_t:file { create getattr open read rename unlink write };

The rules section reads fairly easily. For example:

allow httpd_rotatelogs_t user_home_t:dir { add_name getattr open read remove_name search write };

roughly means:

Allow commands with type httpd_rotatelogs_t to perform the operations of add_name, geattr, open, read, remove_name, search, and write on directories with the type user_home_t.

Note that each class needs a separate rule, since different classes will have different permissions associated with them.

Compilation and Installation

This is redundant, but I'll mention it again. You need to compile the Type Enforcement file into a Policy Package. When running make, the Type Enforcement file needs to be in your current working directory.

make -f /usr/share/selinux/devel/Makefile homelogs.pp

Lastly, install the Policy Package as a module:

semodule -i homelogs.pp


Useful Resources on Writing Custom Policies: