mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-11-23 02:27:05 +00:00
Added key rollover code.
This commit is contained in:
parent
94aab7c5e2
commit
e6657d6ebe
@ -364,6 +364,77 @@ def ssl_get_csr(domain):
|
|||||||
ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem'))
|
ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem'))
|
||||||
return create_csr(domain, ssl_private_key, request.form.get('countrycode', ''), env)
|
return create_csr(domain, ssl_private_key, request.form.get('countrycode', ''), env)
|
||||||
|
|
||||||
|
@app.route('/ssl/renew/<domain>', methods=['POST'])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def ssl_renew(domain):
|
||||||
|
from exclusiveprocess import Lock
|
||||||
|
from utils import load_environment
|
||||||
|
from ssl_certificates import provision_certificates
|
||||||
|
existing_key = request.form.get('existing_key')
|
||||||
|
Lock(die=True).forever()
|
||||||
|
env = load_environment()
|
||||||
|
if existing_key == "yes":
|
||||||
|
status = provision_certificates(env, limit_domains=[], domain_to_be_renewed=domain)
|
||||||
|
app.logger.warning("renew without new key=", status) # TODO: remove this line after testing
|
||||||
|
elif existing_key == "no":
|
||||||
|
import glob
|
||||||
|
try:
|
||||||
|
# steps followed
|
||||||
|
# 1. take a backup of the current /home/user-data/ssl/ folder to be safe
|
||||||
|
# 2. renew all the existing certificates from CSR generated from the existing next_ssl_private_key
|
||||||
|
# 3. if the renew is successful, replace the current ssl_private_key with the next_ssl_private_key and
|
||||||
|
# 4. generate the next_ssl_private_key
|
||||||
|
# 5. if any error occurs, copy everything from the /home/user-data/ssl-backup folder to /home/user-data/ssl
|
||||||
|
|
||||||
|
# step 1
|
||||||
|
files = glob.glob(env["STORAGE_ROOT"] + "/ssl/*")
|
||||||
|
for file in files:
|
||||||
|
subprocess.check_output(["cp", "-r", file, env["STORAGE_ROOT"] + "/ssl-backup/"])
|
||||||
|
|
||||||
|
# step 2
|
||||||
|
status = provision_certificates(env, limit_domains=[], new_key=True)
|
||||||
|
|
||||||
|
# step 3 and 4 is in post_install_func method of ssl_certificates.py
|
||||||
|
app.logger.warning("renew with new key=", status) # TODO: remove this line after proper testing
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
files = glob.glob(env["STORAGE_ROOT"] + "/ssl-backup/*")
|
||||||
|
for file in files:
|
||||||
|
subprocess.check_output(["cp", "-r", file, env["STORAGE_ROOT"] + "/ssl/"])
|
||||||
|
app.logger.warning(traceback.print_exc()) # TODO: remove this line after proper testing
|
||||||
|
return json_response({
|
||||||
|
"title": "Error",
|
||||||
|
"log": "Sorry, something is not right!",
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return json_response({
|
||||||
|
"title": "Error",
|
||||||
|
"log": "Sorry, something is not right!",
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in status:
|
||||||
|
if isinstance(status, str):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if domain in item['domains']:
|
||||||
|
if item['result'] == 'skipped':
|
||||||
|
return json_response({
|
||||||
|
"title": item["result"].capitalize(),
|
||||||
|
"log": "\n".join(item['log']),
|
||||||
|
})
|
||||||
|
elif item['result'] == 'installed':
|
||||||
|
return json_response({
|
||||||
|
"title": item["result"].capitalize(),
|
||||||
|
"log": "Your certificate containing these domains " + ",".join(
|
||||||
|
item['domains']) + " have been renewed",
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return json_response({
|
||||||
|
"title": item["result"].capitalize(),
|
||||||
|
"log": "\n".join(item['log'])
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/ssl/install', methods=['POST'])
|
@app.route('/ssl/install', methods=['POST'])
|
||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def ssl_install_cert():
|
def ssl_install_cert():
|
||||||
@ -604,7 +675,8 @@ def log_failed_login(request):
|
|||||||
# APP
|
# APP
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if "DEBUG" in os.environ: app.debug = True
|
app.debug = True # TODO: remove this line and uncomment the next line after testing
|
||||||
|
# if "DEBUG" in os.environ: app.debug = True
|
||||||
if "APIKEY" in os.environ: auth_service.key = os.environ["APIKEY"]
|
if "APIKEY" in os.environ: auth_service.key = os.environ["APIKEY"]
|
||||||
|
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
|
@ -174,9 +174,11 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
|
|||||||
|
|
||||||
# Add a DANE TLSA record for SMTP.
|
# Add a DANE TLSA record for SMTP.
|
||||||
records.append(("_25._tcp", "TLSA", build_tlsa_record(env), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used."))
|
records.append(("_25._tcp", "TLSA", build_tlsa_record(env), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used."))
|
||||||
|
records.append(("_25._tcp", "TLSA", build_tlsa_record(env, from_cert=False), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used."))
|
||||||
|
|
||||||
# Add a DANE TLSA record for HTTPS, which some browser extensions might make use of.
|
# Add a DANE TLSA record for HTTPS, which some browser extensions might make use of.
|
||||||
records.append(("_443._tcp", "TLSA", build_tlsa_record(env), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it."))
|
records.append(("_443._tcp", "TLSA", build_tlsa_record(env), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it."))
|
||||||
|
records.append(("_443._tcp", "TLSA", build_tlsa_record(env, from_cert=False), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it."))
|
||||||
|
|
||||||
# Add a SSHFP records to help SSH key validation. One per available SSH key on this system.
|
# Add a SSHFP records to help SSH key validation. One per available SSH key on this system.
|
||||||
for value in build_sshfp_records():
|
for value in build_sshfp_records():
|
||||||
@ -367,7 +369,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
|
|||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
def build_tlsa_record(env):
|
def build_tlsa_record(env, from_cert=True):
|
||||||
# A DANE TLSA record in DNS specifies that connections on a port
|
# A DANE TLSA record in DNS specifies that connections on a port
|
||||||
# must use TLS and the certificate must match a particular criteria.
|
# must use TLS and the certificate must match a particular criteria.
|
||||||
#
|
#
|
||||||
@ -390,11 +392,16 @@ def build_tlsa_record(env):
|
|||||||
from ssl_certificates import load_cert_chain, load_pem
|
from ssl_certificates import load_cert_chain, load_pem
|
||||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||||
|
|
||||||
fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem")
|
if from_cert:
|
||||||
cert = load_pem(load_cert_chain(fn)[0])
|
fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem")
|
||||||
|
cert = load_pem(load_cert_chain(fn)[0])
|
||||||
subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
||||||
# We could have also loaded ssl_private_key.pem and called priv_key.public_key().public_bytes(...)
|
else:
|
||||||
|
# this is for Double TLSA scheme of key rollover.
|
||||||
|
# More details here (https://mail.sys4.de/pipermail/dane-users/2018-February/000440.html)
|
||||||
|
fn = os.path.join(env["STORAGE_ROOT"], "ssl", "next_ssl_private_key.pem")
|
||||||
|
private_key = load_pem(open(fn, 'rb').read())
|
||||||
|
subject_public_key = private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
||||||
|
|
||||||
pk_hash = hashlib.sha256(subject_public_key).hexdigest()
|
pk_hash = hashlib.sha256(subject_public_key).hexdigest()
|
||||||
|
|
||||||
@ -862,6 +869,8 @@ def set_custom_dns_record(qname, rtype, value, action, env):
|
|||||||
|
|
||||||
if not re.search(DOMAIN_RE, value):
|
if not re.search(DOMAIN_RE, value):
|
||||||
raise ValueError("Invalid value.")
|
raise ValueError("Invalid value.")
|
||||||
|
elif rtype == "TLSA":
|
||||||
|
pass
|
||||||
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
|
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
|
||||||
# anything goes
|
# anything goes
|
||||||
pass
|
pass
|
||||||
|
@ -45,7 +45,7 @@ def get_ssl_certificates(env):
|
|||||||
|
|
||||||
# Remember stuff.
|
# Remember stuff.
|
||||||
private_keys = { }
|
private_keys = { }
|
||||||
certificates = [ ]
|
certificates = []
|
||||||
|
|
||||||
# Scan each of the files to find private keys and certificates.
|
# Scan each of the files to find private keys and certificates.
|
||||||
# We must load all of the private keys first before processing
|
# We must load all of the private keys first before processing
|
||||||
@ -73,6 +73,7 @@ def get_ssl_certificates(env):
|
|||||||
domains = { }
|
domains = { }
|
||||||
for cert in certificates:
|
for cert in certificates:
|
||||||
# What domains is this certificate good for?
|
# What domains is this certificate good for?
|
||||||
|
# cert_domains = cert common name + all SANs, primary_domain = cert common name
|
||||||
cert_domains, primary_domain = get_certificate_domains(cert)
|
cert_domains, primary_domain = get_certificate_domains(cert)
|
||||||
cert._primary_domain = primary_domain
|
cert._primary_domain = primary_domain
|
||||||
|
|
||||||
@ -186,8 +187,16 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True
|
|||||||
from web_update import get_web_domains
|
from web_update import get_web_domains
|
||||||
from status_checks import query_dns, normalize_ip
|
from status_checks import query_dns, normalize_ip
|
||||||
|
|
||||||
|
# existing_certs = all valid certificates that are in /home/user-data/ssl or 1 level deep
|
||||||
|
# validity indicator -> private key exists for the cert public key?, not_expired?
|
||||||
|
# if multiple valid certs exist, then the one with the furthest expiry date and filename
|
||||||
|
# lexicographically smallest is returned
|
||||||
existing_certs = get_ssl_certificates(env)
|
existing_certs = get_ssl_certificates(env)
|
||||||
|
|
||||||
|
# this function returns the list of domain names of all the email addresses and adds
|
||||||
|
# autoconfig, www, mta-sts, autodiscover subdomain to each of these domains
|
||||||
|
# if exclude_dns_elsewhere flag is set, then all the domains having A/AAAA record not on this machine
|
||||||
|
# are excluded
|
||||||
plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False)
|
plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False)
|
||||||
actual_web_domains = get_web_domains(env)
|
actual_web_domains = get_web_domains(env)
|
||||||
|
|
||||||
@ -212,7 +221,8 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True
|
|||||||
# how Let's Encrypt will connect.
|
# how Let's Encrypt will connect.
|
||||||
bad_dns = []
|
bad_dns = []
|
||||||
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
|
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
|
||||||
if not value: continue # IPv6 is not configured
|
if not value:
|
||||||
|
continue # IPv6 is not configured
|
||||||
response = query_dns(domain, rtype)
|
response = query_dns(domain, rtype)
|
||||||
if response != normalize_ip(value):
|
if response != normalize_ip(value):
|
||||||
bad_dns.append("%s (%s)" % (response, rtype))
|
bad_dns.append("%s (%s)" % (response, rtype))
|
||||||
@ -226,6 +236,7 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True
|
|||||||
# DNS is all good.
|
# DNS is all good.
|
||||||
|
|
||||||
# Check for a good existing cert.
|
# Check for a good existing cert.
|
||||||
|
# existing_cert = existing cert for domain
|
||||||
existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True)
|
existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True)
|
||||||
if existing_cert:
|
if existing_cert:
|
||||||
existing_cert_check = check_certificate(domain, existing_cert['certificate'], existing_cert['private-key'],
|
existing_cert_check = check_certificate(domain, existing_cert['certificate'], existing_cert['private-key'],
|
||||||
@ -242,19 +253,30 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True
|
|||||||
|
|
||||||
return (domains_to_provision, domains_cant_provision)
|
return (domains_to_provision, domains_cant_provision)
|
||||||
|
|
||||||
def provision_certificates(env, limit_domains):
|
def provision_certificates(env, limit_domains, domain_to_be_renewed=None, new_key=False):
|
||||||
# What domains should we provision certificates for? And what
|
# What domains should we provision certificates for? And what
|
||||||
# errors prevent provisioning for other domains.
|
# errors prevent provisioning for other domains.
|
||||||
domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains)
|
|
||||||
|
|
||||||
# Build a list of what happened on each domain or domain-set.
|
|
||||||
ret = []
|
ret = []
|
||||||
for domain, error in domains_cant_provision.items():
|
is_tlsa_update_required = False
|
||||||
ret.append({
|
if new_key:
|
||||||
"domains": [domain],
|
from web_update import get_web_domains
|
||||||
"log": [error],
|
domains = get_web_domains(env)
|
||||||
"result": "skipped",
|
elif domain_to_be_renewed:
|
||||||
})
|
existing_certs = get_ssl_certificates(env)
|
||||||
|
existing_cert = get_domain_ssl_files(domain_to_be_renewed, existing_certs, env, use_main_cert=False, allow_missing_cert=True)
|
||||||
|
domains, primary_domain = get_certificate_domains(load_pem(load_cert_chain(existing_cert["certificate"])[0]))
|
||||||
|
else:
|
||||||
|
# domains = domains for which a certificate can be provisioned
|
||||||
|
# domains_cant_provision = domains for which a certificate can't be provisioned and the reason
|
||||||
|
domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains)
|
||||||
|
|
||||||
|
# Build a list of what happened on each domain or domain-set.
|
||||||
|
for domain, error in domains_cant_provision.items():
|
||||||
|
ret.append({
|
||||||
|
"domains": [domain],
|
||||||
|
"log": [error],
|
||||||
|
"result": "skipped",
|
||||||
|
})
|
||||||
|
|
||||||
# Break into groups by DNS zone: Group every domain with its parent domain, if
|
# Break into groups by DNS zone: Group every domain with its parent domain, if
|
||||||
# its parent domain is in the list of domains to request a certificate for.
|
# its parent domain is in the list of domains to request a certificate for.
|
||||||
@ -309,6 +331,8 @@ def provision_certificates(env, limit_domains):
|
|||||||
# Create a CSR file for our master private key so that certbot
|
# Create a CSR file for our master private key so that certbot
|
||||||
# uses our private key.
|
# uses our private key.
|
||||||
key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem')
|
key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem')
|
||||||
|
if new_key:
|
||||||
|
key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'next_ssl_private_key.pem')
|
||||||
with tempfile.NamedTemporaryFile() as csr_file:
|
with tempfile.NamedTemporaryFile() as csr_file:
|
||||||
# We could use openssl, but certbot requires
|
# We could use openssl, but certbot requires
|
||||||
# that the CN domain and SAN domains match
|
# that the CN domain and SAN domains match
|
||||||
@ -345,9 +369,9 @@ def provision_certificates(env, limit_domains):
|
|||||||
"certbot",
|
"certbot",
|
||||||
"certonly",
|
"certonly",
|
||||||
#"-v", # just enough to see ACME errors
|
#"-v", # just enough to see ACME errors
|
||||||
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
|
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
|
||||||
|
|
||||||
"-d", ",".join(domain_list), # first will be main domain
|
"-d", ",".join(domain_list), # first will be main domain
|
||||||
|
|
||||||
"--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually
|
"--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually
|
||||||
"--cert-path", os.path.join(d, 'cert'), # we only use the full chain
|
"--cert-path", os.path.join(d, 'cert'), # we only use the full chain
|
||||||
@ -363,6 +387,8 @@ def provision_certificates(env, limit_domains):
|
|||||||
|
|
||||||
ret[-1]["log"].append(certbotret)
|
ret[-1]["log"].append(certbotret)
|
||||||
ret[-1]["result"] = "installed"
|
ret[-1]["result"] = "installed"
|
||||||
|
if new_key and env['PRIMARY_HOSTNAME'] in domains:
|
||||||
|
is_tlsa_update_required = True
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
ret[-1]["log"].append(e.output.decode("utf8"))
|
ret[-1]["log"].append(e.output.decode("utf8"))
|
||||||
ret[-1]["result"] = "error"
|
ret[-1]["result"] = "error"
|
||||||
@ -371,7 +397,7 @@ def provision_certificates(env, limit_domains):
|
|||||||
ret[-1]["result"] = "error"
|
ret[-1]["result"] = "error"
|
||||||
|
|
||||||
# Run post-install steps.
|
# Run post-install steps.
|
||||||
ret.extend(post_install_func(env))
|
ret.extend(post_install_func(env, is_tlsa_update_required=is_tlsa_update_required))
|
||||||
|
|
||||||
# Return what happened with each certificate request.
|
# Return what happened with each certificate request.
|
||||||
return ret
|
return ret
|
||||||
@ -466,7 +492,7 @@ def install_cert_copy_file(fn, env):
|
|||||||
shutil.move(fn, ssl_certificate)
|
shutil.move(fn, ssl_certificate)
|
||||||
|
|
||||||
|
|
||||||
def post_install_func(env):
|
def post_install_func(env, is_tlsa_update_required=False):
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
# Get the certificate to use for PRIMARY_HOSTNAME.
|
# Get the certificate to use for PRIMARY_HOSTNAME.
|
||||||
@ -496,6 +522,25 @@ def post_install_func(env):
|
|||||||
# The DANE TLSA record will remain valid so long as the private key
|
# The DANE TLSA record will remain valid so long as the private key
|
||||||
# hasn't changed. We don't ever change the private key automatically.
|
# hasn't changed. We don't ever change the private key automatically.
|
||||||
# If the user does it, they must manually update DNS.
|
# If the user does it, they must manually update DNS.
|
||||||
|
if is_tlsa_update_required:
|
||||||
|
from dns_update import do_dns_update, set_custom_dns_record, build_tlsa_record
|
||||||
|
subprocess.check_output([
|
||||||
|
"mv", env["STORAGE_ROOT"] + "/ssl/next_ssl_private_key.pem",
|
||||||
|
env["STORAGE_ROOT"] + "/ssl/ssl_private_key.pem"
|
||||||
|
])
|
||||||
|
subprocess.check_output([
|
||||||
|
"openssl", "genrsa",
|
||||||
|
"-out", env["STORAGE_ROOT"] + "/ssl/next_ssl_private_key.pem",
|
||||||
|
"2048"])
|
||||||
|
qname1 = "_25._tcp." + env['PRIMARY_HOSTNAME']
|
||||||
|
qname2 = "_443._tcp." + env['PRIMARY_HOSTNAME']
|
||||||
|
rtype = "TLSA"
|
||||||
|
value = build_tlsa_record(env, from_cert=False)
|
||||||
|
action = "add"
|
||||||
|
if set_custom_dns_record(qname1, rtype, value, action, env):
|
||||||
|
set_custom_dns_record(qname2, rtype, value, action, env)
|
||||||
|
ret.append(do_dns_update(env))
|
||||||
|
|
||||||
|
|
||||||
# Update the web configuration so nginx picks up the new certificate file.
|
# Update the web configuration so nginx picks up the new certificate file.
|
||||||
from web_update import do_web_update
|
from web_update import do_web_update
|
||||||
|
@ -454,9 +454,9 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
|||||||
|
|
||||||
# Check the TLSA record.
|
# Check the TLSA record.
|
||||||
tlsa_qname = "_25._tcp." + domain
|
tlsa_qname = "_25._tcp." + domain
|
||||||
tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None)
|
tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None).split('; ')
|
||||||
tlsa25_expected = build_tlsa_record(env)
|
tlsa25_expected = build_tlsa_record(env)
|
||||||
if tlsa25 == tlsa25_expected:
|
if tlsa25_expected in tlsa25:
|
||||||
output.print_ok("""The DANE TLSA record for incoming mail is correct (%s).""" % tlsa_qname,)
|
output.print_ok("""The DANE TLSA record for incoming mail is correct (%s).""" % tlsa_qname,)
|
||||||
elif tlsa25 is None:
|
elif tlsa25 is None:
|
||||||
if has_dnssec:
|
if has_dnssec:
|
||||||
|
@ -40,7 +40,10 @@
|
|||||||
|
|
||||||
<h3 id="ssl_install_header">Install certificate</h3>
|
<h3 id="ssl_install_header">Install certificate</h3>
|
||||||
|
|
||||||
<p>If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. You can generate the needed CSR below.</p>
|
<p>If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. Click on install certificate button
|
||||||
|
if there is no certificate for your intended domain or
|
||||||
|
click on renew or replace certificate button and click replace if there is an existing certificate and you want to replace it with a new one from a different CA.
|
||||||
|
You can generate the needed CSR below.</p>
|
||||||
|
|
||||||
<p>Which domain are you getting a certificate for?</p>
|
<p>Which domain are you getting a certificate for?</p>
|
||||||
|
|
||||||
@ -101,7 +104,8 @@ function show_tls(keep_provisioning_shown) {
|
|||||||
$('#ssldomain').html('<option value="">(select)</option>');
|
$('#ssldomain').html('<option value="">(select)</option>');
|
||||||
$('#ssl_domains').show();
|
$('#ssl_domains').show();
|
||||||
for (var i = 0; i < domains.length; i++) {
|
for (var i = 0; i < domains.length; i++) {
|
||||||
var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td> <td class='actions'><a href='#' onclick='return ssl_install(this);' class='btn btn-xs'>Install Certificate</a></td></tr>");
|
var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td> " +
|
||||||
|
"<td class='actions'><a href='#' onclick='return ssl_install(this);' class='btn btn-xs'>Install Certificate</a></td></tr>");
|
||||||
tb.append(row);
|
tb.append(row);
|
||||||
row.attr('data-domain', domains[i].domain);
|
row.attr('data-domain', domains[i].domain);
|
||||||
row.find('.domain a').text(domains[i].domain);
|
row.find('.domain a').text(domains[i].domain);
|
||||||
@ -113,7 +117,10 @@ function show_tls(keep_provisioning_shown) {
|
|||||||
row.addClass("text-" + domains[i].status);
|
row.addClass("text-" + domains[i].status);
|
||||||
row.find('.status').text(domains[i].text);
|
row.find('.status').text(domains[i].text);
|
||||||
if (domains[i].status == "success") {
|
if (domains[i].status == "success") {
|
||||||
row.find('.actions a').addClass('btn-default').text('Replace Certificate');
|
row.find('.actions a').addClass('btn-default').text('Renew or replace Certificate');
|
||||||
|
row.find('.actions a').addClass('btn-default').on("click", function () {
|
||||||
|
ssl_renew_or_replace_modal(this);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
row.find('.actions a').addClass('btn-primary').text('Install Certificate');
|
row.find('.actions a').addClass('btn-primary').text('Install Certificate');
|
||||||
}
|
}
|
||||||
@ -131,6 +138,65 @@ function ssl_install(elem) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ssl_renew_or_replace_modal(elem) {
|
||||||
|
show_modal_confirm(
|
||||||
|
"Options",
|
||||||
|
"Do you want to replace the certificate with a new one or just renew this one?",
|
||||||
|
["Replace", "Renew"],
|
||||||
|
function () {
|
||||||
|
ssl_install(elem);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
ssl_cert_renew(elem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function ssl_cert_renew(elem) {
|
||||||
|
var domain = $(elem).parents('tr').attr('data-domain');
|
||||||
|
show_modal_confirm(
|
||||||
|
"Options",
|
||||||
|
"Do you want to renew with the existing key?",
|
||||||
|
["Yes", "No"],
|
||||||
|
function () {
|
||||||
|
ajax_with_indicator(true);
|
||||||
|
api(
|
||||||
|
"/ssl/renew/" + domain,
|
||||||
|
"POST",
|
||||||
|
{
|
||||||
|
existing_key: "yes"
|
||||||
|
},
|
||||||
|
function(data) {
|
||||||
|
$('#ajax_loading_indicator').stop(true).hide();
|
||||||
|
show_modal_error(data["title"], data["log"]);
|
||||||
|
show_tls(true);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
$('#ajax_loading_indicator').stop(true).hide();
|
||||||
|
show_modal_error("Error", "Something is not right, sorry!");
|
||||||
|
show_tls(true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
ajax_with_indicator(true);
|
||||||
|
api(
|
||||||
|
"/ssl/renew/" + domain,
|
||||||
|
"POST",
|
||||||
|
{
|
||||||
|
existing_key: "no"
|
||||||
|
},
|
||||||
|
function(data) {
|
||||||
|
$('#ajax_loading_indicator').stop(true).hide();
|
||||||
|
show_modal_error(data["title"], data["log"]);
|
||||||
|
show_tls(true);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
$('#ajax_loading_indicator').stop(true).hide();
|
||||||
|
show_modal_error("Error", "Something is not right, sorry!");
|
||||||
|
show_tls(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function show_csr() {
|
function show_csr() {
|
||||||
// Can't show a CSR until both inputs are entered.
|
// Can't show a CSR until both inputs are entered.
|
||||||
if ($('#ssldomain').val() == "") return;
|
if ($('#ssldomain').val() == "") return;
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
# The Diffie-Hellman cipher bits are used for SMTP and HTTPS, when a
|
# The Diffie-Hellman cipher bits are used for SMTP and HTTPS, when a
|
||||||
# Diffie-Hellman cipher is selected during TLS negotiation. Diffie-Hellman
|
# Diffie-Hellman cipher is selected during TLS negotiation. Diffie-Hellman
|
||||||
# provides Perfect Forward Secrecy.
|
# provides Perfect Forward Secrecy.
|
||||||
|
|
||||||
source setup/functions.sh # load our functions
|
source setup/functions.sh # load our functions
|
||||||
source /etc/mailinabox.conf # load global vars
|
source /etc/mailinabox.conf # load global vars
|
||||||
@ -66,6 +66,13 @@ if [ ! -f $STORAGE_ROOT/ssl/ssl_private_key.pem ]; then
|
|||||||
openssl genrsa -out $STORAGE_ROOT/ssl/ssl_private_key.pem 2048)
|
openssl genrsa -out $STORAGE_ROOT/ssl/ssl_private_key.pem 2048)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# for Double TLSA scheme. More details here (https://mail.sys4.de/pipermail/dane-users/2018-February/000440.html)
|
||||||
|
if [ ! -f $STORAGE_ROOT/ssl/next_ssl_private_key.pem ]; then
|
||||||
|
# Set the umask so the key file is never world-readable.
|
||||||
|
(umask 077; hide_output \
|
||||||
|
openssl genrsa -out $STORAGE_ROOT/ssl/next_ssl_private_key.pem 2048)
|
||||||
|
fi
|
||||||
|
|
||||||
# Generate a self-signed SSL certificate because things like nginx, dovecot,
|
# Generate a self-signed SSL certificate because things like nginx, dovecot,
|
||||||
# etc. won't even start without some certificate in place, and we need nginx
|
# etc. won't even start without some certificate in place, and we need nginx
|
||||||
# so we can offer the user a control panel to install a better certificate.
|
# so we can offer the user a control panel to install a better certificate.
|
||||||
|
Loading…
Reference in New Issue
Block a user