Openstuff Wiki : HowtoSVNDNS

HomePage :: Categories :: PageIndex :: RecentChanges :: RecentlyCommented :: Login/Register

Allons plus loin avec Subversion: application aux DNS


Nous allons voir comment utiliser SVN pour gérer des zones DNS. En effet, Subversion s'intègre particulièrement bien au sein d'une architecture DNS multimaster. Attention, nous considérons que vous êtes déjà familier avec SVN et que vous maîtrisez le fonctionnement du DNS (architecture, configuration, ...).

Dans cet article, nous utiliserons Bind comme serveur DNS.

1. Motivation de l'architecture DNS avec l'utilisation de SVN


Classiquement, les DNS sont mis en maître/esclave avec des transferts de zone entre eux. L'idée ici est de n'avoir plus qu'une machine centrale qui poussera les zones en SSH sur les autoritaire qui seront tous déclaré master. C'est ce que nous appelons une architecture DNS multimaster. Les intérêts sont multiples :

L'utilisation d'un outil de gestion de version tel que Subversion est motivée par les éléments suivants:

Pourquoi Subversion et pas CVS ? Principalement pour la possibilité d'utiliser des "Hook" qui vont nous permettent d'exécuter des scripts avant et après modification du repository Subversion. Mais d'autres avantages viennent également s'ajouter à cela:


2. Mise en place


Les serveurs DNS autoritaires


Nous considérons que l'architecture DNS multimaster est déjà en place. Vous devriez avoir les directives suivantes dans la configuration de vos serveurs (section options):
recursion   no;
allow-transfer { none; }
notify         no;


Le serveur Master


Ce serveur doit contenir le repository Subversion avec l'ensemble de vos zones. Deux scripts de "hook" vont être créés qui permettront d'effectuer automatiquement la vérification et la propagation des zones lors d'un commit.

Les serials seront gérés par nos scripts, ils seront donc mis à 0000000000 dans le repository. Un remplacement sera fait automatiquement lors du post-commit via la commande zsu. Le fichier /var/lib/misc/zones.serialZ servira de stockage pour l'ensemble des serials. Son format est le suivant:
mazone.com 2007010502
mazone.reverse 2007010502


Script Hook Subversion: pre-commit

Rôle: vérification que les zones sont correcte

#!/bin/bash

#################################################
#                                               #
#       SVN pre-commit (DNS zone edition)       #
#                                               #
#################################################

# Redirect all output to standard error output
exec >&2

# uncomment this for debugging
#set -x

# required utils
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SVNLOOK="/usr/bin/svnlook"

# misc variables
REPOS="$1"
TXN="$2"
DATE=`date +%Y%m%d%H%M`
TMPDIR="/tmp/svndnstmp-$DATE-$TXN"
EDITED_ZONES="`$SVNLOOK changed -t $TXN $REPOS | grep -v '^D' | awk '{ print $2 }' | grep -v '\.inc$'`"   # Do not get Deleted files
LOG_FILE="/var/log/svn/debug.log"

echo ">>> Starting PRE-COMMIT ... (`date '+%X %x'`)" >> $LOG_FILE

# get the edited zones and put them into temporary files
mkdir $TMPDIR
for i in $EDITED_ZONES
do
        echo "extracting: "$i >> $LOG_FILE
        $SVNLOOK cat -t $TXN $REPOS $i > "$TMPDIR/$i"
done


# checking the edited zones
ERRZONE=0
cd $TMPDIR
for i in $EDITED_ZONES
do
        echo "checking: "$i >> $LOG_FILE

        # Check SERIAL
        val=`grep -E "^[[:blank:]]*[0-9]{10}($|[[:blank:]]*;.*$)" $i | awk '{ print $1 }'`
        [ -z "$val" ] && { echo -e "\033[00;31m>>> NO SERIAL on zone $i ! \033[00;00m" ; ERRZONE=1 ; continue ; }
        if [ "$val" != '0000000000' ]
        then
                ERRZONE=1
                echo -e "\033[00;31m>>> BAD SERIAL on zone $i : serial != '000000000' !\033[00;00m"
        fi

        # Check ORIGIN
        ZONE=`echo $i | grep '$ORIGIN' "$i" | awk '{print $2}'`
        [ -z "$ZONE" ] && { echo -e "\033[00;31m>>> ORIGIN IS MISSING on zone $i ! \033[00;00m" ; ERRZONE=1 ; continue ; }

        # Check zone file
        ERRMSG=`named-checkzone $ZONE "$i"`
        if [ $? != 0 ]
        then
                ERRZONE=1
                echo -e "\033[00;31m>>> ERROR on zone $i\033[00;00m"
                echo $ERRMSG
        fi
