docker-commandbox icon indicating copy to clipboard operation
docker-commandbox copied to clipboard

Container does not work with non-root user

Open coryasilva opened this issue 3 years ago • 3 comments

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

coryasilva avatar Dec 01 '20 21:12 coryasilva

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.

coryasilva avatar Dec 02 '20 00:12 coryasilva

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

coryasilva avatar Dec 02 '20 00:12 coryasilva

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 avatar Dec 02 '20 22:12 coryasilva

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

jclausen avatar Oct 18 '22 15:10 jclausen