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

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).

Related

Switch USER for single RUN command in Dockerfile

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.

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.

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.

Ruby on Rails- how to run a bash script as root?

What I'm wanting to do is use 'button_to' & friends to start different scripts on a linux server. Not all scripts will need to be root, but some will, since they'll be running "apt-get dist-upgrade" and such.
The PassengerDefaultUser is set to www-data in apache2.conf
I have already tried running scripts from the controller that do little things like writing to text files, etc, just so that I know that I am having Rails execute the script correctly. (in other words, I know how to run a script from the controller) But I cannot figure out how to run a script that requires root access. Can anyone give me a lead?
A note on security: Thanks for all the warnings against hacking that were given. You don't need to loose any sleep, though, because A) the webapp is not accessible from the public internet, it will only be on private intranets, B) the app is password protected, and C) because the user will not be able to supply custom input, only make selections from a form that will be passed as variables to the script. However, because I say this does not mean that I am disregarding your recommendations for security- I will be considering them very carefully in my design.
You should be using the setuid bit to achieve the same functionality without sudo. But you shouldn't be running Bash scripts. Setuid is much more secure than sudo. Using either sudo or setuid, you are running a program as root. But only setuid (with the help of certain languages) offers some added security measures.
Essentially you'll be using scripts that are temporarily allowed to run as a the owner, instead of the user that invoked them. Ruby and Perl can detect when a script is run as a different user than the caller and enforces security measures to protect against unsafe calls. This is called Taint mode. Bash does not run in taint mode at all.
Taint mode essentially works by declaring all input from an outside source unsafe for use when passed to a system call.
Setting it up:
Use chmod to set permissions on the script you want to run as 4755 and set it's owner to root:
$ chmod 4755 script.rb
$ chown root script.rb
Then just run the script as you normally would. The setuid bit kicks in and runs the script as if it was run by root. This is the safest way to temporarily elevate privileges.
See Ruby's documentation on safe levels and taint to understand Ruby's sanitation requirements to protect against tainted input causing harm. Or the perlsec faq to learn the how the same thing is done in Perl.
Again. If you're dead set on running scripts as root from an automated system. Do Not Use Bash! Use Ruby or Perl instead of Bash. Taint mode forces you to take security seriously and can avoid many unnecessary problems down the line.

Resources