done

# if anything gone wrong, start yelling
rm -rf $TMPDIR
echo ">>> End PRE-COMMIT ... (`date '+%X %x'`)" >> $LOG_FILE

exit $ERRZONE


Script Hook Subversion: post-commit

Rôle: déploiement des modifications sur les serveurs

#!/bin/sh
REPOS="$1"
REV="$2"

# Program PATH
DNS_ALL="/usr/local/sbin/dns_all_ng.sh"
SVNLOOK="/usr/bin/svnlook"
SVN="/usr/bin/svn"
RSYNC="/usr/bin/rsync"
PERL="/usr/bin/perl"
ECHO="/bin/echo"
AWK="/usr/bin/awk"
RM="/bin/rm"
GREP="/bin/grep"
ZSU="/usr/local/bin/zsu"
DATE="/bin/date"
SUDO="/usr/bin/sudo"
CHMOD="/bin/chmod"

# Var def
FILE="/tmp/svn.$$"
BIND_REPOS="/etc/bind/zones/master"
MAIL_ADDR="admin@example.com"
FILE_SERIAL="/var/lib/misc/zones.serialZ"
LOG_FILE="/var/log/svn/debug.log"
SUDO_USER="dns"

echo ">>> Starting POST-COMMIT ... (`$DATE '+%X %x'`)" >> $LOG_FILE

# Get infos from SVNLOOK
SVN_AUTHOR=`$SVNLOOK author --revision $REV $REPOS`
SVN_DATE=`$SVNLOOK date --revision $REV $REPOS`
SVN_LOG=`$SVNLOOK log --revision $REV $REPOS`
SVN_CHANGED=`$SVNLOOK changed --revision $REV $REPOS`
SVN_DIFF=`$SVNLOOK diff --revision $REV $REPOS`
ZONE_ALL_CHANGED=`echo "$SVN_CHANGED" | $AWK '{print $NF}'` # Get deleted files
ZONE_CHANGED=`echo "$SVN_CHANGED" | $GREP -v "^D" | $AWK '{print $NF}'` # Do not get deleted files

# Define return code
MAJ_REPOS_BIND_RET="NOK"
RSYNC_SVN_RET="NOK"
REPLACE_SERIAL_RET="OK"
DNS_ALL_RET="NOK"


# Update bind zone files
# ----------------------

# Delete modified files inside bind repository
for zone in $ZONE_ALL_CHANGED
do
        $RM -f $BIND_REPOS/$zone
done

# "svn up" in bind repository
MAJ_REPOS_BIND=`$SVN update $BIND_REPOS -q 2>&1` && MAJ_REPOS_BIND_RET="OK"

echo "*Update Bind repository* [$MAJ_REPOS_BIND_RET]
$MAJ_REPOS_BIND"
>> $LOG_FILE


