diff --git a/Dockerfile b/Dockerfile index 02895c7..d93095d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM alpine:3.10 +FROM alpine:3.11 MAINTAINER PrivateBin -ENV RELEASE 1.3.1 +ENV RELEASE 1.3.4 ENV PBURL https://github.com/PrivateBin/PrivateBin/ ENV S6RELEASE v1.22.1.0 ENV S6URL https://github.com/just-containers/s6-overlay/releases/download/ @@ -10,49 +10,52 @@ ENV S6_READ_ONLY_ROOT 1 RUN \ # Install dependencies - apk add --no-cache tzdata nginx php7-fpm php7-json php7-gd \ - php7-opcache php7-pdo_mysql php7-pdo_pgsql \ + apk add --no-cache gnupg nginx php7-fpm php7-json php7-gd \ + php7-opcache php7-pdo_mysql php7-pdo_pgsql tzdata \ + && apk upgrade --no-cache \ # Remove (some of the) default nginx config && rm -f /etc/nginx.conf /etc/nginx/conf.d/default.conf /etc/php7/php-fpm.d/www.conf \ && rm -rf /etc/nginx/sites-* \ # Ensure nginx logs, even if the config has errors, are written to stderr && ln -s /dev/stderr /var/log/nginx/error.log \ # Install PrivateBin - && apk add --no-cache gnupg curl libcap \ && export GNUPGHOME="$(mktemp -d)" \ && gpg2 --list-public-keys || /bin/true \ - && curl -s https://privatebin.info/key/release.asc | gpg2 --import - \ + && wget -qO - https://privatebin.info/key/release.asc | gpg2 --import - \ && rm -rf /var/www/* \ && cd /tmp \ - && curl -Ls ${PBURL}releases/download/${RELEASE}/PrivateBin-${RELEASE}.tar.gz.asc > PrivateBin-${RELEASE}.tar.gz.asc \ - && curl -Ls ${PBURL}archive/${RELEASE}.tar.gz > PrivateBin-${RELEASE}.tar.gz \ - && gpg2 --verify PrivateBin-${RELEASE}.tar.gz.asc \ + && wget -qO ${RELEASE}.tar.gz.asc ${PBURL}releases/download/${RELEASE}/PrivateBin-${RELEASE}.tar.gz.asc \ + && wget -q ${PBURL}archive/${RELEASE}.tar.gz \ + && gpg2 --verify ${RELEASE}.tar.gz.asc \ && cd /var/www \ - && tar -xzf /tmp/PrivateBin-${RELEASE}.tar.gz --strip 1 \ + && tar -xzf /tmp/${RELEASE}.tar.gz --strip 1 \ && rm *.md cfg/conf.sample.php \ - && mv cfg /srv \ - && mv lib /srv \ - && mv tpl /srv \ - && mv vendor /srv \ + && mv cfg lib tpl vendor /srv \ && mkdir -p /srv/data \ && sed -i "s#define('PATH', '');#define('PATH', '/srv/');#" index.php \ # Install s6 overlay for service management - && curl -s https://keybase.io/justcontainers/key.asc | gpg2 --import - \ + && wget -qO - https://keybase.io/justcontainers/key.asc | gpg2 --import - \ && cd /tmp \ - && curl -Ls ${S6URL}${S6RELEASE}/s6-overlay-amd64.tar.gz.sig > s6-overlay-amd64.tar.gz.sig \ - && curl -Ls ${S6URL}${S6RELEASE}/s6-overlay-amd64.tar.gz > s6-overlay-amd64.tar.gz \ - && gpg2 --verify s6-overlay-amd64.tar.gz.sig \ - && tar -xzf s6-overlay-amd64.tar.gz -C / \ + && S6ARCH=$(uname -m) \ + && case ${S6ARCH} in \ + x86_64) S6ARCH=amd64;; \ + armv7l) S6ARCH=armhf;; \ + esac \ + && wget -q ${S6URL}${S6RELEASE}/s6-overlay-${S6ARCH}.tar.gz.sig \ + && wget -q ${S6URL}${S6RELEASE}/s6-overlay-${S6ARCH}.tar.gz \ + && gpg2 --verify s6-overlay-${S6ARCH}.tar.gz.sig \ + && tar -xzf s6-overlay-${S6ARCH}.tar.gz -C / \ # Support running s6 under a non-root user && mkdir -p /etc/services.d/nginx/supervise /etc/services.d/php-fpm7/supervise \ - && mkfifo /etc/services.d/nginx/supervise/control \ - && mkfifo /etc/services.d/php-fpm7/supervise/control \ - && mkfifo /etc/s6/services/s6-fdholderd/supervise/control \ - && setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx \ - && chown -R nobody.www-data /var/lib/nginx /var/tmp/nginx /var/www /srv/* /etc/services.d /etc/s6 /run \ + && mkfifo \ + /etc/services.d/nginx/supervise/control \ + /etc/services.d/php-fpm7/supervise/control \ + /etc/s6/services/s6-fdholderd/supervise/control \ + && adduser nobody www-data \ + && chown -R nobody.www-data /etc/services.d /etc/s6 /run /srv/* /var/lib/nginx /var/www \ # Clean up && rm -rf "${GNUPGHOME}" /tmp/* \ - && apk del gnupg curl libcap + && apk del gnupg COPY etc/ /etc/ @@ -60,8 +63,8 @@ WORKDIR /var/www USER nobody:www-data # mark dirs as volumes that need to be writable, allows running the container --read-only -VOLUME /srv/data /tmp /var/tmp/nginx /run /var/log +VOLUME /run /srv/data /tmp /var/lib/nginx/tmp -EXPOSE 80 +EXPOSE 8080 ENTRYPOINT ["/init"] diff --git a/README.md b/README.md index 4de6de0..d50333b 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,27 @@ This repository contains the Dockerfile and resources needed to create a docker Assuming you have docker successfully installed and internet access, you can fetch and run the image from the docker hub like this: ```bash -docker run -d --restart="always" --read-only -p 8080:80 -v privatebin-data:/srv/data privatebin/nginx-fpm-alpine +docker run -d --restart="always" --read-only -p 8080:8080 -v privatebin-data:/srv/data privatebin/nginx-fpm-alpine ``` The parameters in detail: - `-v privatebin-data:/srv/data` - replace `privatebin-data` with the path to the folder on your system, where the pastes and other service data should be persisted. This guarantees that your pastes aren't lost after you stop and restart the image or when you replace it. May be skipped if you just want to test the image. -- `-p 8080:80` - The Nginx webserver inside the container listens on port 80, this parameter exposes it on your system on port 8080. Be sure to use a reverse proxy for HTTPS termination in front of it for production environments. +- `-p 8080:8080` - The Nginx webserver inside the container listens on port 8080, this parameter exposes it on your system on port 8080. Be sure to use a reverse proxy for HTTPS termination in front of it in production environments. - `--read-only` - This image supports running in read-only mode. Using this reduces the attack surface slightly, since an exploit in one of the images services can't overwrite arbitrary files in the container. Only /tmp, /var/tmp, /var/run & /srv/data may be written into. - `-d` - launches the container in the background. You can use `docker ps` and `docker logs` to check if the container is alive and well. - `--restart="always"` - restart the container if it crashes, mainly useful for production setups > Note that the volume mounted must be owned by UID 65534 / GID 82. If you run the container in a docker instance with "userns-remap" you need to add your subuid/subgid range to these numbers. +> +> Note, too, that this image exposes the same service on port 80, for backwards compatibility with older versions of the image. To use port 80 with the current image, you either need to have a filesystem with extended attribute support so the nginx binary can be granted the capability to bind to ports below 1024 as non-root user or you need to start the image with user id 0 (root) using the parameter `-u 0`. ### Custom configuration In case you want to use a customized [conf.php](https://github.com/PrivateBin/PrivateBin/blob/master/cfg/conf.sample.php) file, for example one that has file uploads enabled or that uses a different template, add the file as a second volume: ```bash -docker run -d --restart="always" --read-only -p 8080:80 -v conf.php:/srv/cfg/conf.php:ro -v privatebin-data:/srv/data privatebin/nginx-fpm-alpine +docker run -d --restart="always" --read-only -p 8080:8080 -v conf.php:/srv/cfg/conf.php:ro -v privatebin-data:/srv/data privatebin/nginx-fpm-alpine ``` Note: The `Filesystem` data storage is supported out of the box. The image includes PDO modules for MySQL, PostgreSQL and SQLite, required for the `Database` one, but you still need to keep the /srv/data persisted for the server salt and the traffic limiter. @@ -47,6 +49,61 @@ The image supports the use of the following two environment variables to adjust Note: The application internally handles expiration of pastes based on a UNIX timestamp that is calculated based on the timezone set during its creation. Changing the PHP_TZ will affect this and leads to earlier (if the timezone is increased) or later (if it is decreased) expiration then expected. +### Kubernetes deployment + +Below is an example deployment for Kubernetes. + +```yaml +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: privatebin-deployment + labels: + app: privatebin +spec: + replicas: 3 + selector: + matchLabels: + run: privatebin + template: + metadata: + labels: + app: privatebin + spec: + initContainers: + - name: privatebin-volume-permissions + image: busybox + command: ['chown', '65534:82', '/mnt'] + securityContext: + runAsUser: 0 + readOnlyRootFilesystem: True + volumeMounts: + - mountPath: /mnt + name: privatebin-data + readOnly: False + containers: + - name: privatebin + image: privatebin/nginx-fpm-alpine:1.3.3 + ports: + - containerPort: 8080 + env: + - name: TZ + value: Antarctica/South_Pole + - name: PHP_TZ + value: Antarctica/South_Pole + securityContext: + runAsUser: 65534 + runAsGroup: 82 + readOnlyRootFilesystem: True + volumeMounts: + - mountPath: /srv/data + name: privatebin-data + readOnly: False +``` + +Note that the volume `privatebin-data` has to be a shared, persisted volume across all nodes, i.e. on an NFS share. It is required even when using a database, as some data is always stored in files (server salt, traffic limiters IP hashes, purge limiter time stamp). + ## Rolling your own image To reproduce the image, run: @@ -57,10 +114,11 @@ docker build -t privatebin/nginx-fpm-alpine . ### Behind the scenes -The two processes, Nginx and php-fpm, are started by supervisord, which will also try to restart the services in case they crash. +The two processes, Nginx and php-fpm, are started by s6 overlay. + +Nginx is required to serve static files and caches them, too. Requests to the index.php (which is the only PHP file exposed in the document root at /var/www) are passed to php-fpm via a socket at /run/php-fpm.sock. All other PHP files and the data are stored under /srv. -Nginx is required to serve static files and caches them, too. Requests to the index.php (which is the only PHP file exposed in the document root at /var/www) are passed on to php-fpm via fastCGI to port 9000. All other PHP files and the data are stored in /srv. +The Nginx setup supports only HTTP, so make sure that you run a reverse proxy in front of this for HTTPS offloading and reducing the attack surface on your TLS stack. The Nginx in this image is set up to deflate/gzip text content. -The Nginx setup supports only HTTP, so make sure that you run another webserver as reverse proxy in front of this for HTTPS offloading and reducing the attack surface on your TLS stack. The Nginx in this image is set up to deflate/gzip text content. +During the build of the image the PrivateBin release archive and the s6 overlay binaries are downloaded from Github. All the downloaded Alpine packages, s6 overlay binaries and the PrivateBin archive are validated using cryptographic signatures to ensure they have not been tempered with, before deploying them in the image. -During the build of the image, the opcache & GD PHP modules are compiled from source and the PrivateBin release archive is downloaded from Github. All the downloaded Alpine packages and the PrivateBin archive are validated using cryptographic signatures to ensure the have not been tempered with, before deploying them in the image. diff --git a/etc/nginx/nginx.conf b/etc/nginx/nginx.conf index 279f76d..8cb7dbc 100644 --- a/etc/nginx/nginx.conf +++ b/etc/nginx/nginx.conf @@ -1,3 +1,6 @@ +# Run as a unique, less privileged user for security reasons. +user nobody www-data; + # Sets the worker threads to the number of CPU cores available in the system for best performance. # Should be > the number of CPU cores. # Maximum number of connections = worker_processes * worker_connections @@ -19,7 +22,7 @@ events { error_log /dev/stderr warn; # The file storing the process ID of the main process -pid /var/run/nginx.pid; +pid /run/nginx.pid; # The process is managed in the docker-env daemon off; diff --git a/etc/nginx/sites-available/site.conf b/etc/nginx/sites-available/site.conf index d17ec18..4b8a7af 100644 --- a/etc/nginx/sites-available/site.conf +++ b/etc/nginx/sites-available/site.conf @@ -1,5 +1,6 @@ server { - listen 80 default_server; + listen 8080 default_server; + listen [::]:8080 default_server; root /var/www; index index.php index.html index.htm; diff --git a/etc/php7/php-fpm.d/zz-docker.conf b/etc/php7/php-fpm.d/zz-docker.conf index 2817ad1..c0f3ca9 100644 --- a/etc/php7/php-fpm.d/zz-docker.conf +++ b/etc/php7/php-fpm.d/zz-docker.conf @@ -4,7 +4,11 @@ daemonize = no error_log = /dev/stderr [www] +user = nobody +group = www-data listen = /run/php-fpm.sock +listen.owner = nobody +listen.group = www-data access.log = /dev/null clear_env = On pm = dynamic