mirror of
https://github.com/samba-team/samba.git
synced 2025-02-28 01:58:17 +03:00
samba-tool: Add new command 'samba-tool drs clone-dc-database'
This command makes a clone of an existing AD Domain, but does not join the domain. This allows us to test if the join would work without adding objects to the target DC. The server password will need to be reset for the clone to be any use, see the source4/scripting/devel/chgtdcpass (Based on patches written with Garming Sam) Andrew Bartlett Signed-off-by: Andrew Bartlett <abartlet@samba.org> Signed-off-by: Garming Sam <garming@catalyst.net.nz> Reviewed-by: Garming Sam <garming@catalyst.net.nz>
This commit is contained in:
parent
80171ddcff
commit
6d301ad1c9
@ -54,12 +54,13 @@ class dc_join(object):
|
||||
def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
|
||||
netbios_name=None, targetdir=None, domain=None,
|
||||
machinepass=None, use_ntvfs=False, dns_backend=None,
|
||||
promote_existing=False):
|
||||
promote_existing=False, clone_only=False):
|
||||
ctx.clone_only=clone_only
|
||||
|
||||
ctx.logger = logger
|
||||
ctx.creds = creds
|
||||
ctx.lp = lp
|
||||
ctx.site = site
|
||||
ctx.netbios_name = netbios_name
|
||||
ctx.targetdir = targetdir
|
||||
ctx.use_ntvfs = use_ntvfs
|
||||
|
||||
@ -89,8 +90,6 @@ class dc_join(object):
|
||||
raise DCJoinException(estr)
|
||||
|
||||
|
||||
ctx.myname = netbios_name
|
||||
ctx.samname = "%s$" % ctx.myname
|
||||
ctx.base_dn = str(ctx.samdb.get_default_basedn())
|
||||
ctx.root_dn = str(ctx.samdb.get_root_basedn())
|
||||
ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
|
||||
@ -110,17 +109,34 @@ class dc_join(object):
|
||||
else:
|
||||
ctx.acct_pass = samba.generate_random_password(32, 40)
|
||||
|
||||
# work out the DNs of all the objects we will be adding
|
||||
ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
|
||||
ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
|
||||
topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
|
||||
if ctx.dn_exists(topology_base):
|
||||
ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
|
||||
else:
|
||||
ctx.topology_dn = None
|
||||
|
||||
ctx.dnsdomain = ctx.samdb.domain_dns_name()
|
||||
ctx.dnsforest = ctx.samdb.forest_dns_name()
|
||||
if clone_only:
|
||||
# As we don't want to create or delete these DNs, we set them to None
|
||||
ctx.server_dn = None
|
||||
ctx.ntds_dn = None
|
||||
ctx.acct_dn = None
|
||||
ctx.myname = ctx.server.split('.')[0]
|
||||
ctx.ntds_guid = None
|
||||
else:
|
||||
# work out the DNs of all the objects we will be adding
|
||||
ctx.myname = netbios_name
|
||||
ctx.samname = "%s$" % ctx.myname
|
||||
ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
|
||||
ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
|
||||
ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
|
||||
ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
|
||||
ctx.dnsforest = ctx.samdb.forest_dns_name()
|
||||
|
||||
topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
|
||||
if ctx.dn_exists(topology_base):
|
||||
ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
|
||||
else:
|
||||
ctx.topology_dn = None
|
||||
|
||||
ctx.SPNs = [ "HOST/%s" % ctx.myname,
|
||||
"HOST/%s" % ctx.dnshostname,
|
||||
"GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
|
||||
|
||||
ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
|
||||
ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
|
||||
|
||||
@ -137,18 +153,10 @@ class dc_join(object):
|
||||
else:
|
||||
ctx.dns_backend = dns_backend
|
||||
|
||||
ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
|
||||
|
||||
ctx.realm = ctx.dnsdomain
|
||||
|
||||
ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
|
||||
|
||||
ctx.tmp_samdb = None
|
||||
|
||||
ctx.SPNs = [ "HOST/%s" % ctx.myname,
|
||||
"HOST/%s" % ctx.dnshostname,
|
||||
"GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
|
||||
|
||||
# these elements are optional
|
||||
ctx.never_reveal_sid = None
|
||||
ctx.reveal_sid = None
|
||||
@ -538,28 +546,30 @@ class dc_join(object):
|
||||
if ctx.krbtgt_dn:
|
||||
ctx.add_krbtgt_account()
|
||||
|
||||
print "Adding %s" % ctx.server_dn
|
||||
rec = {
|
||||
"dn": ctx.server_dn,
|
||||
"objectclass" : "server",
|
||||
# windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
|
||||
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
|
||||
samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
|
||||
samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
|
||||
# windows seems to add the dnsHostName later
|
||||
"dnsHostName" : ctx.dnshostname}
|
||||
if ctx.server_dn:
|
||||
print "Adding %s" % ctx.server_dn
|
||||
rec = {
|
||||
"dn": ctx.server_dn,
|
||||
"objectclass" : "server",
|
||||
# windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
|
||||
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
|
||||
samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
|
||||
samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
|
||||
# windows seems to add the dnsHostName later
|
||||
"dnsHostName" : ctx.dnshostname}
|
||||
|
||||
if ctx.acct_dn:
|
||||
rec["serverReference"] = ctx.acct_dn
|
||||
if ctx.acct_dn:
|
||||
rec["serverReference"] = ctx.acct_dn
|
||||
|
||||
ctx.samdb.add(rec)
|
||||
ctx.samdb.add(rec)
|
||||
|
||||
if ctx.subdomain:
|
||||
# the rest is done after replication
|
||||
ctx.ntds_guid = None
|
||||
return
|
||||
|
||||
ctx.join_add_ntdsdsa()
|
||||
if ctx.ntds_dn:
|
||||
ctx.join_add_ntdsdsa()
|
||||
|
||||
if ctx.connection_dn is not None:
|
||||
print "Adding %s" % ctx.connection_dn
|
||||
@ -876,15 +886,17 @@ class dc_join(object):
|
||||
"""Finalise the join, mark us synchronised and setup secrets db."""
|
||||
|
||||
# FIXME we shouldn't do this in all cases
|
||||
|
||||
# If for some reasons we joined in another site than the one of
|
||||
# DC we just replicated from then we don't need to send the updatereplicateref
|
||||
# as replication between sites is time based and on the initiative of the
|
||||
# requesting DC
|
||||
ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
|
||||
for nc in ctx.nc_list:
|
||||
ctx.send_DsReplicaUpdateRefs(nc)
|
||||
if not ctx.clone_only:
|
||||
ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
|
||||
for nc in ctx.nc_list:
|
||||
ctx.send_DsReplicaUpdateRefs(nc)
|
||||
|
||||
if ctx.RODC:
|
||||
if not ctx.clone_only and ctx.RODC:
|
||||
print "Setting RODC invocationId"
|
||||
ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
|
||||
ctx.local_samdb.set_opaque_integer("domainFunctionality",
|
||||
@ -914,11 +926,18 @@ class dc_join(object):
|
||||
m = ldb.Message()
|
||||
m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
|
||||
m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
|
||||
m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(ctx.ntds_guid),
|
||||
|
||||
# We want to appear to be the server we just cloned
|
||||
if ctx.clone_only:
|
||||
guid = ctx.samdb.get_ntds_GUID()
|
||||
else:
|
||||
guid = ctx.ntds_guid
|
||||
|
||||
m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
|
||||
ldb.FLAG_MOD_REPLACE, "dsServiceName")
|
||||
ctx.local_samdb.modify(m)
|
||||
|
||||
if ctx.subdomain:
|
||||
if ctx.clone_only or ctx.subdomain:
|
||||
return
|
||||
|
||||
secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
|
||||
@ -1077,23 +1096,26 @@ class dc_join(object):
|
||||
ctx.full_nc_list += [ctx.domaindns_zone]
|
||||
ctx.full_nc_list += [ctx.forestdns_zone]
|
||||
|
||||
if ctx.promote_existing:
|
||||
ctx.promote_possible()
|
||||
else:
|
||||
ctx.cleanup_old_join()
|
||||
if not ctx.clone_only:
|
||||
if ctx.promote_existing:
|
||||
ctx.promote_possible()
|
||||
else:
|
||||
ctx.cleanup_old_join()
|
||||
|
||||
try:
|
||||
ctx.join_add_objects()
|
||||
if not ctx.clone_only:
|
||||
ctx.join_add_objects()
|
||||
ctx.join_provision()
|
||||
ctx.join_replicate()
|
||||
if ctx.subdomain:
|
||||
if (not ctx.clone_only and ctx.subdomain):
|
||||
ctx.join_add_objects2()
|
||||
ctx.join_provision_own_domain()
|
||||
ctx.join_setup_trusts()
|
||||
ctx.join_finalise()
|
||||
except:
|
||||
print "Join failed - cleaning up"
|
||||
ctx.cleanup_old_join()
|
||||
if not ctx.clone_only:
|
||||
ctx.cleanup_old_join()
|
||||
raise
|
||||
|
||||
|
||||
@ -1183,6 +1205,28 @@ def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_na
|
||||
ctx.do_join()
|
||||
logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
|
||||
|
||||
def join_clone(logger=None, server=None, creds=None, lp=None,
|
||||
targetdir=None, domain=None):
|
||||
"""Join as a DC."""
|
||||
ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
|
||||
machinepass=None, use_ntvfs=False, dns_backend="NONE", promote_existing=False, clone_only=True)
|
||||
|
||||
lp.set("workgroup", ctx.domain_name)
|
||||
logger.info("workgroup is %s" % ctx.domain_name)
|
||||
|
||||
lp.set("realm", ctx.realm)
|
||||
logger.info("realm is %s" % ctx.realm)
|
||||
|
||||
ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
|
||||
drsuapi.DRSUAPI_DRS_INIT_SYNC |
|
||||
drsuapi.DRSUAPI_DRS_PER_SYNC |
|
||||
drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
|
||||
drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
|
||||
ctx.domain_replica_flags = ctx.replica_flags
|
||||
|
||||
ctx.do_join()
|
||||
logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
|
||||
|
||||
def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
|
||||
netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
|
||||
netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
import samba.getopt as options
|
||||
import ldb
|
||||
import logging
|
||||
|
||||
from samba.auth import system_session
|
||||
from samba.netcmd import (
|
||||
@ -32,6 +33,7 @@ from samba.samdb import SamDB
|
||||
from samba import drs_utils, nttime2string, dsdb
|
||||
from samba.dcerpc import drsuapi, misc
|
||||
import common
|
||||
from samba.join import join_clone
|
||||
|
||||
def drsuapi_connect(ctx):
|
||||
'''make a DRSUAPI connection to the server'''
|
||||
@ -513,6 +515,44 @@ class cmd_drs_options(Command):
|
||||
self.message("New DSA options: " + ", ".join(cur_opts))
|
||||
|
||||
|
||||
class cmd_drs_clone_dc_database(Command):
|
||||
"""Replicate an initial clone of domain, but DO NOT JOIN it."""
|
||||
|
||||
synopsis = "%prog <dnsdomain> [options]"
|
||||
|
||||
takes_optiongroups = {
|
||||
"sambaopts": options.SambaOptions,
|
||||
"versionopts": options.VersionOptions,
|
||||
"credopts": options.CredentialsOptions,
|
||||
}
|
||||
|
||||
takes_options = [
|
||||
Option("--server", help="DC to join", type=str),
|
||||
Option("--targetdir", help="where to store provision", type=str),
|
||||
Option("--quiet", help="Be quiet", action="store_true"),
|
||||
Option("--verbose", help="Be verbose", action="store_true")
|
||||
]
|
||||
|
||||
takes_args = ["domain"]
|
||||
|
||||
def run(self, domain, sambaopts=None, credopts=None,
|
||||
versionopts=None, server=None, targetdir=None,
|
||||
quiet=False, verbose=False):
|
||||
lp = sambaopts.get_loadparm()
|
||||
creds = credopts.get_credentials(lp)
|
||||
|
||||
logger = self.get_logger()
|
||||
if verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
elif quiet:
|
||||
logger.setLevel(logging.WARNING)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
join_clone(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
|
||||
targetdir=targetdir)
|
||||
|
||||
|
||||
class cmd_drs(SuperCommand):
|
||||
"""Directory Replication Services (DRS) management."""
|
||||
|
||||
@ -522,3 +562,4 @@ class cmd_drs(SuperCommand):
|
||||
subcommands["replicate"] = cmd_drs_replicate()
|
||||
subcommands["showrepl"] = cmd_drs_showrepl()
|
||||
subcommands["options"] = cmd_drs_options()
|
||||
subcommands["clone-dc-database"] = cmd_drs_clone_dc_database()
|
||||
|
@ -253,7 +253,7 @@ class BlackboxProcessError(Exception):
|
||||
return "Command '%s'; exit status %d; stdout: '%s'; stderr: '%s'" % (self.cmd, self.returncode,
|
||||
self.stdout, self.stderr)
|
||||
|
||||
class BlackboxTestCase(TestCase):
|
||||
class BlackboxTestCase(TestCaseInTempDir):
|
||||
"""Base test case for blackbox tests."""
|
||||
|
||||
def _make_cmdline(self, line):
|
||||
|
@ -18,7 +18,8 @@
|
||||
"""Blackbox tests for samba-tool drs."""
|
||||
|
||||
import samba.tests
|
||||
|
||||
import shutil
|
||||
import os
|
||||
|
||||
class SambaToolDrsTests(samba.tests.BlackboxTestCase):
|
||||
"""Blackbox test case for samba-tool drs."""
|
||||
@ -33,10 +34,10 @@ class SambaToolDrsTests(samba.tests.BlackboxTestCase):
|
||||
self.cmdline_creds = "-U%s/%s%%%s" % (creds.get_domain(),
|
||||
creds.get_username(), creds.get_password())
|
||||
|
||||
def _get_rootDSE(self, dc):
|
||||
def _get_rootDSE(self, dc, ldap_only=True):
|
||||
samdb = samba.tests.connect_samdb(dc, lp=self.get_loadparm(),
|
||||
credentials=self.get_credentials(),
|
||||
ldap_only=True)
|
||||
ldap_only=ldap_only)
|
||||
return samdb.search(base="", scope=samba.tests.ldb.SCOPE_BASE)[0]
|
||||
|
||||
def test_samba_tool_bind(self):
|
||||
@ -100,3 +101,30 @@ class SambaToolDrsTests(samba.tests.BlackboxTestCase):
|
||||
self.cmdline_creds))
|
||||
self.assertTrue("Replicate from" in out)
|
||||
self.assertTrue("was successful" in out)
|
||||
|
||||
def test_samba_tool_drs_clone_dc(self):
|
||||
"""Tests 'samba-tool drs clone-dc-database' command."""
|
||||
server_rootdse = self._get_rootDSE(self.dc1)
|
||||
server_nc_name = server_rootdse["defaultNamingContext"]
|
||||
server_ds_name = server_rootdse["dsServiceName"]
|
||||
server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
|
||||
server_realm = server_ldap_service_name.split(":")[0]
|
||||
creds = self.get_credentials()
|
||||
out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s --targetdir=%s"
|
||||
% (server_realm,
|
||||
self.dc1,
|
||||
self.cmdline_creds,
|
||||
self.tempdir))
|
||||
ldb_rootdse = self._get_rootDSE("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), ldap_only=False)
|
||||
nc_name = ldb_rootdse["defaultNamingContext"]
|
||||
ds_name = ldb_rootdse["dsServiceName"]
|
||||
ldap_service_name = str(server_rootdse["ldapServiceName"][0])
|
||||
self.assertEqual(nc_name, server_nc_name)
|
||||
# The clone should pretend to be the source server
|
||||
self.assertEqual(ds_name, server_ds_name)
|
||||
self.assertEqual(ldap_service_name, server_ldap_service_name)
|
||||
shutil.rmtree(os.path.join(self.tempdir, "private"))
|
||||
shutil.rmtree(os.path.join(self.tempdir, "etc"))
|
||||
shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
|
||||
os.remove(os.path.join(self.tempdir, "names.tdb"))
|
||||
shutil.rmtree(os.path.join(self.tempdir, "state"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user