38 Commits

Author SHA1 Message Date
bunkerity
07be626842 hotfix - fix API in autoconf swarm mode 2021-04-28 17:40:54 +02:00
bunkerity
3bb164395e hotfix - move API_WHITELIST_IP edit to lua.sh 2021-04-28 17:00:50 +02:00
bunkerity
bc2568a172 v1.2.4 - nginx 1.20.0 support 2021-04-27 17:43:38 +02:00
Bunkerity
5ec74880d8 update README for v1.2.4 2021-04-27 17:40:33 +02:00
bunkerity
f84fd7c9a2 fix permissions issues for autoconf and fix volume for ghost example 2021-04-27 16:49:45 +02:00
bunkerity
6521d7a27a fix client cache so it works in combination with reverse proxy and examples update 2021-04-27 15:31:56 +02:00
bunkerity
813607fbc3 improve crowdsec example and disable modsec logging when not necessary 2021-04-27 11:21:30 +02:00
bunkerity
843644f806 log - replace some WARN tags from LUA logs with NOTICE to avoid confusion 2021-04-27 09:57:07 +02:00
bunkerity
19fa0eb25f log - print modsec_audit.log to make debugging easier 2021-04-27 09:46:40 +02:00
bunkerity
b4df287228 log - send logs to remote syslog server 2021-04-27 09:30:10 +02:00
florian
5ce41edc03 api - whitelist IP/network for API 2021-04-26 22:22:34 +02:00
florian
a3cfb50b4d example - fix certbot wildcard 2021-04-26 21:34:18 +02:00
bunkerity
25494acace example - wildcard certificate with certbot 2021-04-26 17:44:48 +02:00
bunkerity
a98dae1fb6 fix CVE-2021-20205 and examples update 2021-04-26 17:00:23 +02:00
bunkerity
1a7abab570 nginx 1.20.0 support 2021-04-26 14:59:12 +02:00
florian
42b7a57f01 fix autoconf bug when removing config with multiple server name and increase default LIMIT_CONN_MAX for average website with HTTP2 2021-04-26 11:39:12 +02:00
bunkerity
02f9fbe5fc autoconf - fix certbot bug when multiple server_name for one service 2021-04-20 11:46:53 +02:00
bunkerity
69fe066777 autoconf - fix bug when multiple server_name for one service 2021-04-20 10:00:25 +02:00
bunkerity
74417abc9c fixing bugs - run as GID 101 instead of 0, different permissions checks in swarm mode and disable including server confs in swarm mode 2021-04-16 16:56:45 +02:00
bunkerity
ba7524a419 fixed LUA bug 2021-04-13 17:27:52 +02:00
bunkerity
b55aafb997 finding the LUA bug 2021-04-13 17:01:27 +02:00
Bunkerity
deeb7a76a2 Merge pull request #117 from thelittlefireman/patch-9
Fix lua mistake
2021-04-13 16:49:45 +02:00
thelittlefireman
ee8aaa4e7e fix lua crash 2 2021-04-11 15:45:46 +02:00
thelittlefireman
605d59a45c Fix lua mistake
#116
2021-04-11 15:33:31 +02:00
bunkerity
b85c991b6e bug fixes - /usr/local/lib/lua rights and syntax error in site-config 2021-04-09 17:40:19 +02:00
bunkerity
0d3658adf0 REVERSE_PROXY_HEADERS - use proxy_set_header instead of more_set_headers 2021-04-09 17:27:22 +02:00
bunkerity
0b22209c96 documentation - userns remap feature 2021-04-09 16:22:31 +02:00
bunkerity
e44a1f3e14 added the uri to limit_req_zone key to limit bruteforce attack on a specific resource instead of the whole service 2021-04-09 15:54:26 +02:00
bunkerity
aa614f82f9 print error when permissions are wrong on common volumes 2021-04-09 14:54:15 +02:00
bunkerity
c03d410b0a refactored whitelisting of user-agents 2021-04-09 14:23:52 +02:00
bunkerity
e190167bfc CIDR support with whitelist/blacklist IP 2021-04-09 14:10:17 +02:00
bunkerity
31e72dce1c fix /usr/local/lib/lua rights and multiple server_name support with autoconf 2021-04-09 11:37:13 +02:00
bunkerity
b8105fc558 feature - whitelist URI 2021-04-09 10:31:00 +02:00
bunkerity
e73c10fd80 crowdsec - fix permissions on /usr/local/lib/lua and on /var/log files 2021-04-09 10:01:23 +02:00
bunkerity
a122a259c0 minor fix on AutoConf logs and auto disable etag with reverse proxy 2021-04-09 09:51:17 +02:00
bunkerity
7c4894d3b8 autoconf - fix remove event, generate config from nginx vars, more logs 2021-03-26 15:18:35 +01:00
bunkerity
533c2a1034 fix sed script when writing site env 2021-03-22 09:38:36 +01:00
bunkerity
5611d544d6 remove reference to USE_PHP 2021-03-19 09:38:44 +01:00
78 changed files with 747 additions and 230 deletions

View File

@@ -1,4 +1,4 @@
FROM nginx:stable-alpine
FROM nginx:1.20.0-alpine
COPY nginx-keys/ /tmp/nginx-keys
COPY compile.sh /tmp/compile.sh
@@ -16,10 +16,13 @@ COPY lua/ /opt/lua
COPY prepare.sh /tmp/prepare.sh
RUN chmod +x /tmp/prepare.sh && /tmp/prepare.sh && rm -f /tmp/prepare.sh
# fix CVE-2021-20205
RUN apk add "libjpeg-turbo>=2.1.0-r0"
VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge
EXPOSE 8080/tcp 8443/tcp
USER nginx
USER nginx:nginx
ENTRYPOINT ["/opt/entrypoint/entrypoint.sh"]

View File

@@ -1,4 +1,4 @@
FROM amd64/nginx:stable-alpine
FROM amd64/nginx:1.20.0-alpine
COPY nginx-keys/ /tmp/nginx-keys
COPY compile.sh /tmp/compile.sh
@@ -16,10 +16,13 @@ COPY lua/ /opt/lua
COPY prepare.sh /tmp/prepare.sh
RUN chmod +x /tmp/prepare.sh && /tmp/prepare.sh && rm -f /tmp/prepare.sh
# fix CVE-2021-20205
RUN apk add "libjpeg-turbo>=2.1.0-r0"
VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge
EXPOSE 8080/tcp 8443/tcp
USER nginx
USER nginx:nginx
ENTRYPOINT ["/opt/entrypoint/entrypoint.sh"]

View File

@@ -3,7 +3,7 @@ FROM alpine AS builder
ENV QEMU_URL https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz
RUN apk add curl && curl -L ${QEMU_URL} | tar zxvf - -C . --strip-components 1
FROM arm32v7/nginx:stable-alpine
FROM arm32v7/nginx:1.20.0-alpine
COPY --from=builder qemu-arm-static /usr/bin
@@ -23,10 +23,13 @@ COPY lua/ /opt/lua
COPY prepare.sh /tmp/prepare.sh
RUN chmod +x /tmp/prepare.sh && /tmp/prepare.sh && rm -f /tmp/prepare.sh
# fix CVE-2021-20205
RUN apk add "libjpeg-turbo>=2.1.0-r0"
VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge
EXPOSE 8080/tcp 8443/tcp
USER nginx
USER nginx:nginx
ENTRYPOINT ["/opt/entrypoint/entrypoint.sh"]

View File

@@ -3,7 +3,7 @@ FROM alpine AS builder
ENV QEMU_URL https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-aarch64.tar.gz
RUN apk add curl && curl -L ${QEMU_URL} | tar zxvf - -C . --strip-components 1
FROM arm64v8/nginx:stable-alpine
FROM arm64v8/nginx:1.20.0-alpine
COPY --from=builder qemu-aarch64-static /usr/bin
@@ -23,10 +23,13 @@ COPY lua/ /opt/lua
COPY prepare.sh /tmp/prepare.sh
RUN chmod +x /tmp/prepare.sh && /tmp/prepare.sh && rm -f /tmp/prepare.sh
# fix CVE-2021-20205
RUN apk add "libjpeg-turbo>=2.1.0-r0"
VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge
EXPOSE 8080/tcp 8443/tcp
USER nginx
USER nginx:nginx
ENTRYPOINT ["/opt/entrypoint/entrypoint.sh"]

View File

@@ -1,4 +1,4 @@
FROM i386/nginx:stable-alpine
FROM i386/nginx:1.20.0-alpine
COPY nginx-keys/ /tmp/nginx-keys
COPY compile.sh /tmp/compile.sh
@@ -16,10 +16,13 @@ COPY lua/ /opt/lua
COPY prepare.sh /tmp/prepare.sh
RUN chmod +x /tmp/prepare.sh && /tmp/prepare.sh && rm -f /tmp/prepare.sh
# fix CVE-2021-20205
RUN apk add "libjpeg-turbo>=2.1.0-r0"
VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge
EXPOSE 8080/tcp 8443/tcp
USER nginx
USER nginx:nginx
ENTRYPOINT ["/opt/entrypoint/entrypoint.sh"]

102
README.md
View File

