Skip to content

Instantly share code, notes, and snippets.

@lucashalbert
Last active March 19, 2019 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucashalbert/f5b6a76e948b2eccee0ef819a97601e9 to your computer and use it in GitHub Desktop.
Save lucashalbert/f5b6a76e948b2eccee0ef819a97601e9 to your computer and use it in GitHub Desktop.
Utilizes named and DNSSEC utilities to assist in generating KSKs and ZSKs as well as assisting in signing DNS zones with DNSSEC. This utility also has the ability to leverage rndc commands for reloading/refreshing zones on local and remote bind servers.
#!/bin/bash
print_version() {
cat <<EOF
##########################################################################
#
# Author: Lucas Halbert <contactme@lhalbert.xyz>
# Date: 04/29/2016
# Last Edited: 03/07/2019
# Version: 2019.03.07
# Description: Utilizes named and DNSSEC utilities to assist in
# generating KSKs and ZSKs as well as assisting in signing
# DNS zones with DNSSEC. This utility also has the ability
# to leverage rndc commands for reloading/refreshing zones on local
# and remote bind servers.
# License: BSD 3-Clause License
#
# Copyright (c) 2016, Lucas Halbert
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
####################################################################################
EOF
}
print_changelog() {
cat <<EOF
####################################################################################
#
# Revisions: 2019.03.07 - Add argument documentation
#
# 2017.07.10 - Zone sanity check fixes
#
# 2016.12.13 - Add zonetest/configtest options and documentation
#
# 2016.11.18 - Clean up if statements for ALLZONES and ALLSERVERS.
# Remove unneeded print functions and add Changlog data.
#
# 2016.11.08 - Add ability to select specific which slaves to reload
#
# 2016.06.28 - Add modernize function to bring any zone serial up
# to current date.
#
# 2016.05.26 - Add sort zones functionality based on most recent
# serial update.
#
# 2016.04.29 - Write sanity checks for zone refresh function
#
####################################################################################
EOF
}
print_usage() {
cat <<EOF
Usage: $0[-cCGSRIDMvVhH|vv] [-z zone<,zone2,zone3>] [-s server<,server2,server3>]
Examples:
$0 [-z|--zone zone1<,zone2,zone3>] [-s|--server server1<,server2,server3>]
[-M|--modernize] [-I|--increment] [-D|--decrement] [-G|--generate] [-S|--sign]
[-c|--zonetest] [-C|--configtest] [-R|--refresh] [-r|--reload] [-v|-vv] [-h|-H|-V]
EOF
}
print_help() {
cat <<EOF
NAME
$0 - assists in generating KSKs, ZSKs, and signing DNS zones
SYNOPSIS
$0 [-z|--zone zone1<,zone2,zone3>] [-s|--server server1<,server2,server3>]
[-M|--modernize] [-I|--increment] [-D|--decrement] [-G|--generate] [-S|--sign]
[-c|--zonetest] [-C|--configtest] [-R|--refresh] [-r|--reload] [-v|-vv] [-h|-H|-V]
DESCRIPTION
$0 Utilizes named and DNSSEC utilities to assist in generating KSKs and ZSKs as well as assisting in signing DNS zones with DNSSEC. This utility also has the ability
to leverage rndc commands for refreshing zones on local and remote bind servers.
OPTIONS
-c|--zonetest
Test zone Sanity.
-C|--configtest
Test named config Sanity.
-G|--generate
Generate new ZSK and KSK keys.
-S|--sign
Sign specified zones with the current ZSK and KSK keys.
-I|--increment
Increment specified zone serials.
-D|--decrement
Decrement specified zone serials.
-M|--modernize
Modernize specified zone serials. Modernization will update the zone serial to the current date. If the serial is already on the current date, then it will be
incremented.
-R|--refresh
Refresh specified zones on the specified servers.
-r|--reload
Reload specified zones on the master.
-z|--zone zone[,zone1,zone2]
Specify zone[s] that other arguments should act on. Zones specified must be valid.
-s|--server server[,server1,server2]
Specify server[s] that other arguments should act on. Servers specified must be valid and contained in /etc/rndc.conf
-v|-vv
Increase verbosity
-h
Print usage
-H|--help
Print detailed usage (this page)
-V
Print version details and changelog
EOF
}
AUTHOR="Lucas Halbert <lhalbert@lhalbert.xyz>"
VERSION="2017.07.10"
APP=$(basename $0)
DATE=$(date +"%Y%m%d-%H:%M")
ZONESERIALDATE=$(date +"%Y%m%d00")
CONF_DIR="/var/named/chroot/etc/"
ZONE_DIR="/var/named/chroot/var/named/zones/"
DNSSEC_DIR="/var/named/chroot/var/named/dnssec/"
AVAILSERVERS=()
AVAILZONES=()
SERVERS=()
ZONES=()
VERBOSE=0
function revokeOldKeys() {
local ZONE=$1
for key in $(ls ${DNSSEC_DIR}K${ZONE}*.{key,private} 2>/dev/null); do
echo "Revoking Key: $key"
mv ${key} ${key}.REVOKED_${DATE}
done
for key in $(ls ${DNSSEC_DIR}K${ZONE}*.{key,private}.REVOKED_* 2>/dev/null); do
mv $key ${DNSSEC_DIR}REVOKED/
done
}
# generate ZSK (Zone Signing Key) for zone
function generateZSK() {
local ZONE=$1
echo "Generating ZSK for ${ZONE}"
dnssec-keygen -a NSEC3RSASHA1 -b 2048 -r /dev/urandom -K ${DNSSEC_DIR} -n ZONE ${ZONE}
}
# generate KSK (Key Signing Key) for zone
function generateKSK() {
local ZONE=$1
echo "Generating KSK for ${ZONE}"
local keyFile=$(dnssec-keygen -f KSK -a NSEC3RSASHA1 -b 4096 -r /dev/urandom -K ${DNSSEC_DIR} -n ZONE ${ZONE})
generateDS ${keyFile}
}
# generate DS (Delegation Signer) for zone
function generateDS() {
local keyFile=$1
echo "Generating DS for ${ZONE}"
dnssec-dsfromkey ${DNSSEC_DIR}${keyFile} > ${DNSSEC_DIR}${keyFile}.ds
}
# get all available servers configured in /etc/rndc.conf
function getAvailableServers() {
for server in $(grep "^server" /etc/rndc.conf | grep -v localhost | awk '{print $2}'); do
AVAILSERVERS+=($server)
done
}
# test the chrooted BIND config
function testConfig() {
echo "Checking BIND config"
named-checkconf -t /var/named/chroot /etc/named.conf
if [[ $? -gt 0 ]]; then
echo "There is a problem with the named configuration"
exit 2
fi
}
# test zone sanity
function testZone() {
local ZONE=$1
local output=''
echo "Testing ${ZONE} sanity"
output=$(named-checkzone -t /var/named/chroot ${ZONE} /var/named/zones/${ZONE})
if [[ $? -gt 0 ]]; then
echo "${output}"
exit 1
fi
}
# perform a zone reload
function reloadZone() {
local ZONE=$1
if [[ $(grep "^zone \"${ZONE}\"" ${CONF_DIR}named.internal.zones) ]] || [[ $(grep "^zone \"${ZONE}\"" ${CONF_DIR}named.rpz.zones) ]]; then
rndc reload ${ZONE} IN internal-view
fi
if [[ $(grep "^zone \"${ZONE}\"" ${CONF_DIR}named.external.zones) ]]; then
rndc reload ${ZONE} IN external-view
fi
}
# perform a zone refresh
function refreshZone() {
local ZONE=$1
for server in "${SERVERS[@]}"; do
# Refresh all internal-view zones
if [[ $(grep "^zone \"${ZONE}\"" ${CONF_DIR}named.internal.zones) ]] || [[ $(grep "^zone \"${ZONE}\"" ${CONF_DIR}named.rpz.zones) ]]; then
rndc -s ${server} refresh ${ZONE} IN internal-view
fi
if [[ $(grep "^zone \"${ZONE}\"" ${CONF_DIR}named.external.zones) ]]; then
rndc -s ${server} refresh ${ZONE} IN external-view
fi
done
}
# Sign zone with key using random salt
function signZone() {
local ZONE=$1
cd ${DNSSEC_DIR}
dnssec-signzone -A -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) -N INCREMENT -o ${ZONE} -t ${ZONE_DIR}${ZONE}
}
function getIncludedPublicKeys() {
local ZONE=$1
echo $(grep "${DNSSEC_DIR}K${ZONE}.*.key" ${ZONE_DIR}${ZONE} | wc -l)
}
# Add public keys to zone files
function includePublicKeys() {
local ZONE=$1
for key in $(ls ${DNSSEC_DIR}K${ZONE}*.key); do
echo "\$INCLUDE /var/named/dnssec/$(basename ${key})" >> ${ZONE_DIR}${ZONE}
done
}
# remove existing public keys from zone file
function removePublicKeys() {
local ZONE=$1
echo "Removing Public Key from ${ZONE}"
#if [[ "$(grep -c "${DNSSEC_DIR}K${ZONE}.*.key" ${ZONE_DIR}${ZONE})" -gt "0" ]]; then
echo "performing deletion of \$INCLUDE statements"
sed -i '/^\$INCLUDE/d' ${ZONE_DIR}${ZONE}
#fi
}
# get all available zones
function getAvailableZones() {
local tempZones=()
for zone in $(grep "^zone" ${CONF_DIR}named.*.zones | awk -F '"' '{print $2}' | sort | uniq); do
if [ -f ${ZONE_DIR}${zone} ]; then
tempZones+=($(getZoneSerial ${zone}):${zone})
fi
done
# sort by serial
for i in $(echo "${tempZones[*]}" | tr " " "\n" | sort -n); do
AVAILZONES+=(${i})
done
unset tempZones
}
# check if zone is valid
function checkZoneValidity() {
local ZONE=$1
if [ ! -z "${ZONE}" ]; then
for (( i=0; i<${#AVAILZONES[@]}; i++ )); do
if [[ "$(echo ${AVAILZONES[$i]} | awk -F':' '{print $2}')" == "${ZONE}" ]]; then
echo 0
return
fi
done
echo 1
return
fi
echo 1
return
}
# Parse specified zones
function parseSpecifiedZones() {
if [[ $optZone =~ "," ]]; then
saveIFS=$IFS
IFS=","
read -r -a ZONES <<< "$optZone"
IFS=$saveIFS
else
ZONES+=($optZone)
fi
}
# Parse specified servers
function parseSpecifiedServers() {
if [[ $optServer =~ "," ]]; then
saveIFS=$IFS
IFS=","
read -r -a SERVERS <<< "$optServer"
IFS=$saveIFS
else
SERVERS+=($optServer)
fi
}
# get zone serial
function getZoneSerial() {
local ZONE=$1
echo $(grep -Eo "[0-9]{10}\s+;\s+serial" ${ZONE_DIR}${ZONE} | awk '{print $1}')
}
# increment zone serial
function incrementZoneSerial() {
local ZONE=$1
local ZONESERIAL=$(getZoneSerial ${ZONE})
sed -i "s/${ZONESERIAL}/$((ZONESERIAL+1))/" ${ZONE_DIR}${ZONE}
}
# decrement zone serial
function decrementZoneSerial() {
local ZONE=$1
local ZONESERIAL=$(getZoneSerial ${ZONE})
sed -i "s/${ZONESERIAL}/$((ZONESERIAL-1))/" ${ZONE_DIR}${ZONE}
}
# modernize zone serial
function modernizeZoneSerial() {
local ZONE=$1
local ZONESERIAL=$(getZoneSerial ${ZONE})
if [ "${ZONESERIAL}" -ge "${ZONESERIALDATE}" ]; then
local NEWZONESERIAL=$((ZONESERIAL+1))
else
local NEWZONESERIAL=${ZONESERIALDATE}
fi
sed -i "s/${ZONESERIAL}/${NEWZONESERIAL}/" ${ZONE_DIR}${ZONE}
}
# print all available servers
function printServers() {
echo -e "--- Available Servers ---"
for server in "${AVAILSERVERS[@]}"; do
echo Server: ${server}
done
}
# print all available zone
function printZones() {
local tempZones=()
echo -e "--- Available Zones ---"
for zone in $(grep "^zone" ${CONF_DIR}named.*.zones | awk -F '"' '{print $2}' | sort | uniq); do
if [ -f ${ZONE_DIR}${zone} ]; then
tempZones+=($(getZoneSerial ${zone}):${zone})
fi
done
# sort by serial
for i in $(echo "${tempZones[*]}" | tr " " "\n" | sort -n); do
echo Zone: $(echo "${i}" | tr ":" " ")
done
unset tempZones
}
if [ "$#" -lt 1 ]; then
print_usage
exit 1
fi
OPTS=$(getopt -o s:z:rcCGSRIDMvVhH --long help,server:,zone:,configtest,zonetest,generate,sign,reload,refresh,increment,decrement,modernize,verbose,version -- "$@")
if [ $? != 0 ]; then
echo "Failed parsing options." >&2;
exit $STATE_UNKNOWN
fi
eval set -- "$OPTS"
while true; do
case "$1" in
-s|--server)
optServer=$2; shift 2 ;;
-z|--zone)
optZone=$2; shift 2 ;;
-C|--configtest)
optConfTest="true"; shift ;;
-c|--zonetest)
optZoneTest="true"; shift ;;
-G|--generate)
optGen="true"; shift ;;
-S|--sign)
optSign="true"; shift ;;
-r|--reload)
optReload="true"; shift ;;
-R|--refresh)
optRefresh="true"; shift ;;
-I|--increment)
optIncrement="true"; shift ;;
-D|--decrement)
optDecrement="true"; shift ;;
-M|--modernize)
optModernize="true"; shift ;;
-v|--verbose)
VERBOSE=$((VERBOSE+1)); shift ;;
-V|--version)
print_version
print_changelog
exit 0
;;
-h)
print_usage
exit 0
;;
-H|--help)
print_help
exit 0
;;
--)
shift; break ;;
\?)
echo "Invalid option: $OPTARG"
print_usage
exit 1
;;
:)
echo "Option -$OPTARG requires an argument"
exit 1
;;
*)
break
echo "Syntax Error"
print_help
exit 2
;;
esac
done
getAvailableZones
getAvailableServers
if [[ "$VERBOSE" > 0 ]]; then
echo -e "##########################"
echo -e "# Start Verbose Output #"
echo -e "##########################"
printZones
if [[ "$VERBOSE" > 1 ]]; then
printServers
fi
echo -e "##########################"
echo -e "# End Verbose Output #"
echo -e "##########################"
fi
# Parse specified zones
if [[ ! -z ${optZone} ]]; then
parseSpecifiedZones
else
for (( i=0; i<${#AVAILZONES[@]}; i++ )); do
ZONES+=($(echo ${AVAILZONES[$i]} | awk -F':' '{print $2}'))
done
fi
# Parse specified server
if [[ ! -z ${optServer} ]]; then
parseSpecifiedServers
else
SERVERS=(${AVAILSERVERS[*]})
fi
if [ "${optModernize}" == "true" ] || [ "${optIncrement}" == "true" ] || [ "${optDecrement}" == "true" ]; then
if [[ "${optModernize}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
modernizeZoneSerial ${ZONES[$i]}
done
elif [[ "${optIncrement}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
incrementZoneSerial ${ZONES[$i]}
done
elif [[ "${optDecrement}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
decrementZoneSerial ${ZONES[$i]}
done
fi
# Print new zone serials after increment or decrement
printZones
fi
if [[ "${optGen}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
if [[ "$(checkZoneValidity ${ZONES[$i]})" -eq 0 ]]; then
echo "Generating KSK & ZSK for the '${ZONES[$i]}' zone"
revokeOldKeys ${ZONES[$i]}
generateZSK ${ZONES[$i]}
generateKSK ${ZONES[$i]}
removePublicKeys ${ZONES[$i]}
includePublicKeys ${ZONES[$i]}
fi
done
fi
if [[ "${optSign}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
if [[ "$(checkZoneValidity ${ZONES[$i]})" -eq 0 ]]; then
echo "zone is valid"
if [[ "$(getIncludedPublicKeys ${ZONES[$i]})" -ne 2 ]]; then
echo "zone does not contain public keys"
includePublicKeys ${ZONES[$i]}
fi
echo "Signing the '${ZONES[$i]}' zone"
signZone ${ZONES[$i]}
fi
done
fi
if [[ "${optConfTest}" == "true" ]]; then
testConfig
fi
if [[ "${optZoneTest}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
if [[ "$(checkZoneValidity ${ZONES[$i]})" -eq 0 ]]; then
testZone ${ZONES[$i]}
fi
done
fi
if [[ "${optReload}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
reloadZone ${ZONES[$i]}
done
fi
if [[ "${optRefresh}" == "true" ]]; then
for (( i=0; i<${#ZONES[@]}; i++ )); do
refreshZone ${ZONES[$i]}
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment