Beispiel #1
0
def get_domain_ssl_files(domain, env):
	# What SSL private key will we use? Allow the user to override this, but
	# in many cases using the same private key for all domains would be fine.
	# Don't allow the user to override the key for PRIMARY_HOSTNAME because
	# that's what's in the main file.
	ssl_key = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_private_key.pem')
	alt_key = os.path.join(env["STORAGE_ROOT"], 'ssl/domains/%s_private_key.pem' % safe_domain_name(domain))
	if domain != env['PRIMARY_HOSTNAME'] and os.path.exists(alt_key):
		ssl_key = alt_key

	# What SSL certificate will we use? This has to be differnet for each
	# domain name. For PRIMARY_HOSTNAME, use the one we generated at set-up
	# time.
	if domain == env['PRIMARY_HOSTNAME']:
		ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem')
	else:
		ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/domains/%s_certifiate.pem' % safe_domain_name(domain))

	# Where would the CSR go? As with the SSL cert itself, the CSR must be
	# different for each domain name.
	if domain == env['PRIMARY_HOSTNAME']:
		csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_cert_sign_req.csr')
	else:
		csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/domains/%s_cert_sign_req.csr' % safe_domain_name(domain))

	return ssl_key, ssl_certificate, csr_path
Beispiel #2
0
def get_domain_ssl_files(domain, env, allow_shared_cert=True):
	# What SSL private key will we use? Allow the user to override this, but
	# in many cases using the same private key for all domains would be fine.
	# Don't allow the user to override the key for PRIMARY_HOSTNAME because
	# that's what's in the main file.
	ssl_key = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_private_key.pem')
	ssl_key_is_alt = False
	alt_key = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/private_key.pem' % safe_domain_name(domain))
	if domain != env['PRIMARY_HOSTNAME'] and os.path.exists(alt_key):
		ssl_key = alt_key
		ssl_key_is_alt = True

	# What SSL certificate will we use?
	ssl_certificate_primary = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem')
	if domain == env['PRIMARY_HOSTNAME']:
		# For PRIMARY_HOSTNAME, use the one we generated at set-up time.
		ssl_certificate = ssl_certificate_primary
	else:
		# For other domains, we'll probably use a certificate in a different path.
		ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/ssl_certificate.pem' % safe_domain_name(domain))

		# But we can be smart and reuse the main SSL certificate if is has
		# a Subject Alternative Name matching this domain. Don't do this if
		# the user has uploaded a different private key for this domain.
		if not ssl_key_is_alt and allow_shared_cert:
			from status_checks import check_certificate
			if check_certificate(domain, ssl_certificate_primary, None)[0] == "OK":
				ssl_certificate = ssl_certificate_primary

	return ssl_key, ssl_certificate
Beispiel #3
0
def make_domain_config(domain, template, template_for_primaryhost, env):
	# How will we configure this domain.

	# Where will its root directory be for static files?

	root = get_web_root(domain, env)

	# What private key and SSL certificate will we use for this domain?
	ssl_key, ssl_certificate, csr_path = get_domain_ssl_files(domain, env)

	# For hostnames created after the initial setup, ensure we have an SSL certificate
	# available. Make a self-signed one now if one doesn't exist.
	ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, csr_path, env)

	# Put pieces together.
	nginx_conf_parts = re.split("\s*# ADDITIONAL DIRECTIVES HERE\s*", template)
	nginx_conf = nginx_conf_parts[0] + "\n"
	if domain == env['PRIMARY_HOSTNAME']:
		nginx_conf += template_for_primaryhost + "\n"

	# Replace substitution strings in the template & return.
	nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
	nginx_conf = nginx_conf.replace("$HOSTNAME", domain.encode("idna").decode("ascii"))
	nginx_conf = nginx_conf.replace("$ROOT", root)
	nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
	nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)

	# Because the certificate may change, we should recognize this so we
	# can trigger an nginx update.
	def hashfile(filepath):
		import hashlib
		sha1 = hashlib.sha1()
		f = open(filepath, 'rb')
		try:
			sha1.update(f.read())
		finally:
			f.close()
		return sha1.hexdigest()
	nginx_conf += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))

	# Add in any user customizations in YAML format.
	nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
	if os.path.exists(nginx_conf_custom_fn):
		yaml = rtyaml.load(open(nginx_conf_custom_fn))
		if domain in yaml:
			yaml = yaml[domain]
			for path, url in yaml.get("proxies", {}).items():
				nginx_conf += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
			for path, url in yaml.get("redirects", {}).items():
				nginx_conf += "\trewrite %s %s permanent;\n" % (path, url)

	# Add in any user customizations in the includes/ folder.
	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
	if os.path.exists(nginx_conf_custom_include):
		nginx_conf += "\tinclude %s;\n" % (nginx_conf_custom_include)

	# Ending.
	nginx_conf += nginx_conf_parts[1]

	return nginx_conf
