diff --git a/LICENSE b/LICENSE index 204b93d..d745f27 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License Copyright (c) +MIT License Copyright (c) 2021 "Stichting Disroot.org" Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..e37a827 --- /dev/null +++ b/README.MD @@ -0,0 +1,48 @@ +# Nginx role + +This role, installs nginx webserver and manages all vhosts. The role is released under MIT Licence and we give no warranty for this piece of software. Currently supported OS - Debian. + +Apart from installing and configuring nginx, you can also make use of bundeled vhost templates for many FLOSS services which helps out setup your server. Check `templates/etc/nginx/sites-available` for more details. +Multiple Vhosts can be deployed and make use of templates. By default role does not deploy any Vhost. +Example vhosts: + +``` +nginx_vhosts: + - name: 'devchat' + template: 'conversejs' + proto: 'http' + listen: '80' + root: 'conversejs' + index: 'index.php' + use_access_log: 'true' + use_error_log: 'true' + nginx_error_log_level: 'warn' + http_upload_url: 'https://example.org' + bosh_url: 'https://example.org' + headers: 'none' + state: 'enable' + letsencrypt: 'false' + - name: 'framadate' + template: 'framadate' + proto: 'http' + listen: '80' + root: 'framadate/{{ framadate_version }}/framadate' + index: 'index.php' + use_access_log: 'true' + use_error_log: 'true' + nginx_error_log_level: 'warn' + upstream_params: + - 'fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;' + - 'fastcgi_index index.php;' + - 'include /etc/nginx/fastcgi_params;' + - 'fastcgi_pass unix:{{ pool_listen }};' + state: 'enable' + letsencrypt: 'false' +``` + +## Changelog +- **04.03.2021** - Modyfied header info for proxy (locations), and core templates +- **26.01.2021** - Rewritten templates structure +- **02.12.2020** - conversejs template +- **14.12.2019** - Start changelog +- **14.12.2019** - Updated hubzilla,privatebin, framadate template diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..8648724 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,65 @@ +--- + +nginx_ssl_dir: '/etc/letsencrypt/live' + +#nginx_ssl_dir: '/mnt' + +nginx_etc_dir: '/etc/nginx' +nginx_user: 'www-data' +nginx_worker_processes: '4' +nginx_pid: '/run/nginx.pid' +nginx_events_worker_connections: '10000' +nginx_events_multi_accept: 'on' +nginx_root: "/srv/www" +nginx_log_dir: '/var/log/nginx' +nginx_events_use: 'epoll' +nginx_www_dir: '/var/www/' +nginx_HSTS_policy: 'false' +nginx_http_types_hash_max_size: 4096 +nginx_http_default_type: 'application/octet-stream' +nginx_http_access_log: 'off' +nginx_http_error_log: 'off' +nginx_http_client_body_buffer_size: '1M' +nginx_http_client_header_buffer_size: '1M' +nginx_http_client_max_body_size: '10M' +nginx_http_large_client_header_buffers: '8 8k' +nginx_http_client_body_timeout: '60' +nginx_http_client_header_timeout: '60' +nginx_http_keepalive_timeout: '30 30' +nginx_http_send_timeout: '120' +nginx_http_ignore_invalid_headers: 'on' +nginx_http_keepalive_requests: '100' +nginx_http_recursive_error_pages: 'on' +nginx_http_sendfile: 'on' +nginx_http_server_name_in_redirect: 'off' +nginx_http_server_tokens: 'off' +nginx_http_tcp_nodelay: 'on' +nginx_http_tcp_nopush: 'on' +nginx_http_reset_timedout_connection: 'on' +nginx_proxy_buffers: '16 16k' +nginx_proxy_buffer_size: '16k' +nginx_http_gzip: 'on' +nginx_http_gzip_buffers: '16 8k' +nginx_http_gzip_comp_level: '9' +nginx_http_gzip_http_version: '1.0' +nginx_http_gzip_min_length: '0' +nginx_http_gzip_types: 'text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml' +nginx_http_gzip_vary: 'on' +nginx_http_gzip_disable: '"msie6"' + +nginx_gen_dh: 'false' +nginx_dh_path: '{{ nginx_ssl_dir }}/dhparam.pem' +nginx_dh_length: 4096 +nginx_ssl_protocols: 'TLSv1.2' +nginx_ssl_ciphers: 'EECDH+AESGCM:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305' +nginx_ssl_ecdh_curve: 'secp384r1' + +letsencrypt_webroot_path: '' +install_letsencrypt: 'false' +letsencrypt_domains: + - name: '' + +letsencrypt_copy_certs: 'false' +letsencrypt_copy_cert_from: + - name: '' + letsencrypt_dest: '' diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..f2d5c1c --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,11 @@ +--- + +# Check nginx config and if ok, reload +- name: reload nginx + command: nginx -t + notify: real-reload nginx + +- name: real-reload nginx + systemd: + name: nginx + state: reloaded diff --git a/tasks/config.yml b/tasks/config.yml new file mode 100644 index 0000000..bd02a35 --- /dev/null +++ b/tasks/config.yml @@ -0,0 +1,15 @@ +--- + +- name: "[NGINX] - Deploy nginx.conf" + template: + src: etc/nginx/nginx.conf.j2 + dest: "{{ nginx_etc_dir }}/nginx.conf" + notify: + - reload nginx + +- name: "[NGINX] - Deploy ssl.conf" + template: + src: etc/nginx/conf.d/ssl.conf.j2 + dest: "{{ nginx_etc_dir }}/conf.d/ssl.conf" + notify: + - reload nginx diff --git a/tasks/install.yml b/tasks/install.yml new file mode 100644 index 0000000..33d1b16 --- /dev/null +++ b/tasks/install.yml @@ -0,0 +1,8 @@ +--- + +- name: '[APT] - Install nginx and dependencies' + apt: + name: nginx + state: latest + update_cache: yes + cache_valid_time: 3600 diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..dc6e344 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,13 @@ +--- + +- name: "[NGINX] - Install packages" + include: install.yml + +- name: "[NGINX] - Configure nginx" + include: config.yml + +- name: "[NGINX] - Set SSL configuration" + include: ssl.yml + +- name: "[NGINX] - Create Vhosts configuration" + include: vhost.yml diff --git a/tasks/ssl.yml b/tasks/ssl.yml new file mode 100644 index 0000000..9bc0771 --- /dev/null +++ b/tasks/ssl.yml @@ -0,0 +1,50 @@ +--- + +- name: "[NGINX] - Create local ssl Directory" + file: + path: "{{ nginx_ssl_dir }}" + state: directory + mode: 0755 + +- name: "[NGINX] - Generate DH file" + command: openssl dhparam -out {{ nginx_dh_path }} {{ nginx_dh_length }} + args: + creates: "{{ nginx_dh_path }}" + when: nginx_gen_dh == 'true' + notify: + - reload nginx + +- name: "[NGINX] - Deploy DH file from vars" + copy: + content: "{{ nginx_dh }}" + dest: "{{ nginx_dh_path }}" + when: nginx_dh is defined + notify: + - reload nginx + +- name: "[NGINX] - Create SSL keys subfolder" + file: + path: "{{ nginx_ssl_dir }}/{{ item.ssl_name }}" + state: directory + mode: 0755 + with_items: "{{ nginx_vhosts }}" + when: item.copy_ssl is defined + notify: reload nginx + +- name: "[NGINX] - Deploy SSL keys" + copy: + src: "{{ ssl_src_path }}/{{ item.ssl_name }}/privkey.pem" + dest: "{{ nginx_ssl_dir}}/{{ item.ssl_name }}/privkey.pem" + mode: 0700 + with_items: "{{ nginx_vhosts }}" + when: item.copy_ssl is defined + notify: reload nginx + +- name: "[NGINX] - Deploy SSL certs" + copy: + src: "{{ ssl_src_path }}/{{ item.ssl_name }}/fullchain.pem" + dest: "{{ nginx_ssl_dir}}/{{ item.ssl_name }}/fullchain.pem" + mode: 0644 + with_items: "{{ nginx_vhosts }}" + when: item.copy_ssl is defined + notify: reload nginx diff --git a/tasks/vhost.yml b/tasks/vhost.yml new file mode 100644 index 0000000..6fba3e6 --- /dev/null +++ b/tasks/vhost.yml @@ -0,0 +1,46 @@ +--- + +- name: "[NGINX] - Create vhosts" + template: + src: etc/nginx/sites-available/{{ item.template }}.j2 + dest: "{{ nginx_etc_dir }}/sites-available/{{ item.name }}" + with_items: "{{ nginx_vhosts }}" + notify: + - reload nginx + when: item.state is defined and item.state != 'delete' + +- name: "[NGINX] - Delete vhosts" + file: + path: "{{ nginx_etc_dir }}/sites-available/{{ item.name }}" + state: absent + with_items: "{{ nginx_vhosts }}" + notify: + - reload nginx + when: item.state is defined and item.state == 'delete' + +- name: "[NGINX] - Enable vhosts" + file: + src: "{{ nginx_etc_dir }}/sites-available/{{ item.name }}" + dest: "{{ nginx_etc_dir }}/sites-enabled/{{ item.name }}" + state: link + with_items: "{{ nginx_vhosts }}" + notify: + - reload nginx + when: item.state is defined and item.state == 'enable' + +- name: "[NGINX] - Disable vhosts" + file: + path: "{{ nginx_etc_dir}}/sites-enabled/{{ item.name }}" + state: absent + with_items: "{{ nginx_vhosts }}" + notify: + - reload nginx + when: item.state is defined and (item.state == 'disable' or item.state == 'delete') + +- name: "[NGINX] - Delete default vhost when explicitely defined" + file: + path: "{{ nginx_etc_dir }}/sites-enabled/default" + state: absent + notify: + - reload nginx + when: nginx_default_vhost is not none diff --git a/templates/etc/nginx/conf.d/ssl.conf.j2 b/templates/etc/nginx/conf.d/ssl.conf.j2 new file mode 100644 index 0000000..bbb3a48 --- /dev/null +++ b/templates/etc/nginx/conf.d/ssl.conf.j2 @@ -0,0 +1,12 @@ +#### +# {{ ansible_managed }} +#### + +ssl_ciphers '{{ nginx_ssl_ciphers }}'; +ssl_ecdh_curve {{ nginx_ssl_ecdh_curve }}; + + +ssl_protocols {{ nginx_ssl_protocols }}; +ssl_prefer_server_ciphers on; +ssl_session_cache shared:SSL:10m; +ssl_dhparam {{ nginx_dh_path }}; diff --git a/templates/etc/nginx/nginx.conf.j2 b/templates/etc/nginx/nginx.conf.j2 new file mode 100644 index 0000000..87cfc6e --- /dev/null +++ b/templates/etc/nginx/nginx.conf.j2 @@ -0,0 +1,55 @@ +#### +# {{ ansible_managed }} +#### + +user {{ nginx_user }}; +worker_processes {{ nginx_worker_processes }}; +pid {{ nginx_pid }}; +{% if nginx_worker_rlimit_nofile is defined %} +worker_rlimit_nofile {{ nginx_worker_rlimit_nofile }}; +{% endif %} + +events { + worker_connections {{ nginx_events_worker_connections }}; + multi_accept {{ nginx_events_multi_accept }}; + use {{ nginx_events_use }}; +} + +http { + types_hash_max_size {{ nginx_http_types_hash_max_size }}; + include {{ nginx_etc_dir }}/mime.types; + + default_type {{ nginx_http_default_type }}; + access_log {{ nginx_http_access_log }}; + error_log {{ nginx_http_error_log }}; + client_body_buffer_size {{ nginx_http_client_body_buffer_size }}; + client_header_buffer_size {{ nginx_http_client_header_buffer_size }}; + client_max_body_size {{ nginx_http_client_max_body_size }}; + large_client_header_buffers {{ nginx_http_large_client_header_buffers }}; + client_body_timeout {{ nginx_http_client_body_timeout }}; + client_header_timeout {{ nginx_http_client_header_timeout }}; + keepalive_timeout {{ nginx_http_keepalive_timeout }}; + send_timeout {{ nginx_http_send_timeout }}; + ignore_invalid_headers {{ nginx_http_ignore_invalid_headers }}; + keepalive_requests {{ nginx_http_keepalive_requests }}; + recursive_error_pages {{ nginx_http_recursive_error_pages }}; + sendfile {{ nginx_http_sendfile }}; + server_name_in_redirect {{ nginx_http_server_name_in_redirect }}; + server_tokens {{ nginx_http_server_tokens }}; + tcp_nodelay {{ nginx_http_tcp_nodelay }}; + tcp_nopush {{ nginx_http_tcp_nopush }}; + reset_timedout_connection {{ nginx_http_reset_timedout_connection }}; + proxy_buffers {{ nginx_proxy_buffers }}; + proxy_buffer_size {{ nginx_proxy_buffer_size }}; + gzip {{ nginx_http_gzip }}; + gzip_buffers {{ nginx_http_gzip_buffers }}; + gzip_comp_level {{ nginx_http_gzip_comp_level }}; + gzip_http_version {{ nginx_http_gzip_http_version }}; + gzip_min_length {{ nginx_http_gzip_min_length }}; + gzip_types {{ nginx_http_gzip_types }}; + gzip_vary {{ nginx_http_gzip_vary }}; + gzip_disable {{ nginx_http_gzip_disable }}; + + include {{ nginx_etc_dir }}/conf.d/*.conf; + include {{ nginx_etc_dir }}/sites-enabled/*; +} diff --git a/templates/etc/nginx/sites-available/base.j2 b/templates/etc/nginx/sites-available/base.j2 new file mode 100644 index 0000000..50cf514 --- /dev/null +++ b/templates/etc/nginx/sites-available/base.j2 @@ -0,0 +1,45 @@ +{% extends "core.j2" %} + +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index }}; +{% endblock %} + +{% block location %} + location / { + try_files {{ item.override_try_files | default('$uri $uri/ =404') }}; +{% endblock %} +{% block app_root_location %} +{% endblock %} + } + +{% block extra_locations %} +{% endblock %} + +{% block custom_locations %} +{% endblock %} + +{% block local_content %} +{% if item.manage_local_content is not defined %} + # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac). + # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) + location ~ /\. { + deny all; + } + +{% if item.favicon is defined %} + location /favicon.ico { + alias {{ item.favicon }}; + expires 30d; + access_log off; + log_not_found off; + } +{% endif %} + + location ~* \.(txt|js|css|png|jpe?g|gif|ico|svg)$ { + expires 30d; + log_not_found off; + } +{% endif %} +{% endblock %} + diff --git a/templates/etc/nginx/sites-available/conversejs.j2 b/templates/etc/nginx/sites-available/conversejs.j2 new file mode 100644 index 0000000..7dfbd72 --- /dev/null +++ b/templates/etc/nginx/sites-available/conversejs.j2 @@ -0,0 +1,42 @@ +{% extends "core.j2" %} +{% block location %} + +## LOCATIONS + location / { + root {{ conversejs_app_dir }}; + index {{ conversejs_mode }}.html; + } + + location ~ /\. { + deny all; + } + +{% if item.bosh_url is defined %} + location ^/http-bind { + proxy_pass {{ item.bosh_url }}; + } +{% endif %} + + location ^/upload { + proxy_pass {{ item.http_upload_url }}; + } +{% if item.websocket is defined %} + location ^/xmpp-websocket { + proxy_pass {{ item.websocket_url }}; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + #proxy_set_header X-Forwarded-Proto https; + proxy_redirect off; + } + +{% endif %} + include mime.types; + + location ~ .(ttf|ttc|otf|eot|woff|woff2|font.css|css|js)$ { + add_header Access-Control-Allow-Origin "*"; # Decide here whether you want to allow all or only a particular domain + root {{ conversejs_app_dir }}; # Properly set the path here + + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/core.j2 b/templates/etc/nginx/sites-available/core.j2 new file mode 100644 index 0000000..17a05d7 --- /dev/null +++ b/templates/etc/nginx/sites-available/core.j2 @@ -0,0 +1,222 @@ +#### +# {{ ansible_managed }} +#### + + +{% block extra_upstreams %} +{% endblock %} + +{% block server_info %} +## SERVER INFO +server { + server_name {% if item.name is string %}{{ item.name }}{% else %}{{ item.name | join(' ') }}{% endif %}; +{% if item.proto == 'http' %} + listen {{ item.listen }} {% if nginx_default_vhost == item.name %} default_server{% endif %}; +{% endif %} +{% if item.proto == 'https' %} + listen {{ item.listen }} ssl {% if item.http2 is defined %}http2{% endif %}; + + ssl_certificate {{ nginx_ssl_dir + '/' + item.ssl_name + '/' + 'fullchain.pem' + ';' }} + ssl_certificate_key {{ nginx_ssl_dir + '/' + item.ssl_name + '/' + 'privkey.pem;' }} + +{% endif %} + server_tokens off; +{% if item.max_upload is defined %} + client_max_body_size {{ item.max_upload }}; +{% if item.htpasswd is defined %} + {{ htpasswd(item.htpasswd, 1) }} +{% endif %} +{% endif %} +{% endblock %} +{% block root %} +{% endblock %} +{% block logs %} + +## LOGS +{% if item.use_access_log is defined and item.use_access_log == 'true' %} + access_log {{ nginx_log_dir }}/{{ item.name }}_access.log combined; +{% else %} + access_log off; +{% endif %} +{% if item.use_error_log is defined and item.use_error_log == 'true' %} + error_log {{ nginx_log_dir }}/{{ item.name }}_error.log {{ item.nginx_error_log_level }}; +{% else %} + error_log off; +{% endif %} +{% endblock %} + +{% block headers %} +{% if item.headers is defined and item.headers == 'none' %} +{% else %} +## HEADERS +{% if item.secure_site is defined %} + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy {{ item.referrer | default('no-referrer') }}; +{% if item.header_sameorigin is defined %} + add_header X-Frame-Options "SAMEORIGIN"; +{% endif %} +{% endif %} +{% if item.nginx_HSTS_policy is defined %} + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; +{% endif %} +{% if item.referrer is defined %} + add_header Referrer-Policy no-referrer; +{% endif %} +{% if item.csp is defined %} + add_header Content-Security-Policy "{{ item.csp }}"; +{% endif %} +{% if item.cto is defined %} +{% if item.cto == 'none' %} +{% else %} + add_header X-Content-Type-Options {{ item.cto }}; +{% endif %} +{% else %} + add_header X-Content-Type-Options nosniff; +{% endif %} +{%if item.xss is defined %} +{% if item.xss == 'none' %} +{% else %} + add_header X-XSS-Protection "{{ item.xss }}"; +{% endif %} +{% else %} + add_header X-XSS-Protection "1; mode=block"; +{% endif %} +{% if item.robots is defined %} + add_header X-Robots-Tag "{{ item.robots }}"; +{% else %} + add_header X-Robots-Tag none; +{% endif %} +{% endif %} +{% endblock %} + +{% block location %} +{% endblock %} +{% block extra_locations %} +{% endblock %} +{% block custom_locations %} +{% if item.custom_locations is defined %} + +## CUSTOM LOCATIONS +{% for location in item.custom_locations %} + location {{ location.name }} { +{% for item in location.options %} + {{ item }} +{% endfor %} + {% if location.headers is defined %} +{% for item in location.headers %} +{% if item.secure_site is defined %} + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy {{ item.referrer | default('no-referrer') }}; +{% if item.header_sameorigin is defined %} + add_header X-Frame-Options "SAMEORIGIN"; +{% endif %} +{% endif %} +{% if item.nginx_HSTS_policy is defined %} + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; +{% endif %} +{% if item.referrer is defined %} + add_header Referrer-Policy no-referrer; +{% endif %} +{% if item.csp is defined %} + add_header Content-Security-Policy "{{ item.csp }}"; +{% endif %} +{% if item.cto is defined %} +{% if item.cto == 'none' %} +{% else %} + add_header X-Content-Type-Options {{ item.cto }}; +{% endif %} +{% else %} + add_header X-Content-Type-Options nosniff; +{% endif %} +{%if item.xss is defined %} +{% if item.xss == 'none' %} +{% else %} + add_header X-XSS-Protection "{{ item.xss }}"; +{% endif %} +{% else %} + add_header X-XSS-Protection "1; mode=block"; +{% endif %} +{% if item.robots is defined %} + add_header X-Robots-Tag {{ item.robots }}; +{% else %} + add_header X-Robots-Tag none; +{% endif %} + +{% endfor %} +{% endif %} + } +{% endfor %} +{% endif %} +{% endblock %} +{% block local_content %} +{% endblock %} +{% block app_specific %} +{% endblock %} +{% if item.letsencrypt == 'true' %} + location ^~ /.well-known/acme-challenge { + root {{ letsencrypt_webroot_path }}; + try_files $uri =404; + } +{% endif %} +{% block extras %} +{% if item.more is defined and item.more is iterable %} + +{% for line in item.more %} + {{ line }} +{% endfor %} +{% endif %} +{% endblock %} +} + +{% block redirects %} +{% if item.www is defined %} +server { + listen 80; + server_name www.{{ item.name }}; + return 301 https://{{ item.name }}$request_uri; +} +{% endif %} +{% if item.redirect_https is defined and item.redirect_https %} + +## REDIRECTS + +### http to https +server { + + listen 80; + + server_name {% if item.name is string %}{{ item.name }}{% else %}{{ item.name | join(' ') }}{% endif %}; + return 301 https://{{ item.name }}$request_uri; +{% if item.letsencrypt == 'true' %} + + location /.well-known/acme-challenge { + root {{ letsencrypt_webroot_path }}; + try_files $uri =404; + } +{% endif %} +} + + +{% if item.www is defined %} +### www to fwdn +server { + + listen 443 ssl; + ssl_certificate {{ nginx_ssl_dir + '/www.' + item.ssl_name + '/' + 'fullchain.pem' + ';' }} + ssl_certificate_key {{ nginx_ssl_dir + '/www.' + item.ssl_name + '/' + 'privkey.pem;' }} + + server_name www.{% if item.name is string %}{{ item.name }}{% else %}{{ item.name | join(' ') }}{% endif %}; + return 301 https://{{ item.name }}{% if '443' not in item.listen %}:item.listen[0]{% endif %}$request_uri; +{% if item.letsencrypt == 'true' %} + + location /.well-known/acme-challenge { + root {{ letsencrypt_webroot_path }}; + try_files $uri =404; + } +{% endif %} +} +{% endif %} +{% endif %} +{% endblock %} diff --git a/templates/etc/nginx/sites-available/cryptpad.j2 b/templates/etc/nginx/sites-available/cryptpad.j2 new file mode 100644 index 0000000..790da4e --- /dev/null +++ b/templates/etc/nginx/sites-available/cryptpad.j2 @@ -0,0 +1,179 @@ +{% extends "core.j2" %} + +{% block app_specific %} + # CryptPad serves static assets over these two domains. + # `main_domain` is what users will enter in their address bar. + # Privileged computation such as key management is handled in this scope + # UI content is loaded via the `sandbox_domain`. + # "Content Security Policy" headers prevent content loaded via the sandbox + # from accessing privileged information. + # These variables must be different to take advantage of CryptPad's sandboxing techniques. + # In the event of an XSS vulnerability in CryptPad's front-end code + # this will limit the amount of information accessible to attackers. + set $main_domain "{{ item.ssl_name }}"; + set $sandbox_domain "sandbox.{{ item.ssl_name }}"; + + # CryptPad's dynamic content (websocket traffic and encrypted blobs) + # can be served over separate domains. Using dedicated domains (or subdomains) + # for these purposes allows you to move them to a separate machine at a later date + # if you find that a single machine cannot handle all of your users. + # If you don't use dedicated domains, this can be the same as $main_domain + # If you do, they'll be added as exceptions to any rules which block connections to remote domains. + set $api_domain "{{ item.ssl_name }}"; + set $files_domain "{{ item.ssl_name }}"; + + add_header Access-Control-Allow-Origin "*"; + + set $coop ''; + if ($uri ~ ^\/sheet\/.*$) { set $coop 'same-origin'; } + + # Enable SharedArrayBuffer in Firefox (for .xlsx export) + add_header Cross-Origin-Resource-Policy cross-origin; + add_header Cross-Origin-Opener-Policy $coop; + add_header Cross-Origin-Embedder-Policy require-corp; + # any static assets loaded with "ver=" in their URL will be cached for a year + if ($args ~ ver=) { + set $cacheControl max-age=31536000; + } + if ($uri ~ ^/.*(\/|\.html)$) { + set $cacheControl no-cache; + } + # Will not set any header if it is emptystring + add_header Cache-Control $cacheControl; + + # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain + set $styleSrc "'unsafe-inline' 'self' ${main_domain}"; + + # connect-src restricts URLs which can be loaded using script interfaces + set $connectSrc "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}"; + + # fonts can be loaded from data-URLs or the main domain + set $fontSrc "'self' data: ${main_domain}"; + + # images can be loaded from anywhere, though we'd like to deprecate this as it allows the use of images for tracking + set $imgSrc "'self' data: * blob: ${main_domain}"; + + # frame-src specifies valid sources for nested browsing contexts. + # this prevents loading any iframes from anywhere other than the sandbox domain + set $frameSrc "'self' ${sandbox_domain} blob:"; + + # specifies valid sources for loading media using video or audio + set $mediaSrc "'self' data: * blob: ${main_domain}"; + + # defines valid sources for webworkers and nested browser contexts + # deprecated in favour of worker-src and frame-src + set $childSrc "https://${main_domain}"; + + # specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts. + # supercedes child-src but is unfortunately not yet universally supported. + set $workerSrc "https://${main_domain}"; + + # script-src specifies valid sources for javascript, including inline handlers + set $scriptSrc "'self' resource: ${main_domain}"; + + set $unsafe 0; + # the following assets are loaded via the sandbox domain + # they unfortunately still require exceptions to the sandboxing to work correctly. + if ($uri = "/sheet/inner.html") { set $unsafe 1; } + if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; } + + # everything except the sandbox domain is a privileged scope, as they might be used to handle keys + if ($host != $sandbox_domain) { set $unsafe 0; } + + # privileged contexts allow a few more rights than unprivileged contexts, though limits are still applied + if ($unsafe) { + set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ${main_domain}"; + } + + # Finally, set all the rules you composed above. + add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + +{% endblock %} + +{% block root %} + + root {{ nginx_www_dir }}{{ item.root }}; + index index.html; + error_page 404 /customize.dist/404.html; + + # Finally, serve anything the above exceptions don't govern. + try_files /www/$uri /www/$uri/index.html /customize/$uri; +{% endblock %} +{% block location%} + location ^~ /cryptpad_websocket { + proxy_pass http://{{ item.proxy_pass }}:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket support (nginx 1.4) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + } + + location ^~ /customize.dist/ { + # This is needed in order to prevent infinite recursion between /customize/ and the root + } + + # try to load customizeable content via /customize/ and fall back to the default content + # located at /customize.dist/ + # This is what allows you to override behaviour. + location ^~ /customize/ { + rewrite ^/customize/(.*)$ $1 break; + try_files /customize/$uri /customize.dist/$uri; + } + + # /api/config is loaded once per page load and is used to retrieve + # the caching variable which is applied to every other resource + # which is loaded during that session. + location = /api/config { + proxy_pass http://{{ item.proxy_pass }}:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # encrypted blobs are immutable and are thus cached for a year + location ^~ /blob/ { + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'application/octet-stream; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + add_header Cache-Control max-age=31536000; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length'; + add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length'; + try_files $uri =404; + } + + # the "block-store" serves encrypted payloads containing users' drive keys + # these payloads are unlocked via login credentials. They are mutable + # and are thus never cached. They're small enough that it doesn't matter, in any case. + location ^~ /block/ { + add_header Cache-Control max-age=0; + try_files $uri =404; + } + + # This block provides an alternative means of loading content + # otherwise only served via websocket. This is solely for debugging purposes, + # and is thus not allowed by default. + location ^~ /datastore/ { + add_header Cache-Control max-age=0; + try_files $uri =404; + } + + # The nodejs server has some built-in forwarding rules to prevent + # URLs like /pad from resulting in a 404. This simply adds a trailing slash + # to a variety of applications. + location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams)$ { + rewrite ^(.*)$ $1/ redirect; + } +{% endblock %} + diff --git a/templates/etc/nginx/sites-available/expired.j2 b/templates/etc/nginx/sites-available/expired.j2 new file mode 100644 index 0000000..edc677e --- /dev/null +++ b/templates/etc/nginx/sites-available/expired.j2 @@ -0,0 +1,16 @@ +#### +# {{ ansible_managed }} +#### + + +### HTTP + +server { + server_name {{ item.name }}; + return 301 {{ item.send_to }}; +} + +server { + server_name www.{{ item.name }}; + return 301 $scheme://{{ item.name }}$request_uri; +} diff --git a/templates/etc/nginx/sites-available/framadate.j2 b/templates/etc/nginx/sites-available/framadate.j2 new file mode 100644 index 0000000..b376741 --- /dev/null +++ b/templates/etc/nginx/sites-available/framadate.j2 @@ -0,0 +1,51 @@ +{% extends "core.j2" %} + +{% block location %} + +## LOCATIONS + location / { + rewrite "^/admin$" "/admin/" permanent; + + # Clean URL + rewrite "^/([a-zA-Z0-9-]+)$" "/studs.php?poll=$1" last; + rewrite "^/([a-zA-Z0-9-]+)/action/([a-zA-Z_-]+)/(.+)$" "/studs.php?poll=$1&$2=$3" last; + rewrite "^/([a-zA-Z0-9-]+)/vote/([a-zA-Z0-9]{16})$" "/studs.php?poll=$1&vote=$2" last; + rewrite "^/([a-zA-Z0-9]{24})/admin$" "/adminstuds.php?poll=$1" last; + rewrite "^/([a-zA-Z0-9]{24})/admin/vote/([a-zA-Z0-9]{16})$" "/adminstuds.php?poll=$1&vote=$2" last; + rewrite "^/([a-zA-Z0-9]{24})/admin/action/([a-zA-Z_-]+)(/([A-Za-z0-9]+))?$" "/adminstuds.php?poll=$1&$2=$4" last; + } + + location ~^/(\.git)/{ + deny all; + } + + location ~ /\. { + deny all; + } + + location ~ ^/composer\.json.*$|^/composer\.lock.*$|^/php\.ini.*$|^/.*\.sh { + deny all; + } + + location /admin/ { + auth_basic "closed site"; + auth_basic_user_file {{ nginx_www_dir }}/{{ item.root }}/admin/.htpasswd; + + location ~ \.php$ { + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include /etc/nginx/fastcgi_params; + fastcgi_pass unix:{{ pool_listen }}; + } + + try_files $uri $uri/ =401; + } + + location ~ \.php$ { + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_index index.php; + include /etc/nginx/fastcgi_params; + fastcgi_pass unix:{{ pool_listen }}; + } + +{% endblock %} + diff --git a/templates/etc/nginx/sites-available/grav.j2 b/templates/etc/nginx/sites-available/grav.j2 new file mode 100644 index 0000000..8a1f524 --- /dev/null +++ b/templates/etc/nginx/sites-available/grav.j2 @@ -0,0 +1,47 @@ +{% extends "core.j2" %} + +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index index.html index.php; +{% endblock %} + +{% block location %} + +## LOCATIONS + location / { + try_files $uri $uri/ /index.php?_url=$uri&$query_string; + } + + location /favicon.ico { + alias {{ nginx_www_dir }}{{ item.root }}/favicon.png; + } + + # deny all direct access for these folders + location ~* /(.git|cache|bin|logs|backup|tests)/.*$ { + return 403; + } + + # deny running scripts inside core system folders + location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { + return 403; + } + + # deny running scripts inside user folder + location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { + return 403; + } + + # deny access to specific files in the root folder + location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { + return 403; + } + + location ~ \.php$ { +{% if item.upstream_params is defined and item.upstream_params is iterable %} +{% for param in item.upstream_params %} + {{ param }} +{% endfor %} +{% endif %} + } +{% endblock %} + diff --git a/templates/etc/nginx/sites-available/hubzilla.j2 b/templates/etc/nginx/sites-available/hubzilla.j2 new file mode 100644 index 0000000..9a9fe3a --- /dev/null +++ b/templates/etc/nginx/sites-available/hubzilla.j2 @@ -0,0 +1,65 @@ +{% extends "core.j2" %} + +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index index.php; + charset utf-8; + include mime.types; + autoindex off +{% endblock %} + +{% block location %} + +## LOCATIONS + location / { + rewrite ^/\.well\-known/.* /index.php?q=$1; + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php?q=$1; + } + } + + location ^~ /.well-known/ { + allow all; + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php?q=$1; + } + } + + location ~* \.(jpg|jpeg|gif|png|ico|css|js|htm|html|map|ttf|woff|woff2|svg)$ { + expires 30d; + try_files $uri /index.php?q=$uri&$args; + } + + # block these file types + location ~* \.(tpl|md|tgz|log|out)$ { + deny all; + } + + location ~* \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; +{% if item.upstream_params is defined and item.upstream_params is iterable %} +{% for param in item.upstream_params %} + {{ param }} +{% endfor %} +{% endif %} + + } + + location ~ /\. { + deny all; + } + + location ~ "(^|/)store" { + deny all; + } + + location ~ "(^|/)\.git" { + return 403; + } + + location ~ /util { + deny all; + } + +{% endblock %} \ No newline at end of file diff --git a/templates/etc/nginx/sites-available/letsencrypt.j2 b/templates/etc/nginx/sites-available/letsencrypt.j2 new file mode 100644 index 0000000..143f8f5 --- /dev/null +++ b/templates/etc/nginx/sites-available/letsencrypt.j2 @@ -0,0 +1,14 @@ +#### +# {{ ansible_managed }} +#### + +server { + + listen 80; + server_name {{ item.name }}; + + location /.well-known/acme-challenge { + root {{ letsencrypt_webroot_path }}; + try_files $uri =404; + } +} diff --git a/templates/etc/nginx/sites-available/lufi.j2 b/templates/etc/nginx/sites-available/lufi.j2 new file mode 100644 index 0000000..eccda41 --- /dev/null +++ b/templates/etc/nginx/sites-available/lufi.j2 @@ -0,0 +1,33 @@ +{% extends "core.j2" %} + +{% block location %} + +## LOCATIONS + location / { + proxy_pass {{ item.upstream_proto }}://{{ item.upstream_name }}:{{ item.upstream_port}}; + # Add cache for static files + if ($request_uri ~* ^/(img|css|font|js)/) { + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + add_header Cache-Control "public, max-age=315360000"; + } + + # HTTPS only header, improves security + add_header Strict-Transport-Security "max-age=15768000"; + + # Really important! Lufi uses WebSocket, it won't work without this + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # If you want to log the remote port of the file senders, you'll need that + proxy_set_header X-Remote-Port $remote_port; + proxy_set_header X-Forwarded-Proto $scheme; + + # We expect the downstream servers to redirect to the right hostname, so don't do any rewrites here. + proxy_redirect off; + } +{% endblock %} + diff --git a/templates/etc/nginx/sites-available/maintenance.j2 b/templates/etc/nginx/sites-available/maintenance.j2 new file mode 100644 index 0000000..c522429 --- /dev/null +++ b/templates/etc/nginx/sites-available/maintenance.j2 @@ -0,0 +1,6 @@ +{% extends "core.j2" %} + +{%location root %} + root {{ nginx_maintenance_page_root }}; + index index.html; +{% endblock %} diff --git a/templates/etc/nginx/sites-available/mumble-web.j2 b/templates/etc/nginx/sites-available/mumble-web.j2 new file mode 100644 index 0000000..40c5e41 --- /dev/null +++ b/templates/etc/nginx/sites-available/mumble-web.j2 @@ -0,0 +1,21 @@ +{% extends core.j2 %} + +{% block extra_upstreams %} +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +{% endblock %} + +{% block location %} + location / { + root {{ item.root }}; + } + location /server { + proxy_pass {{ item.mumble_proto }}://{{ item.mumble_server }}:{{ item.mumble_port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/nextcloud.j2 b/templates/etc/nginx/sites-available/nextcloud.j2 new file mode 100644 index 0000000..a59fb07 --- /dev/null +++ b/templates/etc/nginx/sites-available/nextcloud.j2 @@ -0,0 +1,137 @@ +{% extends "core.j2" %} + +{% block extra_upstreams %} +upstream php-handler { + server {{ item.fastcgi_pass }}; +} +{% endblock %} + +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index | default('index.html index.htm') }}; + + error_page 403 /core/templates/403.php; + error_page 404 /core/templates/404.php; +{% endblock %} + +{% block headers %} + +## HEADERS + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; +{% endblock %} + +{% block location %} + # Path to the root of your installation + location = /robots.txt { + {% if item.robot_txt is defined %} + allow all; + log_not_found off; + access_log off; + {% else %} + deny all; + log_not_found off; + access_log off; + {% endif %} + } +{% for app in nc_apps %} +{% if app.name == 'user_webfinger' and app.state == 'enabled' %} + # The following 2 rules are only needed for the user_webfinger app. + rewrite ^/.well-known/host-meta /public.php?service=host-meta last; + rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; +{% endif %} +{% if app.name == 'social' and app.state == 'enabled'%} + # The following rule is only needed for the Social app. + rewrite ^/.well-known/webfinger /public.php?service=webfinger last; +{% endif %} +{% endfor %} + + location = /.well-known/carddav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + location = /.well-known/caldav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + + location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ { + deny all; + } + location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) { + deny all; + } + + location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) { + fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; + try_files $fastcgi_script_name =404; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTPS on; + # Avoid sending the security headers twice + fastcgi_param modHeadersAvailable true; + # Enable pretty urls + fastcgi_param front_controller_active true; + fastcgi_pass php-handler; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + } + + location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) { + try_files $uri/ =404; + index index.php; + } + + # Adding the cache control header for js, css and map files + # Make sure it is BELOW the PHP block + location ~ \.(?:css|js|woff2?|svg|gif|map)$ { + try_files $uri /index.php$request_uri; + add_header Cache-Control "public, max-age=15778463"; + # Add headers to serve security related headers (It is intended to + # have those duplicated to the ones above) + # Before enabling Strict-Transport-Security headers please read into + # this topic first. + #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Optional: Don't log access to assets + access_log off; + } + + location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap)$ { + try_files $uri /index.php$request_uri; + # Optional: Don't log access to other assets + access_log off; + } +{% endblock %} + +{% block app_specific %} + fastcgi_buffers 64 4K; + + # Enable gzip but do not remove ETag headers + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + +{% endblock %} diff --git a/templates/etc/nginx/sites-available/privatebin.j2 b/templates/etc/nginx/sites-available/privatebin.j2 new file mode 100644 index 0000000..3a32da5 --- /dev/null +++ b/templates/etc/nginx/sites-available/privatebin.j2 @@ -0,0 +1,55 @@ +{% extends "core.j2" %} +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index }}; + try_files {{ item.override_try_files | default('$uri $uri/ /index.php') }}; +{% endblock %} + +{% block location %} + +## LOCATIONS + location ~ \.php$ { +{% if item.upstream_params is defined and item.upstream_params is iterable %} +{% for param in item.upstream_params %} + {{ param }} +{% endfor %} +{% endif %} + include fastcgi_params; + } + + location ~ /\. { + deny all; + } + + location ~* \.(txt|js|css|png|jpe?g|gif|ico|svg)$ { + expires 30d; + log_not_found off; + } + +{% endblock %} + +{% block extra_upstreams %} +map $http_user_agent $pastebin_badagent { + ~*bot 1; + ~*spider 1; + ~*crawl 1; + ~https?:// 1; + WhatsApp 1; + SkypeUriPreview 1; + facebookexternalhit 1; +} +{% endblock %} + +{% block app_specific %} + if ($pastebin_badagent) { + return 403; + } + + location /cfg { + return 403; + } + + location /data { + deny all; + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/proxy.j2 b/templates/etc/nginx/sites-available/proxy.j2 new file mode 100644 index 0000000..f25989a --- /dev/null +++ b/templates/etc/nginx/sites-available/proxy.j2 @@ -0,0 +1,133 @@ +{% extends "core.j2" %} + +{% block location %} + +## LOCATIONS +# ROOT LOCATION + location / { + proxy_pass {{ item.upstream_proto }}://{{ item.upstream_name }}:{{ item.upstream_port}}; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_redirect off; +{% if item.secure_cookie is defined %} + proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; +{% endif %} +{% if item.root_custom_headers is defined %} +{% for header in item.root_custom_headers %} +{% if header.secure_site is defined %} + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy {{ item.referrer | default('no-referrer') }}; +{% if header.header_sameorigin is defined %} + add_header X-Frame-Options "SAMEORIGIN"; +{% endif %} +{% endif %} +{% if header.nginx_HSTS_policy is defined %} + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; +{% endif %} +{% if header.referrer is defined %} + add_header Referrer-Policy no-referrer; +{% endif %} +{% if header.csp is defined %} + add_header Content-Security-Policy "{{ header.csp }}"; +{% endif %} +{% if header.cto is defined %} +{% if header.cto == 'none' %} +{% else %} + add_header X-Content-Type-Options {{ header.cto }}; +{% endif %} +{% else %} + add_header X-Content-Type-Options nosniff; +{% endif %} +{%if header.xss is defined %} +{% if header.xss == 'none' %} +{% else %} + add_header X-XSS-Protection "{{ header.xss }}"; +{% endif %} +{% else %} + add_header X-XSS-Protection "1; mode=block"; +{% endif %} +{% if header.robots is defined %} + add_header X-Robots-Tag "{{ header.robots }}"; +{% else %} + add_header X-Robots-Tag none; +{% endif %} + +{% endfor %} +{% endif %} + + } + +{% if item.favicon is defined %} + location /favicon.ico { + alias {{ item.favicon }}; + expires 30d; + access_log off; + log_not_found off; + } +{% endif %} + +{% if item.extra_locations is defined %} +# EXTRA LOCATIONS +{% for locations in item.extra_locations %} + location {{ locations.name }} { + proxy_pass {{ locations.upstream_proto }}://{{ locations.upstream_name }}:{{ locations.upstream_port}}; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_redirect off; +{% if item.secure_cookie is defined %} + proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; +{% endif %} +{% if locations.headers is defined %} +{% for item in locations.headers %} +{% if item.secure_site is defined %} + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy {{ item.referrer | default('no-referrer') }}; +{% if item.header_sameorigin is defined %} + add_header X-Frame-Options "SAMEORIGIN"; +{% endif %} +{% endif %} +{% if item.nginx_HSTS_policy is defined %} + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; +{% endif %} +{% if item.referrer is defined %} + add_header Referrer-Policy no-referrer; +{% endif %} +{% if item.csp is defined %} + add_header Content-Security-Policy "{{ item.csp }}"; +{% endif %} +{% if item.cto is defined %} +{% if item.cto == 'none' %} +{% else %} + add_header X-Content-Type-Options {{ item.cto }}; +{% endif %} +{% else %} + add_header X-Content-Type-Options nosniff; +{% endif %} +{%if item.xss is defined %} +{% if item.xss == 'none' %} +{% else %} + add_header X-XSS-Protection "{{ item.xss }}"; +{% endif %} +{% else %} + add_header X-XSS-Protection "1; mode=block"; +{% endif %} +{% if item.robots is defined %} + add_header X-Robots-Tag "{{ item.robots }}"; +{% else %} + add_header X-Robots-Tag none; +{% endif %} + +{% endfor %} +{% endif %} + } + +{% endfor %} +{% endif %} +{% endblock %} +``` \ No newline at end of file diff --git a/templates/etc/nginx/sites-available/pwm.j2 b/templates/etc/nginx/sites-available/pwm.j2 new file mode 100644 index 0000000..3cd177d --- /dev/null +++ b/templates/etc/nginx/sites-available/pwm.j2 @@ -0,0 +1,33 @@ +{% extends "core.j2" %} + +{% block location %} + +## LOCATIONS + location / { + rewrite ^ {{ item.proto }}://{{ item.name }}/{{item.root }}/ last; + } + + location /pwm { + proxy_pass {{ item.upstream_proto }}://{{ item.upstream_name }}:{{ item.upstream_port}}; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_redirect off; + + proxy_max_temp_file_size 0; + proxy_buffering off; + proxy_connect_timeout 60; + proxy_send_timeout 60; + proxy_read_timeout 60; + proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; + + } + + {% if ansible_date_time.weekday_number == '6' or ansible_date_time.weekday_number == '7' %} + location /pwm/public/newuser { + root /var/www/out-of-order; + index index.html; + } +{% endif %} +{% endblock %} diff --git a/templates/etc/nginx/sites-available/roundcube.j2 b/templates/etc/nginx/sites-available/roundcube.j2 new file mode 100644 index 0000000..1134873 --- /dev/null +++ b/templates/etc/nginx/sites-available/roundcube.j2 @@ -0,0 +1,40 @@ +{% extends "core.j2" %} + +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index }}; +{% endblock %} + +{% block location %} +## LOCATIONS + location ~ /\. { + deny all; + } + + location = /favicon.ico { + expires 30d; + access_log off; + log_not_found off; + } + + location ~* \.(txt|js|css|png|jpe?g|gif|ico|svg)$ { + expires 30d; + log_not_found off; + } + + location ~ ^/(README.md|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ { + deny all; + } + + location ~ ^/(config|temp|logs)/ { + deny all; + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_pass {{ item.fastcgi_pass }}; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/searx.j2 b/templates/etc/nginx/sites-available/searx.j2 new file mode 100644 index 0000000..3db3509 --- /dev/null +++ b/templates/etc/nginx/sites-available/searx.j2 @@ -0,0 +1,20 @@ +{% extends "core.j2" %} + +{%block root %} + root {{ searx_app_dir }}/searx; +{% endblock %} + +{% block location %} + location / { +{% if item.filtron == 'true' %} + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + proxy_pass http://127.0.0.1:4004/; +{% else %} + include uwsgi_params; + uwsgi_pass unix:/run/uwsgi/app/searx/socket; +{% endif %} + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/taiga.j2 b/templates/etc/nginx/sites-available/taiga.j2 new file mode 100644 index 0000000..3ff1b61 --- /dev/null +++ b/templates/etc/nginx/sites-available/taiga.j2 @@ -0,0 +1,35 @@ +{% extends "core.j2" %} +{%block app_specific %} + large_client_header_buffers 4 32k; + client_max_body_size 50M; + charset utf-8; +{% endblock %} + +{% block location %} + location / { + root {{ taiga_front_dir }}; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass {{ taiga_api_url }}; + proxy_redirect off; + } + + location /static { + alias {{ taiga_back_dir }}/static; + } + + location /media { + alias {{ taiga_back_dir }}/media; + } + + location /user-settings/user-change-password { + return 301 {{ taiga_user_change_passwd_url }}; + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/ttrss.j2 b/templates/etc/nginx/sites-available/ttrss.j2 new file mode 100644 index 0000000..733596b --- /dev/null +++ b/templates/etc/nginx/sites-available/ttrss.j2 @@ -0,0 +1,48 @@ +{% extends "core.j2" %} + +{% block root %} +root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index }}; + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + +{% endblock %} +{% block location %} + +## LOCATIONS + location /tt-rss/cache { + aio threads; + deny all; + } + + location = /tt-rss/config.php { + deny all; + } + + location /tt-rss/backups { + deny all; + } + + location / { + try_files $uri $uri/ =404; + } + + location ~ \.php$ { + # regex to split $uri to $fastcgi_script_name and $fastcgi_path + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + + # Check that the PHP script exists before passing it + try_files $fastcgi_script_name =404; + + # Bypass the fact that try_files resets $fastcgi_path_info + # see: http://trac.nginx.org/nginx/ticket/321 + set $path_info $fastcgi_path_info; + fastcgi_param PATH_INFO $path_info; + + fastcgi_index index.php; + include fastcgi.conf; + + fastcgi_pass {{ item.fastcgi_pass }}; + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/wp.j2 b/templates/etc/nginx/sites-available/wp.j2 new file mode 100644 index 0000000..f115a54 --- /dev/null +++ b/templates/etc/nginx/sites-available/wp.j2 @@ -0,0 +1,34 @@ +{% extends "core.j2" %} + +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index }}; + try_files {{ item.override_try_files | default('$uri $uri/ /index.php') }}; +{% endblock %} + +{% block location %} + location ~ \.php$ { +{% if item.upstream_params is defined and item.upstream_params is iterable %} +{% for param in item.upstream_params %} + {{ param }} +{% endfor %} +{% endif %} + include fastcgi_params; + } +{% endblock %} + +{% block app_specific %} + + location ^~ /blogs.dir { + internal; + alias {{ nginx_www_dir }}/{{ item.root }}/wp-content/blogs.dir ; + access_log off; + log_not_found off; + expires max; + } + if (!-e $request_filename) { + rewrite /wp-admin$ $scheme://$host$uri/ permanent; + rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last; + rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last; + } +{% endblock %} diff --git a/templates/etc/nginx/sites-available/wpfarm.j2 b/templates/etc/nginx/sites-available/wpfarm.j2 new file mode 100644 index 0000000..4da0a7a --- /dev/null +++ b/templates/etc/nginx/sites-available/wpfarm.j2 @@ -0,0 +1,73 @@ +{% extends "core.j2" %} +{% block root %} + root {{ nginx_www_dir }}{{ item.root }}; + index {{ item.index }}; + try_files {{ item.override_try_files | default('$uri $uri/ /index.php') }}; +{% endblock %} + +{% block location %} + location ~ \.php$ { +{% if item.upstream_params is defined and item.upstream_params is iterable %} +{% for param in item.upstream_params %} + {{ param }} +{% endfor %} +{% endif %} + include fastcgi_params; + } +{% endblock %} + +{% block app_specific %} + + # BEGIN W3TC Browser Cache + rewrite ^/wp-content/cache/minify.*/w3tc_rewrite_test$ /wp-content/plugins/w3-total-cache/pub/minify.php?w3tc_rewrite_test=1 last; + rewrite ^/wp-content/cache/minify/(.+/[X]+\.css)$ /wp-content/plugins/w3-total-cache/pub/minify.php?test_file=$1 last; + rewrite ^/wp-content/cache/minify/(.+\.(css|js))$ /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1 last; + + # END W3TC Minify core + + # WordPress multisite subdirectory rules. + # Designed to be included in any server {} block. + + # This order might seem weird - this is attempted to match last if rules below fail. + # http://wiki.nginx.org/HttpCoreModule + #location / { + # try_files $uri $uri/ /index.php?$args; + #} + + location ~ ^(/[^/]+/)?files/(.+) { + try_files /wp-content/blogs.dir/$blogid/files/[ /wp-includes/ms-files.php?file=[ ; + access_log off; + log_not_found off; + expires max; + } + + + # Rewrite multisite '.../wp-.*' and '.../*.php'. + if (!-e $request_filename) { + rewrite /wp-admin$ $scheme://$host$uri/ permanent; + rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) ( last; + rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ ( last; + } + + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + + # Deny access to any files with a .php extension in the uploads directory + # Works in sub-directory installs and also in multisite network + # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) + location ~* /(?:uploads|files)/.*\.php$ { + deny all; + } + +{% endblock %} + +{% block extra_upstreams %} +map $http_host $blogid { + default 0; +} +{% endblock %} diff --git a/templates/etc/nginx/sites-available/zabbix_check.j2 b/templates/etc/nginx/sites-available/zabbix_check.j2 new file mode 100644 index 0000000..0efc4c2 --- /dev/null +++ b/templates/etc/nginx/sites-available/zabbix_check.j2 @@ -0,0 +1,10 @@ +server { + listen 10061; + + location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + deny all; + } +}