Commit f6096b95 authored by Evan Hunt's avatar Evan Hunt

[master] dnssec-keymgr

4349.   [contrib]       kasp2policy: A python script to create a DNSSEC
                        policy file from an OpenDNSSEC KASP XML file.

4348.	[func]		dnssec-keymgr: A new python-based DNSSEC key
			management utility, which reads a policy definition
			file and can create or update DNSSEC keys as needed
			to ensure that a zone's keys match policy, roll over
			correctly on schedule, etc.  Thanks to Sebastian
			Castro for assistance in development. [RT #39211]
parent 16591ba9
4349. [contrib] kasp2policy: A python script to create a DNSSEC
policy file from an OpenDNSSEC KASP XML file.
4348. [func] dnssec-keymgr: A new python-based DNSSEC key
management utility, which reads a policy definition
file and can create or update DNSSEC keys as needed
to ensure that a zone's keys match policy, roll over
correctly on schedule, etc. Thanks to Sebastian
Castro for assistance in development. [RT #39211]
4347. [port] Corrected a build error on x86_64 Solaris. [RT #42150]
4346. [bug] Fixed a regression introduced in change #4337 which
......
......@@ -519,11 +519,12 @@ main(int argc, char **argv) {
if ((setdel && setinact && del < inact) ||
(dst_key_gettime(key, DST_TIME_INACTIVE,
&previnact) == ISC_R_SUCCESS &&
setdel && !setinact && del < previnact) ||
setdel && !setinact && !unsetinact && del < previnact) ||
(dst_key_gettime(key, DST_TIME_DELETE,
&prevdel) == ISC_R_SUCCESS &&
setinact && !setdel && prevdel < inact) ||
(!setdel && !setinact && prevdel < previnact))
setinact && !setdel && !unsetdel && prevdel < inact) ||
(!setdel && !unsetdel && !setinact && !unsetinact &&
prevdel < previnact))
fprintf(stderr, "%s: warning: Key is scheduled to "
"be deleted before it is\n\t"
"scheduled to be inactive.\n",
......
......@@ -2,3 +2,6 @@ dnssec-checkds
dnssec-checkds.py
dnssec-coverage
dnssec-coverage.py
dnssec-keymgr
dnssec-keymgr.py
*.pyc
......@@ -12,8 +12,6 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# $Id$
srcdir = @srcdir@
VPATH = @srcdir@
top_srcdir = @top_srcdir@
......@@ -22,11 +20,13 @@ top_srcdir = @top_srcdir@
PYTHON = @PYTHON@
TARGETS = dnssec-checkds dnssec-coverage
PYSRCS = dnssec-checkds.py dnssec-coverage.py
SUBDIRS = isc
TARGETS = dnssec-checkds dnssec-coverage dnssec-keymgr
PYSRCS = dnssec-checkds.py dnssec-coverage.py dnssec-keymgr.py
MANPAGES = dnssec-checkds.8 dnssec-coverage.8
HTMLPAGES = dnssec-checkds.html dnssec-coverage.html
MANPAGES = dnssec-checkds.8 dnssec-coverage.8 dnssec-keymgr.8
HTMLPAGES = dnssec-checkds.html dnssec-coverage.html dnssec-keymgr.html
MANOBJS = ${MANPAGES} ${HTMLPAGES}
@BIND9_MAKE_RULES@
......@@ -39,6 +39,10 @@ dnssec-coverage: dnssec-coverage.py
cp -f dnssec-coverage.py dnssec-coverage
chmod +x dnssec-coverage
dnssec-keymgr: dnssec-keymgr.py
cp -f dnssec-keymgr.py dnssec-keymgr
chmod +x dnssec-keymgr
doc man:: ${MANOBJS}
docclean manclean maintainer-clean::
......@@ -49,13 +53,15 @@ installdirs:
$(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${mandir}/man8
install:: ${TARGETS} installdirs
${INSTALL_SCRIPT} dnssec-checkds@EXEEXT@ ${DESTDIR}${sbindir}
${INSTALL_SCRIPT} dnssec-coverage@EXEEXT@ ${DESTDIR}${sbindir}
${INSTALL_SCRIPT} dnssec-checkds ${DESTDIR}${sbindir}
${INSTALL_SCRIPT} dnssec-coverage ${DESTDIR}${sbindir}
${INSTALL_SCRIPT} dnssec-keymgr ${DESTDIR}${sbindir}
${INSTALL_DATA} ${srcdir}/dnssec-checkds.8 ${DESTDIR}${mandir}/man8
${INSTALL_DATA} ${srcdir}/dnssec-coverage.8 ${DESTDIR}${mandir}/man8
${INSTALL_DATA} ${srcdir}/dnssec-keymgr.8 ${DESTDIR}${mandir}/man8
clean distclean::
rm -f ${TARGETS}
distclean::
rm -f dnssec-checkds.py dnssec-coverage.py
rm -f dnssec-checkds.py dnssec-coverage.py dnssec-keymgr.py
......@@ -15,314 +15,13 @@
# PERFORMANCE OF THIS SOFTWARE.
############################################################################
import argparse
import pprint
import os
import sys
prog='dnssec-checkds'
sys.path.insert(0, os.path.dirname(sys.argv[0]))
sys.path.insert(1, os.path.join('@prefix@', 'lib'))
# These routines permit platform-independent location of BIND 9 tools
if os.name == 'nt':
import win32con
import win32api
def prefix(bindir = ''):
if os.name != 'nt':
return os.path.join('@prefix@', bindir)
bind_subkey = "Software\\ISC\\BIND"
hKey = None
keyFound = True
try:
hKey = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, bind_subkey)
except:
keyFound = False
if keyFound:
try:
(namedBase, _) = win32api.RegQueryValueEx(hKey, "InstallDir")
except:
keyFound = False
win32api.RegCloseKey(hKey)
if keyFound:
return os.path.join(namedBase, bindir)
return os.path.join(win32api.GetSystemDirectory(), bindir)
def shellquote(s):
if os.name == 'nt':
return '"' + s.replace('"', '"\\"') + '"'
return "'" + s.replace("'", "'\\''") + "'"
############################################################################
# DSRR class:
# Delegation Signer (DS) resource record
############################################################################
class DSRR:
hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384' }
rrname=''
rrclass='IN'
rrtype='DS'
keyid=None
keyalg=None
hashalg=None
digest=''
ttl=0
def __init__(self, rrtext):
if not rrtext:
return
fields = rrtext.split()
if len(fields) < 7:
return
self.rrname = fields[0].lower()
fields = fields[1:]
if fields[0].upper() in ['IN','CH','HS']:
self.rrclass = fields[0].upper()
fields = fields[1:]
else:
self.ttl = int(fields[0])
self.rrclass = fields[1].upper()
fields = fields[2:]
if fields[0].upper() != 'DS':
raise Exception
self.rrtype = 'DS'
self.keyid = int(fields[1])
self.keyalg = int(fields[2])
self.hashalg = int(fields[3])
self.digest = ''.join(fields[4:]).upper()
def __repr__(self):
return('%s %s %s %d %d %d %s' %
(self.rrname, self.rrclass, self.rrtype, self.keyid,
self.keyalg, self.hashalg, self.digest))
def __eq__(self, other):
return self.__repr__() == other.__repr__()
############################################################################
# DLVRR class:
# DNSSEC Lookaside Validation (DLV) resource record
############################################################################
class DLVRR:
hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384' }
parent=''
dlvname=''
rrname='IN'
rrclass='IN'
rrtype='DLV'
keyid=None
keyalg=None
hashalg=None
digest=''
ttl=0
def __init__(self, rrtext, dlvname):
if not rrtext:
return
fields = rrtext.split()
if len(fields) < 7:
return
self.dlvname = dlvname.lower()
parent = fields[0].lower().strip('.').split('.')
parent.reverse()
dlv = dlvname.split('.')
dlv.reverse()
while len(dlv) != 0 and len(parent) != 0 and parent[0] == dlv[0]:
parent = parent[1:]
dlv = dlv[1:]
if len(dlv) != 0:
raise Exception
parent.reverse()
self.parent = '.'.join(parent)
self.rrname = self.parent + '.' + self.dlvname + '.'
fields = fields[1:]
if fields[0].upper() in ['IN','CH','HS']:
self.rrclass = fields[0].upper()
fields = fields[1:]
else:
self.ttl = int(fields[0])
self.rrclass = fields[1].upper()
fields = fields[2:]
if fields[0].upper() != 'DLV':
raise Exception
self.rrtype = 'DLV'
self.keyid = int(fields[1])
self.keyalg = int(fields[2])
self.hashalg = int(fields[3])
self.digest = ''.join(fields[4:]).upper()
def __repr__(self):
return('%s %s %s %d %d %d %s' %
(self.rrname, self.rrclass, self.rrtype,
self.keyid, self.keyalg, self.hashalg, self.digest))
def __eq__(self, other):
return self.__repr__() == other.__repr__()
############################################################################
# checkds:
# Fetch DS RRset for the given zone from the DNS; fetch DNSKEY
# RRset from the masterfile if specified, or from DNS if not.
# Generate a set of expected DS records from the DNSKEY RRset,
# and report on congruency.
############################################################################
def checkds(zone, masterfile = None):
dslist=[]
fp=os.popen("%s +noall +answer -t ds -q %s" %
(shellquote(args.dig), shellquote(zone)))
for line in fp:
dslist.append(DSRR(line))
dslist = sorted(dslist, key=lambda ds: (ds.keyid, ds.keyalg, ds.hashalg))
fp.close()
dsklist=[]
if masterfile:
fp = os.popen("%s -f %s %s " %
(shellquote(args.dsfromkey), shellquote(masterfile),
shellquote(zone)))
else:
fp = os.popen("%s +noall +answer -t dnskey -q %s | %s -f - %s" %
(shellquote(args.dig), shellquote(zone),
shellquote(args.dsfromkey), shellquote(zone)))
for line in fp:
dsklist.append(DSRR(line))
fp.close()
if (len(dsklist) < 1):
print ("No DNSKEY records found in zone apex")
return False
found = False
for ds in dsklist:
if ds in dslist:
print ("DS for KSK %s/%03d/%05d (%s) found in parent" %
(ds.rrname.strip('.'), ds.keyalg,
ds.keyid, DSRR.hashalgs[ds.hashalg]))
found = True
else:
print ("DS for KSK %s/%03d/%05d (%s) missing from parent" %
(ds.rrname.strip('.'), ds.keyalg,
ds.keyid, DSRR.hashalgs[ds.hashalg]))
if not found:
print ("No DS records were found for any DNSKEY")
return found
############################################################################
# checkdlv:
# Fetch DLV RRset for the given zone from the DNS; fetch DNSKEY
# RRset from the masterfile if specified, or from DNS if not.
# Generate a set of expected DLV records from the DNSKEY RRset,
# and report on congruency.
############################################################################
def checkdlv(zone, lookaside, masterfile = None):
dlvlist=[]
fp=os.popen("%s +noall +answer -t dlv -q %s" %
(shellquote(args.dig), shellquote(zone + '.' + lookaside)))
for line in fp:
dlvlist.append(DLVRR(line, lookaside))
dlvlist = sorted(dlvlist,
key=lambda dlv: (dlv.keyid, dlv.keyalg, dlv.hashalg))
fp.close()
#
# Fetch DNSKEY records from DNS and generate DLV records from them
#
dlvklist=[]
if masterfile:
fp = os.popen("%s -f %s -l %s %s " %
(args.dsfromkey, masterfile, lookaside, zone))
else:
fp = os.popen("%s +noall +answer -t dnskey %s | %s -f - -l %s %s"
% (shellquote(args.dig), shellquote(zone),
shellquote(args.dsfromkey), shellquote(lookaside),
shellquote(zone)))
for line in fp:
dlvklist.append(DLVRR(line, lookaside))
fp.close()
if (len(dlvklist) < 1):
print ("No DNSKEY records found in zone apex")
return False
found = False
for dlv in dlvklist:
if dlv in dlvlist:
print ("DLV for KSK %s/%03d/%05d (%s) found in %s" %
(dlv.parent, dlv.keyalg, dlv.keyid,
DLVRR.hashalgs[dlv.hashalg], dlv.dlvname))
found = True
else:
print ("DLV for KSK %s/%03d/%05d (%s) missing from %s" %
(dlv.parent, dlv.keyalg, dlv.keyid,
DLVRR.hashalgs[dlv.hashalg], dlv.dlvname))
if not found:
print ("No DLV records were found for any DNSKEY")
return found
############################################################################
# parse_args:
# Read command line arguments, set global 'args' structure
############################################################################
def parse_args():
global args
parser = argparse.ArgumentParser(description=prog + ': checks DS coverage')
bindir = 'bin'
if os.name == 'nt':
sbindir = 'bin'
else:
sbindir = 'sbin'
parser.add_argument('zone', type=str, help='zone to check')
parser.add_argument('-f', '--file', dest='masterfile', type=str,
help='zone master file')
parser.add_argument('-l', '--lookaside', dest='lookaside', type=str,
help='DLV lookaside zone')
parser.add_argument('-d', '--dig', dest='dig',
default=os.path.join(prefix(bindir), 'dig'),
type=str, help='path to \'dig\'')
parser.add_argument('-D', '--dsfromkey', dest='dsfromkey',
default=os.path.join(prefix(sbindir),
'dnssec-dsfromkey'),
type=str, help='path to \'dig\'')
parser.add_argument('-v', '--version', action='version',
version='@BIND9_VERSION@')
args = parser.parse_args()
args.zone = args.zone.strip('.')
if args.lookaside:
lookaside = args.lookaside.strip('.')
############################################################################
# Main
############################################################################
def main():
parse_args()
if args.lookaside:
found = checkdlv(args.zone, args.lookaside, args.masterfile)
else:
found = checkds(args.zone, args.masterfile)
exit(0 if found else 1)
import isc.checkds
if __name__ == "__main__":
main()
isc.checkds.main()
......@@ -56,7 +56,7 @@
<arg choice="opt" rep="norepeat"><option>-c <replaceable class="parameter">compilezone path</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-k</option></arg>
<arg choice="opt" rep="norepeat"><option>-z</option></arg>
<arg choice="opt" rep="norepeat">zone</arg>
<arg choice="opt" rep="repeat">zone</arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -151,10 +151,15 @@
'd' for days, 'w' for weeks, 'mo' for months, 'y' for years.
</para>
<para>
This option is mandatory unless the <option>-f</option> has
been used to specify a zone file. (If <option>-f</option> has
This option is not necessary if the <option>-f</option> has
been used to specify a zone file. If <option>-f</option> has
been specified, this option may still be used; it will override
the value found in the file.)
the value found in the file.
</para>
<para>
If this option is not used and the maximum TTL cannot be retrieved
from a zone file, a warning is generated and a default value of
1 week is used.
</para>
</listitem>
</varlistentry>
......@@ -166,11 +171,10 @@
Sets the value to be used as the DNSKEY TTL for the zone or
zones being analyzed when determining whether there is a
possibility of validation failure. When a key is rolled (that
is, replaced with a new key), there must be enough time
for the old DNSKEY RRset to have expired from resolver caches
before the new key is activated and begins generating
signatures. If that condition does not apply, a warning
will be generated.
is, replaced with a new key), there must be enough time for the
old DNSKEY RRset to have expired from resolver caches before
the new key is activated and begins generating signatures. If
that condition does not apply, a warning will be generated.
</para>
<para>
The length of the TTL can be set in seconds, or in larger units
......@@ -178,12 +182,18 @@
'd' for days, 'w' for weeks, 'mo' for months, 'y' for years.
</para>
<para>
This option is mandatory unless the <option>-f</option> has
been used to specify a zone file, or a default key TTL was
set with the <option>-L</option> to
<command>dnssec-keygen</command>. (If either of those is true,
this option may still be used; it will override the value found
in the zone or key file.)
This option is not necessary if <option>-f</option> has
been used to specify a zone file from which the TTL
of the DNSKEY RRset can be read, or if a default key TTL was
set using ith the <option>-L</option> to
<command>dnssec-keygen</command>. If either of those is true,
this option may still be used; it will override the values
found in the zone file or the key file.
</para>
<para>
If this option is not used and the key TTL cannot be retrieved
from the zone file or the key file, then a warning is generated
and a default value of 1 day is used.
</para>
</listitem>
</varlistentry>
......
This diff is collapsed.
<!--
- Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- PERFORMANCE OF THIS SOFTWARE.
-->
<!-- Converted by db4-upgrade version 1.0 -->
<refentry xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="man.dnssec-keymgr">
<info>
<date>2016-04-03</date>
</info>
<refentryinfo>
<corpname>ISC</corpname>
<corpauthor>Internet Systems Consortium, Inc.</corpauthor>
</refentryinfo>
<refmeta>
<refentrytitle><application>dnssec-keymgr</application></refentrytitle>
<manvolnum>8</manvolnum>
<refmiscinfo>BIND9</refmiscinfo>
</refmeta>
<refnamediv>
<refname><application>dnssec-keymgr</application></refname>
<refpurpose>Ensures correct DNSKEY coverage for a zone based on a defined policy</refpurpose>
</refnamediv>
<docinfo>
<copyright>
<year>2016</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
<refsynopsisdiv>
<cmdsynopsis sepchar=" ">
<command>dnssec-keymgr</command>
<arg choice="opt" rep="norepeat"><option>-K <replaceable class="parameter">directory</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-c <replaceable class="parameter">file</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-d <replaceable class="parameter">time</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-k</option></arg>
<arg choice="opt" rep="norepeat"><option>-z</option></arg>
<arg choice="opt" rep="norepeat"><option>-g <replaceable class="parameter">path</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-s <replaceable class="parameter">path</replaceable></option></arg>
<arg choice="opt" rep="repeat">zone</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsection><info><title>DESCRIPTION</title></info>
<para>
<command>dnssec-keymgr</command>
is a high level Python wrapper to facilitate the key rollover
process for zones handled by BIND. It uses the BIND commands
for manipulating DNSSEC key metadata:
<command>dnssec-keygen</command> and
<command>dnssec-settime</command>.
</para>
<para>
DNSSEC policy can be read from a configuration file (default
<filename>/etc/dnssec.policy</filename>), from which the key
parameters, publication and rollover schedule, and desired
coverage duration for any given zone can be determined. This
file may be used to define individual DNSSEC policies on a
per-zone basis, or to set a default policy used for all zones.
</para>
<para>
When <command>dnssec-keymgr</command> runs, it examines the DNSSEC
keys for one or more zones, comparing their timing metadata against
the policies for those zones. If key settings do not conform to the
DNSSEC policy (for example, because the policy has been changed),
they are automatically corrected.
</para>
</para>
A zone policy can specify a duration for which we want to
ensure the key correctness (<option>coverage</option>). It can
also specify a rollover period (<option>roll-period</option>).
If policy indicates that a key should roll over before the
coverage period ends, then a successor key will automatically be
created and added to the end of the key series.
<para>
<para>
If zones are specified on the command line,
<command>dnssec-keymgr</command> will examine only those zones.
If a specified zone does not already have keys in place, then
keys will be generated for it according to policy.
</para>
<para>
If zones are <emphasis>not</emphasis> specified on the command
line, then <command>dnssec-keymgr</command> will search the
key directory (either the current working directory or the directory
set by the <option>-K</option> option), and check the keys for
all the zones represented in the directory.
</para>
<para>
It is expected that this tool will be run automatically and
unattended (for example, by <command>cron</command>).
</para>
</refsection>
<refsection><info><title>OPTIONS</title></info>
<variablelist>
<varlistentry>
<term>-K <replaceable class="parameter">directory</replaceable></term>
<listitem>
<para>
Sets the directory in which keys can be found. Defaults to the
current working directory.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-c <replaceable class="parameter">file</replaceable></term>
<listitem>
<para>
If <option>-c</option> is specified, then the DNSSEC
policy is read from <option>file</option>. (If not
specified, then the policy is read from
<filename>/etc/policy.conf</filename>; if that file
doesn't exist, a built-in global default policy is used.)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-f</term>
<listitem>
<para>
Force: allow updating of key events even if they are
already in the past. This is not recommended for use with
zones in which keys have already been published. However,
if a set of keys has been generated all of which have
publication and activation dates in the past, but the
keys have not been published in a zone as yet, then this
option can be used to clean them up and turn them into a
proper series of keys with appropriate rollover intervals.
</para>
</listitem>
</varlistentry>