Beispiel #4
0
def get_web_root(domain, env, test_exists=True):
    # Try STORAGE_ROOT/web/domain_name if it exists, but fall back to STORAGE_ROOT/web/default.
    for test_domain in (domain, "default"):
        root = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(test_domain))
        if os.path.exists(root) or not test_exists:
            break
    return root
Beispiel #5
0
def get_dns_zones(env):
    # What domains should we create DNS zones for? Never create a zone for
    # a domain & a subdomain of that domain.
    domains = get_dns_domains(env)

    # Exclude domains that are subdomains of other domains we know. Proceed
    # by looking at shorter domains first.
    zone_domains = set()
    for domain in sorted(domains, key=lambda d: len(d)):
        for d in zone_domains:
            if domain.endswith("." + d):
                # We found a parent domain already in the list.
                break
        else:
            # 'break' did not occur: there is no parent domain.
            zone_domains.add(domain)

    # Make a nice and safe filename for each domain.
    zonefiles = []
    for domain in zone_domains:
        zonefiles.append([domain, safe_domain_name(domain) + ".txt"])

    # Sort the list so that the order is nice and so that nsd.conf has a
    # stable order so we don't rewrite the file & restart the service
    # meaninglessly.
    zone_order = sort_domains([zone[0] for zone in zonefiles], env)
    zonefiles.sort(key=lambda zone: zone_order.index(zone[0]))

    return zonefiles
Beispiel #6
0
def make_domain_config(domain, template, template_for_primaryhost, env):
	# How will we configure this domain.

	# Where will its root directory be for static files?

	root = get_web_root(domain, env)

	# What private key and SSL certificate will we use for this domain?
	ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)

	# For hostnames created after the initial setup, ensure we have an SSL certificate
	# available. Make a self-signed one now if one doesn't exist.
	ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, env)

	# Put pieces together.
	nginx_conf_parts = re.split("\s*# ADDITIONAL DIRECTIVES HERE\s*", template)
	nginx_conf = nginx_conf_parts[0] + "\n"
	if domain == env['PRIMARY_HOSTNAME']:
		nginx_conf += template_for_primaryhost + "\n"

	# Replace substitution strings in the template & return.
	nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
	nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
	nginx_conf = nginx_conf.replace("$ROOT", root)
	nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
	nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)

	# Because the certificate may change, we should recognize this so we
	# can trigger an nginx update.
	def hashfile(filepath):
		import hashlib
		sha1 = hashlib.sha1()
		f = open(filepath, 'rb')
		try:
			sha1.update(f.read())
		finally:
			f.close()
		return sha1.hexdigest()
	nginx_conf += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))

	# Add in any user customizations in YAML format.
	nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
	if os.path.exists(nginx_conf_custom_fn):
		yaml = rtyaml.load(open(nginx_conf_custom_fn))
		if domain in yaml:
			yaml = yaml[domain]
			for path, url in yaml.get("proxies", {}).items():
				nginx_conf += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
			for path, url in yaml.get("redirects", {}).items():
				nginx_conf += "\trewrite %s %s permanent;\n" % (path, url)

	# Add in any user customizations in the includes/ folder.
	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
	if os.path.exists(nginx_conf_custom_include):
		nginx_conf += "\tinclude %s;\n" % (nginx_conf_custom_include)

	# Ending.
	nginx_conf += nginx_conf_parts[1]

	return nginx_conf
