netcmd: Extend 'backup restore' command to handle renamed domains
authorTim Beale <timbeale@catalyst.net.nz>
Sun, 10 Jun 2018 23:18:09 +0000 (11:18 +1200)
committerDouglas Bagnall <dbagnall@samba.org>
Thu, 5 Jul 2018 02:01:25 +0000 (04:01 +0200)
When restoring a renamed domain backup, we need to register the new
realm's DNS zone. We do this in the restore step because we don't know
the new server's IP/hostname in the backup step.

Because we may have removed the old realm's DNS entries in the rename
step, the remove_dc() code may fail to find the expected DNS entries for
the DC's domain (the DCs' dnsHostname still maps to the old DNS realm).
We just needed to adjust remove_dns_references() as it was getting a
slightly different error code.

Signed-off-by: Tim Beale <timbeale@catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/netcmd/domain_backup.py
python/samba/provision/sambadns.py
python/samba/remove_dc.py

index 24407a4952d6937d12f9080961a1c8f3185aab51..931e333b57bb81c9be643c54d0104a94b8599824 100644 (file)
@@ -41,7 +41,10 @@ from samba.remove_dc import remove_dc
 from samba.provision import secretsdb_self_join
 from samba.dbchecker import dbcheck
 import re
-
+from samba.provision import guess_names, determine_host_ip, determine_host_ip6
+from samba.provision.sambadns import (fill_dns_data_partitions,
+                                      get_dnsadmins_sid,
+                                      get_domainguid)
 
 
 # work out a SID (based on a free RID) to use when the domain gets restored.
@@ -235,6 +238,10 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         Option("--backup-file", help="Path to backup file", type=str),
         Option("--targetdir", help="Path to write to", type=str),
         Option("--newservername", help="Name for new server", type=str),
+        Option("--host-ip", type="string", metavar="IPADDRESS",
+               help="set IPv4 ipaddress"),
+        Option("--host-ip6", type="string", metavar="IP6ADDRESS",
+               help="set IPv6 ipaddress"),
     ]
 
     takes_optiongroups = {
@@ -242,8 +249,41 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         "credopts": options.CredentialsOptions,
     }
 
+    def register_dns_zone(self, logger, samdb, lp, ntdsguid, host_ip,
+                          host_ip6):
+        '''
+        Registers the new realm's DNS objects when a renamed domain backup
+        is restored.
+        '''
+        names = guess_names(lp)
+        domaindn = names.domaindn
+        forestdn = samdb.get_root_basedn().get_linearized()
+        dnsdomain = names.dnsdomain.lower()
+        dnsforest = dnsdomain
+        hostname = names.netbiosname.lower()
+        domainsid = dom_sid(samdb.get_domain_sid())
+        dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
+        domainguid = get_domainguid(samdb, domaindn)
+
+        # work out the IP address to use for the new DC's DNS records
+        host_ip = determine_host_ip(logger, lp, host_ip)
+        host_ip6 = determine_host_ip6(logger, lp, host_ip6)
+
+        if host_ip is None and host_ip6 is None:
+            raise CommandError('Please specify a host-ip for the new server')
+
+        logger.info("DNS realm was renamed to %s" % dnsdomain)
+        logger.info("Populating DNS partitions for new realm...")
+
+        # Add the DNS objects for the new realm (note: the backup clone already
+        # has the root server objects, so don't add them again)
+        fill_dns_data_partitions(samdb, domainsid, names.sitename, domaindn,
+                                 forestdn, dnsdomain, dnsforest, hostname,
+                                 host_ip, host_ip6, domainguid, ntdsguid,
+                                 dnsadmins_sid, add_root=False)
+
     def run(self, sambaopts=None, credopts=None, backup_file=None,
-            targetdir=None, newservername=None):
+            targetdir=None, newservername=None, host_ip=None, host_ip6=None):
         if not (backup_file and os.path.exists(backup_file)):
             raise CommandError('Backup file not found.')
         if targetdir is None:
@@ -314,7 +354,8 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         # Get the SID saved by the backup process and create account
         res = samdb.search(base=ldb.Dn(samdb, "@SAMBA_DSDB"),
                            scope=ldb.SCOPE_BASE,
-                           attrs=['sidForRestore'])
+                           attrs=['sidForRestore', 'backupRename'])
+        is_rename = True if 'backupRename' in res[0] else False
         sid = res[0].get('sidForRestore')[0]
         logger.info('Creating account with SID: ' + str(sid))
         ctx.join_add_objects(specified_sid=dom_sid(sid))
@@ -327,6 +368,13 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                                                 "dsServiceName")
         samdb.modify(m)
 
+        # if we renamed the backed-up domain, then we need to add the DNS
+        # objects for the new realm (we do this in the restore, now that we
+        # know the new DC's IP address)
+        if is_rename:
+            self.register_dns_zone(logger, samdb, lp, ctx.ntds_guid,
+                                   host_ip, host_ip6)
+
         secrets_path = os.path.join(private_dir, 'secrets.ldb')
         secrets_ldb = Ldb(secrets_path, session_info=system_session(), lp=lp)
         secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
@@ -376,8 +424,8 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                                                  ldb.FLAG_MOD_REPLACE,
                                                  "repsFrom")
             msg["repsTo"] = ldb.MessageElement([],
-                                                 ldb.FLAG_MOD_REPLACE,
-                                                 "repsTo")
+                                               ldb.FLAG_MOD_REPLACE,
+                                               "repsTo")
             samdb.modify(msg)
 
         # Update the krbtgt passwords twice, ensuring no tickets from
@@ -401,6 +449,9 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                                              "backupDate")
         m["sidForRestore"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
                                                 "sidForRestore")
+        if is_rename:
+            m["backupRename"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
+                                                   "backupRename")
         samdb.modify(m)
 
         logger.info("Backup file successfully restored to %s" % targetdir)
index 63ebff0bb3d1ad5f3b50ac2567bf311c89901d5b..e2b6fcd0c2831157463a1ca567888010b6c06d17 100644 (file)
@@ -1039,7 +1039,7 @@ def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
 def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
                              dnsdomain, dnsforest, hostname, hostip, hostip6,
                              domainguid, ntdsguid, dnsadmins_sid, autofill=True,
-                             fill_level=FILL_FULL):
+                             fill_level=FILL_FULL, add_root=True):
     """Fill data in various AD partitions
 
     :param samdb: LDB object connected to sam.ldb file
@@ -1060,7 +1060,8 @@ def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
 
     ##### Set up DC=DomainDnsZones,<DOMAINDN>
     # Add rootserver records
-    add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
+    if add_root:
+        add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
 
     # Add domain record
     add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
index d1904610e91bf1039fd717d5e81c5387becb4049..e6513ae04fafd7fd004be6b4c7852ad6b555cc39 100644 (file)
@@ -102,7 +102,8 @@ def remove_dns_references(samdb, logger, dnsHostName, ignore_no_name=False):
         (dn, primary_recs) = samdb.dns_lookup(dnsHostName)
     except RuntimeError as e4:
         (enum, estr) = e4.args
-        if enum == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
+        if (enum == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST or
+            enum == werror.WERR_DNS_ERROR_RCODE_NAME_ERROR):
             if ignore_no_name:
                 remove_hanging_dns_references(samdb, logger,
                                               dnsHostNameUpper,