diff --git a/Dockerfile b/Dockerfile index d6da946..8864aa8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,6 @@ ENV RUN_GID 2002 ENV CONFLUENCE_HOME /var/atlassian/application-data/confluence ENV CONFLUENCE_INSTALL_DIR /opt/atlassian/confluence -VOLUME ["${CONFLUENCE_HOME}"] WORKDIR $CONFLUENCE_HOME # Expose HTTP and Synchrony ports @@ -31,7 +30,8 @@ ARG CONFLUENCE_VERSION ARG DOWNLOAD_URL=https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-${CONFLUENCE_VERSION}.tar.gz RUN groupadd --gid ${RUN_GID} ${RUN_GROUP} \ - && useradd --uid ${RUN_UID} --gid ${RUN_GID} --home-dir ${CONFLUENCE_HOME} ${RUN_USER} \ + && useradd --uid ${RUN_UID} --gid ${RUN_GID} --home-dir ${CONFLUENCE_HOME} --shell /bin/bash ${RUN_USER} \ + && echo PATH=$PATH > /etc/environment \ \ && mkdir -p ${CONFLUENCE_INSTALL_DIR} \ && curl -L --silent ${DOWNLOAD_URL} | tar -xz --strip-components=1 -C "${CONFLUENCE_INSTALL_DIR}" \ @@ -40,8 +40,11 @@ RUN groupadd --gid ${RUN_GID} ${RUN_GROUP} \ && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_INSTALL_DIR}/logs \ && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_INSTALL_DIR}/temp \ && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_INSTALL_DIR}/work \ + && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_HOME} \ \ && sed -i -e 's/-Xms\([0-9]\+[kmg]\) -Xmx\([0-9]\+[kmg]\)/-Xms\${JVM_MINIMUM_MEMORY:=\1} -Xmx\${JVM_MAXIMUM_MEMORY:=\2} \${JVM_SUPPORT_RECOMMENDED_ARGS} -Dconfluence.home=\${CONFLUENCE_HOME}/g' ${CONFLUENCE_INSTALL_DIR}/bin/setenv.sh +VOLUME ["${CONFLUENCE_HOME}"] # Must be declared after setting perms + COPY entrypoint.py /entrypoint.py COPY config/* /opt/atlassian/etc/ diff --git a/Dockerfile-alpine b/Dockerfile-alpine index dc0c89f..5cf626c 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -9,7 +9,6 @@ ENV RUN_GID 2002 ENV CONFLUENCE_HOME /var/atlassian/application-data/confluence ENV CONFLUENCE_INSTALL_DIR /opt/atlassian/confluence -VOLUME ["${CONFLUENCE_HOME}"] WORKDIR $CONFLUENCE_HOME # Expose HTTP and Synchrony ports @@ -31,7 +30,7 @@ ARG CONFLUENCE_VERSION ARG DOWNLOAD_URL=https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-${CONFLUENCE_VERSION}.tar.gz RUN addgroup -g ${RUN_GID} ${RUN_GROUP} \ - && adduser -u ${RUN_UID} -G ${RUN_GROUP} -h ${CONFLUENCE_HOME} -D ${RUN_USER} \ + && adduser -u ${RUN_UID} -G ${RUN_GROUP} -h ${CONFLUENCE_HOME} -s /bin/bash -D ${RUN_USER} \ \ && mkdir -p ${CONFLUENCE_INSTALL_DIR} \ && curl -L --silent ${DOWNLOAD_URL} | tar -xz --strip-components=1 -C "${CONFLUENCE_INSTALL_DIR}" \ @@ -40,8 +39,11 @@ RUN addgroup -g ${RUN_GID} ${RUN_GROUP} \ && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_INSTALL_DIR}/logs \ && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_INSTALL_DIR}/temp \ && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_INSTALL_DIR}/work \ + && chown -R ${RUN_USER}:${RUN_GROUP} ${CONFLUENCE_HOME} \ \ && sed -i -e 's/-Xms\([0-9]\+[kmg]\) -Xmx\([0-9]\+[kmg]\)/-Xms\${JVM_MINIMUM_MEMORY:=\1} -Xmx\${JVM_MAXIMUM_MEMORY:=\2} \${JVM_SUPPORT_RECOMMENDED_ARGS} -Dconfluence.home=\${CONFLUENCE_HOME}/g' ${CONFLUENCE_INSTALL_DIR}/bin/setenv.sh +VOLUME ["${CONFLUENCE_HOME}"] # Must be declared after setting perms + COPY entrypoint.py /entrypoint.py COPY config/* /opt/atlassian/etc/ diff --git a/README.md b/README.md index 9bcfe02..5d19111 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,18 @@ of options available: * Under Linux, the UID can be remapped using [user namespace remapping][8]. +To preserve strict permissions for certain configuration files, this container starts as +`root` to perform bootstrapping before running Confluence under a non-privileged user +account. If you wish to start the container as a non-root user, please note that Tomcat +configuration, and the bootstrapping of seraph-config.xml (SSO) & +confluence-init.properties (overriding `$CONFLUENCE_HOME`) will be skipped and a warning +will be logged. You may still apply custom configuration in this situation by mounting a +custom file directly, e.g. by mounting your own server.xml file directly to +`/opt/atlassian/confluence/conf/server.xml` + +Database and Clustering bootstrapping will work as expected when starting this container +as a non-root user. + # Upgrade To upgrade to a more recent version of Confluence Server you can simply stop the diff --git a/bin/push-readme.py b/bin/push-readme.py new file mode 100644 index 0000000..e4fd067 --- /dev/null +++ b/bin/push-readme.py @@ -0,0 +1,32 @@ +import logging +import os + +import requests + + +logging.basicConfig(level=logging.INFO) + + +DOCKER_REPO = os.environ.get('DOCKER_REPO') +DOCKER_USERNAME = os.environ.get('DOCKER_USERNAME') +DOCKER_PASSWORD = os.environ.get('DOCKER_PASSWORD') +README_FILE = os.environ.get('README_FILE') or 'README.md' + + +logging.info('Generating Docker Hub JWT') +data = {'username': DOCKER_USERNAME, 'password': DOCKER_PASSWORD} +r = requests.post('https://hub.docker.com/v2/users/login/', json=data) +docker_token = r.json().get('token') + +logging.info(f'Updating Docker Hub description for {DOCKER_REPO}') +with open(README_FILE) as f: + full_description = f.read() +data = {'registry': 'registry-1.docker.io', 'full_description': full_description} +headers = {'Authorization': f'JWT {docker_token}'} +r = requests.patch(f'https://cloud.docker.com/v2/repositories/{DOCKER_REPO}/', + json=data, headers=headers) + +if r.status_code == requests.codes.ok: + logging.info(f'Successfully updated {README_FILE} for {DOCKER_REPO}') +else: + logging.info(f'Unable to update {README_FILE} for {DOCKER_REPO}, response code: {r.status_code}') \ No newline at end of file diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index ac487c7..3a7ade1 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -53,7 +53,7 @@ pipelines: script: - echo ${DOCKER_PASSWORD} | docker login --username ${DOCKER_USERNAME} --password-stdin - > - docker build -t atlassian/confluence-server:${DOCKER_TAG} + docker build -t atlassian/confluence-server:${DOCKER_TAG} -f Dockerfile-alpine --build-arg CONFLUENCE_VERSION=${CONFLUENCE_VERSION} . - docker push atlassian/confluence-server:${DOCKER_TAG} @@ -86,6 +86,13 @@ pipelines: - export TAG_SUFFIXES='adoptopenjdk8,jdk8,ubuntu,ubuntu-18.04-adoptopenjdk8' - echo ${DOCKER_PASSWORD} | docker login --username ${DOCKER_USERNAME} --password-stdin - python /usr/src/app/run.py --update + - step: + name: Update README + image: python:3.7-alpine3.9 + script: + - pip install -q requests + - export DOCKER_REPO='atlassian/confluence-server' + - python bin/push-readme.py pull-requests: '**': - step: @@ -98,4 +105,4 @@ pipelines: definitions: services: docker: - memory: 2048 \ No newline at end of file + memory: 2048 diff --git a/entrypoint.py b/entrypoint.py index bd730ff..fc767ba 100755 --- a/entrypoint.py +++ b/entrypoint.py @@ -48,14 +48,17 @@ env = {k.lower(): v ###################################################################### # Generate all configuration files for Confluence -gen_cfg('server.xml.j2', - f"{env['confluence_install_dir']}/conf/server.xml", env) +if os.getuid() == 0: + gen_cfg('server.xml.j2', + f"{env['confluence_install_dir']}/conf/server.xml", env) -gen_cfg('seraph-config.xml.j2', - f"{env['confluence_install_dir']}/confluence/WEB-INF/classes/seraph-config.xml", env) + gen_cfg('seraph-config.xml.j2', + f"{env['confluence_install_dir']}/confluence/WEB-INF/classes/seraph-config.xml", env) -gen_cfg('confluence-init.properties.j2', - f"{env['confluence_install_dir']}/confluence/WEB-INF/classes/confluence-init.properties", env) + gen_cfg('confluence-init.properties.j2', + f"{env['confluence_install_dir']}/confluence/WEB-INF/classes/confluence-init.properties", env) +else: + logging.warning("Container not started as root. Tomcat, seraph-config.xml, confluence-init.properties boostrapping will be skipped.") gen_cfg('confluence.cfg.xml.j2', f"{env['confluence_home']}/confluence.cfg.xml", env, diff --git a/tests/test_image.py b/tests/test_image.py index 6eb8c84..32628c0 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -14,8 +14,8 @@ CONF_SHARED_HOME = '/media/atl/confluence/shared-home' # Run an image and wrap it in a TestInfra host for convenience. # FIXME: There's probably a way to turn this into a fixture with parameters. -def run_image(docker_cli, image, environment={}, ports={}): - container = docker_cli.containers.run(image, environment=environment, ports=ports, detach=True) +def run_image(docker_cli, image, **kwargs): + container = docker_cli.containers.run(image, detach=True, **kwargs) return testinfra.get_host("docker://"+container.id) # TestInfra's process command doesn't seem to work for arg matching @@ -65,10 +65,10 @@ def test_jvm_args(docker_cli, image): def test_install_permissions(docker_cli, image): container = run_image(docker_cli, image) - assert container.file(f'{CONF_INSTALL}/conf/server.xml').user == 'root' + assert container.file(f'{CONF_INSTALL}').user == 'root' for d in ['logs', 'work', 'temp']: - path = f'{CONF_INSTALL}/{d}/' + path = f'{CONF_INSTALL}/{d}' assert container.file(path).user == 'confluence' @@ -321,3 +321,15 @@ def test_confluence_xml_cluster_tcp(docker_cli, image): assert xml.findall('.//property[@name="confluence.cluster.join.type"]')[0].text == "tcp_ip" assert xml.findall('.//property[@name="confluence.cluster.name"]')[0].text == "atl_cluster_name" assert xml.findall('.//property[@name="confluence.cluster.peers"]')[0].text == "1.1.1.1,99.99.99.99" + +def test_java_in_run_user_path(docker_cli, image): + RUN_USER = 'confluence' + container = run_image(docker_cli, image) + proc = container.run(f'su -c "which java" {RUN_USER}') + assert len(proc.stdout) > 0 + +def test_non_root_user(docker_cli, image): + RUN_UID = 2002 + RUN_GID = 2002 + container = run_image(docker_cli, image, user=f'{RUN_UID}:{RUN_GID}') + jvm = wait_for_proc(container, "org.apache.catalina.startup.Bootstrap") \ No newline at end of file