Compare commits

...

7 Commits

14 changed files with 274 additions and 76 deletions

View File

@ -1,5 +1,9 @@
base_domain: "dev.local" base_domain: "dev.local"
self_signed: false self_signed: true
generate_cert:
# only copy existing if both are false
root: false
wildcard: false
admin_email: "lukas@kucharczyk.xyz" admin_email: "lukas@kucharczyk.xyz"
server_ip: "192.168.0.104" server_ip: "192.168.0.104"
data_folder: "/home/vagrant/docker-data" data_folder: "/home/vagrant/docker-data"

View File

@ -4,3 +4,5 @@
- docker - docker
- nginx - nginx
- jellyfin - jellyfin
vars_files:
- vault/certs/{{ base_domain }}.yml

View File

@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIID8zCCAtugAwIBAgIUPXGk144K0wqfLNwO7MK4g3ddbewwDQYJKoZIhvcNAQEL
BQAwgYgxCzAJBgNVBAYTAkNaMRMwEQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQH
DAZQcmFndWUxGDAWBgNVBAoMD0t1Y2hhcmN6eWsgTHRkLjEUMBIGA1UEAwwLKi5k
ZXYubG9jYWwxIzAhBgkqhkiG9w0BCQEWFGx1a2FzQGt1Y2hhcmN6eWsueHl6MB4X
DTIxMDQyNjIxMDA1OFoXDTMxMDQyNDIxMDA1OFowgYgxCzAJBgNVBAYTAkNaMRMw
EQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQHDAZQcmFndWUxGDAWBgNVBAoMD0t1
Y2hhcmN6eWsgTHRkLjEUMBIGA1UEAwwLKi5kZXYubG9jYWwxIzAhBgkqhkiG9w0B
CQEWFGx1a2FzQGt1Y2hhcmN6eWsueHl6MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAx/LUqs2Z8GS9x0KZrlrdL9Ndsskw5Q8EYGNJDxqebncMAkFbYbi1
ZhHz4mnVzLKFg6mQ7yXdnUz5DFCltEQcQHpIULPcbLyj3XXBA0Hd40Hc8+7hghJ0
Un9tH47JwMetnlNQcvmY2XJfQ+MV92pmIh7qzvkyj1EgjkNaTdf87zYl6zYnPJjy
MU7K3KMikPd8jECh5zhsrw9imgr86bqtWBjNkcA3F9Oauui6UhyN3/eGIa74+vx6
nYomiMSjuN3zkN0cyxX/PFVOZZOzTbmHUIPhIN3p5pXLhqA9tc3ePpifRATzU+Sn
ePPM++PVvSgf2PMFvTtPC/z/jKXHDB4C2QIDAQABo1MwUTAdBgNVHQ4EFgQUUa10
hrJc8F/WBDC0rWVISN5o6C8wHwYDVR0jBBgwFoAUUa10hrJc8F/WBDC0rWVISN5o
6C8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAZOwIDGg+CzK3
/wwIE3ttv/OeU1lHu8I87dR51ixG6kGfj6+iKyP9OhA4If+mRIlMRSmigZe/ENbw
2tVDcoIogu8zw3SLlpMzxSDiAE3Ro4O60x8IQ7+HNOvbfiVq5Hdhb39T9VvDkNv8
k9mectnlqTNRmbw9oYDbg7zjY+5yGz264QeakF2UwJdtGlDUHVt2w83WJLY/rYAv
uuowVf4Tqt1evIr5lfsxVbRO9oVzBnbivYZPe5hjNxOBXTti17DfedIC+y6bLavm
VNlba18xxCDEPadyJnOSLFQlD0aoMoV7m5hZCJZEciw6X/JpX3SIpRF0MQm02RPi
wncx4+iqGA==
-----END CERTIFICATE-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDH8tSqzZnwZL3H
QpmuWt0v012yyTDlDwRgY0kPGp5udwwCQVthuLVmEfPiadXMsoWDqZDvJd2dTPkM
UKW0RBxAekhQs9xsvKPddcEDQd3jQdzz7uGCEnRSf20fjsnAx62eU1By+ZjZcl9D
4xX3amYiHurO+TKPUSCOQ1pN1/zvNiXrNic8mPIxTsrcoyKQ93yMQKHnOGyvD2Ka
Cvzpuq1YGM2RwDcX05q66LpSHI3f94Yhrvj6/HqdiiaIxKO43fOQ3RzLFf88VU5l
k7NNuYdQg+Eg3enmlcuGoD21zd4+mJ9EBPNT5Kd488z749W9KB/Y8wW9O08L/P+M
pccMHgLZAgMBAAECggEANoUiNAin5wKxNLoWSZKBKV9K90sgx/SWTuoDq9ioRpqz
bZIApt7Ep7ZRd/U/1PcArOv5lxwOF6w7ZH5BEKxZv8MYINlKS2QMkoBfRtIY/LSi
2OjUGLGLgks4qBg5LxXDY554G0GvyesDyvEKtlIO01L218TfYnDOnbwDzaINV/RC
W/nbyx8Z5teorU+vS5IlDaNMDSFkAMFOfQ8gNrwCaWxX2FJRUyyzM/54J4bNgGUi
+3APpt8w4N54CrMbI6FzyQhUbAY9MRFryaLgw22v0haiM1nAE9BWpU5m20Q8Ilx8
f1e9PVDqwTXshxx01b0Z6RrHZViyi9+XOo6QU+4xYQKBgQDrYQZ8trvV3j+1E6Eu
rESMni+93ZbhRMbVVUDOhxU7BzfsFnvGodQms9sCaNQrexML0xH9DhWbssCDOvOm
MWSOnxUA1yivMiYvE4FzEM5iRRyFSjE3uS3+FZ0yY3iUr1vf9G9fJrtt/ag05AF5
ZywhmvvtiA06F9+/qZzMW4jxfQKBgQDZdy/tYZQI9PlfznI6juy/Kx310vGNN9aW
Zzv3WRon40awiRMxUhGViiq9eD1IEzLDIXGXHdOI4E+DQUd+pn7Cr08QoWbOQk2n
BujCDY0mevc2kDi5NASa2SUFvUXo/vCqZdfP/EA7BudYwknZp3hCV+nZt6DcW+M2
+WLYjxTVjQKBgGs/QTpv9HQFGRgDgqyGd5FuvigPoCCyOrqXZrjzmbvUlSlwMSOX
NtgPmRmm9A1/vXTkzkx0L2pK7yacJMoztTK6z1IbtwDko7tNcu0f2jmybcVZwcU3
Dfq4lHCSHC37HKyVbm13c20xZ4P546YSWWxSdrmLBSPUHvHwcW43wtTtAoGBAMOb
yAqYiRX9dQrOokU2JpJWcF6cXhaOsYEcXv5AsZxLfVxyEEd+8L44R/Wh4E2ipziK
LqildY9bZWpFH9A2ZzMric0FwXmqfjrNpjRXCC4i0ZfM5pkAx1uaEwk0lVvdZhGA
Bj3ZQtVVgmd+a/tR/oR+m6Tw3csZBCqA7H6rRSxJAoGBAIFd0PRdKm5+mUc575GQ
sxVznovODCdRyOAeD9IKyCcpMo8zB9uiNDRPG/sUebnowC3v7EeCYUUHlUDtsSjc
BZyasg71mrZElWg2mDonw/J9fcrEPGrh59wmqk49MLvzB0An/eP+gGK3RFgbijkN
EZcByagGNGdICcaaxr+RhgcU
-----END PRIVATE KEY-----

