diff --git a/README.md b/README.md
index 1b74490..c3f9859 100644
--- a/README.md
+++ b/README.md
@@ -285,7 +285,7 @@ NOTE:
- You will need to have two files, named `customssl.crt` and `customssl.key`.
`customssl.crt` should contain the main certificate concatenated with intermediate and
root certificates.
-- Copy these two files into `dhis2-server-tools/deploy/roles/proxy/files/` directory, preserving their names.
+- Copy these two files into `dhis2-server-tools/deploy/roles/create-instance/files/` directory, preserving their names.
- Edit hosts file and set `TLS_TYPE=customssl`
```
vim dhis2-server-tools/deploy/inventory/hosts
diff --git a/deploy/inventory/hosts.template b/deploy/inventory/hosts.template
index f4d69b4..7444950 100644
--- a/deploy/inventory/hosts.template
+++ b/deploy/inventory/hosts.template
@@ -70,7 +70,7 @@ guest_os_arch=amd64
# lxd_storage_driver=dir
-# Options: nginx, apache2 defaults to nginx
+# Options: nginx, apache2, openresty defaults to nginx
proxy=nginx
# Enable proxy_protocol if behind a reverse proxy that sends proxy protocol headers
diff --git a/deploy/roles/create-instance/files/nginx/default.conf b/deploy/roles/create-instance/files/nginx/default.conf
index 5faa3b4..c4baa3e 100644
--- a/deploy/roles/create-instance/files/nginx/default.conf
+++ b/deploy/roles/create-instance/files/nginx/default.conf
@@ -2,7 +2,6 @@
ssl_certificate /etc/ssl/selfsigned/selfsigned.crt;
ssl_certificate_key /etc/ssl/selfsigned/selfsigned.key;
ssl_protocols TLSv1.2 TLSv1.3;
-#ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10h;
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_prefer_server_ciphers on;
@@ -11,12 +10,11 @@ ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
underscores_in_headers on;
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "no-referrer";
-add_header X-XSS-Protection "1; mode=block" always;
-keepalive_timeout 10;
-send_timeout 10;
+keepalive_timeout 65;
+send_timeout 60s;
error_log /var/log/nginx/error.log info;
ssl_session_tickets off;
-client_body_timeout 10;
-client_header_timeout 10;
+client_body_timeout 60s;
+client_header_timeout 30s;
client_max_body_size 100M;
-proxy_set_header X-Frame-Options "SAMEORIGIN";
+add_header X-Frame-Options "SAMEORIGIN" always;
diff --git a/deploy/roles/create-instance/files/nginx/static.conf b/deploy/roles/create-instance/files/nginx/static.conf
index f074019..df60c36 100644
--- a/deploy/roles/create-instance/files/nginx/static.conf
+++ b/deploy/roles/create-instance/files/nginx/static.conf
@@ -1,6 +1,5 @@
#
ssl_protocols TLSv1.2 TLSv1.3;
-#ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10h;
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_prefer_server_ciphers on;
@@ -10,11 +9,10 @@ ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
underscores_in_headers on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "no-referrer";
-add_header X-XSS-Protection "1; mode=block" always;
-keepalive_timeout 10; # helps mitigate denial of service attacks that establish too many persistent connections, exhausting server resources.
-send_timeout 10; # mitigate slow HTTP denial of service attacks by ensuring write operations taking up large amounts of time are closed.
+keepalive_timeout 65;
+send_timeout 60s;
error_log /var/log/nginx/error.log info;
-client_body_timeout 10;
-client_header_timeout 10;
+client_body_timeout 60s;
+client_header_timeout 30s;
client_max_body_size 100M;
-proxy_set_header X-Frame-Options "SAMEORIGIN";
+add_header X-Frame-Options "SAMEORIGIN" always;
diff --git a/deploy/roles/create-instance/files/openresty/default.conf b/deploy/roles/create-instance/files/openresty/default.conf
new file mode 100644
index 0000000..e8cc538
--- /dev/null
+++ b/deploy/roles/create-instance/files/openresty/default.conf
@@ -0,0 +1,20 @@
+#
+ssl_certificate /etc/ssl/selfsigned/selfsigned.crt;
+ssl_certificate_key /etc/ssl/selfsigned/selfsigned.key;
+ssl_protocols TLSv1.2 TLSv1.3;
+ssl_session_timeout 10h;
+ssl_dhparam /usr/local/openresty/nginx/conf/dhparams.pem;
+ssl_prefer_server_ciphers on;
+# This is quite strict. If you have much older windoze browsers
+ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
+underscores_in_headers on;
+# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+add_header Referrer-Policy "no-referrer";
+keepalive_timeout 65;
+send_timeout 60s;
+error_log /usr/local/openresty/nginx/logs/error.log info;
+ssl_session_tickets off;
+client_body_timeout 60s;
+client_header_timeout 30s;
+client_max_body_size 100M;
+add_header X-Frame-Options "SAMEORIGIN" always;
diff --git a/deploy/roles/create-instance/files/openresty/static.conf b/deploy/roles/create-instance/files/openresty/static.conf
new file mode 100644
index 0000000..8753a37
--- /dev/null
+++ b/deploy/roles/create-instance/files/openresty/static.conf
@@ -0,0 +1,18 @@
+#
+ssl_protocols TLSv1.2 TLSv1.3;
+ssl_session_timeout 10h;
+ssl_dhparam /usr/local/openresty/nginx/conf/dhparams.pem;
+ssl_prefer_server_ciphers on;
+ssl_session_tickets off;
+ # This is quite strict. If you have much older windoze browsers
+ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
+underscores_in_headers on;
+add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
+add_header Referrer-Policy "no-referrer";
+keepalive_timeout 65;
+send_timeout 60s;
+error_log /usr/local/openresty/nginx/logs/error.log info;
+client_body_timeout 60s;
+client_header_timeout 30s;
+client_max_body_size 100M;
+add_header X-Frame-Options "SAMEORIGIN" always;
diff --git a/deploy/roles/create-instance/handlers/main.yml b/deploy/roles/create-instance/handlers/main.yml
index 4ad6aae..e66c457 100644
--- a/deploy/roles/create-instance/handlers/main.yml
+++ b/deploy/roles/create-instance/handlers/main.yml
@@ -45,6 +45,17 @@
state: reloaded
enabled: true
+- name: Reload Openresty
+ ansible.builtin.service:
+ name: openresty
+ state: reloaded
+ enabled: true
+
+- name: Restart Openresty
+ ansible.builtin.service:
+ name: openresty
+ state: restarted
+
- name: Restart Munin-Node
ansible.builtin.service:
name: munin-node
diff --git a/deploy/roles/create-instance/tasks/proxy/openresty.yml b/deploy/roles/create-instance/tasks/proxy/openresty.yml
new file mode 100644
index 0000000..61ba4c2
--- /dev/null
+++ b/deploy/roles/create-instance/tasks/proxy/openresty.yml
@@ -0,0 +1,136 @@
+---
+# Get OpenResty/nginx version for conditional features (e.g., ssl_reject_handshake requires nginx 1.19.4+)
+- name: Get OpenResty version
+ ansible.builtin.command: openresty -v
+ register: openresty_version_output
+ changed_when: false
+ failed_when: false
+
+- name: Set nginx version fact from OpenResty
+ ansible.builtin.set_fact:
+ # openresty -v outputs "nginx version: openresty/1.x.x" — match either prefix.
+ # | default([]) guards against regex_search returning None (no match) before | first.
+ nginx_version: "{{ openresty_version_output.stderr | default('') | regex_search('(?:nginx|openresty)/([0-9.]+)', '\\1') | default([]) | first | default('0') }}"
+ when: openresty_version_output.rc == 0
+
+- name: Set nginx version to 0 if OpenResty not installed
+ ansible.builtin.set_fact:
+ nginx_version: "0"
+ when: openresty_version_output.rc != 0
+
+- name: Assert two instances using the same fqdn have unique Instance Names
+ ansible.builtin.assert:
+ that:
+ - hostvars[item]['dhis2_base_path'] != hostvars[ansible_loop.nextitem]['dhis2_base_path']
+ fail_msg: "Instances Names for {{ item }}: hostvars[item]['dhis2_base_path'] and {{ ansible_loop.nextitem }}: hostvars[item]['dhis2_base_path'] are the same"
+ loop: "{{ groups['instances'] }}"
+ loop_control:
+ extended: true
+ label: "{{ item }}"
+ when:
+ - ansible_loop.nextitem is defined
+ - hostvars[item]['dhis2_base_path'] is defined and hostvars[ansible_loop.nextitem]['dhis2_base_path'] is defined
+ - hostvars[item]['fqdn'] is defined and hostvars[ansible_loop.nextitem]['fqdn'] is defined
+ - hostvars[item]['fqdn'] == hostvars[ansible_loop.nextitem]['fqdn']
+
+- name: Include TLS configuration tasks
+ ansible.builtin.include_tasks: "tls/{{ TLS_TYPE | default('letsencrypt') }}.yml"
+ loop: "{{ groups['instances'] }}"
+ when:
+ - (hostvars[item]['fqdn'] | default('') | string | trim | length > 0)
+ - hostvars[item]['instance_state'] is undefined
+
+- name: Instance location configuration
+ ansible.builtin.template:
+ src: openresty/instance.j2
+ dest: /usr/local/openresty/nginx/conf/conf.d/upstream/{{ item | to_fixed_string }}.conf
+ owner: root
+ group: root
+ mode: "0640"
+ loop: "{{ groups['instances'] }}"
+ notify: Reload Openresty
+ when:
+ - hostvars[item]['instance_state'] is undefined
+
+- name: Include selfsigned TLS Certificate tasks, fqdn is not defined
+ ansible.builtin.include_tasks: ./tls/selfsigned.yml
+ loop: "{{ groups['instances'] }}"
+ when:
+ - not (hostvars[item]['fqdn'] | default('') | string | trim | length > 0)
+ - hostvars[item]['instance_state'] is undefined
+
+- name: Find files to delete in /usr/local/openresty/nginx/conf/conf.d/
+ ansible.builtin.find:
+ paths: /usr/local/openresty/nginx/conf/conf.d/
+ patterns: '*'
+ file_type: file
+ excludes: "{{ groups['instances'] | map('extract', hostvars, 'fqdn') | map('default', 'default', true) | map('regex_replace', '^\\s+|\\s+$', '') | unique | map('regex_replace', '^$', 'default') | map('regex_replace', '(.+)', '\\1.conf') | list }}"
+ register: found_files
+
+- name: Clean /usr/local/openresty/nginx/conf/conf.d/ directory
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: absent
+ loop: "{{ found_files['files'] | map(attribute='path') }}"
+
+- name: Copy static OpenResty config for valid FQDNs
+ ansible.builtin.copy:
+ src: openresty/static.conf
+ dest: /usr/local/openresty/nginx/conf/static/{{ hostvars[item]['fqdn'] | trim }}.conf
+ group: root
+ owner: root
+ force: false
+ mode: "0640"
+ loop: "{{ groups['instances'] }}"
+ when:
+ - (hostvars[item]['fqdn'] | default('') | string | trim | length > 0)
+ - hostvars[item]['instance_state'] is undefined
+ notify: Restart Openresty
+
+- name: Generate OpenResty config for instances with valid FQDNs
+ ansible.builtin.template:
+ src: openresty/site.j2
+ dest: /usr/local/openresty/nginx/conf/conf.d/{{ hostvars[item]['fqdn'] | trim }}.conf
+ owner: root
+ group: root
+ mode: "0640"
+ loop: "{{ groups['instances'] }}"
+ when:
+ - (hostvars[item]['fqdn'] | default('') | string | trim | length > 0)
+ - hostvars[item]['instance_state'] is undefined
+ notify: Restart Openresty
+
+- name: Copy static OpenResty config for instances without FQDNs
+ ansible.builtin.copy:
+ src: openresty/default.conf
+ dest: /usr/local/openresty/nginx/conf/static/default.conf
+ owner: root
+ group: root
+ mode: "0640"
+ force: false
+ loop: "{{ groups['instances'] }}"
+ when:
+ - not (hostvars[item]['fqdn'] | default('') | string | trim | length > 0)
+ - hostvars[item]['instance_state'] is undefined
+ notify: Restart Openresty
+
+- name: Generate dynamic OpenResty config for instances without FQDN
+ ansible.builtin.template:
+ src: openresty/default.j2
+ dest: /usr/local/openresty/nginx/conf/conf.d/default.conf
+ owner: root
+ group: root
+ mode: "0640"
+ loop: "{{ groups['instances'] }}"
+ when:
+ - not (hostvars[item]['fqdn'] | default('') | string | trim | length > 0)
+ - hostvars[item]['instance_state'] is undefined
+ notify: Restart Openresty
+
+- name: Start OpenResty service
+ ansible.builtin.service:
+ name: openresty
+ state: started
+
+- name: Flush Handlers
+ ansible.builtin.meta: flush_handlers
diff --git a/deploy/roles/create-instance/tasks/tomcat-setup.yml b/deploy/roles/create-instance/tasks/tomcat-setup.yml
index dd970ae..4dfc240 100644
--- a/deploy/roles/create-instance/tasks/tomcat-setup.yml
+++ b/deploy/roles/create-instance/tasks/tomcat-setup.yml
@@ -57,7 +57,6 @@
mode: "0640"
owner: root
group: tomcat
- force: false
notify: Restart Tomcat
- name: Create tomcat service directory
diff --git a/deploy/roles/create-instance/templates/nginx/default.j2 b/deploy/roles/create-instance/templates/nginx/default.j2
index 1ea5aa6..c4b56da 100644
--- a/deploy/roles/create-instance/templates/nginx/default.j2
+++ b/deploy/roles/create-instance/templates/nginx/default.j2
@@ -3,10 +3,21 @@
# during future Ansible runs.
# Please place any custom configurations in the following file
-# /etc/nginx/custom/default.conf
+# /etc/nginx/custom/default.conf
# configurations in /etc/nginx/static/default.conf are be included in this nginx configuration.
+# Tomcat upstreams with keepalive — for instances without an FQDN
+{% for instance in groups['instances']
+ if (hostvars[instance]['instance_state'] is not defined)
+ and not (hostvars[instance]['fqdn'] | default('', true) | trim) %}
+upstream {{ instance | replace('-', '_') }}_tomcat {
+ server {{ hostvars[instance]['ansible_host'] }}:8080;
+ keepalive {{ tomcat_keepalive | default(32) }};
+ keepalive_requests {{ tomcat_keepalive_requests | default(1000) }};
+ keepalive_timeout {{ tomcat_keepalive_timeout | default('60s') }};
+}
+{% endfor %}
# Let local monitoring agent access stats
server {
@@ -29,8 +40,10 @@ server {
# https block
server {
- listen {{ https_port | default('443') }} ssl http2{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }};
+ listen {{ https_port | default('443') }} ssl{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }};
+ http2 on;
server_name _;
+ ssl_session_cache shared:SSL:20m;
include static/default.conf;
{% if use_proxy_protocol | default(false) and ansible_connection == 'lxd' %}
# Extract real client IP from proxy protocol header
diff --git a/deploy/roles/create-instance/templates/nginx/instance.j2 b/deploy/roles/create-instance/templates/nginx/instance.j2
index e06e979..38e95ea 100644
--- a/deploy/roles/create-instance/templates/nginx/instance.j2
+++ b/deploy/roles/create-instance/templates/nginx/instance.j2
@@ -1,14 +1,13 @@
{% if hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string == "ROOT" %}
location / {
- proxy_pass http://{{hostvars[item]['ansible_host']+':8080' }};
+ proxy_pass http://{{ item | replace('-', '_') }}_tomcat;
include /etc/nginx/proxy_params;
proxy_redirect off;
- proxy_set_header X-Frame-Options "SAMEORIGIN"; # protect against clickjacking attacks
- proxy_set_header X-Content-Type-Options "nosniff"; # drive-by download attacks
proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
+ include /etc/nginx/security_headers.conf;
}
{% if app_monitoring is defined and app_monitoring | trim == 'glowroot' %}
{# glowroot location block configs #}
@@ -16,26 +15,24 @@ location /glowroot/ {
proxy_pass http://{{ hostvars[item]['ansible_host']+':4000' }}/glowroot/;
include /etc/nginx/proxy_params;
proxy_redirect off;
- proxy_set_header X-Frame-Options "SAMEORIGIN"; # To mitigate the risk of clickjacking attacks
- proxy_set_header X-Content-Type-Options "nosniff"; # drive-by download attacks
proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
+ include /etc/nginx/security_headers.conf;
}
{% endif %}
{% else %}
location /{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}/ {
- {# instance location block configs #}
- proxy_pass http://{{hostvars[item]['ansible_host']+':8080' }}/{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}/;
+ {# instance location block configs #}
+ proxy_pass http://{{ item | replace('-', '_') }}_tomcat/{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}/;
include /etc/nginx/proxy_params;
proxy_redirect off;
- proxy_set_header X-Frame-Options "SAMEORIGIN"; # protect against clickjacking attacks
- proxy_set_header X-Content-Type-Options "nosniff"; # drive-by download attacks
proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
+ include /etc/nginx/security_headers.conf;
}
{% if app_monitoring is defined and app_monitoring | trim == 'glowroot' %}
{# glowroot location block configs #}
@@ -43,12 +40,11 @@ location /{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string
proxy_pass http://{{ hostvars[item]['ansible_host']+':4000' }}/{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}-glowroot;
include /etc/nginx/proxy_params;
proxy_redirect off;
- proxy_set_header X-Frame-Options "SAMEORIGIN"; # To mitigate the risk of clickjacking attacks
- proxy_set_header X-Content-Type-Options "nosniff"; # drive-by download attacks
proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
+ include /etc/nginx/security_headers.conf;
}
{% endif %}
{% endif %}
diff --git a/deploy/roles/create-instance/templates/nginx/site.j2 b/deploy/roles/create-instance/templates/nginx/site.j2
index e85d74c..877f01a 100644
--- a/deploy/roles/create-instance/templates/nginx/site.j2
+++ b/deploy/roles/create-instance/templates/nginx/site.j2
@@ -7,6 +7,18 @@
# configurations within that file will be included in this nginx configuration.
+# Tomcat upstreams with keepalive — one per instance under this FQDN
+{% for instance in groups['instances']
+ if (hostvars[instance]['fqdn'] | default('') | trim == hostvars[item]['fqdn'] | trim)
+ and (hostvars[instance]['instance_state'] is undefined) %}
+upstream {{ instance | replace('-', '_') }}_tomcat {
+ server {{ hostvars[instance]['ansible_host'] }}:8080;
+ keepalive {{ tomcat_keepalive | default(32) }};
+ keepalive_requests {{ tomcat_keepalive_requests | default(1000) }};
+ keepalive_timeout {{ tomcat_keepalive_timeout | default('60s') }};
+}
+{% endfor %}
+
{% if groups['instances'] | all_have_fqdn(hostvars) %}
# SNI protection - prevents certificate/domain leakage via direct IP access
# Only deployed when all instances have fqdn defined
@@ -17,7 +29,8 @@ server {
}
server {
- listen 443 ssl http2{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }} default_server;
+ listen 443 ssl{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }} default_server;
+ http2 on;
server_name _;
{% if nginx_version is defined and nginx_version is version('1.19.4', '>=') %}
ssl_reject_handshake on;
@@ -59,19 +72,22 @@ server {
# https block
server {
{%if TLS_TYPE is undefined or TLS_TYPE == 'letsencrypt' -%}
- listen 443 ssl http2;
+ listen 443 ssl;
+ http2 on;
server_name {{ hostvars[item]['fqdn'] | trim }};
# ssl settings
ssl_certificate /etc/letsencrypt/live/{{ hostvars[item]['fqdn'] | trim }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ hostvars[item]['fqdn'] | trim }}/privkey.pem;
{%elif TLS_TYPE is defined and TLS_TYPE == 'customssl' -%}
- listen {{ https_port | default('443') }} ssl http2;
+ listen {{ https_port | default('443') }} ssl;
+ http2 on;
server_name {{ hostvars[item]['fqdn'] | trim }};
# ssl settings
ssl_certificate /etc/ssl/{{ hostvars[item]['fqdn'] | trim }}/customssl.crt;
ssl_certificate_key /etc/ssl/{{ hostvars[item]['fqdn'] | trim }}/customssl.key;
{%elif TLS_TYPE is defined and TLS_TYPE == 'selfsigned' -%}
- listen {{ https_port | default('443') }} ssl http2;
+ listen {{ https_port | default('443') }} ssl;
+ http2 on;
server_name {{ hostvars[item]['fqdn'] | trim }};
# ssl settings
ssl_certificate /etc/ssl/selfsigned/selfsigned.crt;
@@ -82,6 +98,7 @@ server {
ssl_stapling on;
ssl_stapling_verify on;
{% endif %}
+ ssl_session_cache shared:SSL:20m;
{% if use_proxy_protocol | default(false) and ansible_connection == 'lxd' %}
# Extract real client IP from proxy protocol header
@@ -141,7 +158,4 @@ server {
{% endif %}
{% endfor %}
-if ($host != "{{ hostvars[item]['fqdn'] | trim }}") {
- return 444;
- }
}
diff --git a/deploy/roles/create-instance/templates/openresty/default.j2 b/deploy/roles/create-instance/templates/openresty/default.j2
new file mode 100644
index 0000000..1c89bd0
--- /dev/null
+++ b/deploy/roles/create-instance/templates/openresty/default.j2
@@ -0,0 +1,93 @@
+# WARNING: This file is automatically generated by an Ansible template.
+# Any custom modifications made directly to this file will be overwritten
+# during future Ansible runs.
+
+# Please place any custom configurations in the following file
+# /usr/local/openresty/nginx/conf/custom/default.conf
+
+# configurations in /usr/local/openresty/nginx/conf/static/default.conf are included in this openresty configuration.
+
+# Tomcat upstreams with keepalive — for instances without an FQDN
+{% for instance in groups['instances']
+ if (hostvars[instance]['instance_state'] is not defined)
+ and not (hostvars[instance]['fqdn'] | default('', true) | trim) %}
+upstream {{ instance | replace('-', '_') }}_tomcat {
+ server {{ hostvars[instance]['ansible_host'] }}:8080;
+ keepalive {{ tomcat_keepalive | default(32) }};
+ keepalive_requests {{ tomcat_keepalive_requests | default(1000) }};
+ keepalive_timeout {{ tomcat_keepalive_timeout | default('60s') }};
+}
+{% endfor %}
+
+# Let local monitoring agent access stats
+server {
+ listen 127.0.0.1;
+ server_name localhost;
+ location /nginx_status {
+ stub_status on;
+ access_log off;
+ allow 127.0.0.1;
+ deny all;
+ }
+}
+
+# http block
+server {
+ listen {{ http_port | default('80') }}{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }};
+ server_name _;
+ return 302 https://$host:{{ https_port | default("$server_port") }}$request_uri;
+}
+
+# https block
+server {
+ listen {{ https_port | default('443') }} ssl{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }};
+ http2 on;
+ server_name _;
+ ssl_session_cache shared:SSL:20m;
+ include static/default.conf;
+{% if use_proxy_protocol | default(false) and ansible_connection == 'lxd' %}
+ # Extract real client IP from proxy protocol header
+ set_real_ip_from {{ proxy_protocol_trusted_cidr | default(lxd_network | default('172.19.0.0/16')) }};
+ real_ip_header proxy_protocol;
+{% endif %}
+
+ {% for instance in groups['instances']
+ if (hostvars[instance]['instance_state'] is not defined)
+ and (hostvars[instance]['proxy_rewrite'] | default(false) in [true, 'true'])
+ and not (hostvars[instance]['fqdn'] | default('', true) | trim) and ('ROOT' not in groups['instances'] |
+ map('extract', hostvars, 'dhis2_base_path') |
+ map('default', 'None')) %}
+ {% if loop.first %}
+ # in case you want '/' access re-written to /{{ hostvars[instance]['dhis2_base_path'] | default(instance) | to_fixed_string }}
+ rewrite ^/$ /{{ hostvars[instance]['dhis2_base_path'] | default(instance) | to_fixed_string }};
+ {% endif %}
+{% endfor %}
+
+# location configs for dhis2 instances
+{% for instance in groups['instances'] if (hostvars[instance]['instance_state'] is not defined)
+and not (hostvars[instance]['fqdn'] | default('', true) | trim) %}
+ include conf.d/upstream/{{ instance | to_fixed_string }}.conf;
+{%endfor%}
+
+{% set monitoring = (server_monitoring | default('') | lower | trim) %}
+{% if monitoring == 'munin' %}
+ # Location config for Munin server backend
+ include conf.d/upstream/munin.conf;
+{% elif monitoring in ['grafana', 'prometheus', 'grafana/prometheus'] %}
+ # Location config for Grafana/Prometheus backend
+ include conf.d/upstream/grafana.conf;
+{% elif monitoring %}
+ # server_monitoring is defined but not recognized: {{ server_monitoring }}
+{% else %}
+ # server_monitoring is not defined or empty
+{% endif %}
+
+{% for instance in groups['instances'] if 'ROOT' not in groups['instances'] |
+ map('extract', hostvars, 'dhis2_base_path') | map('default', 'None') %}
+{% if loop.first %}
+location / {
+ return 444;
+ }
+{%endif%}
+{%endfor%}
+}
diff --git a/deploy/roles/create-instance/templates/openresty/instance.j2 b/deploy/roles/create-instance/templates/openresty/instance.j2
new file mode 100644
index 0000000..542e047
--- /dev/null
+++ b/deploy/roles/create-instance/templates/openresty/instance.j2
@@ -0,0 +1,50 @@
+{% if hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string == "ROOT" %}
+location / {
+ proxy_pass http://{{ item | replace('-', '_') }}_tomcat;
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
+ proxy_hide_header Strict-Transport-Security;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+ }
+{% if app_monitoring is defined and app_monitoring | trim == 'glowroot' %}
+{# glowroot location block configs #}
+location /glowroot/ {
+ proxy_pass http://{{ hostvars[item]['ansible_host']+':4000' }}/glowroot/;
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
+ proxy_hide_header Strict-Transport-Security;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+ }
+{% endif %}
+{% else %}
+location /{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}/ {
+ {# instance location block configs #}
+ proxy_pass http://{{ item | replace('-', '_') }}_tomcat/{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}/;
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
+ proxy_hide_header Strict-Transport-Security;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+ }
+{% if app_monitoring is defined and app_monitoring | trim == 'glowroot' %}
+{# glowroot location block configs #}
+location /{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}-glowroot {
+ proxy_pass http://{{ hostvars[item]['ansible_host']+':4000' }}/{{ hostvars[item]['dhis2_base_path'] | default(item) | to_fixed_string }}-glowroot;
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_set_header X-Forwarded-Port {{ https_port | default('443') }};
+ proxy_hide_header Strict-Transport-Security;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+ }
+{% endif %}
+{% endif %}
diff --git a/deploy/roles/create-instance/templates/openresty/site.j2 b/deploy/roles/create-instance/templates/openresty/site.j2
new file mode 100644
index 0000000..3c16c4e
--- /dev/null
+++ b/deploy/roles/create-instance/templates/openresty/site.j2
@@ -0,0 +1,161 @@
+# WARNING: This file is automatically generated by an Ansible template.
+# Any custom modifications made directly to this file will be overwritten
+# during future Ansible runs.
+
+# Please place any custom configurations in the following file
+# /usr/local/openresty/nginx/conf/custom/{{ hostvars[item]['fqdn'] }}.conf
+
+# configurations within that file will be included in this openresty configuration.
+
+# Tomcat upstreams with keepalive — one per instance under this FQDN
+{% for instance in groups['instances']
+ if (hostvars[instance]['fqdn'] | default('') | trim == hostvars[item]['fqdn'] | trim)
+ and (hostvars[instance]['instance_state'] is undefined) %}
+upstream {{ instance | replace('-', '_') }}_tomcat {
+ server {{ hostvars[instance]['ansible_host'] }}:8080;
+ keepalive {{ tomcat_keepalive | default(32) }};
+ keepalive_requests {{ tomcat_keepalive_requests | default(1000) }};
+ keepalive_timeout {{ tomcat_keepalive_timeout | default('60s') }};
+}
+{% endfor %}
+
+{% if groups['instances'] | all_have_fqdn(hostvars) %}
+# SNI protection - prevents certificate/domain leakage via direct IP access
+# Only deployed when all instances have fqdn defined
+server {
+ listen 80{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }} default_server;
+ server_name _;
+ return 444;
+}
+
+server {
+ listen 443 ssl{{ ' proxy_protocol' if use_proxy_protocol | default(false) and ansible_connection == 'lxd' else '' }} default_server;
+ http2 on;
+ server_name _;
+{% if nginx_version is defined and nginx_version is version('1.19.4', '>=') %}
+ ssl_reject_handshake on;
+{% else %}
+ # ssl_reject_handshake requires nginx >= 1.19.4 (current: {{ nginx_version | default('unknown') }})
+ # Fallback: return 444 with self-signed cert
+ ssl_certificate /etc/ssl/selfsigned/selfsigned.crt;
+ ssl_certificate_key /etc/ssl/selfsigned/selfsigned.key;
+ return 444;
+{% endif %}
+}
+{% endif %}
+
+# Let local monitoring agent access stats
+server {
+ listen 127.0.0.1;
+ server_name localhost;
+ location /nginx_status {
+ stub_status on;
+ access_log off;
+ allow 127.0.0.1;
+ deny all;
+ }
+}
+
+# http block
+server {
+ {%if TLS_TYPE is undefined or TLS_TYPE == 'letsencrypt' -%}
+ listen 80;
+ server_name {{ hostvars[item]['fqdn'] | trim }};
+ return 302 https://$host:{{ https_port | default("$server_port") }}$request_uri;
+ {% else %}
+ listen {{ http_port | default('80') }};
+ server_name {{ hostvars[item]['fqdn'] | trim }};
+ return 302 https://$host:{{ https_port | default("$server_port") }}$request_uri;
+ {%endif-%}
+}
+
+# https block
+server {
+ {%if TLS_TYPE is undefined or TLS_TYPE == 'letsencrypt' -%}
+ listen 443 ssl;
+ http2 on;
+ server_name {{ hostvars[item]['fqdn'] | trim }};
+ # ssl settings
+ ssl_certificate /etc/letsencrypt/live/{{ hostvars[item]['fqdn'] | trim }}/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/{{ hostvars[item]['fqdn'] | trim }}/privkey.pem;
+ {%elif TLS_TYPE is defined and TLS_TYPE == 'customssl' -%}
+ listen {{ https_port | default('443') }} ssl;
+ http2 on;
+ server_name {{ hostvars[item]['fqdn'] | trim }};
+ # ssl settings
+ ssl_certificate /etc/ssl/{{ hostvars[item]['fqdn'] | trim }}/customssl.crt;
+ ssl_certificate_key /etc/ssl/{{ hostvars[item]['fqdn'] | trim }}/customssl.key;
+ {%elif TLS_TYPE is defined and TLS_TYPE == 'selfsigned' -%}
+ listen {{ https_port | default('443') }} ssl;
+ http2 on;
+ server_name {{ hostvars[item]['fqdn'] | trim }};
+ # ssl settings
+ ssl_certificate /etc/ssl/selfsigned/selfsigned.crt;
+ ssl_certificate_key /etc/ssl/selfsigned/selfsigned.key;
+ {%endif-%}
+{% if TLS_TYPE is defined and TLS_TYPE == 'customssl' %}
+ # ssl_stapling only for customssl (Let's Encrypt phased out OCSP in 2024)
+ ssl_stapling on;
+ ssl_stapling_verify on;
+{% endif %}
+ ssl_session_cache shared:SSL:20m;
+{% if use_proxy_protocol | default(false) and ansible_connection == 'lxd' %}
+
+ # Extract real client IP from proxy protocol header
+ set_real_ip_from {{ proxy_protocol_trusted_cidr | default(lxd_network | default('172.19.0.0/16')) }};
+ real_ip_header proxy_protocol;
+{% endif %}
+
+ # please write your custom changes to the static/{{ hostvars[item]['fqdn'] }}.conf; and
+ # will be kept across subsequent ansible runs
+{% for instance in groups['instances']
+ if (hostvars[instance]['fqdn'] | default('') | trim) == (hostvars[item]['fqdn'] | default('') | trim)
+ and (hostvars[instance]['fqdn'] | default('') | trim) and (hostvars[instance]['instance_state'] is undefined) %}
+{% if loop.first %}
+ include static/{{ hostvars[instance]['fqdn'] | trim }}.conf;
+{% endif %}
+{% endfor %}
+
+ # rewrite rule
+{% for instance in groups['instances']
+ if (hostvars[instance]['fqdn'] | default('') | trim == hostvars[item]['fqdn'] | trim)
+ and (hostvars[instance]['instance_state'] is not defined)
+ and (hostvars[instance]['dhis2_base_path'] | default('') != 'ROOT')
+ and (hostvars[instance]['proxy_rewrite'] | default(false) in [true, 'true']) %}
+ {% if loop.first %}
+ rewrite ^/$ /{{ hostvars[instance]['dhis2_base_path'] | default(instance) | to_fixed_string }};
+ {% endif %}
+{% endfor %}
+
+ # Location configuration for the instances
+{% for instance in groups['instances']
+ if (hostvars[instance]['fqdn'] | default('') | trim == hostvars[item]['fqdn'] | trim)
+ and (hostvars[instance]['instance_state'] is undefined) %}
+ include conf.d/upstream/{{ instance | to_fixed_string }}.conf;
+{% endfor %}
+
+{% set monitoring = (server_monitoring | default('') | lower | trim) %}
+ # Location configs for server monitoring
+{% if monitoring == 'munin' %}
+ include conf.d/upstream/munin.conf;
+{% elif monitoring in ['grafana', 'prometheus', 'grafana/prometheus'] %}
+ include conf.d/upstream/grafana.conf;
+{% elif monitoring %}
+ # server_monitoring is set but not recognized: {{ server_monitoring }}
+{% else %}
+ # server_monitoring is not defined or empty
+{% endif %}
+ # Return 444 if nothing is served on '/'
+ {% for instance in groups['instances']
+ if (hostvars[instance]['fqdn'] | default('') | trim == hostvars[item]['fqdn'] | trim)
+ and ('ROOT' not in groups['instances']
+ | map('extract', hostvars, 'dhis2_base_path')
+ | map('default', 'None')) %}
+{% if loop.first %}
+ location / {
+ return 444;
+ }
+{% endif %}
+{% endfor %}
+
+}
diff --git a/deploy/roles/proxy/files/security_headers.conf b/deploy/roles/proxy/files/security_headers.conf
new file mode 100644
index 0000000..a56b7a4
--- /dev/null
+++ b/deploy/roles/proxy/files/security_headers.conf
@@ -0,0 +1,12 @@
+# Managed by Ansible — do not edit, changes will be overwritten.
+# Include from any server{} or location{} block to emit security headers.
+# Re-include in each location that has its own add_header (nginx drops
+# server-level add_header inheritance the moment a location adds any).
+#
+# HSTS is intentionally NOT included here — it must stay scoped to vhosts
+# with a valid CA-signed cert (managed via static/.conf), never on
+# self-signed/IP-based access.
+
+add_header X-Frame-Options "SAMEORIGIN" always;
+add_header X-Content-Type-Options "nosniff" always;
+add_header Referrer-Policy "no-referrer" always;
diff --git a/deploy/roles/proxy/handlers/main.yml b/deploy/roles/proxy/handlers/main.yml
index c0d6f93..ed1583d 100644
--- a/deploy/roles/proxy/handlers/main.yml
+++ b/deploy/roles/proxy/handlers/main.yml
@@ -17,3 +17,15 @@
name: apache2
state: reloaded
enabled: true
+
+- name: Reload Openresty
+ ansible.builtin.service:
+ name: openresty
+ state: reloaded
+ enabled: true
+
+- name: Restart Openresty
+ ansible.builtin.service:
+ name: openresty
+ state: restarted
+ enabled: true
diff --git a/deploy/roles/proxy/tasks/nginx-install.yml b/deploy/roles/proxy/tasks/nginx-install.yml
index 47c05a0..3b1bee5 100644
--- a/deploy/roles/proxy/tasks/nginx-install.yml
+++ b/deploy/roles/proxy/tasks/nginx-install.yml
@@ -52,7 +52,9 @@
Pin: origin nginx.org
Pin: release o=nginx
Pin-Priority: 900
- mode: "0644"
+ owner: root
+ group: root
+ mode: "0640"
- name: Installing Nginx
ansible.builtin.apt:
@@ -71,8 +73,26 @@
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
- mode: "0644"
- force: false
+ # Keepalive to upstream — requires HTTP/1.1 and an empty Connection header.
+ # Activates only when proxy_pass targets an upstream{} block with keepalive set.
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ # Timeouts for long-running DHIS2 operations (analytics, imports)
+ proxy_connect_timeout 60s;
+ proxy_send_timeout 300s;
+ proxy_read_timeout 300s;
+ owner: root
+ group: root
+ mode: "0640"
+
+- name: Deploy security_headers.conf
+ ansible.builtin.copy:
+ src: security_headers.conf
+ dest: /etc/nginx/security_headers.conf
+ owner: root
+ group: root
+ mode: "0640"
+ notify: Reload Nginx
- name: Disabling nginx version show
ansible.builtin.lineinfile:
@@ -97,6 +117,43 @@
tags: [nginx-perf-log]
notify: Reload Nginx
+- name: Nginx TCP optimizations
+ ansible.builtin.blockinfile:
+ path: /etc/nginx/nginx.conf
+ block: |
+ tcp_nopush on;
+ tcp_nodelay on;
+ marker: "# {mark} TCP OPTIMIZATIONS"
+ insertafter: "http {"
+ tags: [nginx-tcp]
+ notify: Reload Nginx
+
+- name: Nginx gzip compression
+ ansible.builtin.blockinfile:
+ path: /etc/nginx/nginx.conf
+ block: |
+ gzip on;
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 5;
+ gzip_min_length 1024;
+ gzip_types
+ text/plain
+ text/css
+ text/xml
+ text/javascript
+ application/json
+ application/javascript
+ application/xml
+ application/rss+xml
+ application/atom+xml
+ application/wasm
+ image/svg+xml;
+ marker: "# {mark} GZIP COMPRESSION"
+ insertafter: "http {"
+ tags: [nginx-gzip]
+ notify: Reload Nginx
+
- name: Remove default nginx site
ansible.builtin.file:
name: /etc/nginx/sites-enabled/default
diff --git a/deploy/roles/proxy/tasks/openresty-install.yml b/deploy/roles/proxy/tasks/openresty-install.yml
new file mode 100644
index 0000000..a1df5c5
--- /dev/null
+++ b/deploy/roles/proxy/tasks/openresty-install.yml
@@ -0,0 +1,123 @@
+---
+- name: Open http port {{ http_port | default('80') }}
+ community.general.ufw:
+ rule: allow
+ port: "{{ http_port | default('80') }}"
+ proto: tcp
+ comment: http
+ state: enabled
+
+- name: Open https port {{ https_port | default('443') }}
+ community.general.ufw:
+ rule: allow
+ port: "{{ https_port | default('443') }}"
+ proto: tcp
+ comment: https
+
+- name: Stop and disable other proxy services
+ ansible.builtin.include_tasks: stop-other-proxies.yml
+
+- name: Updating and Upgrading
+ ansible.builtin.apt:
+ upgrade: "yes"
+ update_cache: true
+ cache_valid_time: 3600
+
+- name: Install prerequisites for OpenResty repo
+ ansible.builtin.apt:
+ name:
+ - curl
+ - gnupg2
+ - ca-certificates
+ - lsb-release
+ state: present
+
+- name: Add OpenResty signing key
+ ansible.builtin.shell:
+ cmd: curl -fsSL https://openresty.org/package/pubkey.gpg | gpg --dearmor -o /usr/share/keyrings/openresty-archive-keyring.gpg
+ creates: /usr/share/keyrings/openresty-archive-keyring.gpg
+
+- name: Add OpenResty official repository
+ ansible.builtin.apt_repository:
+ repo: "deb [signed-by=/usr/share/keyrings/openresty-archive-keyring.gpg] https://openresty.org/package/ubuntu {{ ansible_distribution_release }} main"
+ state: present
+ filename: openresty
+
+- name: Installing OpenResty
+ ansible.builtin.apt:
+ name:
+ - openresty
+ - libwww-perl # required for munin monitoring
+ state: latest
+ update_cache: true
+
+- name: Creating OpenResty config directories
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ owner: root
+ group: root
+ mode: "0750"
+ loop:
+ - '/usr/local/openresty/nginx/conf/conf.d/upstream'
+ - '/usr/local/openresty/nginx/conf/static'
+
+- name: Deploy main nginx.conf
+ ansible.builtin.template:
+ src: openresty/nginx.conf.j2
+ dest: /usr/local/openresty/nginx/conf/nginx.conf
+ owner: root
+ group: root
+ mode: "0640"
+ notify: Reload Openresty
+
+- name: Create proxy_params file
+ ansible.builtin.copy:
+ dest: /usr/local/openresty/nginx/conf/proxy_params
+ content: |
+ 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-Forwarded-Proto $scheme;
+ # Keepalive to upstream — requires HTTP/1.1 and an empty Connection header.
+ # Activates only when proxy_pass targets an upstream{} block with keepalive set.
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ # Timeouts for long-running DHIS2 operations (analytics, imports)
+ proxy_connect_timeout 60s;
+ proxy_send_timeout 300s;
+ proxy_read_timeout 300s;
+ owner: root
+ group: root
+ mode: "0640"
+
+- name: Deploy security_headers.conf
+ ansible.builtin.copy:
+ src: security_headers.conf
+ dest: /usr/local/openresty/nginx/conf/security_headers.conf
+ owner: root
+ group: root
+ mode: "0640"
+ notify: Reload Openresty
+
+- name: Generating Diffie-Hellman key parameters, takes a while, you may grab some coffee
+ ansible.builtin.command: openssl dhparam -out /usr/local/openresty/nginx/conf/dhparams.pem 2048
+ args:
+ creates: /usr/local/openresty/nginx/conf/dhparams.pem
+ notify: Reload Openresty
+
+- name: Location configs {{ server_monitoring }}
+ ansible.builtin.template:
+ src: openresty/{{ server_monitoring | trim }}.j2
+ dest: /usr/local/openresty/nginx/conf/conf.d/upstream/{{ server_monitoring }}.conf
+ owner: root
+ group: root
+ mode: "0640"
+ loop: "{{ groups['monitoring'] }}"
+ notify: Reload Openresty
+ when:
+ - groups['monitoring'] | length > 0
+ - server_monitoring is defined
+
+- name: Flushing handlers
+ ansible.builtin.meta: flush_handlers
diff --git a/deploy/roles/proxy/tasks/securing-munin.yml b/deploy/roles/proxy/tasks/securing-munin.yml
index 8b26a1d..2971eb4 100644
--- a/deploy/roles/proxy/tasks/securing-munin.yml
+++ b/deploy/roles/proxy/tasks/securing-munin.yml
@@ -6,10 +6,16 @@
- name: Detect proxy worker user from config
ansible.builtin.shell: |
- grep -E '^\s*user\s+' /etc/{{ proxy }}/{{ proxy }}.conf | awk '{print $2}' | tr -d ';' | head -1
+ # OpenResty uses nginx.conf as its main config file
+ conf_file="/etc/{{ proxy }}/{{ proxy }}.conf"
+ if [ ! -f "$conf_file" ]; then
+ conf_file="/usr/local/openresty/nginx/conf/nginx.conf"
+ fi
+ grep -E '^\s*user\s+' "$conf_file" | awk '{print $2}' | tr -d ';' | head -1
register: _proxy_worker_user
changed_when: false
- failed_when: false
+ check_mode: false
+
- name: Set proxy_user fact
ansible.builtin.set_fact:
@@ -17,7 +23,7 @@
- name: "Munin authenticatin, defaults: Username: admin, password: district"
community.general.htpasswd:
- path: /etc/{{ proxy }}/.htpasswd
+ path: "{{ '/usr/local/openresty/nginx/conf/.htpasswd' if proxy == 'openresty' else '/etc/' + proxy + '/.htpasswd' }}"
name: "{{ item.name }}"
password: "{{ item.password }}"
owner: root
diff --git a/deploy/roles/proxy/templates/nginx/grafana.j2 b/deploy/roles/proxy/templates/nginx/grafana.j2
index e6979bc..5a82fa5 100644
--- a/deploy/roles/proxy/templates/nginx/grafana.j2
+++ b/deploy/roles/proxy/templates/nginx/grafana.j2
@@ -8,9 +8,6 @@ location /{{ server_monitoring }} {
include /etc/nginx/proxy_params;
proxy_redirect off;
proxy_hide_header X-Powered-By;
- proxy_set_header X-Frame-Options "SAMEORIGIN"; # To mitigate the risk of clickjacking attacks
- proxy_set_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
- proxy_set_header X-Content-Type-Options "nosniff"; # drive-by download attacks
- proxy_set_header X-Xss-Protection "1; mode=block"; #To leverage browser-based protections against cross-site scripting
proxy_hide_header Server;
+ include /etc/nginx/security_headers.conf;
}
diff --git a/deploy/roles/proxy/templates/nginx/munin.j2 b/deploy/roles/proxy/templates/nginx/munin.j2
index e4164ab..e02b77b 100644
--- a/deploy/roles/proxy/templates/nginx/munin.j2
+++ b/deploy/roles/proxy/templates/nginx/munin.j2
@@ -9,11 +9,8 @@ location /{{ server_monitoring }} {
include /etc/nginx/proxy_params;
proxy_redirect off;
proxy_hide_header X-Powered-By;
- proxy_set_header X-Frame-Options "SAMEORIGIN"; # To mitigate the risk of clickjacking attacks
- proxy_set_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
- proxy_set_header X-Content-Type-Options "nosniff"; # drive-by download attacks
- proxy_set_header X-Xss-Protection "1; mode=block"; #To leverage browser-based protections against cross-site scripting
proxy_hide_header Server;
+ include /etc/nginx/security_headers.conf;
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;
}
diff --git a/deploy/roles/proxy/templates/openresty/grafana.j2 b/deploy/roles/proxy/templates/openresty/grafana.j2
new file mode 100644
index 0000000..890835b
--- /dev/null
+++ b/deploy/roles/proxy/templates/openresty/grafana.j2
@@ -0,0 +1,13 @@
+{% if hostvars['proxy']['grafana_base_path'] is defined and hostvars['proxy']['grafana_base_path'] is not none and hostvars['proxy']['grafana_base_path'] | trim != '' %}
+location /{{ grafana_base_path }} {
+ proxy_pass http://{{hostvars[item]['ansible_host']+':3000' }}/{{ grafana_base_path }};
+{% else %}
+location /{{ server_monitoring }} {
+ proxy_pass http://{{hostvars[item]['ansible_host']+':3000' }}/{{ server_monitoring }};
+{% endif %}
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+}
diff --git a/deploy/roles/proxy/templates/openresty/munin.j2 b/deploy/roles/proxy/templates/openresty/munin.j2
new file mode 100644
index 0000000..a87b3b9
--- /dev/null
+++ b/deploy/roles/proxy/templates/openresty/munin.j2
@@ -0,0 +1,17 @@
+{% for host in groups['monitoring'] %}
+{% if hostvars['proxy']['munin_base_path'] is defined and hostvars['proxy']['munin_base_path'] is not none and hostvars['proxy']['munin_base_path'] | trim != '' %}
+location /{{ munin_base_path }} {
+ proxy_pass http://{{hostvars[host]['ansible_host']+':80' }}/{{ munin_base_path }};
+{% else %}
+location /{{ server_monitoring }} {
+ proxy_pass http://{{hostvars[host]['ansible_host']+':80' }}/{{ server_monitoring }};
+{% endif %}
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+ auth_basic "Restricted Content";
+ auth_basic_user_file /usr/local/openresty/nginx/conf/.htpasswd;
+}
+{% endfor %}
diff --git a/deploy/roles/proxy/templates/openresty/nginx.conf.j2 b/deploy/roles/proxy/templates/openresty/nginx.conf.j2
new file mode 100644
index 0000000..1ab5858
--- /dev/null
+++ b/deploy/roles/proxy/templates/openresty/nginx.conf.j2
@@ -0,0 +1,50 @@
+user www-data;
+worker_processes auto;
+
+error_log /usr/local/openresty/nginx/logs/error.log notice;
+pid /usr/local/openresty/nginx/logs/nginx.pid;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ server_tokens off;
+ include mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ log_format performance '$remote_addr\t$remote_user\t[$time_local]\t"$request"\t'
+ '$status\t$body_bytes_sent\t"$http_referer"\t"$http_user_agent"\t'
+ '$request_time';
+
+ access_log /usr/local/openresty/nginx/logs/perf.log performance;
+
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ keepalive_timeout 65;
+
+ gzip on;
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 5;
+ gzip_min_length 1024;
+ gzip_types
+ text/plain
+ text/css
+ text/xml
+ text/javascript
+ application/json
+ application/javascript
+ application/xml
+ application/rss+xml
+ application/atom+xml
+ application/wasm
+ image/svg+xml;
+
+ include /usr/local/openresty/nginx/conf/conf.d/*.conf;
+}
diff --git a/deploy/roles/proxy/templates/openresty/prometheus.j2 b/deploy/roles/proxy/templates/openresty/prometheus.j2
new file mode 100644
index 0000000..890835b
--- /dev/null
+++ b/deploy/roles/proxy/templates/openresty/prometheus.j2
@@ -0,0 +1,13 @@
+{% if hostvars['proxy']['grafana_base_path'] is defined and hostvars['proxy']['grafana_base_path'] is not none and hostvars['proxy']['grafana_base_path'] | trim != '' %}
+location /{{ grafana_base_path }} {
+ proxy_pass http://{{hostvars[item]['ansible_host']+':3000' }}/{{ grafana_base_path }};
+{% else %}
+location /{{ server_monitoring }} {
+ proxy_pass http://{{hostvars[item]['ansible_host']+':3000' }}/{{ server_monitoring }};
+{% endif %}
+ include /usr/local/openresty/nginx/conf/proxy_params;
+ proxy_redirect off;
+ proxy_hide_header X-Powered-By;
+ proxy_hide_header Server;
+ include /usr/local/openresty/nginx/conf/security_headers.conf;
+}