feature: new global setting options to enable SNI-forward to external domains to cover cases where YunoHost is hosted behind a VPN or to host several servers behind the same IP

This commit is contained in:
Alexandre Aubin 2023-08-11 22:59:46 +02:00
parent 465f6da5cd
commit 4fd41688c0
7 changed files with 109 additions and 0 deletions

View file

@ -39,8 +39,12 @@ server {
} }
server { server {
{% if sni_forward_enabled != "True" %}
listen 443 ssl http2; listen 443 ssl http2;
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
{% else %}
listen 127.0.0.1:443 ssl http2;
{% endif %}
server_name {{ domain }}; server_name {{ domain }};
include /etc/nginx/conf.d/security.conf.inc; include /etc/nginx/conf.d/security.conf.inc;

View file

@ -0,0 +1,27 @@
{% set domain_ip_map = sni_forward_list.split(',') %}
stream {
map $ssl_preread_server_name $name {
{% for domain_ip in domain_ip_map %}
{{ domain_ip.split(":")[0] }} {{ domain_ip.split(":")[0].replace('.', '') }};
{% endfor %}
default https_default_backend;
}
{% for domain_ip in domain_ip_map %}
upstream {{ domain_ip.split(":")[0].replace('.', '') }} {
server {{ domain_ip.split(":")[1] }}:443;
}
{% endfor %}
upstream https_default_backend {
server 127.0.0.1:443;
}
server {
listen 443;
listen [::]:443;
proxy_pass $name;
ssl_preread on;
}
}

View file

@ -0,0 +1,38 @@
# This snippet is only here to redirect traffic to another domain on port 80,
# which is also forwarded for port 443 based on the SNI (which is handled
# differently because of the whole SNI story)
# We don't explicitly redirect to HTTPS by default and let the forwarded server
# handle the redirection (or not depending on what's configured on the other
# server)
server {
listen 80;
listen [::]:80;
server_name {{ sni_forward_domain }};
location / {
proxy_pass http://{{ sni_forward_ip }};
proxy_set_header Host $host;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Connection "";
real_ip_header X-Forwarded-For;
real_ip_recursive on;
send_timeout 5m;
proxy_read_timeout 360;
proxy_send_timeout 360;
proxy_connect_timeout 360;
}
access_log /var/log/nginx/{{ sni_forward_domain }}-access.log;
error_log /var/log/nginx/{{ sni_forward_domain }}-error.log;
}

View file

@ -68,6 +68,23 @@ do_pre_regen() {
export redirect_to_https="$(yunohost settings get 'security.nginx.nginx_redirect_to_https' | int_to_bool)" export redirect_to_https="$(yunohost settings get 'security.nginx.nginx_redirect_to_https' | int_to_bool)"
export compatibility="$(yunohost settings get 'security.nginx.nginx_compatibility')" export compatibility="$(yunohost settings get 'security.nginx.nginx_compatibility')"
export experimental="$(yunohost settings get 'security.experimental.security_experimental_enabled' | int_to_bool)" export experimental="$(yunohost settings get 'security.experimental.security_experimental_enabled' | int_to_bool)"
export sni_forward_enabled="$(yunohost settings get 'misc.sni_forward.sni_forward_enabled' | int_to_bool)"
export sni_forward_list="$(yunohost settings get 'misc.sni_forward.sni_forward_list')"
local sni_module="${pending_dir}/etc/nginx/modules-enabled/sni_forward.conf"
if [[ "$sni_forward_enabled" == "True" ]]
then
ynh_render_template "sni_forward.conf" "${sni_module}"
for sni_forward_domain_and_ip in $(echo "$sni_forward_list" | sed 's/,/\n/g')
do
export sni_forward_domain=$(echo $sni_forward_domain_and_ip | awk -F: '{print $1}')
export sni_forward_ip=$(echo $sni_forward_domain_and_ip | awk -F: '{print $2}')
ynh_render_template "sni_forward_server.conf" "${nginx_conf_dir}/${sni_forward_domain}.forward80.conf"
done
else
touch "${sni_module}"
fi
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
cert_status=$(yunohost domain cert status --json) cert_status=$(yunohost domain cert status --json)
@ -128,6 +145,8 @@ do_pre_regen() {
|| touch "${nginx_conf_dir}/${file}" || touch "${nginx_conf_dir}/${file}"
done done
# FIXME : also add the .forward80 files
# remove old mail-autoconfig files # remove old mail-autoconfig files
autoconfig_files=$(ls -1 /var/www/.well-known/*/autoconfig/mail/config-v1.1.xml 2>/dev/null || true) autoconfig_files=$(ls -1 /var/www/.well-known/*/autoconfig/mail/config-v1.1.xml 2>/dev/null || true)
for file in $autoconfig_files; do for file in $autoconfig_files; do

View file

@ -424,6 +424,10 @@
"firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.", "firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.",
"global_settings_reset_success": "Reset global settings", "global_settings_reset_success": "Reset global settings",
"global_settings_setting_admin_strength": "Admin password strength requirements", "global_settings_setting_admin_strength": "Admin password strength requirements",
"global_settings_setting_sni_forward_enabled": "Enable SNI-based forwarding",
"global_settings_setting_sni_forward_enabled_help": "This is an advanced feature to reverse-proxy an entire domain to another machine *without* decrypting the traffic. Useful when you want to expose several machines behind the same IP but still allow each machine to handle the SSL termination.",
"global_settings_setting_sni_forward_list": "List of forwarding",
"global_settings_setting_sni_forward_list_help": "Should be a list of DOMAIN:IPv4, such as domain.tld:1.2.3.4",
"global_settings_setting_admin_strength_help": "These requirements are only enforced when initializing or changing the password", "global_settings_setting_admin_strength_help": "These requirements are only enforced when initializing or changing the password",
"global_settings_setting_backup_compress_tar_archives": "Compress backups", "global_settings_setting_backup_compress_tar_archives": "Compress backups",
"global_settings_setting_backup_compress_tar_archives_help": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", "global_settings_setting_backup_compress_tar_archives_help": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.",

View file

@ -169,3 +169,18 @@ name = "Other"
choices.ipv4 = "IPv4 Only" choices.ipv4 = "IPv4 Only"
choices.ipv6 = "IPv6 Only" choices.ipv6 = "IPv6 Only"
default = "both" default = "both"
[misc.sni_forward]
name = "SNI-based forwarding"
[misc.sni_forward.sni_forward_enabled]
type = "boolean"
default = false
[misc.sni_forward.sni_forward_list]
type = "tags"
# Regex is just <domain regex>:<ip regex>
pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}):((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$'
pattern.error = "You should specify a list of items formatted as DOMAIN:IPv4, such as yolo.test:12.34.56.78"
default = ""
visible = "sni_forward_enabled"

View file

@ -300,6 +300,8 @@ def regen_ssowatconf(setting_name, old_value, new_value):
app_ssowatconf() app_ssowatconf()
@post_change_hook("sni_forward_enabled")
@post_change_hook("sni_forward_list")
@post_change_hook("ssowat_panel_overlay_enabled") @post_change_hook("ssowat_panel_overlay_enabled")
@post_change_hook("nginx_redirect_to_https") @post_change_hook("nginx_redirect_to_https")
@post_change_hook("nginx_compatibility") @post_change_hook("nginx_compatibility")