Skip to content

Commit

Permalink
Merge pull request #3 from panubo/k8s_nonroot_upgrade
Browse files Browse the repository at this point in the history
Upgrade to support a Kubernetes nginx deployment and run as non-root
  • Loading branch information
trnubo committed Feb 1, 2024
2 parents 0091f1b + 385f916 commit e9eb10f
Show file tree
Hide file tree
Showing 20 changed files with 239 additions and 60 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "tests/test_helpers/bats-support"]
path = tests/test_helpers/bats-support
url = https://github.com/bats-core/bats-support.git
[submodule "tests/test_helpers/bats-assert"]
path = tests/test_helpers/bats-assert
url = https://github.com/bats-core/bats-assert.git
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ RUN set -x \
;

RUN apk update \
&& apk add --no-cache bash git nginx aws-cli \
&& apk add --no-cache bash tree git nginx aws-cli \
&& cd /etc/nginx \
&& mkdir -p /run/nginx /var/www/html \
&& ln -s /dev/stdout /var/log/nginx/access.log \
&& ln -s /dev/stderr /var/log/nginx/error.log \
&& chown nginx:nginx /etc/nginx/http.d \
&& chown nginx:nginx /var/www/html \
;

RUN echo "daemon off;" >> /etc/nginx/nginx.conf
Expand All @@ -49,7 +53,9 @@ COPY *.sh /

ENTRYPOINT ["/entry.sh"]

EXPOSE 80
EXPOSE 8080
STOPSIGNAL SIGTERM

USER nginx

CMD ["nginx"]
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ This image is available on quay.io `quay.io/panubo/staticsite` and AWS ECR Publi
## Entrypoint Commands

- `nginx` - Serve static files from `/var/www/html` (default)
- `s3sync` - Synchronize the files in `/var/www/html` with a S3 bucket (uses awscli).
- `s3sync` - Synchronize the files in `/var/www/html` with a S3 bucket (uses awscli)
- `k8s-init` - Copy all files to volume and template files (intended for a Kubernetes initContainer)
- `k8s-nginx` - Start nginx only (no rendering, intended to run after `k8s-init`)

## Configuration / Environment Options

For `nginx` entrypoint:

- `PORT` - server port for nginx to listen on (Default: `8080`)
- `NGINX_SERVER_ROOT` - server web root (Default: `/var/www/html`)
- `NGINX_SERVER_INDEX` - server index page(s) (Default: `index.html index.htm`)
- `NGINX_SINGLE_PAGE_ENABLED` - if set to `true` all requests will be routed through `/$NGINX_SINGLE_PAGE_INDEX`
Expand Down Expand Up @@ -48,7 +51,10 @@ Additional entrypoint pre-commands or post-commands can be specified in a `Deplo
- `DEPLOYFILE_POST` - Post Deployfile location, (Default: `/Deployfile.post`)
- `RUN_DEPLOYFILE_COMMANDS` - Set to `true` to enable this functionality.

N.B. When running `nginx` command, only the `DEPLOYFILE_PRE` is able to execute.
Notes:

* When running `nginx` command, only the `DEPLOYFILE_PRE` is able to execute.
* Pre and post Deployfile is disabled for the `k8s-*` entrypoints

### Cache Control Override

Expand All @@ -70,11 +76,34 @@ See [docs](./docs/) for usage examples.

This is used for deploying production sites, however it should be considered subject to functionality changes.

## v0.4.0 Upgrade **BREAKING CHANGES**

There are two main breaking changes includes in the v0.4.0 release.

**Run as non-root**

The image is setup to be run as non-root. This requires any content added to the image be owned by the `nginx` user. This can be achieved with one of the following methods.

Using `COPY --chown=nginx:nginx . /var/www/html` (recommended)

Or, using

```
USER root
RUN chown -R nginx:nginx /var/www/html
USER nginx
```

Alternatively this change can be simply reverted by adding `USER root` to your downstream image.

**Change nginx port to 8080**

Previously nginx listened on port `80` however this is considered a privileged port, the image now defaults to listening on port `8080`. This can be overridden by setting the env var `PORT=80`.

### Known issues

* Setting both cache control override and content type override may result in unexpected behaviour.

### TODO

* Implement similar Cache-Control functionality for Nginx hosted static sites.
* Get everything to work with a non-root user
4 changes: 2 additions & 2 deletions default.conf.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
listen {{env.Getenv "PORT" "8080"}} default_server;
listen [::]:{{env.Getenv "PORT" "8080"}} default_server;

index {{.Env.NGINX_SERVER_INDEX}};
root {{.Env.NGINX_SERVER_ROOT}};
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ ADD . .
# Build the site using yarn
RUN yarn install --pure-lockfile --ignore-optional --no-progress

FROM panubo/staticsite:latest
FROM quay.io/panubo/staticsite:latest

# Copy build artefacts to staticsite image
COPY --from=build /usr/src/app/dist /var/www/html
COPY --from=build --chown=nginx:nginx /usr/src/app/dist /var/www/html

WORKDIR /var/www/html

Expand Down
4 changes: 2 additions & 2 deletions docs/example/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ ADD . .
# Build the site using yarn
RUN yarn install --pure-lockfile --ignore-optional --no-progress

FROM panubo/staticsite:latest
FROM quay.io/panubo/staticsite:latest

# Copy build artefacts to staticsite image
# COPY --from=build /usr/src/app/dist /var/www/html
COPY --from=build --chown=nginx:nginx /usr/src/app/dist /var/www/html

WORKDIR /var/www/html

Expand Down
29 changes: 22 additions & 7 deletions entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,42 @@ export NGINX_SINGLE_PAGE_INDEX=${NGINX_SINGLE_PAGE_INDEX:-'index.html'}
export DEPLOYFILE_PRE=${DEPLOYFILE_PRE:-'/Deployfile.pre'}
export DEPLOYFILE_POST=${DEPLOYFILE_POST:-'/Deployfile.post'}

echo "> Running templater"
/templater.sh
templater() {
echo "> Running templater"
/templater.sh
}

deployfile_pre() {
if [[ -f "${DEPLOYFILE_PRE}" ]] && [[ "${RUN_DEPLOYFILE_COMMANDS:-}" == 'true' ]]; then
echo "> Running all commands in ${DEPLOYFILE_PRE}"
run_all "${DEPLOYFILE_PRE}"
fi
}

echo "> Entrypoint command:" "${@}"

if [[ -f "${DEPLOYFILE_PRE}" ]] && [[ "${RUN_DEPLOYFILE_COMMANDS:-}" == 'true' ]]; then
echo "> Running all commands in ${DEPLOYFILE_PRE}"
run_all "${DEPLOYFILE_PRE}"
fi

if [[ "${1}" == "s3sync" ]]; then
templater
deployfile_pre
echo "> Running s3sync"
/s3sync.sh
if [[ -f "${DEPLOYFILE_POST}" ]] && [[ "${RUN_DEPLOYFILE_COMMANDS:-}" == 'true' ]]; then
echo "> Running all commands in ${DEPLOYFILE_POST}"
run_all "${DEPLOYFILE_POST}"
fi
elif [[ "${1}" == "nginx" ]]; then
templater
deployfile_pre
echo "> Running nginx"
render_templates /etc/nginx/http.d/default.conf.tmpl
nginx
elif [[ "${1}" == "k8s-nginx" ]]; then
echo "> Running nginx"
nginx
elif [[ "${1}" == "k8s-init" ]]; then
echo "> Running Kubernetes template script"
/k8s-init.sh
exit
else
if [[ "$1" != "" ]]; then
exec "${@}"
Expand Down
27 changes: 27 additions & 0 deletions k8s-init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

# This script will copy the nginx config and html content to /volume then run
# the normal renderers. This is used in an initContainer in a Kubernetes pod.
# The /volume mount should then be mounted into the main container as
# readOnly mounts and using `k8s-nginx` as the command.

K8S_VOLUME_PATH="${K8S_VOLUME_PATH:=/volume}"

source /panubo-functions.sh

set -euo pipefail
IFS=$'\n\t'

[[ "${DEBUG:-}" == 'true' ]] && set -x

mkdir "${K8S_VOLUME_PATH}/config"
mkdir "${K8S_VOLUME_PATH}/content"

cp -a /etc/nginx/http.d "${K8S_VOLUME_PATH}/config"
cp -a "${NGINX_SERVER_ROOT}" "${K8S_VOLUME_PATH}/content"

export OLD_NGINX_SERVER_ROOT="${NGINX_SERVER_ROOT}"
export NGINX_SERVER_ROOT=${K8S_VOLUME_PATH}/content/html
/templater.sh

render_templates "${K8S_VOLUME_PATH}/config/http.d/default.conf.tmpl"
6 changes: 4 additions & 2 deletions templater.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ IFS=$'\n\t'

# render templates when set
while read -r line; do
echo ">> Running templater on ${line#*\=}"
newline="${line#*\=}"
echo ">> Running templater on ${newline}"
(
# relative paths to server root
cd "${NGINX_SERVER_ROOT}"
render_templates "${line#*\=}"
# render templates and replace any full paths if OLD_NGINX_SERVER_ROOT is set (set by k8s-init.sh)
render_templates "${newline/${OLD_NGINX_SERVER_ROOT:-}/${NGINX_SERVER_ROOT}}"
)
done < <(env | grep "^RENDER_TEMPLATE")
6 changes: 4 additions & 2 deletions tests/html/v1/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ENV \
CACHE_CONTROL_OVERRIDE_INDEX="index.html" \
CACHE_CONTROL_OVERRIDE_404="404.html" \
CACHE_CONTROL_DEFAULT_OVERRIDE="public, max-age=60, s-maxage=60" \
CACHE_CONTROL_OVERRIDE_SPECIAL="special-cache.html:public, max-age=600"
CACHE_CONTROL_OVERRIDE_SPECIAL="special-cache.html:public, max-age=600" \
RENDER_TEMPLATE_ENV_CONFIG=/var/www/html/env-config.js.tmpl \
RENDER_TEMPLATE_ENV_CONFIG2=env-config2.js.tmpl

COPY . /var/www/html
COPY --chown=nginx:nginx . /var/www/html
3 changes: 3 additions & 0 deletions tests/html/v1/env-config.js.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
window._env_ = {
"hostname": "{{ env.Getenv "HOSTNAME" }}",
}
3 changes: 3 additions & 0 deletions tests/html/v1/env-config2.js.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
window._env_ = {
"hostname": "{{ env.Getenv "HOSTNAME" }}",
}
4 changes: 4 additions & 0 deletions tests/html/v2/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ ENV \
CACHE_CONTROL_OVERRIDE_CACHED_JSON="cached-json:public, max-age=1200"

COPY . /var/www/html

USER root
RUN chown -R nginx:nginx /var/www/html
USER nginx
56 changes: 56 additions & 0 deletions tests/k8s.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
load test_helpers/bats-support/load
load test_helpers/bats-assert/load
load functions.bash
# load setup.bash

setup_file() {
# Disable parallel execution in this file
export BATS_NO_PARALLELIZE_WITHIN_FILE=true

docker_volume="$(docker volume create)"

export docker_volume
}

teardown_file() {
docker volume rm -f "${docker_volume}" || true
}

@test "k8s-init" {
# Fix volume permissions to match K8s behaviour
docker run --rm -v "${docker_volume}:/volume" busybox install -d -o root -g 2000 -m 2775 /volume

# Run k8s-init - content and config should be copied to the volume
docker run --rm -v "${docker_volume}:/volume" --group-add 2000 panubo/staticsite-testsite:1 k8s-init

# Print the content of the volume
run docker run --rm -v "${docker_volume}:/volume" busybox sh -c 'find /volume | sort'

# diag "${output}"
assert_line '/volume/config/http.d/default.conf'
assert_line '/volume/content/html/env-config.js'
assert_line '/volume/content/html/env-config2.js'

# assert_output --regexp '/volume/config/http\.d/default\.conf[^.]'
# assert_output --regexp '/volume/content/html/env-config\.js[^.]'
# assert_output --regexp '/volume/content/html/env-config2\.js[^.]'
}

@test "k8s-nginx" {
# This test isn't possible with docker since your cannot mount a subPath
# from a docker volume. eg `-v "$
# {docker_volume}/config/http.d:/etc/nginx/http.d:ro` (or whatever the
# syntax will be if implemented in docker).
skip "Unable to implement with docker, missing subPath support"

