A developer contacted me about building a container that will run as a log aggregator for
fluentd
. This container needed to be a SPC container that would manage parts of the host system, namely the log files under /var/logs.
Being a good conscientious developer, he wanted to run his application as securely as possible.
The option he wanted to avoid was running the container in --privileged
mode, removing all security from the container. When he ran his container SELinux
complained about the container processes trying to read log files.
The Problem: A Logger SPC
He asked me if there was a way to run a container where SELinux would allow the access but the container process could still be confined. I suggested that he could disable SELinux protections for just this container, leaving SELinux enforcing on for the other containers and for the host:
docker run -d --security-opt label:disable -v /var/log:/var/log fluentd
We did not like this solution. I believe SELinux provides the best security separation currently available for containers.
Another option we talked about was relabeling the content in the /var/log directories:
docker run -d -v /var/log:/var/log:Z fluentd
The problem with this is that all of the files under /var/log would not be labeled with a container
specific label (svirt_sandbox_file_t
). Other parts of the host system like Logrotate, and log scanners would now be blocked from access the log files.
The best option we came up with was to generate a new type
to run the container with.
We need to write a little bit of writing policy to make this happen. Here is what I came up with:
cat container_logger.te
policy_module(container_logger, 1.0)
virt_sandbox_domain_template(container_logger)
##############################
# virt_sandbox_net_domain(container_logger_t)
gen_require(`
attribute sandbox_net_domain;
')
typeattribute container_logger_t sandbox_net_domain;
##############################
logging_manage_all_logs(container_logger_t)
Compile and install the policy.
make -f /usr/selinux/devel/Makefile container_login.pp
semodule -i container_login.pp
Run the container with the new policy.
docker run -d -v /var/log:/var/log --security-opt label:type:container_logger_t -n logger fluentd
Exec into the container to make sure you can read/write the log files.
docker exec -ti logger cat /var/log/messages
docker exec -ti logger touch /var/log/foobar
docker exec -ti logger rm /var/log/foobar
Everything works!
A Closer Look at This Policy
policy_module(container_logger, 1.0)
policy_module
names the policy and also brings in all standard definitions of policy. All policy type
enforcement files start with this.
virt_sandbox_domain_template(container_logger)
virt_sandbox_domain_template
is a template macro that actually creates the container_logger_t
type, and
sets up all of the policy so that the docker process (docker_t
) can transition to it. It also defines
rules that allow it to manage svirt_sandbox_file_t
files and sets it up to be MCS Separated. This means it
will only be able to use its content and no other containers’ content, whether or not the container is running
as the default type svirt_lxc_net_t
or a custom type.
##############################
# virt_sandbox_net_domain(container_logger_t)
gen_require(`
attribute sandbox_net_domain;
')
typeattribute container_logger_t sandbox_net_domain;
##############################
This section will eventually be an interface virt_sandbox_net_domain
. (I sent a patch to the upstream
selinux-policy package to add this interface.) This new interface just assigns an attribute to container_logger_t
. Attributes bring in lots of policy rules, basically this attribute gives full network access to the container_logger_t
processes. If your container did not need access to the network, or you wanted to tighten the network ports that container_logger_t
would be able to listen on or connect to, you would not use this interface.
logging_manage_all_logs(container_logger_t)
This last interface logging_manage_all_logs
gives container_logger_t
the ability to manage all of the log file types. SELinux interfaces are defined and shipped under /usr/share/selinux/devel.
Conclusion
Adding a fairly simple policy module allows us to run the container as securely as possible and still able to get the job done.