@@ -3,13 +3,17 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/bunkerized--nginx-1.2.3-blue" />
<img src="https://img.shields.io/badge/nginx-1.18.0-blue" />
<img src="https://img.shields.io/github/last-commit/bunkerity/bunkerized-nginx" />
<img src="https://img.shields.io/badge/bunkerized--nginx-1.2.4-blue" />
<img src="https://img.shields.io/badge/nginx-1.20.0-blue" />
<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/docker/cloud/build/bunkerity/bunkerized-nginx" />
</p>
<p align="center">
<a href="https://matrix.to/#/#bunkerized-nginx:matrix.org"><img src="https://img.shields.io/badge/matrix%20chat-%23bunkerized--nginx%3Amatrix.org-blue" /></a>
<img src="https://img.shields.io/github/workflow/status/bunkerity/bunkerized-nginx/Automatic%20test?label=automatic%20test" />
<img src="https://img.shields.io/docker/cloud/build/bunkerity/bunkerized-nginx" />
<a href="https://twitter.com/bunkerity"><img src="https://img.shields.io/twitter/follow/bunkerity?style=social" /></a>
<a href="https://www.bunkerity.com"><img src="https://img.shields.io/badge/website-www.bunkerity.com-blue" /></a>
<a href="https://twitter.com/bunkerity"><img src="https://img.shields.io/twitter/follow/bunkerity?style=social" /></a>
</p>
nginx Docker image secure by default.
@@ -34,9 +38,13 @@ Fooling automated tools/scanners :
<img src="https://github.com/bunkerity/bunkerized-nginx/blob/master/demo.gif?raw=true" />
You can find a live demo at https://demo-nginx.bunkerity.com, feel free to do some security tests.
# Table of contents
<details>
<summary>Click to show</summary>
- [Table of contents](#table-of-contents)
- [Live demo](#live-demo)
- [Quickstart guide](#quickstart-guide)
* [Run HTTP server with default settings](#run-http-server-with-default-settings)
* [In combination with PHP](#in-combination-with-php)
@@ -85,9 +93,7 @@ Fooling automated tools/scanners :
* [Logrotate](#logrotate)
* [Cron jobs](#cron-jobs)
* [Misc](#misc-2)
# Live demo
You can find a live demo at https://demo-nginx.bunkerity.com.
</details>
# Quickstart guide
@@ -440,15 +446,32 @@ When `USE_ANTIBOT` is set to *captcha*, every users visiting your website must c
## Hardening
### Drop capabilities
By default, *bunkerized-nginx* runs as non-root user inside the container and should not use any of the default [capabilities](https://docs.docker.com/engine/security/#linux-kernel-capabilities) allowed by Docker. You can safely remove all capabilities to harden the container :
```shell
docker run ... --drop-cap=all ... bunkerity/bunkerized-nginx
```
### User namespace remap
Another hardening trick is [user namespace remapping](https://docs.docker.com/engine/security/userns-remap/) : 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 :
```shell
$ chown root:100101 /path/to/letsencrypt
$ chmod 770 /path/to/letsencrypt
$ docker run ... -v /path/to/letsencrypt:/etc/letsencrypt ... bunkerity/bunkerized-nginx
```
# Tutorials and examples
You will find some docker-compose examples in the [examples directory](https://github.com/bunkerity/bunkerized-nginx/tree/master/examples).
You will find some docker-compose examples in the [examples directory](https://github.com/bunkerity/bunkerized-nginx/tree/master/examples) and tutorials on our [blog](https://www.bunkerity.com/blog).
# Include custom configurations
Custom configurations files (ending with .conf suffix) can be added in some directory inside the container :
@@ -648,11 +671,10 @@ Only valid when `USE_REVERSE_PROXY` is set to *yes*. Set it to *yes* when the co
You can set multiple url/host by adding a suffix number to the variable name like this : `REVERSE_PROXY_WS_1`, `REVERSE_PROXY_WS_2`, `REVERSE_PROXY_WS_3`, ...
`REVERSE_PROXY_HEADERS`
Values : *\<list of custom headers separated with a semicolon\>*
Examples : Access-Control-Allow-Origin 'https://mydomain.dev'; Custom_Api_Header 'test';
Default value : ""
Values : *\<list of custom headers separated with a semicolon like this : header1 value1;header2 value2...\>*
Default value :
Context : *global*, *multisite*
Only valid when `USE_REVERSE_PROXY` is set to *yes*. Set it to *yes* when the corresponding `REVERSE_PROXY_HOST` is a WebSocket server.
Only valid when `USE_REVERSE_PROXY` is set to *yes*.
You can set multiple url/host by adding a suffix number to the variable name like this : `REVERSE_PROXY_HEADERS_1`, `REVERSE_PROXY_HEADERS_2`, `REVERSE_PROXY_HEADERS_3`, ...
`PROXY_REAL_IP`
@@ -876,19 +898,19 @@ If set to yes, nginx will redirect all HTTP requests to HTTPS.
`USE_CUSTOM_HTTPS`
Values : *yes* | *no*
Default value : *no*
Context : *global*
Context : *global*, *multisite*
If set to yes, HTTPS will be enabled with certificate/key of your choice.
`CUSTOM_HTTPS_CERT`
Values : *\<any valid path inside the container\>*
Default value :
Context : *global*
Context : *global*, *multisite*
Full path of the certificate file to use when `USE_CUSTOM_HTTPS` is set to yes.
`CUSTOM_HTTPS_KEY`
Values : *\<any valid path inside the container\>*
Default value :
Context : *global*
Context : *global*, *multisite*
Full path of the key file to use when `USE_CUSTOM_HTTPS` is set to yes.
### Self-signed certificate
@@ -1173,10 +1195,10 @@ Context : *global*, *multisite*
If set to *yes*, lets you define custom IP addresses to be whitelisted through the `WHITELIST_IP_LIST` environment variable.
`WHITELIST_IP_LIST`
Values : *\<list of IP addresses separated with spaces\>*
Values : *\<list of IP addresses and/or network CIDR blocks separated with spaces\>*
Default value : *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*
Context : *global*
The list of IP addresses to whitelist when `USE_WHITELIST_IP` is set to *yes*. The default list contains IP addresses of the [DuckDuckGo crawler](https://help.duckduckgo.com/duckduckgo-help-pages/results/duckduckbot/).
The list of IP addresses and/or network CIDR blocks to whitelist when `USE_WHITELIST_IP` is set to *yes*. The default list contains IP addresses of the [DuckDuckGo crawler](https://help.duckduckgo.com/duckduckgo-help-pages/results/duckduckbot/).
`USE_WHITELIST_REVERSE`
Values : *yes* | *no*
@@ -1196,6 +1218,12 @@ Default value :
Context : *global*, *multisite*
Whitelist user agent from being blocked by `BLOCK_USER_AGENT`.
`WHITELIST_URI`
Values : *\<list of URI separated with spaces\>*
Default value :
Context : *global*, *multisite*
URI listed here have security checks like bad user-agents, bad IP, ... disabled. Useful when using callbacks for example.
### Custom blacklisting
`USE_BLACKLIST_IP`
@@ -1205,10 +1233,10 @@ Context : *global*, *multisite*
If set to *yes*, lets you define custom IP addresses to be blacklisted through the `BLACKLIST_IP_LIST` environment variable.
`BLACKLIST_IP_LIST`
Values : *\<list of IP addresses separated with spaces\>*
Values : *\<list of IP addresses and/or network CIDR blocks separated with spaces\>*
Default value :
Context : *global*
The list of IP addresses to blacklist when `USE_BLACKLIST_IP` is set to *yes*.
The list of IP addresses and/or network CIDR blocks to blacklist when `USE_BLACKLIST_IP` is set to *yes*.
`USE_BLACKLIST_REVERSE`
Values : *yes* | *no*
@@ -1228,18 +1256,18 @@ The list of reverse DNS suffixes to blacklist when `USE_BLACKLIST_REVERSE` is se
Values : *yes* | *no*
Default value : *yes*
Context : *global*, *multisite*
If set to yes, the amount of HTTP requests made by a user will be limited during a period of time.
More info rate limiting [here](https://www.nginx.com/blog/rate-limiting-nginx/).
If set to yes, the amount of HTTP requests made by a user for a given resource will be limited during a period of time.
More info rate limiting [here](https://www.nginx.com/blog/rate-limiting-nginx/) (the key used is $binary_remote_addr$uri).
`LIMIT_REQ_RATE`
Values : *Xr/s* | *Xr/m*
Default value : *20r/s*
Default value : *1r/s*
Context : *global*, *multisite*
The rate limit to apply when `USE_LIMIT_REQ` is set to *yes*. Default is 10 requests per second.
The rate limit to apply when `USE_LIMIT_REQ` is set to *yes*. Default is 1 request to the same URI and from the same IP per second.
`LIMIT_REQ_BURST`
Values : *<any valid integer\>*
Default value : *40*
Default value : *2*
Context : *global*, *multisite*
The number of requests to put in queue before rejecting requests.
@@ -1255,12 +1283,12 @@ The size of the cache to store information about request limiting.
Values : *yes* | *no*
Default value : *yes*
Context : *global*, *multisite*
If set to yes, the number of connections made by an ip will be limited during a period of time. (ie. Very small/weak ddos protection)
If set to yes, the number of connections made by an ip will be limited during a period of time. (ie. very small/weak ddos protection)
More info connections limiting [here](http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html).
`LIMIT_CONN_MAX`
Values : *<any valid integer\>*
Default value : *40*
Default value : *50*
Context : *global*, *multisite*
The maximum number of connections per ip to put in queue before rejecting requests.
@@ -1290,7 +1318,7 @@ Only allow specific countries accessing your website. Use 2 letters country code
Values : *\<any valid IP/hostname\>*
Default value :
Context : *global*, *multisite*
Set the IP/hostname address of a remote PHP-FPM to execute .php files. See `USE_PHP` if you want to run a PHP-FPM instance on the same container as bunkerized-nginx.
Set the IP/hostname address of a remote PHP-FPM to execute .php files.
`REMOTE_PHP_PATH`
Values : *\<any valid absolute path\>*
@@ -1358,6 +1386,14 @@ Default value : *yes*
Context : *global*
If set to yes, ClamAV will automatically remove the detected files.
## Syslog
`REMOTE_SYSLOG`
Values : *\<any IP/hostname\>*
Default value :
Context : *global*
When defined, rsyslog will send logs (access.log and error.log) to the corresponding IP/hostname using syslog UDP protocol.
## Logrotate
`LOGROTATE_MINSIZE`
@@ -1453,3 +1489,9 @@ Values : *random* | *\<any valid URI path\>*
Default value : *random*
Context : *global*
Set it to a random path when you use *bunkerized-nginx* with *autoconf* feature in swarm mode. More info [here](#swarm-mode).
`API_WHITELIST_IP`
Values : *\<list of IP/CIDR separated with space\>*
Default value : *192.168.0.0/16 172.16.0.0/12 10.0.0.0/8*
Context : *global*
List of IP/CIDR block allowed to send API order using the `API_URI` uri.

View File

@@ -1 +1 @@
1.2.3
1.2.4

View File

@@ -11,6 +11,11 @@ class AutoConf :
self.__sites = {}
self.__config = Config(self.__swarm, api)
def get_server(self, id) :
if id in self.__servers :
return self.__servers[id]
return False
def reload(self) :
return self.__config.reload(self.__instances)
@@ -60,9 +65,9 @@ class AutoConf :
self.__instances[id] = instance
if self.__swarm and len(self.__instances) == 1 :
if self.__config.initconf(self.__instances) :
utils.log("[*] initial config succeeded")
utils.log("[*] Initial config succeeded")
else :
utils.log("[!] initial config failed")
utils.log("[!] Initial config failed")
utils.log("[*] bunkerized-nginx instance created : " + name + " / " + id)
elif event == "start" :
self.__instances[id].reload()
@@ -82,10 +87,12 @@ class AutoConf :
def __process_server(self, instance, event, id, name, labels) :
vars = { k.replace("bunkerized-nginx.", "", 1) : v for k, v in labels.items() if k.startswith("bunkerized-nginx.")}
if event == "create" :
utils.log("[*] Generating config for " + vars["SERVER_NAME"] + " ...")
if self.__config.generate(self.__instances, vars) :
utils.log("[*] Generated config for " + vars["SERVER_NAME"])
self.__servers[id] = instance
if self.__swarm :
utils.log("[*] Activating config for " + vars["SERVER_NAME"] + " ...")
if self.__config.activate(self.__instances, vars) :
utils.log("[*] Activated config for " + vars["SERVER_NAME"])
else :
@@ -95,6 +102,7 @@ class AutoConf :
elif event == "start" :
if id in self.__servers :
self.__servers[id].reload()
utils.log("[*] Activating config for " + vars["SERVER_NAME"] + " ...")
if self.__config.activate(self.__instances, vars) :
utils.log("[*] Activated config for " + vars["SERVER_NAME"])
else :
@@ -102,6 +110,7 @@ class AutoConf :
elif event == "die" :
if id in self.__servers :
self.__servers[id].reload()
utils.log("[*] Deactivating config for " + vars["SERVER_NAME"])
if self.__config.deactivate(self.__instances, vars) :
utils.log("[*] Deactivated config for " + vars["SERVER_NAME"])
else :
@@ -109,11 +118,13 @@ class AutoConf :
elif event == "destroy" or event == "remove" :
if id in self.__servers :
if self.__swarm :
utils.log("[*] Deactivating config for " + vars["SERVER_NAME"])
if self.__config.deactivate(self.__instances, vars) :
utils.log("[*] Deactivated config for " + vars["SERVER_NAME"])
else :
utils.log("[!] Can't deactivate config for " + vars["SERVER_NAME"])
del self.__servers[id]
utils.log("[*] Removing config for " + vars["SERVER_NAME"])
if self.__config.remove(vars) :
utils.log("[*] Removed config for " + vars["SERVER_NAME"])
else :

View File

@@ -20,25 +20,42 @@ class Config :
var = var_value.split("=")[0]
value = var_value.replace(var + "=", "", 1)
vars[var] = value
if self.globalconf(instances) :
i = 0
started = False
while i < 10 :
if self.__ping(instances) :
started = True
break
i = i + 1
utils.log("[!] Waiting " + str(i) + " seconds before retrying to contact nginx instances")
time.sleep(i)
if started :
proc = subprocess.run(["/bin/su", "-s", "/opt/entrypoint/jobs.sh", "nginx"], env=vars, capture_output=True)
return proc.returncode == 0
else :
utils.log("[!] bunkerized-nginx instances are not started")
utils.log("[*] Generating global config ...")
if not self.globalconf(instances) :
utils.log("[!] Can't generate global config")
return False
utils.log("[*] Generated global config")
if "SERVER_NAME" in vars and vars["SERVER_NAME"] != "" :
for server in vars["SERVER_NAME"].split(" ") :
vars_site = vars.copy()
vars_site["SERVER_NAME"] = server
utils.log("[*] Generating config for " + vars["SERVER_NAME"] + " ...")
if not self.generate(instances, vars_site) or not self.activate(instances, vars_site, reload=False) :
utils.log("[!] Can't generate/activate site config for " + server)
return False
utils.log("[*] Generated config for " + vars["SERVER_NAME"])
with open("/etc/nginx/autoconf", "w") as f :
f.write("ok")
utils.log("[*] Waiting for bunkerized-nginx tasks ...")
i = 1
started = False
while i <= 10 :
time.sleep(i)
if self.__ping(instances) :
started = True
break
i = i + 1
utils.log("[!] Waiting " + str(i) + " seconds before retrying to contact bunkerized-nginx tasks")
if started :
utils.log("[*] bunkerized-nginx tasks started")
proc = subprocess.run(["/bin/su", "-s", "/opt/entrypoint/jobs.sh", "nginx"], env=vars, capture_output=True)
return proc.returncode == 0
else :
utils.log("[!] Can't generate global conf")
utils.log("[!] bunkerized-nginx tasks are not started")
except Exception as e :
traceback.print_exc()
utils.log("[!] Error while initializing config : " + str(e))
return False
@@ -54,12 +71,11 @@ class Config :
vars[var] = value
proc = subprocess.run(["/bin/su", "-s", "/opt/entrypoint/global-config.sh", "nginx"], env=vars, capture_output=True)
if proc.returncode == 0 :
with open("/etc/nginx/autoconf", "w") as f :
f.write("ok")
return True
else :
utils.log("[*] Error while generating global config : return code = " + str(proc.returncode))
except Exception as e :
traceback.print_exc()
utils.log("[!] Error while generating global config : " + str(e))
utils.log("[!] Exception while generating global config : " + str(e))
return False
def generate(self, instances, vars) :
@@ -79,61 +95,76 @@ class Config :
vars_defaults.update(vars_instances)
vars_defaults.update(vars)
# Call site-config.sh to generate the config
proc = subprocess.run(["/bin/su", "-s", "/bin/sh", "-c", "/opt/entrypoint/site-config.sh" + " " + vars["SERVER_NAME"], "nginx"], env=vars_defaults, capture_output=True)
proc = subprocess.run(["/bin/su", "-s", "/bin/sh", "-c", "/opt/entrypoint/site-config.sh" + " \"" + vars["SERVER_NAME"] + "\"", "nginx"], env=vars_defaults, capture_output=True)
if proc.returncode == 0 and vars_defaults["MULTISITE"] == "yes" and self.__swarm :
proc = subprocess.run(["/bin/su", "-s", "/opt/entrypoint/multisite-config.sh", "nginx"], env=vars_defaults, capture_output=True)
return proc.returncode == 0
return proc.returncode == 0
if proc.returncode == 0 :
return True
utils.log("[!] Error while generating site config for " + vars["SERVER_NAME"] + " : return code = " + str(proc.returncode))
except Exception as e :
traceback.print_exc()
utils.log("[!] Error while generating site config : " + str(e))
utils.log("[!] Exception while generating site config : " + str(e))
return False
def activate(self, instances, vars) :
def activate(self, instances, vars, reload=True) :
try :
# Get first server name
first_server_name = vars["SERVER_NAME"].split(" ")[0]
# Check if file exists
if not os.path.isfile("/etc/nginx/" + vars["SERVER_NAME"] + "/server.conf") :
utils.log("[!] /etc/nginx/" + vars["SERVER_NAME"] + "/server.conf doesn't exist")
if not os.path.isfile("/etc/nginx/" + first_server_name + "/server.conf") :
utils.log("[!] /etc/nginx/" + first_server_name + "/server.conf doesn't exist")
return False
# Include the server conf
utils.replace_in_file("/etc/nginx/nginx.conf", "}", "include /etc/nginx/" + vars["SERVER_NAME"] + "/server.conf;\n}")
utils.replace_in_file("/etc/nginx/nginx.conf", "}", "include /etc/nginx/" + first_server_name + "/server.conf;\n}")
# Reload
if not reload or self.reload(instances) :
return True
return self.reload(instances)
except Exception as e :
traceback.print_exc()
utils.log("[!] Error while activating config : " + str(e))
utils.log("[!] Exception while activating config : " + str(e))
return False
def deactivate(self, instances, vars) :
try :
# Get first server name
first_server_name = vars["SERVER_NAME"].split(" ")[0]
# Check if file exists
if not os.path.isfile("/etc/nginx/" + vars["SERVER_NAME"] + "/server.conf") :
utils.log("[!] /etc/nginx/" + vars["SERVER_NAME"] + "/server.conf doesn't exist")
if not os.path.isfile("/etc/nginx/" + first_server_name + "/server.conf") :
utils.log("[!] /etc/nginx/" + first_server_name + "/server.conf doesn't exist")
return False
# Remove the include
utils.replace_in_file("/etc/nginx/nginx.conf", "include /etc/nginx/" + vars["SERVER_NAME"] + "/server.conf;\n", "")
utils.replace_in_file("/etc/nginx/nginx.conf", "include /etc/nginx/" + first_server_name + "/server.conf;\n", "")
return self.reload(instances)
# Reload
if self.reload(instances) :
return True
except Exception as e :
traceback.print_exc()
utils.log("[!] Error while deactivating config : " + str(e))
utils.log("[!] Exception while deactivating config : " + str(e))
return False
def remove(self, instances, vars) :
def remove(self, vars) :
try :
# Get first server name
first_server_name = vars["SERVER_NAME"].split(" ")[0]
# Check if file exists
if not os.path.isfile("/etc/nginx/" + vars["SERVER_NAME"] + "/server.conf") :
utils.log("[!] /etc/nginx/" + vars["SERVER_NAME"] + "/server.conf doesn't exist")
if not os.path.isfile("/etc/nginx/" + first_server_name + "/server.conf") :
utils.log("[!] /etc/nginx/" + first_server_name + "/server.conf doesn't exist")
return False
# Remove the folder
shutil.rmtree("/etc/nginx/" + vars["SERVER_NAME"])
shutil.rmtree("/etc/nginx/" + first_server_name)
return True
except Exception as e :
utils.log("[!] Error while deactivating config : " + str(e))
return False
def reload(self, instances) :
@@ -149,7 +180,7 @@ class Config :
instance.reload()
# Reload via API
if self.__swarm :
# Send POST request on http://serviceName.NodeID.TaskID:8000/reload
# Send POST request on http://serviceName.NodeID.TaskID:8000/action
name = instance.name
for task in instance.tasks() :
if task["Status"]["State"] != "running" :

View File

@@ -12,6 +12,9 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
mkdir /opt/scripts && \
addgroup -g 101 nginx && \
adduser -h /var/cache/nginx -g nginx -s /sbin/nologin -G nginx -D -H -u 101 nginx && \
mkdir /etc/letsencrypt && \
chown root:nginx /etc/letsencrypt && \
chmod 770 /etc/letsencrypt && \
mkdir /var/log/letsencrypt && \
chown root:nginx /var/log/letsencrypt && \
chmod 770 /var/log/letsencrypt && \
@@ -25,7 +28,11 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
chown root:nginx /var/log/jobs.log && \
chmod 770 /var/log/jobs.log && \
chown -R root:nginx /opt/confs/nginx && \
chmod -R 770 /opt/confs/nginx
chmod -R 770 /opt/confs/nginx && \
mkdir /acme-challenge && \
chown root:nginx /acme-challenge && \
chmod 770 /acme-challenge
COPY autoconf/misc/logrotate.conf /etc/logrotate.conf
COPY scripts/* /opt/scripts/

View File

@@ -7,10 +7,14 @@ COPY --from=builder /etc/nginx/ /opt/confs/nginx
RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
pip3 install docker requests && \
mkdir /opt/entrypoint && \
mkdir -p /opt/confs/site && \
mkdir -p /opt/confs/site && \
mkdir -p /opt/confs/global && \
mkdir /opt/scripts && \
addgroup -g 101 nginx && \
adduser -h /var/cache/nginx -g nginx -s /sbin/nologin -G nginx -D -H -u 101 nginx && \
mkdir /etc/letsencrypt && \
chown root:nginx /etc/letsencrypt && \
chmod 770 /etc/letsencrypt && \
mkdir /var/log/letsencrypt && \
chown root:nginx /var/log/letsencrypt && \
chmod 770 /var/log/letsencrypt && \
@@ -24,7 +28,10 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
chown root:nginx /var/log/jobs.log && \
chmod 770 /var/log/jobs.log && \
chown -R root:nginx /opt/confs/nginx && \
chmod -R 770 /opt/confs/nginx
chmod -R 770 /opt/confs/nginx && \
mkdir /acme-challenge && \
chown root:nginx /acme-challenge && \
chmod 770 /acme-challenge
COPY autoconf/misc/logrotate.conf /etc/logrotate.conf
COPY scripts/* /opt/scripts/

View File

@@ -15,8 +15,12 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
mkdir /opt/entrypoint && \
mkdir -p /opt/confs/site && \
mkdir -p /opt/confs/global && \
mkdir /opt/scripts && \
addgroup -g 101 nginx && \
adduser -h /var/cache/nginx -g nginx -s /sbin/nologin -G nginx -D -H -u 101 nginx && \
mkdir /etc/letsencrypt && \
chown root:nginx /etc/letsencrypt && \
chmod 770 /etc/letsencrypt && \
mkdir /var/log/letsencrypt && \
chown root:nginx /var/log/letsencrypt && \
chmod 770 /var/log/letsencrypt && \
@@ -30,7 +34,10 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
chown root:nginx /var/log/jobs.log && \
chmod 770 /var/log/jobs.log && \
chown -R root:nginx /opt/confs/nginx && \
chmod -R 770 /opt/confs/nginx
chmod -R 770 /opt/confs/nginx && \
mkdir /acme-challenge && \
chown root:nginx /acme-challenge && \
chmod 770 /acme-challenge
COPY autoconf/misc/logrotate.conf /etc/logrotate.conf
COPY scripts/* /opt/scripts/

View File

@@ -15,8 +15,12 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
mkdir /opt/entrypoint && \
mkdir -p /opt/confs/site && \
mkdir -p /opt/confs/global && \
mkdir /opt/scripts && \
addgroup -g 101 nginx && \
adduser -h /var/cache/nginx -g nginx -s /sbin/nologin -G nginx -D -H -u 101 nginx && \
mkdir /etc/letsencrypt && \
chown root:nginx /etc/letsencrypt && \
chmod 770 /etc/letsencrypt && \
mkdir /var/log/letsencrypt && \
chown root:nginx /var/log/letsencrypt && \
chmod 770 /var/log/letsencrypt && \
@@ -30,7 +34,10 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
chown root:nginx /var/log/jobs.log && \
chmod 770 /var/log/jobs.log && \
chown -R root:nginx /opt/confs/nginx && \
chmod -R 770 /opt/confs/nginx
chmod -R 770 /opt/confs/nginx && \
mkdir /acme-challenge && \
chown root:nginx /acme-challenge && \
chmod 770 /acme-challenge
COPY autoconf/misc/logrotate.conf /etc/logrotate.conf
COPY scripts/* /opt/scripts/

View File

@@ -9,8 +9,12 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
mkdir /opt/entrypoint && \
mkdir -p /opt/confs/site && \
mkdir -p /opt/confs/global && \
mkdir /opt/scripts && \
addgroup -g 101 nginx && \
adduser -h /var/cache/nginx -g nginx -s /sbin/nologin -G nginx -D -H -u 101 nginx && \
mkdir /etc/letsencrypt && \
chown root:nginx /etc/letsencrypt && \
chmod 770 /etc/letsencrypt && \
mkdir /var/log/letsencrypt && \
chown root:nginx /var/log/letsencrypt && \
chmod 770 /var/log/letsencrypt && \
@@ -24,7 +28,10 @@ RUN apk add py3-pip apache2-utils bash certbot curl logrotate openssl && \
chown root:nginx /var/log/jobs.log && \
chmod 770 /var/log/jobs.log && \
chown -R root:nginx /opt/confs/nginx && \
chmod -R 770 /opt/confs/nginx
chmod -R 770 /opt/confs/nginx && \
mkdir /acme-challenge && \
chown root:nginx /acme-challenge && \
chmod 770 /acme-challenge
COPY autoconf/misc/logrotate.conf /etc/logrotate.conf
COPY scripts/* /opt/scripts/

View File

@@ -44,6 +44,7 @@ with lock :
# Process events received from Docker
try :
utils.log("[*] Listening for Docker events ...")
for event in client.events(decode=True) :
# Process only container/service events
@@ -53,11 +54,15 @@ try :
# Get Container/Service object
try :
if swarm :
server = client.services.get(service_id=event["Actor"]["ID"])
id = service_id=event["Actor"]["ID"]
server = client.services.get(service_id=id)
else :
server = client.containers.get(event["id"])
id = event["id"]
server = client.containers.get(id)
except docker.errors.NotFound as e :
continue
server = autoconf.get_server(id)
if not server :
continue
# Process the event
with lock :

View File

@@ -2,6 +2,12 @@
echo "[*] Starting autoconf ..."
# check permissions
su -s "/opt/entrypoint/permissions.sh" nginx
if [ "$?" -ne 0 ] ; then
exit 1
fi
if [ "$SWARM_MODE" = "yes" ] ; then
cp -r /opt/confs/nginx/* /etc/nginx
chown -R root:nginx /etc/nginx

View File

@@ -136,6 +136,10 @@ sed -i 's/^API_KEY=.*/API_KEY=%CROWDSEC_KEY%/' /usr/local/lib/lua/crowdsec/crowd
sed -i 's/require "lrucache"/require "resty.lrucache"/' /usr/local/lib/lua/crowdsec/CrowdSec.lua
sed -i 's/require "config"/require "crowdsec.config"/' /usr/local/lib/lua/crowdsec/CrowdSec.lua
cd /tmp
git_secure_clone https://github.com/hamishforbes/lua-resty-iputils.git 3151d6485e830421266eee5c0f386c32c835dba4
cd lua-resty-iputils
make LUA_LIB_DIR=/usr/local/lib/lua install
cd /tmp
git_secure_clone https://github.com/openresty/lua-nginx-module.git 2d23bc4f0a29ed79aaaa754c11bffb1080aa44ba
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.1
@@ -153,7 +157,7 @@ fi
tar -xvzf nginx-${NGINX_VERSION}.tar.gz
cd nginx-$NGINX_VERSION
CONFARGS=$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p')
CONFARGS=${CONFARGS/-Os -fomit-frame-pointer/-Os}
CONFARGS=${CONFARGS/-Os -fomit-frame-pointer -g/-Os}
./configure $CONFARGS --add-dynamic-module=/tmp/ModSecurity-nginx --add-dynamic-module=/tmp/headers-more-nginx-module --add-dynamic-module=/tmp/ngx_http_geoip2_module --add-dynamic-module=/tmp/nginx_cookie_flag_module --add-dynamic-module=/tmp/lua-nginx-module --add-dynamic-module=/tmp/ngx_brotli
make -j $NTASK modules
cp ./objs/*.so /usr/lib/nginx/modules

View File

@@ -13,7 +13,7 @@ rewrite_by_lua_block {
if api.is_api_call(api_uri) then
ngx.header.content_type = 'text/plain'
if api.do_api_call(api_uri) then
ngx.log(ngx.WARN, "[API] API call " .. ngx.var.request_uri .. " successfull from " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[API] API call " .. ngx.var.request_uri .. " successfull from " .. ngx.var.remote_addr)
ngx.say("ok")
else
ngx.log(ngx.WARN, "[API] API call " .. ngx.var.request_uri .. " failed from " .. ngx.var.remote_addr)

View File

@@ -6,7 +6,7 @@ rewrite_by_lua_block {
if api.is_api_call(api_uri) then
ngx.header.content_type = 'text/plain'
if api.do_api_call(api_uri) then
ngx.log(ngx.WARN, "[API] API call " .. ngx.var.request_uri .. " successfull from " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[API] API call " .. ngx.var.request_uri .. " successfull from " .. ngx.var.remote_addr)
ngx.say("ok")
else
ngx.log(ngx.WARN, "[API] API call " .. ngx.var.request_uri .. " failed from " .. ngx.var.remote_addr)

View File

@@ -5,5 +5,5 @@ init_by_lua_block {
ngx.log(ngx.ERR, "[Crowdsec] " .. err)
error()
end
ngx.log(ngx.ERR, "[Crowdsec] Initialisation done")
ngx.log(ngx.NOTICE, "[Crowdsec] Initialisation done")
}

View File

@@ -50,7 +50,7 @@ http {
# write logs to local syslog
log_format logf '%LOG_FORMAT%';
access_log syslog:server=unix:/tmp/log,nohostname,facility=local0,severity=notice logf;
error_log syslog:server=unix:/tmp/log,nohostname,facility=local0 warn;
error_log syslog:server=unix:/tmp/log,nohostname,facility=local0 notice;
# temp paths
proxy_temp_path /tmp/proxy_temp;

View File

@@ -7,7 +7,7 @@ location = %ANTIBOT_URI% {
local cookie = require "cookie"
local captcha = require "captcha"
if not cookie.is_set("uri") then
ngx.log(ngx.WARN, "[ANTIBOT] captcha fail (1) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] captcha fail (1) for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
local img, res = captcha.get_challenge()
@@ -22,19 +22,19 @@ location = %ANTIBOT_URI% {
local cookie = require "cookie"
local captcha = require "captcha"
if not cookie.is_set("captchares") then
ngx.log(ngx.WARN, "[ANTIBOT] captcha fail (2) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] captcha fail (2) for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
ngx.req.read_body()
local args, err = ngx.req.get_post_args(1)
if err == "truncated" or not args or not args["captcha"] then
ngx.log(ngx.WARN, "[ANTIBOT] captcha fail (3) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] captcha fail (3) for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
local captcha_user = args["captcha"]
local check = captcha.check(captcha_user, cookie.get("captchares"))
if not check then
ngx.log(ngx.WARN, "[ANTIBOT] captcha fail (4) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] captcha fail (4) for " .. ngx.var.remote_addr)
return ngx.redirect("%ANTIBOT_URI%")
end
cookie.set({captcha = "ok"})

View File

@@ -7,7 +7,7 @@ location = %ANTIBOT_URI% {
local cookie = require "cookie"
local recaptcha = require "recaptcha"
if not cookie.is_set("uri") then
ngx.log(ngx.WARN, "[ANTIBOT] recaptcha fail (1) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] recaptcha fail (1) for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
local code = recaptcha.get_code("%ANTIBOT_URI%", "%ANTIBOT_RECAPTCHA_SITEKEY%")
@@ -20,19 +20,19 @@ location = %ANTIBOT_URI% {
local cookie = require "cookie"
local recaptcha = require "recaptcha"
if not cookie.is_set("uri") then
ngx.log(ngx.WARN, "[ANTIBOT] recaptcha fail (2) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] recaptcha fail (2) for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
ngx.req.read_body()
local args, err = ngx.req.get_post_args(1)
if err == "truncated" or not args or not args["token"] then
ngx.log(ngx.WARN, "[ANTIBOT] recaptcha fail (3) for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] recaptcha fail (3) for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
local token = args["token"]
local check = recaptcha.check(token, "%ANTIBOT_RECAPTCHA_SECRET%")
if check < %ANTIBOT_RECAPTCHA_SCORE% then
ngx.log(ngx.WARN, "[ANTIBOT] recaptcha fail (4) for " .. ngx.var.remote_addr .. " (score = " .. tostring(check) .. ")")
ngx.log(ngx.NOTICE, "[ANTIBOT] recaptcha fail (4) for " .. ngx.var.remote_addr .. " (score = " .. tostring(check) .. ")")
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
cookie.set({recaptcha = "ok"})

View File

@@ -1,4 +1,6 @@
location ~* \.(%CLIENT_CACHE_EXTENSIONS%)$ {
etag %CLIENT_CACHE_ETAG%;
add_header Cache-Control "%CLIENT_CACHE_CONTROL%";
etag %CLIENT_CACHE_ETAG%;
set $cache "";
if ($uri ~* \.(%CLIENT_CACHE_EXTENSIONS%)$) {
set $cache "%CLIENT_CACHE_CONTROL%";
}
add_header Cache-Control $cache;

View File

@@ -19,7 +19,6 @@ local use_antibot_captcha = %USE_ANTIBOT_CAPTCHA%
local use_antibot_recaptcha = %USE_ANTIBOT_RECAPTCHA%
-- include LUA code
local whitelist = require "whitelist"
local blacklist = require "blacklist"
local dnsbl = require "dnsbl"
@@ -31,11 +30,7 @@ local recaptcha = require "recaptcha"
-- user variables
local antibot_uri = "%ANTIBOT_URI%"
local whitelist_user_agent = {%WHITELIST_USER_AGENT%}
-- check if it's let's encrypt bot
if use_lets_encrypt and string.match(ngx.var.request_uri, "^/.well-known/acme-challenge/") then
ngx.exit(ngx.OK)
end
local whitelist_uri = {%WHITELIST_URI%}
-- check if already in whitelist cache
if use_whitelist_ip and whitelist.ip_cached_ok() then
@@ -72,6 +67,19 @@ if use_whitelist_reverse and not whitelist.reverse_cached() then
end
end
-- check if URI is whitelisted
for k, v in pairs(whitelist_uri) do
if ngx.var.request_uri == v then
ngx.log(ngx.NOTICE, "[WHITELIST] URI " .. v .. " is whitelisted")
ngx.exit(ngx.OK)
end
end
-- check if it's certbot
if use_lets_encrypt and string.match(ngx.var.request_uri, "^/.well-known/acme-challenge/") then
ngx.exit(ngx.OK)
end
-- check if IP is blacklisted (only if not in cache)
if use_blacklist_ip and not blacklist.ip_cached() then
if blacklist.check_ip() then
@@ -88,32 +96,29 @@ end
-- check if user-agent is allowed
if use_user_agent and ngx.var.bad_user_agent == "yes" then
local headers = ngx.req.get_headers()
local ua = headers["User-Agent"]
if not whitelist_user_agent ~= "" then
local k_ua_white, v_ua_white = next(whitelist_user_agent, nil)
while v_ua_white do
local rst_whitelist = string.match(ua, v_ua_white)
if rst_whitelist ~= nil and rst_whitelist ~= "" then
ngx.log(ngx.WARN, "[ALLOW] User-Agent " .. ngx.var.http_user_agent .. " is whitelisted")
ngx.exit(ngx.OK)
end
k_ua_white, v_ua_white = next(whitelist_user_agent, k_ua_white)
local block = false
for k, v in pairs(whitelist_user_agent) do
if string.match(ngx.var.http_user_agent, v) then
ngx.log(ngx.NOTICE, "[ALLOW] User-Agent " .. ngx.var.http_user_agent .. " is whitelisted")
block = false
break
end
end
ngx.log(ngx.WARN, "[BLOCK] User-Agent " .. ngx.var.http_user_agent .. " is blacklisted")
ngx.exit(ngx.HTTP_FORBIDDEN)
if block then
ngx.log(ngx.NOTICE, "[BLOCK] User-Agent " .. ngx.var.http_user_agent .. " is blacklisted")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
-- check if referrer is allowed
if use_referrer and ngx.var.bad_referrer == "yes" then
ngx.log(ngx.WARN, "[BLOCK] Referrer " .. ngx.var.http_referer .. " is blacklisted")
ngx.log(ngx.NOTICE, "[BLOCK] Referrer " .. ngx.var.http_referer .. " is blacklisted")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- check if country is allowed
if use_country and ngx.var.allowed_country == "no" then
ngx.log(ngx.WARN, "[BLOCK] Country of " .. ngx.var.remote_addr .. " is blacklisted")
ngx.log(ngx.NOTICE, "[BLOCK] Country of " .. ngx.var.remote_addr .. " is blacklisted")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
@@ -131,7 +136,7 @@ if use_crowdsec then
ngx.log(ngx.ERR, "[Crowdsec] " .. err)
end
if not ok then
ngx.log(ngx.ERR, "[Crowdsec] denied '" .. ngx.var.remote_addr .. "'")
ngx.log(ngx.NOTICE, "[Crowdsec] denied '" .. ngx.var.remote_addr .. "'")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
@@ -143,7 +148,7 @@ if use_antibot_cookie then
cookie.set({uri = ngx.var.request_uri})
return ngx.redirect(antibot_uri)
end
ngx.log(ngx.WARN, "[ANTIBOT] cookie fail for " .. ngx.var.remote_addr)
ngx.log(ngx.NOTICE, "[ANTIBOT] cookie fail for " .. ngx.var.remote_addr)
return ngx.exit(ngx.HTTP_FORBIDDEN)
else
if ngx.var.request_uri == antibot_uri then

View File

@@ -50,7 +50,6 @@ SecResponseBodyLimitAction ProcessPartial
# log usefull stuff
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log

View File

@@ -1,4 +1,5 @@
location %REVERSE_PROXY_URL% {
etag off;
proxy_pass %REVERSE_PROXY_HOST%;
%REVERSE_PROXY_HEADERS%
%REVERSE_PROXY_WS%

View File

@@ -102,11 +102,11 @@ BLACKLIST_REVERSE_LIST="${BLACKLIST_REVERSE_LIST-.shodan.io}"
USE_DNSBL="${USE_DNSBL-yes}"
DNSBL_LIST="${DNSBL_LIST-bl.blocklist.de problems.dnsbl.sorbs.net sbl.spamhaus.org xbl.spamhaus.org}"
USE_LIMIT_REQ="${USE_LIMIT_REQ-yes}"
LIMIT_REQ_RATE="${LIMIT_REQ_RATE-20r/s}"
LIMIT_REQ_BURST="${LIMIT_REQ_BURST-40}"
LIMIT_REQ_RATE="${LIMIT_REQ_RATE-1r/s}"
LIMIT_REQ_BURST="${LIMIT_REQ_BURST-2}"
LIMIT_REQ_CACHE="${LIMIT_REQ_CACHE-10m}"
USE_LIMIT_CONN="${USE_LIMIT_CONN-yes}"
LIMIT_CONN_MAX="${LIMIT_CONN_MAX-40}"
LIMIT_CONN_MAX="${LIMIT_CONN_MAX-50}"
LIMIT_CONN_CACHE="${LIMIT_CONN_CACHE-10m}"
PROXY_REAL_IP="${PROXY_REAL_IP-no}"
PROXY_REAL_IP_FROM="${PROXY_REAL_IP_FROM-192.168.0.0/16 172.16.0.0/12 10.0.0.0/8}"
@@ -128,4 +128,5 @@ ANTIBOT_SESSION_SECRET="${ANTIBOT_SESSION_SECRET-random}"
USE_CROWDSEC="${USE_CROWDSEC-no}"
USE_API="${USE_API-no}"
API_URI="${API_URI-random}"
API_WHITELIST_IP="${API_WHITELIST_IP-192.168.0.0/16 172.16.0.0/12 10.0.0.0/8}"
SWARM_MODE="${SWARM_MODE-no}"

View File

@@ -52,6 +52,16 @@ if [ ! -f "/opt/installed" ] ; then
echo "[*] Configuring bunkerized-nginx ..."
# check permissions
if [ "$SWARM_MODE" = "no" ] ; then
/opt/entrypoint/permissions.sh
else
/opt/entrypoint/permissions-swarm.sh
fi
if [ "$?" -ne 0 ] ; then
exit 1
fi
# logs config
/opt/entrypoint/logs.sh
@@ -122,7 +132,7 @@ else
fi
# list of log files to display
LOGS="/var/log/access.log /var/log/error.log /var/log/jobs.log"
LOGS="/var/log/access.log /var/log/error.log /var/log/jobs.log /var/log/nginx/modsec_audit.log"
# start fail2ban
if [ "$USE_FAIL2BAN" = "yes" ] ; then

View File

@@ -10,14 +10,18 @@
cp /opt/confs/global/* /etc/nginx/
# include server block(s)
if [ "$MULTISITE" = "yes" ] ; then
includes=""
for server in $SERVER_NAME ; do
includes="${includes}include /etc/nginx/${server}/server.conf;\n"
done
replace_in_file "/etc/nginx/nginx.conf" "%INCLUDE_SERVER%" "$includes"
if [ "$SWARM_MODE" = "no" ] ; then
if [ "$MULTISITE" = "yes" ] ; then
includes=""
for server in $SERVER_NAME ; do
includes="${includes}include /etc/nginx/${server}/server.conf;\n"
done
replace_in_file "/etc/nginx/nginx.conf" "%INCLUDE_SERVER%" "$includes"
else
replace_in_file "/etc/nginx/nginx.conf" "%INCLUDE_SERVER%" "include /etc/nginx/server.conf;"
fi
else
replace_in_file "/etc/nginx/nginx.conf" "%INCLUDE_SERVER%" "include /etc/nginx/server.conf;"
replace_in_file "/etc/nginx/nginx.conf" "%INCLUDE_SERVER%" ""
fi
# setup default server block if multisite
@@ -171,7 +175,7 @@ fi
# request limiting
if [ "$(has_value USE_LIMIT_REQ yes)" != "" ] ; then
replace_in_file "/etc/nginx/nginx.conf" "%LIMIT_REQ_ZONE%" "limit_req_zone \$binary_remote_addr zone=limit:${LIMIT_REQ_CACHE} rate=${LIMIT_REQ_RATE};"
replace_in_file "/etc/nginx/nginx.conf" "%LIMIT_REQ_ZONE%" "limit_req_zone \$binary_remote_addr\$uri zone=limit:${LIMIT_REQ_CACHE} rate=${LIMIT_REQ_RATE};"
else
replace_in_file "/etc/nginx/nginx.conf" "%LIMIT_REQ_ZONE%" ""
fi

View File

@@ -19,3 +19,10 @@ touch /var/log/jobs.log
replace_in_file "/etc/logrotate.conf" "%LOGROTATE_MAXAGE%" "$LOGROTATE_MAXAGE"
replace_in_file "/etc/logrotate.conf" "%LOGROTATE_MINSIZE%" "$LOGROTATE_MINSIZE"
echo "$LOGROTATE_CRON /opt/scripts/logrotate.sh > /dev/null 2>&1" >> /etc/crontabs/nginx
# setup rsyslog
if [ "$REMOTE_SYSLOG" != "" ] ; then
replace_in_file "/etc/rsyslog.conf" "%REMOTE_SYSLOG%" "local0.* @${REMOTE_SYSLOG};rawFormat"
else
replace_in_file "/etc/rsyslog.conf" "%REMOTE_SYSLOG%" ""
fi

View File

@@ -38,3 +38,9 @@ if [ "$(has_value USE_CROWDSEC yes)" != "" ] ; then
replace_in_file "/usr/local/lib/lua/crowdsec/crowdsec.conf" "%CROWDSEC_HOST%" "$CROWDSEC_HOST"
replace_in_file "/usr/local/lib/lua/crowdsec/crowdsec.conf" "%CROWDSEC_KEY%" "$CROWDSEC_KEY"
fi
# Whitelist IP for API
if [ "$USE_API" = "yes" ] ; then
list=$(spaces_to_lua "$API_WHITELIST_IP")
replace_in_file "/usr/local/lib/lua/api.lua" "%API_WHITELIST_IP%" "$list"
fi

View File

@@ -14,7 +14,8 @@ if [ "$MULTISITE" = "yes" ] ; then
fi
SERVER_PREFIX="/etc/nginx/${server}/"
if grep "/etc/letsencrypt/live" ${SERVER_PREFIX}https.conf > /dev/null && [ ! -f /etc/letsencrypt/live/${server}/fullchain.pem ] ; then
/opt/scripts/certbot-new.sh "$server" "$(cat ${SERVER_PREFIX}email-lets-encrypt.txt)"
domains=$(cat ${SERVER_PREFIX}server.conf | sed -nE 's/^.*server_name (.*);$/\1/p' | sed "s/ /,/g")
/opt/scripts/certbot-new.sh "$domains" "$(cat ${SERVER_PREFIX}email-lets-encrypt.txt)"
fi
if grep "modsecurity.conf" ${SERVER_PREFIX}server.conf > /dev/null ; then
modsec_custom=""

View File

@@ -0,0 +1,25 @@
#!/bin/bash
# /etc/letsencrypt
if [ ! -r "/etc/letsencrypt" ] || [ ! -x "/etc/letsencrypt" ] ; then
echo "[!] WARNING - wrong permissions on /etc/letsencrypt"
exit 1
fi
# /www
if [ ! -r "/www" ] || [ ! -x "/www" ] ; then
echo "[!] ERROR - wrong permissions on /www"
exit 2
fi
# /etc/nginx
if [ ! -r "/etc/nginx" ] || [ ! -x "/etc/nginx" ] ; then
echo "[!] ERROR - wrong permissions on /etc/nginx"
exit 3
fi
# /acme-challenge
if [ ! -r "/acme-challenge" ] || [ ! -x "/acme-challenge" ] ; then
echo "[!] ERROR - wrong permissions on /acme-challenge"
exit 4
fi

29
entrypoint/permissions.sh Normal file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# /etc/letsencrypt
if [ ! -w "/etc/letsencrypt" ] || [ ! -r "/etc/letsencrypt" ] || [ ! -x "/etc/letsencrypt" ] ; then
echo "[!] WARNING - wrong permissions on /etc/letsencrypt"
exit 1
fi
if [ -f "/usr/sbin/nginx" ] ; then
# /www
if [ ! -r "/www" ] || [ ! -x "/www" ] ; then
echo "[!] ERROR - wrong permissions on /www"
exit 2
fi
fi
# /acme-challenge
if [ ! -w "/acme-challenge" ] || [ ! -r "/acme-challenge" ] || [ ! -x "/acme-challenge" ] ; then
echo "[!] ERROR - wrong permissions on /acme-challenge"
exit 3
fi
# /etc/nginx
if [ ! -w "/etc/nginx" ] || [ ! -r "/etc/nginx" ] || [ ! -x "/etc/nginx" ] ; then
echo "[!] ERROR - wrong permissions on /etc/nginx"
exit 4
fi

View File

@@ -9,16 +9,14 @@
# get nginx path and override multisite variables
NGINX_PREFIX="/etc/nginx/"
if [ "$MULTISITE" = "yes" ] ; then
NGINX_PREFIX="${NGINX_PREFIX}${1}/"
first_server=$(echo "$1" | cut -d ' ' -f 1)
NGINX_PREFIX="${NGINX_PREFIX}${first_server}/"
if [ ! -d "$NGINX_PREFIX" ] ; then
mkdir "$NGINX_PREFIX"
fi
ROOT_FOLDER="${ROOT_FOLDER}/$1"
fi
if [ "$MULTISITE" = "yes" ] ; then
for var in $(env | cut -d '=' -f 1 | grep -E "^${1}_") ; do
repl_name=$(echo "$var" | sed "s~${1}_~~")
ROOT_FOLDER="${ROOT_FOLDER}/$first_server"
for var in $(env | cut -d '=' -f 1 | grep -E "^${first_server}_") ; do
repl_name=$(echo "$var" | sed "s~${first_server}_~~")
repl_value=$(env | grep -E "^${var}=" | sed "s~^${var}=~~")
read -r "$repl_name" <<< $repl_value
done
@@ -27,9 +25,9 @@ fi
set | grep -E -v "^(HOSTNAME|PWD|PKG_RELEASE|NJS_VERSION|SHLVL|PATH|_|NGINX_VERSION|HOME)=" > "${NGINX_PREFIX}nginx.env"
if [ "$MULTISITE" = "yes" ] ; then
for server in $SERVER_NAME ; do
sed -i "~^${server}_.*=.*~d" "${NGINX_PREFIX}nginx.env"
sed -i "/^${server}_.*=.*/d" "${NGINX_PREFIX}nginx.env"
done
sed -i "~^SERVER_NAME=.*~SERVER_NAME=${1}~" "${NGINX_PREFIX}nginx.env"
sed -i "s~^SERVER_NAME=.*~SERVER_NAME=${1}~" "${NGINX_PREFIX}nginx.env"
fi
# copy stub confs
@@ -38,8 +36,8 @@ cp /opt/confs/site/* "$NGINX_PREFIX"
# replace paths
replace_in_file "${NGINX_PREFIX}server.conf" "%MAIN_LUA%" "include ${NGINX_PREFIX}main-lua.conf;"
if [ "$MULTISITE" = "yes" ] ; then
replace_in_file "${NGINX_PREFIX}server.conf" "%SERVER_CONF%" "include /server-confs/*.conf;\ninclude /server-confs/${1}/*.conf;"
replace_in_file "${NGINX_PREFIX}server.conf" "%PRE_SERVER_CONF%" "include /pre-server-confs/*.conf;\ninclude /pre-server-confs/${1}/*.conf;"
replace_in_file "${NGINX_PREFIX}server.conf" "%SERVER_CONF%" "include /server-confs/*.conf;\ninclude /server-confs/${first_server}/*.conf;"
replace_in_file "${NGINX_PREFIX}server.conf" "%PRE_SERVER_CONF%" "include /pre-server-confs/*.conf;\ninclude /pre-server-confs/${first_server}/*.conf;"
else
replace_in_file "${NGINX_PREFIX}server.conf" "%SERVER_CONF%" "include /server-confs/*.conf;"
replace_in_file "${NGINX_PREFIX}server.conf" "%PRE_SERVER_CONF%" "include /pre-server-confs/*.conf;"
@@ -67,10 +65,10 @@ if [ "$USE_REVERSE_PROXY" = "yes" ] ; then
replace_in_file "${NGINX_PREFIX}reverse-proxy-${i}.conf" "%REVERSE_PROXY_URL%" "$url_value"
replace_in_file "${NGINX_PREFIX}reverse-proxy-${i}.conf" "%REVERSE_PROXY_HOST%" "$host_value"
if [ "$custom_headers_value" != "" ] ; then
IFS_=$IFS
IFS_=$IFS
IFS=';'
for header_value in $(echo "$custom_headers_value") ; do
replace_in_file "${NGINX_PREFIX}reverse-proxy-${i}.conf" "%REVERSE_PROXY_CUSTOM_HEADERS%" "more_set_headers $header_value;\n%REVERSE_PROXY_CUSTOM_HEADERS%"
for header_value in $(echo $custom_headers_value) ; do
replace_in_file "${NGINX_PREFIX}reverse-proxy-${i}.conf" "%REVERSE_PROXY_CUSTOM_HEADERS%" "proxy_set_header $header_value;\n%REVERSE_PROXY_CUSTOM_HEADERS%"
done
IFS=$IFS_
fi
@@ -288,6 +286,14 @@ else
replace_in_file "${NGINX_PREFIX}main-lua.conf" "%WHITELIST_USER_AGENT%" ""
fi
# whitelist URI
if [ "$WHITELIST_URI" != "" ] ; then
list=$(spaces_to_lua "$WHITELIST_URI")
replace_in_file "${NGINX_PREFIX}main-lua.conf" "%WHITELIST_URI%" "$list"
else
replace_in_file "${NGINX_PREFIX}main-lua.conf" "%WHITELIST_URI%" ""
fi
# block bad referrer
if [ "$BLOCK_REFERRER" = "yes" ] ; then
replace_in_file "${NGINX_PREFIX}main-lua.conf" "%USE_REFERRER%" "true"
@@ -343,8 +349,8 @@ if [ "$AUTO_LETS_ENCRYPT" = "yes" ] || [ "$USE_CUSTOM_HTTPS" = "yes" ] || [ "$GE
if [ "$MULTISITE" = "no" ] ; then
FIRST_SERVER_NAME=$(echo "$SERVER_NAME" | cut -d " " -f 1)
else
FIRST_SERVER_NAME="$1"
EMAIL_LETS_ENCRYPT="${EMAIL_LETS_ENCRYPT-contact@$1}"
FIRST_SERVER_NAME="$first_server"
EMAIL_LETS_ENCRYPT="${EMAIL_LETS_ENCRYPT-contact@$first_server}"
echo -n "$EMAIL_LETS_ENCRYPT" > ${NGINX_PREFIX}email-lets-encrypt.txt
fi
replace_in_file "${NGINX_PREFIX}https.conf" "%HTTPS_CERT%" "/etc/letsencrypt/live/${FIRST_SERVER_NAME}/fullchain.pem"

View File

@@ -20,7 +20,6 @@ services:
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
labels:
- "bunkerized-nginx.AUTOCONF"

View File

@@ -18,8 +18,8 @@ services:
- REDIRECT_HTTP_TO_HTTPS=yes
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_PROXY_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
labels:
- "bunkerized-nginx.AUTOCONF"

View File

@@ -18,7 +18,6 @@ services:
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- REMOTE_PHP=myphp
- REMOTE_PHP_PATH=/app

View File

@@ -23,7 +23,6 @@ services:
- PROXY_REAL_IP=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- REMOTE_PHP=myphp1
- REMOTE_PHP_PATH=/app
labels:
@@ -41,7 +40,6 @@ services:
- PROXY_REAL_IP=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- REMOTE_PHP=myphp2
- REMOTE_PHP_PATH=/app
labels:

View File

@@ -0,0 +1,26 @@
#!/bin/sh
# you need to run it before starting bunkerized-nginx
# since it's manual there is no auto renew, you need to run it again before it expires
DOMAIN="*.website.com"
SERVICE="mywww"
# ask for wildcard certificate
# it's interactive and you will need to add a DNS entry
docker run --rm -it -v "${PWD}/letsencrypt:/etc/letsencrypt" certbot/certbot certonly --manual -d $DOMAIN --agree-tos
if [ $? -ne 0 ] ; then
echo "error while getting certificate for $DOMAIN"
exit 1
fi
# fix permissions
chown -R 101:101 "${PWD}/letsencrypt"
# reload nginx if it's already running (in case of a "renew")
if [ -z `docker-compose ps -q $SERVICE` ] || [ -z `docker ps -q --no-trunc | grep $(docker-compose ps -q $SERVICE)` ]; then
echo "bunkerized-nginx is not running, skipping nginx reload"
else
echo "bunkerized-nginx is running, sending reload order"
docker-compose exec $SERVICE nginx -s reload
fi

View File

@@ -0,0 +1,39 @@
version: '3'
services:
mywww:
image: bunkerity/bunkerized-nginx
restart: always
ports:
- 80:8080
- 443:8443
volumes:
- ./web-files:/www:ro
- ./letsencrypt:/letsencrypt:ro
environment:
- SERVER_NAME=app1.website.com app2.website.com # replace with your domains
- MULTISITE=yes
- USE_CUSTOM_HTTPS=yes
- CUSTOM_HTTPS_CERT=/letsencrypt/live/website.com/fullchain.pem
- CUSTOM_HTTPS_KEY=/letsencrypt/live/website.com/privkey.pem
- REDIRECT_HTTP_TO_HTTPS=yes
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- app1.website.com_REMOTE_PHP=myapp1
- app1.website.com_REMOTE_PHP_PATH=/app
- app2.website.com_REMOTE_PHP=myapp2
- app2.website.com_REMOTE_PHP_PATH=/app
myapp1:
image: php:fpm
restart: always
volumes:
- ./web-files/app1.website.com:/app
myapp2:
image: php:fpm
restart: always
volumes:
- ./web-files/app2.website.com:/app

View File

@@ -0,0 +1,5 @@
<?php
echo "hello from app1 !";
?>

View File

@@ -0,0 +1,5 @@
<?php
echo "hello from app2 !";
?>

View File

@@ -1,3 +1,23 @@
#!/bin/sh
# first, you need to run the crowdsec service
echo "running crowdsec service ..."
docker-compose up -d mycrowdsec
# wait a little until it's up
sleep 10
# get the bouncer key
docker-compose exec mycrowdsec cscli bouncers add MyBouncer
# enter the key into the CROWDSEC_KEY environment variable
read -p -s "edit CROWDSEC_KEY env var in docker-compose.yml file and press enter"
# start all services
docker-compose up -d
# wait a little until it's up
sleep 10
# restart crowdsec so it reads the log files
docker-compose restart mycrowdsec

View File

@@ -20,7 +20,6 @@ services:
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_CROWDSEC=yes
- CROWDSEC_HOST=http://mycrowdsec:8080
- CROWDSEC_KEY= # you need to generate it (see bouncer_key.sh)
@@ -34,7 +33,7 @@ services:
- net2
mycrowdsec:
image: crowdsecurity/crowdsec:v1.0.2
image: crowdsecurity/crowdsec:v1.0.13
restart: always
volumes:
- ./acquis.yaml:/etc/crowdsec/acquis.yaml

View File

@@ -0,0 +1,30 @@
version: '3'
services:
myreverse:
image: bunkerity/bunkerized-nginx
restart: always
ports:
- 80:8080
- 443:8443
volumes:
- ./letsencrypt:/etc/letsencrypt
environment:
- SERVER_NAME=www.website.com # replace with your domain
- SERVE_FILES=no
- DISABLE_DEFAULT_SERVER=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_GZIP=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=http://myghost:2368/
myghost:
image: ghost:alpine
volumes:
- ./data-ghost:/var/lib/ghost/content
environment:
- url=https://www.website.com # replace with your domain

View File

@@ -0,0 +1,30 @@
version: '3'
services:
myreverse:
image: bunkerity/bunkerized-nginx
restart: always
ports:
- 80:8080
- 443:8443
volumes:
- ./letsencrypt:/etc/letsencrypt
- ./modsec-crs-confs:/modsec-crs-confs:ro # fix FP with CRS
environment:
- SERVER_NAME=www.website.com # replace with your domain
- SERVE_FILES=no
- DISABLE_DEFAULT_SERVER=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=http://mygogs:3000/
mygogs:
image: gogs/gogs
volumes:
- ./data-gogs:/data

View File

@@ -0,0 +1,7 @@
SecAction \
"id:900220,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/x-amf| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json| |application/octet-stream| |application/csp-report| |application/xss-auditor-report| |text/plain| |application/x-git-upload-pack-request| |application/x-git-receive-pack-request|'"

View File

@@ -0,0 +1,45 @@
version: '3'
services:
mywww:
image: bunkerity/bunkerized-nginx
restart: always
ports:
- 80:8080
- 443:8443
volumes:
- ./joomla-files:/www:ro
- ./letsencrypt:/etc/letsencrypt
environment:
- SERVER_NAME=www.website.com # replace with your domain
- AUTO_LETS_ENCRYPT=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- DISABLE_DEFAULT_SERVER=yes
- MAX_CLIENT_SIZE=50m
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- REMOTE_PHP=myjoomla
- REMOTE_PHP_PATH=/var/www/html
myjoomla:
image: joomla:fpm-alpine
restart: always
volumes:
- ./joomla-files:/var/www/html
environment:
- JOOMLA_DB_HOST=mydb
- JOOMLA_DB_NAME=joomladb
- JOOMLA_DB_USER=user
- JOOMLA_DB_PASSWORD=db-user-pwd # replace with a stronger password (must match MYSQL_PASSWORD)
mydb:
image: mariadb
restart: always
volumes:
- ./db-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
- MYSQL_DATABASE=joomladb
- MYSQL_USER=user
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match JOOMLA_DB_PASSWORD)

View File

@@ -18,8 +18,8 @@ services:
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=http://app

View File

@@ -18,8 +18,8 @@ services:
- MAX_CLIENT_SIZE=50m
- SERVE_FILES=no
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=https://mymoodle:8443

View File

@@ -19,13 +19,11 @@ services:
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- app1.website.com_REMOTE_PHP=myapp1
- app1.website.com_REMOTE_PHP_PATH=/app
- app2.website.com_REMOTE_PHP=myapp2
- app2.website.com_REMOTE_PHP_PATH=/app
- app3.website.com_SERVE_FILES=no
- app3.website.com_USE_CLIENT_CACHE=no
- app3.website.com_USE_PROXY_CACHE=yes
- app3.website.com_USE_REVERSE_PROXY=yes
- app3.website.com_REVERSE_PROXY_URL=/

View File

@@ -22,14 +22,13 @@ services:
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- wp.website.com_REMOTE_PHP=mywp
- wp.website.com_REMOTE_PHP_PATH=/var/www/html
- nc.website.com_REMOTE_PHP=mync
- nc.website.com_REMOTE_PHP_PATH=/var/www/html
- nc.website.com_LIMIT_REQ_RATE=40r/s
- nc.website.com_LIMIT_REQ_BURST=60
- nc.website.com_ALLOWED_METHODS=GET|POST|HEAD|PROPFIND|DELETE|PUT|MKCOL|MOVE|COPY|PROPPATCH|REPORT
- nc.website.com_LIMIT_REQ_RATE=5r/s
- nc.website.com_LIMIT_REQ_BURST=10
- nc.website.com_ALLOWED_METHODS=GET|POST|HEAD|COPY|DELETE|LOCK|MKCOL|MOVE|PROPFIND|PROPPATCH|PUT|UNLOCK|OPTIONS
- nc.website.com_X_FRAME_OPTIONS=SAMEORIGIN
- nc.website.com_FAIL2BAN_STATUS_CODE=400|401|403|405|444
networks:

View File

@@ -1 +1,2 @@
SecRuleRemoveById 921110
SecRule REQUEST_FILENAME "@contains /remote.php/webdav" "id:1,ctl:ruleRemoveByTag=OWASP_CRS"

View File

@@ -1 +1,4 @@
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"
SecRuleRemoveById 953120

View File

@@ -12,4 +12,4 @@ SecAction \
nolog,\
pass,\
t:none,\
setvar:'tx.allowed_methods=GET HEAD POST PROPFIND DELETE PUT MKCOL MOVE COPY PROPPATCH REPORT'"
setvar:'tx.allowed_methods=GET POST HEAD COPY DELETE LOCK MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK OPTIONS'"

View File

@@ -23,16 +23,15 @@ services:
- USE_CLIENT_CACHE=yes
- REMOTE_PHP=mync
- REMOTE_PHP_PATH=/var/www/html
- LIMIT_REQ_RATE=40r/s
- LIMIT_REQ_BURST=60
- ALLOWED_METHODS=GET|POST|HEAD|PROPFIND|DELETE|PUT|MKCOL|MOVE|COPY|PROPPATCH|REPORT
- LIMIT_REQ_RATE=5r/s
- LIMIT_REQ_BURST=10
- ALLOWED_METHODS=GET|POST|HEAD|COPY|DELETE|LOCK|MKCOL|MOVE|PROPFIND|PROPPATCH|PUT|UNLOCK|OPTIONS
- X_FRAME_OPTIONS=SAMEORIGIN
- USE_GZIP=yes
- USE_BROTLI=yes
- FAIL2BAN_STATUS_CODE=400|401|403|405|444
mync:
image: nextcloud:20-fpm
image: nextcloud:21-fpm
restart: always
volumes:
- ./nc-files:/var/www/html

View File

@@ -1 +1,2 @@
SecRuleRemoveById 921110
SecRule REQUEST_FILENAME "@contains /remote.php/webdav" "id:1,ctl:ruleRemoveByTag=OWASP_CRS"

View File

@@ -12,4 +12,4 @@ SecAction \
nolog,\
pass,\
t:none,\
setvar:'tx.allowed_methods=GET HEAD POST PROPFIND DELETE PUT MKCOL MOVE COPY PROPPATCH REPORT'"
setvar:'tx.allowed_methods=GET POST HEAD COPY DELETE LOCK MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK OPTIONS'"

View File

@@ -20,8 +20,8 @@ services:
- ALLOWED_METHODS=GET|POST|HEAD|PUT|DELETE
- SERVE_FILES=no
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=https://mypassbolt

View File

@@ -20,9 +20,10 @@ services:
- MAX_CLIENT_SIZE=50m
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- REMOTE_PHP=myprestashop
- REMOTE_PHP_PATH=/var/www/html
- LIMIT_REQ_RATE=5r/s
- LIMIT_REQ_BURST=10
myprestashop:
image: prestashop/prestashop:1.7-fpm

View File

@@ -0,0 +1,46 @@
version: '3'
services:
myreverse:
image: bunkerity/bunkerized-nginx
restart: always
ports:
- 80:8080
- 443:8443
volumes:
- ./letsencrypt:/etc/letsencrypt
environment:
- SERVER_NAME=www.website.com # replace with your domain
- SERVE_FILES=no
- DISABLE_DEFAULT_SERVER=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=http://myredmine:3000/
myredmine:
image: redmine
restart: always
volumes:
- ./redmine-data:/usr/src/redmine/files
environment:
- REDMINE_DB_MYSQL=mydb
- REDMINE_DB_DATABASE=redminedb
- REDMINE_DB_USERNAME=user
- REDMINE_DB_PASSWORD=db-user-pwd # replace with a stronger password (must match MYSQL_PASSWORD)
mydb:
image: mariadb
restart: always
volumes:
- ./db-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
- MYSQL_DATABASE=redminedb
- MYSQL_USER=user
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match REDMINE_DB_PASSWORD)

View File

@@ -18,8 +18,8 @@ services:
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- app1.website.com_REVERSE_PROXY_URL=/
- app1.website.com_REVERSE_PROXY_HOST=http://app1:3000

View File

@@ -18,8 +18,8 @@ services:
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL_1=/app1/
- REVERSE_PROXY_HOST_1=http://app1:3000/

View File

@@ -17,8 +17,8 @@ services:
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/ws/
- REVERSE_PROXY_HOST=http://myws:8010/

View File

@@ -32,7 +32,7 @@ services:
mode: host
protocol: tcp
volumes:
- /shared/confs:/etc/nginx:ro
- /shared/confs:/etc/nginx
- /shared/letsencrypt:/etc/letsencrypt:ro
- /shared/acme-challenge:/acme-challenge:ro
- /shared/www:/www:ro
@@ -45,6 +45,7 @@ services:
- AUTO_LETS_ENCRYPT=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
networks:
- net_config
- net_services
@@ -86,6 +87,7 @@ services:
- "node.role==worker"
labels:
- "bunkerized-nginx.SERVER_NAME=app2.website.com"
- "bunkerized-nginx.USE_PROXY_CACHE=yes"
- "bunkerized-nginx.USE_REVERSE_PROXY=yes"
- "bunkerized-nginx.REVERSE_PROXY_URL=/"
- "bunkerized-nginx.REVERSE_PROXY_HOST=http://app2"

View File

@@ -17,8 +17,8 @@ services:
- REDIRECT_HTTP_TO_HTTPS=yes
- AUTO_LETS_ENCRYPT=yes
- USE_PROXY_CACHE=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- USE_REVERSE_PROXY=yes
- REVERSE_PROXY_URL=/
- REVERSE_PROXY_HOST=http://mytomcat:8080/sample/

View File

@@ -29,7 +29,6 @@ services:
- USE_ANTIBOT=captcha
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- REMOTE_PHP=myphp
- REMOTE_PHP_PATH=/app

View File

@@ -18,8 +18,8 @@ services:
- AUTO_LETS_ENCRYPT=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- DISABLE_DEFAULT_SERVER=yes
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- admin.website.com_SERVE_FILES=no
- admin.website.com_USE_AUTH_BASIC=yes
- admin.website.com_AUTH_BASIC_USER=admin # change it to something hard to guess

View File

@@ -13,6 +13,7 @@ services:
- ./letsencrypt:/etc/letsencrypt
- ./server-confs:/server-confs:ro # custom confs at server context for permalinks
- ./modsec-crs-confs:/modsec-crs-confs:ro # custom Core Rule Set confs to add Wordpress exclusions
- ./modsec-confs:/modsec-confs:ro # avoid some FP with CRS
environment:
- SERVER_NAME=www.website.com # replace with your domain
- AUTO_LETS_ENCRYPT=yes
@@ -21,7 +22,6 @@ services:
- MAX_CLIENT_SIZE=50m
- USE_CLIENT_CACHE=yes
- USE_GZIP=yes
- USE_BROTLI=yes
- REMOTE_PHP=mywp
- REMOTE_PHP_PATH=/var/www/html

View File

@@ -0,0 +1,4 @@
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"
SecRuleRemoveById 953120

View File

@@ -25,3 +25,5 @@ module(load="imuxsock" SysSock.Name="/tmp/log")
$template rawFormat,"%msg:2:2048%\n"
local0.=notice /var/log/access.log;rawFormat
local0.*;local0.!=notice /var/log/error.log;rawFormat
%REMOTE_SYSLOG%

View File

@@ -1,5 +1,8 @@
local M = {}
local api_list = {}
local M = {}
local api_list = {}
local api_whitelist_ip = {%API_WHITELIST_IP%}
local iputils = require "resty.iputils"
local whitelist = iputils.parse_cidrs(api_whitelist_ip)
api_list["^/ping$"] = function ()
return true
@@ -10,7 +13,7 @@ api_list["^/reload$"] = function ()
end
function M.is_api_call (api_uri)
if ngx.var.request_uri:sub(1, #api_uri) .. "/" == api_uri .. "/" then
if iputils.ip_in_cidrs(ngx.var.remote_addr, whitelist) and ngx.var.request_uri:sub(1, #api_uri) .. "/" == api_uri .. "/" then
for uri, code in pairs(api_list) do
if string.match(ngx.var.request_uri:sub(#api_uri + 1), uri) then
return true

View File

@@ -1,6 +1,8 @@
local M = {}
local dns = require "dns"
local iputils = require "resty.iputils"
local ip_list = {%BLACKLIST_IP_LIST%}
local blacklist = iputils.parse_cidrs(ip_list)
local reverse_list = {%BLACKLIST_REVERSE_LIST%}
local ip = ngx.var.remote_addr
@@ -21,10 +23,10 @@ function M.reverse_cached ()
end
function M.check_ip ()
for k, v in ipairs(ip_list) do
if v == ip then
if #ip_list > 0 then
if iputils.ip_in_cidrs(ip, blacklist) then
ngx.shared.blacklist_ip_cache:set(ip, "ko", 86400)
ngx.log(ngx.WARN, "ip " .. ip .. " is in blacklist")
ngx.log(ngx.NOTICE, "ip " .. ip .. " is in blacklist")
return true
end
end
@@ -33,13 +35,15 @@ function M.check_ip ()
end
function M.check_reverse ()
local rdns = dns.get_reverse()
if rdns ~= "" then
for k, v in ipairs(reverse_list) do
if rdns:sub(-#v) == v then
ngx.shared.blacklist_reverse_cache:set(ip, "ko", 86400)
ngx.log(ngx.WARN, "reverse " .. rdns .. " is in blacklist")
return true
if #reverse_list > 0 then
local rdns = dns.get_reverse()
if rdns ~= "" then
for k, v in ipairs(reverse_list) do
if rdns:sub(-#v) == v then
ngx.shared.blacklist_reverse_cache:set(ip, "ko", 86400)
ngx.log(ngx.NOTICE, "reverse " .. rdns .. " is in blacklist")
return true
end
end
end
end

View File

@@ -20,7 +20,7 @@ function M.check ()
local a,b,c,d = v2:match("([%d]+).([%d]+).([%d]+).([%d]+)")
if a == "127" then
ngx.shared.dnsbl_cache:set(ip, "ko", 86400)
ngx.log(ngx.WARN, "ip " .. ip .. " is in DNSBL " .. v)
ngx.log(ngx.NOTICE, "ip " .. ip .. " is in DNSBL " .. v)
return true
end
end

View File

@@ -1,7 +1,9 @@
local M = {}
local dns = require "dns"
local iputils = require "resty.iputils"
local ip_list = {%WHITELIST_IP_LIST%}
local reverse_list = {%WHITELIST_REVERSE_LIST%}
local whitelist = iputils.parse_cidrs(ip_list)
local ip = ngx.var.remote_addr
function M.ip_cached_ok ()
@@ -21,10 +23,10 @@ function M.reverse_cached ()
end
function M.check_ip ()
for k, v in ipairs(ip_list) do
if v == ip then
if #ip_list > 0 then
if iputils.ip_in_cidrs(ip, whitelist) then
ngx.shared.whitelist_ip_cache:set(ip, "ok", 86400)
ngx.log(ngx.WARN, "ip " .. ip .. " is in whitelist")
ngx.log(ngx.NOTICE, "ip " .. ip .. " is in whitelist")
return true
end
end
@@ -33,22 +35,24 @@ function M.check_ip ()
end
function M.check_reverse ()
local rdns = dns.get_reverse()
if rdns ~= "" then
local whitelisted = false
for k, v in ipairs(reverse_list) do
if rdns:sub(-#v) == v then
whitelisted = true
break
if #reverse_list > 0 then
local rdns = dns.get_reverse()
if rdns ~= "" then
local whitelisted = false
for k, v in ipairs(reverse_list) do
if rdns:sub(-#v) == v then
whitelisted = true
break
end
end
end
if whitelisted then
local ips = dns.get_ips(rdns)
for k, v in ipairs(ips) do
if v == ip then
ngx.shared.whitelist_reverse_cache:set(ip, "ok", 86400)
ngx.log(ngx.WARN, "reverse " .. rdns .. " is in whitelist")
return true
if whitelisted then
local ips = dns.get_ips(rdns)
for k, v in ipairs(ips) do
if v == ip then
ngx.shared.whitelist_reverse_cache:set(ip, "ok", 86400)
ngx.log(ngx.NOTICE, "reverse " .. rdns .. " is in whitelist")
return true
end
end
end
end

View File

@@ -40,6 +40,7 @@ chmod 770 /var/log/letsencrypt
touch /var/log/clamav.log
chown root:nginx /var/log/clamav.log
chmod 770 /var/log/clamav.log
find /var/log -type f -exec chmod 0774 {} \;
# prepare /acme-challenge
mkdir /acme-challenge
@@ -67,8 +68,11 @@ chown -R root:nginx /var/run/fail2ban /var/lib/fail2ban
chmod -R 770 /var/run/fail2ban /var/lib/fail2ban
# prepare /usr/local/lib/lua
chown root:nginx /usr/local/lib/lua
chown -R root:nginx /usr/local/lib/lua
chmod 770 /usr/local/lib/lua
find /usr/local/lib/lua -type f -name "*.conf" -exec chmod 0760 {} \;
find /usr/local/lib/lua -type f -name "*.lua" -exec chmod 0760 {} \;
find /usr/local/lib/lua -type d -exec chmod 0770 {} \;
# prepare /cache
mkdir /cache