Merge pull request #60 from modoboa/feature/letsencrypt

Feature/letsencrypt
This commit is contained in:
Antoine Nguyen
2016-10-21 17:52:01 +02:00
committed by GitHub
4 changed files with 97 additions and 22 deletions

View File

@@ -39,3 +39,26 @@ By default, the following components are installed:
If you want more information about the installation process, add the If you want more information about the installation process, add the
``--debug`` option to your command line. ``--debug`` option to your command line.
Let's Encrypt certificate
-------------------------
.. warning::
Please note this option requires the hostname you're using to be
valid (ie. it can be resolved with a DNS query) and to match the
server you're installing Modoboa on.
If you want to generate a valid certificate using `Let's Encrypt
<https://letsencrypt.org/>`_, edit the ``installer.cfg`` file and
modify the following settings::
[certificate]
generate = true
type = letsencrypt
[letsencrypt]
email = admin@example.com
Change the ``email`` setting to a valid value since it will be used
for account recovery.

View File

@@ -1,3 +1,11 @@
[certificate]
generate = true
# Choose between self-signed or letsencrypt
type = self-signed
[letsencrypt]
email = admin@example.com
[database] [database]
# Select database engine : postgres or mysql # Select database engine : postgres or mysql
engine = postgres engine = postgres

View File

@@ -11,32 +11,42 @@ class CertificateBackend(object):
def __init__(self, config): def __init__(self, config):
"""Set path to certificates.""" """Set path to certificates."""
self.config = config self.config = config
if not config.has_option("general", "tls_key_file"):
for base_dir in ["/etc/pki/tls", "/etc/ssl"]:
if os.path.exists(base_dir):
self.config.set(
"general", "tls_key_file",
"{}/private/%(hostname)s.key".format(base_dir))
self.config.set(
"general", "tls_cert_file",
"{}/certs/%(hostname)s.cert".format(base_dir))
return
raise RuntimeError("Cannot find a directory to store certificate")
else:
return
def overwrite_existing_certificate(self):
class SelfSignedCertificate(CertificateBackend): """Check if certificate already exists."""
"""Create a self signed certificate."""
def create(self):
"""Create a certificate."""
if os.path.exists(self.config.get("general", "tls_key_file")): if os.path.exists(self.config.get("general", "tls_key_file")):
if not self.config.getboolean("general", "force"): if not self.config.getboolean("general", "force"):
answer = utils.user_input( answer = utils.user_input(
"Overwrite the existing SSL certificate? (y/N) ") "Overwrite the existing SSL certificate? (y/N) ")
if not answer.lower().startswith("y"): if not answer.lower().startswith("y"):
return return False
return True
class SelfSignedCertificate(CertificateBackend):
"""Create a self signed certificate."""
def __init__(self, *args, **kwargs):
"""Sanity checks."""
super(SelfSignedCertificate, self).__init__(*args, **kwargs)
if self.config.has_option("general", "tls_key_file"):
# Compatibility
return
for base_dir in ["/etc/pki/tls", "/etc/ssl"]:
if os.path.exists(base_dir):
self.config.set(
"general", "tls_key_file",
"{}/private/%(hostname)s.key".format(base_dir))
self.config.set(
"general", "tls_cert_file",
"{}/certs/%(hostname)s.cert".format(base_dir))
return
raise RuntimeError("Cannot find a directory to store certificate")
def create(self):
"""Create a certificate."""
if not self.overwrite_existing_certificate():
return
utils.printcolor( utils.printcolor(
"Generating new self-signed certificate", utils.YELLOW) "Generating new self-signed certificate", utils.YELLOW)
utils.exec_cmd( utils.exec_cmd(
@@ -48,6 +58,37 @@ class SelfSignedCertificate(CertificateBackend):
) )
class LetsEncryptCertificate(CertificateBackend):
"""Create a certificate using letsencrypt."""
def create(self):
"""Create a certificate."""
utils.printcolor(
"Generating new certificate using letsencrypt", utils.YELLOW)
hostname = self.config.get("general", "hostname")
utils.exec_cmd(
"wget https://dl.eff.org/certbot-auto; chmod a+x certbot-auto",
cwd="/opt")
utils.exec_cmd(
"/opt/certbot-auto certonly -n --standalone -d {} "
"-m {} --agree-tos".format(
hostname, self.config.get("letsencrypt", "email")))
self.config.set("general", "tls_cert_file", (
"/etc/letsencrypt/live/{}/fullchain.pem".format(hostname)))
self.config.set("general", "tls_key_file", (
"/etc/letsencrypt/live/{}/privkey.pem".format(hostname)))
with open("/etc/cron.d/letsencrypt", "w") as fp:
fp.write("0 */12 * * * root /opt/certbot-auto renew "
"--quiet --no-self-upgrade && "
"service nginx reload && "
"service postfix reload && "
"service dovecot reload")
def get_backend(config): def get_backend(config):
"""Return the appropriate backend.""" """Return the appropriate backend."""
if not config.getboolean("certificate", "generate"):
return None
if config.get("certificate", "type") == "letsencrypt":
return LetsEncryptCertificate(config)
return SelfSignedCertificate(config) return SelfSignedCertificate(config)

7
run.py
View File

@@ -39,7 +39,8 @@ def main():
.format(args.hostname), utils.BLUE) .format(args.hostname), utils.BLUE)
components = [] components = []
for section in config.sections(): for section in config.sections():
if section in ["general", "database", "mysql", "postgres"]: if section in ["general", "database", "mysql", "postgres",
"certificate", "letsencrypt"]:
continue continue
if (config.has_option(section, "enabled") and if (config.has_option(section, "enabled") and
not config.getboolean(section, "enabled")): not config.getboolean(section, "enabled")):
@@ -56,7 +57,9 @@ def main():
"and come back later ;)", utils.BLUE) "and come back later ;)", utils.BLUE)
utils.printcolor("Starting...", utils.GREEN) utils.printcolor("Starting...", utils.GREEN)
package.backend.install("sudo") package.backend.install("sudo")
ssl.get_backend(config).create() ssl_backend = ssl.get_backend(config)
if ssl_backend:
ssl_backend.create()
scripts.install("modoboa", config) scripts.install("modoboa", config)
scripts.install("postfix", config) scripts.install("postfix", config)
scripts.install("amavis", config) scripts.install("amavis", config)