docker-commandbox
docker-commandbox copied to clipboard
Container does not work with non-root user
When running this container in more secure environments like a locked down jenkins (okay stop laughing) and openshift the container may run as a random high numbered user. This is related to https://github.com/Ortus-Solutions/docker-commandbox/issues/24.
Expected:
box version
should print commandbox version
Actual:
box version
prints this error
ERROR: Unable to create system bundle directory.
javax.servlet.ServletException: java.lang.NullPointerException
at lucee.loader.engine.CFMLEngineFactory.initEngine(CFMLEngineFactory.java:384)
at lucee.loader.engine.CFMLEngineFactory.initEngineIfNecessary(CFMLEngineFactory.java:267)
at lucee.loader.engine.CFMLEngineFactory.getInstance(CFMLEngineFactory.java:169)
at lucee.runtime.script.BaseScriptEngineFactory.<init>(BaseScriptEngineFactory.java:59)
at lucee.runtime.script.LuceeScriptEngineFactory.<init>(LuceeScriptEngineFactory.java:27)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at java.util.ServiceLoader$LazyIterator.nextService(Unknown Source)
at java.util.ServiceLoader$LazyIterator.next(Unknown Source)
at java.util.ServiceLoader$1.next(Unknown Source)
at javax.script.ScriptEngineManager.initEngines(Unknown Source)
at javax.script.ScriptEngineManager.init(Unknown Source)
at javax.script.ScriptEngineManager.<init>(Unknown Source)
at cliloader.LoaderCLIMain.execute(LoaderCLIMain.java:305)
at cliloader.LoaderCLIMain.execute(LoaderCLIMain.java:155)
at cliloader.LoaderCLIMain.main(LoaderCLIMain.java:580)
Caused by: java.lang.NullPointerException
at org.apache.felix.framework.Felix.handleJavaVersionChange(Felix.java:985)
at org.apache.felix.framework.Felix.init(Felix.java:799)
at org.apache.felix.framework.Felix.init(Felix.java:641)
at org.apache.felix.framework.Felix.start(Felix.java:1084)
at lucee.loader.engine.CFMLEngineFactory.getFelix(CFMLEngineFactory.java:510)
at lucee.loader.osgi.BundleLoader.loadBundles(BundleLoader.java:105)
at lucee.loader.engine.CFMLEngineFactory.initEngine(CFMLEngineFactory.java:368)
... 18 more
ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider lucee.runtime.script.LuceeScriptEngineFactory could not be instantiated
ERROR: Unable to create system bundle directory.
javax.servlet.ServletException: java.lang.NullPointerException
at lucee.loader.engine.CFMLEngineFactory.initEngine(CFMLEngineFactory.java:384)
at lucee.loader.engine.CFMLEngineFactory.initEngineIfNecessary(CFMLEngineFactory.java:267)
at lucee.loader.engine.CFMLEngineFactory.getInstance(CFMLEngineFactory.java:169)
at lucee.runtime.script.BaseScriptEngineFactory.<init>(BaseScriptEngineFactory.java:59)
at lucee.runtime.script.CFMLScriptEngineFactory.<init>(CFMLScriptEngineFactory.java:27)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at java.util.ServiceLoader$LazyIterator.nextService(Unknown Source)
at java.util.ServiceLoader$LazyIterator.next(Unknown Source)
at java.util.ServiceLoader$1.next(Unknown Source)
at javax.script.ScriptEngineManager.initEngines(Unknown Source)
at javax.script.ScriptEngineManager.init(Unknown Source)
at javax.script.ScriptEngineManager.<init>(Unknown Source)
at cliloader.LoaderCLIMain.execute(LoaderCLIMain.java:305)
at cliloader.LoaderCLIMain.execute(LoaderCLIMain.java:155)
at cliloader.LoaderCLIMain.main(LoaderCLIMain.java:580)
Caused by: java.lang.NullPointerException
at org.apache.felix.framework.Felix.handleJavaVersionChange(Felix.java:985)
at org.apache.felix.framework.Felix.init(Felix.java:799)
at org.apache.felix.framework.Felix.init(Felix.java:641)
at org.apache.felix.framework.Felix.start(Felix.java:1084)
at lucee.loader.engine.CFMLEngineFactory.getFelix(CFMLEngineFactory.java:510)
at lucee.loader.osgi.BundleLoader.loadBundles(BundleLoader.java:105)
at lucee.loader.engine.CFMLEngineFactory.initEngine(CFMLEngineFactory.java:368)
... 18 more
ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider lucee.runtime.script.CFMLScriptEngineFactory could not be instantiated
java.lang.NullPointerException
at cliloader.LoaderCLIMain.execute(LoaderCLIMain.java:330)
at cliloader.LoaderCLIMain.execute(LoaderCLIMain.java:155)
at cliloader.LoaderCLIMain.main(LoaderCLIMain.java:580)
Steps to reproduce:
# Sanity Check
docker run -v "$PWD:/app" ortussolutions/commandbox:adobe11-alpine id
# uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
# Sanity Check: Running with different as a user
docker run -u 1000 -v "$PWD:/app" ortussolutions/commandbox:adobe11-alpine id
# uid=1000 gid=0(root)
# Sanity Check: box version as root
docker run -v "$PWD:/app" ortussolutions/commandbox:adobe11-alpine box version
# CommandBox 5.2.0+00280
# Testcase: box version as different user
docker run -u 1000 -v "$PWD:/app" ortussolutions/commandbox:adobe11-alpine box version
While debugging this I found that https://github.com/Ortus-Solutions/commandbox/blob/686ff4611b9756f8009eeccd95e72d3cd5e0ef4c/src/cfml/system/util/FileSystem.cfc#L27 further complicates the issue since java.lang.System.getProperty( 'user.home' )
resolves as ?
(probably because the user does not exist in passwd). The FileSystem
util could employ a strategy to get the environment variable$HOME
in this case.
This might require adding an ENTRYPOINT
that creates a user based on the current user.
Workaround
I will just stop being lazy and make my own container, but I do believe that this is something that should be considered and handled by the base container.
Dockerfile
FROM ortussolutions/commandbox:adobe11-alpine
RUN for dir in \
/usr/lib/CommandBox \
/root ; \
do mkdir -m 0775 -p $dir ; chmod -R g+rwx $dir ; done \
&& \
for file in \
/etc/passwd \
/etc/group ; \
do touch $file ; chmod g+rw $file ; done
COPY entrypoint.sh /bin/entrypoint
RUN chmod +x /bin/entrypoint
ENTRYPOINT ["/bin/entrypoint"]
CMD ["box", "version"]
Entrypoint
#!/usr/bin/env bash
if [[ (`id -u` -ge 500 || -z "${USER}") ]]; then
cat << EOF > /etc/passwd
root:x:0:0:root:/root:/bin/bash
box:x:`id -u`:`id -g`:,,,:/root:/bin/bash
EOF
cat <<EOF > /etc/group
root:x:0:box
box:x:`id -g`:
EOF
fi
exec "${@}"
Tests
docker run -u 1000 commandbox-test:latest box version
# CommandBox 5.2.0+00280
docker run -u 1000 commandbox-test:latest whoami
# box
Workaround (jenkins)
So because jenkins does not run entrypoints (https://issues.jenkins.io/browse/JENKINS-54389), I thought it nice to post this workaround for whoever needs it. Its kind of hacky but necessary if your Jenkins server is not running as root.
pipeline {
stages {
stage('CF Unit Test') {
agent {
docker {
image "ortussolutions/commandbox:adobe11-alpine"
args '-u root -v $WORKSPACE:/app'
}
}
steps {
sh 'cd /app && box install save=false'
sh '''
cd /app && \
box server start \
debug=true \
saveSettings=false \
cfengine=${CFENGINE} \
host=0.0.0.0 \
port=8080
'''
sh '''
cd /app && \
box testbox run \
runner=http://localhost:8080/tests/runner.cfm \
reporter=junit \
outputFile=tests/results/cf-junit.xml
'''
junit "tests/results/cf-junit.xml"
}
post {
cleanup {
// Cleanup box install packages (since we are using root in container and jenkins is not)
sh "rm -rf `jq -r '.installPaths | join(\" \")' box.json` tests/results"
}
}
}
}
}
@coryasilva : The functionality you are looking for should be resolved in the next version of the image, but the user ID will need to come from the environment - not the -u
flag on docker run
docker run -e "USER=mynonrootuser" -e "USER_ID=1000"
The reason being is that the file system is owned by the default user ( in this case root
) in the core Dockerfiles. your solution of using a Dockerfile to override is valid because, within the build context, that user has all of the permissions it needs to modify the filesystem permissions.
When you pass the USER
and USER_ID
environment variables, the container initializes as the default user, but the actual runtime CMD is executed by the specified user.
Aside from doing a chmod
on all of the files in use in the container, granting read/write/execute permissions, which presents additional security vulnerabilities, I'm not sure the juice is worth the squeeze - especially as you noted above that Jenkins doesn't run entrypoints.
I'm open to being influenced or finding another workaround, but I'm reluctant to open up the file system to support the -u
flag without a custom Dockerfile build.