container="$(docker run -d -v "${docker_volume}:/volume:ro" --group-add 2000 -p 8080 panubo/staticsite-testsite:1 k8s-nginx)"
container_ip="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${container})"
container_http_port="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort}}' ${container} || { docker logs ${container} >&3 2>&3; return 1; })"
( wait_http "http://127.0.0.1:${container_http_port}"; )

run curl -sSf http://127.0.0.1:${container_http_port}

docker rm -f "${container}" || true

assert_success
}
10 changes: 6 additions & 4 deletions tests/nginx.bats
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
load test_helpers/bats-support/load
load test_helpers/bats-assert/load
load functions.bash
# load setup.bash

setup_file() {
container="$(docker run -d -p 80 panubo/staticsite-testsite:1 nginx)"
container="$(docker run -d -p 8080 panubo/staticsite-testsite:1 nginx)"
container_ip="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${container})"
container_http_port="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostPort}}' ${container})"
container_http_port="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort}}' ${container} || { docker logs ${container} >&3 2>&3; return 1; })"
( wait_http "http://127.0.0.1:${container_http_port}"; )
export container container_ip container_http_port
}
Expand All @@ -17,6 +19,6 @@ teardown_file() {
# echo "# curl -sSf http://127.0.0.1:${container_http_port}" >&3
run curl -sSf http://127.0.0.1:${container_http_port}
# diag "${output}"
[[ "${status}" -eq 0 ]]
[[ "${lines[0]}" = "<h1>Hello World!</h1>" ]]
assert_success
assert_line "<h1>Hello World!</h1>"
}
14 changes: 8 additions & 6 deletions tests/s3-rollback.bats
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load test_helpers/bats-support/load
load test_helpers/bats-assert/load
load functions.bash
# load setup.bash

Expand Down Expand Up @@ -51,8 +53,8 @@ teardown_file() {
run curl -i -sSf http://127.0.0.1:${minio_container_http_port}/test-bucket/index.html
# diag "${output}"

[[ "${status}" -eq 0 ]]
grep "<p>v1</p>" <<<"${output}"
assert_success
assert_line "<p>v1</p>"
}

@test "s3-rollback upload v2" {
Expand All @@ -69,8 +71,8 @@ teardown_file() {
run curl -i -sSf http://127.0.0.1:${minio_container_http_port}/test-bucket/index.html
# diag "${output}"

[[ "${status}" -eq 0 ]]
grep "<p>v2</p>" <<<"${output}"
assert_success
assert_line "<p>v2</p>"
}

@test "s3-rollback rollback to v1" {
Expand All @@ -87,6 +89,6 @@ teardown_file() {
run curl -sSf http://127.0.0.1:${minio_container_http_port}/test-bucket/index.html
# diag "${output}"

[[ "${status}" -eq 0 ]]
grep "<p>v1</p>" <<<"${output}"
assert_success
assert_line "<p>v1</p>"
}
Loading

0 comments on commit e9eb10f

Please sign in to comment.