Last active
March 19, 2019 13:42
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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