scripting/join.py: Handle creating the dns-NAME account during a DC join
authorAndrew Bartlett <abartlet@samba.org>
Sun, 23 Dec 2012 21:56:50 +0000 (08:56 +1100)
committerStefan Metzmacher <metze@samba.org>
Tue, 29 Jan 2013 21:03:16 +0000 (22:03 +0100)
This will ensure that the DLZ plugin works out of the box when joining a second Samba DC to the
domain.

Andrew Bartlett

Reviewed-by: Stefan Metzmacher <metze@samba.org>
source4/scripting/bin/samba_upgradedns
source4/scripting/python/samba/join.py
source4/scripting/python/samba/provision/sambadns.py
source4/setup/secrets_dns.ldif

index f389ef7f4a7651f652f07d6c90de692ba53644a8..731276df44f2d6363d10eef6a4f40f1529713bcb 100755 (executable)
@@ -450,10 +450,19 @@ if __name__ == '__main__':
                     "DNSNAME" : dnsname }
                            )
 
+            res = ldbs.sam.search(base=domaindn, scope=ldb.SCOPE_DEFAULT,
+                                  expression='(sAMAccountName=dns-%s)' % (hostname),
+                                  attrs=["msDS-KeyVersionNumber"])
+            if "msDS-KeyVersionNumber" in res[0]:
+                dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
+            else:
+                dns_key_version_number = None
+
             secretsdb_setup_dns(ldbs.secrets, names,
                                 paths.private_dir, realm=names.realm,
                                 dnsdomain=names.dnsdomain,
-                                dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
+                                dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
+                                key_version_number=dns_key_version_number)
         else:
             logger.info("dns-%s account already exists" % hostname)
 
index c55c22cad539169e0466d742bd71d283a2358979..b2f4da4790cb5ea026bbf69ee5a98660933b9cb8 100644 (file)
@@ -26,9 +26,12 @@ from samba.ndr import ndr_pack
 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs
 from samba.credentials import Credentials, DONT_USE_KERBEROS
 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
+from samba.provision.common import setup_path
 from samba.schema import Schema
 from samba.net import Net
 from samba.provision.sambadns import setup_bind9_dns
+from samba import read_and_sub_file
+from base64 import b64encode
 import logging
 import talloc
 import random
@@ -179,6 +182,19 @@ class dc_join(object):
                                        attrs=["msDS-krbTgtLink"])
                 if res:
                     ctx.del_noerror(res[0].dn, recursive=True)
+
+                res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+                                       expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' % (ldb.binary_encode("dns-%s" % ctx.myname), ldb.binary_encode("dns/%s" % ctx.dnshostname)),
+                                       attrs=[])
+                if res:
+                    ctx.del_noerror(res[0].dn, recursive=True)
+
+                res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+                                       expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
+                                       attrs=[])
+                if res:
+                    raise RuntimeError("Not removing account %s which looks like a Samba DNS service account but does not have servicePrincipalName=%s" % (ldb.binary_encode("dns-%s" % ctx.myname), ldb.binary_encode("dns/%s" % ctx.dnshostname)))
+
             if ctx.connection_dn is not None:
                 ctx.del_noerror(ctx.connection_dn)
             if ctx.krbtgt_dn is not None:
@@ -579,6 +595,56 @@ class dc_join(object):
                                                          "userAccountControl")
             ctx.samdb.modify(m)
 
+        if ctx.dns_backend.startswith("BIND9_"):
+            ctx.dnspass = samba.generate_random_password(128, 255)
+
+            recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
+                                                                {"DNSDOMAIN": ctx.dnsdomain,
+                                                                 "DOMAINDN": ctx.base_dn,
+                                                                 "HOSTNAME" : ctx.myname,
+                                                                 "DNSPASS_B64": b64encode(ctx.dnspass),
+                                                                 "DNSNAME" : ctx.dnshostname}))
+            for changetype, msg in recs:
+                assert changetype == ldb.CHANGETYPE_NONE
+                print "Adding DNS account %s with dns/ SPN" % msg["dn"]
+
+                # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
+                del msg["clearTextPassword"]
+                # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
+                del msg["isCriticalSystemObject"]
+                try:
+                    ctx.samdb.add(msg)
+                    dns_acct_dn = msg["dn"]
+                except ldb.LdbError, (num, _):
+                    if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
+                        raise
+
+            # The account password set operation should normally be done over
+            # LDAP. Windows 2000 DCs however allow this only with SSL
+            # connections which are hard to set up and otherwise refuse with
+            # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
+            # over SAMR.
+            print "Setting account password for %s" % ctx.samname
+            try:
+                ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
+                                      % ldb.binary_encode(ctx.myname),
+                                      ctx.dnspass,
+                                      force_change_at_next_login=False,
+                                      username=ctx.samname)
+            except ldb.LdbError, (num, _):
+                if num != ldb.ERR_UNWILLING_TO_PERFORM:
+                    pass
+                ctx.net.set_password(account_name="dns-" % ctx.myname,
+                                     domain_name=ctx.domain_name,
+                                     newpassword=ctx.dnspass)
+
+            res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
+                                   attrs=["msDS-KeyVersionNumber"])
+            if "msDS-KeyVersionNumber" in res[0]:
+                ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
+            else:
+                ctx.dns_key_version_number = None
+
     def join_add_objects2(ctx):
         """add the various objects needed for the join, for subdomains post replication"""
 