Beispiel #7
0
def get_web_root(domain, env, test_exists=True):
    # Try STORAGE_ROOT/web/domain_name if it exists, but fall back to STORAGE_ROOT/web/default.
    for test_domain in (domain, 'default'):
        root = os.path.join(env["STORAGE_ROOT"], "www",
                            safe_domain_name(test_domain))
        if os.path.exists(root) or not test_exists: break
    return root
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)
Beispiel #9
0
def get_dns_zones(env):
	# What domains should we create DNS zones for? Never create a zone for
	# a domain & a subdomain of that domain.
	domains = get_dns_domains(env)

	# Exclude domains that are subdomains of other domains we know. Proceed
	# by looking at shorter domains first.
	zone_domains = set()
	for domain in sorted(domains, key=lambda d : len(d)):
		for d in zone_domains:
			if domain.endswith("." + d):
				# We found a parent domain already in the list.
				break
		else:
			# 'break' did not occur: there is no parent domain.
			zone_domains.add(domain)

	# Make a nice and safe filename for each domain.
	zonefiles = []
	for domain in zone_domains:
		zonefiles.append([domain, safe_domain_name(domain) + ".txt"])

	# Sort the list so that the order is nice and so that nsd.conf has a
	# stable order so we don't rewrite the file & restart the service
	# meaninglessly.
	zone_order = sort_domains([ zone[0] for zone in zonefiles ], env)
	zonefiles.sort(key = lambda zone : zone_order.index(zone[0]) )

	return zonefiles
Beispiel #10
0
def get_domain_ssl_files(domain, env):
    # What SSL private key will we use? Allow the user to override this, but
    # in many cases using the same private key for all domains would be fine.
    # Don't allow the user to override the key for PRIMARY_HOSTNAME because
    # that's what's in the main file.
    ssl_key = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_private_key.pem')
    ssl_key_is_alt = False
    alt_key = os.path.join(env["STORAGE_ROOT"],
                           'ssl/%s/private_key.pem' % safe_domain_name(domain))
    if domain != env['PRIMARY_HOSTNAME'] and os.path.exists(alt_key):
        ssl_key = alt_key
        ssl_key_is_alt = True

    # What SSL certificate will we use?
    ssl_certificate_primary = os.path.join(env["STORAGE_ROOT"],
                                           'ssl/ssl_certificate.pem')
    if domain == env['PRIMARY_HOSTNAME']:
        # For PRIMARY_HOSTNAME, use the one we generated at set-up time.
        ssl_certificate = ssl_certificate_primary
    else:
        # For other domains, we'll probably use a certificate in a different path.
        ssl_certificate = os.path.join(
            env["STORAGE_ROOT"],
            'ssl/%s/ssl_certificate.pem' % safe_domain_name(domain))

        # But we can be smart and reuse the main SSL certificate if is has
        # a Subject Alternative Name matching this domain. Don't do this if
        # the user has uploaded a different private key for this domain.
        if not ssl_key_is_alt:
            from whats_next import check_certificate
            if check_certificate(domain, ssl_certificate_primary,
                                 None) == "OK":
                ssl_certificate = ssl_certificate_primary

    # Where would the CSR go? As with the SSL cert itself, the CSR must be
    # different for each domain name.
    if domain == env['PRIMARY_HOSTNAME']:
        csr_path = os.path.join(env["STORAGE_ROOT"],
                                'ssl/ssl_cert_sign_req.csr')
    else:
        csr_path = os.path.join(
            env["STORAGE_ROOT"], 'ssl/%s/certificate_signing_request.csr' %
            safe_domain_name(domain))

    return ssl_key, ssl_certificate, csr_path
Beispiel #11
0
def get_domain_ssl_files(domain, env, allow_shared_cert=True):
	# What SSL private key will we use? Allow the user to override this, but
	# in many cases using the same private key for all domains would be fine.
	# Don't allow the user to override the key for PRIMARY_HOSTNAME because
	# that's what's in the main file.
	ssl_key = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_private_key.pem')
	ssl_key_is_alt = False
	alt_key = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/private_key.pem' % safe_domain_name(domain))
	if domain != env['PRIMARY_HOSTNAME'] and os.path.exists(alt_key):
		ssl_key = alt_key
		ssl_key_is_alt = True

	# What SSL certificate will we use?
	ssl_certificate_primary = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem')
	ssl_via = None
	if domain == env['PRIMARY_HOSTNAME']:
		# For PRIMARY_HOSTNAME, use the one we generated at set-up time.
		ssl_certificate = ssl_certificate_primary
	else:
		# For other domains, we'll probably use a certificate in a different path.
		ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/ssl_certificate.pem' % safe_domain_name(domain))

		# But we can be smart and reuse the main SSL certificate if is has
		# a Subject Alternative Name matching this domain. Don't do this if
		# the user has uploaded a different private key for this domain.
		if not ssl_key_is_alt and allow_shared_cert:
			from status_checks import check_certificate
			if check_certificate(domain, ssl_certificate_primary, None)[0] == "OK":
				ssl_certificate = ssl_certificate_primary
				ssl_via = "Using multi/wildcard certificate of %s." % env['PRIMARY_HOSTNAME']

			# For a 'www.' domain, see if we can reuse the cert of the parent.
			elif domain.startswith('www.'):
				ssl_certificate_parent = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/ssl_certificate.pem' % safe_domain_name(domain[4:]))
				if os.path.exists(ssl_certificate_parent) and check_certificate(domain, ssl_certificate_parent, None)[0] == "OK":
					ssl_certificate = ssl_certificate_parent
					ssl_via = "Using multi/wildcard certificate of %s." % domain[4:]

	return ssl_key, ssl_certificate, ssl_via
Beispiel #12
0
def get_domain_ssl_files(domain, env):
	# What SSL private key will we use? Allow the user to override this, but
	# in many cases using the same private key for all domains would be fine.
	# Don't allow the user to override the key for PRIMARY_HOSTNAME because
	# that's what's in the main file.
	ssl_key = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_private_key.pem')
	ssl_key_is_alt = False
	alt_key = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/private_key.pem' % safe_domain_name(domain))
	if domain != env['PRIMARY_HOSTNAME'] and os.path.exists(alt_key):
		ssl_key = alt_key
		ssl_key_is_alt = True

	# What SSL certificate will we use?
	ssl_certificate_primary = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem')
	if domain == env['PRIMARY_HOSTNAME']:
		# For PRIMARY_HOSTNAME, use the one we generated at set-up time.
		ssl_certificate = ssl_certificate_primary
	else:
		# For other domains, we'll probably use a certificate in a different path.
		ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/ssl_certificate.pem' % safe_domain_name(domain))

		# But we can be smart and reuse the main SSL certificate if is has
		# a Subject Alternative Name matching this domain. Don't do this if
		# the user has uploaded a different private key for this domain.
		if not ssl_key_is_alt:
			from status_checks import check_certificate
			if check_certificate(domain, ssl_certificate_primary, None)[0] == "OK":
				ssl_certificate = ssl_certificate_primary

	# Where would the CSR go? As with the SSL cert itself, the CSR must be
	# different for each domain name.
	if domain == env['PRIMARY_HOSTNAME']:
		csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_cert_sign_req.csr')
	else:
		csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/certificate_signing_request.csr' % safe_domain_name(domain))

	return ssl_key, ssl_certificate, csr_path
