Configure DHCP to update DNS records with BIND9

From SambaWiki
Jump to: navigation, search

Introduction

This HowTo describes how to configure isc DHCP to update a Samba DC BIND DNS backend. See Setting_up_a_BIND_DNS_Server for how to set up Bind.

It has not been tested with the Samba 4 internal DNS server and it probably will not work with the Samba 4 internal DNS.

As this HowTo is based on a Debian OS install, the paths given may be different if you use another OS.


Preconditions

  • Bind9 is installed and working with the Samba 4 DC, tested with various 9.x versions
  • You have created the reverse zone.
  • you are logged into the DC as 'root'


Names and Addresses used in this howto

  • Realm  : SAMDOM.EXAMPLE.COM
  • Subnet  : 192.168.0.0
  • Netmask  : 255.255.255.0
  • Subnet-mask  : 255.255.255.0
  • Broadcast-address  : 192.168.0.255
  • Gateway  : 192.168.0.1
  • Domain-name  : samdom.example.com
  • Domain-name-servers  : 192.168.0.6, 192.168.0.5
  • Netbios-name-servers : 192.168.0.5, 192.168.0.6
  • Ntp-servers  : 192.168.0.5, 192.168.0.6;
  • Pool range  : 192.168.0.50 192.168.0.229


Install isc DHCP

First install the DHCP server

# apt-get install isc-dhcp-server


Create a user to carry out the updates

You need a user that the script will run as, set a random password because you will never logon as the user.

# samba-tool user create dhcpduser --description="Unprivileged user for TSIG-GSSAPI DNS updates via ISC DHCP server" --random-password

Now set the users password to never expire and add the user to the DnsAdmins group.

# samba-tool user setexpiry dhcpduser --noexpiry
# samba-tool group addmembers DnsAdmins dhcpduser

Now export the required keytab.

# samba-tool domain exportkeytab --principal=dhcpduser@SAMDOM.EXAMPLE.COM /etc/dhcp/dhcpduser.keytab
# chown root:root  /etc/dhcp/dhcpduser.keytab
# chmod 400  /etc/dhcp/dhcpduser.keytab


Create the script for the updates

First make a directory to store the script in.

# mkdir -p /etc/dhcp/bin

Next, copy this script to /etc/dhcp/bin/dhcp-dyndns.sh

#!/bin/bash

# /etc/bin/dhcp-dyndns.sh

# This script is for secure DDNS updates on Samba 4
# Version: 0.8.8

# DNS domain
domain=$(hostname -d)
if [ -z ${domain} ]; then
    echo "Cannot obtain domain name, is DNS set up correctly?"
    echo "Cannot continue... Exiting."
    logger "Cannot obtain domain name, is DNS set up correctly?"
    logger "Cannot continue... Exiting."
    exit 1
fi

# Samba 4 realm
REALM=$(echo ${domain^^})

# Additional nsupdate flags (-g already applied), e.g. "-d" for debug
#NSUPDFLAGS="-d"

# krbcc ticket cache
export KRB5CCNAME="/tmp/dhcp-dyndns.cc"

# Kerberos principal
SETPRINCIPAL="dhcpduser@${REALM}"
# Kerberos keytab
# /etc/dhcpduser.keytab
# krbcc ticket cache
# /tmp/dhcp-dyndns.cc

TESTUSER=$(wbinfo -u | grep dhcpduser)
if [ -z "${TESTUSER}" ]; then
    echo "No AD dhcp user exists, need to create it first.. exiting."
    echo "you can do this by typing the following commands"
    echo "kinit Administrator@${REALM}"
    echo "samba-tool user create dhcpduser --random-password --description=\"Unprivileged user for DNS updates via ISC DHCP server\""
    echo "samba-tool user setexpiry dhcpduser --noexpiry"
    echo "samba-tool group addmembers DnsAdmins dhcpduser"
    exit 1
fi

