From 75eb2e3a09ed7ab5beac4593d93e6ea0e506f857 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 17 Feb 2017 18:23:23 +1300 Subject: [PATCH] join.py Add DNS records at domain join time This avoids issues getting replication going after the DC first starts as the rest of the domain does not have to wait for samba_dnsupdate to run successfully We do not just run samba_dnsupdate as we want to strictly operate against the DC we just joined: - We do not want to query another DNS server - We do not want to obtain a Kerberos ticket for the new DC (as the KDC we select may not be the DC we just joined, and so may not be in sync with the password we just set) - We do not wish to set the _ldap records until we have started - We do not wish to use NTLM (the --use-samba-tool mode forces NTLM) The downside to using DCE/RPC rather than DNS is that these will be regarded as static entries, and (against windows) have a the ACL assigned for static entries. However this is still better than no DNS at all. Because some tests want a DNS record matching their own name this fixes some tests and removes entires from knownfail Signed-off-by: Andrew Bartlett Reviewed-by: Garming Sam Autobuild-User(master): Andrew Bartlett Autobuild-Date(master): Sun Jun 11 02:04:52 CEST 2017 on sn-devel-144 --- python/samba/join.py | 200 ++++++++++++++++++++++++++++++- selftest/knownfail.d/dns | 13 +- selftest/knownfail.d/dns-at-join | 2 - 3 files changed, 200 insertions(+), 15 deletions(-) delete mode 100644 selftest/knownfail.d/dns-at-join diff --git a/python/samba/join.py b/python/samba/join.py index a76772a5b0f..fa87f0bb32f 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -22,8 +22,8 @@ from samba.auth import system_session from samba.samdb import SamDB from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array import ldb, samba, sys, uuid -from samba.ndr import ndr_pack -from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs +from samba.ndr import ndr_pack, ndr_unpack +from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp from samba.dsdb import DS_DOMAIN_FUNCTION_2003 from samba.credentials import Credentials, DONT_USE_KERBEROS from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN @@ -35,6 +35,9 @@ from samba.provision.sambadns import setup_bind9_dns from samba import read_and_sub_file from samba import werror from base64 import b64encode +from samba import WERRORError +from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord +from samba import sd_utils import logging import talloc import random @@ -187,6 +190,12 @@ class dc_join(object): ctx.adminpass = None ctx.partition_dn = None + ctx.dns_a_dn = None + ctx.dns_cname_dn = None + + # Do not normally register 127. addresses but allow override for selftest + ctx.force_all_ips = False + def del_noerror(ctx, dn, recursive=False): if recursive: try: @@ -294,6 +303,13 @@ class dc_join(object): lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid) + if ctx.dns_a_dn: + ctx.del_noerror(ctx.dns_a_dn) + + if ctx.dns_cname_dn: + ctx.del_noerror(ctx.dns_cname_dn) + + def promote_possible(ctx): """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted""" @@ -692,12 +708,16 @@ class dc_join(object): newpassword=ctx.acct_pass.encode('utf-8')) res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE, - attrs=["msDS-KeyVersionNumber"]) + attrs=["msDS-KeyVersionNumber", + "objectSID"]) if "msDS-KeyVersionNumber" in res[0]: ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0]) else: ctx.key_version_number = None + ctx.new_dc_account_sid = ndr_unpack(security.dom_sid, + res[0]["objectSid"][0]) + print("Enabling account") m = ldb.Message() m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn) @@ -974,6 +994,175 @@ class dc_join(object): ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r) + def join_add_dns_records(ctx): + """Remotely Add a DNS record to the target DC. We assume that if we + replicate DNS that the server holds the DNS roles and can accept + updates. + + This avoids issues getting replication going after the DC + first starts as the rest of the domain does not have to + wait for samba_dnsupdate to run successfully. + + Specifically, we add the records implied by the DsReplicaUpdateRefs + call above. + + We do not just run samba_dnsupdate as we want to strictly + operate against the DC we just joined: + - We do not want to query another DNS server + - We do not want to obtain a Kerberos ticket + (as the KDC we select may not be the DC we just joined, + and so may not be in sync with the password we just set) + - We do not wish to set the _ldap records until we have started + - We do not wish to use NTLM (the --use-samba-tool mode forces + NTLM) + + """ + + client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN + record_type = dnsp.DNS_TYPE_A + select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\ + dnsserver.DNS_RPC_VIEW_NO_CHILDREN + + zone = ctx.dnsdomain + msdcs_zone = "_msdcs.%s" % ctx.dnsforest + name = ctx.myname + msdcs_cname = str(ctx.ntds_guid) + cname_target = "%s.%s" % (name, zone) + IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips) + + ctx.logger.info("Adding %d remote DNS records for %s.%s" % \ + (len(IPs), name, zone)) + + binding_options = "sign" + dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options), + ctx.lp, ctx.creds) + + + name_found = True + + sd_helper = samba.sd_utils.SDUtils(ctx.samdb) + + change_owner_sd = security.descriptor() + change_owner_sd.owner_sid = ctx.new_dc_account_sid + change_owner_sd.group_sid = security.dom_sid("%s-%d" % + (str(ctx.domsid), + security.DOMAIN_RID_DCS)) + + # TODO: Remove any old records from the primary DNS name + try: + (buflen, res) \ + = dns_conn.DnssrvEnumRecords2(client_version, + 0, + ctx.server, + zone, + name, + None, + dnsp.DNS_TYPE_ALL, + select_flags, + None, + None) + except WERRORError as e: + if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: + name_found = False + pass + + if name_found: + for rec in res.rec: + for record in rec.records: + if record.wType == dnsp.DNS_TYPE_A or \ + record.wType == dnsp.DNS_TYPE_AAAA: + # delete record + del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + del_rec_buf.rec = record + try: + dns_conn.DnssrvUpdateRecord2(client_version, + 0, + ctx.server, + zone, + name, + None, + del_rec_buf) + except WERRORError as e: + if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: + pass + else: + raise + + for IP in IPs: + if IP.find(':') != -1: + ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s" + % (name, zone, IP)) + rec = AAAARecord(IP) + else: + ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s" + % (name, zone, IP)) + rec = ARecord(IP) + + # Add record + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + dns_conn.DnssrvUpdateRecord2(client_version, + 0, + ctx.server, + zone, + name, + add_rec_buf, + None) + + if (len(IPs) > 0): + domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone) + (ctx.dns_a_dn, ldap_record) \ + = ctx.samdb.dns_lookup("%s.%s" % (name, zone), + dns_partition=domaindns_zone_dn) + + # Make the DC own the DNS record, not the administrator + sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd, + controls=["sd_flags:1:%d" + % (security.SECINFO_OWNER + | security.SECINFO_GROUP)]) + + + # Add record + ctx.logger.info("Adding DNS CNAME record %s.%s for %s" + % (msdcs_cname, msdcs_zone, cname_target)) + + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + rec = CNameRecord(cname_target) + add_rec_buf.rec = rec + dns_conn.DnssrvUpdateRecord2(client_version, + 0, + ctx.server, + msdcs_zone, + msdcs_cname, + add_rec_buf, + None) + + forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone) + (ctx.dns_cname_dn, ldap_record) \ + = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone), + dns_partition=forestdns_zone_dn) + + # Make the DC own the DNS record, not the administrator + sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd, + controls=["sd_flags:1:%d" + % (security.SECINFO_OWNER + | security.SECINFO_GROUP)]) + + ctx.logger.info("All other DNS records (like _ldap SRV records) " + + "will be created samba_dnsupdate on first startup") + + + def join_replicate_new_dns_records(ctx): + for nc in (ctx.domaindns_zone, ctx.forestdns_zone): + if nc in ctx.nc_list: + ctx.logger.info("Replicating new DNS records in %s" % (str(nc))) + ctx.repl.replicate(nc, ctx.source_dsa_invocation_id, + ctx.ntds_guid, rodc=ctx.RODC, + replica_flags=ctx.replica_flags, + full_sync=False) + + + def join_finalise(ctx): """Finalise the join, mark us synchronised and setup secrets db.""" @@ -1190,6 +1379,11 @@ class dc_join(object): ctx.join_add_objects2() ctx.join_provision_own_domain() ctx.join_setup_trusts() + + if not ctx.clone_only and ctx.dns_backend != "NONE": + ctx.join_add_dns_records() + ctx.join_replicate_new_dns_records() + ctx.join_finalise() except: try: diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns index c40041d1892..cb3003240ea 100644 --- a/selftest/knownfail.d/dns +++ b/selftest/knownfail.d/dns @@ -36,19 +36,12 @@ samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_txt_rpc_to_dns\(rodc:l samba.tests.dns.__main__.TestZones.test_soa_query\(rodc:local\) samba.tests.dns.__main__.TestComplexQueries.test_cname_two_chain\(vampire_dc:local\) samba.tests.dns.__main__.TestComplexQueries.test_one_a_query\(vampire_dc:local\) - -# The SOA override should not pass against the RODC, it must not overstamp -samba.tests.dns.__main__.TestSimpleQueries.test_one_SOA_query\(rodc:local\) - -# The very first DC will have DNS records, but subsequent DCs only get entries into -# the dns_hosts_file in our selftest env samba.tests.dns.__main__.TestSimpleQueries.test_one_a_query\(vampire_dc:local\) samba.tests.dns.__main__.TestSimpleQueries.test_one_a_query_tcp\(vampire_dc:local\) -samba.tests.dns.__main__.TestSimpleQueries.test_one_mx_query\(vampire_dc:local\) samba.tests.dns.__main__.TestSimpleQueries.test_qtype_all_query\(vampire_dc:local\) -samba.tests.dns.__main__.TestSimpleQueries.test_soa_hostname_query\(vampire_dc:local\) samba.tests.dns.__main__.TestSimpleQueries.test_one_a_query\(rodc:local\) samba.tests.dns.__main__.TestSimpleQueries.test_one_a_query_tcp\(rodc:local\) -samba.tests.dns.__main__.TestSimpleQueries.test_one_mx_query\(rodc:local\) samba.tests.dns.__main__.TestSimpleQueries.test_qtype_all_query\(rodc:local\) -samba.tests.dns.__main__.TestSimpleQueries.test_soa_hostname_query\(rodc:local\) + +# The SOA override should not pass against the RODC, it must not overstamp +samba.tests.dns.__main__.TestSimpleQueries.test_one_SOA_query\(rodc:local\) diff --git a/selftest/knownfail.d/dns-at-join b/selftest/knownfail.d/dns-at-join deleted file mode 100644 index 3737f1fb239..00000000000 --- a/selftest/knownfail.d/dns-at-join +++ /dev/null @@ -1,2 +0,0 @@ -samba.tests.join.python\(ad_dc_ntvfs\).samba.tests.join.JoinTestCase.test_join_makes_records\(ad_dc_ntvfs\) -samba.tests.join.python\(ad_dc_ntvfs\).samba.tests.join.JoinTestCase.test_join_records_can_update\(ad_dc_ntvfs\) -- 2.34.1