Beispiel #13
0
def make_domain_config(domain, template, template_for_primaryhost, env):
    # How will we configure this domain.

    # Where will its root directory be for static files?

    root = get_web_root(domain, env)

    # What private key and SSL certificate will we use for this domain?
    ssl_key, ssl_certificate, csr_path = get_domain_ssl_files(domain, env)

    # For hostnames created after the initial setup, ensure we have an SSL certificate
    # available. Make a self-signed one now if one doesn't exist.
    ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, csr_path,
                                  env)

    # Put pieces together.
    nginx_conf_parts = re.split("\s*# ADDITIONAL DIRECTIVES HERE\s*", template)
    nginx_conf = nginx_conf_parts[0] + "\n"
    if domain == env['PRIMARY_HOSTNAME']:
        nginx_conf += template_for_primaryhost + "\n"

    # Replace substitution strings in the template & return.
    nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
    nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
    nginx_conf = nginx_conf.replace("$ROOT", root)
    nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
    nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)

    # Add in any user customizations in YAML format.
    nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
    if os.path.exists(nginx_conf_custom_fn):
        yaml = rtyaml.load(open(nginx_conf_custom_fn))
        if domain in yaml:
            yaml = yaml[domain]
            for path, url in yaml.get("proxies", {}).items():
                nginx_conf += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (
                    path, url)

    # Add in any user customizations in the includes/ folder.
    nginx_conf_custom_include = os.path.join(
        env["STORAGE_ROOT"], "www",
        safe_domain_name(domain) + ".conf")
    if os.path.exists(nginx_conf_custom_include):
        nginx_conf += "\tinclude %s;\n" % (nginx_conf_custom_include)

    # Ending.
    nginx_conf += nginx_conf_parts[1]

    return nginx_conf
Beispiel #14
0
def install_cert_copy_file(fn, env):
	# 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)
Beispiel #15
0
def make_domain_config(domain, template, template_for_primaryhost, env):
	# How will we configure this domain.

	# Where will its root directory be for static files?

	root = get_web_root(domain, env)

	# What private key and SSL certificate will we use for this domain?
	ssl_key, ssl_certificate, csr_path = get_domain_ssl_files(domain, env)

	# For hostnames created after the initial setup, ensure we have an SSL certificate
	# available. Make a self-signed one now if one doesn't exist.
	ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, csr_path, env)

	# Put pieces together.
	nginx_conf_parts = re.split("\s*# ADDITIONAL DIRECTIVES HERE\s*", template)
	nginx_conf = nginx_conf_parts[0] + "\n"
	if domain == env['PRIMARY_HOSTNAME']:
		nginx_conf += template_for_primaryhost + "\n"

	# Replace substitution strings in the template & return.
	nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
	nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
	nginx_conf = nginx_conf.replace("$ROOT", root)
	nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
	nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)

	# Add in any user customizations in YAML format.
	nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
	if os.path.exists(nginx_conf_custom_fn):
		yaml = rtyaml.load(open(nginx_conf_custom_fn))
		if domain in yaml:
			yaml = yaml[domain]
			for path, url in yaml.get("proxies", {}).items():
				nginx_conf += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)

	# Add in any user customizations in the includes/ folder.
	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
	if os.path.exists(nginx_conf_custom_include):
		nginx_conf += "\tinclude %s;\n" % (nginx_conf_custom_include)

	# Ending.
	nginx_conf += nginx_conf_parts[1]

	return nginx_conf