View File

@ -0,0 +1,136 @@
## Block SQL injections
set $block_sql_injections 0;
if ($query_string ~ "union.*select.*\(") {
set $block_sql_injections 1;
}
if ($query_string ~ "union.*all.*select.*") {
set $block_sql_injections 1;
}
if ($query_string ~ "concat.*\(") {
set $block_sql_injections 1;
}
if ($block_sql_injections = 1) {
return 403;
}
## Block file injections
set $block_file_injections 0;
if ($query_string ~ "[a-zA-Z0-9_]=http://") {
set $block_file_injections 1;
}
if ($query_string ~ "[a-zA-Z0-9_]=(\.\.//?)+") {
set $block_file_injections 1;
}
if ($query_string ~ "[a-zA-Z0-9_]=/([a-z0-9_.]//?)+") {
set $block_file_injections 1;
}
if ($block_file_injections = 1) {
return 403;
}
## Block common exploits
set $block_common_exploits 0;
if ($query_string ~ "(<|%3C).*script.*(>|%3E)") {
set $block_common_exploits 1;
}
if ($query_string ~ "GLOBALS(=|\[|\%[0-9A-Z]{0,2})") {
set $block_common_exploits 1;
}
if ($query_string ~ "_REQUEST(=|\[|\%[0-9A-Z]{0,2})") {
set $block_common_exploits 1;
}
if ($query_string ~ "proc/self/environ") {
set $block_common_exploits 1;
}
if ($query_string ~ "mosConfig_[a-zA-Z_]{1,21}(=|\%3D)") {
set $block_common_exploits 1;
}
if ($query_string ~ "base64_(en|de)code\(.*\)") {
set $block_common_exploits 1;
}
if ($block_common_exploits = 1) {
return 403;
}
## Block spam
set $block_spam 0;
if ($query_string ~ "\b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)\b") {
set $block_spam 1;
}
if ($query_string ~ "\b(erections|hoodia|huronriveracres|impotence|levitra|libido)\b") {
set $block_spam 1;
}
if ($query_string ~ "\b(ambien|blue\spill|cialis|cocaine|ejaculation|erectile)\b") {
set $block_spam 1;
}
if ($query_string ~ "\b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)\b") {
set $block_spam 1;
}
if ($block_spam = 1) {
return 403;
}
## Block user agents
set $block_user_agents 0;
# Disable Akeeba Remote Control 2.5 and earlier
if ($http_user_agent ~ "Indy Library") {
set $block_user_agents 1;
}
# Common bandwidth hoggers and hacking tools.
if ($http_user_agent ~ "libwww-perl") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "GetRight") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "GetWeb!") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "Go!Zilla") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "Download Demon") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "Go-Ahead-Got-It") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "TurnitinBot") {
set $block_user_agents 1;
}
if ($http_user_agent ~ "GrabNet") {
set $block_user_agents 1;
}
if ($block_user_agents = 1) {
return 403;
}

View File

@ -0,0 +1,31 @@
location ~* ^.*\.(css|js|jpe?g|gif|png|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
if_modified_since off;
# use the public cache
proxy_cache public-cache;
proxy_cache_key $host$request_uri;
# ignore these headers for media
proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires;
# cache 200s and also 404s (not ideal but there are a few 404 images for some reason)
proxy_cache_valid any 30m;
proxy_cache_valid 404 1m;
# strip this header to avoid If-Modified-Since requests
proxy_hide_header Last-Modified;
proxy_hide_header Cache-Control;
proxy_hide_header Vary;
proxy_cache_bypass 0;
proxy_no_cache 0;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404;
proxy_connect_timeout 5s;
proxy_read_timeout 45s;
expires @30m;
access_log off;
include conf.d/include/proxy.conf;
}

View File

@ -0,0 +1,3 @@
if ($scheme = "http") {
return 301 https://$host$request_uri;
}

View File

@ -0,0 +1,29 @@
# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {
# Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
# we need to open up access by turning off auth and IP ACL for this location.
auth_basic off;
allow all;
# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
# Current specification requires "text/plain" or no content header at all.
# It seems that "text/plain" is a safe option.
default_type "text/plain";
# This directory must be the same as in /etc/letsencrypt/cli.ini
# as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
# there to "webroot".
# Do NOT use alias, use root! Target directory is located here:
# /var/www/common/letsencrypt/.well-known/acme-challenge/
root /data/letsencrypt-acme-challenge;
}
# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
return 404;
}

View File

@ -0,0 +1,7 @@
add_header X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass $forward_scheme://$server:$port;

View File

@ -0,0 +1,9 @@
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'EECDH+AESGCM:AES256+EECDH:AES256+EDH:EDH+AESGCM:ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-
ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AE
S128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES';
ssl_prefer_server_ciphers on;

View File

@ -1,11 +1,17 @@
# If self_signed = true, in nginx/files: generate root CA (if regenereate_root_ca = true),
# and sign a wildcard certificate. Copy certificates to /etc/ssl/.
- name: ensure directories exist - name: ensure directories exist
file: file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
mode: '0755' mode: '0755'
loop: loop:
- "{{ nginx_confd_folder }}" - "{{ data_folder }}/nginx"
- name: generate and install self-signed certs - "{{ data_folder }}/nginx/conf.d"
- "{{ data_folder }}/nginx/sites-enabled"
- "{{ data_folder }}/nginx/sites-available"
- "{{ data_folder }}/nginx/snippets"
- name: generate self-signed certs
import_tasks: self-signed.yml import_tasks: self-signed.yml
when: self_signed when: self_signed
- name: create nginx bridge network - name: create nginx bridge network
@ -14,6 +20,18 @@
attachable: true attachable: true
internal: true internal: true
state: present state: present
- name: copy nginx.conf
template:
src: nginx.conf.j2
dest: "{{ data_folder }}/nginx/nginx.conf"
mode: '0755'
- name: copy snippets
template:
src: "{{ item }}"
dest: "{{ data_folder }}/nginx/snippets/{{ item | basename | regex_replace('.j2$', '') }}"
mode: '0755'
with_fileglob:
- "../templates/snippets/*.conf"
- name: run container - name: run container
docker_container: docker_container:
name: 'nginx' name: 'nginx'
@ -22,10 +40,13 @@
- name: bridge - name: bridge
- name: nginx-internal - name: nginx-internal
volumes: volumes:
- "{{ data_folder }}/nginx/conf.d:/etc/nginx/conf.d"
- "{{ data_folder }}/nginx/nginx.conf:/etc/nginx/nginx.conf" - "{{ data_folder }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
- "{{ data_folder }}/nginx/{{ base_domain }}.key:/etc/nginx/{{ base_domain }}.key" - "{{ data_folder }}/nginx/sites-available:/etc/nginx/sites-available"
- "{{ data_folder }}/nginx/{{ base_domain }}.crt:/etc/nginx/{{ base_domain }}.crt" - "{{ data_folder }}/nginx/sites-enabled:/etc/nginx/sites-enabled"
- "{{ nginx_confd_folder }}:/etc/nginx/conf.d" - "{{ data_folder }}/nginx/snippets:/etc/nginx/snippets"
- "{{ data_folder }}/nginx/{{ base_domain }}.key:/etc/ssl/{{ base_domain }}.key"
- "{{ data_folder }}/nginx/{{ base_domain }}.crt:/etc/ssl/{{ base_domain }}.crt"
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"

