Docker Entrypoint Script Root Permission

littlespice3 picture littlespice3 · Jan 30, 2018 · Viewed 7.6k times · Source

I have a Dockerfile that has 3 constraints:

1.) The final USER statement in the Dockerfile must be USER tomcat for security purposes (it is built off the tomcat:8.5.23-jre8-alpine image)

2.) It cannot run chmod on the /root/ directory or the $JAVA_HOME

3.) It must be able to modify the $JAVA_HOME/lib/security directory within the entrypoint and not in the Dockerfile (as certificates are passed in on container startup)

As of now I have

FROM tomcat:8.5.23-jre8-alpine

#### OTHER IMAGE COMMANDS ####

COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh

RUN chown root:root /usr/local/bin/entrypoint.sh

RUN chmod 4755 /usr/local/bin/entrypoint.sh

USER tomcat

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["catalina.sh", "run"]

I figured giving ownership of the file to root and chmod 4755 would allow the tomcat user to execute the entrypoint, but have entrypoint run as root so that it could modify the file system as it would like to. However, when I run this line in the entrypoint

$JAVA_HOME/bin/keytool -import -alias ca_local -keystore $JAVA_HOME/lib/security/cacerts -storepass XXXXX -noprompt -trustcacerts -file /usr/local/tomcat/certificates/ca-local.cer

I get the following error:

keytool error: java.io.FileNotFoundException: /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts (Permission denied)

It seems the entrypoint isn't actually running as root, as the chown/chmod would have implied. I know that all of my files are in the correct places because when I remove USER tomcat from the Dockerfile everything works fine. What am I missing in setting the permission of the entrypoint?

Answer

larsks picture larsks · Jan 30, 2018

In these two steps:

RUN chown root:root /usr/local/bin/entrypoint.sh
RUN chmod 4755 /usr/local/bin/entrypoint.sh

You are ensuring the your entrypoint.sh script is owned by root, and then you are attempting to set the setuid bit. Unfortunately, this second step isn't going to work: you can't mark a shell script (or in fact any interpreted script) as setuid; the bit will simply be ignored. See, e.g, this question.

But event if that worked, you would have another problem: your ENTRYPOINT script is responsible for handling any command passed into your container, either on the docker run command line or via the CMD directive in the Dockerfile. If that setuid attempt worked, your ENTRYPOINT script would be running as root...which means you would end up running catalina.sh run as root, which means your USER directive is completely pointless.

You could get to the same place by simply dropping the USER directive, allowing the entrypoint to run as root, and then have your ENTRYPOINT script run the CMD as the tomcat user (e.g., using su or sudo):

FROM tomcat:8.5.23-jre8-alpine

#### OTHER IMAGE COMMANDS ####

COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["sh", "/usr/local/bin/entrypoint.sh"]
CMD ["catalina.sh", "run"]

And in entrypoint.sh:

#!/bin/sh

# Do stuff as root here.
$JAVA_HOME/bin/keytool -import -alias ca_local \
  -keystore $JAVA_HOME/lib/security/cacerts 
  -storepass XXXXX -noprompt -trustcacerts \
  -file /usr/local/tomcat/certificates/ca-local.cer

# Now run everything else as a non-root user
exec su - tomcat -c "$*"

This is a very common mechanism for handling initial setup tasks that must be run as root while running everything else as a non-root user.

There are still a number of problems here: the underlying tomcat:8.5.23-jre8-alpine doesn't have a tomcat user, and the scripts in /usr/local/tomcat/bin are only executable by root. So if you want this particular image to work with a non-root user you probably have a bunch of configuration changes to make first.