From 268d312d1945e07fbb521400568149fe59d56938 Mon Sep 17 00:00:00 2001 From: "Md. Ishtiaq Ashiq" Date: Mon, 5 Oct 2020 21:26:39 +0600 Subject: [PATCH 01/13] Update CONTRIBUTING.md --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00e15ec7e..227727a49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,9 +4,10 @@ Mail-in-a-Box is an open source project. Your contributions and pull requests ar ## Development -To start developing Mail-in-a-Box, [clone the repository](https://github.com/mail-in-a-box/mailinabox) and familiarize yourself with the code. +To start developing Mail-in-a-Box, [clone the repository](https://github.com/mail-in-a-box/mailinabox) and familiarize yourself with the code. Then move to the cloned mailinabox directory. $ git clone https://github.com/mail-in-a-box/mailinabox + $ cd mailinabox ### Vagrant and VirtualBox From a01c096d74fd2872c3279d76b593e58f2cd3e738 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Thu, 29 Oct 2020 00:49:37 +0600 Subject: [PATCH 02/13] Added the configuration document and updated line separators from Windows to Unix --- CONTRIBUTING.md | 7 +++- Vagrantfile | 2 +- WhatIDidSoFar.md | 69 ++++++++++++++++++++++++++++++++++ management/auth.py | 4 +- management/daemon.py | 2 +- management/ssl_certificates.py | 4 +- setup/migrate.py | 4 +- setup/nextcloud.sh | 2 +- setup/start.sh | 1 + 9 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 WhatIDidSoFar.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00e15ec7e..4f5506bc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,9 +4,10 @@ Mail-in-a-Box is an open source project. Your contributions and pull requests ar ## Development -To start developing Mail-in-a-Box, [clone the repository](https://github.com/mail-in-a-box/mailinabox) and familiarize yourself with the code. +To start developing Mail-in-a-Box, [clone the repository](https://github.com/mail-in-a-box/mailinabox) and familiarize yourself with the code. Then enter into the directory you cloned mailinabox into. $ git clone https://github.com/mail-in-a-box/mailinabox + $ cd mailinabox ### Vagrant and VirtualBox @@ -16,7 +17,9 @@ With Vagrant set up, the following should boot up Mail-in-a-Box inside a virtual $ vagrant up --provision -_If you're seeing an error message about your *IP address being listed in the Spamhaus Block List*, simply uncomment the `export SKIP_NETWORK_CHECKS=1` line in `Vagrantfile`. It's normal, you're probably using a dynamic IP address assigned by your Internet provider–they're almost all listed._ +For possible errors encountered, please follow WhatIDidSoFar file. + + ### Modifying your `hosts` file diff --git a/Vagrantfile b/Vagrantfile index 467fb95ee..0e6f0b89c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -19,7 +19,7 @@ Vagrant.configure("2") do |config| export PUBLIC_IP=auto export PUBLIC_IPV6=auto export PRIMARY_HOSTNAME=auto - #export SKIP_NETWORK_CHECKS=1 + export SKIP_NETWORK_CHECKS=1 # Start the setup script. cd /vagrant diff --git a/WhatIDidSoFar.md b/WhatIDidSoFar.md new file mode 100644 index 000000000..3c5a4f1c9 --- /dev/null +++ b/WhatIDidSoFar.md @@ -0,0 +1,69 @@ +Vagrant commands that you'd need most: +1. _To view the list of vagrant boxes, use `vagrant box list`_ +2. _To initialize a vagrant VM, use `vagrant init boxname`_ +3. _To start a vagrant VM, use `vagrant up`_ +4. _To shut down the vagrant VM, use `vagrant halt ubuntu/bionic64`_ +5. _To remove a vagrant box, use `vagrant box remove `_ + + +UserName and Password + +1. _Generally vagrant created VM's username is `vagrant`, password is `vagrant`_ +2. _hostname/ IP address will be available in +`config.vm.network "private_network", ip: `. _ + + +Errors encountered while setting up MIAB +1. _If you're seeing an error message about your *IP address being listed in the Spamhaus Block List*, +simply uncomment the `export SKIP_NETWORK_CHECKS=1` line in `Vagrantfile`. +It's normal, you're probably using a dynamic IP address assigned by your Internet provider–they're almost all listed._ +2. _If you're seeing an error message such as this `Bash script and /bin/bash^M: bad interpreter: No such file or directory`, + then most likely you're on windows host and your vm is ubuntu. + Then you've to change the format of all .py and .sh files in all the mailinabox directories to Unix (LF)._ +3. _If you're encountering migration error, please add this line *return* in line 216 at setup/migrate.py. +Then after the up --provision command is successful, you gotta uncomment this or remove this line. (Not sure yet)_ +4. _If your vagrant up command is stuck at upgrading to nextcloud, it is because the nextcloud server is either down +or very slow. Check the /tmp folder whether the nextcloud.zip is being downloaded. +If not, download it yourself and paste it in the /tmp folder._ +5. _As your vagrant VM is CLI, to see the contents of 192.168.50.4, do the following._ + + +To make sure that you can view the curl contents in your host machine's browser by executing commands from guest VM CLI, these +are the steps that you gotta follow: +1. _Copy the private key that vagrant generated for you and paste it in .ssh directory (for windows: by default this is the path `C:\\Users\HP\.ssh folder`) with a name_ +2. _Now if you try to login using the following SSH command, + `ssh -i username@hostname or username@ipaddress` +3. _You should be logged in to the vagrant VM_ +4. _CD into the directory /etc/ssh_ +5. _Edit the sshd_config file with sudo permission and uncomment these 3 lines:_ + + `X11Forwarding yes` + + `X11DisplayOffset 10` + + `X11UseLocalhost yes` +6. _Now restart the sshd service by the following command:_ + `sudo systemctl restart sshd` +7. _logout from your account_ +8. _If you're in ubuntu host, then do the following:_ + `ssh -X -i username@hostname or username@ipaddress` + _you should be logged into the host as username. type `echo $DISPLAY` and see whether `localhost=10.0.0` comes up or not. + If it does, then X11Forwarding is enabled. Now type firefox in your terminal + and you should see the output in firefox browser in your ubuntu host machine +9. _If you're in windows host, install XMing and Putty_ + + a) _Open Puttygen app and from conversions -> import key, load the key you saved in line 6_ + + b) _Save the key by pressing save private key button in the same folder_ + + c) _In Putty, go to Connections->SSH->Auth and load the private key by clicking load key button_ + + d) _go to Connections->SSH->X11 and tick on X11forwarding_ + + e) _Now, write the IP address/ hostname in sessions, save it with a session name and click on open._ + + f) _Type vagrant as username and you should be logged in with X11 forwarding option enabled_ + + g) _To check this option, type $ echo $DISPLAY and see whether localhost=10.0.0 comes up or not. If it does, then you're good to go._ + + h) _Now type firefox in your putty terminal and you should see the output in firefox browser in your windows host machine_ diff --git a/management/auth.py b/management/auth.py index 55f59664e..df976f246 100644 --- a/management/auth.py +++ b/management/auth.py @@ -5,7 +5,7 @@ import utils from mailconfig import get_mail_password, get_mail_user_privileges -DEFAULT_KEY_PATH = '/var/lib/mailinabox/api.key' +DEFAULT_KEY_PATH = '/var/lib/mailinabox/api.key' DEFAULT_AUTH_REALM = 'Mail-in-a-Box Management Server' class KeyAuthService: @@ -82,7 +82,7 @@ def parse_basic_auth(header): def get_user_credentials(self, email, pw, env): # Validate a user's credentials. On success returns a list of # privileges (e.g. [] or ['admin']). On failure raises a ValueError - # with a login error message. + # with a login error message. # Sanity check. if email == "" or pw == "": diff --git a/management/daemon.py b/management/daemon.py index b7bf2a661..956f6cf54 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -334,7 +334,7 @@ def ssl_get_status(): # What domains can we provision certificates for? What unexpected problems do we have? provision, cant_provision = get_certificates_to_provision(env, show_valid_certs=False) - + # What's the current status of TLS certificates on all of the domain? domains_status = get_web_domains_info(env) domains_status = [ diff --git a/management/ssl_certificates.py b/management/ssl_certificates.py index 1b1e9f833..cb4ee3d55 100755 --- a/management/ssl_certificates.py +++ b/management/ssl_certificates.py @@ -216,12 +216,12 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True response = query_dns(domain, rtype) if response != normalize_ip(value): bad_dns.append("%s (%s)" % (response, rtype)) - + if bad_dns: domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \ + (", ".join(bad_dns)) \ + "." - + else: # DNS is all good. diff --git a/setup/migrate.py b/setup/migrate.py index b10f085fc..7bcd7a75f 100755 --- a/setup/migrate.py +++ b/setup/migrate.py @@ -202,7 +202,7 @@ def run_migrations(): migration_id = None if os.path.exists(migration_id_file): with open(migration_id_file) as f: - migration_id = f.read().strip(); + migration_id = f.read().strip() if migration_id is None: # Load the legacy location of the migration ID. We'll drop support @@ -213,7 +213,7 @@ def run_migrations(): print() print("%s file doesn't exists. Skipping migration..." % (migration_id_file,)) return - + return ourver = int(migration_id) while True: diff --git a/setup/nextcloud.sh b/setup/nextcloud.sh index 90485c8b4..8d06f93a9 100755 --- a/setup/nextcloud.sh +++ b/setup/nextcloud.sh @@ -26,7 +26,7 @@ InstallNextcloud() { echo # Download and verify - wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip + wget_verify http://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip # Remove the current owncloud/Nextcloud rm -rf /usr/local/lib/owncloud diff --git a/setup/start.sh b/setup/start.sh index cedc426d3..c079fce22 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -174,3 +174,4 @@ else echo Then you can confirm the security exception and continue. echo fi + From e6657d6ebe4587ab060ce1c730f4ec989de96688 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Wed, 4 Nov 2020 20:25:25 +0600 Subject: [PATCH 03/13] Added key rollover code. --- management/daemon.py | 74 +++++++++++++++++++++++++++++++- management/dns_update.py | 21 +++++++--- management/ssl_certificates.py | 77 +++++++++++++++++++++++++++------- management/status_checks.py | 4 +- management/templates/ssl.html | 72 +++++++++++++++++++++++++++++-- setup/ssl.sh | 9 +++- 6 files changed, 228 insertions(+), 29 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 956f6cf54..1698f9e87 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -364,6 +364,77 @@ def ssl_get_csr(domain): ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem')) return create_csr(domain, ssl_private_key, request.form.get('countrycode', ''), env) +@app.route('/ssl/renew/', methods=['POST']) +@authorized_personnel_only +def ssl_renew(domain): + from exclusiveprocess import Lock + from utils import load_environment + from ssl_certificates import provision_certificates + existing_key = request.form.get('existing_key') + Lock(die=True).forever() + env = load_environment() + if existing_key == "yes": + status = provision_certificates(env, limit_domains=[], domain_to_be_renewed=domain) + app.logger.warning("renew without new key=", status) # TODO: remove this line after testing + elif existing_key == "no": + import glob + try: + # steps followed + # 1. take a backup of the current /home/user-data/ssl/ folder to be safe + # 2. renew all the existing certificates from CSR generated from the existing next_ssl_private_key + # 3. if the renew is successful, replace the current ssl_private_key with the next_ssl_private_key and + # 4. generate the next_ssl_private_key + # 5. if any error occurs, copy everything from the /home/user-data/ssl-backup folder to /home/user-data/ssl + + # step 1 + files = glob.glob(env["STORAGE_ROOT"] + "/ssl/*") + for file in files: + subprocess.check_output(["cp", "-r", file, env["STORAGE_ROOT"] + "/ssl-backup/"]) + + # step 2 + status = provision_certificates(env, limit_domains=[], new_key=True) + + # step 3 and 4 is in post_install_func method of ssl_certificates.py + app.logger.warning("renew with new key=", status) # TODO: remove this line after proper testing + except Exception as e: + import traceback + files = glob.glob(env["STORAGE_ROOT"] + "/ssl-backup/*") + for file in files: + subprocess.check_output(["cp", "-r", file, env["STORAGE_ROOT"] + "/ssl/"]) + app.logger.warning(traceback.print_exc()) # TODO: remove this line after proper testing + return json_response({ + "title": "Error", + "log": "Sorry, something is not right!", + }) + else: + return json_response({ + "title": "Error", + "log": "Sorry, something is not right!", + }) + + for item in status: + if isinstance(status, str): + continue + else: + if domain in item['domains']: + if item['result'] == 'skipped': + return json_response({ + "title": item["result"].capitalize(), + "log": "\n".join(item['log']), + }) + elif item['result'] == 'installed': + return json_response({ + "title": item["result"].capitalize(), + "log": "Your certificate containing these domains " + ",".join( + item['domains']) + " have been renewed", + }) + else: + return json_response({ + "title": item["result"].capitalize(), + "log": "\n".join(item['log']) + }) + + @app.route('/ssl/install', methods=['POST']) @authorized_personnel_only def ssl_install_cert(): @@ -604,7 +675,8 @@ def log_failed_login(request): # APP if __name__ == '__main__': - if "DEBUG" in os.environ: app.debug = True + app.debug = True # TODO: remove this line and uncomment the next line after testing + # if "DEBUG" in os.environ: app.debug = True if "APIKEY" in os.environ: auth_service.key = os.environ["APIKEY"] if not app.debug: diff --git a/management/dns_update.py b/management/dns_update.py index 748f87f15..7b3b766ae 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -174,9 +174,11 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Add a DANE TLSA record for SMTP. records.append(("_25._tcp", "TLSA", build_tlsa_record(env), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used.")) + records.append(("_25._tcp", "TLSA", build_tlsa_record(env, from_cert=False), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used.")) # Add a DANE TLSA record for HTTPS, which some browser extensions might make use of. records.append(("_443._tcp", "TLSA", build_tlsa_record(env), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it.")) + records.append(("_443._tcp", "TLSA", build_tlsa_record(env, from_cert=False), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it.")) # Add a SSHFP records to help SSH key validation. One per available SSH key on this system. for value in build_sshfp_records(): @@ -367,7 +369,7 @@ def has_rec(qname, rtype, prefix=None): ######################################################################## -def build_tlsa_record(env): +def build_tlsa_record(env, from_cert=True): # A DANE TLSA record in DNS specifies that connections on a port # must use TLS and the certificate must match a particular criteria. # @@ -390,11 +392,16 @@ def build_tlsa_record(env): from ssl_certificates import load_cert_chain, load_pem from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat - fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem") - cert = load_pem(load_cert_chain(fn)[0]) - - subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) - # We could have also loaded ssl_private_key.pem and called priv_key.public_key().public_bytes(...) + if from_cert: + fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem") + cert = load_pem(load_cert_chain(fn)[0]) + subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) + else: + # this is for Double TLSA scheme of key rollover. + # More details here (https://mail.sys4.de/pipermail/dane-users/2018-February/000440.html) + fn = os.path.join(env["STORAGE_ROOT"], "ssl", "next_ssl_private_key.pem") + private_key = load_pem(open(fn, 'rb').read()) + subject_public_key = private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) pk_hash = hashlib.sha256(subject_public_key).hexdigest() @@ -862,6 +869,8 @@ def set_custom_dns_record(qname, rtype, value, action, env): if not re.search(DOMAIN_RE, value): raise ValueError("Invalid value.") + elif rtype == "TLSA": + pass elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"): # anything goes pass diff --git a/management/ssl_certificates.py b/management/ssl_certificates.py index cb4ee3d55..ec74b4854 100755 --- a/management/ssl_certificates.py +++ b/management/ssl_certificates.py @@ -45,7 +45,7 @@ def get_file_list(): # Remember stuff. private_keys = { } - certificates = [ ] + certificates = [] # Scan each of the files to find private keys and certificates. # We must load all of the private keys first before processing @@ -73,6 +73,7 @@ def get_file_list(): domains = { } for cert in certificates: # What domains is this certificate good for? + # cert_domains = cert common name + all SANs, primary_domain = cert common name cert_domains, primary_domain = get_certificate_domains(cert) cert._primary_domain = primary_domain @@ -186,8 +187,16 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True from web_update import get_web_domains from status_checks import query_dns, normalize_ip + # existing_certs = all valid certificates that are in /home/user-data/ssl or 1 level deep + # validity indicator -> private key exists for the cert public key?, not_expired? + # if multiple valid certs exist, then the one with the furthest expiry date and filename + # lexicographically smallest is returned existing_certs = get_ssl_certificates(env) + # this function returns the list of domain names of all the email addresses and adds + # autoconfig, www, mta-sts, autodiscover subdomain to each of these domains + # if exclude_dns_elsewhere flag is set, then all the domains having A/AAAA record not on this machine + # are excluded plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False) actual_web_domains = get_web_domains(env) @@ -212,7 +221,8 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True # how Let's Encrypt will connect. bad_dns = [] for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: - if not value: continue # IPv6 is not configured + if not value: + continue # IPv6 is not configured response = query_dns(domain, rtype) if response != normalize_ip(value): bad_dns.append("%s (%s)" % (response, rtype)) @@ -226,6 +236,7 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True # DNS is all good. # Check for a good existing cert. + # existing_cert = existing cert for domain existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True) if existing_cert: existing_cert_check = check_certificate(domain, existing_cert['certificate'], existing_cert['private-key'], @@ -242,19 +253,30 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True return (domains_to_provision, domains_cant_provision) -def provision_certificates(env, limit_domains): +def provision_certificates(env, limit_domains, domain_to_be_renewed=None, new_key=False): # What domains should we provision certificates for? And what # errors prevent provisioning for other domains. - domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains) - - # Build a list of what happened on each domain or domain-set. ret = [] - for domain, error in domains_cant_provision.items(): - ret.append({ - "domains": [domain], - "log": [error], - "result": "skipped", - }) + is_tlsa_update_required = False + if new_key: + from web_update import get_web_domains + domains = get_web_domains(env) + elif domain_to_be_renewed: + existing_certs = get_ssl_certificates(env) + existing_cert = get_domain_ssl_files(domain_to_be_renewed, existing_certs, env, use_main_cert=False, allow_missing_cert=True) + domains, primary_domain = get_certificate_domains(load_pem(load_cert_chain(existing_cert["certificate"])[0])) + else: + # domains = domains for which a certificate can be provisioned + # domains_cant_provision = domains for which a certificate can't be provisioned and the reason + domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains) + + # Build a list of what happened on each domain or domain-set. + for domain, error in domains_cant_provision.items(): + ret.append({ + "domains": [domain], + "log": [error], + "result": "skipped", + }) # Break into groups by DNS zone: Group every domain with its parent domain, if # its parent domain is in the list of domains to request a certificate for. @@ -309,6 +331,8 @@ def provision_certificates(env, limit_domains): # Create a CSR file for our master private key so that certbot # uses our private key. key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem') + if new_key: + key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'next_ssl_private_key.pem') with tempfile.NamedTemporaryFile() as csr_file: # We could use openssl, but certbot requires # that the CN domain and SAN domains match @@ -345,9 +369,9 @@ def provision_certificates(env, limit_domains): "certbot", "certonly", #"-v", # just enough to see ACME errors - "--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup + "--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup - "-d", ",".join(domain_list), # first will be main domain + "-d", ",".join(domain_list), # first will be main domain "--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually "--cert-path", os.path.join(d, 'cert'), # we only use the full chain @@ -363,6 +387,8 @@ def provision_certificates(env, limit_domains): ret[-1]["log"].append(certbotret) ret[-1]["result"] = "installed" + if new_key and env['PRIMARY_HOSTNAME'] in domains: + is_tlsa_update_required = True except subprocess.CalledProcessError as e: ret[-1]["log"].append(e.output.decode("utf8")) ret[-1]["result"] = "error" @@ -371,7 +397,7 @@ def provision_certificates(env, limit_domains): ret[-1]["result"] = "error" # Run post-install steps. - ret.extend(post_install_func(env)) + ret.extend(post_install_func(env, is_tlsa_update_required=is_tlsa_update_required)) # Return what happened with each certificate request. return ret @@ -466,7 +492,7 @@ def install_cert_copy_file(fn, env): shutil.move(fn, ssl_certificate) -def post_install_func(env): +def post_install_func(env, is_tlsa_update_required=False): ret = [] # Get the certificate to use for PRIMARY_HOSTNAME. @@ -496,6 +522,25 @@ def post_install_func(env): # The DANE TLSA record will remain valid so long as the private key # hasn't changed. We don't ever change the private key automatically. # If the user does it, they must manually update DNS. + if is_tlsa_update_required: + from dns_update import do_dns_update, set_custom_dns_record, build_tlsa_record + subprocess.check_output([ + "mv", env["STORAGE_ROOT"] + "/ssl/next_ssl_private_key.pem", + env["STORAGE_ROOT"] + "/ssl/ssl_private_key.pem" + ]) + subprocess.check_output([ + "openssl", "genrsa", + "-out", env["STORAGE_ROOT"] + "/ssl/next_ssl_private_key.pem", + "2048"]) + qname1 = "_25._tcp." + env['PRIMARY_HOSTNAME'] + qname2 = "_443._tcp." + env['PRIMARY_HOSTNAME'] + rtype = "TLSA" + value = build_tlsa_record(env, from_cert=False) + action = "add" + if set_custom_dns_record(qname1, rtype, value, action, env): + set_custom_dns_record(qname2, rtype, value, action, env) + ret.append(do_dns_update(env)) + # Update the web configuration so nginx picks up the new certificate file. from web_update import do_web_update diff --git a/management/status_checks.py b/management/status_checks.py index 36da034a1..7582a2e9d 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -454,9 +454,9 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles): # Check the TLSA record. tlsa_qname = "_25._tcp." + domain - tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None) + tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None).split('; ') tlsa25_expected = build_tlsa_record(env) - if tlsa25 == tlsa25_expected: + if tlsa25_expected in tlsa25: output.print_ok("""The DANE TLSA record for incoming mail is correct (%s).""" % tlsa_qname,) elif tlsa25 is None: if has_dnssec: diff --git a/management/templates/ssl.html b/management/templates/ssl.html index a6b913eed..5a18e1427 100644 --- a/management/templates/ssl.html +++ b/management/templates/ssl.html @@ -40,7 +40,10 @@

