mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-11-22 02:17:26 +00:00
when serving a 'www.' domain, check if the parent domain's ssl certificate can be used besides checking PRIMARY_HOSTNAME
Removing buy_certificate.py which is not working and I don't want to update its call signatures.
This commit is contained in:
parent
3c10ec70a5
commit
3c50c9a18b
@ -23,6 +23,7 @@ Web:
|
|||||||
* Static websites now deny access to certain dot (.) files and directories which typically have sensitive info: .ht*, .svn*, .git*, .hg*, .bzr*.
|
* Static websites now deny access to certain dot (.) files and directories which typically have sensitive info: .ht*, .svn*, .git*, .hg*, .bzr*.
|
||||||
* The nginx server no longer reports its version and OS for better privacy.
|
* The nginx server no longer reports its version and OS for better privacy.
|
||||||
* The HTTP->HTTPS redirect is now more efficient.
|
* The HTTP->HTTPS redirect is now more efficient.
|
||||||
|
* When serving a 'www.' domain, reuse the SSL certificate for the parent domain if it covers the 'www' subdomain too
|
||||||
|
|
||||||
Control panel:
|
Control panel:
|
||||||
|
|
||||||
|
@ -1,155 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
# Helps you purchase a SSL certificate from Gandi.net using
|
|
||||||
# their API.
|
|
||||||
#
|
|
||||||
# Before you begin:
|
|
||||||
# 1) Create an account on Gandi.net.
|
|
||||||
# 2) Pre-pay $16 into your account at https://www.gandi.net/prepaid/operations. Wait until the payment goes through.
|
|
||||||
# 3) Activate your API key first on the test platform (wait a while, refresh the page) and then activate the production API at https://www.gandi.net/admin/api_key.
|
|
||||||
|
|
||||||
import sys, re, os.path, urllib.request
|
|
||||||
import xmlrpc.client
|
|
||||||
import rtyaml
|
|
||||||
|
|
||||||
from utils import load_environment, shell
|
|
||||||
from web_update import get_web_domains, get_domain_ssl_files, get_web_root
|
|
||||||
from status_checks import check_certificate
|
|
||||||
|
|
||||||
def buy_ssl_certificate(api_key, domain, command, env):
|
|
||||||
if domain != env['PRIMARY_HOSTNAME'] \
|
|
||||||
and domain not in get_web_domains(env):
|
|
||||||
raise ValueError("Domain is not %s or a domain we're serving a website for." % env['PRIMARY_HOSTNAME'])
|
|
||||||
|
|
||||||
# Initialize.
|
|
||||||
|
|
||||||
gandi = xmlrpc.client.ServerProxy('https://rpc.gandi.net/xmlrpc/')
|
|
||||||
|
|
||||||
try:
|
|
||||||
existing_certs = gandi.cert.list(api_key)
|
|
||||||
except Exception as e:
|
|
||||||
if "Invalid API key" in str(e):
|
|
||||||
print("Invalid API key. Check that you copied the API Key correctly from https://www.gandi.net/admin/api_key.")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Where is the SSL cert stored?
|
|
||||||
|
|
||||||
ssl_key, ssl_certificate = get_domain_ssl_files(domain, env)
|
|
||||||
|
|
||||||
# Have we already created a cert for this domain?
|
|
||||||
|
|
||||||
for cert in existing_certs:
|
|
||||||
if cert['cn'] == domain:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# No existing cert found. Purchase one.
|
|
||||||
if command != 'purchase':
|
|
||||||
print("No certificate or order found yet. If you haven't yet purchased a certificate, run ths script again with the 'purchase' command. Otherwise wait a moment and try again.")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
# Start an order for a single standard SSL certificate.
|
|
||||||
# Use DNS validation. Web-based validation won't work because they
|
|
||||||
# require a file on HTTP but not HTTPS w/o redirects and we don't
|
|
||||||
# serve anything plainly over HTTP. Email might be another way but
|
|
||||||
# DNS is easier to automate.
|
|
||||||
op = gandi.cert.create(api_key, {
|
|
||||||
"csr": open(ssl_csr_path).read(),
|
|
||||||
"dcv_method": "dns",
|
|
||||||
"duration": 1, # year?
|
|
||||||
"package": "cert_std_1_0_0",
|
|
||||||
})
|
|
||||||
print("An SSL certificate has been ordered.")
|
|
||||||
print()
|
|
||||||
print(op)
|
|
||||||
print()
|
|
||||||
print("In a moment please run this script again with the 'setup' command.")
|
|
||||||
|
|
||||||
if cert['status'] == 'pending':
|
|
||||||
# Get the information we need to update our DNS with a code so that
|
|
||||||
# Gandi can verify that we own the domain.
|
|
||||||
|
|
||||||
dcv = gandi.cert.get_dcv_params(api_key, {
|
|
||||||
"csr": open(ssl_csr_path).read(),
|
|
||||||
"cert_id": cert['id'],
|
|
||||||
"dcv_method": "dns",
|
|
||||||
"duration": 1, # year?
|
|
||||||
"package": "cert_std_1_0_0",
|
|
||||||
})
|
|
||||||
if dcv["dcv_method"] != "dns":
|
|
||||||
raise Exception("Certificate ordered with an unknown validation method.")
|
|
||||||
|
|
||||||
# Update our DNS data.
|
|
||||||
|
|
||||||
dns_config = env['STORAGE_ROOT'] + '/dns/custom.yaml'
|
|
||||||
if os.path.exists(dns_config):
|
|
||||||
dns_records = rtyaml.load(open(dns_config))
|
|
||||||
else:
|
|
||||||
dns_records = { }
|
|
||||||
|
|
||||||
qname = dcv['md5'] + '.' + domain
|
|
||||||
value = dcv['sha1'] + '.comodoca.com.'
|
|
||||||
dns_records[qname] = { "CNAME": value }
|
|
||||||
|
|
||||||
with open(dns_config, 'w') as f:
|
|
||||||
f.write(rtyaml.dump(dns_records))
|
|
||||||
|
|
||||||
shell('check_call', ['tools/dns_update'])
|
|
||||||
|
|
||||||
# Okay, done with this step.
|
|
||||||
|
|
||||||
print("DNS has been updated. Gandi will check within 60 minutes.")
|
|
||||||
print()
|
|
||||||
print("See https://www.gandi.net/admin/ssl/%d/details for the status of this order." % cert['id'])
|
|
||||||
|
|
||||||
elif cert['status'] == 'valid':
|
|
||||||
# The certificate is ready.
|
|
||||||
|
|
||||||
# Check before we overwrite something we shouldn't.
|
|
||||||
if os.path.exists(ssl_certificate):
|
|
||||||
cert_status, cert_status_details = check_certificate(None, ssl_certificate, None)
|
|
||||||
if cert_status != "SELF-SIGNED":
|
|
||||||
print("Please back up and delete the file %s so I can save your new certificate." % ssl_certificate)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Form the certificate.
|
|
||||||
|
|
||||||
# The certificate comes as a long base64-encoded string. Break in
|
|
||||||
# into lines in the usual way.
|
|
||||||
pem = "-----BEGIN CERTIFICATE-----\n"
|
|
||||||
pem += "\n".join(chunk for chunk in re.split(r"(.{64})", cert['cert']) if chunk != "")
|
|
||||||
pem += "\n-----END CERTIFICATE-----\n\n"
|
|
||||||
|
|
||||||
# Append intermediary certificates.
|
|
||||||
pem += urllib.request.urlopen("https://www.gandi.net/static/CAs/GandiStandardSSLCA.pem").read().decode("ascii")
|
|
||||||
|
|
||||||
# Write out.
|
|
||||||
|
|
||||||
with open(ssl_certificate, "w") as f:
|
|
||||||
f.write(pem)
|
|
||||||
|
|
||||||
print("The certificate has been installed in %s. Restarting services..." % ssl_certificate)
|
|
||||||
|
|
||||||
# Restart dovecot and if this is for PRIMARY_HOSTNAME.
|
|
||||||
|
|
||||||
if domain == env['PRIMARY_HOSTNAME']:
|
|
||||||
shell('check_call', ["/usr/sbin/service", "dovecot", "restart"])
|
|
||||||
shell('check_call', ["/usr/sbin/service", "postfix", "restart"])
|
|
||||||
|
|
||||||
# Restart nginx in all cases.
|
|
||||||
|
|
||||||
shell('check_call', ["/usr/sbin/service", "nginx", "restart"])
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("The certificate has an unknown status. Please check https://www.gandi.net/admin/ssl/%d/details for the status of this order." % cert['id'])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) < 4:
|
|
||||||
print("Usage: python management/buy_certificate.py gandi_api_key domain_name {purchase, setup}")
|
|
||||||
sys.exit(1)
|
|
||||||
api_key = sys.argv[1]
|
|
||||||
domain_name = sys.argv[2]
|
|
||||||
cmd = sys.argv[3]
|
|
||||||
buy_ssl_certificate(api_key, domain_name, cmd, load_environment())
|
|
||||||
|
|
@ -272,7 +272,7 @@ def dns_get_dump():
|
|||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def ssl_get_csr(domain):
|
def ssl_get_csr(domain):
|
||||||
from web_update import get_domain_ssl_files, create_csr
|
from web_update import get_domain_ssl_files, create_csr
|
||||||
ssl_key, ssl_certificate = get_domain_ssl_files(domain, env)
|
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)
|
||||||
return create_csr(domain, ssl_key, env)
|
return create_csr(domain, ssl_key, env)
|
||||||
|
|
||||||
@app.route('/ssl/install', methods=['POST'])
|
@app.route('/ssl/install', methods=['POST'])
|
||||||
|
@ -523,7 +523,7 @@ def check_ssl_cert(domain, env, output):
|
|||||||
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 SSL stored?
|
||||||
ssl_key, ssl_certificate = get_domain_ssl_files(domain, env)
|
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)
|
||||||
|
|
||||||
if not os.path.exists(ssl_certificate):
|
if not os.path.exists(ssl_certificate):
|
||||||
output.print_error("The SSL certificate file for this domain is missing.")
|
output.print_error("The SSL certificate file for this domain is missing.")
|
||||||
@ -535,7 +535,7 @@ def check_ssl_cert(domain, env, output):
|
|||||||
|
|
||||||
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. " + cert_status_details)
|
output.print_ok("SSL certificate is signed & valid. %s %s" % (ssl_via if ssl_via else "", 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.
|
||||||
@ -788,7 +788,7 @@ if __name__ == "__main__":
|
|||||||
domain = env['PRIMARY_HOSTNAME']
|
domain = env['PRIMARY_HOSTNAME']
|
||||||
if query_dns(domain, "A") != env['PUBLIC_IP']:
|
if query_dns(domain, "A") != env['PUBLIC_IP']:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
ssl_key, ssl_certificate = get_domain_ssl_files(domain, env)
|
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)
|
||||||
if not os.path.exists(ssl_certificate):
|
if not os.path.exists(ssl_certificate):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
|
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
|
||||||
|
@ -74,7 +74,7 @@ def make_domain_config(domain, template, template_for_primaryhost, 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 = get_domain_ssl_files(domain, env)
|
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
|
# 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.
|
# available. Make a self-signed one now if one doesn't exist.
|
||||||
@ -148,6 +148,7 @@ def get_domain_ssl_files(domain, env, allow_shared_cert=True):
|
|||||||
|
|
||||||
# What SSL certificate will we use?
|
# What SSL certificate will we use?
|
||||||
ssl_certificate_primary = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem')
|
ssl_certificate_primary = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem')
|
||||||
|
ssl_via = None
|
||||||
if domain == env['PRIMARY_HOSTNAME']:
|
if domain == env['PRIMARY_HOSTNAME']:
|
||||||
# For PRIMARY_HOSTNAME, use the one we generated at set-up time.
|
# For PRIMARY_HOSTNAME, use the one we generated at set-up time.
|
||||||
ssl_certificate = ssl_certificate_primary
|
ssl_certificate = ssl_certificate_primary
|
||||||
@ -162,8 +163,16 @@ def get_domain_ssl_files(domain, env, allow_shared_cert=True):
|
|||||||
from status_checks import check_certificate
|
from status_checks import check_certificate
|
||||||
if check_certificate(domain, ssl_certificate_primary, None)[0] == "OK":
|
if check_certificate(domain, ssl_certificate_primary, None)[0] == "OK":
|
||||||
ssl_certificate = ssl_certificate_primary
|
ssl_certificate = ssl_certificate_primary
|
||||||
|
ssl_via = "Using multi/wildcard certificate of %s." % env['PRIMARY_HOSTNAME']
|
||||||
|
|
||||||
return ssl_key, ssl_certificate
|
# 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
|
||||||
|
|
||||||
def ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, env):
|
def ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, env):
|
||||||
# For domains besides PRIMARY_HOSTNAME, generate a self-signed certificate if
|
# For domains besides PRIMARY_HOSTNAME, generate a self-signed certificate if
|
||||||
@ -218,7 +227,7 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
|
|||||||
|
|
||||||
# Do validation on the certificate before installing it.
|
# Do validation on the certificate before installing it.
|
||||||
from status_checks import check_certificate
|
from status_checks import check_certificate
|
||||||
ssl_key, ssl_certificate = get_domain_ssl_files(domain, env, allow_shared_cert=False)
|
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env, allow_shared_cert=False)
|
||||||
cert_status, cert_status_details = check_certificate(domain, fn, ssl_key)
|
cert_status, cert_status_details = check_certificate(domain, fn, ssl_key)
|
||||||
if cert_status != "OK":
|
if cert_status != "OK":
|
||||||
if cert_status == "SELF-SIGNED":
|
if cert_status == "SELF-SIGNED":
|
||||||
@ -261,16 +270,16 @@ def get_web_domains_info(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):
|
||||||
from status_checks import check_certificate
|
from status_checks import check_certificate
|
||||||
ssl_key, ssl_certificate = get_domain_ssl_files(domain, env)
|
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)
|
||||||
if not os.path.exists(ssl_certificate):
|
if not os.path.exists(ssl_certificate):
|
||||||
return ("danger", "No Certificate Installed")
|
return ("danger", "No Certificate Installed")
|
||||||
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
|
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
|
||||||
if cert_status == "OK":
|
if cert_status == "OK":
|
||||||
if domain == env['PRIMARY_HOSTNAME'] or ssl_certificate != get_domain_ssl_files(env['PRIMARY_HOSTNAME'], env)[1]:
|
if not ssl_via:
|
||||||
return ("success", "Signed & valid. " + cert_status_details)
|
return ("success", "Signed & valid. " + cert_status_details)
|
||||||
else:
|
else:
|
||||||
# This is an alternate domain but using the same cert as the primary domain.
|
# This is an alternate domain but using the same cert as the primary domain.
|
||||||
return ("success", "Signed & valid. Using multi/wildcard certificate of %s." % env['PRIMARY_HOSTNAME'])
|
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:
|
||||||
|
Loading…
Reference in New Issue
Block a user