Beispiel #16
0
def make_domain_config(domain, templates, ssl_certificates, env):
    # GET SOME VARIABLES

    # Where will its root directory be for static files?
    root = get_web_root(domain, env)

    # What private key and SSL certificate will we use for this domain?
    tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)

    # ADDITIONAL DIRECTIVES.

    nginx_conf_extra = ""

    # Because the certificate may change, we should recognize this so we
    # can trigger an nginx update.
    def hashfile(filepath):
        import hashlib
        sha1 = hashlib.sha1()
        f = open(filepath, 'rb')
        try:
            sha1.update(f.read())
        finally:
            f.close()
        return sha1.hexdigest()

    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.
    hsts = "yes"
    nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
    if os.path.exists(nginx_conf_custom_fn):
        yaml = rtyaml.load(open(nginx_conf_custom_fn))
        if domain in yaml:
            yaml = yaml[domain]

            # any proxy or redirect here?
            for path, url in yaml.get("proxies", {}).items():
                nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
            for path, url in yaml.get("redirects", {}).items():
                nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)

            # override the HSTS directive type
            hsts = yaml.get("hsts", hsts)

    # Add the HSTS header.
    if hsts == "yes":
        nginx_conf_extra += "add_header Strict-Transport-Security max-age=31536000;\n"
    elif hsts == "preload":
        nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=10886400; includeSubDomains; preload\";\n"

    # Add in any user customizations in the includes/ folder.
    nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
    if os.path.exists(nginx_conf_custom_include):
        nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
    # PUT IT ALL TOGETHER

    # Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
    # of the previous template.
    nginx_conf = "# ADDITIONAL DIRECTIVES HERE\n"
    for t in templates + [nginx_conf_extra]:
        nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)

    # Replace substitution strings in the template & return.
    nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
    nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
    nginx_conf = nginx_conf.replace("$ROOT", root)
    nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
    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

    return nginx_conf
Beispiel #17
0
def is_default_web_root(domain, env):
	root = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain))
	return not os.path.exists(root)
Beispiel #18
0
def make_domain_config(domain, templates, ssl_certificates, env):
	# GET SOME VARIABLES

	# Where will its root directory be for static files?
	root = get_web_root(domain, env)

	# 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)

	# ADDITIONAL DIRECTIVES.

	nginx_conf_extra = ""

	# Because the certificate may change, we should recognize this so we
	# can trigger an nginx update.
	def hashfile(filepath):
		import hashlib
		sha1 = hashlib.sha1()
		f = open(filepath, 'rb')
		try:
			sha1.update(f.read())
		finally:
			f.close()
		return sha1.hexdigest()
	nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))

	# Add in any user customizations in YAML format.
	hsts = "yes"
	nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
	if os.path.exists(nginx_conf_custom_fn):
		yaml = rtyaml.load(open(nginx_conf_custom_fn))
		if domain in yaml:
			yaml = yaml[domain]

			# any proxy or redirect here?
			for path, url in yaml.get("proxies", {}).items():
				nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
			for path, url in yaml.get("redirects", {}).items():
				nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)

			# override the HSTS directive type
			hsts = yaml.get("hsts", hsts)

	# Add the HSTS header.
	if hsts == "yes":
		nginx_conf_extra += "add_header Strict-Transport-Security max-age=31536000;\n"
	elif hsts == "preload":
		nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=10886400; includeSubDomains; preload\";\n"

	# Add in any user customizations in the includes/ folder.
	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
	if os.path.exists(nginx_conf_custom_include):
		nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
	# PUT IT ALL TOGETHER

	# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
	# of the previous template.
	nginx_conf = "# ADDITIONAL DIRECTIVES HERE\n"
	for t in templates + [nginx_conf_extra]:
		nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)

	# Replace substitution strings in the template & return.
	nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
	nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
	nginx_conf = nginx_conf.replace("$ROOT", root)
	nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
	nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
	nginx_conf = nginx_conf.replace("$REDIRECT_DOMAIN", re.sub(r"^www\.", "", domain)) # for default www redirects to parent domain

	return nginx_conf
