It means allocating the user or program the least privilege possible to carry out given task.
Till now we know that by default docker runs a container as a privileged user unless User Namespaces are implemented. By default, the user inside the container is same as the root user outside the container.
If container does not require high level privileges. Then its always good to run containers with non-privileged users.
Its a good practice to add Non-privileged user in docker image.
Adding Non-privileged User in Docker image
we know that docker image is build from Dockerfile. So while defining the dependencies, base OS and other system commands inside Dockerfile. We will also add system commands for adding users and Groups as shown below
FROM alpine RUN addgroup -g 3000 -S pankaj && \ adduser -u 3000 -S -G pankaj pankaj USER pankaj:pankaj
When you check the user after running the container, you will see user named pankaj
Protecting Container Filesystems
We can also minimise the file system access.
You may notice that most of the exploits are carried out by tampering of the file system content.
We can start a container with Read-only Filesystem privilege.
We can do this by using
--read-only config flag when a container is started.
Once container spins up, you can't make any changes to container filesystem.
We can minimise filesystem in a container, as it will reduce the attack surface which can be compromised.
Creating a minimal Container
Below is the Dockerfile for Minimal Filesystem Container
FROM scratch COPY ./httpserv /web/ EXPOSE 8000 ENTRYPOINT ["./httpserv"]
See, its not important to use a whole operating system as base image to build a container. We can build an image from scratch and add all require dependencies inside the Dockerfile. Hence it will remove all unwanted filesystem.
But here another issue, due to no base image that is no user and group user we can't run our process as non-privileged users. This is weird right, by introducing one security measure we are losing another.
But don't worry we have one way around it. We can write our Dockerfile in stages or say Multi-stage Building of DockerFile.
It means that a single image file can have multiple FROM instructions.
Where one FROM acts as Build stage and other act as serving stage.
FROM golang:alpine as build Run mkdir -p /web/assets RUN addgroup -S http && adduser -S -G http http . . FROM scratch COPY --from=build /web / COPY --from=build /etc/passwd /etc/group /etc/ . . USER http:http ENTRYPOINT ["./httpserv"]
Implementing Access Control
We know that Access Control means who can access and what on system.
Lets learn how linux handles access control. In linux there are two main controls which handle most part of Linux Access controls
- Discretionary Access Control
- Mandatory Access Control.
Discretionary Access Control
It works by granting access to subject based on the identity of subject. It is used for controlling access to files and directories.
If we want to implement access controls to other system resources like network interfaces, network port etc. To achieve such control we make use of Mandatory Access Control. It is policy driven.
For implementation of mandatory access control, several solutions have been provided like
- SELinux(Fedora based)
- AppArmor(Ubuntu and SUSE lINUX)
Here, This is how linux kernel works between DAC and MAC.
Note that Linux security modules are policy oriented and policy is defined in User Space which is later loaded into kernel with some LSM specific tools.
AppArmor (Application Armor) is a Linux security module that protects an operating system and its applications from security threats. To use it, a system administrator associates an AppArmor security profile with each program. Docker expects to find an AppArmor policy loaded and enforced
Docker automatically generates and loads a default profile for containers named docker-default. On Docker versions 1.13.0 and later, the Docker binary generates this profile in tmpfs and then loads it into the kernel. On Docker versions earlier than 1.13.0, this profile is generated in /etc/apparmor.d/docker instead.
Note: This profile is used on containers, not on the Docker Daemon.
A profile for the Docker Engine daemon exists but it is not currently installed with the deb packages. If you are interested in the source for the daemon profile, it is located in contrib/apparmor in the Docker Engine source repository.
As stated above that by default, Docker uses docker-default profile which provides wide application compatibility.
When we run a container and do not define any profile explicitly then docker by default set docker-default.
$ docker run --rm -it --security-opt apparmor=docker-default hello-world
Here we are explicitly defining the default profile.
Loading and unloading profiles into apparmor
- To load a profile,
$ apparmor_parser -r -W /path/to/your_profile
Then, run the custom profile with
$ docker run --rm -it --security-opt apparmor=your_profile hello-world
To unload a profile from AppArmor:
$ apparmor_parser -R /path/to/profile
More detailed information on docs.docker.com Link for AppArmor use: AppArmor Documentation