# Check for Kerberos keytab
if [ ! -f /etc/dhcp/dhcpduser.keytab ]; then
    echo "Required keytab /etc/dhcpduser.keytab not found, it needs to be created."
    echo "Use the following commands as root"
    echo "samba-tool domain exportkeytab --principal=${SETPRINCIPAL} /etc/dhcpduser.keytab"
    echo "chown XXXX:XXXX /etc/dhcpduser.keytab"
    echo "Replace 'XXXX:XXXX' with the user & group that dhcpd runs as on your distro"
    echo "chmod 400 /etc/dhcpduser.keytab"
    exit 1
fi

# Variables supplied by dhcpd.conf
action=$1
ip=$2
DHCID=$3
name=${4%%.*}

usage()
{
echo "USAGE:"
echo "  `basename $0` add ip-address dhcid|mac-address hostname"
echo "  `basename $0` delete ip-address dhcid|mac-address"
}

_KERBEROS () {
# get current time as a number
test=$(date +%d'-'%m'-'%y' '%H':'%M':'%S)
# Note: there have been problems with this
# check that 'date' returns something like
# 04-09-15 09:38:14

# Check for valid kerberos ticket
#logger "${test} [dyndns] : Running check for valid kerberos ticket"
klist -c /tmp/dhcp-dyndns.cc -s
if [ "$?" != "0" ]; then
    logger "${test} [dyndns] : Getting new ticket, old one has expired"
    kinit -F -k -t /etc/dhcp/dhcpduser.keytab -c /tmp/dhcp-dyndns.cc "${SETPRINCIPAL}"
    if [ "$?" != "0" ]; then
        logger "${test} [dyndns] : dhcpd kinit for dynamic DNS failed"
        exit 1;
    fi
fi

}

# Exit if no ip address or mac-address
if [ -z "${ip}" ] || [ -z "${DHCID}" ]; then
    usage
    exit 1
fi

# Exit if no computer name supplied, unless the action is 'delete'
if [ "${name}" = "" ]; then
    if [ "${action}" = "delete" ]; then
        name=$(host -t PTR "${ip}" | awk '{print $NF}' | awk -F '.' '{print $1}')
    else
        usage
        exit 1;
    fi
fi

# Set PTR address
ptr=$(echo ${ip} | awk -F '.' '{print $4"."$3"."$2"."$1".in-addr.arpa"}')

## nsupdate ##

case "${action}" in
add)
    _KERBEROS

nsupdate -g ${NSUPDFLAGS} << UPDATE
server 127.0.0.1
realm ${REALM}
update delete ${name}.${domain} 3600 A
update add ${name}.${domain} 3600 A ${ip}
send
UPDATE
result1=$?

nsupdate -g ${NSUPDFLAGS} << UPDATE
server 127.0.0.1
realm ${REALM}
update delete ${ptr} 3600 PTR
update add ${ptr} 3600 PTR ${name}.${domain}
send
UPDATE
result2=$?
;;
delete)
     _KERBEROS

nsupdate -g ${NSUPDFLAGS} << UPDATE
server 127.0.0.1
realm ${REALM}
update delete ${name}.${domain} 3600 A
send
UPDATE
result1=$?
nsupdate -g ${NSUPDFLAGS} << UPDATE
server 127.0.0.1
realm ${REALM}
update delete ${ptr} 3600 PTR
send
UPDATE
result2=$?
;;
*)
echo "Invalid action specified"
exit 103
;;
esac

result="${result1}${result2}"

if [ "${result}" != "00" ]; then
    logger "DHCP-DNS Update failed: ${result}"
else
    logger "DHCP-DNS Update succeeded"
fi

exit ${result}


Set the permissions on the script.

# chmod 755 /etc/dhcp/bin/dhcp-dyndns.sh


Modify the dhcp conf file

First backup the original conf file.

# cp /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.orig

Now edit /etc/dhcp/dhcpd.conf and make it look similar to the this.

authoritative;
ddns-update-style none;

subnet 192.168.0.0 netmask 255.255.255.0 {
  option subnet-mask 255.255.255.0;
  option broadcast-address 192.168.0.255;
  option time-offset 0;
  option routers 192.168.0.1;
  option domain-name "samdom.example.com";
  option domain-name-servers 192.168.0.6, 192.168.0.5;
  option netbios-name-servers 192.168.0.5, 192.168.0.6;
  option ntp-servers 192.168.0.5, 192.168.0.6;
  pool {
    max-lease-time 1800; # 30 minutes
    range 192.168.0.50 192.168.0.229;
  }
}