View File

@ -4,34 +4,39 @@
-new \ -new \
-nodes \ -nodes \
-newkey rsa:2048 \ -newkey rsa:2048 \
-keyout "{{ data_folder }}/nginx/rootca.key" \ -keyout "{{ playbook_dir }}/roles/nginx/files/rootca.key" \
-out "{{ data_folder }}/nginx/rootca.pem" \ -out "{{ playbook_dir }}/roles/nginx/files/rootca.pem" \
-sha256 \ -sha256 \
-days 3650 \ -days 3650 \
-subj "/C=CZ/L=Prague/CN=Homelab/emailAddress={{ admin_email }}" -subj "/C=CZ/L=Prague/CN=Homelab/emailAddress={{ admin_email }}"
when: generate_cert.root
- name: generate wildcard csr - name: generate wildcard csr
command: openssl req \ command: openssl req \
-new \ -new \
-nodes \ -nodes \
-newkey rsa:2048 \ -newkey rsa:2048 \
-keyout "{{ data_folder }}/nginx/{{ base_domain }}.key" \ -keyout "{{ playbook_dir }}/roles/nginx/files/{{ base_domain }}.key" \
-out "{{ data_folder }}/nginx/{{ base_domain }}.csr" \ -out "{{ playbook_dir }}/roles/nginx/files/{{ base_domain }}.csr" \
-subj "/C=CZ/L=Prague/CN=*.{{ base_domain }}/emailAddress={{ admin_email }}" -subj "/C=CZ/L=Prague/CN=*.{{ base_domain }}/emailAddress={{ admin_email }}"
when: generate_cert.wildcard
- name: sign wildcard csr with root ca - name: sign wildcard csr with root ca
command: openssl x509 \ command: openssl x509 \
-req \ -req \
-in "{{ data_folder }}/nginx/{{ base_domain }}.csr" \ -in "{{ playbook_dir }}/roles/nginx/files/{{ base_domain }}.csr" \
-CA "{{ data_folder }}/nginx/rootca.pem" \ -CA "{{ playbook_dir }}/roles/nginx/files/rootca.pem" \
-CAkey "{{ data_folder }}/nginx/rootca.key" \ -CAkey "{{ playbook_dir }}/roles/nginx/files/rootca.key" \
-CAcreateserial \ -CAcreateserial \
-out "{{ data_folder }}/nginx/{{ base_domain }}.crt" \ -out "{{ playbook_dir }}/roles/nginx/files/{{ base_domain }}.crt" \
-days 3650 \ -days 3650 \
-sha256 -sha256
- name: install root ca when: generate_cert.wildcard
command: trust anchor "{{ data_folder }}/nginx/rootca.pem" - name: copy wildcard certificate and key from vault
become: yes copy:
- name: copy .conf file content: "{{ item.content }}"
template: dest: "{{ data_folder }}/nginx/{{ item.name }}"
src: nginx.conf.j2 owner: root
dest: "{{ data_folder }}/nginx/nginx.conf" group: root
mode: '0755' mode: '0700'
with_items:
- "{{ certificates }}"
no_log: true

View File

@ -27,7 +27,8 @@ http {
#gzip on; #gzip on;
resolver 127.0.0.11 valid=30; resolver 127.0.0.11 valid=30;
ssl_certificate /etc/nginx/{{ base_domain }}.crt; {% if self_signed == true %}
ssl_certificate_key /etc/nginx/{{ base_domain }}.key; include /etc/nginx/snippets/self-signed.conf;
{% endif %}
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
} }

View File

@ -0,0 +1,2 @@
ssl_certificate /etc/ssl/{{ base_domain }}.crt;
ssl_certificate_key /etc/ssl/{{ base_domain }}.key;