def install_cert(domain, ssl_cert, ssl_chain, env): # Write the combined cert+chain to a temporary path and validate that it is OK. # The certificate always goes above the chain. import tempfile fd, fn = tempfile.mkstemp('.pem') os.write(fd, (ssl_cert + '\n' + ssl_chain).encode("ascii")) os.close(fd) # Do validation on the certificate before installing it. ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem')) cert_status, cert_status_details = check_certificate(domain, fn, ssl_private_key) if cert_status != "OK": if cert_status == "SELF-SIGNED": cert_status = "This is a self-signed certificate. I can't install that." os.unlink(fn) if cert_status_details is not None: cert_status += " " + cert_status_details return cert_status # Where to put it? # Make a unique path for the certificate. from cryptography.hazmat.primitives import hashes from binascii import hexlify cert = load_pem(load_cert_chain(fn)[0]) all_domains, cn = get_certificate_domains(cert) path = "%s-%s-%s.pem" % ( safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix ) ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', path)) # Install the certificate. os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True) shutil.move(fn, ssl_certificate) ret = ["OK"] # When updating the cert for PRIMARY_HOSTNAME, symlink it from the system # certificate path, which is hard-coded for various purposes, and then # restart postfix and dovecot. if domain == env['PRIMARY_HOSTNAME']: # Update symlink. system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem')) os.unlink(system_ssl_certificate) os.symlink(ssl_certificate, system_ssl_certificate) # Restart postfix and dovecot so they pick up the new file. shell('check_call', ["/usr/sbin/service", "postfix", "restart"]) shell('check_call', ["/usr/sbin/service", "dovecot", "restart"]) ret.append("mail services restarted") # 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. # If the user does it, they must manually update DNS. # Update the web configuration so nginx picks up the new certificate file. from web_update import do_web_update ret.append( do_web_update(env) ) return "\n".join(ret)
def kick(env, mail_result=None): results = [] # Include the current operation's result in output. if mail_result is not None: results.append(mail_result + "\n") # Ensure every required alias exists. existing_users = get_mail_users(env) existing_aliases = get_mail_aliases(env) required_aliases = get_required_aliases(env) def ensure_admin_alias_exists(source): # If a user account exists with that address, we're good. if source in existing_users: return # Does this alias exists? for s, t in existing_aliases: if s == source: return # Doesn't exist. administrator = get_system_administrator(env) if source == administrator: return # don't make an alias from the administrator to itself --- this alias must be created manually add_mail_alias(source, administrator, env, do_kick=False) results.append("added alias %s (=> %s)\n" % (source, administrator)) for alias in required_aliases: ensure_admin_alias_exists(alias) # Remove auto-generated postmaster/admin on domains we no # longer have any other email addresses for. for source, target in existing_aliases: user, domain = source.split("@") if ( user in ("postmaster", "admin") and source not in required_aliases and target == get_system_administrator(env) ): remove_mail_alias(source, env, do_kick=False) results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (source, target)) # Update DNS and nginx in case any domains are added/removed. from dns_update import do_dns_update results.append(do_dns_update(env)) from web_update import do_web_update results.append(do_web_update(env)) return "".join(s for s in results if s != "")
def kick(env, mail_result): # Update DNS and nginx in case any domains are added/removed. from dns_update import do_dns_update from web_update import do_web_update results = [ do_dns_update(env), mail_result + "\n", do_web_update(env), ] return "".join(s for s in results if s != "")
def kick(env, mail_result=None): results = [] # Include the current operation's result in output. if mail_result is not None: results.append(mail_result + "\n") # Ensure every required alias exists. existing_users = get_mail_users(env) existing_alias_records = get_mail_aliases(env) existing_aliases = set(a for a, *_ in existing_alias_records) # just first entry in tuple required_aliases = get_required_aliases(env) def ensure_admin_alias_exists(address): # If a user account exists with that address, we're good. if address in existing_users: return # If the alias already exists, we're good. if address in existing_aliases: return # Doesn't exist. administrator = get_system_administrator(env) if address == administrator: return # don't make an alias from the administrator to itself --- this alias must be created manually add_mail_alias(address, administrator, "", env, do_kick=False) if administrator not in existing_aliases: return # don't report the alias in output if the administrator alias isn't in yet -- this is a hack to supress confusing output on initial setup results.append("added alias %s (=> %s)\n" % (address, administrator)) for address in required_aliases: ensure_admin_alias_exists(address) # Remove auto-generated postmaster/admin on domains we no # longer have any other email addresses for. for address, forwards_to, *_ in existing_alias_records: user, domain = address.split("@") if user in ("postmaster", "admin", "abuse") \ and address not in required_aliases \ and forwards_to == get_system_administrator(env): remove_mail_alias(address, env, do_kick=False) results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (address, forwards_to)) # Update DNS and nginx in case any domains are added/removed. from dns_update import do_dns_update results.append( do_dns_update(env) ) from web_update import do_web_update results.append( do_web_update(env) ) return "".join(s for s in results if s != "")
def post_install_func(env): ret = [] # Get the certificate to use for PRIMARY_HOSTNAME. ssl_certificates = get_ssl_certificates(env) cert = get_domain_ssl_files(env['PRIMARY_HOSTNAME'], ssl_certificates, env, use_main_cert=False) if not cert: # Ruh-row, we don't have any certificate usable # for the primary hostname. ret.append("there is no valid certificate for " + env['PRIMARY_HOSTNAME']) # Symlink the best cert for PRIMARY_HOSTNAME to the system # certificate path, which is hard-coded for various purposes, and then # restart postfix and dovecot. system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem')) if cert and os.readlink(system_ssl_certificate) != cert['certificate']: # Update symlink. ret.append("updating primary certificate") ssl_certificate = cert['certificate'] os.unlink(system_ssl_certificate) os.symlink(ssl_certificate, system_ssl_certificate) # Restart postfix and dovecot so they pick up the new file. shell('check_call', ["/usr/sbin/service", "postfix", "restart"]) shell('check_call', ["/usr/sbin/service", "dovecot", "restart"]) ret.append("mail services restarted") # 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. # If the user does it, they must manually update DNS. # Update the web configuration so nginx picks up the new certificate file. from web_update import do_web_update ret.append( do_web_update(env) ) return ret
def kick(env, mail_result=None): results = [] # Inclde the current operation's result in output. if mail_result is not None: results.append(mail_result + "\n") # Create hostmaster@ for the primary domain if it does not already exist. # Default the target to administrator@ which the user is responsible for # setting and keeping up to date. existing_aliases = get_mail_aliases(env) administrator = "administrator@" + env['PRIMARY_HOSTNAME'] def ensure_admin_alias_exists(source): # Does this alias exists? for s, t in existing_aliases: if s == source: return # Doesn't exist. add_mail_alias(source, administrator, env, do_kick=False) results.append("added alias %s (=> %s)\n" % (source, administrator)) ensure_admin_alias_exists("hostmaster@" + env['PRIMARY_HOSTNAME']) # Get a list of domains we serve mail for, except ones for which the only # email on that domain is a postmaster/admin alias to the administrator. real_mail_domains = get_mail_domains(env, filter_aliases = lambda alias : \ (not alias[0].startswith("postmaster@") \ and not alias[0].startswith("admin@")) \ or alias[1] != administrator \ ) # Create postmaster@ and admin@ for all domains we serve mail on. # postmaster@ is assumed to exist by our Postfix configuration. admin@ # isn't anything, but it might save the user some trouble e.g. when # buying an SSL certificate. for domain in real_mail_domains: ensure_admin_alias_exists("postmaster@" + domain) ensure_admin_alias_exists("admin@" + domain) # Remove auto-generated hostmaster/postmaster/admin on domains we no # longer have any other email addresses for. for source, target in existing_aliases: user, domain = source.split("@") if user in ("postmaster", "admin") and domain not in real_mail_domains \ and target == administrator: remove_mail_alias(source, env, do_kick=False) results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (source, target)) # Update DNS and nginx in case any domains are added/removed. from dns_update import do_dns_update results.append( do_dns_update(env) ) from web_update import do_web_update results.append( do_web_update(env) ) return "".join(s for s in results if s != "")
def web_update(): from web_update import do_web_update return do_web_update(env)
def web_update(): from web_update import do_web_update try: return do_web_update(env) except Exception as e: return (str(e), 500)
def install_cert(domain, ssl_cert, ssl_chain, env, raw=False): # Write the combined cert+chain to a temporary path and validate that it is OK. # The certificate always goes above the chain. import tempfile fd, fn = tempfile.mkstemp('.pem') os.write(fd, (ssl_cert + '\n' + ssl_chain).encode("ascii")) os.close(fd) # Do validation on the certificate before installing it. ssl_private_key = os.path.join( os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem')) cert_status, cert_status_details = check_certificate( domain, fn, ssl_private_key) if cert_status != "OK": if cert_status == "SELF-SIGNED": cert_status = "This is a self-signed certificate. I can't install that." os.unlink(fn) if cert_status_details is not None: cert_status += " " + cert_status_details return cert_status # Where to put it? # Make a unique path for the certificate. from cryptography.hazmat.primitives import hashes from binascii import hexlify cert = load_pem(load_cert_chain(fn)[0]) all_domains, cn = get_certificate_domains(cert) path = "%s-%s-%s.pem" % ( safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date hexlify(cert.fingerprint( hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix ) ssl_certificate = os.path.join( os.path.join(env["STORAGE_ROOT"], 'ssl', path)) # Install the certificate. os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True) shutil.move(fn, ssl_certificate) ret = ["OK"] # When updating the cert for PRIMARY_HOSTNAME, symlink it from the system # certificate path, which is hard-coded for various purposes, and then # restart postfix and dovecot. if domain == env['PRIMARY_HOSTNAME']: # Update symlink. system_ssl_certificate = os.path.join( os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem')) os.unlink(system_ssl_certificate) os.symlink(ssl_certificate, system_ssl_certificate) # Restart postfix and dovecot so they pick up the new file. shell('check_call', ["/usr/sbin/service", "postfix", "restart"]) shell('check_call', ["/usr/sbin/service", "dovecot", "restart"]) ret.append("mail services restarted") # 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. # If the user does it, they must manually update DNS. # Update the web configuration so nginx picks up the new certificate file. from web_update import do_web_update ret.append(do_web_update(env)) if raw: return ret return "\n".join(ret)