1
0
mirror of https://github.com/samba-team/samba.git synced 2025-07-28 11:42:03 +03:00

s4-provision Add initial support for joining as a new subdomain

To do this we need to reorganise a lot of the provision code, so that
we can create the framework for the inbound replicaton of the config
and schema partitions and then add in the new subdomain locally.

Andrew Bartlett
This commit is contained in:
Andrew Bartlett
2011-08-24 15:39:51 +10:00
committed by Andrew Tridgell
parent 4e058475fb
commit aa960d8fa7
2 changed files with 141 additions and 120 deletions

View File

@ -304,40 +304,47 @@ class dc_join(object):
r.value_ctr = 1 r.value_ctr = 1
def DsAddEntry(ctx, rec): def DsAddEntry(ctx, recs):
'''add a record via the DRSUAPI DsAddEntry call''' '''add a record via the DRSUAPI DsAddEntry call'''
if ctx.drsuapi is None: if ctx.drsuapi is None:
ctx.drsuapi_connect() ctx.drsuapi_connect()
if ctx.tmp_samdb is None: if ctx.tmp_samdb is None:
ctx.create_tmp_samdb() ctx.create_tmp_samdb()
id = drsuapi.DsReplicaObjectIdentifier() objects = []
id.dn = rec['dn'] for rec in recs:
id = drsuapi.DsReplicaObjectIdentifier()
id.dn = rec['dn']
attrs = [] attrs = []
for a in rec: for a in rec:
if a == 'dn': if a == 'dn':
continue continue
if not isinstance(rec[a], list): if not isinstance(rec[a], list):
v = [rec[a]] v = [rec[a]]
else: else:
v = rec[a] v = rec[a]
rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v) rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
attrs.append(rattr) attrs.append(rattr)
attribute_ctr = drsuapi.DsReplicaAttributeCtr() attribute_ctr = drsuapi.DsReplicaAttributeCtr()
attribute_ctr.num_attributes = len(attrs) attribute_ctr.num_attributes = len(attrs)
attribute_ctr.attributes = attrs attribute_ctr.attributes = attrs
object = drsuapi.DsReplicaObject() object = drsuapi.DsReplicaObject()
object.identifier = id object.identifier = id
object.attribute_ctr = attribute_ctr object.attribute_ctr = attribute_ctr
first_object = drsuapi.DsReplicaObjectListItem() list_object = drsuapi.DsReplicaObjectListItem()
first_object.object = object list_object.object = object
objects.append(list_object)
req2 = drsuapi.DsAddEntryRequest2() req2 = drsuapi.DsAddEntryRequest2()
req2.first_object = first_object req2.first_object = objects[0]
prev = req2.first_object
for o in objects[1:]:
prev.next_object = o
prev = o
(level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2) (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
if ctr.err_ver != 1: if ctr.err_ver != 1:
@ -349,6 +356,7 @@ class dc_join(object):
if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK: if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err) print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
raise RuntimeError("DsAddEntry failed") raise RuntimeError("DsAddEntry failed")
return ctr.objects
def join_add_ntdsdsa(ctx): def join_add_ntdsdsa(ctx):
@ -361,17 +369,12 @@ class dc_join(object):
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE), "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
"dMDLocation" : ctx.schema_dn} "dMDLocation" : ctx.schema_dn}
if ctx.subdomain: nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
# the local subdomain NC doesn't exist at this time
# so we have to add the base_dn NC later
nc_list = [ ctx.config_dn, ctx.schema_dn ]
else:
nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003: if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec["msDS-Behavior-Version"] = str(ctx.behavior_version) rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003 and not ctx.subdomain: if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec["msDS-HasDomainNCs"] = ctx.base_dn rec["msDS-HasDomainNCs"] = ctx.base_dn
if ctx.RODC: if ctx.RODC:
@ -386,32 +389,13 @@ class dc_join(object):
rec["msDS-HasMasterNCs"] = nc_list rec["msDS-HasMasterNCs"] = nc_list
rec["options"] = "1" rec["options"] = "1"
rec["invocationId"] = ndr_pack(ctx.invocation_id) rec["invocationId"] = ndr_pack(ctx.invocation_id)
if ctx.subdomain: ctx.DsAddEntry([rec])
ctx.samdb.add(rec, ['relax:0'])
else:
ctx.DsAddEntry(rec)
# find the GUID of our NTDS DN # find the GUID of our NTDS DN
res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"]) res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0])) ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
def join_modify_ntdsdsa(ctx):
'''modify the ntdsdsa object to add local partitions'''
print "Modifying %s using system privileges" % ctx.ntds_dn
# this works around the Enterprise Admins ACL on the NTDSDSA object
system_session_info = system_session()
ctx.samdb.set_session_info(system_session_info)
m = ldb.Message()
m.dn = ldb.Dn(ctx.samdb, ctx.ntds_dn)
m["HasMasterNCs"] = ldb.MessageElement(ctx.base_dn, ldb.FLAG_MOD_ADD, "HasMasterNCs")
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
m["msDS-HasDomainNCs"] = ldb.MessageElement(ctx.base_dn, ldb.FLAG_MOD_ADD, "msDS-HasDomainNCs")
m["msDS-HasMasterNCs"] = ldb.MessageElement(ctx.base_dn, ldb.FLAG_MOD_ADD, "msDS-HasMasterNCs")
ctx.samdb.modify(m, controls=['relax:0'])
def join_add_objects(ctx): def join_add_objects(ctx):
'''add the various objects needed for the join''' '''add the various objects needed for the join'''
if ctx.acct_dn: if ctx.acct_dn:
@ -440,9 +424,11 @@ class dc_join(object):
rec = { rec = {
"dn": ctx.server_dn, "dn": ctx.server_dn,
"objectclass" : "server", "objectclass" : "server",
# windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME | "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE | samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE), samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
# windows seems to add the dnsHostName later
"dnsHostName" : ctx.dnshostname} "dnsHostName" : ctx.dnshostname}
if ctx.acct_dn: if ctx.acct_dn:
@ -507,9 +493,6 @@ class dc_join(object):
def join_add_objects2(ctx): def join_add_objects2(ctx):
'''add the various objects needed for the join, for subdomains post replication''' '''add the various objects needed for the join, for subdomains post replication'''
if not ctx.subdomain:
return
print "Adding %s" % ctx.partition_dn print "Adding %s" % ctx.partition_dn
# NOTE: windows sends a ntSecurityDescriptor here, we # NOTE: windows sends a ntSecurityDescriptor here, we
# let it default # let it default
@ -524,8 +507,47 @@ class dc_join(object):
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN)} "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN)}
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003: if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec["msDS-Behavior-Version"] = str(ctx.behavior_version) rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
ctx.DsAddEntry(rec)
rec2 = {
"dn" : ctx.ntds_dn,
"objectclass" : "nTDSDSA",
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
"dMDLocation" : ctx.schema_dn}
nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec2["msDS-Behavior-Version"] = str(ctx.behavior_version)
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec2["msDS-HasDomainNCs"] = ctx.base_dn
rec2["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
rec2["HasMasterNCs"] = nc_list
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec2["msDS-HasMasterNCs"] = nc_list
rec2["options"] = "1"
rec2["invocationId"] = ndr_pack(ctx.invocation_id)
objects = ctx.DsAddEntry([rec, rec2])
if len(objects) != 2:
raise DCJoinException("Expected 2 objects from DsAddEntry")
ctx.ntds_guid = objects[1].guid
print("Replicating partition DN")
ctx.repl.replicate(ctx.partition_dn,
misc.GUID("00000000-0000-0000-0000-000000000000"),
ctx.ntds_guid,
exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
print("Replicating NTDS DN")
ctx.repl.replicate(ctx.ntds_dn,
misc.GUID("00000000-0000-0000-0000-000000000000"),
ctx.ntds_guid,
exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
def join_provision(ctx): def join_provision(ctx):
'''provision the local SAM''' '''provision the local SAM'''
@ -565,14 +587,11 @@ class dc_join(object):
ctx.local_samdb = ctx.samdb ctx.local_samdb = ctx.samdb
print("Finding domain GUID from ncName") print("Finding domain GUID from ncName")
res = ctx.samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'], res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
controls=["extended_dn:1:1"]) controls=["extended_dn:1:1"])
domguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID'))) domguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
print("Got domain GUID %s" % domguid) print("Got domain GUID %s" % domguid)
ctx.join_add_ntdsdsa()
print("Calling own domain provision") print("Calling own domain provision")
logger = logging.getLogger("provision") logger = logging.getLogger("provision")
@ -597,6 +616,7 @@ class dc_join(object):
try: try:
source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id()) source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
if ctx.ntds_guid is None: if ctx.ntds_guid is None:
print("Using DS_BIND_GUID_W2K3")
destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3) destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
else: else:
destination_dsa_guid = ctx.ntds_guid destination_dsa_guid = ctx.ntds_guid
@ -634,6 +654,9 @@ class dc_join(object):
repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id, repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
destination_dsa_guid, destination_dsa_guid,
exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
ctx.repl = repl
ctx.source_dsa_invocation_id = source_dsa_invocation_id
ctx.destination_dsa_guid = destination_dsa_guid
print "Committing SAM database" print "Committing SAM database"
except: except:
@ -787,12 +810,11 @@ class dc_join(object):
try: try:
ctx.join_add_objects() ctx.join_add_objects()
ctx.join_provision() ctx.join_provision()
ctx.join_add_objects2()
ctx.join_replicate() ctx.join_replicate()
if ctx.subdomain: if ctx.subdomain:
ctx.join_add_objects2()
ctx.join_provision_own_domain() ctx.join_provision_own_domain()
ctx.join_setup_trusts() ctx.join_setup_trusts()
ctx.join_modify_ntdsdsa()
ctx.join_finalise() ctx.join_finalise()
except Exception: except Exception:
print "Join failed - cleaning up" print "Join failed - cleaning up"

