def migration_miabldap_1(env): # This migration step moves users from sqlite3 to openldap # users table: # for each row create an ldap entry of the form: # dn: uid=[uuid],ou=Users,dc=mailinabox # objectClass: inetOrgPerson, mailUser, shadowAccount # mail: [email] # maildrop: [email] # userPassword: [password] # mailaccess: [privilege] # multi-valued # # aliases table: # for each row create an ldap entry of the form: # dn: cn=[uuid],ou=aliases,ou=Users,dc=mailinabox # objectClass: mailGroup # mail: [source] # member: [destination-dn] # multi-valued # rfc822MailMember: [email] # multi-values # # if the alias has permitted_senders, create: # dn: cn=[uuid],ou=permitted-senders,ou=Config,dc=mailinabox # objectClass: mailGroup # mail: [source] # member: [user-dn] # multi-valued print("Migrating users and aliases from sqlite to ldap") # Get the ldap server up and running shell("check_call", ["setup/ldap.sh", "-v"]) import sqlite3, ldap3 import migration_13 as m13 # 2. get ldap site details (miab_ldap.conf was created by ldap.sh) ldapvars = load_env_vars_from_file(os.path.join(env["STORAGE_ROOT"], "ldap/miab_ldap.conf"), strip_quotes=True) ldap_base = ldapvars.LDAP_BASE ldap_domains_base = ldapvars.LDAP_DOMAINS_BASE ldap_permitted_senders_base = ldapvars.LDAP_PERMITTED_SENDERS_BASE ldap_users_base = ldapvars.LDAP_USERS_BASE ldap_aliases_base = ldapvars.LDAP_ALIASES_BASE ldap_services_base = ldapvars.LDAP_SERVICES_BASE ldap_admin_dn = ldapvars.LDAP_ADMIN_DN ldap_admin_pass = ldapvars.LDAP_ADMIN_PASSWORD # 3. connect conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/users.sqlite")) ldap = ldap3.Connection('127.0.0.1', ldap_admin_dn, ldap_admin_pass, raise_exceptions=True) ldap.bind() # 4. perform the migration users=m13.create_users(env, conn, ldap, ldap_base, ldap_users_base, ldap_domains_base) aliases=m13.create_aliases(env, conn, ldap, ldap_aliases_base) permitted=m13.create_permitted_senders(conn, ldap, ldap_users_base, ldap_permitted_senders_base) m13.populate_aliases(conn, ldap, users, aliases) ldap.unbind() conn.close()
def check_dns_zone(domain, env, dns_zonefiles): # We provide a DNS zone for the domain. It should have NS records set up # at the domain name's registrar pointing to this box. existing_ns = query_dns(domain, "NS") correct_ns = "ns1.BOX; ns2.BOX".replace("BOX", env['PRIMARY_HOSTNAME']) if existing_ns.lower() == correct_ns.lower(): env['out'].print_ok("Nameservers are set correctly at registrar. [%s]" % correct_ns) else: env['out'].print_error("""The nameservers set on this domain are incorrect. They are currently %s. Use your domain name registar's control panel to set the nameservers to %s.""" % (existing_ns, correct_ns) ) # See if the domain has a DS record set at the registrar. The DS record may have # several forms. We have to be prepared to check for any valid record. We've # pre-generated all of the valid digests --- read them in. ds_correct = open('/etc/nsd/zones/' + dns_zonefiles[domain] + '.ds').read().strip().split("\n") digests = { } for rr_ds in ds_correct: ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split(" ") digests[ds_digalg] = ds_digest # Some registrars may want the public key so they can compute the digest. The DS # record that we suggest using is for the KSK (and that's how the DS records were generated). dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/keys.conf')) dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3] # Query public DNS for the DS record at the registrar. ds = query_dns(domain, "DS", nxdomain=None) ds_looks_valid = ds and len(ds.split(" ")) == 4 if ds_looks_valid: ds = ds.split(" ") if ds_looks_valid and ds[0] == ds_keytag and ds[1] == '7' and ds[3] == digests.get(ds[2]): env['out'].print_ok("DNS 'DS' record is set correctly at registrar.") else: if ds == None: env['out'].print_error("""This domain's DNS DS record is not set. The DS record is optional. The DS record activates DNSSEC. To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:""") else: env['out'].print_error("""This domain's DNS DS record is incorrect. The chain of trust is broken between the public DNS system and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and provide to them this information:""") env['out'].print_line("") env['out'].print_line("Key Tag: " + ds_keytag + ("" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0])) env['out'].print_line("Key Flags: KSK") env['out'].print_line("Algorithm: 7 / RSASHA1-NSEC3-SHA1" + ("" if not ds_looks_valid or ds[1] == '7' else " (Got '%s')" % ds[1])) # see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml env['out'].print_line("Digest Type: 2 / SHA-256") # http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml env['out'].print_line("Digest: " + digests['2']) if ds_looks_valid and ds[3] != digests.get(ds[2]): env['out'].print_line("(Got digest type %s and digest %s which do not match.)" % (ds[2], ds[3])) env['out'].print_line("Public Key: ") env['out'].print_line(dnsssec_pubkey, monospace=True) env['out'].print_line("") env['out'].print_line("Bulk/Record Format:") env['out'].print_line("" + ds_correct[0]) env['out'].print_line("")
def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False): # See if the domain has a DS record set at the registrar. The DS record may have # several forms. We have to be prepared to check for any valid record. We've # pre-generated all of the valid digests --- read them in. ds_file = '/etc/nsd/zones/' + dns_zonefiles[domain] + '.ds' if not os.path.exists(ds_file): return # Domain is in our database but DNS has not yet been updated. ds_correct = open(ds_file).read().strip().split("\n") digests = { } for rr_ds in ds_correct: ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split(" ") digests[ds_digalg] = ds_digest # Some registrars may want the public key so they can compute the digest. The DS # record that we suggest using is for the KSK (and that's how the DS records were generated). alg_name_map = { '7': 'RSASHA1-NSEC3-SHA1', '8': 'RSASHA256' } dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg])) dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3] # Query public DNS for the DS record at the registrar. ds = query_dns(domain, "DS", nxdomain=None) ds_looks_valid = ds and len(ds.split(" ")) == 4 if ds_looks_valid: ds = ds.split(" ") if ds_looks_valid and ds[0] == ds_keytag and ds[1] == ds_alg and ds[3] == digests.get(ds[2]): if is_checking_primary: return output.print_ok("DNSSEC 'DS' record is set correctly at registrar.") else: if ds == None: if is_checking_primary: return output.print_warning("""This domain's DNSSEC DS record is not set. The DS record is optional. The DS record activates DNSSEC. To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:""") else: if is_checking_primary: output.print_error("""The DNSSEC 'DS' record for %s is incorrect. See further details below.""" % domain) return output.print_error("""This domain's DNSSEC DS record is incorrect. The chain of trust is broken between the public DNS system and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and provide to them this information:""") output.print_line("") output.print_line("Key Tag: " + ds_keytag + ("" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0])) output.print_line("Key Flags: KSK") output.print_line( ("Algorithm: %s / %s" % (ds_alg, alg_name_map[ds_alg])) + ("" if not ds_looks_valid or ds[1] == ds_alg else " (Got '%s')" % ds[1])) # see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml output.print_line("Digest Type: 2 / SHA-256") # http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml output.print_line("Digest: " + digests['2']) if ds_looks_valid and ds[3] != digests.get(ds[2]): output.print_line("(Got digest type %s and digest %s which do not match.)" % (ds[2], ds[3])) output.print_line("Public Key: ") output.print_line(dnsssec_pubkey, monospace=True) output.print_line("") output.print_line("Bulk/Record Format:") output.print_line("" + ds_correct[0]) output.print_line("")
def find_dnssec_signing_keys(domain, env): # For key that we generated (one per algorithm)... d = os.path.join(env['STORAGE_ROOT'], 'dns/dnssec') keyconfs = [f for f in os.listdir(d) if f.endswith(".conf")] for keyconf in keyconfs: # Load the file holding the KSK and ZSK key filenames. keyconf_fn = os.path.join(d, keyconf) keyinfo = load_env_vars_from_file(keyconf_fn) # Skip this key if the conf file has a setting named DOMAINS, # holding a comma-separated list of domain names, and if this # domain is not in the list. This allows easily disabling a # key by setting "DOMAINS=" or "DOMAINS=none", other than # deleting the key's .conf file, which might result in the key # being regenerated next upgrade. Keys should be disabled if # they are not needed to reduce the DNSSEC query response size. if "DOMAINS" in keyinfo and domain not in [dd.strip() for dd in keyinfo["DOMAINS"].split(",")]: continue for keytype in ("KSK", "ZSK"): yield keytype, keyinfo[keytype]
def sign_zone(domain, zonefile, env): algo = dnssec_choose_algo(domain, env) dnssec_keys = load_env_vars_from_file( os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % algo)) # In order to use the same keys for all domains, we have to generate # a new .key file with a DNSSEC record for the specific domain. We # can reuse the same key, but it won't validate without a DNSSEC # record specifically for the domain. # # Copy the .key and .private files to /tmp to patch them up. # # Use os.umask and open().write() to securely create a copy that only # we (root) can read. files_to_kill = [] for key in ("KSK", "ZSK"): if dnssec_keys.get(key, "").strip() == "": raise Exception("DNSSEC is not properly set up.") oldkeyfn = os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys[key]) newkeyfn = '/tmp/' + dnssec_keys[key].replace("_domain_", domain) dnssec_keys[key] = newkeyfn for ext in (".private", ".key"): if not os.path.exists(oldkeyfn + ext): raise Exception("DNSSEC is not properly set up.") with open(oldkeyfn + ext, "r") as fr: keydata = fr.read() keydata = keydata.replace( "_domain_", domain ) # trick ldns-signkey into letting our generic key be used by this zone fn = newkeyfn + ext prev_umask = os.umask( 0o77) # ensure written file is not world-readable try: with open(fn, "w") as fw: fw.write(keydata) finally: os.umask(prev_umask ) # other files we write should be world-readable files_to_kill.append(fn) # Do the signing. expiry_date = (datetime.datetime.now() + datetime.timedelta(days=30)).strftime("%Y%m%d") shell( 'check_call', [ "/usr/bin/ldns-signzone", # expire the zone after 30 days "-e", expiry_date, # use NSEC3 "-n", # zonefile to sign "/etc/nsd/zones/" + zonefile, # keys to sign with (order doesn't matter -- it'll figure it out) dnssec_keys["KSK"], dnssec_keys["ZSK"], ]) # Create a DS record based on the patched-up key files. The DS record is specific to the # zone being signed, so we can't use the .ds files generated when we created the keys. # The DS record points to the KSK only. Write this next to the zone file so we can # get it later to give to the user with instructions on what to do with it. # # We want to be able to validate DS records too, but multiple forms may be valid depending # on the digest type. So we'll write all (both) valid records. Only one DS record should # actually be deployed. Preferebly the first. with open("/etc/nsd/zones/" + zonefile + ".ds", "w") as f: for digest_type in ('2', '1'): rr_ds = shell( 'check_output', [ "/usr/bin/ldns-key2ds", "-n", # output to stdout "-" + digest_type, # 1=SHA1, 2=SHA256 dnssec_keys["KSK"] + ".key" ]) f.write(rr_ds) # Remove our temporary file. for fn in files_to_kill: os.unlink(fn)
def sign_zone(domain, zonefile, env): algo = dnssec_choose_algo(domain, env) dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % algo)) # In order to use the same keys for all domains, we have to generate # a new .key file with a DNSSEC record for the specific domain. We # can reuse the same key, but it won't validate without a DNSSEC # record specifically for the domain. # # Copy the .key and .private files to /tmp to patch them up. # # Use os.umask and open().write() to securely create a copy that only # we (root) can read. files_to_kill = [] for key in ("KSK", "ZSK"): if dnssec_keys.get(key, "").strip() == "": raise Exception("DNSSEC is not properly set up.") oldkeyfn = os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys[key]) newkeyfn = '/tmp/' + dnssec_keys[key].replace("_domain_", domain) dnssec_keys[key] = newkeyfn for ext in (".private", ".key"): if not os.path.exists(oldkeyfn + ext): raise Exception("DNSSEC is not properly set up.") with open(oldkeyfn + ext, "r") as fr: keydata = fr.read() keydata = keydata.replace("_domain_", domain) # trick ldns-signkey into letting our generic key be used by this zone fn = newkeyfn + ext prev_umask = os.umask(0o77) # ensure written file is not world-readable try: with open(fn, "w") as fw: fw.write(keydata) finally: os.umask(prev_umask) # other files we write should be world-readable files_to_kill.append(fn) # Do the signing. expiry_date = (datetime.datetime.now() + datetime.timedelta(days=30)).strftime("%Y%m%d") shell('check_call', ["/usr/bin/ldns-signzone", # expire the zone after 30 days "-e", expiry_date, # use NSEC3 "-n", # zonefile to sign "/etc/nsd/zones/" + zonefile, # keys to sign with (order doesn't matter -- it'll figure it out) dnssec_keys["KSK"], dnssec_keys["ZSK"], ]) # Create a DS record based on the patched-up key files. The DS record is specific to the # zone being signed, so we can't use the .ds files generated when we created the keys. # The DS record points to the KSK only. Write this next to the zone file so we can # get it later to give to the user with instructions on what to do with it. # # We want to be able to validate DS records too, but multiple forms may be valid depending # on the digest type. So we'll write all (both) valid records. Only one DS record should # actually be deployed. Preferebly the first. with open("/etc/nsd/zones/" + zonefile + ".ds", "w") as f: for digest_type in ('2', '1'): rr_ds = shell('check_output', ["/usr/bin/ldns-key2ds", "-n", # output to stdout "-" + digest_type, # 1=SHA1, 2=SHA256 dnssec_keys["KSK"] + ".key" ]) f.write(rr_ds) # Remove our temporary file. for fn in files_to_kill: os.unlink(fn)
def check_dns_zone(domain, env, dns_zonefiles): # We provide a DNS zone for the domain. It should have NS records set up # at the domain name's registrar pointing to this box. existing_ns = query_dns(domain, "NS") correct_ns = "ns1.BOX; ns2.BOX".replace("BOX", env['PRIMARY_HOSTNAME']) if existing_ns.lower() == correct_ns.lower(): env['out'].print_ok( "Nameservers are set correctly at registrar. [%s]" % correct_ns) else: env['out'].print_error( """The nameservers set on this domain are incorrect. They are currently %s. Use your domain name registar's control panel to set the nameservers to %s.""" % (existing_ns, correct_ns)) # See if the domain has a DS record set at the registrar. The DS record may have # several forms. We have to be prepared to check for any valid record. We've # pre-generated all of the valid digests --- read them in. ds_correct = open('/etc/nsd/zones/' + dns_zonefiles[domain] + '.ds').read().strip().split("\n") digests = {} for rr_ds in ds_correct: ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split( " ") digests[ds_digalg] = ds_digest # Some registrars may want the public key so they can compute the digest. The DS # record that we suggest using is for the KSK (and that's how the DS records were generated). dnssec_keys = load_env_vars_from_file( os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/keys.conf')) dnsssec_pubkey = open( os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3] # Query public DNS for the DS record at the registrar. ds = query_dns(domain, "DS", nxdomain=None) ds_looks_valid = ds and len(ds.split(" ")) == 4 if ds_looks_valid: ds = ds.split(" ") if ds_looks_valid and ds[0] == ds_keytag and ds[1] == '7' and ds[ 3] == digests.get(ds[2]): env['out'].print_ok("DNS 'DS' record is set correctly at registrar.") else: if ds == None: env['out'].print_error( """This domain's DNS DS record is not set. The DS record is optional. The DS record activates DNSSEC. To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:""" ) else: env['out'].print_error( """This domain's DNS DS record is incorrect. The chain of trust is broken between the public DNS system and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and provide to them this information:""") env['out'].print_line("") env['out'].print_line("Key Tag: " + ds_keytag + ( "" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0])) env['out'].print_line("Key Flags: KSK") env['out'].print_line("Algorithm: 7 / RSASHA1-NSEC3-SHA1" + ( "" if not ds_looks_valid or ds[1] == '7' else " (Got '%s')" % ds[1])) # see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml env['out'].print_line("Digest Type: 2 / SHA-256") # http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml env['out'].print_line("Digest: " + digests['2']) if ds_looks_valid and ds[3] != digests.get(ds[2]): env['out'].print_line( "(Got digest type %s and digest %s which do not match.)" % (ds[2], ds[3])) env['out'].print_line("Public Key: ") env['out'].print_line(dnsssec_pubkey, monospace=True) env['out'].print_line("") env['out'].print_line("Bulk/Record Format:") env['out'].print_line("" + ds_correct[0]) env['out'].print_line("")