refactor, cleanup and strip off the nginx

This commit is contained in:
Andy 2020-05-17 22:43:54 +02:00
parent 8ad95a56e9
commit 60b596ddb2
Signed by: arno
GPG Key ID: 9076D5E6B31AE99C
14 changed files with 114 additions and 356 deletions

View File

@ -1,70 +1,49 @@
FROM alpine:3.11 FROM alpine:3.11
MAINTAINER PrivateBin <support@privatebin.org> LABEL maintainer="andrey.arapov@nixaid.com"
ENV RELEASE 1.3.4 ENV RELEASE 1.3.4
ENV PBURL https://github.com/PrivateBin/PrivateBin/ 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 \ RUN \
# Install dependencies # 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 \ php7-opcache php7-pdo_mysql php7-pdo_pgsql tzdata \
&& apk upgrade --no-cache \ && 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 # Install PrivateBin
&& export GNUPGHOME="$(mktemp -d)" \ && export GNUPGHOME="$(mktemp -d)" \
&& gpg2 --list-public-keys || /bin/true \ && gpg2 --list-public-keys || /bin/true \
&& wget -qO - https://privatebin.info/key/release.asc | gpg2 --import - \ && wget -qO - https://privatebin.info/key/release.asc | gpg2 --import - \
&& rm -rf /var/www/* \
&& cd /tmp \ && cd /tmp \
&& wget -qO ${RELEASE}.tar.gz.asc ${PBURL}releases/download/${RELEASE}/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 \ && wget -q ${PBURL}archive/${RELEASE}.tar.gz \
&& gpg2 --verify ${RELEASE}.tar.gz.asc \ && gpg2 --verify ${RELEASE}.tar.gz.asc \
&& mkdir /var/www \
&& cd /var/www \ && cd /var/www \
&& tar -xzf /tmp/${RELEASE}.tar.gz --strip 1 \ && tar -xzf /tmp/${RELEASE}.tar.gz --strip 1 \
&& rm *.md cfg/conf.sample.php \ && rm *.md cfg/conf.sample.php \
&& mv cfg lib tpl vendor /srv \ && mv cfg lib tpl vendor /srv \
&& mkdir -p /srv/data \ && mkdir /srv/data \
&& sed -i "s#define('PATH', '');#define('PATH', '/srv/');#" index.php \ && 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/* \ && rm -rf "${GNUPGHOME}" /tmp/* \
&& apk del gnupg && apk del gnupg
# 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
# 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
# Copy the php-fpm & nginx configs
COPY etc/ /etc/ COPY etc/ /etc/
WORKDIR /var/www WORKDIR $DATA
USER nobody:www-data USER $USER
CMD php-fpm7 -F
# mark dirs as volumes that need to be writable, allows running the container --read-only
VOLUME /run /srv/data /tmp /var/lib/nginx/tmp
EXPOSE 8080
ENTRYPOINT ["/init"]

163
README.md
View File

@ -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 ```
version: '3.3'
Assuming you have docker successfully installed and internet access, you can fetch and run the image from the docker hub like this: services:
privatebin-fpm:
image: yourrepo/privatebin:1.3.4
restart: always
networks:
- backend
volumes:
- privatebin_html:/var/www
- privatebin_nginx:/etc/nginx/conf.d
```bash privatebin-nginx:
docker run -d --restart="always" --read-only -p 8080:8080 -v privatebin-data:/srv/data privatebin/nginx-fpm-alpine 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
``` ```
The parameters in detail: ## updating the image
- `-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. Update and reset the containers
- `-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
``` ```
docker-compose stop privatebin-fpm privatebin-nginx
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. docker-compose rm -f privatebin-fpm privatebin-nginx
docker volume rm srv_privatebin_html srv_privatebin_nginx
### Adjusting nginx or php-fpm settings rm -rf /srv/data/privatebin/{html,nginx}
mkdir -p /srv/data/privatebin/{html,nginx}
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. docker pull yourrepo/privatebin:1.3.4
docker-compose up -d
### 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`
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:
```bash
docker build -t privatebin/nginx-fpm-alpine .
```
### 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.

View File

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

View File

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

View File

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

View File

@ -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 "";
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1 +0,0 @@
/etc/nginx/sites-available/site.conf

View File

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

View File

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

View File

@ -1,2 +0,0 @@
#!/usr/bin/execlineb -P
/usr/sbin/nginx

View File

@ -1,2 +0,0 @@
#!/usr/bin/execlineb -P
/usr/sbin/php-fpm7