1

I am trying to use python DNS module (dnspython) to create (add) new DNS record.

Documentation specifies how to create update http://www.dnspython.org/examples.html :

import dns.tsigkeyring
import dns.update
import sys

keyring = dns.tsigkeyring.from_text({
    'host-example.' : 'XXXXXXXXXXXXXXXXXXXXXX=='
})

update = dns.update.Update('dyn.test.example', keyring=keyring)
update.replace('host', 300, 'a', sys.argv[1])

But it does not precise, how to actually generate keyring string that can be passed to dns.tsigkeyring.from_text() method in the first place.

What is the correct way to generate the key? I am using krb5 at my organization.

Majus Misiak
  • 643
  • 7
  • 14
  • 1
    Which nameserver do you use? `bind` ships with a `dnssec-keygen` command that you can use to generate TSIG keys – Patrick Mevzek Apr 29 '19 at 16:05
  • @PatrickMevzek Server is running on Microsoft AD DNS with GSS-TSIG. I have double-checked and it seems that `dnspython` does not support GSS-TSIG and is not going to in near future https://github.com/rthalley/dnspython/blob/master/doc/rfc.rst - for BIND I think you can add `dnssec-keygen` as answer - I will accept it. – Majus Misiak Apr 30 '19 at 08:49

1 Answers1

3

Server is running on Microsoft AD DNS with GSS-TSIG.

TSIG and GSS-TSIG are different beasts – the former uses a static preshared key that can be simply copied from the server, but the latter uses Kerberos (GSSAPI) to negotiate a session key for every transaction.

At the time when this thread was originally posted, dnspython 1.x did not have any support for GSS-TSIG whatsoever.

(The handshake does not result in a static key that could be converted to a regular TSIG keyring; instead the GSSAPI library itself must be called to build an authenticator – dnspython 1.x could not do that, although dnspython 2.1 finally can.)

If you are trying to update an Active Directory DNS server, BIND's nsupdate command-line tool supports GSS-TSIG (and sometimes it even works). You should be able to run it through subprocess and simply feed the necessary updates via stdin.

cmds = [f'zone {dyn_zone}\n',
        f'del {fqdn}\n',
        f'add {fqdn} 60 TXT "{challenge}"\n',
        f'send\n']
subprocess.run(["nsupdate", "-g"],
               input="".join(cmds).encode(),
               check=True)

As with most Kerberos client applications, nsupdate expects the credentials to be already present in the environment (that is, you need to have already obtained a TGT using kinit beforehand; or alternatively, if a recent version of MIT Krb5 is used, you can point $KRB5_CLIENT_KTNAME to the keytab containing the client credentials).

Update: dnspython 2.1 finally has the necessary pieces for GSS-TSIG, but creating the keyring is currently a very manual process – you have to call the GSSAPI library and process the TKEY negotiation yourself. The code for doing so is included at the bottom.

(The Python code below can be passed a custom gssapi.Credentials object, but otherwise it looks for credentials in the environment just like nsupdate does.)

import dns.rdtypes.ANY.TKEY
import dns.resolver
import dns.update
import gssapi
import socket
import time
import uuid

def _build_tkey_query(token, key_ring, key_name):
    inception_time = int(time.time())
    tkey = dns.rdtypes.ANY.TKEY.TKEY(dns.rdataclass.ANY,
                                     dns.rdatatype.TKEY,
                                     dns.tsig.GSS_TSIG,
                                     inception_time,
                                     inception_time,
                                     3,
                                     dns.rcode.NOERROR,
                                     token,
                                     b"")

    query = dns.message.make_query(key_name,
                                   dns.rdatatype.TKEY,
                                   dns.rdataclass.ANY)
    query.keyring = key_ring
    query.find_rrset(dns.message.ADDITIONAL,
                     key_name,
                     dns.rdataclass.ANY,
                     dns.rdatatype.TKEY,
                     create=True).add(tkey)
    return query

def _probe_server(server_name, zone):
    gai = socket.getaddrinfo(str(server_name),
                             "domain",
                             socket.AF_UNSPEC,
                             socket.SOCK_DGRAM)
    for af, sf, pt, cname, sa in gai:
        query = dns.message.make_query(zone, "SOA")
        res = dns.query.udp(query, sa[0], timeout=2)
        return sa[0]

def gss_tsig_negotiate(server_name, server_addr, creds=None):
    # Acquire GSSAPI credentials
    gss_name = gssapi.Name(f"DNS@{server_name}",
                           gssapi.NameType.hostbased_service)
    gss_ctx = gssapi.SecurityContext(name=gss_name,
                                     creds=creds,
                                     usage="initiate")

    # Name generation tips: https://tools.ietf.org/html/rfc2930#section-2.1
    key_name = dns.name.from_text(f"{uuid.uuid4()}.{server_name}")
    tsig_key = dns.tsig.Key(key_name, gss_ctx, dns.tsig.GSS_TSIG)

    key_ring = {key_name: tsig_key}
    key_ring = dns.tsig.GSSTSigAdapter(key_ring)

    token = gss_ctx.step()
    while not gss_ctx.complete:
        tkey_query = _build_tkey_query(token, key_ring, key_name)
        response = dns.query.tcp(tkey_query, server_addr, timeout=5)
        if not gss_ctx.complete:
            # Original comment:
            # https://github.com/rthalley/dnspython/pull/530#issuecomment-658959755
            # "this if statement is a bit redundant, but if the final token comes
            # back with TSIG attached the patch to message.py will automatically step
            # the security context. We dont want to excessively step the context."
            token = gss_ctx.step(response.answer[0][0].key)

    return key_name, key_ring

def gss_tsig_update(zone, update_msg, creds=None):
    # Find the SOA of our zone
    answer = dns.resolver.resolve(zone, "SOA")
    soa_server = answer.rrset[0].mname
    server_addr = _probe_server(soa_server, zone)

    # Get the GSS-TSIG key
    key_name, key_ring = gss_tsig_negotiate(soa_server, server_addr, creds)

    # Dispatch the update
    update_msg.use_tsig(keyring=key_ring,
                        keyname=key_name,
                        algorithm=dns.tsig.GSS_TSIG)
    response = dns.query.tcp(update_msg, server_addr)
    return response
user1686
  • 13,155
  • 2
  • 35
  • 54