on commit {
set noname = concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address));
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
set ClientDHCID = binary-to-ascii(16, 8, ":", hardware);
set ClientName = pick-first-value(option host-name, config-option-host-name, client-name, noname);
log(concat("Commit: IP: ", ClientIP, " DHCID: ", ClientDHCID, " Name: ", ClientName));
execute("/etc/dhcp/bin/dhcp-dyndns.sh", "add", ClientIP, ClientDHCID, ClientName);
}

on release {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
set ClientDHCID = binary-to-ascii(16, 8, ":", hardware);
log(concat("Release: IP: ", ClientIP));
execute("/etc/dhcp/bin/dhcp-dyndns.sh", "delete", ClientIP, ClientDHCID);
}

on expiry {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
# cannot get a ClientMac here, apparently this only works when actually receiving a packet
log(concat("Expired: IP: ", ClientIP));
# cannot get a ClientName here, for some reason that always fails
execute("/etc/dhcp/bin/dhcp-dyndns.sh", "delete", ClientIP, "", "0");
}


Start the dhcp server and see what happens, don't forget to stop your windows clients trying to update their own records, as this will fail.


Add failover

Add the following to the /etc/dhcp/dhcpd.conf file on the primary:

failover peer "dhcp-failover" {
  primary;
  address dc1.samdom.example.com;
  peer address dc2.samdom.example.com;
  max-response-delay 60;
  max-unacked-updates 10;
  mclt 3600;
  split 128;
  load balance max seconds 3;
}

..and secondary:

failover peer "dhcp-failover" {
  secondary;
  address dc2.samdom.example.com;
  peer address dc1.samdom.example.com;
  max-response-delay 60;
  max-unacked-updates 10;
  load balance max seconds 3;
}


Add references for the subnet/pool which will do failover.

subnet 192.168.0.0 netmask 255.255.255.0 {
  option subnet-mask 255.255.255.0;
  option broadcast-address 192.168.0.255;
  option time-offset 0;
  option routers 192.168.0.1;
  option domain-name "samdom.example.com";
  option domain-name-servers 192.168.0.5, 192.168.0.6;
  option ntp-servers 192.168.0.5, 192.168.0.6;
  pool {
    failover peer "dhcp-failover";
    max-lease-time 1800; # 30 minutes
    range 192.168.0.50 192.168.0.229;
  }
}

Configure OMAPI and define a secret key.

Generate a random OMAPI key on the primary, using the dnssec-keygen utility distributed with BIND.

dnssec‐keygen ‐a HMAC‐MD5 ‐b 512 ‐n USER DHCP_OMAPI

Now extract the actual key:

cat Kdhcp_omapi.+*.private |grep ^Key|cut -d ' ' -f2-

Add the following to dhcpd.conf on both primary and secondary.

omapi-port 7911;
omapi-key omapi_key;

key omapi_key {
     algorithm hmac-md5;
     secret "PUT_YOUR_KEY_HERE";
}

Replace PUT_YOUR_KEY_HERE with the key you extracted from the private key created by the dnssec command

Restart both servers to apply the configuration changes.

You should find lines similar to these, in the system logs on both machines:

Feb 28 17:34:39 dc1 dhcpd: failover peer dhcp-failover: peer moves from recover-done to normal
Feb 28 17:34:39 dc1 dhcpd: failover peer dhcp-failover: Both servers normal

If OMAPI is working properly you can test failover by stopping the primary server.

If you are using a firewall, you will need to open TCP ports 647 and 7911

Once you are sure everything is working as expected, restart both servers to ensure everything is running correctly.

The 'split' value '128' on the 'primary', divides responsibility for the clients between the two failover partners. If you want the primary to answer all dhcp requests unless it is down (for whatever reason) set the value to '255', use '0' to make the secondary responsible.

For more information, read the dhcpd.conf manpage man dhcpd.conf.

Any questions or problems, ask on the Samba mailing list.