mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-11-22 02:17:26 +00:00
provision tls certificates from the control panel
This commit is contained in:
parent
4b4f670adf
commit
bac15d3919
@ -327,6 +327,19 @@ def dns_get_dump():
|
|||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
|
|
||||||
|
@app.route('/ssl/status')
|
||||||
|
@authorized_personnel_only
|
||||||
|
def ssl_get_status():
|
||||||
|
from ssl_certificates import get_certificates_to_provision
|
||||||
|
from web_update import get_web_domains_info
|
||||||
|
provision, cant_provision = get_certificates_to_provision(env, ok_as_problem=False)
|
||||||
|
domains_status = get_web_domains_info(env)
|
||||||
|
return json_response({
|
||||||
|
"can_provision": list(provision),
|
||||||
|
"cant_provision": [{ "domain": domain, "problem": cant_provision[domain] } for domain in utils.sort_domains(cant_provision, env) ],
|
||||||
|
"status": [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ],
|
||||||
|
})
|
||||||
|
|
||||||
@app.route('/ssl/csr/<domain>', methods=['POST'])
|
@app.route('/ssl/csr/<domain>', methods=['POST'])
|
||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def ssl_get_csr(domain):
|
def ssl_get_csr(domain):
|
||||||
|
@ -156,7 +156,7 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
|
|||||||
|
|
||||||
# PROVISIONING CERTIFICATES FROM LETSENCRYPT
|
# PROVISIONING CERTIFICATES FROM LETSENCRYPT
|
||||||
|
|
||||||
def get_certificates_to_provision(env):
|
def get_certificates_to_provision(env, ok_as_problem=True):
|
||||||
# Get a set of domain names that we should now provision certificates
|
# Get a set of domain names that we should now provision certificates
|
||||||
# for. Provision if a domain name has no valid certificate or if any
|
# for. Provision if a domain name has no valid certificate or if any
|
||||||
# certificate is expiring in 14 days. If provisioning anything, also
|
# certificate is expiring in 14 days. If provisioning anything, also
|
||||||
@ -196,8 +196,13 @@ def get_certificates_to_provision(env):
|
|||||||
elif cert.not_valid_after-now < datetime.timedelta(days=30):
|
elif cert.not_valid_after-now < datetime.timedelta(days=30):
|
||||||
domains_if_any.add(domain)
|
domains_if_any.add(domain)
|
||||||
|
|
||||||
# It's valid.
|
# It's valid. Should we report its validness?
|
||||||
problems[domain] = "The certificate is valid for at least another 30 days --- no need to replace."
|
if ok_as_problem:
|
||||||
|
problems[domain] = "The certificate is valid for at least another 30 days --- no need to replace."
|
||||||
|
|
||||||
|
# Warn the user about domains hosted elsewhere.
|
||||||
|
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
|
||||||
|
problems[domain] = "The domain's DNS is pointed elsewhere, so a TLS certificate is not necessary here and cannot be provisioned automatically anyway."
|
||||||
|
|
||||||
# Filter out domains that we can't provision a certificate for.
|
# Filter out domains that we can't provision a certificate for.
|
||||||
def can_provision_for_domain(domain):
|
def can_provision_for_domain(domain):
|
||||||
|
@ -3,10 +3,30 @@
|
|||||||
|
|
||||||
<h2>TLS (SSL) Certificates</h2>
|
<h2>TLS (SSL) Certificates</h2>
|
||||||
|
|
||||||
|
<p>An SSL certificate is a cryptographic file that proves to anyone connecting to this machine (like you right now) that the connection is secure.</p>
|
||||||
|
|
||||||
|
<p>You need an SSL certificate for this box’s hostname ({{hostname}}), and although optional you should also get one for every domain name and subdomain managed by this box (unless you’ve directed DNS for a domain elsewhere through custom or external DNS).</p>
|
||||||
|
|
||||||
|
<h3>Provision a Certificate</h3>
|
||||||
|
|
||||||
|
<p>We can provision an SSL certificate for you from <a href="https://letsencrypt.org/" target="_blank">Let’s Encrypt</a>, a free SSL certificate provider.</p>
|
||||||
|
|
||||||
|
<p id="ssl_provision_status"></p>
|
||||||
|
|
||||||
|
<table id="ssl_provision_problems" style="display: none" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Problem</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<h3>Certificate Status</h3>
|
<h3>Certificate Status</h3>
|
||||||
|
|
||||||
|
<table id="ssl_domains" class="table" style="margin-bottom: 2em; width: auto; display: none">
|
||||||
<table id="ssl_domains" class="table" style="margin-bottom: 2em; width: auto;">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Domain</th>
|
<th>Domain</th>
|
||||||
@ -60,24 +80,47 @@
|
|||||||
<script>
|
<script>
|
||||||
function show_tls() {
|
function show_tls() {
|
||||||
api(
|
api(
|
||||||
"/web/domains",
|
"/ssl/status",
|
||||||
"GET",
|
"GET",
|
||||||
{
|
{
|
||||||
},
|
},
|
||||||
function(domains) {
|
function(res) {
|
||||||
|
// provisioning status
|
||||||
|
if (res.can_provision.length > 0) {
|
||||||
|
$('#ssl_provision_status').removeClass("text-warning").removeClass("text-success")
|
||||||
|
.text("Domains: " + res.can_provision.join(", "));
|
||||||
|
} else if (res.cant_provision.length == 0) {
|
||||||
|
$('#ssl_provision_status').addClass("text-success").text("No domains hosted on this box need a new TLS certificate at this time.");
|
||||||
|
} else {
|
||||||
|
$('#ssl_provision_status').addClass("text-warning").text("No TLS certificates can be provisoned at this time:");
|
||||||
|
}
|
||||||
|
$('#ssl_provision_problems').toggle(res.cant_provision.length > 0);
|
||||||
|
$('#ssl_provision_problems tbody').text("");
|
||||||
|
for (var i = 0; i < res.cant_provision.length; i++) {
|
||||||
|
var domain = res.cant_provision[i];
|
||||||
|
var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td></tr>");
|
||||||
|
$('#ssl_provision_problems tbody').append(row);
|
||||||
|
row.attr('data-domain', domain.domain);
|
||||||
|
row.find('.domain a').text(domain.domain);
|
||||||
|
row.find('.domain a').attr('href', 'https://' + domain.domain);
|
||||||
|
row.find('.status').text(domain.problem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// certificate status
|
||||||
|
var domains = res.status;
|
||||||
var tb = $('#ssl_domains tbody');
|
var tb = $('#ssl_domains tbody');
|
||||||
tb.text('');
|
tb.text('');
|
||||||
$('#ssldomain').html('<option value="">(select)</option>');
|
$('#ssldomain').html('<option value="">(select)</option>');
|
||||||
|
$('#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);
|
||||||
row.find('.domain a').attr('href', 'https://' + domains[i].domain);
|
row.find('.domain a').attr('href', 'https://' + domains[i].domain);
|
||||||
row.addClass("text-" + domains[i].ssl_certificate[0]);
|
row.addClass("text-" + domains[i].status);
|
||||||
row.find('.status').text(domains[i].ssl_certificate[1]);
|
row.find('.status').text(domains[i].text);
|
||||||
if (domains[i].ssl_certificate[0] == "success") {
|
if (domains[i].status == "success") {
|
||||||
row.find('.actions a').addClass('btn-default').text('Replace Certificate');
|
row.find('.actions a').addClass('btn-default').text('Replace Certificate');
|
||||||
} else {
|
} else {
|
||||||
row.find('.actions a').addClass('btn-primary').text('Install Certificate');
|
row.find('.actions a').addClass('btn-primary').text('Install Certificate');
|
||||||
|
@ -9,7 +9,7 @@ from dns_update import get_custom_dns_config, get_dns_zones
|
|||||||
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
||||||
from utils import shell, safe_domain_name, sort_domains
|
from utils import shell, safe_domain_name, sort_domains
|
||||||
|
|
||||||
def get_web_domains(env, include_www_redirects=True):
|
def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True):
|
||||||
# What domains should we serve HTTP(S) for?
|
# What domains should we serve HTTP(S) for?
|
||||||
domains = set()
|
domains = set()
|
||||||
|
|
||||||
@ -24,9 +24,10 @@ def get_web_domains(env, include_www_redirects=True):
|
|||||||
# the topmost of each domain we serve.
|
# the topmost of each domain we serve.
|
||||||
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
|
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
|
||||||
|
|
||||||
# ...Unless the domain has an A/AAAA record that maps it to a different
|
if exclude_dns_elsewhere:
|
||||||
# IP address than this box. Remove those domains from our list.
|
# ...Unless the domain has an A/AAAA record that maps it to a different
|
||||||
domains -= get_domains_with_a_records(env)
|
# IP address than this box. Remove those domains from our list.
|
||||||
|
domains -= get_domains_with_a_records(env)
|
||||||
|
|
||||||
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
|
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
|
||||||
# as well as Z-Push for Exchange ActiveSync. This can't be removed
|
# as well as Z-Push for Exchange ActiveSync. This can't be removed
|
||||||
|
Loading…
Reference in New Issue
Block a user