diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index fbd956e7c..5c9c67f11 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -42,12 +42,18 @@ do_post_regen() { regen_conf_files=$1 # retrieve variables + main_domain=$(cat /etc/yunohost/current_host) domain_list=$(yunohost domain list --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" done + # http_upload directory must be writable by metronome and readable by nginx + mkdir -p "/var/xmpp-upload/${main_domain}/upload" + chmod g+s "/var/xmpp-upload/${main_domain}/upload" + chown -R metronome:www-data "/var/xmpp-upload/${main_domain}" + # fix some permissions chown -R metronome: /var/lib/metronome/ diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 55a5494b2..11e5f596c 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -49,6 +49,7 @@ do_pre_regen() { # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.nginx.compatibility')" + ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" # add domain conf files for domain in $domain_list; do diff --git a/data/templates/metronome/metronome.cfg.lua b/data/templates/metronome/metronome.cfg.lua index 0640ef9d5..b35684add 100644 --- a/data/templates/metronome/metronome.cfg.lua +++ b/data/templates/metronome/metronome.cfg.lua @@ -85,7 +85,7 @@ use_ipv6 = true disco_items = { { "muc.{{ main_domain }}" }, { "pubsub.{{ main_domain }}" }, - { "upload.{{ main_domain }}" }, + { "xmpp-upload.{{ main_domain }}" }, { "vjud.{{ main_domain }}" } }; @@ -141,11 +141,16 @@ Component "pubsub.{{ main_domain }}" "pubsub" unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server) ---Set up a HTTP Upload service -Component "upload.{{ main_domain }}" "http_upload" +Component "xmpp-upload.{{ main_domain }}" "http_upload" name = "{{ main_domain }} Sharing Service" + http_file_path = "/var/xmpp-upload/{{ main_domain }}/upload" + http_external_url = "https://xmpp-upload.{{ main_domain }}:443" + http_file_base_path = "/upload" http_file_size_limit = 6*1024*1024 http_file_quota = 60*1024*1024 + http_upload_file_size_limit = 100 * 1024 * 1024 -- bytes + http_upload_quota = 10 * 1024 * 1024 * 1024 -- bytes ---Set up a VJUD service diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc new file mode 100644 index 000000000..272a29e26 --- /dev/null +++ b/data/templates/nginx/security.conf.inc @@ -0,0 +1,33 @@ +{% if compatibility == "modern" %} +# Ciphers with modern compatibility +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern +# The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) +ssl_protocols TLSv1.2; +ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; +ssl_prefer_server_ciphers on; +{% else %} +# As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 +ssl_ecdh_curve secp521r1:secp384r1:prime256v1; +ssl_prefer_server_ciphers on; + +# Ciphers with intermediate compatibility +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; + +# Uncomment the following directive after DH generation +# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 +#ssl_dhparam /etc/ssl/private/dh2048.pem; +{% endif %} + +more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; +more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; +more_set_headers "X-Content-Type-Options : nosniff"; +more_set_headers "X-XSS-Protection : 1; mode=block"; +more_set_headers "X-Download-Options : noopen"; +more_set_headers "X-Permitted-Cross-Domain-Policies : none"; +more_set_headers "X-Frame-Options : SAMEORIGIN"; + +# Disable gzip to protect against BREACH +# Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) +gzip off; diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 9acc6c0fd..6316960c4 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -6,7 +6,7 @@ map $http_upgrade $connection_upgrade { server { listen 80; listen [::]:80; - server_name {{ domain }}; + server_name {{ domain }} xmpp-upload.{{ domain }}; access_by_lua_file /usr/share/ssowat/access.lua; @@ -38,42 +38,11 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if compatibility == "modern" %} - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - ssl_protocols TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - ssl_prefer_server_ciphers on; - {% else %} - # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 - ssl_ecdh_curve secp521r1:secp384r1:prime256v1; - ssl_prefer_server_ciphers on; + include /etc/nginx/conf.d/security.conf.inc; - # Ciphers with intermediate compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - - # Uncomment the following directive after DH generation - # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 - #ssl_dhparam /etc/ssl/private/dh2048.pem; - {% endif %} - - # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners - # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ {% if domain_cert_ca != "Self-signed" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; {% endif %} - more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; - more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; - more_set_headers "X-Content-Type-Options : nosniff"; - more_set_headers "X-XSS-Protection : 1; mode=block"; - more_set_headers "X-Download-Options : noopen"; - more_set_headers "X-Permitted-Cross-Domain-Policies : none"; - more_set_headers "X-Frame-Options : SAMEORIGIN"; - {% if domain_cert_ca == "Let's Encrypt" %} # OCSP settings ssl_stapling on; @@ -83,10 +52,6 @@ server { resolver_timeout 5s; {% endif %} - # Disable gzip to protect against BREACH - # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) - gzip off; - access_by_lua_file /usr/share/ssowat/access.lua; include /etc/nginx/conf.d/{{ domain }}.d/*.conf; @@ -97,3 +62,48 @@ server { access_log /var/log/nginx/{{ domain }}-access.log; error_log /var/log/nginx/{{ domain }}-error.log; } + +# vhost dedicated to XMPP http_upload +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name xmpp-upload.{{ domain }}; + root /dev/null; + + location /upload/ { + alias /var/xmpp-upload/{{ domain }}/upload; + # Pass all requests to metronome, except for GET and HEAD requests. + limit_except GET HEAD { + proxy_pass http://localhost:5290; + } + + include proxy_params; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'HEAD, GET, PUT, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization'; + add_header 'Access-Control-Allow-Credentials' 'true'; + client_max_body_size 105M; # Choose a value a bit higher than the max upload configured in XMPP server + } + + ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; + ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; + ssl_session_timeout 5m; + ssl_session_cache shared:SSL:50m; + + include /etc/nginx/conf.d/security.conf.inc; + + {% if domain_cert_ca != "Self-signed" %} + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + {% endif %} + {% if domain_cert_ca == "Let's Encrypt" %} + # OCSP settings + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; + resolver 127.0.0.1 127.0.1.1 valid=300s; + resolver_timeout 5s; + {% endif %} + + access_log /var/log/nginx/xmpp-upload.{{ domain }}-access.log; + error_log /var/log/nginx/xmpp-upload.{{ domain }}-error.log; +} diff --git a/data/templates/ssl/openssl.cnf b/data/templates/ssl/openssl.cnf index fa5d19fa3..3ef7d80c3 100644 --- a/data/templates/ssl/openssl.cnf +++ b/data/templates/ssl/openssl.cnf @@ -192,7 +192,7 @@ authorityKeyIdentifier=keyid,issuer basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment -subjectAltName=DNS:yunohost.org,DNS:www.yunohost.org,DNS:ns.yunohost.org +subjectAltName=DNS:yunohost.org,DNS:www.yunohost.org,DNS:ns.yunohost.org,DNS:xmpp-upload.yunohost.org [ v3_ca ] diff --git a/locales/en.json b/locales/en.json index 33b45c4fa..c2d29af37 100644 --- a/locales/en.json +++ b/locales/en.json @@ -133,6 +133,7 @@ "certmanager_domain_http_not_working": "It seems the domain {domain:s} cannot be accessed through HTTP. Check that your DNS and NGINX configuration is correct", "certmanager_domain_unknown": "Unknown domain '{domain:s}'", "certmanager_error_no_A_record": "No DNS 'A' record found for '{domain:s}'. You need to make your domain name point to your machine to be able to install a Let's Encrypt certificate. (If you know what you are doing, use '--no-checks' to turn off those checks.)", + "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain:s}' does not resolve to the same IP address as '{domain:s}'. Some features will not be available until you fix this and regenerate the certificate.", "certmanager_hit_rate_limit": "Too many certificates already issued for this exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", "certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using a public IP address (domain '{domain:s}' with IP '{ip:s}'). You may be experiencing a hairpinning issue, or the firewall/router ahead of your server is misconfigured.", "certmanager_no_cert_file": "Could not read the certificate file for the domain {domain:s} (file: {file:s})", @@ -224,6 +225,7 @@ "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", + "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index d141ac8e5..5fae59060 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -639,6 +639,16 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain + from yunohost.domain import _get_maindomain + if domain == _get_maindomain(): + # Include xmpp-upload subdomain in subject alternate names + subdomain="xmpp-upload." + domain + try: + _dns_ip_match_public_ip(get_public_ip(), subdomain) + csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) + except YunohostError: + logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) + # Set the key with open(key_file, 'rt') as f: key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8f8a68812..eb84f27d0 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -79,6 +79,9 @@ def domain_add(operation_logger, domain, dyndns=False): from yunohost.app import app_ssowatconf from yunohost.utils.ldap import _get_ldap_interface + if domain.startswith("xmpp-upload."): + raise YunohostError("domain_cannot_add_xmpp_upload") + ldap = _get_ldap_interface() try: @@ -412,6 +415,7 @@ def _build_dns_conf(domain, ttl=3600): {"type": "CNAME", "name": "muc", "value": "@", "ttl": 3600}, {"type": "CNAME", "name": "pubsub", "value": "@", "ttl": 3600}, {"type": "CNAME", "name": "vjud", "value": "@", "ttl": 3600} + {"type": "CNAME", "name": "xmpp-upload", "value": "@", "ttl": 3600} ], "mail": [ {"type": "MX", "name": "@", "value": "10 domain.tld.", "ttl": 3600}, @@ -453,6 +457,7 @@ def _build_dns_conf(domain, ttl=3600): ["muc", ttl, "CNAME", "@"], ["pubsub", ttl, "CNAME", "@"], ["vjud", ttl, "CNAME", "@"], + ["xmpp-upload", ttl, "CNAME", "@"], ] # SPF record