Security tuning¶
bunkerized-nginx comes with a set of predefined security settings that you can (and you should) tune to meet your own use case.
Miscellaneous¶
Here is a list of miscellaneous environment variables related more or less to security :
MAX_CLIENT_SIZE=10m
: maximum size of client bodyALLOWED_METHODS=GET|POST|HEAD
: list of HTTP methods that clients are allowed to useDISABLE_DEFAULT_SERVER=no
: enable/disable the default server (i.e. : should your server respond to unknown Host header ?)SERVER_TOKENS=off
: enable/disable sending the version number of nginx
HTTPS¶
Settings¶
Here is a list of environment variables and the corresponding default value related to HTTPS :
LISTEN_HTTP=yes
: you can set it tono
if you want to disable HTTP accessREDIRECT_HTTP_TO_HTTPS=no
: enable/disable HTTP to HTTPS redirectionHTTPS_PROTOCOLS=TLSv1.2 TLSv1.3
: list of TLS versions to useHTTP2=yes
: enable/disable HTTP2 when HTTPS is enabledCOOKIE_AUTO_SECURE_FLAG=yes
: enable/disable adding Secure flag when HTTPS is enabledSTRICT_TRANSPORT_SECURITY=max-age=31536000
: force users to visit the website in HTTPS (more info here)
Let’s Encrypt¶
Using Let’s Encrypt with the AUTO_LETS_ENCRYPT=yes
environment variable is the easiest way to add HTTPS supports to your web services if they are connected to internet and you have public DNS A record(s).
You can also set the EMAIL_LETS_ENCRYPT
environment variable if you want to receive notifications from Let’s Encrypt like expiration alerts.
Custom certificate(s)¶
If you have security constraints (e.g., local network, custom PKI, …) you can use custom certificates of your choice and tell bunkerized-nginx to use them with the following environment variables :
USE_CUSTOM_HTTPS=yes
CUSTOM_HTTPS_CERT=/path/inside/container/to/cert.pem
CUSTOM_HTTPS_KEY=/path/inside/container/to/key.pem
Here is a an example on how to use custom certificates :
$ ls /etc/ssl/my-web-app
cert.pem key.pem
$ docker run -p 80:8080 \
-p 443:8443 \
-v /etc/ssl/my-web-app:/certs:ro \
-e USE_CUSTOM_HTTPS=yes \
-e CUSTOM_HTTPS_CERT=/certs/cert.pem \
-e CUSTOM_HTTPS_KEY=/certs/key.pem \
...
bunkerity/bunkerized-nginx
Please note that if you have one or more intermediate certificate(s) in your chain of trust, you will need to provide the bundle to CUSTOM_HTTPS_CERT
(more info here).
You can reload the certificate(s) (i.e., in case of a renewal) by sending a reload order to bunkerized-nginx.
Docker reload :
docker kill --signal=SIGHUP my-container
Swarm and Kubernetes reload (repeat for each node) :
$ curl http://node-local-ip:80/api-uri/reload
Linux reload :
$ /usr/sbin/nginx -s reload
Self-signed certificate¶
This method is not recommended in production but can be used to quickly deploy HTTPS for testing purposes. Just use the GENERATE_SELF_SIGNED_SSL=yes
environment variable and bunkerized-nginx will generate a self-signed certificate for you :
$ docker run -p 80:8080 \
-p 443:8443 \
-e GENERATE_SELF_SIGNED_SSL=yes \
...
bunkerity/bunkerized-nginx
Headers¶
Some important HTTP headers related to client security are sent with a default value. Sometimes it can break a web application or can be tuned to provide even more security. The complete list is available here.
You can also remove headers (e.g., too verbose ones) by using the REMOVE_HEADERS
environment variable which takes a list of header name separated with space (default value = Server X-Powered-By X-AspNet-Version X-AspNetMvc-Version
).
If you want to keep your application headers and tell bunkerized-nginx to not override it, just set the corresponding environment variable to an empty value (e.g., CONTENT_SECURITY_POLICY=
, PERMISSIONS_POLICY=
, …).
ModSecurity¶
ModSecurity is integrated and enabled by default alongside the OWASP Core Rule Set within bunkerized-nginx. To change this behaviour you can use the USE_MODSECURITY=no
or USE_MODSECURITY_CRS=no
environment variables.
We strongly recommend to keep both ModSecurity and the OWASP Core Rule Set enabled. The only downsides are the false positives that may occur. But they can be fixed easily and the CRS team maintains a list of exclusions for common application (e.g., wordpress, nextcloud, drupal, cpanel, …).
Tuning the CRS with bunkerized-nginx is pretty simple : you can add configuration before and after the rules are loaded. You just need to mount your .conf files into the /modsec-crs-confs
(before CRS is loaded) and /modsec-confs
(after CRS is loaded) volumes. If you are using Linux integration the special folders are /opt/bunkerized-nginx/modsec-confs
and /opt/bunkerized-nginx/modsec-crs-confs
.
Here is a Docker example to illustrate it :
$ cat /data/exclusions-crs/wordpress.conf
SecAction \
"id:900130,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.crs_exclusions_wordpress=1"
$ cat /data/tuning-crs/remove-false-positives.conf
SecRule REQUEST_FILENAME "/wp-admin/admin-ajax.php" "id:1,ctl:ruleRemoveByTag=attack-xss,ctl:ruleRemoveByTag=attack-rce"
SecRule REQUEST_FILENAME "/wp-admin/options.php" "id:2,ctl:ruleRemoveByTag=attack-xss"
SecRule REQUEST_FILENAME "^/wp-json/yoast" "id:3,ctl:ruleRemoveById=930120"
$ docker run -p 80:8080 \
-p 443:8443 \
-v /data/exclusions-crs:/modsec-crs-confs:ro \
-v /data/tuning-crs:/modsec-confs:ro \
...
bunkerity/bunkerized-nginx
Bad behaviors detection¶
When attackers search for and/or exploit vulnerabilities they might generate some suspicious HTTP status codes that a “regular” user won’t generate within a period of time. If we detect that kind of behavior we can ban the offending IP address and force the attacker to come with a new one.
That kind of security measure is implemented and enabled by default in bunkerized-nginx. Here is the list of the related environment variables and their default value :
USE_BAD_BEHAVIOR=yes
: enable/disable “bad behavior” detection and automatic ban of IPBAD_BEHAVIOR_STATUS_CODES=400 401 403 404 405 429 444
: the list of HTTP status codes considered as “suspicious”BAD_BEHAVIOR_THRESHOLD=10
: the number of “suspicious” HTTP status codes required before we ban the corresponding IP addressBAD_BEHAVIOR_BAN_TIME=86400
: the duration time (in seconds) of the banBAD_BEHAVIOR_COUNT_TIME=60
: the duration time (in seconds) to wait before resetting the counter of “suspicious” HTTP status codes for a given IP
Antibot challenge¶
Attackers will certainly use automated tools to exploit/find some vulnerabilities on your web services. One countermeasure is to challenge the users to detect if they look like a bot. It might be effective against script kiddies or “lazy” attackers.
You can use the USE_ANTIBOT
environment variable to add that kind of checks whenever a new client is connecting. The available challenges are : cookie
, javascript
, captcha
and recaptcha
. More info here.
External blacklists¶
Distributed¶
This feature is in beta and will be improved regularly.
You can benefit from a distributed blacklist shared among all of the bunkerized-nginx users.
Each time a bunkerized-nginx instance detect a bad request, the offending IP is sent to a remote API and will enrich a database. An extract of the top malicious IP is downloaded on a periodic basis and integrated into bunkerized-nginx as a blacklist.
This feature is controlled with the USE_REMOTE_API=yes
environment variable.
To avoid poisoning, in addition to the various security checks made by the API we only mark IP as bad in the database if it has been seen by one of our honeypots under our control.
DNSBL¶
Automatic checks on external DNS BlackLists are enabled by default with the USE_DNSBL=yes
environment variable. The list of DNSBL zones is also configurable, you just need to edit the DNSBL_LIST
environment variable which contains the following value by default bl.blocklist.de problems.dnsbl.sorbs.net sbl.spamhaus.org xbl.spamhaus.org
.
User-Agents¶
Sometimes script kiddies or lazy attackers don’t put a “legitimate” value inside the User-Agent HTTP header so we can block them. This is controlled with the BLOCK_USER_AGENT=yes
environment variable. The blacklist is composed of two files from here and here.
If a legitimate User-Agent is blacklisted, you can use the WHITELIST_USER_AGENT
while still keeping the BLOCK_USER_AGENT=yes
(more info here).
TOR exit nodes¶
Blocking TOR exit nodes might not be a good decision depending on your use case. We decided to enable it by default with the BLOCK_TOR_EXIT_NODE=yes
environment variable. If privacy is a concern for you and/or your clients, you can override the default value (i.e : BLOCK_TOR_EXIT_NODE=no
).
Please note that you have a concrete example on how to use bunkerized-nginx with a .onion hidden service here.
Proxies¶
This list contains IP addresses and networks known to be open proxies (downloaded from here). Unless privacy is important for you and/or your clients, you should keep the default environment variable BLOCK_PROXIES=yes
.
Abusers¶
This list contains IP addresses and networks known to be abusing (downloaded from here). You can control this feature with the BLOCK_ABUSERS
environment variable (default : yes
).
Referrers¶
This list contains bad referrers domains known for spamming (downloaded from here). If one value is found inside the Referer HTTP header, request will be blocked. You can control this feature with the BLOCK_REFERRER
environment variable (default = yes
).
Limiting¶
Requests¶
To limit bruteforce attacks or rate limit access to your API you can use the “request limit” feature so attackers will be limited to X request(s) within a period of time for the same resource. That kind of protection might be useful against other attacks too (e.g., blind SQL injection).
Here is the list of related environment variables and their default value :
USE_LIMIT_REQ=yes
: enable/disable request limitingLIMIT_REQ_URL=
: the URL you want to protect, use/
to apply the limit for all URLLIMIT_REQ_RATE=1r/s
: the rate to apply for the resource, valid period are :s
(second),m
(minute),h
(hour) andd
(day)`LIMIT_REQ_BURST=5 : the number of request tu put in a queue before effectively rejecting requests
LIMIT_REQ_DELAY=1
: the number of seconds to wait before we proceed requests in queue
Please note that you can apply different rate to different URL by appending a number as suffix (more info here).
Connections¶
Opening too many connections from the same IP address might be considered as suspicious (unless it’s a shared IP and everyone is sending requests to your web service). It can be a dos/ddos attempt too. Bunkerized-nginx levarages the ngx_http_conn_module from nginx to prevent users opening too many connections.
Here is the list of related environment variables and their default value :
USE_LIMIT_CONN=yes
: enable disable connection limitingLIMIT_CONN_MAX=50
: maximum number of connections per IP
Country¶
If the location of your clients is known, you may want to add another security layer by whitelisting or blacklisting some countries. You can use the BLACKLIST_COUNTRY
or WHITELIST_COUNTRY
environment variables depending on your approach. They both take a list of 2 letters country code separated with space.
Authentication¶
You can quickly protect sensitive resources (e.g. : admin panels) by requiring HTTP authentication. Here is the list of related environment variables and their default value :
USE_AUTH_BASIC=no
: enable/disable auth basicAUTH_BASIC_LOCATION=sitewide
: location of the sensitive resource (e.g./admin
) orsitewide
to force authentication on the whole serviceAUTH_BASIC_USER=changeme
: the username requiredAUTH_BASIC_PASSWORD=changeme
: the password requiredAUTH_BASIC_TEXT=Restricted area
: the text that will be displayed to the user
Please note that bunkerized-nginx also supports Authelia for authentication (see the corresponding environment variables and a full example).
Whitelisting¶
Adding extra security can sometimes trigger false positives. Also, it might be not useful to do the security checks for specific clients because we decided to trust them. Bunkerized-nginx supports two types of whitelist : by IP address and by reverse DNS.
Here is the list of related environment variables and their default value :
USE_WHITELIST_IP=yes
: enable/disable whitelisting by IP addressWHITELIST_IP_LIST=23.21.227.69 40.88.21.235 50.16.241.113 50.16.241.114 50.16.241.117 50.16.247.234 52.204.97.54 52.5.190.19 54.197.234.188 54.208.100.253 54.208.102.37 107.21.1.8
: list of IP addresses and/or network CIDR blocks to whitelist (default contains the IP addresses of the DuckDuckGo crawler)USE_WHITELIST_REVERSE=yes
: enable/disable whitelisting by reverse DNSWHITELIST_REVERSE_LIST=.googlebot.com .google.com .search.msn.com .crawl.yahoot.net .crawl.baidu.jp .crawl.baidu.com .yandex.com .yandex.ru .yandex.net
: the list of reverse DNS suffixes to trust (default contains the list of major search engines crawlers)
Blacklisting¶
Sometimes it isn’t necessary to spend some resources for a particular client because we know for sure that he is malicious. Bunkerized-nginx nginx supports two types of blacklisting : by IP address and by reverse DNS.
Here is the list of related environment variables and their default value :
USE_BLACKLIST_IP=yes
: enable/disable blacklisting by IP addressBLACKLIST_IP_LIST=
: list of IP addresses and/or network CIDR blocks to blacklistUSE_BLACKLIST_REVERSE=yes
: enable/disable blacklisting by reverse DNSBLACKLIST_REVERSE_LIST=.shodan.io
: the list of reverse DNS suffixes to never trust
Plugins¶
Some security features can be added through the plugins system (e.g., ClamAV, CrowdSec, …). You will find more info in the plugins section.
Container hardening¶
You will find a ready to use docker-compose.yml file focused on container hardening here.
Drop capabilities¶
By default, bunkerized-nginx runs as non-root user inside the container and should not use any of the default capabilities allowed by Docker. You can safely remove all capabilities to harden the container :
docker run ... --drop-cap=all ... bunkerity/bunkerized-nginx
No new privileges¶
Bunkerized-nginx should never tries to gain additional privileges through setuid/setgid executables. You can safely add the no-new-privileges security configuration when creating the container :
docker run ... --security-opt no-new-privileges ... bunkerity/bunkerized-nginx
Read-only¶
Since the locations where bunkerized-nginx needs to write are known, we can run the container with a read-only root file system and only allow writes to specific locations by adding volumes and a tmpfs mount :
docker run ... --read-only --tmpfs /tmp -v cache-vol:/cache -v conf-vol:/etc/nginx -v /path/to/web/files:/www:ro -v /where/to/store/certificates:/etc/letsencrypt bunkerity/bunkerized-nginx
User namespace remap¶
Another hardening trick is user namespace remapping : it allows you to map the UID/GID of users inside a container to another UID/GID on the host. For example, you can map the user nginx with UID/GID 101 inside the container to a non-existent user with UID/GID 100101 on the host.
Let’s assume you have the /etc/subuid and /etc/subgid files like this :
user:100000:65536
It means that everything done inside the container will be remapped to UID/GID 100101 (100000 + 101) on the host.
Please note that you must set the rights on the volumes (e.g. : /etc/letsencrypt, /www, …) according to the remapped UID/GID :
$ chown root:100101 /path/to/letsencrypt
$ chmod 770 /path/to/letsencrypt
$ docker run ... -v /path/to/letsencrypt:/etc/letsencrypt ... bunkerity/bunkerized-nginx