diff --git a/Dockerfile b/Dockerfile index d93095d..ba0a556 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,70 +1,49 @@ FROM alpine:3.11 -MAINTAINER PrivateBin +LABEL maintainer="andrey.arapov@nixaid.com" 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/ -ENV S6_READ_ONLY_ROOT 1 RUN \ # Install dependencies - apk add --no-cache gnupg nginx php7-fpm php7-json php7-gd \ + apk add --no-cache gnupg 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 && export GNUPGHOME="$(mktemp -d)" \ && gpg2 --list-public-keys || /bin/true \ && wget -qO - https://privatebin.info/key/release.asc | gpg2 --import - \ - && rm -rf /var/www/* \ && cd /tmp \ && 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 \ + && mkdir /var/www \ && cd /var/www \ && tar -xzf /tmp/${RELEASE}.tar.gz --strip 1 \ && rm *.md cfg/conf.sample.php \ && mv cfg lib tpl vendor /srv \ - && mkdir -p /srv/data \ + && mkdir /srv/data \ && sed -i "s#define('PATH', '');#define('PATH', '/srv/');#" index.php \ -# Install s6 overlay for service management - && wget -qO - https://keybase.io/justcontainers/key.asc | gpg2 --import - \ - && cd /tmp \ - && 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 \ - /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 -COPY etc/ /etc/ - -WORKDIR /var/www -USER nobody:www-data +# nginx group id must match to the nginx group id of the container running nginx (e.g. "nginx:mainline-alpine") +ENV NGINXGID 101 +RUN addgroup -S -g $NGINXGID nginx -# mark dirs as volumes that need to be writable, allows running the container --read-only -VOLUME /run /srv/data /tmp /var/lib/nginx/tmp +# Create the application user under which PHP-FPM will run +ENV USER user +ENV UID 1000 +ENV HOME /home/$USER +ENV DATA /var/www +RUN adduser -D -u $UID -h $HOME -s /bin/true $USER && \ + mkdir -p $DATA && \ + chown -Rh $USER:$NGINXGID $DATA /srv -EXPOSE 8080 +# Copy the php-fpm & nginx configs +COPY etc/ /etc/ -ENTRYPOINT ["/init"] +WORKDIR $DATA +USER $USER +CMD php-fpm7 -F diff --git a/README.md b/README.md index d50333b..fcde58b 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,63 @@ -[![Build Status](https://drone.nixaid.com/api/badges/arno/privatebin/status.svg)](https://drone.nixaid.com/arno/privatebin) +# PrivateBin in docker -# PrivateBin on nginx, php-fpm & alpine +## docker-compose.yml example -**PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin) where the server has zero knowledge of pasted data. Data is encrypted and decrypted in the browser using 256bit AES in [Galois Counter mode](https://en.wikipedia.org/wiki/Galois/Counter_Mode). +This will automatically populate the data under the /srv/data/privatebin/html and /srv/data/privatebin/nginx directories. -This repository contains the Dockerfile and resources needed to create a docker image with a pre-installed PrivateBin instance in a secure default configuration. The images are based on the docker hub alpine image, extended with the GD module required to generate discussion avatars and the Nginx webserver to serve static JavaScript libraries, CSS & the logos. All logs of php-fpm and Nginx (access & errors) are forwarded to docker logs. +Make sure to create these directories first. -## Running the image - -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: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: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:8080 -v conf.php:/srv/cfg/conf.php:ro -v privatebin-data:/srv/data privatebin/nginx-fpm-alpine +version: '3.3' + +services: + privatebin-fpm: + image: yourrepo/privatebin:1.3.4 + restart: always + networks: + - backend + volumes: + - privatebin_html:/var/www + - privatebin_nginx:/etc/nginx/conf.d + + privatebin-nginx: + image: nginx:mainline-alpine + restart: always + networks: + - backend + volumes: + - privatebin_html:/var/www + - privatebin_nginx:/etc/nginx/conf.d + - /srv/data/privatebin/data:/srv/data + depends_on: + - privatebin-fpm + # add whatever lables/directives you need to expose your nginx container + +volumes: + privatebin_html: + driver: local + driver_opts: + type: none + device: /srv/data/privatebin/html + o: bind + + privatebin_nginx: + driver: local + driver_opts: + type: none + device: /srv/data/privatebin/nginx + o: bind ``` -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. - -### Adjusting nginx or php-fpm settings - -You can attach your own `php.ini` or nginx configuration files to the folders `/etc/php7/conf.d/` and `/etc/nginx/conf.d/` respectively. This would for example let you adjust the maximum size these two services accept for file uploads, if you need more then the default 10 MiB. - -### Timezone settings - -The image supports the use of the following two environment variables to adjust the timezone. This is most useful to ensure the logs show the correct local time. - -- `TZ` -- `PHP_TZ` +## updating the image -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. +Update and reset the containers -### 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: - -```bash -docker build -t privatebin/nginx-fpm-alpine . +docker-compose stop privatebin-fpm privatebin-nginx +docker-compose rm -f privatebin-fpm privatebin-nginx +docker volume rm srv_privatebin_html srv_privatebin_nginx +rm -rf /srv/data/privatebin/{html,nginx} +mkdir -p /srv/data/privatebin/{html,nginx} +docker pull yourrepo/privatebin:1.3.4 +docker-compose up -d ``` - -### Behind the scenes - -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. - -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. - -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. - diff --git a/etc/nginx/conf.d/access_log.conf b/etc/nginx/conf.d/access_log.conf deleted file mode 100644 index a54704b..0000000 --- a/etc/nginx/conf.d/access_log.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Log access to this file -# This is only used when you don't override it on a server{} level -access_log /dev/stdout main; - -# Note: Feel free to overwrite this file if you want a custom logging format! diff --git a/etc/nginx/conf.d/cache-file-descriptors.conf b/etc/nginx/conf.d/cache-file-descriptors.conf deleted file mode 100644 index ed312c0..0000000 --- a/etc/nginx/conf.d/cache-file-descriptors.conf +++ /dev/null @@ -1,19 +0,0 @@ -# This tells Nginx to cache open file handles, "not found" errors, metadata about files and their permissions, etc. -# -# The upside of this is that Nginx can immediately begin sending data when a popular file is requested, -# and will also know to immediately send a 404 if a file is missing on disk, and so on. -# -# However, it also means that the server won't react immediately to changes on disk, which may be undesirable. -# -# In the below configuration, inactive files are released from the cache after 20 seconds, whereas -# active (recently requested) files are re-validated every 30 seconds. -# -# Descriptors will not be cached unless they are used at least 2 times within 20 seconds (the inactive time). -# -# A maximum of the 1000 most recently used file descriptors can be cached at any time. -# -# Production servers with stable file collections will definitely want to enable the cache. -open_file_cache max=1000 inactive=20s; -open_file_cache_valid 30s; -open_file_cache_min_uses 2; -open_file_cache_errors on; diff --git a/etc/nginx/conf.d/compression.conf b/etc/nginx/conf.d/compression.conf deleted file mode 100644 index 26537ad..0000000 --- a/etc/nginx/conf.d/compression.conf +++ /dev/null @@ -1,56 +0,0 @@ -# Enable gzip compression. -gzip on; - -# Compression level (1-9). -# 5 is a perfect compromise between size and CPU usage, offering about -# 75% reduction for most ASCII files (almost identical to level 9). -gzip_comp_level 5; - -# Don't compress anything that's already small and unlikely to shrink much -# if at all (the default is 20 bytes, which is bad as that usually leads to -# larger files after gzipping). -gzip_min_length 256; - -# Compress data even for clients that are connecting to us via proxies, -# identified by the "Via" header (required for CloudFront). -gzip_proxied any; - -# Tell proxies to cache both the gzipped and regular version of a resource -# whenever the client's Accept-Encoding capabilities header varies; -# Avoids the issue where a non-gzip capable client (which is extremely rare -# today) would display gibberish if their proxy gave them the gzipped version. -gzip_vary on; - -# Compress all output labeled with one of the following MIME-types. -gzip_types - application/atom+xml - application/javascript - application/json - application/ld+json - application/manifest+json - application/rss+xml - application/vnd.geo+json - application/vnd.ms-fontobject - application/x-font-ttf - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/bmp - image/svg+xml - image/x-icon - text/cache-manifest - text/css - text/plain - text/vcard - text/vnd.rim.location.xloc - text/vtt - text/x-component - text/x-cross-domain-policy; - # text/html is always compressed by gzip module - -# This should be turned on if you are going to have pre-compressed copies (.gz) of -# static files available. If not it should be left off as it will cause extra I/O -# for the check. It is best if you enable this in a location{} block for -# a specific directory, or on an individual server{} level. -# gzip_static on; diff --git a/etc/nginx/conf.d/privatebin.conf b/etc/nginx/conf.d/privatebin.conf new file mode 100644 index 0000000..c24350c --- /dev/null +++ b/etc/nginx/conf.d/privatebin.conf @@ -0,0 +1,24 @@ +server { + listen 80 default_server; + + root /var/www; + index index.php index.html index.htm; + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + # since I am running in Docker + resolver 127.0.0.11 ipv6=off; + set $privatebin privatebin-fpm:9000; + + location ~ \.php$ { + fastcgi_pass $privatebin; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + + # Prevent exposing nginx + version to $_SERVER + fastcgi_param SERVER_SOFTWARE ""; + } +} diff --git a/etc/nginx/nginx.conf b/etc/nginx/nginx.conf deleted file mode 100644 index 8cb7dbc..0000000 --- a/etc/nginx/nginx.conf +++ /dev/null @@ -1,75 +0,0 @@ -# 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 -worker_processes auto; - -# Maximum number of open files per worker process. -# Should be > worker_connections. -worker_rlimit_nofile 8192; - -events { - # If you need more connections than this, you start optimizing your OS. - # That's probably the point at which you hire people who are smarter than you as this is *a lot* of requests. - # Should be < worker_rlimit_nofile. - worker_connections 8000; -} - -# Log errors and warnings to this file -# This is only used when you don't override it on a server{} level -error_log /dev/stderr warn; - -# The file storing the process ID of the main process -pid /run/nginx.pid; - -# The process is managed in the docker-env -daemon off; - -# Free some CPU cycles -timer_resolution 500ms; - -http { - # Specify MIME types for files. - include mime.types; - default_type application/octet-stream; - - # Update charset_types to match updated mime.types. - # text/html is always included by charset module. - charset_types text/css text/plain text/vnd.wap.wml application/javascript application/json application/rss+xml application/xml; - - # Include $http_x_forwarded_for within default format used in log files - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - # Hide used software - server_tokens off; - - # Default charset - charset utf-8; - - # How long to allow each connection to stay idle. - # Longer values are better for each individual client, particularly for SSL, - # but means that worker connections are tied up longer. - keepalive_timeout 20s; - - # Speed up file transfers by using sendfile() to copy directly - # between descriptors rather than using read()/write(). - # For performance reasons, on FreeBSD systems w/ ZFS - # this option should be disabled as ZFS's ARC caches - # frequently used files in RAM by default. - sendfile on; - - # Don't send out partial frames; this increases throughput - # since TCP frames are filled up before being sent out. - tcp_nopush on; - - # Allow up to 15 MiB payload, privatebin defaults to 10 MiB. - client_max_body_size 15M; - - # Load even moar configs - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*.conf; -} diff --git a/etc/nginx/sites-available/site.conf b/etc/nginx/sites-available/site.conf deleted file mode 100644 index ece77a3..0000000 --- a/etc/nginx/sites-available/site.conf +++ /dev/null @@ -1,24 +0,0 @@ -server { - listen 8080 default_server; - - root /var/www; - index index.php index.html index.htm; - - location / { - include /etc/nginx/location.d/*.conf; - try_files $uri $uri/ /index.php$is_args$args; - } - - location ~ \.php$ { - include /etc/nginx/location.d/*.conf; - fastcgi_pass unix:/run/php-fpm.sock; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - include fastcgi_params; - - # Prevent exposing nginx + version to $_SERVER - fastcgi_param SERVER_SOFTWARE ""; - } - - include /etc/nginx/server.d/*.conf; -} diff --git a/etc/nginx/sites-enabled/site.conf b/etc/nginx/sites-enabled/site.conf deleted file mode 120000 index 35141db..0000000 --- a/etc/nginx/sites-enabled/site.conf +++ /dev/null @@ -1 +0,0 @@ -/etc/nginx/sites-available/site.conf \ No newline at end of file diff --git a/etc/php7/conf.d/00-docker.ini b/etc/php7/conf.d/00-privatebin.ini similarity index 100% rename from etc/php7/conf.d/00-docker.ini rename to etc/php7/conf.d/00-privatebin.ini diff --git a/etc/php7/php-fpm.d/zz-docker.conf b/etc/php7/php-fpm.d/zz-docker.conf deleted file mode 100644 index c0f3ca9..0000000 --- a/etc/php7/php-fpm.d/zz-docker.conf +++ /dev/null @@ -1,18 +0,0 @@ -[global] -pid = /run/php-fpm7.pid -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 -pm.max_children = 5 -pm.start_servers = 2 -pm.min_spare_servers = 1 -pm.max_spare_servers = 3 diff --git a/etc/php7/php-fpm.d/zz-privatebin.conf b/etc/php7/php-fpm.d/zz-privatebin.conf new file mode 100644 index 0000000..89fec7c --- /dev/null +++ b/etc/php7/php-fpm.d/zz-privatebin.conf @@ -0,0 +1,18 @@ +[global] +daemonize = no +error_log = /dev/stderr + +[www] +user = user +group = user +listen = 0.0.0.0:9000 +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +clear_env = On +;access.log = /dev/null +;php_admin_value[upload_max_filesize] = 15M +;php_admin_value[post_max_size] = 15M +;php_admin_value[output_buffering] = 0 diff --git a/etc/services.d/nginx/run b/etc/services.d/nginx/run deleted file mode 100644 index 3d184d6..0000000 --- a/etc/services.d/nginx/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/execlineb -P -/usr/sbin/nginx diff --git a/etc/services.d/php-fpm7/run b/etc/services.d/php-fpm7/run deleted file mode 100644 index 395b320..0000000 --- a/etc/services.d/php-fpm7/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/execlineb -P -/usr/sbin/php-fpm7