Beispiel #19
0
def make_domain_config(domain, templates, env):
    # GET SOME VARIABLES

    # Where will its root directory be for static files?
    root = get_web_root(domain, env)

    # What private key and SSL certificate will we use for this domain?
    ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)

    # For hostnames created after the initial setup, ensure we have an SSL certificate
    # available. Make a self-signed one now if one doesn't exist.
    ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, env)

    # ADDITIONAL DIRECTIVES.

    nginx_conf_extra = ""

    # Because the certificate may change, we should recognize this so we
    # can trigger an nginx update.
    def hashfile(filepath):
        import hashlib
        sha1 = hashlib.sha1()
        f = open(filepath, 'rb')
        try:
            sha1.update(f.read())
        finally:
            f.close()
        return sha1.hexdigest()

    nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (
        hashfile(ssl_key), hashfile(ssl_certificate))

    # Add in any user customizations in YAML format.
    nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
    if os.path.exists(nginx_conf_custom_fn):
        yaml = rtyaml.load(open(nginx_conf_custom_fn))
        if domain in yaml:
            yaml = yaml[domain]
            for path, url in yaml.get("proxies", {}).items():
                nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (
                    path, url)
            for path, url in yaml.get("redirects", {}).items():
                nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path,
                                                                      url)

    # Add in any user customizations in the includes/ folder.
    nginx_conf_custom_include = os.path.join(
        env["STORAGE_ROOT"], "www",
        safe_domain_name(domain) + ".conf")
    if os.path.exists(nginx_conf_custom_include):
        nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
    # PUT IT ALL TOGETHER

    # Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
    # of the previous template.
    nginx_conf = "# ADDITIONAL DIRECTIVES HERE\n"
    for t in templates + [nginx_conf_extra]:
        nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t,
                            nginx_conf)

    # Replace substitution strings in the template & return.
    nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
    nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
    nginx_conf = nginx_conf.replace("$ROOT", root)
    nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
    nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
    nginx_conf = nginx_conf.replace(
        "$REDIRECT_DOMAIN",
        re.sub(r"^www\.", "",
               domain))  # for default www redirects to parent domain

    return nginx_conf
Beispiel #20
0
def make_domain_config(domain, templates, env):
	# GET SOME VARIABLES

	# Where will its root directory be for static files?
	root = get_web_root(domain, env)

	# What private key and SSL certificate will we use for this domain?
	ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)

	# For hostnames created after the initial setup, ensure we have an SSL certificate
	# available. Make a self-signed one now if one doesn't exist.
	ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, env)

	# ADDITIONAL DIRECTIVES.

	nginx_conf_extra = ""

	# Because the certificate may change, we should recognize this so we
	# can trigger an nginx update.
	def hashfile(filepath):
		import hashlib
		sha1 = hashlib.sha1()
		f = open(filepath, 'rb')
		try:
			sha1.update(f.read())
		finally:
			f.close()
		return sha1.hexdigest()
	nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))

	# Add in any user customizations in YAML format.
	nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
	if os.path.exists(nginx_conf_custom_fn):
		yaml = rtyaml.load(open(nginx_conf_custom_fn))
		if domain in yaml:
			yaml = yaml[domain]
			for path, url in yaml.get("proxies", {}).items():
				nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
			for path, url in yaml.get("redirects", {}).items():
				nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)

	# Add in any user customizations in the includes/ folder.
	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
	if os.path.exists(nginx_conf_custom_include):
		nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
	# PUT IT ALL TOGETHER

	# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
	# of the previous template.
	nginx_conf = "# ADDITIONAL DIRECTIVES HERE\n"
	for t in templates + [nginx_conf_extra]:
		nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)

	# Replace substitution strings in the template & return.
	nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
	nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
	nginx_conf = nginx_conf.replace("$ROOT", root)
	nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
	nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
	nginx_conf = nginx_conf.replace("$REDIRECT_DOMAIN", re.sub(r"^www\.", "", domain)) # for default www redirects to parent domain

	return nginx_conf
