Switch USER for single RUN command in Dockerfile - docker

Currently I am facing the following challenge:
I am extending a base image, which sets a USER "safeuser" at the end. In my dependent image I try to make some changes to the filesystem of the baseimage, but since "safeuser" can't modify files from "root" I would need to change via USER ROOT, do my changes and then go back to USER SAFEUSER.
This approach does seem quite ugly, what if for example the baseimage changes the username from "safuser" to "othername"? Is there any way I can change the USER only during the build process, or RUN single commands as a different user without having to explicitly switch back to the original user? Or can I at least store some reference to the original USER during the build process somehow?

This can be done using sudo --user:
root#machine:/home/user# whoami
root
root#machine:/home/user# sudo --user=user whoami
user
root#machine:/home/user# whoami
root
When used in a Dockerfile, it will look like the following:
# ...
RUN sudo --user=user mkdir /tmp/dir
RUN touch /tmp/dir/root_file
Edit: as #CharlesDuffy wrote below, doing this would require adding the safeuser to the sudoers of the build machine, which isn't ideal. I'd consider other (less elegant but more secure) options before choosing this one.

Related

How to avoid sprinkling chowns all over

I'm trying to figure out how it's supposed to work based on these two articles:
https://medium.com/#mccode/processes-in-containers-should-not-run-as-root-2feae3f0df3b
https://vsupalov.com/docker-shared-permissions/
I am setting up a dockerfile based on an image that has user Ubuntu already set (the subject of the first linked article above) so that the container runs by default under user ubuntu. This is adhering to best practice.
The problem I'm having is that the code directories COPYed in the dockerfile are all owned by root, and calling cmake .. required for the docker build fails because of this. I understand that the COPYs will by default run as root, and that even if I use the --chown flag with COPY, any parent directories implicitly created by the COPY would be owned by root regardless of any --chown flag used.
Doesn't the fact that the container already has a Ubuntu user mean that calling RUN adduser --uid 1000 ubuntu in the dockerfile (the suggestion from the second linked article above) would be problematic (it'd be at best redundant)?
Then that would mean that we would not want to RUN adduser so does this mean the only remaining option is to actually just sprinkle tons of chowns everywhere in the dockerfile? I refuse to do this.
By specifying USER ubuntu near the top of the dockerfile prior to any of the COPYs it appears to work to eliminate most of the required sudo chown calls. There will still be some required, if some part of the directory tree being copied to started out owned by root and need to be owrked with later.

With Docker USER directive, multiple shell command run into permission problems

I've browsed a couple of articles about Docker best practices, and recognize that running a container as a non-privileged user has some obvious security bonuses. So my first question is: why use the USER directive at all to build your image? That is, why not simply build the image as root, but run the container as a restricted user? In that case, it seems USER is superfluous.
The story does not end there. I tried various use-cases with USER. As I am building an image based off a debian snapshot, I placed USER after all relevant apt-get installations. So far so good. But as soon as I tried creating content within the USER's home directory, permission issues surfaced -- no matter if I explicitly assigned USER and group permissions to the enclosing parent directory.
Whenever I run into a feature that does not work in the obvious way, I ask myself whether it is a feature worth keeping. So, is there any practical reason to retain USER, given that you probably could do everything in a user-restricted way -- at least from a permissions perspective -- from outside the container?
One of the main reasons to run things as a non-root user (Docker or otherwise) is as an additional layer of security: even if your application is somehow compromised, it can't overwrite its own source code or static content that it's serving to end users. I would generally include a USER directive, but only at the very end so that it only affects the (default) user for the docker run command.
FROM some-base-image
...
# Do all installation as root
...
# And pick an alternate user for runtime only
USER something
CMD ["the_app"]
"Home directory" isn't really a Docker concept. It's very typical to store your application in, say, /app (mode 0755 owned by root).

Deriving FROM existing Dockerfile + setting USER to non-root

I'm trying to find a generic best practice for how to:
Take an arbitrary (parent) Dockerfile, e.g. one of the official Docker images that run their containerized service as root,
Derive a custom (child) Dockerfile from it (via FROM ...),
Adjust the child in the way that it runs the same service as the parent, but as non-root user.
I've been searching and trying for days now but haven't been able to come up with a satisfying solution.
I'd like to come up with an approach e.g. similar to the following, simply for adjusting the user the original service runs as:
FROM mariadb:10.3
RUN chgrp -R 0 /var/lib/mysql && \
chmod g=u /var/lib/mysql
USER 1234
However, the issue I'm running into again and again is whenever the parent Dockerfile declares some path as VOLUME (in the example above actually VOLUME /var/lib/mysql), that effectively makes it impossible for the child Dockerfile to adjust file permissions for that specific path. The chgrp & chmod are without effect in that case, so the resulting docker container won't be able to start successfully, due to file access permission issues.
I understand that the VOLUME directive works that way by design and also why it's like that, but to me it seems that it completely prevents a simple solution for the given problem: Taking a Dockerfile and adjusting it in a simple, clean and minimalistic way to run as non-root instead of root.
The background is: I'm trying to run arbitrary Docker images on an Openshift Cluster. Openshift by default prevents running containers as root, which I'd like to keep that way, as it seems quite sane and a step into the right direction, security-wise.
This implies that a solution like gosu, expecting the container to be started as root in order to drop privileges during runtime isn't good enough here. I'd like to have an approach that doesn't require the container to be started as root at all, but only as the specified USER or even with a random UID.
The unsatisfying approaches that I've found until now are:
Copy the parent Dockerfile and adjust it in the way necessary (effectively duplicating code)
sed/awk through all the service's config files during build time to replace the original VOLUME path with an alternate path, so the chgrp and chmod can work (leaving the original VOLUME path orphaned).
I really don't like these approaches, as they require to really dig into the logic and infrastructure of the parent Dockerfile and how the service itself operates.
So there must be better ways to do this, right? What is it that I'm missing? Help is greatly appreciated.
Permissions on volume mount points don't matter at all, the mount covers up whatever underlying permissions were there to start with. Additionally you can set this kind of thing at the Kubernetes level rather than worrying about the Dockerfile at all. This is usually though a PodSecurityPolicy but you can also set it in the SecurityContext on the pod itself.

