Commit 07f92286 authored by Joshua Tauberer's avatar Joshua Tauberer

Merge branch 'letsencrypt' for automatic provisioning of TLS certificates from Let's Encrypt

parents 50b5b912 2b9fb964
...@@ -10,11 +10,13 @@ Mail: ...@@ -10,11 +10,13 @@ Mail:
Control Panel: Control Panel:
* The SSL (now "TLS") certificates page now supports provisioning free SSL certificates from Let's Encrypt.
* Report free memory usage. * Report free memory usage.
System: System:
* The daily backup will now email the administrator if there is a problem. * The daily backup will now email the administrator if there is a problem.
* Expiring TLS (SSL) certificates are now automatically renewed via Let's Encrypt.
v0.15a (January 9, 2016) v0.15a (January 9, 2016)
------------------------ ------------------------
......
## $HOSTNAME ## $HOSTNAME
# Redirect all HTTP to HTTPS. # Redirect all HTTP to HTTPS *except* the ACME challenges (Let's Encrypt TLS certificate
# domain validation challenges) path, which must be served over HTTP per the ACME spec
# (due to some Apache vulnerability).
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
...@@ -12,10 +14,19 @@ server { ...@@ -12,10 +14,19 @@ server {
# error pages and in the "Server" HTTP-Header. # error pages and in the "Server" HTTP-Header.
server_tokens off; server_tokens off;
# Redirect using the 'return' directive and the built-in location / {
# variable '$request_uri' to avoid any capturing, matching # Redirect using the 'return' directive and the built-in
# or evaluation of regular expressions. # variable '$request_uri' to avoid any capturing, matching
return 301 https://$HOSTNAME$request_uri; # or evaluation of regular expressions.
return 301 https://$HOSTNAME$request_uri;
}
location /.well-known/acme-challenge/ {
# This path must be served over HTTP for ACME domain validation.
# We map this to a special path where our TLS cert provisioning
# tool knows to store challenge response files.
alias $STORAGE_ROOT/ssl/lets_encrypt/acme_challenges/;
}
} }
# The secure HTTPS server. # The secure HTTPS server.
......
...@@ -327,6 +327,33 @@ def dns_get_dump(): ...@@ -327,6 +327,33 @@ def dns_get_dump():
# SSL # SSL
@app.route('/ssl/status')
@authorized_personnel_only
def ssl_get_status():
from ssl_certificates import get_certificates_to_provision
from web_update import get_web_domains_info, get_web_domains
# What domains can we provision certificates for? What unexpected problems do we have?
provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False)
# What's the current status of TLS certificates on all of the domain?
domains_status = get_web_domains_info(env)
domains_status = [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ]
# Warn the user about domain names not hosted here because of other settings.
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
domains_status.append({
"domain": domain,
"status": "not-applicable",
"text": "The domain's website is hosted elsewhere.",
})
return json_response({
"can_provision": utils.sort_domains(provision, env),
"cant_provision": [{ "domain": domain, "problem": cant_provision[domain] } for domain in utils.sort_domains(cant_provision, env) ],
"status": domains_status,
})
@app.route('/ssl/csr/<domain>', methods=['POST']) @app.route('/ssl/csr/<domain>', methods=['POST'])
@authorized_personnel_only @authorized_personnel_only
def ssl_get_csr(domain): def ssl_get_csr(domain):
...@@ -346,6 +373,17 @@ def ssl_install_cert(): ...@@ -346,6 +373,17 @@ def ssl_install_cert():
return "Invalid domain name." return "Invalid domain name."
return install_cert(domain, ssl_cert, ssl_chain, env) return install_cert(domain, ssl_cert, ssl_chain, env)
@app.route('/ssl/provision', methods=['POST'])
@authorized_personnel_only
def ssl_provision_certs():
from ssl_certificates import provision_certificates
agree_to_tos_url = request.form.get('agree_to_tos_url')
status = provision_certificates(env,
agree_to_tos_url=agree_to_tos_url,
jsonable=True)
return json_response(status)
# WEB # WEB
@app.route('/web/domains') @app.route('/web/domains')
......
...@@ -4,5 +4,8 @@ ...@@ -4,5 +4,8 @@
# Take a backup. # Take a backup.
management/backup.py | management/email_administrator.py "Backup Status" management/backup.py | management/email_administrator.py "Backup Status"
# Provision any new certificates for new domains or domains with expiring certificates.
management/ssl_certificates.py --headless | management/email_administrator.py "Error Provisioning TLS Certificate"
# Run status checks and email the administrator if anything changed. # Run status checks and email the administrator if anything changed.
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice" management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"
This diff is collapsed.
#!/usr/bin/python3 #!/usr/bin/python3
# #
# Checks that the upstream DNS has been set correctly and that # Checks that the upstream DNS has been set correctly and that
# SSL certificates have been signed, etc., and if not tells the user # TLS certificates have been signed, etc., and if not tells the user
# what to do next. # what to do next.
import sys, os, os.path, re, subprocess, datetime, multiprocessing.pool import sys, os, os.path, re, subprocess, datetime, multiprocessing.pool
...@@ -278,23 +278,24 @@ def run_domain_checks(rounded_time, env, output, pool): ...@@ -278,23 +278,24 @@ def run_domain_checks(rounded_time, env, output, pool):
# Get the list of domains that we don't serve web for because of a custom CNAME/A record. # Get the list of domains that we don't serve web for because of a custom CNAME/A record.
domains_with_a_records = get_domains_with_a_records(env) domains_with_a_records = get_domains_with_a_records(env)
ssl_certificates = get_ssl_certificates(env)
# Serial version: # Serial version:
#for domain in sort_domains(domains_to_check, env): #for domain in sort_domains(domains_to_check, env):
# run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains) # run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)
# Parallelize the checks across a worker pool. # Parallelize the checks across a worker pool.
args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records, ssl_certificates) args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records)
for domain in domains_to_check) for domain in domains_to_check)
ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1) ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
ret = dict(ret) # (domain, output) => { domain: output } ret = dict(ret) # (domain, output) => { domain: output }
for domain in sort_domains(ret, env): for domain in sort_domains(ret, env):
ret[domain].playback(output) ret[domain].playback(output)
def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records, ssl_certificates): def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records):
output = BufferedOutput() output = BufferedOutput()
# we'd move this up, but this returns non-pickleable values
ssl_certificates = get_ssl_certificates(env)
# The domain is IDNA-encoded in the database, but for display use Unicode. # The domain is IDNA-encoded in the database, but for display use Unicode.
try: try:
domain_display = idna.decode(domain.encode('ascii')) domain_display = idna.decode(domain.encode('ascii'))
...@@ -600,15 +601,23 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output): ...@@ -600,15 +601,23 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
# for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and # for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and
# other domains, it is required to access its website. # other domains, it is required to access its website.
if domain != env['PRIMARY_HOSTNAME']: if domain != env['PRIMARY_HOSTNAME']:
ip = query_dns(domain, "A") ok_values = []
if ip == env['PUBLIC_IP']: for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
output.print_ok("Domain resolves to this box's IP address. [%s ↦ %s]" % (domain, env['PUBLIC_IP'])) if not expected: continue # IPv6 is not configured
else: value = query_dns(domain, rtype)
output.print_error("""This domain should resolve to your box's IP address (%s) if you would like the box to serve if value == expected:
webmail or a website on this domain. The domain currently resolves to %s in public DNS. It may take several hours for ok_values.append(value)
public DNS to update after a change. This problem may result from other issues listed here.""" % (env['PUBLIC_IP'], ip)) else:
output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve
webmail or a website on this domain. The domain currently resolves to %s in public DNS. It may take several hours for
public DNS to update after a change. This problem may result from other issues listed here.""" % (rtype, expected, value))
return
# We need a SSL certificate for PRIMARY_HOSTNAME because that's where the # If both A and AAAA are correct...
output.print_ok("Domain resolves to this box's IP address. [%s ↦ %s]" % (domain, '; '.join(ok_values)))
# We need a TLS certificate for PRIMARY_HOSTNAME because that's where the
# user will log in with IMAP or webmail. Any other domain we serve a # user will log in with IMAP or webmail. Any other domain we serve a
# website for also needs a signed certificate. # website for also needs a signed certificate.
check_ssl_cert(domain, rounded_time, ssl_certificates, env, output) check_ssl_cert(domain, rounded_time, ssl_certificates, env, output)
...@@ -650,56 +659,39 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None): ...@@ -650,56 +659,39 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
return "; ".join(sorted(str(r).rstrip('.') for r in response)) return "; ".join(sorted(str(r).rstrip('.') for r in response))
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output): def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
# Check that SSL certificate is signed. # Check that TLS certificate is signed.
# Skip the check if the A record is not pointed here. # Skip the check if the A record is not pointed here.
if query_dns(domain, "A", None) not in (env['PUBLIC_IP'], None): return if query_dns(domain, "A", None) not in (env['PUBLIC_IP'], None): return
# Where is the SSL stored? # Where is the certificate file stored?
x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True) tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if tls_cert is None:
if x is None: output.print_warning("""No TLS (SSL) certificate is installed for this domain. Visitors to a website on
output.print_warning("""No SSL certificate is installed for this domain. Visitors to a website on
this domain will get a security warning. If you are not serving a website on this domain, you do this domain will get a security warning. If you are not serving a website on this domain, you do
not need to take any action. Use the SSL Certificates page in the control panel to install a not need to take any action. Use the TLS Certificates page in the control panel to install a
SSL certificate.""") TLS certificate.""")
return return
ssl_key, ssl_certificate, ssl_via = x
# Check that the certificate is good. # Check that the certificate is good.
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, rounded_time=rounded_time) cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"], rounded_time=rounded_time)
if cert_status == "OK": if cert_status == "OK":
# The certificate is ok. The details has expiry info. # The certificate is ok. The details has expiry info.
output.print_ok("SSL certificate is signed & valid. %s %s" % (ssl_via if ssl_via else "", cert_status_details)) output.print_ok("TLS (SSL) certificate is signed & valid. " + cert_status_details)
elif cert_status == "SELF-SIGNED": elif cert_status == "SELF-SIGNED":
# Offer instructions for purchasing a signed certificate. # Offer instructions for purchasing a signed certificate.
fingerprint = shell('check_output', [
"openssl",
"x509",
"-in", ssl_certificate,
"-noout",
"-fingerprint"
])
fingerprint = re.sub(".*Fingerprint=", "", fingerprint).strip()
if domain == env['PRIMARY_HOSTNAME']: if domain == env['PRIMARY_HOSTNAME']:
output.print_error("""The SSL certificate for this domain is currently self-signed. You will get a security output.print_error("""The TLS (SSL) certificate for this domain is currently self-signed. You will get a security
warning when you check or send email and when visiting this domain in a web browser (for webmail or warning when you check or send email and when visiting this domain in a web browser (for webmail or
static site hosting). Use the SSL Certificates page in the control panel to install a signed SSL certificate. static site hosting).""")
You may choose to leave the self-signed certificate in place and confirm the security exception, but check that
the certificate fingerprint matches the following:""")
output.print_line("")
output.print_line(" " + fingerprint, monospace=True)
else: else:
output.print_error("""The SSL certificate for this domain is self-signed.""") output.print_error("""The TLS (SSL) certificate for this domain is self-signed.""")
else: else:
output.print_error("The SSL certificate has a problem: " + cert_status) output.print_error("The TLS (SSL) certificate has a problem: " + cert_status)
if cert_status_details: if cert_status_details:
output.print_line("") output.print_line("")
output.print_line(cert_status_details) output.print_line(cert_status_details)
...@@ -927,10 +919,10 @@ if __name__ == "__main__": ...@@ -927,10 +919,10 @@ if __name__ == "__main__":
if query_dns(domain, "A") != env['PUBLIC_IP']: if query_dns(domain, "A") != env['PUBLIC_IP']:
sys.exit(1) sys.exit(1)
ssl_certificates = get_ssl_certificates(env) ssl_certificates = get_ssl_certificates(env)
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, ssl_certificates, env) tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)
if not os.path.exists(ssl_certificate): if not os.path.exists(tls_cert["certificate"]):
sys.exit(1) sys.exit(1)
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, warn_if_expiring_soon=False) cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"], warn_if_expiring_soon=False)
if cert_status != "OK": if cert_status != "OK":
sys.exit(1) sys.exit(1)
sys.exit(0) sys.exit(0)
......
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li> <li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
<li><a href="#ssl" onclick="return show_panel(this);">SSL Certificates</a></li> <li><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
<li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li> <li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
<li class="divider"></li> <li class="divider"></li>
<li class="dropdown-header">Advanced Pages</li> <li class="dropdown-header">Advanced Pages</li>
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
{% include "web.html" %} {% include "web.html" %}
</div> </div>
<div id="panel_ssl" class="admin_panel"> <div id="panel_tls" class="admin_panel">
{% include "ssl.html" %} {% include "ssl.html" %}
</div> </div>
......
This diff is collapsed.
...@@ -9,7 +9,7 @@ from dns_update import get_custom_dns_config, get_dns_zones ...@@ -9,7 +9,7 @@ from dns_update import get_custom_dns_config, get_dns_zones
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
from utils import shell, safe_domain_name, sort_domains from utils import shell, safe_domain_name, sort_domains
def get_web_domains(env, include_www_redirects=True): def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True):
# What domains should we serve HTTP(S) for? # What domains should we serve HTTP(S) for?
domains = set() domains = set()
...@@ -24,9 +24,10 @@ def get_web_domains(env, include_www_redirects=True): ...@@ -24,9 +24,10 @@ def get_web_domains(env, include_www_redirects=True):
# the topmost of each domain we serve. # the topmost of each domain we serve.
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env)) domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
# ...Unless the domain has an A/AAAA record that maps it to a different if exclude_dns_elsewhere:
# IP address than this box. Remove those domains from our list. # ...Unless the domain has an A/AAAA record that maps it to a different
domains -= get_domains_with_a_records(env) # IP address than this box. Remove those domains from our list.
domains -= get_domains_with_a_records(env)
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail # Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
# as well as Z-Push for Exchange ActiveSync. This can't be removed # as well as Z-Push for Exchange ActiveSync. This can't be removed
...@@ -119,7 +120,7 @@ def make_domain_config(domain, templates, ssl_certificates, env): ...@@ -119,7 +120,7 @@ def make_domain_config(domain, templates, ssl_certificates, env):
root = get_web_root(domain, env) root = get_web_root(domain, env)
# What private key and SSL certificate will we use for this domain? # What private key and SSL certificate will we use for this domain?
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, ssl_certificates, env) tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)
# ADDITIONAL DIRECTIVES. # ADDITIONAL DIRECTIVES.
...@@ -136,7 +137,7 @@ def make_domain_config(domain, templates, ssl_certificates, env): ...@@ -136,7 +137,7 @@ def make_domain_config(domain, templates, ssl_certificates, env):
finally: finally:
f.close() f.close()
return sha1.hexdigest() return sha1.hexdigest()
nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate)) nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(tls_cert["private-key"]), hashfile(tls_cert["certificate"]))
# Add in any user customizations in YAML format. # Add in any user customizations in YAML format.
hsts = "yes" hsts = "yes"
...@@ -177,8 +178,8 @@ def make_domain_config(domain, templates, ssl_certificates, env): ...@@ -177,8 +178,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT']) nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
nginx_conf = nginx_conf.replace("$HOSTNAME", domain) nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
nginx_conf = nginx_conf.replace("$ROOT", root) nginx_conf = nginx_conf.replace("$ROOT", root)
nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key) nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate) nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", tls_cert["certificate"])
nginx_conf = nginx_conf.replace("$REDIRECT_DOMAIN", re.sub(r"^www\.", "", domain)) # for default www redirects to parent domain nginx_conf = nginx_conf.replace("$REDIRECT_DOMAIN", re.sub(r"^www\.", "", domain)) # for default www redirects to parent domain
return nginx_conf return nginx_conf
...@@ -193,20 +194,15 @@ def get_web_root(domain, env, test_exists=True): ...@@ -193,20 +194,15 @@ def get_web_root(domain, env, test_exists=True):
def get_web_domains_info(env): def get_web_domains_info(env):
www_redirects = set(get_web_domains(env)) - set(get_web_domains(env, include_www_redirects=False)) www_redirects = set(get_web_domains(env)) - set(get_web_domains(env, include_www_redirects=False))
has_root_proxy_or_redirect = set(get_web_domains_with_root_overrides(env)) has_root_proxy_or_redirect = set(get_web_domains_with_root_overrides(env))
ssl_certificates = get_ssl_certificates(env)
# for the SSL config panel, get cert status # for the SSL config panel, get cert status
def check_cert(domain): def check_cert(domain):
ssl_certificates = get_ssl_certificates(env) tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True) if tls_cert is None: return ("danger", "No Certificate Installed")
if x is None: return ("danger", "No Certificate Installed") cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
ssl_key, ssl_certificate, ssl_via = x
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
if cert_status == "OK": if cert_status == "OK":
if not ssl_via: return ("success", "Signed & valid. " + cert_status_details)
return ("success", "Signed & valid. " + cert_status_details)
else:
# This is an alternate domain but using the same cert as the primary domain.
return ("success", "Signed & valid. " + ssl_via)
elif cert_status == "SELF-SIGNED": elif cert_status == "SELF-SIGNED":
return ("warning", "Self-signed. Get a signed certificate to stop warnings.") return ("warning", "Self-signed. Get a signed certificate to stop warnings.")
else: else:
...@@ -221,4 +217,4 @@ def get_web_domains_info(env): ...@@ -221,4 +217,4 @@ def get_web_domains_info(env):
"static_enabled": domain not in (www_redirects | has_root_proxy_or_redirect), "static_enabled": domain not in (www_redirects | has_root_proxy_or_redirect),
} }
for domain in get_web_domains(env) for domain in get_web_domains(env)
] ]
\ No newline at end of file
...@@ -38,7 +38,7 @@ These services are protected by [TLS](https://en.wikipedia.org/wiki/Transport_La ...@@ -38,7 +38,7 @@ These services are protected by [TLS](https://en.wikipedia.org/wiki/Transport_La
The services all follow these rules: The services all follow these rules:
* SSL certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh)) * TLS certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh))
* Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered). * Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered).
* Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered. * Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered.
* The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. * The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy.
......
...@@ -11,8 +11,11 @@ if [ -f /usr/local/lib/python2.7/dist-packages/boto/__init__.py ]; then hide_out ...@@ -11,8 +11,11 @@ if [ -f /usr/local/lib/python2.7/dist-packages/boto/__init__.py ]; then hide_out
# build-essential libssl-dev libffi-dev python3-dev: Required to pip install cryptography. # build-essential libssl-dev libffi-dev python3-dev: Required to pip install cryptography.
apt_install python3-flask links duplicity python-boto libyaml-dev python3-dnspython python3-dateutil \ apt_install python3-flask links duplicity python-boto libyaml-dev python3-dnspython python3-dateutil \
build-essential libssl-dev libffi-dev python3-dev python-pip build-essential libssl-dev libffi-dev python3-dev python-pip
hide_output pip3 install --upgrade rtyaml "email_validator>=1.0.0" "idna>=2.0.0" "cryptography>=1.0.2" boto psutil
# Install other Python packages. The first line is the packages that Josh maintains himself!
hide_output pip3 install --upgrade \
rtyaml "email_validator>=1.0.0" free_tls_certificates \
"idna>=2.0.0" "cryptography>=1.0.2" boto psutil
# email_validator is repeated in setup/questions.sh # email_validator is repeated in setup/questions.sh
# Create a backup directory and a random key for encrypting backups. # Create a backup directory and a random key for encrypting backups.
...@@ -44,5 +47,5 @@ cat > /etc/cron.d/mailinabox-nightly << EOF; ...@@ -44,5 +47,5 @@ cat > /etc/cron.d/mailinabox-nightly << EOF;
0 3 * * * root (cd `pwd` && management/daily_tasks.sh) 0 3 * * * root (cd `pwd` && management/daily_tasks.sh)
EOF EOF
# Start it. # Start the management server.
restart_service mailinabox restart_service mailinabox
...@@ -116,6 +116,9 @@ done ...@@ -116,6 +116,9 @@ done
tools/dns_update tools/dns_update
tools/web_update tools/web_update
# If DNS is already working, try to provision TLS certficates from Let's Encrypt.
management/ssl_certificates.py
# If there aren't any mail users yet, create one. # If there aren't any mail users yet, create one.
source setup/firstuser.sh source setup/firstuser.sh
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment