Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
mailinabox
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
mailinabox
Commits
b6933a73
Commit
b6933a73
authored
Oct 10, 2015
by
Joshua Tauberer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
provision and install free SSL certificates from Let's Encrypt
parent
5033042b
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
408 additions
and
79 deletions
+408
-79
nginx.conf
conf/nginx.conf
+16
-5
daily_tasks.sh
management/daily_tasks.sh
+3
-0
ssl_certificates.py
management/ssl_certificates.py
+359
-29
status_checks.py
management/status_checks.py
+13
-29
web_update.py
management/web_update.py
+9
-14
management.sh
setup/management.sh
+5
-2
start.sh
setup/start.sh
+3
-0
No files found.
conf/nginx.conf
View file @
b6933a73
## $HOSTNAME
# Redirect all HTTP to HTTPS.
# Redirect all HTTP to HTTPS *except* the ACME challenges (Let's Encrypt SSL certificate
# domain validation challenges) path, which must be served over HTTP per the ACME spec
# (due to some Apache vulnerability).
server
{
listen
80
;
listen
[::]:80
;
...
...
@@ -12,10 +14,19 @@ server {
# error pages and in the "Server" HTTP-Header.
server_tokens
off
;
# Redirect using the 'return' directive and the built-in
# variable '$request_uri' to avoid any capturing, matching
# or evaluation of regular expressions.
return
301
https://
$HOSTNAME$request_uri
;
location
/
{
# Redirect using the 'return' directive and the built-in
# variable '$request_uri' to avoid any capturing, matching
# or evaluation of regular expressions.
return
301
https://
$HOSTNAME$request_uri
;
}
location
/.well-known/acme-challenge/
{
# This path must be served over HTTP for ACME domain validation.
# We map this to a special path where our SSL cert provisioning
# tool knows to store challenge response files.
alias
$STORAGE_ROOT
/ssl/lets_encrypt/acme_challenges/
;
}
}
# The secure HTTPS server.
...
...
management/daily_tasks.sh
View file @
b6933a73
...
...
@@ -4,5 +4,8 @@
# Take a backup.
management/backup.py | management/email_administrator.py
"Backup Status"
# Provision any new certificates for new domains or domains with expiring certificates.
management/ssl_certificates.py
--headless
| management/email_administrator.py
"Error Provisioning TLS Certificate"
# Run status checks and email the administrator if anything changed.
management/status_checks.py
--show-changes
| management/email_administrator.py
"Status Checks Change Notice"
management/ssl_certificates.py
100644 → 100755
View file @
b6933a73
This diff is collapsed.
Click to expand it.
management/status_checks.py
View file @
b6933a73
...
...
@@ -278,23 +278,24 @@ def run_domain_checks(rounded_time, env, output, pool):
# Get the list of domains that we don't serve web for because of a custom CNAME/A record.
domains_with_a_records
=
get_domains_with_a_records
(
env
)
ssl_certificates
=
get_ssl_certificates
(
env
)
# Serial version:
#for domain in sort_domains(domains_to_check, env):
# run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)
# Parallelize the checks across a worker pool.
args
=
((
domain
,
rounded_time
,
env
,
dns_domains
,
dns_zonefiles
,
mail_domains
,
web_domains
,
domains_with_a_records
,
ssl_certificates
)
args
=
((
domain
,
rounded_time
,
env
,
dns_domains
,
dns_zonefiles
,
mail_domains
,
web_domains
,
domains_with_a_records
)
for
domain
in
domains_to_check
)
ret
=
pool
.
starmap
(
run_domain_checks_on_domain
,
args
,
chunksize
=
1
)
ret
=
dict
(
ret
)
# (domain, output) => { domain: output }
for
domain
in
sort_domains
(
ret
,
env
):
ret
[
domain
]
.
playback
(
output
)
def
run_domain_checks_on_domain
(
domain
,
rounded_time
,
env
,
dns_domains
,
dns_zonefiles
,
mail_domains
,
web_domains
,
domains_with_a_records
,
ssl_certificates
):
def
run_domain_checks_on_domain
(
domain
,
rounded_time
,
env
,
dns_domains
,
dns_zonefiles
,
mail_domains
,
web_domains
,
domains_with_a_records
):
output
=
BufferedOutput
()
# we'd move this up, but this returns non-pickleable values
ssl_certificates
=
get_ssl_certificates
(
env
)
# The domain is IDNA-encoded in the database, but for display use Unicode.
try
:
domain_display
=
idna
.
decode
(
domain
.
encode
(
'ascii'
))
...
...
@@ -656,45 +657,28 @@ def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
if
query_dns
(
domain
,
"A"
,
None
)
not
in
(
env
[
'PUBLIC_IP'
],
None
):
return
# Where is the SSL stored?
x
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
,
allow_missing_cert
=
True
)
if
x
is
None
:
tls_cert
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
,
allow_missing_cert
=
True
)
if
tls_cert
is
None
:
output
.
print_warning
(
"""No SSL certificate is installed for this domain. Visitors to a website on
this domain will get a security warning. If you are not serving a website on this domain, you do
not need to take any action. Use the SSL Certificates page in the control panel to install a
SSL certificate."""
)
return
ssl_key
,
ssl_certificate
,
ssl_via
=
x
# Check that the certificate is good.
cert_status
,
cert_status_details
=
check_certificate
(
domain
,
ssl_certificate
,
ssl_key
,
rounded_time
=
rounded_time
)
cert_status
,
cert_status_details
=
check_certificate
(
domain
,
tls_cert
[
"certificate"
],
tls_cert
[
"private-key"
]
,
rounded_time
=
rounded_time
)
if
cert_status
==
"OK"
:
# The certificate is ok. The details has expiry info.
output
.
print_ok
(
"SSL certificate is signed & valid.
%
s
%
s"
%
(
ssl_via
if
ssl_via
else
""
,
cert_status_details
)
)
output
.
print_ok
(
"SSL certificate is signed & valid.
"
+
cert_status_details
)
elif
cert_status
==
"SELF-SIGNED"
:
# Offer instructions for purchasing a signed certificate.
fingerprint
=
shell
(
'check_output'
,
[
"openssl"
,
"x509"
,
"-in"
,
ssl_certificate
,
"-noout"
,
"-fingerprint"
])
fingerprint
=
re
.
sub
(
".*Fingerprint="
,
""
,
fingerprint
)
.
strip
()
if
domain
==
env
[
'PRIMARY_HOSTNAME'
]:
output
.
print_error
(
"""The SSL certificate for this domain is currently self-signed. You will get a security
warning when you check or send email and when visiting this domain in a web browser (for webmail or
static site hosting). Use the SSL Certificates page in the control panel to install a signed SSL certificate.
You may choose to leave the self-signed certificate in place and confirm the security exception, but check that
the certificate fingerprint matches the following:"""
)
output
.
print_line
(
""
)
output
.
print_line
(
" "
+
fingerprint
,
monospace
=
True
)
static site hosting)."""
)
else
:
output
.
print_error
(
"""The SSL certificate for this domain is self-signed."""
)
...
...
@@ -927,10 +911,10 @@ if __name__ == "__main__":
if
query_dns
(
domain
,
"A"
)
!=
env
[
'PUBLIC_IP'
]:
sys
.
exit
(
1
)
ssl_certificates
=
get_ssl_certificates
(
env
)
ssl_key
,
ssl_certificate
,
ssl_via
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
)
if
not
os
.
path
.
exists
(
ssl_certificate
):
tls_cert
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
)
if
not
os
.
path
.
exists
(
tls_cert
[
"certificate"
]
):
sys
.
exit
(
1
)
cert_status
,
cert_status_details
=
check_certificate
(
domain
,
ssl_certificate
,
ssl_key
,
warn_if_expiring_soon
=
False
)
cert_status
,
cert_status_details
=
check_certificate
(
domain
,
tls_cert
[
"certificate"
],
tls_cert
[
"private-key"
]
,
warn_if_expiring_soon
=
False
)
if
cert_status
!=
"OK"
:
sys
.
exit
(
1
)
sys
.
exit
(
0
)
...
...
management/web_update.py
View file @
b6933a73
...
...
@@ -119,7 +119,7 @@ def make_domain_config(domain, templates, ssl_certificates, env):
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
)
tls_cert
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
)
# ADDITIONAL DIRECTIVES.
...
...
@@ -136,7 +136,7 @@ def make_domain_config(domain, templates, ssl_certificates, env):
finally
:
f
.
close
()
return
sha1
.
hexdigest
()
nginx_conf_extra
+=
"# ssl files sha1:
%
s /
%
s
\n
"
%
(
hashfile
(
ssl_key
),
hashfile
(
ssl_certificate
))
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"
...
...
@@ -177,8 +177,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
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
(
"$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
...
...
@@ -193,20 +193,15 @@ def get_web_root(domain, env, test_exists=True):
def
get_web_domains_info
(
env
):
www_redirects
=
set
(
get_web_domains
(
env
))
-
set
(
get_web_domains
(
env
,
include_www_redirects
=
False
))
has_root_proxy_or_redirect
=
set
(
get_web_domains_with_root_overrides
(
env
))
ssl_certificates
=
get_ssl_certificates
(
env
)
# for the SSL config panel, get cert status
def
check_cert
(
domain
):
ssl_certificates
=
get_ssl_certificates
(
env
)
x
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
,
allow_missing_cert
=
True
)
if
x
is
None
:
return
(
"danger"
,
"No Certificate Installed"
)
ssl_key
,
ssl_certificate
,
ssl_via
=
x
cert_status
,
cert_status_details
=
check_certificate
(
domain
,
ssl_certificate
,
ssl_key
)
tls_cert
=
get_domain_ssl_files
(
domain
,
ssl_certificates
,
env
,
allow_missing_cert
=
True
)
if
tls_cert
is
None
:
return
(
"danger"
,
"No Certificate Installed"
)
cert_status
,
cert_status_details
=
check_certificate
(
domain
,
tls_cert
[
"certificate"
],
tls_cert
[
"private-key"
])
if
cert_status
==
"OK"
:
if
not
ssl_via
:
return
(
"success"
,
"Signed & valid. "
+
cert_status_details
)
else
:
# This is an alternate domain but using the same cert as the primary domain.
return
(
"success"
,
"Signed & valid. "
+
ssl_via
)
return
(
"success"
,
"Signed & valid. "
+
cert_status_details
)
elif
cert_status
==
"SELF-SIGNED"
:
return
(
"warning"
,
"Self-signed. Get a signed certificate to stop warnings."
)
else
:
...
...
setup/management.sh
View file @
b6933a73
...
...
@@ -11,8 +11,11 @@ if [ -f /usr/local/lib/python2.7/dist-packages/boto/__init__.py ]; then hide_out
# build-essential libssl-dev libffi-dev python3-dev: Required to pip install cryptography.
apt_install python3-flask links duplicity python-boto libyaml-dev python3-dnspython python3-dateutil
\
build-essential libssl-dev libffi-dev python3-dev python-pip
hide_output pip3
install
--upgrade
rtyaml
"email_validator>=1.0.0"
"idna>=2.0.0"
"cryptography>=1.0.2"
boto psutil
# Install other Python packages. The first line is the packages that Josh maintains himself!
hide_output pip3
install
--upgrade
\
rtyaml
"email_validator>=1.0.0"
free_tls_certificates
\
"idna>=2.0.0"
"cryptography>=1.0.2"
boto psutil
# email_validator is repeated in setup/questions.sh
# Create a backup directory and a random key for encrypting backups.
...
...
@@ -44,5 +47,5 @@ cat > /etc/cron.d/mailinabox-nightly << EOF;
0 3 * * * root (cd `pwd` && management/daily_tasks.sh)
EOF
# Start
it
.
# Start
the management server
.
restart_service mailinabox
setup/start.sh
View file @
b6933a73
...
...
@@ -116,6 +116,9 @@ done
tools/dns_update
tools/web_update
# If DNS is already working, try to provision TLS certficates from Let's Encrypt.
management/ssl_certificates.py
# If there aren't any mail users yet, create one.
source
setup/firstuser.sh
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment