Compared to just adding the user to the docker group the second links solution is not any more secure.
Note how that page still can launch with the --privileged
flag, and that it is running unconfined.
unconfined_u:unconfined_r:unconfined_t
This means that the container can access all resources including the hosts disks and hardware. It is security through obscurity, which is not security. Anyone who knows how to use mknod
or walk the /sys and /proc trees could easily compromise the host and all containers with zero logging.
The reality is that docker shifts both complexity and the security boundaries. All users who can launch container or hit the API need to be restricted to trusted users in that security context.
--privileged
disables all apparmor and selinux policies which is actually far less secure than a native package.
Namespaces are not a security function and depend on apparmor and selinux to enforce reasonable constraints.
Docker notes this on this page.
https://docs.docker.com/engine/security/security/#control-groups
'First of all, only trusted users should be allowed to control your Docker daemon.'
The security functions of docker/kube are not administrative boundaries like classical unix permissions, they are tools to prevent non-privileged containers from breaking out.
In a docker world the administrative boundary becomes the host, and the selection and segmentation required to isolate applications or users within that context needs to be applied at that boundary.
The benefits of this shift in complexity and responsibility generally outweigh the risks if implemented with those changes in mind.
TLDR
Docker API user == Sudo ALL user
Running a container with the --privlaged flag == running a web service as suid or as the root user.
Edited per the OP's request for additional information
The referenced issue with breakout int he OP's edit was an non uid0 privilege escalation.
Unfortunately, due to the need to perform root only actions Docker needs to enable some capabilities so that apt/dnf can install packages etc...
This need does pose a risk if production workloads are run in this default configuration and one should adopt the security principle of least privilege for production workloads.
--privileged
disables apparmor/selinux and opens up capabilities
I am using ubuntu but it may be useful to work through the following steps. First start a default container with docker run -i --rm -t debian bash
From the parent host find the PID for bash using ps and note that the process is owned root. If you look in /proc/$PID/status
you will see the contexts it is running under.
# egrep '^Cap(Prm|Inh|Eff)' /proc/16026/status
CapInh: 00000000a80425fb
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
You will want to refer to man 7 man capabilities and /usr/include/linux/capability.h for better info but a summer is
CapInh = The Inherited capabilities (what docker provided)
CapPrm = The capabilities due to permissions (inside the container OS)
CapEff = The effective capabilities
You can decode these to human readable form by running:
$ capsh --decode=00000000a80425fb
Now do the same with $ docker run -i --rm --privileged -t debian bash
and you will find that the effective capabilities are 0000003fffffffff
Also just do a dir /dev
in both VM's and you will see just how much access a privileged container has.
By looking at /etc/apparmor.d/docker
for apparmor systems or the lables in SElinux you will see the implications.
Going back to the principle of least privileges, I would ensure that my docker container is running process as a unprivileged user and with as few enabled capabilities as possible.
As an example, you can test this by running it first by dropping all caps.
$ docker run -i --rm -t --cap-drop=all -t debian bash
This can be validated through the /proc/$PID/status
method above.
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
Note it is all zeros, from CapInh. Then if I had to enable features for the application I would use --cap-add
after the --cap-drop=all
Example, --cap-drop=all
will break ping:
# ping -c 1 www.google.com
ping: Lacking privilege for raw socket.
So we can add the cap_net_raw
cap. Docker expects the arguments with the cap_
prefix removed so the command is docker run -i --rm -t --cap-drop=all --cap-add=net_raw -t debian bash
# ping -c 1 www.google.com
PING www.google.com (216.58.216.164): 56 data bytes
64 bytes from 216.58.216.164: icmp_seq=0 ttl=54 time=4.779 ms
But be very careful adding them as an example.
cap_sys_module will allow a container to add or remove kernel modules from the parent host.
cap_sys_rawio will open memory and all block devices to attack
cap_sys_admin is super dangerous.
So in this case I would see if you can make things run in this context.
$ docker run -i --rm -t --cap-drop=all -u nobody:nogroup -t debian bash
Hopefully the robustness of apparmor and selinux profiles improves over time, but if that is not enough for your security needs you can look in there too.
Really, avoiding the --privileged flag and using principle of least privilege will make the most impact. Especially if you take advantage of the ephemeral nature of containers to keep packages up to date.
If you need more Red Hat covers some basic seccomp options here.
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html/container_security_guide/linux_capabilities_and_seccomp
docker
command opens uproot
access on the host (but this is specifically not what this question is about), and it contains references to old security issues from 2015.