@@ -861,13 +927,12 @@ class dc_join(object):
                             key_version_number=ctx.key_version_number)
 
         if ctx.dns_backend.startswith("BIND9_"):
-            dnspass = samba.generate_random_password(128, 255)
-
             setup_bind9_dns(ctx.local_samdb, secrets_ldb, security.dom_sid(ctx.domsid),
                             ctx.names, ctx.paths, ctx.lp, logger,
                             dns_backend=ctx.dns_backend,
-                            dnspass=dnspass, os_level=ctx.behavior_version,
-                            targetdir=ctx.targetdir)
+                            dnspass=ctx.dnspass, os_level=ctx.behavior_version,
+                            targetdir=ctx.targetdir,
+                            key_version_number=ctx.dns_key_version_number)
 
     def join_setup_trusts(ctx):
         """provision the local SAM."""
index 740dd384176fc2c0104b0b35fc986c05590dab25..ac56bc9b3451bea086566fb5d8f2e48ce941109b 100644 (file)
@@ -619,7 +619,7 @@ def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
 
 
 def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
-                        dnsdomain, dns_keytab_path, dnspass):
+                        dnsdomain, dns_keytab_path, dnspass, key_version_number):
     """Add DNS specific bits to a secrets database.
 
     :param secretsdb: Ldb Handle to the secrets database
@@ -631,11 +631,15 @@ def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
     except OSError:
         pass
 
+    if key_version_number is None:
+        key_version_number = 1
+
     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
             "REALM": realm,
             "DNSDOMAIN": dnsdomain,
             "DNS_KEYTAB": dns_keytab_path,
             "DNSPASS_B64": b64encode(dnspass),
+            "KEY_VERSION_NUMBER": str(key_version_number),
             "HOSTNAME": names.hostname,
             "DNSNAME" : '%s.%s' % (
                 names.netbiosname.lower(), names.dnsdomain.lower())
@@ -1091,7 +1095,7 @@ def setup_ad_dns(samdb, secretsdb, domainsid, names, paths, lp, logger,
 
 def setup_bind9_dns(samdb, secretsdb, domainsid, names, paths, lp, logger,
         dns_backend, os_level, site=None, dnspass=None, hostip=None,
-        hostip6=None, targetdir=None):
+        hostip6=None, targetdir=None, key_version_number=None):
     """Provision DNS information (assuming BIND9 backend in DC role)
 
     :param samdb: LDB object connected to sam.ldb file
@@ -1124,7 +1128,8 @@ def setup_bind9_dns(samdb, secretsdb, domainsid, names, paths, lp, logger,
     secretsdb_setup_dns(secretsdb, names,
                         paths.private_dir, realm=names.realm,
                         dnsdomain=names.dnsdomain,
-                        dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
+                        dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
+                        key_version_number=key_version_number)
 
     create_dns_dir(logger, paths)
 
index 67fd66b0570671b2df633515459252f47adf0186..192c06d286a2f9f1a5e3608897212673391d61d3 100644 (file)
@@ -5,7 +5,7 @@ objectClass: secret
 objectClass: kerberosSecret
 realm: ${REALM}
 servicePrincipalName: DNS/${DNSNAME}
-msDS-KeyVersionNumber: 1
+msDS-KeyVersionNumber: ${KEY_VERSION_NUMBER}
 privateKeytab: ${DNS_KEYTAB}
 secret:: ${DNSPASS_B64}
 samAccountName: dns-${HOSTNAME}