# Update serial for each file changed
# -----------------------------------
for zone in $ZONE_CHANGED
do
        insert=0
       
        # Verify serial file => we must have only one entry
        num=`$GREP -c "^$zone " $FILE_SERIAL`
        if [ "$num" != "1" ]
        then    
                REPLACE_SERIAL=$REPLACE_SERIAL" Zone $zone have $num entry in FILE_SERIAL"
                REPLACE_SERIAL_RET="NOK"
        fi
       
        # Get current serial from serial_file
        serial=`$GREP -E "^$zone [0-9]{10}$" $FILE_SERIAL | $AWK '{print $2}'`
       
        # If void, create new serial
        [ -z "$serial" ] && { serial=`$DATE +%Y%m%d00`; REPLACE_SERIAL=$REPLACE_SERIAL" Create new serial for zone $zone. "; insert=1; }
       
        # Replace serial on zone file
        export serial
        REPLACE_SERIAL=$REPLACE_SERIAL`$PERL -pi -e 'if ($SOA) { s/\d{10}/$ENV{serial}/; $SOA=0 } ; $SOA++ if /IN\s+SOA/' $BIND_REPOS/$zone 2>&1`
        [ $? -ne 0 ] && REPLACE_SERIAL_RET="NOK"
       
        # Update serial
        REPLACE_SERIAL=$REPLACE_SERIAL`$ZSU -f $BIND_REPOS/$zone 2>&1`
        [ $? -ne 0 ] && REPLACE_SERIAL_RET="NOK"
       
        # Replace serial on serial file
        #serial=`$GREP -E "^[[:blank:]]*[0-9]{10}($|[[:blank:]]*;.*$)" $BIND_REPOS/$zone | $AWK '{ print $1 }'`
        serial=`$PERL -n -e 'if ($SOA) { print $1 if /(\d{10})/; $SOA=0 } ; $SOA++ if /IN\s+SOA/' $BIND_REPOS/$zone 2>&1`
        [ -z "$serial" ] && REPLACE_SERIAL_RET="NOK"
        REPLACE_SERIAL=$REPLACE_SERIAL`$PERL -pi -e "s/^$zone [0-9]{10}$/$zone $serial/" $FILE_SERIAL 2>&1`
       
        # If zone do not exist on serial file, add it
        [ $insert -eq 1 ] && $ECHO "$zone $serial" >> $FILE_SERIAL
done

echo "*Serial replace* [$REPLACE_SERIAL_RET]
$REPLACE_SERIAL"
>> $LOG_FILE


# Apply changes
# -------------
DNS_ALL=`$SUDO -u $SUDO_USER $DNS_ALL $ZONE_CHANGED 2>&1` && DNS_ALL_RET="OK"

echo "*dns_all.sh* [$DNS_ALL_RET]
$DNS_ALL"
>> $LOG_FILE


# Create report
# -------------
{

/bin/cat <<EOF
Repository:     $REPOS
Revision:       $REV
Author:         $SVN_AUTHOR
Date:           $SVN_DATE
Log message:    $SVN_LOG
Modified paths:
$SVN_CHANGED

DNS Report:
===========

*Update Bind repository* [$MAJ_REPOS_BIND_RET]
$MAJ_REPOS_BIND
*Serial replace* [$REPLACE_SERIAL_RET]
$REPLACE_SERIAL
*Rsync* [$RSYNC_SVN_RET]
$RSYNC_SVN
*dns_all.sh* [$DNS_ALL_RET]

$DNS_ALL


$SVN_DIFF

EOF

} > $FILE

# Mail file and delete it
# -----------------------
if [ "$MAJ_REPOS_BIND_RET" == "OK" ] && [ "$REPLACE_SERIAL_RET" == "OK" ] && [ "$RSYNC_SVN_RET" == "OK" ] && [ "$DNS_ALL_RET" == "OK" ]
then
        STATUS="OK"
else
        STATUS="NOK"
fi
/bin/cat $FILE | /usr/bin/mail -s "DNS SVN update (rev. $REV): [$STATUS] $SVN_LOG" $MAIL_ADDR
rm -f $FILE
echo ">>> End POST-COMMIT ... (`$DATE '+%X %x'`)" >> $LOG_FILE



Script de déploiement: dns_all_ng.sh
Rôle:

#! /bin/sh


AUTHORITATIVES="serv1.auth.fqdn serv2.auth.fqdn"
RESOLVERS="serv1.solv.fqdn serv2.solv.fqdn"
BIND_DIR="/var/bind/master"
ZONES_DIR="/etc/bind/zones/master"
TMP_FILE=/tmp/dns_all.$$
SSH_OPT="-o ConnectTimeout=10"
ALL_ZONES=""
RET=0

# Delete temp file on exit
trap "rm -f $TMP_FILE" EXIT

# Get zone file from parameters
FILES="$*"

# Synchronize zones files and reload zones
synchro() {
        machine=$1

        fping $machine > /dev/null || { echo "ERROR: host $machine is dead !" >&2; RET=1; return; }
        scp $SSH_OPT $ALL_FILES ${machine}:$BIND_DIR/ > /dev/null
        ssh $SSH_OPT ${machine} "for zone in $ALL_ZONES ; do /usr/sbin/rndc reload \$zone > /dev/null || { hostname; echo \"ERROR: rndc reload \$zone failed\" >&2; RET=1; } ; done " || { echo "ERROR: ssh connect failed" >&2; RET=1; }
}

# Flush zone files
flush() {
        solv=$1

        fping $solv > /dev/null || { echo "ERROR: host $solv is dead !" >&2; RET=1; return; }
        ssh $SSH_OPT $solv "/usr/sbin/rndc flush || { hostname; echo \"ERROR: can't flush DNS cache\" >&2; RET=1; }" || { echo "ERROR: ssh connect failed" >&2; RET=1; }
}

# Display SERIAL for all $ALL_ZONES on all authoritatives servers
test_all() {
        for machine in localhost $AUTHORITATIVES ; do
                echo -n "$machine: "
                fping $machine > /dev/null || { echo "ERROR: host $machine is dead, can't test SOA" >&2; RET=1; continue; }
                for zone in $ALL_ZONES ; do
                        dig +short -t SOA $zone @$machine | awk '{ print $3 } ' | xargs echo -n " "
                done
                echo ""
        done
}


# Go to zones directory
cd $ZONES_DIR

# Check zones
for fichier in $FILES ; do
        echo "* $fichier"

        # Origin check
        ZONE=`cat $fichier | grep "\\$ORIGIN" | awk '{print $2}'`
        [ -z "$ZONE" ] && echo -e "ERROR: no $ORIGIN defined ! >>> Do nothing !!! <<<" >&2 && RET=1 && continue

        # Named check zone
        named-checkzone -q $ZONE $fichier || { named-checkzone $ZONE $fichier; echo "ERROR: zone $ZONE is not valid" >&2; RET=1; continue; }
        rndc reload $ZONE || { echo "ERROR: rndc reload failed !" >&2; RET=1; }
        ALL_ZONES="$ALL_ZONES $ZONE"
        ALL_FILES="$ALL_FILES $fichier"
done

# No zone provided, exit
[ "x$ALL_ZONES" == "x" ] && { echo "ERROR: no zone provided" >&2; exit 1; }

# Reload AUTHORITATIVES
echo ""
echo "reload: "
for i in $AUTHORITATIVES ; do
        echo -n "- $i "
        synchro $i
        echo ""
done
echo ""

# Flush RESOLVERS
echo "flush: "
for i in $RESOLVERS ; do
        echo -n "- $i "
        flush $i
        echo ""
done
echo ""

# Check SOA
for count in `seq 3` ; do
        test_all > $TMP_FILE
        [ `uniq -1 $TMP_FILE | wc -l` -eq "1" ] && break
        sleep 1
done

# If error
if [ $count -eq 3 ] ; then
        RET=1
        echo "" >&2
        echo "ERROR: serial mismatch !" >&2
        echo -n "zone: " >&2
        for zone in $ALL_ZONES ; do echo -n "$zone " >&2 ; done ; echo "" >&2
        cat $TMP_FILE >&2
        echo "" >&2
fi

# Delete temp file
rm $TMP_FILE

# Display last logfile message
echo "-----------------"
tail /var/log/bind/named.log
echo "-----------------"

exit $RET


Attention: ce script doit pouvoir se connecter en SSH aux serveurs autoritaires et aux résolveurs. Il faut donc au préalable générer un couple de clé sans passphrase, et déployer les clés publique sur les serveur DNS en question.


3. Utilisation


Une fois les zones insérées dans le repository, les modifications de zones s'effectuent de la sorte:
$ svn up
$ vi <la zone DNS à modifier>
$ svn commit -m "<commentaire>"


Et voilà ! La zone sera vérifiée, le serial sera inséré dans le fichier de zone et celle-ci sera déployée sur les autoritaires. Rapide, efficace, que demande le peuple ? :)
Valid XHTML 1.0 Transitional :: Valid CSS :: Powered by WikkaWiki
Page was generated in 0.1932 seconds