From 0af96368439a96d30e35e139632336cbd05a652a Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 3 Dec 2011 19:28:42 +0000 Subject: [PATCH] bin/zoneconf: Support for signed zones. Specify whether zones want signing, and in which views. Something of a major overhaul. --- bin/zoneconf | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 154 insertions(+), 26 deletions(-) diff --git a/bin/zoneconf b/bin/zoneconf index 7ee6d6a..aad3ac3 100755 --- a/bin/zoneconf +++ b/bin/zoneconf @@ -953,16 +953,32 @@ define-configuration-space zone ZONECFG { define-simple slave-dir "/var/cache/bind" define-simple dir-mode 2775 define-simple zone-file "%v/%z.zone" + define-simple soa-format increment define-list views * + define-list sign-views {} + define-list signzone-command { + /usr/sbin/dnssec-signzone + -g + -S + -K/var/lib/bind/key + -d/var/lib/bind/ds + -s-3600 -e+176400 + -N%q + -o%z + -f%o + %f + } + define-simple auto-dnssec off define-list reload-command {/usr/sbin/rndc reload %z IN %v} + define-list autosign-command {/usr/sbin/rndc sign %z IN %v} define-list checkzone-command { /usr/sbin/named-checkzone - -i full - -k fail - -M fail - -n fail - -S fail - -W fail + -ifull + -kfail + -Mfail + -nfail + -Sfail + -Wfail %z %f } @@ -1020,7 +1036,9 @@ define-configuration-space toplevel ZONECFG { define-simple max-zone-size [expr {512*1024}] define-list reconfig-command {/usr/sbin/rndc reconfig} - define scope {body} { preserving-config ZONECFG { uplevel 1 $body } } + define scope {body} { + preserving-config ZONECFG { uplevel 1 $body } + } define zone {name {body {}}} { global ZONES @@ -1108,26 +1126,34 @@ proc compute-zone-properties {view config} { } } - ## Main dispatch for zone categorization. - switch -exact -- $zone(config-type) { - master { - switch -exact -- $zone(type) { - static { - set zone(file-name) \ - [file join $zone(master-dir) \ - [zone-file-name $zone(mapped-view) $config]] - } - dynamic { - set zone(file-name) [file join $zone(slave-dir) \ - [zone-file-name $view $config]] - } - } + ## Work out the file names. + switch -glob -- $zone(config-type):$zone(type) { + master:static { + set dir $zone(master-dir) + set nameview $zone(mapped-view) } - slave { - set zone(file-name) [file join $zone(slave-dir) \ - [zone-file-name $view $config]] + default { + set dir $zone(slave-dir) + set nameview $view + } + } + set zone(file-name) [file join $dir \ + [zone-file-name $nameview $config]] + + ## Find out whether this zone wants signing. + set zone(sign) false + switch -glob -- $zone(config-type):$zone(type) { + master:static { + foreach sview $zone(sign-views) { + if {[string match $zone(mapped-view) $sview]} { set zone(sign) true } + } } } + if {$zone(sign)} { + set zone(server-file-name) "$zone(file-name).sig" + } else { + set zone(server-file-name) $zone(file-name) + } ## Done. return [array get zone] @@ -1162,6 +1188,35 @@ proc write-ddns-update-policy {prefix chan config} { puts $chan "${prefix}};" } +proc sign-zone-file {info input soafmt} { + ## Sign the zone described by INFO. The input zone file is INPUT; the SOA + ## should be updated according to SOAFMT. + + global QUIS + + array set zone $info + set cmd [build-command $zone(signzone-command) \ + "%z" $zone(name) \ + "%f" $zone(file-name) \ + "%o" $zone(server-file-name) \ + "%q" $soafmt] + set rc [catch { + set out [eval exec -ignorestderr $cmd] + } msg] + if {$rc} { set out $msg } + set out "| [string map [list "\n" "\n| "] $out]" + set ident "zone `$zone(name)' in view `$zone(mapped-view)'" + if {$rc} { + puts stderr "$QUIS: signing zone $ident failed..." + puts stderr $out + return false + } else { + puts "$QUIS: signing zone $ident output..." + puts $out + return true + } +} + proc write-zone-stanza {view chan config} { ## Write a `zone' stanza to CHAN for the zone described by the CONFIG ## plist in the given VIEW. @@ -1181,7 +1236,7 @@ proc write-zone-stanza {view chan config} { switch -glob -- $zone(config-type) { master { puts $chan "\ttype master;" - puts $chan "\tfile \"$zone(file-name)\";" + puts $chan "\tfile \"$zone(server-file-name)\";" switch -exact -- $zone(type) { dynamic { write-ddns-update-policy "\t" $chan $config } } @@ -1192,6 +1247,9 @@ proc write-zone-stanza {view chan config} { foreach host $zone(masters) { lappend masters [host-addr $host] } puts $chan "\tmasters { [join $masters {; }]; };" puts $chan "\tfile \"$zone(file-name)\";" + if {![string equal $zone(auto-dnssec) off]} { + puts $chan "\tauto-dnssec $zone(auto-dnssec);" + } switch -exact -- $zone(type) { dynamic { puts $chan "\tallow-update-forwarding { any; };" } } @@ -1219,22 +1277,40 @@ defcmd update {} { } { global ZONECFG ZONES CONFFILE + ## Read the configuration. confspc-eval toplevel [list source $CONFFILE] + + ## Safely update the files. set win false unwind-protect { + + ## Work through each server view. foreach view $ZONECFG(all-views) { + + ## Open an output file. set out($view) [output-file-name $view] set chan($view) [open "$out($view).new" w] + + ## Write a header. set now [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"] puts $chan($view) "### -*-conf-javaprop-*-" puts $chan($view) "### Generated at $now: do not edit" + + ## Now print a stanza for each zone in the view. foreach zone $ZONES { write-zone-stanza $view $chan($view) $zone } } + + ## Done: don't delete the output. set win true } { + + ## Close the open files. foreach view $ZONECFG(all-views) { close $chan($view) } + + ## If we succeeded, rename the output files into their proper places; + ## otherwise, delete them. if {$win} { foreach view $ZONECFG(all-views) { file rename -force -- "$out($view).new" $out($view) @@ -1249,18 +1325,22 @@ defcmd update {} { defcmd install {user view name} { help-text "Install a new zone file. -The file is for the given zone NAME and the \(user-side) VIEW. The file is +The file is for the given zone NAME and \(user-side) VIEW. The file is provided by the named USER" } { global QUIS ZONECFG ZONES CONFFILE errorInfo errorCode + ## Read the configuration. confspc-eval toplevel [list source $CONFFILE] + ## Make sure there's a temporary directory. file mkdir [file join $ZONECFG(master-dir) "tmp"] + ## Keep track of cleanup jobs. set cleanup {} unwind-protect { + ## Find out which server views are affected by this update. set matchview {} foreach iview $ZONECFG(all-views) { foreach info $ZONES { @@ -1282,6 +1362,7 @@ provided by the named USER" array unset zone array set zone $matchinfo + ## Make a new temporary file to read the zone into. set pid [pid] for {set i 0} {$i < 1000} {incr i} { set tmp [file join $ZONECFG(master-dir) "tmp" \ @@ -1296,6 +1377,7 @@ provided by the named USER" if {![info exists chan]} { error "failed to create temporary file" } set cleanup [list file delete $tmp] + ## Read the zone data from standard input into the file. set total 0 while {true} { set stuff [read stdin 4096] @@ -1321,12 +1403,21 @@ provided by the named USER" if {$rc} { puts stderr "$QUIS: zone check failed..." puts stderr $out + eval $cleanup exit 1 } else { puts "$QUIS: zone check output..." puts $out } + ## If the zone wants signing, better to do that now. + if {![sign-zone-file $matchinfo $tmp keep]} { + eval $cleanup + exit 2 + } + + ## All seems good: stash the file in the proper place and reload the + ## necessary server views. file rename -force -- $tmp $zone(file-name) set cleanup {} foreach view $matchview { @@ -1339,6 +1430,43 @@ provided by the named USER" } } +defcmd sign {} { + help-text "Sign DNSSEC zones." +} { + global QUIS ZONECFG ZONES CONFFILE + + set rc 0 + + ## Read the configuration. + confspc-eval toplevel [list source $CONFFILE] + + ## Grind through all of the zones. + foreach iview $ZONECFG(all-views) { + foreach info $ZONES { + array unset zone + set compinfo [compute-zone-properties $iview $info] + array set zone $compinfo + if {![string equal $zone(config-type) master]} { continue } + if {[string equal $zone(type) static] && $zone(sign)} { + if {![sign-zone-file $compinfo $zone(file-name) $zone(soa-format)]} { + set rc 2 + } + } elseif {[string equal $zone(type) dynamic] && + ![string equal $zone(auto-dnssec) off]} { + set cmd [build-command $zone(autosign-command) \ + "%z" $zone(name) \ + "%v" $iview] + if {[catch { exec $cmd } msg]} { + puts stderr "$QUIS: failed to reload `$zone(name)'" + puts stderr "| [string map [list "\n" "\n| "] $msg]" + set rc 2 + } + } + } + } + exit $rc +} + ###-------------------------------------------------------------------------- ### Main program. -- 2.11.0