Beispiel #21
0
def make_domain_config(domain, templates, ssl_certificates, env):
	# GET SOME VARIABLES

	# Where will its root directory be for static files?
	root = get_web_root(domain, env)

	# What private key and SSL certificate will we use for this domain?
	tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)

	# ADDITIONAL DIRECTIVES.

	nginx_conf_extra = ""

	# Because the certificate may change, we should recognize this so we
	# can trigger an nginx update.
	def hashfile(filepath):
		import hashlib
		sha1 = hashlib.sha1()
		f = open(filepath, 'rb')
		try:
			sha1.update(f.read())
		finally:
			f.close()
		return sha1.hexdigest()
	nginx_conf_extra += "\t# ssl files sha1: %s / %s\n" % (hashfile(tls_cert["private-key"]), hashfile(tls_cert["certificate"]))

	# Add in any user customizations in YAML format.
	hsts = "yes"
	nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
	if os.path.exists(nginx_conf_custom_fn):
		yaml = rtyaml.load(open(nginx_conf_custom_fn))
		if domain in yaml:
			yaml = yaml[domain]

			# any proxy or redirect here?
			for path, url in yaml.get("proxies", {}).items():
				# Parse some flags in the fragment of the URL.
				pass_http_host_header = False
				proxy_redirect_off = False
				frame_options_header_sameorigin = False
				m = re.search("#(.*)$", url)
				if m:
					for flag in m.group(1).split(","):
						if flag == "pass-http-host":
							pass_http_host_header = True
						elif flag == "no-proxy-redirect":
							proxy_redirect_off = True
						elif flag == "frame-options-sameorigin":
							frame_options_header_sameorigin = True
					url = re.sub("#(.*)$", "", url)

				nginx_conf_extra += "\tlocation %s {" % path
				nginx_conf_extra += "\n\t\tproxy_pass %s;" % url
				if proxy_redirect_off:
					nginx_conf_extra += "\n\t\tproxy_redirect off;"
				if pass_http_host_header:
					nginx_conf_extra += "\n\t\tproxy_set_header Host $http_host;"
				if frame_options_header_sameorigin:
					nginx_conf_extra += "\n\t\tproxy_set_header X-Frame-Options SAMEORIGIN;"
				nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
				nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-Host $http_host;"
				nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-Proto $scheme;"
				nginx_conf_extra += "\n\t\tproxy_set_header X-Real-IP $remote_addr;"
				nginx_conf_extra += "\n\t}\n"
			for path, alias in yaml.get("aliases", {}).items():
				nginx_conf_extra += "\tlocation %s {" % path
				nginx_conf_extra += "\n\t\talias %s;" % alias
				nginx_conf_extra += "\n\t}\n"
			for path, url in yaml.get("redirects", {}).items():
				nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)

			# override the HSTS directive type
			hsts = yaml.get("hsts", hsts)

	# Add the HSTS header.
	if hsts == "yes":
		nginx_conf_extra += "\tadd_header Strict-Transport-Security \"max-age=15768000\" always;\n"
	elif hsts == "preload":
		nginx_conf_extra += "\tadd_header Strict-Transport-Security \"max-age=15768000; includeSubDomains; preload\" always;\n"

	# Add in any user customizations in the includes/ folder.
	nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
	if not os.path.exists(nginx_conf_custom_include):
		with open(nginx_conf_custom_include, "a+") as f:
			f.writelines([
				f"# Custom configurations for {domain} go here\n",
				"# To use php: use the \"php-fpm\" alias\n\n",
				"index index.html index.htm;\n"
			])

	nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)

	# PUT IT ALL TOGETHER

	# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
	# of the previous template.
	nginx_conf = "# ADDITIONAL DIRECTIVES HERE\n"
	for t in templates + [nginx_conf_extra]:
		nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)

	# Replace substitution strings in the template & return.
	nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
	nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
	nginx_conf = nginx_conf.replace("$ROOT", root)
	nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
	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

	return nginx_conf
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)