bunkerized-nginx/README.md

440 lines
19 KiB
Markdown
Raw Normal View History

<p align="center">
2021-03-18 18:08:42 +01:00
<img src="https://github.com/bunkerity/bunkerized-nginx/blob/master/logo.png?raw=true" width="425" />
</p>
<p align="center">
2021-06-15 16:53:37 +02:00
<img src="https://img.shields.io/badge/bunkerized--nginx-1.2.7-blue" />
2021-06-03 17:44:12 +02:00
<img src="https://img.shields.io/badge/nginx-1.20.1-blue" />
2021-04-27 17:40:33 +02:00
<img src="https://img.shields.io/github/last-commit/bunkerity/bunkerized-nginx" />
<img src="https://img.shields.io/github/workflow/status/bunkerity/bunkerized-nginx/Automatic%20test?label=automatic%20test" />
<img src="https://img.shields.io/github/workflow/status/bunkerity/bunkerized-nginx/Build%20and%20push%20bunkerized-nginx?label=docker%20build" />
2021-05-14 15:41:15 +02:00
<img src="https://img.shields.io/readthedocs/bunkerized-nginx" />
2021-04-27 17:40:33 +02:00
</p>
<p align="center">
<strong>
2021-06-03 23:37:10 +02:00
<a href="https://bunkerized-nginx.readthedocs.io">Documentation</a>
&#124;
<a href="https://github.com/bunkerity/bunkerized-nginx/tree/master/examples">Examples</a>
&#124;
2021-06-03 23:37:10 +02:00
<a href="https://www.bunkerity.com/category/bunkerized-nginx/">Blog posts</a>
&#124;
2021-06-15 16:53:37 +02:00
<a href="https://coso.me/bunkerity-chat">Community chat</a>
&#124;
2021-06-03 23:37:10 +02:00
<a href="https://coso.me/bunkerity">Follow us</a>
</strong>
2021-03-18 18:08:42 +01:00
</p>
2020-10-10 16:46:23 +02:00
nginx Docker image secure by default.
2021-05-14 15:41:15 +02:00
Avoid the hassle of following security best practices "by hand" each time you need a web server or reverse proxy. Bunkerized-nginx provides generic security configs, settings and tools so you don't need to do it yourself.
2020-09-01 23:33:21 +02:00
2020-06-07 10:13:06 +02:00
Non-exhaustive list of features :
2019-08-20 23:50:42 +02:00
- HTTPS support with transparent Let's Encrypt automation
2020-10-30 11:39:01 +01:00
- State-of-the-art web security : HTTP security headers, prevent leaks, TLS hardening, ...
2019-08-20 23:50:42 +02:00
- Integrated ModSecurity WAF with the OWASP Core Rule Set
2021-05-14 15:41:15 +02:00
- Automatic ban of strange behaviors
2020-10-17 17:02:42 +02:00
- Antibot challenge through cookie, javascript, captcha or recaptcha v3
2020-10-04 21:20:57 +02:00
- Block TOR, proxies, bad user-agents, countries, ...
- Block known bad IP with DNSBL and CrowdSec
2020-10-02 12:01:30 +02:00
- Prevent bruteforce attacks with rate limiting
- Plugins system for external security checks (e.g. : ClamAV)
2020-12-30 17:57:00 +01:00
- Easy to configure with environment variables or web UI
2020-12-07 21:20:13 +01:00
- Automatic configuration with container labels
- Docker Swarm support
2019-08-20 23:50:42 +02:00
2020-10-16 18:08:17 +02:00
Fooling automated tools/scanners :
2020-10-17 17:02:42 +02:00
<img src="https://github.com/bunkerity/bunkerized-nginx/blob/master/demo.gif?raw=true" />
2020-10-16 18:08:17 +02:00
2021-04-27 17:40:33 +02:00
You can find a live demo at https://demo-nginx.bunkerity.com, feel free to do some security tests.
2020-06-07 10:13:06 +02:00
# Table of contents
2021-04-27 17:40:33 +02:00
<details>
<summary>Click to show</summary>
2020-06-07 10:13:06 +02:00
- [Table of contents](#table-of-contents)
- [Quickstart guide](#quickstart-guide)
* [Run HTTP server with default settings](#run-http-server-with-default-settings)
2020-10-10 17:05:44 +02:00
* [In combination with PHP](#in-combination-with-php)
2020-10-22 23:23:30 +02:00
* [Run HTTPS server with automated Let's Encrypt](#run-https-server-with-automated-lets-encrypt)
2020-10-17 17:02:42 +02:00
* [As a reverse proxy](#as-a-reverse-proxy)
2020-11-22 23:39:01 +01:00
* [Behind a reverse proxy](#behind-a-reverse-proxy)
* [Multisite](#multisite)
2020-12-07 21:20:13 +01:00
* [Automatic configuration](#automatic-configuration)
* [Swarm mode](#swarm-mode)
2020-12-30 21:22:18 +01:00
* [Web UI](#web-ui)
2021-05-14 15:41:15 +02:00
- [Security tuning](#security-tuning)
- [Going further](#going-further)
- [License](#license)
- [Contributing](#contributing)
2021-04-27 17:40:33 +02:00
</details>
2020-06-02 22:39:17 +02:00
2020-06-07 10:13:06 +02:00
# Quickstart guide
2019-11-22 14:21:06 +01:00
2020-06-07 10:13:06 +02:00
## Run HTTP server with default settings
2019-11-22 14:21:06 +01:00
```shell
2020-11-22 23:39:01 +01:00
docker run -p 80:8080 -v /path/to/web/files:/www:ro bunkerity/bunkerized-nginx
2019-11-22 14:21:06 +01:00
```
2021-05-14 15:41:15 +02:00
Web files are stored in the /www directory, the container will serve files from there. Please note that *bunkerized-nginx* doesn't run as root but as an unprivileged user with UID/GID 101 therefore you should set the rights of */path/to/web/files* accordingly.
2019-11-22 14:21:06 +01:00
2020-10-10 16:46:23 +02:00
## In combination with PHP
```shell
docker network create mynet
2021-05-14 15:41:15 +02:00
```
```shell
2020-11-22 23:39:01 +01:00
docker run --network mynet \
-p 80:8080 \
2020-11-23 00:05:22 +01:00
-v /path/to/web/files:/www:ro \
2020-11-22 23:39:01 +01:00
-e REMOTE_PHP=myphp \
-e REMOTE_PHP_PATH=/app \
bunkerity/bunkerized-nginx
2021-05-14 15:41:15 +02:00
```
```shell
2020-11-22 23:39:01 +01:00
docker run --network mynet \
2020-12-07 21:20:13 +01:00
--name myphp \
2020-11-22 23:39:01 +01:00
-v /path/to/web/files:/app \
php:fpm
2020-10-10 16:46:23 +02:00
```
The `REMOTE_PHP` environment variable lets you define the address of a remote PHP-FPM instance that will execute the .php files. `REMOTE_PHP_PATH` must be set to the directory where the PHP container will find the files.
2020-06-07 10:13:06 +02:00
## Run HTTPS server with automated Let's Encrypt
2020-11-22 23:39:01 +01:00
2019-11-22 14:21:06 +01:00
```shell
2020-11-22 23:39:01 +01:00
docker run -p 80:8080 \
-p 443:8443 \
-v /path/to/web/files:/www:ro \
-v /where/to/save/certificates:/etc/letsencrypt \
-e SERVER_NAME=www.yourdomain.com \
-e AUTO_LETS_ENCRYPT=yes \
-e REDIRECT_HTTP_TO_HTTPS=yes \
bunkerity/bunkerized-nginx
2019-11-22 14:21:06 +01:00
```
2021-05-14 15:41:15 +02:00
Certificates are stored in the /etc/letsencrypt directory, you should save it on your local drive. Please note that *bunkerized-nginx* doesn't run as root but as an unprivileged user with UID/GID 101 therefore you should set the rights of */where/to/save/certificates* accordingly.
2021-03-18 18:08:42 +01:00
If you don't want your webserver to listen on HTTP add the environment variable `LISTEN_HTTP` with a *no* value (e.g. HTTPS only). But Let's Encrypt needs the port 80 to be opened so redirecting the port is mandatory.
2020-04-13 22:17:17 +02:00
Here you have three environment variables :
2020-08-01 17:47:06 +02:00
- `SERVER_NAME` : define the FQDN of your webserver, this is mandatory for Let's Encrypt (www.yourdomain.com should point to your IP address)
- `AUTO_LETS_ENCRYPT` : enable automatic Let's Encrypt creation and renewal of certificates
- `REDIRECT_HTTP_TO_HTTPS` : enable HTTP to HTTPS redirection
2020-04-13 22:17:17 +02:00
2020-11-22 23:39:01 +01:00
## As a reverse proxy
```shell
docker run -p 80:8080 \
-e USE_REVERSE_PROXY=yes \
-e REVERSE_PROXY_URL=/ \
-e REVERSE_PROXY_HOST=http://myserver:8080 \
bunkerity/bunkerized-nginx
```
This is a simple reverse proxy to a unique application. If you have more than one application you can add more REVERSE_PROXY_URL/REVERSE_PROXY_HOST by appending a suffix number like this :
```shell
docker run -p 80:8080 \
-e USE_REVERSE_PROXY=yes \
-e REVERSE_PROXY_URL_1=/app1/ \
-e REVERSE_PROXY_HOST_1=http://myapp1:3000/ \
-e REVERSE_PROXY_URL_2=/app2/ \
-e REVERSE_PROXY_HOST_2=http://myapp2:3000/ \
bunkerity/bunkerized-nginx
```
2020-10-17 17:02:42 +02:00
## Behind a reverse proxy
2020-11-22 23:39:01 +01:00
2020-10-17 17:02:42 +02:00
```shell
2020-11-22 23:39:01 +01:00
docker run -p 80:8080 \
-v /path/to/web/files:/www \
-e PROXY_REAL_IP=yes \
bunkerity/bunkerized-nginx
2020-10-17 17:02:42 +02:00
```
The `PROXY_REAL_IP` environment variable, when set to *yes*, activates the [ngx_http_realip_module](https://nginx.org/en/docs/http/ngx_http_realip_module.html) to get the real client IP from the reverse proxy.
2021-05-14 15:41:15 +02:00
See [this section](https://bunkerized-nginx.readthedocs.io/en/latest/environment_variables.html#reverse-proxy) if you need to tweak some values (trusted ip/network, header, ...).
2020-10-17 17:02:42 +02:00
2020-11-22 23:39:01 +01:00
## Multisite
By default, bunkerized-nginx will only create one server block. When setting the `MULTISITE` environment variable to *yes*, one server block will be created for each host defined in the `SERVER_NAME` environment variable.
You can set/override values for a specific server by prefixing the environment variable with one of the server name previously defined.
2020-04-13 22:17:17 +02:00
```shell
2020-11-22 23:39:01 +01:00
docker run -p 80:8080 \
-p 443:8443 \
-v /where/to/save/certificates:/etc/letsencrypt \
-e SERVER_NAME=app1.domain.com app2.domain.com \
-e MULTISITE=yes \
-e AUTO_LETS_ENCRYPT=yes \
-e REDIRECT_HTTP_TO_HTTPS=yes \
-e USE_REVERSE_PROXY=yes \
2020-12-10 21:25:39 +01:00
-e app1.domain.com_REVERSE_PROXY_URL=/ \
-e app1.domain.com_REVERSE_PROXY_HOST=http://myapp1:8000 \
-e app2.domain.com_REVERSE_PROXY_URL=/ \
-e app2.domain.com_REVERSE_PROXY_HOST=http://myapp2:8000 \
2020-11-22 23:39:01 +01:00
bunkerity/bunkerized-nginx
2020-04-13 22:17:17 +02:00
```
2020-11-22 23:39:01 +01:00
The `USE_REVERSE_PROXY` is a *global* variable that will be applied to each server block. Whereas the `app1.domain.com_*` and `app2.domain.com_*` will only be applied to the app1.domain.com and app2.domain.com server block respectively.
When serving files, the web root directory should contains subdirectories named as the servers defined in the `SERVER_NAME` environment variable. Here is an example :
2020-04-13 22:17:17 +02:00
```shell
2020-11-22 23:39:01 +01:00
docker run -p 80:8080 \
-p 443:8443 \
-v /where/to/save/certificates:/etc/letsencrypt \
-v /where/are/web/files:/www:ro \
-e SERVER_NAME=app1.domain.com app2.domain.com \
-e MULTISITE=yes \
-e AUTO_LETS_ENCRYPT=yes \
-e REDIRECT_HTTP_TO_HTTPS=yes \
-e app1.domain.com_REMOTE_PHP=php1 \
-e app1.domain.com_REMOTE_PHP_PATH=/app \
-e app2.domain.com_REMOTE_PHP=php2 \
-e app2.domain.com_REMOTE_PHP_PATH=/app \
bunkerity/bunkerized-nginx
2020-04-13 22:17:17 +02:00
```
2020-11-22 23:39:01 +01:00
The */where/are/web/files* directory should have a structure like this :
```shell
/where/are/web/files
├── app1.domain.com
2021-05-14 15:41:15 +02:00
│ └── index.php
│ └── ...
2020-11-22 23:39:01 +01:00
└── app2.domain.com
└── index.php
└── ...
```
2019-11-22 14:21:06 +01:00
2020-12-07 21:20:13 +01:00
## Automatic configuration
2020-12-30 17:57:00 +01:00
The downside of using environment variables is that you need to recreate a new container each time you want to add or remove a web service. An alternative is to use the *bunkerized-nginx-autoconf* image which listens for Docker events and "automagically" generates the configuration.
2020-12-07 21:20:13 +01:00
2020-12-30 17:57:00 +01:00
First we need a volume that will store the configurations :
```shell
docker volume create nginx_conf
```
Then we run bunkerized-nginx with the `bunkerized-nginx.AUTOCONF` label, mount the created volume at /etc/nginx and set some default configurations for our services (e.g. : automatic Let's Encrypt and HTTP to HTTPS redirect) :
2020-12-07 21:20:13 +01:00
```shell
docker network create mynet
docker run -p 80:8080 \
-p 443:8443 \
--network mynet \
-v /where/to/save/certificates:/etc/letsencrypt \
-v /where/are/web/files:/www:ro \
2020-12-30 17:57:00 +01:00
-v nginx_conf:/etc/nginx \
2020-12-07 21:20:13 +01:00
-e SERVER_NAME= \
-e MULTISITE=yes \
-e AUTO_LETS_ENCRYPT=yes \
-e REDIRECT_HTTP_TO_HTTPS=yes \
2021-05-14 15:41:15 +02:00
-l bunkerized.nginx.AUTOCONF \
2020-12-07 21:20:13 +01:00
bunkerity/bunkerized-nginx
```
2020-12-30 21:22:18 +01:00
When setting `SERVER_NAME` to nothing bunkerized-nginx won't create any server block (in case we only want automatic configuration).
2020-12-07 21:20:13 +01:00
2020-12-30 21:22:18 +01:00
Once bunkerized-nginx is created, let's setup the autoconf container :
2020-12-30 17:57:00 +01:00
```shell
docker run -v /var/run/docker.sock:/var/run/docker.sock:ro \
-v nginx_conf:/etc/nginx \
bunkerity/bunkerized-nginx-autoconf
```
We can now create a new container and use labels to dynamically configure bunkerized-nginx. Labels for automatic configuration are the same as environment variables but with the "bunkerized-nginx." prefix.
Here is a PHP example :
2020-12-07 21:20:13 +01:00
```shell
docker run --network mynet \
--name myapp \
-v /where/are/web/files/app.domain.com:/app \
-l bunkerized-nginx.SERVER_NAME=app.domain.com \
-l bunkerized-nginx.REMOTE_PHP=myapp \
-l bunkerized-nginx.REMOTE_PHP_PATH=/app \
2020-12-30 17:57:00 +01:00
php:fpm
```
And a reverse proxy example :
```shell
docker run --network mynet \
--name anotherapp \
-l bunkerized-nginx.SERVER_NAME=app2.domain.com \
-l bunkerized-nginx.USE_REVERSE_PROXY=yes \
-l bunkerized-nginx.REVERSE_PROXY_URL=/ \
-l bunkerized-nginx.REVERSE_PROXY_HOST=http://anotherapp \
2020-12-30 17:57:00 +01:00
tutum/hello-world
```
## Swarm mode
Automatic configuration through labels is also supported in swarm mode. The *bunkerized-nginx-autoconf* is used to listen for Swarm events (e.g. service create/rm) and "automagically" edit configurations files and reload nginx.
As a use case we will assume the following :
- Some managers are also workers (they will only run the *autoconf* container for obvious security reasons)
- The bunkerized-nginx service will be deployed on all workers (global mode) so clients can connect to each of them (e.g. load balancing, CDN, edge proxy, ...)
- There is a shared folder mounted on managers and workers (e.g. NFS, GlusterFS, CephFS, ...)
Let's start by creating the network to allow communications between our services :
```shell
docker network create -d overlay mynet
```
We can now create the *autoconf* service that will listen to swarm events :
```shell
docker service create --name autoconf \
--network mynet \
--mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock,ro \
--mount type=bind,source=/shared/confs,destination=/etc/nginx \
--mount type=bind,source=/shared/letsencrypt,destination=/etc/letsencrypt \
--mount type=bind,source=/shared/acme-challenge,destination=/acme-challenge \
-e SWARM_MODE=yes \
-e API_URI=/ChangeMeToSomethingHardToGuess \
--replicas 1 \
--constraint node.role==manager \
bunkerity/bunkerized-nginx-autoconf
```
**You need to change `API_URI` to something hard to guess since there is no other security mechanism to protect the API at the moment.**
When *autoconf* is created, it's time for the *bunkerized-nginx* service to be up :
```shell
docker service create --name nginx \
--network mynet \
-p published=80,target=8080,mode=host \
-p published=443,target=8443,mode=host \
--mount type=bind,source=/shared/confs,destination=/etc/nginx \
--mount type=bind,source=/shared/letsencrypt,destination=/etc/letsencrypt,ro \
--mount type=bind,source=/shared/acme-challenge,destination=/acme-challenge,ro \
--mount type=bind,source=/shared/www,destination=/www,ro \
-e SWARM_MODE=yes \
-e USE_API=yes \
-e API_URI=/ChangeMeToSomethingHardToGuess \
-e MULTISITE=yes \
-e SERVER_NAME= \
-e AUTO_LETS_ENCRYPT=yes \
-e REDIRECT_HTTP_TO_HTTPS=yes \
-l bunkerized-nginx.AUTOCONF \
--mode global \
--constraint node.role==worker \
bunkerity/bunkerized-nginx
```
The `API_URI` value must be the same as the one specified for the *autoconf* service.
We can now create a new service and use labels to dynamically configure bunkerized-nginx. Labels for automatic configuration are the same as environment variables but with the "bunkerized-nginx." prefix.
Here is a PHP example :
```shell
docker service create --name myapp \
--network mynet \
--mount type=bind,source=/shared/www/app.domain.com,destination=/app \
-l bunkerized-nginx.SERVER_NAME=app.domain.com \
-l bunkerized-nginx.REMOTE_PHP=myapp \
-l bunkerized-nginx.REMOTE_PHP_PATH=/app \
--constraint node.role==worker \
php:fpm
```
And a reverse proxy example :
```shell
docker service create --name anotherapp \
--network mynet \
-l bunkerized-nginx.SERVER_NAME=app2.domain.com \
-l bunkerized-nginx.USE_REVERSE_PROXY=yes \
-l bunkerized-nginx.REVERSE_PROXY_URL=/ \
-l bunkerized-nginx.REVERSE_PROXY_HOST=http://anotherapp \
--constraint node.role==worker \
tutum/hello-world
```
2020-12-30 17:57:00 +01:00
## Web UI
A dedicated image, *bunkerized-nginx-ui*, lets you manage bunkerized-nginx instances and services configurations through a web user interface. This feature is still in beta, feel free to open a new issue if you find a bug and/or you have an idea to improve it.
First we need a volume that will store the configurations and a network because bunkerized-nginx will be used as a reverse proxy for the web UI :
2020-12-30 17:57:00 +01:00
```shell
docker volume create nginx_conf
docker network create mynet
```
Let's create the bunkerized-nginx-ui container that will host the web UI behind bunkerized-nginx :
```shell
docker run --network mynet \
--name myui \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v nginx_conf:/etc/nginx \
-e ABSOLUTE_URI=https://admin.domain.com/webui/ \
bunkerity/bunkerized-nginx-ui
2020-12-30 17:57:00 +01:00
```
You will need to edit the `ABSOLUTE_URI` environment variable to reflect your actual URI of the web UI.
We can now setup the bunkerized-nginx instance with the `bunkerized-nginx.UI` label and a reverse proxy configuration for our web UI :
2020-12-30 17:57:00 +01:00
```shell
docker network create mynet
docker run -p 80:8080 \
-p 443:8443 \
--network mynet \
-v nginx_conf:/etc/nginx \
-v /where/are/web/files:/www:ro \
-v /where/to/save/certificates:/etc/letsencrypt \
-e SERVER_NAME=admin.domain.com \
-e MULTISITE=yes \
-e AUTO_LETS_ENCRYPT=yes \
-e REDIRECT_HTTP_TO_HTTPS=yes \
-e DISABLE_DEFAULT_SERVER=yes \
2021-06-02 17:47:00 +02:00
-e admin.domain.com_USE_MODSECURITY=no \
2020-12-30 17:57:00 +01:00
-e admin.domain.com_SERVE_FILES=no \
-e admin.domain.com_USE_AUTH_BASIC=yes \
-e admin.domain.com_AUTH_BASIC_USER=admin \
-e admin.domain.com_AUTH_BASIC_PASSWORD=password \
-e admin.domain.com_USE_REVERSE_PROXY=yes \
-e admin.domain.com_REVERSE_PROXY_URL=/webui/ \
-e admin.domain.com_REVERSE_PROXY_HOST=http://myui:5000/ \
-l bunkerized-nginx.UI \
2020-12-07 21:20:13 +01:00
bunkerity/bunkerized-nginx
```
2021-06-02 17:47:00 +02:00
The `AUTH_BASIC` environment variables let you define a login/password that must be provided before accessing to the web UI. At the moment, there is no authentication mechanism integrated into bunkerized-nginx-ui so **using auth basic with a strong password coupled with a "hard to guess" URI is strongly recommended**.
2020-12-30 17:57:00 +01:00
Web UI should now be accessible from https://admin.domain.com/webui/.
2020-12-07 21:20:13 +01:00
2021-05-14 15:41:15 +02:00
# Security tuning
2021-05-14 15:41:15 +02:00
bunkerized-nginx comes with a set of predefined security settings that you can (and you should) tune to meet your own use case. We recommend you to read the [security tuning](https://bunkerized-nginx.readthedocs.io/en/latest/security_tuning.html) section of the documentation.
2021-04-09 16:22:31 +02:00
2021-05-14 15:41:15 +02:00
# Going further
2021-05-14 15:41:15 +02:00
- [Official documentation](https://bunkerized-nginx.readthedocs.io/)
- [Full concrete examples](https://github.com/bunkerity/bunkerized-nginx/tree/master/examples)
- [Tutorials in our blog](https://www.bunkerity.com/blog)
# License
This project is licensed under the terms of the [GNU Affero General Public License (AGPL) version 3](https://github.com/bunkerity/bunkerized-nginx/blob/master/LICENSE.md).
# Contributing
If you would like to contribute to the project you can read the [contributing guidelines](https://github.com/bunkerity/bunkerized-nginx/blob/master/CONTRIBUTING.md) to get started.