View File

@ -995,11 +995,11 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
"LDAPADMINREALM": backend_credentials.get_realm(), "LDAPADMINREALM": backend_credentials.get_realm(),
"LDAPADMINPASS_B64": b64encode(backend_credentials.get_password()) "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
}) })
return secrets_ldb
except Exception: except Exception:
secrets_ldb.transaction_cancel() secrets_ldb.transaction_cancel()
raise raise
return secrets_ldb
def setup_privileges(path, session_info, lp): def setup_privileges(path, session_info, lp):
@ -1597,10 +1597,6 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid, setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
domainsid, names.dnsdomain, names.domaindn, lp) domainsid, names.dnsdomain, names.domaindn, lp)
logger.info("Setting up sam.ldb rootDSE marking as synchronized")
setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
{ 'NTDSGUID' : names.ntdsguid })
secretsdb_self_join(secrets_ldb, domain=names.domain, secretsdb_self_join(secrets_ldb, domain=names.domain,
realm=names.realm, dnsdomain=names.dnsdomain, realm=names.realm, dnsdomain=names.dnsdomain,
netbiosname=names.netbiosname, domainsid=domainsid, netbiosname=names.netbiosname, domainsid=domainsid,
@ -1624,69 +1620,72 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
# It might be that this attribute does not exist in this schema # It might be that this attribute does not exist in this schema
raise raise
if serverrole == "domain controller": secretsdb_setup_dns(secrets_ldb, names,
secretsdb_setup_dns(secrets_ldb, names, paths.private_dir, realm=names.realm,
paths.private_dir, realm=names.realm, dnsdomain=names.dnsdomain,
dnsdomain=names.dnsdomain, dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
# Default DNS backend is BIND9 using txt files for zone information # Default DNS backend is BIND9 using txt files for zone information
if not dns_backend: if not dns_backend:
dns_backend = "BIND9" dns_backend = "BIND9"
setup_ad_dns(samdb, names, logger, hostip=hostip, hostip6=hostip6, setup_ad_dns(samdb, names, logger, hostip=hostip, hostip6=hostip6,
dns_backend=dns_backend, os_level=dom_for_fun_level) dns_backend=dns_backend, os_level=dom_for_fun_level)
domainguid = samdb.searchone(basedn=samdb.get_default_basedn(), domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
attribute="objectGUID") attribute="objectGUID")
assert isinstance(domainguid, str) assert isinstance(domainguid, str)
create_dns_dir(logger, paths) create_dns_dir(logger, paths)
# Only make a zone file on the first DC, it should be # Only make a zone file on the first DC, it should be
# replicated with DNS replication # replicated with DNS replication
if dns_backend == "BIND9": if dns_backend == "BIND9":
create_zone_file(lp, logger, paths, targetdir, create_zone_file(lp, logger, paths, targetdir,
dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6, dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
hostname=names.hostname, realm=names.realm, hostname=names.hostname, realm=names.realm,
domainguid=domainguid, ntdsguid=names.ntdsguid) domainguid=domainguid, ntdsguid=names.ntdsguid)
create_named_conf(paths, realm=names.realm, create_named_conf(paths, realm=names.realm,
dnsdomain=names.dnsdomain, dns_backend=dns_backend) dnsdomain=names.dnsdomain, dns_backend=dns_backend)
create_named_txt(paths.namedtxt, create_named_txt(paths.namedtxt,
realm=names.realm, dnsdomain=names.dnsdomain, realm=names.realm, dnsdomain=names.dnsdomain,
dnsname = "%s.%s" % (names.hostname, names.dnsdomain), dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
private_dir=paths.private_dir, private_dir=paths.private_dir,
keytab_name=paths.dns_keytab) keytab_name=paths.dns_keytab)
logger.info("See %s for an example configuration include file for BIND", paths.namedconf) logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
logger.info("and %s for further documentation required for secure DNS " logger.info("and %s for further documentation required for secure DNS "
"updates", paths.namedtxt) "updates", paths.namedtxt)
lastProvisionUSNs = get_last_provision_usn(samdb) lastProvisionUSNs = get_last_provision_usn(samdb)
maxUSN = get_max_usn(samdb, str(names.rootdn)) maxUSN = get_max_usn(samdb, str(names.rootdn))
if lastProvisionUSNs is not None: if lastProvisionUSNs is not None:
update_provision_usn(samdb, 0, maxUSN, invocationid, 1) update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
else: else:
set_provision_usn(samdb, 0, maxUSN, invocationid) set_provision_usn(samdb, 0, maxUSN, invocationid)
# fix any dangling GUIDs from the provision logger.info("Setting up sam.ldb rootDSE marking as synchronized")
logger.info("Fixing provision GUIDs") setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True, quiet=True) { 'NTDSGUID' : names.ntdsguid })
samdb.transaction_start()
# a small number of GUIDs are missing because of ordering issues in the # fix any dangling GUIDs from the provision
# provision code logger.info("Fixing provision GUIDs")
for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']: chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True, quiet=True)
chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn), samdb.transaction_start()
scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory']) # a small number of GUIDs are missing because of ordering issues in the
chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn, # provision code
scope=ldb.SCOPE_ONELEVEL, for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
attrs=['ipsecOwnersReference', chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
'ipsecFilterReference', scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
'ipsecISAKMPReference', chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
'ipsecNegotiationPolicyReference', scope=ldb.SCOPE_ONELEVEL,
'ipsecNFAReference']) attrs=['ipsecOwnersReference',
samdb.transaction_commit() 'ipsecFilterReference',
'ipsecISAKMPReference',
'ipsecNegotiationPolicyReference',
'ipsecNFAReference'])
samdb.transaction_commit()
def provision(logger, session_info, credentials, smbconf=None, def provision(logger, session_info, credentials, smbconf=None,