23
README.rst
23
README.rst
@@ -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.
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
[certificate]
|
||||||
|
generate = true
|
||||||
|
# Choose between self-signed or letsencrypt
|
||||||
|
type = letsencrypt
|
||||||
|
|
||||||
|
[letsencrypt]
|
||||||
|
email = admin@example.com
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
# Select database engine : postgres or mysql
|
# Select database engine : postgres or mysql
|
||||||
engine = postgres
|
engine = postgres
|
||||||
|
|||||||
@@ -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,40 @@ 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")
|
||||||
|
webroot = os.path.join(
|
||||||
|
self.config.get("modoboa", "instance_path"),
|
||||||
|
"sitestatic/.well-known")
|
||||||
|
utils.exec_cmd(
|
||||||
|
"/opt/certbot-auto certonly -n --standalone -d {} "
|
||||||
|
"-m {} --agree-tos".format(
|
||||||
|
webroot, 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
7
run.py
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user