Certificate status

Install certificate

-

If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. You can generate the needed CSR below.

+

If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. Click on install certificate button + if there is no certificate for your intended domain or + click on renew or replace certificate button and click replace if there is an existing certificate and you want to replace it with a new one from a different CA. + You can generate the needed CSR below.

Which domain are you getting a certificate for?

@@ -101,7 +104,8 @@

Install certificate

$('#ssldomain').html(''); $('#ssl_domains').show(); for (var i = 0; i < domains.length; i++) { - var row = $(" Install Certificate"); + var row = $(" " + + "Install Certificate"); tb.append(row); row.attr('data-domain', domains[i].domain); row.find('.domain a').text(domains[i].domain); @@ -113,7 +117,10 @@

Install certificate

row.addClass("text-" + domains[i].status); row.find('.status').text(domains[i].text); if (domains[i].status == "success") { - row.find('.actions a').addClass('btn-default').text('Replace Certificate'); + row.find('.actions a').addClass('btn-default').text('Renew or replace Certificate'); + row.find('.actions a').addClass('btn-default').on("click", function () { + ssl_renew_or_replace_modal(this); + }); } else { row.find('.actions a').addClass('btn-primary').text('Install Certificate'); } @@ -131,6 +138,65 @@

Install certificate

return false; } +function ssl_renew_or_replace_modal(elem) { + show_modal_confirm( + "Options", + "Do you want to replace the certificate with a new one or just renew this one?", + ["Replace", "Renew"], + function () { + ssl_install(elem); + }, + function () { + ssl_cert_renew(elem); + }); +} +function ssl_cert_renew(elem) { + var domain = $(elem).parents('tr').attr('data-domain'); + show_modal_confirm( + "Options", + "Do you want to renew with the existing key?", + ["Yes", "No"], + function () { + ajax_with_indicator(true); + api( + "/ssl/renew/" + domain, + "POST", + { + existing_key: "yes" + }, + function(data) { + $('#ajax_loading_indicator').stop(true).hide(); + show_modal_error(data["title"], data["log"]); + show_tls(true); + }, + function () { + $('#ajax_loading_indicator').stop(true).hide(); + show_modal_error("Error", "Something is not right, sorry!"); + show_tls(true); + }); + }, + function () { + ajax_with_indicator(true); + api( + "/ssl/renew/" + domain, + "POST", + { + existing_key: "no" + }, + function(data) { + $('#ajax_loading_indicator').stop(true).hide(); + show_modal_error(data["title"], data["log"]); + show_tls(true); + }, + function () { + $('#ajax_loading_indicator').stop(true).hide(); + show_modal_error("Error", "Something is not right, sorry!"); + show_tls(true); + } + ); + }); +} + function show_csr() { // Can't show a CSR until both inputs are entered. if ($('#ssldomain').val() == "") return; diff --git a/setup/ssl.sh b/setup/ssl.sh index 61b0b9e5c..2a249492d 100755 --- a/setup/ssl.sh +++ b/setup/ssl.sh @@ -19,7 +19,7 @@ # # The Diffie-Hellman cipher bits are used for SMTP and HTTPS, when a # Diffie-Hellman cipher is selected during TLS negotiation. Diffie-Hellman -# provides Perfect Forward Secrecy. +# provides Perfect Forward Secrecy. source setup/functions.sh # load our functions source /etc/mailinabox.conf # load global vars @@ -66,6 +66,13 @@ if [ ! -f $STORAGE_ROOT/ssl/ssl_private_key.pem ]; then openssl genrsa -out $STORAGE_ROOT/ssl/ssl_private_key.pem 2048) fi +# for Double TLSA scheme. More details here (https://mail.sys4.de/pipermail/dane-users/2018-February/000440.html) +if [ ! -f $STORAGE_ROOT/ssl/next_ssl_private_key.pem ]; then + # Set the umask so the key file is never world-readable. + (umask 077; hide_output \ + openssl genrsa -out $STORAGE_ROOT/ssl/next_ssl_private_key.pem 2048) +fi + # Generate a self-signed SSL certificate because things like nginx, dovecot, # etc. won't even start without some certificate in place, and we need nginx # so we can offer the user a control panel to install a better certificate. From 8b9376c37c8a14c012340a637c5b645ee5a85371 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Wed, 4 Nov 2020 20:46:10 +0600 Subject: [PATCH 04/13] Added key rollover code. --- WhatIDidSoFar.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/WhatIDidSoFar.md b/WhatIDidSoFar.md index 3c5a4f1c9..5e3c00315 100644 --- a/WhatIDidSoFar.md +++ b/WhatIDidSoFar.md @@ -67,3 +67,14 @@ are the steps that you gotta follow: g) _To check this option, type $ echo $DISPLAY and see whether localhost=10.0.0 comes up or not. If it does, then you're good to go._ h) _Now type firefox in your putty terminal and you should see the output in firefox browser in your windows host machine_ + + + +To make it work into an existing mailinabox setup, you need to do the following: +1. sudo setup/ssl.sh +2. sudo setup/dns.sh +3. sudo tools/dns_update +4. sudo service mailinabox restart + +To view the print logs for testing: +1. sudo cat /var/log/syslog From e525dd7e2c237f287c18b8e910c128e50f29cc7a Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Wed, 4 Nov 2020 21:59:35 +0600 Subject: [PATCH 05/13] minor --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 14e6c4a79..a64c166d8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ tools/__pycache__/ externals/ .env .vagrant -api/docs/api-docs.html \ No newline at end of file +api/docs/api-docs.html +WhatIDidSoFar.md From 25ae216e008246e3221c3e706fef5286a4820d71 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Wed, 4 Nov 2020 22:00:28 +0600 Subject: [PATCH 06/13] minor --- .gitignore | 1 - WhatIDidSoFar.md | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a64c166d8..6d7e391b9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ externals/ .env .vagrant api/docs/api-docs.html -WhatIDidSoFar.md diff --git a/WhatIDidSoFar.md b/WhatIDidSoFar.md index 5e3c00315..069fee73e 100644 --- a/WhatIDidSoFar.md +++ b/WhatIDidSoFar.md @@ -68,7 +68,14 @@ are the steps that you gotta follow: h) _Now type firefox in your putty terminal and you should see the output in firefox browser in your windows host machine_ - +What is done here? +Mail-in-a-box is an open source sw that provides you the options to control your mail server yourself. It gives you a DNS server as well and +has all the necessary settings like DNSSEC, DANE TLSA, etc. But the existing software doesn't really provide the options for +renewing key. It creates a key pair initially and uses this key during the whole lifetime of the setup. If any user updates +the key, he or she will have to manually change the certificates and TLSA records which is error-prone. So, what I did is provide an option to +renew the cert for user with both the existing key and with a new key and if user does so with a new key, then update the +TLSA records. I followed the double TLSA scheme. Main motivation of doing this is to reduce the number of misconfigurations +due to manual key rollover. To make it work into an existing mailinabox setup, you need to do the following: 1. sudo setup/ssl.sh From 8574b43542cefb4e8f9d6752f052d06e69074122 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Thu, 5 Nov 2020 21:03:29 +0600 Subject: [PATCH 07/13] bug fix on modal close and repeated cert issuance --- management/daemon.py | 2 -- management/templates/index.html | 11 ++++--- management/templates/ssl.html | 56 +++++++++++++++++++++++---------- setup/ssl.sh | 1 + 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 1698f9e87..88bc0e17c 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -367,11 +367,9 @@ def ssl_get_csr(domain): @app.route('/ssl/renew/', methods=['POST']) @authorized_personnel_only def ssl_renew(domain): - from exclusiveprocess import Lock from utils import load_environment from ssl_certificates import provision_certificates existing_key = request.form.get('existing_key') - Lock(die=True).forever() env = load_environment() if existing_key == "yes": status = provision_certificates(env, limit_domains=[], domain_to_be_renewed=domain) diff --git a/management/templates/index.html b/management/templates/index.html index 2c0d5a9ac..31657a3d5 100644 --- a/management/templates/index.html +++ b/management/templates/index.html @@ -209,17 +209,17 @@ // so that we don't attempt to show another modal while this one // is closing. global_modal_state = 0; // OK - }) + }); $('#global_modal .btn-default').click(function() { global_modal_state = 1; // Cancel - }) + }); $('#global_modal').on('hidden.bs.modal', function (e) { // do the cancel function if (global_modal_state == null) global_modal_state = 1; // cancel if the user hit ESC or clicked outside of the modal if (global_modal_funcs && global_modal_funcs[global_modal_state]) global_modal_funcs[global_modal_state](); }) -}) +}); function show_modal_error(title, message, callback) { $('#global_modal h4').text(title); @@ -239,7 +239,7 @@ return false; // handy when called from onclick } -function show_modal_confirm(title, question, verb, yes_callback, cancel_callback) { +function show_modal_confirm(title, question, verb, yes_callback, cancel_callback, extra_callback=null) { $('#global_modal h4').text(title); if (typeof question == 'string') { $('#global_modal .modal-dialog').addClass("modal-sm"); @@ -256,7 +256,8 @@ $('#global_modal .btn-default').show().text(verb[1]); $('#global_modal .btn-danger').show().text(verb[0]); } - global_modal_funcs = [yes_callback, cancel_callback]; + if (extra_callback) global_modal_funcs = [yes_callback, cancel_callback, extra_callback]; + else global_modal_funcs = [yes_callback, cancel_callback]; global_modal_state = null; $('#global_modal').modal({}); return false; // handy when called from onclick diff --git a/management/templates/ssl.html b/management/templates/ssl.html index 5a18e1427..d3db8edd9 100644 --- a/management/templates/ssl.html +++ b/management/templates/ssl.html @@ -137,64 +137,86 @@

Install certificate

$('html, body').animate({ scrollTop: $('#ssl_install_header').offset().top - $('.navbar-fixed-top').height() - 20 }) return false; } +let flag = false; function ssl_renew_or_replace_modal(elem) { + if (!flag) { + $('#global_modal .modal-footer').append(''); + flag = true; + } + $('#global_modal .btn-warning').click(function() { + global_modal_state = 2; // Renew + }); show_modal_confirm( "Options", "Do you want to replace the certificate with a new one or just renew this one?", - ["Replace", "Renew"], + "Replace", function () { + $('#global_modal .modal-footer .btn-warning').remove(); + flag = false; ssl_install(elem); - }, + }, function() {$('#global_modal .modal-footer .btn-warning').remove();flag = false;}, function () { + $('#global_modal .modal-footer .btn-warning').remove(); + flag = false; ssl_cert_renew(elem); }); } function ssl_cert_renew(elem) { + if (!flag) { + $('#global_modal .modal-footer').append(''); + flag = true; + } + $('#global_modal .btn-warning').click(function() { + global_modal_state = 2; // Renew + }); var domain = $(elem).parents('tr').attr('data-domain'); show_modal_confirm( - "Options", + "Renewing options", "Do you want to renew with the existing key?", - ["Yes", "No"], + "No", function () { - ajax_with_indicator(true); + $('#global_modal .modal-footer .btn-warning').remove();flag = false; + $('#ajax_loading_indicator').show(); api( "/ssl/renew/" + domain, "POST", { - existing_key: "yes" + existing_key: "no" }, function(data) { $('#ajax_loading_indicator').stop(true).hide(); - show_modal_error(data["title"], data["log"]); - show_tls(true); + show_modal_error(data["title"], data["log"]); + show_tls(true); }, function () { $('#ajax_loading_indicator').stop(true).hide(); show_modal_error("Error", "Something is not right, sorry!"); show_tls(true); - }); - }, + } + ); + }, function() {$('#global_modal .modal-footer .btn-warning').remove();flag = false;}, function () { - ajax_with_indicator(true); + $('#global_modal .modal-footer .btn-warning').remove(); + flag = false; + $('#ajax_loading_indicator').show(); api( "/ssl/renew/" + domain, "POST", { - existing_key: "no" + existing_key: "yes" }, function(data) { $('#ajax_loading_indicator').stop(true).hide(); - show_modal_error(data["title"], data["log"]); - show_tls(true); + show_modal_error(data["title"], data["log"]); + show_tls(true); }, function () { $('#ajax_loading_indicator').stop(true).hide(); show_modal_error("Error", "Something is not right, sorry!"); show_tls(true); - } - ); - }); + }); + },); } function show_csr() { diff --git a/setup/ssl.sh b/setup/ssl.sh index 2a249492d..ef07c2741 100755 --- a/setup/ssl.sh +++ b/setup/ssl.sh @@ -39,6 +39,7 @@ apt_install openssl # Create a directory to store TLS-related things like "SSL" certificates. mkdir -p $STORAGE_ROOT/ssl +mkdir -p $STORAGE_ROOT/ssl-backup # creating a backup directory for ssl certs just to be safe # Generate a new private key. # From 3d2a526fe07e3926939a87953b2f2fdbd9497cce Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Thu, 5 Nov 2020 21:41:03 +0600 Subject: [PATCH 08/13] bug fix on error message --- management/daemon.py | 50 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 88bc0e17c..2cb3946dd 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -364,6 +364,26 @@ def ssl_get_csr(domain): ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem')) return create_csr(domain, ssl_private_key, request.form.get('countrycode', ''), env) + +def return_message(item): + if item['result'] == 'skipped': + return { + "title": item["result"].capitalize(), + "log": "\n".join(item['log']), + } + elif item['result'] == 'installed': + return { + "title": item["result"].capitalize(), + "log": "Your certificate containing these domains " + ",".join( + item['domains']) + " have been renewed", + } + else: + return { + "title": item["result"].capitalize(), + "log": "\n".join(item['log']) + } + + @app.route('/ssl/renew/', methods=['POST']) @authorized_personnel_only def ssl_renew(domain): @@ -410,27 +430,19 @@ def ssl_renew(domain): "log": "Sorry, something is not right!", }) + ret_message = {"title": "", "log": ""} for item in status: - if isinstance(status, str): + if isinstance(item, str): continue - else: - if domain in item['domains']: - if item['result'] == 'skipped': - return json_response({ - "title": item["result"].capitalize(), - "log": "\n".join(item['log']), - }) - elif item['result'] == 'installed': - return json_response({ - "title": item["result"].capitalize(), - "log": "Your certificate containing these domains " + ",".join( - item['domains']) + " have been renewed", - }) - else: - return json_response({ - "title": item["result"].capitalize(), - "log": "\n".join(item['log']) - }) + elif existing_key == "no": + message = return_message(item) + ret_message["title"] = message["title"] + ret_message["log"] += "\n" + message["log"] + elif existing_key == "yes" and domain in item["domains"]: + return json_response(return_message(item)) + return json_response(ret_message) + + @app.route('/ssl/install', methods=['POST']) From 6f83bf2e7d7a8cb509edd00ec8c9a50133bf1a09 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Thu, 5 Nov 2020 21:42:25 +0600 Subject: [PATCH 09/13] minor --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6d7e391b9..a64c166d8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ externals/ .env .vagrant api/docs/api-docs.html +WhatIDidSoFar.md From 0ff8d1d429f4d8da79d41d315da0c11f77eafd47 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Thu, 5 Nov 2020 22:37:30 +0600 Subject: [PATCH 10/13] minor --- management/daemon.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 2cb3946dd..5efb93b50 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -685,8 +685,7 @@ def log_failed_login(request): # APP if __name__ == '__main__': - app.debug = True # TODO: remove this line and uncomment the next line after testing - # if "DEBUG" in os.environ: app.debug = True + if "DEBUG" in os.environ: app.debug = True if "APIKEY" in os.environ: auth_service.key = os.environ["APIKEY"] if not app.debug: From 3ce13c17b9df6776e56d348d3da8687050c0fb46 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Thu, 5 Nov 2020 22:41:16 +0600 Subject: [PATCH 11/13] minor --- .gitignore | 2 +- WhatIDidSoFar.md => HeadsUp.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename WhatIDidSoFar.md => HeadsUp.md (100%) diff --git a/.gitignore b/.gitignore index a64c166d8..ec82d24a6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ externals/ .env .vagrant api/docs/api-docs.html -WhatIDidSoFar.md +HeadsUp.md diff --git a/WhatIDidSoFar.md b/HeadsUp.md similarity index 100% rename from WhatIDidSoFar.md rename to HeadsUp.md From a8adb54b0bf91cf1cb13db44ce752af5733348d1 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Fri, 6 Nov 2020 01:05:05 +0600 Subject: [PATCH 12/13] local changes reverted --- .gitignore | 1 - CONTRIBUTING.md | 2 -- HeadsUp.md | 87 ---------------------------------------------- Vagrantfile | 2 +- setup/migrate.py | 2 +- setup/nextcloud.sh | 4 +-- setup/start.sh | 1 - 7 files changed, 4 insertions(+), 95 deletions(-) delete mode 100644 HeadsUp.md diff --git a/.gitignore b/.gitignore index ec82d24a6..6d7e391b9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ externals/ .env .vagrant api/docs/api-docs.html -HeadsUp.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fb74a2ce..ba87115a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,6 @@ With Vagrant set up, the following should boot up Mail-in-a-Box inside a virtual $ vagrant up --provision -For possible errors encountered, please follow WhatIDidSoFar file. - ### Modifying your `hosts` file diff --git a/HeadsUp.md b/HeadsUp.md deleted file mode 100644 index 069fee73e..000000000 --- a/HeadsUp.md +++ /dev/null @@ -1,87 +0,0 @@ -Vagrant commands that you'd need most: -1. _To view the list of vagrant boxes, use `vagrant box list`_ -2. _To initialize a vagrant VM, use `vagrant init boxname`_ -3. _To start a vagrant VM, use `vagrant up`_ -4. _To shut down the vagrant VM, use `vagrant halt ubuntu/bionic64`_ -5. _To remove a vagrant box, use `vagrant box remove `_ - - -UserName and Password - -1. _Generally vagrant created VM's username is `vagrant`, password is `vagrant`_ -2. _hostname/ IP address will be available in -`config.vm.network "private_network", ip: `. _ - - -Errors encountered while setting up MIAB -1. _If you're seeing an error message about your *IP address being listed in the Spamhaus Block List*, -simply uncomment the `export SKIP_NETWORK_CHECKS=1` line in `Vagrantfile`. -It's normal, you're probably using a dynamic IP address assigned by your Internet provider–they're almost all listed._ -2. _If you're seeing an error message such as this `Bash script and /bin/bash^M: bad interpreter: No such file or directory`, - then most likely you're on windows host and your vm is ubuntu. - Then you've to change the format of all .py and .sh files in all the mailinabox directories to Unix (LF)._ -3. _If you're encountering migration error, please add this line *return* in line 216 at setup/migrate.py. -Then after the up --provision command is successful, you gotta uncomment this or remove this line. (Not sure yet)_ -4. _If your vagrant up command is stuck at upgrading to nextcloud, it is because the nextcloud server is either down -or very slow. Check the /tmp folder whether the nextcloud.zip is being downloaded. -If not, download it yourself and paste it in the /tmp folder._ -5. _As your vagrant VM is CLI, to see the contents of 192.168.50.4, do the following._ - - -To make sure that you can view the curl contents in your host machine's browser by executing commands from guest VM CLI, these -are the steps that you gotta follow: -1. _Copy the private key that vagrant generated for you and paste it in .ssh directory (for windows: by default this is the path `C:\\Users\HP\.ssh folder`) with a name_ -2. _Now if you try to login using the following SSH command, - `ssh -i username@hostname or username@ipaddress` -3. _You should be logged in to the vagrant VM_ -4. _CD into the directory /etc/ssh_ -5. _Edit the sshd_config file with sudo permission and uncomment these 3 lines:_ - - `X11Forwarding yes` - - `X11DisplayOffset 10` - - `X11UseLocalhost yes` -6. _Now restart the sshd service by the following command:_ - `sudo systemctl restart sshd` -7. _logout from your account_ -8. _If you're in ubuntu host, then do the following:_ - `ssh -X -i username@hostname or username@ipaddress` - _you should be logged into the host as username. type `echo $DISPLAY` and see whether `localhost=10.0.0` comes up or not. - If it does, then X11Forwarding is enabled. Now type firefox in your terminal - and you should see the output in firefox browser in your ubuntu host machine -9. _If you're in windows host, install XMing and Putty_ - - a) _Open Puttygen app and from conversions -> import key, load the key you saved in line 6_ - - b) _Save the key by pressing save private key button in the same folder_ - - c) _In Putty, go to Connections->SSH->Auth and load the private key by clicking load key button_ - - d) _go to Connections->SSH->X11 and tick on X11forwarding_ - - e) _Now, write the IP address/ hostname in sessions, save it with a session name and click on open._ - - f) _Type vagrant as username and you should be logged in with X11 forwarding option enabled_ - - g) _To check this option, type $ echo $DISPLAY and see whether localhost=10.0.0 comes up or not. If it does, then you're good to go._ - - h) _Now type firefox in your putty terminal and you should see the output in firefox browser in your windows host machine_ - -What is done here? -Mail-in-a-box is an open source sw that provides you the options to control your mail server yourself. It gives you a DNS server as well and -has all the necessary settings like DNSSEC, DANE TLSA, etc. But the existing software doesn't really provide the options for -renewing key. It creates a key pair initially and uses this key during the whole lifetime of the setup. If any user updates -the key, he or she will have to manually change the certificates and TLSA records which is error-prone. So, what I did is provide an option to -renew the cert for user with both the existing key and with a new key and if user does so with a new key, then update the -TLSA records. I followed the double TLSA scheme. Main motivation of doing this is to reduce the number of misconfigurations -due to manual key rollover. - -To make it work into an existing mailinabox setup, you need to do the following: -1. sudo setup/ssl.sh -2. sudo setup/dns.sh -3. sudo tools/dns_update -4. sudo service mailinabox restart - -To view the print logs for testing: -1. sudo cat /var/log/syslog diff --git a/Vagrantfile b/Vagrantfile index 0e6f0b89c..467fb95ee 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -19,7 +19,7 @@ Vagrant.configure("2") do |config| export PUBLIC_IP=auto export PUBLIC_IPV6=auto export PRIMARY_HOSTNAME=auto - export SKIP_NETWORK_CHECKS=1 + #export SKIP_NETWORK_CHECKS=1 # Start the setup script. cd /vagrant diff --git a/setup/migrate.py b/setup/migrate.py index 7bcd7a75f..da8d9ce0b 100755 --- a/setup/migrate.py +++ b/setup/migrate.py @@ -213,7 +213,7 @@ def run_migrations(): print() print("%s file doesn't exists. Skipping migration..." % (migration_id_file,)) return - return + ourver = int(migration_id) while True: diff --git a/setup/nextcloud.sh b/setup/nextcloud.sh index 8d06f93a9..63be809f0 100755 --- a/setup/nextcloud.sh +++ b/setup/nextcloud.sh @@ -26,7 +26,7 @@ InstallNextcloud() { echo # Download and verify - wget_verify http://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip + wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip # Remove the current owncloud/Nextcloud rm -rf /usr/local/lib/owncloud @@ -239,7 +239,7 @@ fi # * We need to set the timezone to the system timezone to allow fail2ban to ban # users within the proper timeframe # * We need to set the logdateformat to something that will work correctly with fail2ban -# * mail_domain' needs to be set every time we run the setup. Making sure we are setting +# * mail_domain' needs to be set every time we run the setup. Making sure we are setting # the correct domain name if the domain is being change from the previous setup. # Use PHP to read the settings file, modify it, and write out the new settings array. TIMEZONE=$(cat /etc/timezone) diff --git a/setup/start.sh b/setup/start.sh index c079fce22..cedc426d3 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -174,4 +174,3 @@ else echo Then you can confirm the security exception and continue. echo fi - From ae96f6f5e6069a4b4b7b53a9610608e06a6e76d8 Mon Sep 17 00:00:00 2001 From: Ashiq5 Date: Fri, 6 Nov 2020 01:45:16 +0600 Subject: [PATCH 13/13] local changes reverted --- CONTRIBUTING.md | 2 +- management/auth.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba87115a8..d3f33274e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ We recommend you use [Vagrant](https://www.vagrantup.com/intro/getting-started/i With Vagrant set up, the following should boot up Mail-in-a-Box inside a virtual machine: $ vagrant up --provision - +_If you're seeing an error message about your *IP address being listed in the Spamhaus Block List*, simply uncomment the `export SKIP_NETWORK_CHECKS=1` line in `Vagrantfile`. It's normal, you're probably using a dynamic IP address assigned by your Internet provider–they're almost all listed._ ### Modifying your `hosts` file diff --git a/management/auth.py b/management/auth.py index 761ec56ee..b97ea057a 100644 --- a/management/auth.py +++ b/management/auth.py @@ -6,7 +6,7 @@ from mailconfig import get_mail_password, get_mail_user_privileges from mfa import get_hash_mfa_state, validate_auth_mfa -DEFAULT_KEY_PATH = '/var/lib/mailinabox/api.key' +DEFAULT_KEY_PATH = '/var/lib/mailinabox/api.key' DEFAULT_AUTH_REALM = 'Mail-in-a-Box Management Server' class KeyAuthService: