Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ NOTE:
- You will need to have two files, named `customssl.crt` and `customssl.key`.<br>
`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
Expand Down
2 changes: 1 addition & 1 deletion deploy/inventory/hosts.template
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 5 additions & 7 deletions deploy/roles/create-instance/files/nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
12 changes: 5 additions & 7 deletions deploy/roles/create-instance/files/nginx/static.conf
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
20 changes: 20 additions & 0 deletions deploy/roles/create-instance/files/openresty/default.conf
Original file line number Diff line number Diff line change
@@ -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;
18 changes: 18 additions & 0 deletions deploy/roles/create-instance/files/openresty/static.conf
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions deploy/roles/create-instance/handlers/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
136 changes: 136 additions & 0 deletions deploy/roles/create-instance/tasks/proxy/openresty.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion deploy/roles/create-instance/tasks/tomcat-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
mode: "0640"
owner: root
group: tomcat
force: false
notify: Restart Tomcat

- name: Create tomcat service directory
Expand Down
17 changes: 15 additions & 2 deletions deploy/roles/create-instance/templates/nginx/default.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
18 changes: 7 additions & 11 deletions deploy/roles/create-instance/templates/nginx/instance.j2
Original file line number Diff line number Diff line change
@@ -1,54 +1,50 @@
{% 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 #}
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 #}
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 /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 %}
Loading
Loading