bin/zoneconf: Support for signed zones.
authorMark Wooding <mdw@distorted.org.uk>
Sat, 3 Dec 2011 19:28:42 +0000 (19:28 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 3 Dec 2011 19:29:09 +0000 (19:29 +0000)
Specify whether zones want signing, and in which views.  Something of
a major overhaul.

bin/zoneconf

index 7ee6d6a..aad3ac3 100755 (executable)
@@ -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.