Can I create a temporary variable in Dockerfile and reuse it later?

In my Dockerfile, I want to temporarily switch to root and then switch back to the original user.
originalUser=`RUN whoami`
USER root
RUN apk update
RUN apk add curl
# switch back to the user before root
USER $originalUser
Is it possible to do something like this in Dockerfile?
On the one hand, no, there's nothing like this. The only similar things are ARG (which get passed at the command line) and ENV (which are fixed strings), neither of which can be set dynamically based on command outputs.
On the other hand, within the context of a Docker image, you, as the Dockerfile author, have complete and absolute control over what goes into the image. You never have to ask questions like "what if the user has a different username" or "what if they want to install into a different path": you get to pick fixed values for these things. I would suggest:
If you're installing a single binary or something with a "normal" installation procedure (it has an Autoconf-style ./configure --prefix=... option), install it into the system directories
If you're installing something in a scripting language that doesn't go into the "normal" directories, /app is a common place for it
Install software exclusively as root (even your application); switch to a non-root USER just once at the end of your Dockerfile
Don't try to mirror any particular system's directory layout, user names, or numeric user IDs; if you try to run the image somewhere else they won't match
If you're trying to extend some other image, you should be fine figuring out what username it uses and putting a fixed string in a USER directory at the end of your derived image's Dockerfile.

Non-privileged, non-root, user to start or restart webserver server such as nginx without root or sudo

I'm using capistrano to deploy a rails web app. I want to give the deploy user on the webserver as few privileges as I can. I was able to do everything I need to do as a non-privileged user except restart the webserver.
I'm doing this on an ubuntu server, but this problem is not specific to my use case (rails, capistrano, deployment), and I've seen a lot of approaches to this problem that seem to involve poor security practices. Wondering whether others can vet my solution and advise whether it's secure?
First, not necessary, but I have no idea why /etc/init.d/nginx would need any (even read) access by other users. If they need to read it, make them become root (by sudo or other means), so I:
chmod 750 /etc/init.d/nginx
Since the ownership is owner root, group root (or can be set such with chown root:root /etc/init.d/nginx) only root, or a user properly sudo'ed, can read, change or run /etc/init.d/nginx, and I'm not going to give my deploy user any such broad rights. Instead, I'm only going to give the deploy user the specific sudo right to run the control script /etc/init.d/nginx. They will not be able to run an editor to edit it, because they will only have the ability to execute that script. That means that if a someone gets access to my box as the deploy user, they can restart and stop, etc, the nginx process, but they cannot do more, like change the script to do lots of other, evil things.
Specifically, I'm doing this:
visudo
visudo is a specific tool used to edit the sudoers file, and you have to have sudoer privileges to access it.
Using visudo, I add:
# Give deploy the right to control nginx
deploy ALL=NOPASSWD: /etc/init.d/nginx
Check the sudo man page, but as I understand this, the first column is the user being given the sudo rights, in this case, “deploy”. The ALL gives deploy access from all types of terminals/logins (for example, over ssh). The end, /etc/init.d/nginx, ONLY gives the deploy user root access to run /etc/init.d/nginx (and in this case, the NOPASSWD means without a password, which I need for an unattended deployment). The deploy user cannot edit the script to make it evil, they would need FULL sudo access to do that. In fact, no one can unless they have root access, in which case there's a bigger problem. (I tested that the user deploy could not edit the script after doing this, and so should you!)
What do you folks think? Does this work? Are there better ways to do this? My question is similar to this and this, but provides more explanation than I found there, sorry if it's too duplicative, if so, I'll delete it, though I'm also asking for different approaches.
The best practice is to use /etc/sudoers.d/myuser
The /etc/sudoers.d/ folder can contain multiple files that allow users to call stuff using sudo without being root.
The file usually contains a user and a list of commands that the user can run without having to specify a password. Such as
sudo service nginx restart
Note that we are running the command using sudo. Without the sudo the sudoers.d/myuser file will never be used.
An example of such a file is
myuser ALL=(ALL) NOPASSWD: /usr/sbin/service nginx start,/usr/sbin/service nginx stop,/usr/sbin/service nginx restart
This will allow the myuser user to call all start, stop and restart for the nginx service.
You could add another line with another service or continue to append them to the comma separated list, for more items to control.
Also make shure you have run the command below to secure things
chmod 0440 /etc/sudoers.d/myuser
This is also the way I start and stop services my own created upstart scripts that live in /etc/init
It can be worth checking that out if you want to be able to run your own services easily.
Instructions:
In all commands, replace myuser with the name of your user that you want to use to start, restart, and stop nginx without sudo.
Open sudoers file for your user:
$ sudo visudo -f /etc/sudoers.d/myuser
Editor will open. There you paste the following line:
$ myusername ALL=(ALL) NOPASSWD: /usr/sbin/service nginx start,/usr/sbin/service nginx stop,/usr/sbin/service nginx restart
Save by hitting ctrl+o. It will ask where you want to save, simply press enter to confirm the default. Then exit out of the editor with ctrl+x.

Resources