You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6053 lines
213 KiB
6053 lines
213 KiB
From 923ea26e87ba7c8b8c818dc6c996604fc709b05a Mon Sep 17 00:00:00 2001 |
|
From: Evan Hunt <each@isc.org> |
|
Date: Thu, 28 Apr 2016 00:12:33 -0700 |
|
Subject: [PATCH] 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] |
|
|
|
Adapt keymgr and coverage tests to v9.9 |
|
--- |
|
bin/dnssec/dnssec-settime.c | 7 +- |
|
bin/python/.gitignore | 7 + |
|
bin/python/Makefile.in | 20 +- |
|
bin/python/dnssec-keymgr.docbook | 354 ++++++++++ |
|
bin/python/dnssec-keymgr.py.in | 27 + |
|
bin/python/isc/.gitignore | 3 + |
|
bin/python/isc/Makefile.in | 67 ++ |
|
bin/python/isc/__init__.py | 25 + |
|
bin/python/isc/checkds.py | 189 ++++++ |
|
bin/python/isc/coverage.py | 292 +++++++++ |
|
bin/python/isc/dnskey.py | 504 +++++++++++++++ |
|
bin/python/isc/eventlist.py | 171 +++++ |
|
bin/python/isc/keydict.py | 89 +++ |
|
bin/python/isc/keyevent.py | 81 +++ |
|
bin/python/isc/keymgr.py | 152 +++++ |
|
bin/python/isc/keyseries.py | 194 ++++++ |
|
bin/python/isc/keyzone.py | 60 ++ |
|
bin/python/isc/policy.py | 690 ++++++++++++++++++++ |
|
bin/python/isc/tests/Makefile.in | 33 + |
|
bin/python/isc/tests/dnskey_test.py | 57 ++ |
|
bin/python/isc/tests/policy_test.py | 90 +++ |
|
bin/python/isc/tests/test-policies/01-keysize.pol | 41 ++ |
|
.../isc/tests/test-policies/02-prepublish.pol | 31 + |
|
.../isc/tests/test-policies/03-postpublish.pol | 31 + |
|
.../tests/test-policies/04-combined-pre-post.pol | 55 ++ |
|
.../isc/tests/testdata/Kexample.com.+007+35529.key | 8 + |
|
.../tests/testdata/Kexample.com.+007+35529.private | 18 + |
|
bin/python/isc/utils.py.in | 57 ++ |
|
bin/tests/system/conf.sh.in | 9 +- |
|
bin/tests/system/keymgr/01-ksk-inactive/README | 2 + |
|
bin/tests/system/keymgr/01-ksk-inactive/expect | 9 + |
|
bin/tests/system/keymgr/02-zsk-inactive/README | 2 + |
|
bin/tests/system/keymgr/02-zsk-inactive/expect | 9 + |
|
bin/tests/system/keymgr/03-ksk-unpublished/README | 2 + |
|
bin/tests/system/keymgr/03-ksk-unpublished/expect | 9 + |
|
bin/tests/system/keymgr/04-zsk-unpublished/README | 2 + |
|
bin/tests/system/keymgr/04-zsk-unpublished/expect | 9 + |
|
bin/tests/system/keymgr/05-ksk-unpub-active/README | 3 + |
|
bin/tests/system/keymgr/05-ksk-unpub-active/expect | 9 + |
|
bin/tests/system/keymgr/06-zsk-unpub-active/README | 3 + |
|
bin/tests/system/keymgr/06-zsk-unpub-active/expect | 9 + |
|
bin/tests/system/keymgr/07-ksk-ttl/README | 2 + |
|
bin/tests/system/keymgr/07-ksk-ttl/expect | 9 + |
|
bin/tests/system/keymgr/08-zsk-ttl/README | 2 + |
|
bin/tests/system/keymgr/08-zsk-ttl/expect | 9 + |
|
bin/tests/system/keymgr/09-no-keys/README | 1 + |
|
bin/tests/system/keymgr/09-no-keys/expect | 9 + |
|
bin/tests/system/keymgr/10-change-roll/README | 3 + |
|
bin/tests/system/keymgr/10-change-roll/expect | 9 + |
|
bin/tests/system/keymgr/11-many-simul/README | 2 + |
|
bin/tests/system/keymgr/11-many-simul/expect | 9 + |
|
bin/tests/system/keymgr/12-many-active/README | 2 + |
|
bin/tests/system/keymgr/12-many-active/expect | 9 + |
|
bin/tests/system/keymgr/13-noroll/README | 2 + |
|
bin/tests/system/keymgr/13-noroll/expect | 9 + |
|
bin/tests/system/keymgr/14-wrongalg/README | 2 + |
|
bin/tests/system/keymgr/14-wrongalg/expect | 9 + |
|
bin/tests/system/keymgr/15-unspec/README | 2 + |
|
bin/tests/system/keymgr/15-unspec/expect | 9 + |
|
bin/tests/system/keymgr/16-wrongalg-unspec/README | 2 + |
|
bin/tests/system/keymgr/16-wrongalg-unspec/expect | 9 + |
|
bin/tests/system/keymgr/17-noforce/README | 2 + |
|
bin/tests/system/keymgr/17-noforce/expect | 9 + |
|
bin/tests/system/keymgr/clean.sh | 21 + |
|
bin/tests/system/keymgr/policy.conf | 10 + |
|
bin/tests/system/keymgr/policy.good | 170 +++++ |
|
bin/tests/system/keymgr/policy.sample | 40 ++ |
|
bin/tests/system/keymgr/prereq.sh | 30 + |
|
bin/tests/system/keymgr/setup.sh | 214 ++++++ |
|
bin/tests/system/keymgr/testpolicy.py | 29 + |
|
bin/tests/system/keymgr/tests.sh | 106 +++ |
|
configure | 11 + |
|
configure.in | 9 + |
|
contrib/kasp/README | 11 + |
|
contrib/kasp/kasp.xml | 134 ++++ |
|
contrib/kasp/kasp2policy.py | 209 ++++++ |
|
contrib/kasp/policy.good | 24 + |
|
doc/arm/notes.xml | 714 +++++++++++++++++++++ |
|
78 files changed, 5271 insertions(+), 12 deletions(-) |
|
create mode 100644 bin/python/.gitignore |
|
create mode 100644 bin/python/dnssec-keymgr.docbook |
|
create mode 100644 bin/python/dnssec-keymgr.py.in |
|
create mode 100644 bin/python/isc/.gitignore |
|
create mode 100644 bin/python/isc/Makefile.in |
|
create mode 100644 bin/python/isc/__init__.py |
|
create mode 100644 bin/python/isc/checkds.py |
|
create mode 100644 bin/python/isc/coverage.py |
|
create mode 100644 bin/python/isc/dnskey.py |
|
create mode 100644 bin/python/isc/eventlist.py |
|
create mode 100644 bin/python/isc/keydict.py |
|
create mode 100644 bin/python/isc/keyevent.py |
|
create mode 100644 bin/python/isc/keymgr.py |
|
create mode 100644 bin/python/isc/keyseries.py |
|
create mode 100644 bin/python/isc/keyzone.py |
|
create mode 100644 bin/python/isc/policy.py |
|
create mode 100644 bin/python/isc/tests/Makefile.in |
|
create mode 100644 bin/python/isc/tests/dnskey_test.py |
|
create mode 100644 bin/python/isc/tests/policy_test.py |
|
create mode 100644 bin/python/isc/tests/test-policies/01-keysize.pol |
|
create mode 100644 bin/python/isc/tests/test-policies/02-prepublish.pol |
|
create mode 100644 bin/python/isc/tests/test-policies/03-postpublish.pol |
|
create mode 100644 bin/python/isc/tests/test-policies/04-combined-pre-post.pol |
|
create mode 100644 bin/python/isc/tests/testdata/Kexample.com.+007+35529.key |
|
create mode 100644 bin/python/isc/tests/testdata/Kexample.com.+007+35529.private |
|
create mode 100644 bin/python/isc/utils.py.in |
|
create mode 100644 bin/tests/system/keymgr/01-ksk-inactive/README |
|
create mode 100644 bin/tests/system/keymgr/01-ksk-inactive/expect |
|
create mode 100644 bin/tests/system/keymgr/02-zsk-inactive/README |
|
create mode 100644 bin/tests/system/keymgr/02-zsk-inactive/expect |
|
create mode 100644 bin/tests/system/keymgr/03-ksk-unpublished/README |
|
create mode 100644 bin/tests/system/keymgr/03-ksk-unpublished/expect |
|
create mode 100644 bin/tests/system/keymgr/04-zsk-unpublished/README |
|
create mode 100644 bin/tests/system/keymgr/04-zsk-unpublished/expect |
|
create mode 100644 bin/tests/system/keymgr/05-ksk-unpub-active/README |
|
create mode 100644 bin/tests/system/keymgr/05-ksk-unpub-active/expect |
|
create mode 100644 bin/tests/system/keymgr/06-zsk-unpub-active/README |
|
create mode 100644 bin/tests/system/keymgr/06-zsk-unpub-active/expect |
|
create mode 100644 bin/tests/system/keymgr/07-ksk-ttl/README |
|
create mode 100644 bin/tests/system/keymgr/07-ksk-ttl/expect |
|
create mode 100644 bin/tests/system/keymgr/08-zsk-ttl/README |
|
create mode 100644 bin/tests/system/keymgr/08-zsk-ttl/expect |
|
create mode 100644 bin/tests/system/keymgr/09-no-keys/README |
|
create mode 100644 bin/tests/system/keymgr/09-no-keys/expect |
|
create mode 100644 bin/tests/system/keymgr/10-change-roll/README |
|
create mode 100644 bin/tests/system/keymgr/10-change-roll/expect |
|
create mode 100644 bin/tests/system/keymgr/11-many-simul/README |
|
create mode 100644 bin/tests/system/keymgr/11-many-simul/expect |
|
create mode 100644 bin/tests/system/keymgr/12-many-active/README |
|
create mode 100644 bin/tests/system/keymgr/12-many-active/expect |
|
create mode 100644 bin/tests/system/keymgr/13-noroll/README |
|
create mode 100644 bin/tests/system/keymgr/13-noroll/expect |
|
create mode 100644 bin/tests/system/keymgr/14-wrongalg/README |
|
create mode 100644 bin/tests/system/keymgr/14-wrongalg/expect |
|
create mode 100644 bin/tests/system/keymgr/15-unspec/README |
|
create mode 100644 bin/tests/system/keymgr/15-unspec/expect |
|
create mode 100644 bin/tests/system/keymgr/16-wrongalg-unspec/README |
|
create mode 100644 bin/tests/system/keymgr/16-wrongalg-unspec/expect |
|
create mode 100644 bin/tests/system/keymgr/17-noforce/README |
|
create mode 100644 bin/tests/system/keymgr/17-noforce/expect |
|
create mode 100644 bin/tests/system/keymgr/clean.sh |
|
create mode 100644 bin/tests/system/keymgr/policy.conf |
|
create mode 100644 bin/tests/system/keymgr/policy.good |
|
create mode 100644 bin/tests/system/keymgr/policy.sample |
|
create mode 100644 bin/tests/system/keymgr/prereq.sh |
|
create mode 100644 bin/tests/system/keymgr/setup.sh |
|
create mode 100644 bin/tests/system/keymgr/testpolicy.py |
|
create mode 100644 bin/tests/system/keymgr/tests.sh |
|
create mode 100644 contrib/kasp/README |
|
create mode 100644 contrib/kasp/kasp.xml |
|
create mode 100644 contrib/kasp/kasp2policy.py |
|
create mode 100644 contrib/kasp/policy.good |
|
create mode 100644 doc/arm/notes.xml |
|
|
|
diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c |
|
index c71cac7..71c1ac5 100644 |
|
--- a/bin/dnssec/dnssec-settime.c |
|
+++ b/bin/dnssec/dnssec-settime.c |
|
@@ -492,11 +492,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", |
|
diff --git a/bin/python/.gitignore b/bin/python/.gitignore |
|
new file mode 100644 |
|
index 0000000..2e6963d |
|
--- /dev/null |
|
+++ b/bin/python/.gitignore |
|
@@ -0,0 +1,7 @@ |
|
+dnssec-checkds |
|
+dnssec-checkds.py |
|
+dnssec-coverage |
|
+dnssec-coverage.py |
|
+dnssec-keymgr |
|
+dnssec-keymgr.py |
|
+*.pyc |
|
diff --git a/bin/python/Makefile.in b/bin/python/Makefile.in |
|
index 12695ed..1e4af9c 100644 |
|
--- a/bin/python/Makefile.in |
|
+++ b/bin/python/Makefile.in |
|
@@ -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 |
|
-SRCS = dnssec-checkds.py dnssec-coverage.py |
|
+TARGETS = dnssec-checkds dnssec-coverage dnssec-keymgr |
|
+SRCS = dnssec-checkds.py dnssec-coverage.py dnssec-keymgr.py |
|
+ |
|
+SUBDIRS = isc |
|
|
|
-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@ |
|
@@ -35,6 +35,10 @@ dnssec-checkds: dnssec-checkds.py |
|
|
|
dnssec-coverage: dnssec-coverage.py |
|
|
|
+dnssec-keymgr: dnssec-keymgr.py |
|
+ cp -f dnssec-keymgr.py dnssec-keymgr |
|
+ chmod +x dnssec-keymgr |
|
+ |
|
doc man:: ${MANOBJS} |
|
|
|
docclean manclean maintainer-clean:: |
|
@@ -47,11 +51,13 @@ installdirs: |
|
install:: ${TARGETS} installdirs |
|
${INSTALL_PROGRAM} dnssec-checkds@EXEEXT@ ${DESTDIR}${sbindir} |
|
${INSTALL_PROGRAM} dnssec-coverage@EXEEXT@ ${DESTDIR}${sbindir} |
|
+ ${INSTALL_PROGRAM} dnssec-keymgr@EXEEXT@ ${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 |
|
diff --git a/bin/python/dnssec-keymgr.docbook b/bin/python/dnssec-keymgr.docbook |
|
new file mode 100644 |
|
index 0000000..2cccb49 |
|
--- /dev/null |
|
+++ b/bin/python/dnssec-keymgr.docbook |
|
@@ -0,0 +1,354 @@ |
|
+<!-- |
|
+ - 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> |
|
+ |
|
+ <varlistentry> |
|
+ <term>-q</term> |
|
+ <listitem> |
|
+ <para> |
|
+ Quiet: suppress printing of <command>dnssec-keygen</command> |
|
+ and <command>dnssec-settime</command>. |
|
+ </para> |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ |
|
+ <varlistentry> |
|
+ <term>-k</term> |
|
+ <listitem> |
|
+ <para> |
|
+ Only apply policies to KSK keys. |
|
+ </para> |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ |
|
+ <varlistentry> |
|
+ <term>-z</term> |
|
+ <listitem> |
|
+ <para> |
|
+ Only apply policies to ZSK keys. |
|
+ </para> |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ |
|
+ <varlistentry> |
|
+ <term>-g <replaceable class="parameter">keygen path</replaceable></term> |
|
+ <listitem> |
|
+ <para> |
|
+ Specifies a path to a <command>dnssec-keygen</command> binary. |
|
+ Used for testing. |
|
+ </para> |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ |
|
+ <varlistentry> |
|
+ <term>-s <replaceable class="parameter">settime path</replaceable></term> |
|
+ <listitem> |
|
+ <para> |
|
+ Specifies a path to a <command>dnssec-settime</command> binary. |
|
+ Used for testing. |
|
+ </para> |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ </variablelist> |
|
+ </refsection> |
|
+ |
|
+ <refsection><info><title>POLICY CONFIGURATION</title></info> |
|
+ <para> |
|
+ The <filename>policy.conf</filename> file can specify three kinds |
|
+ of policies: |
|
+ </para> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <emphasis>Policy classes</emphasis> |
|
+ (<option>policy <replaceable>name</replaceable> { ... };</option>) |
|
+ can be inherited by zone policies or other policy classes; these |
|
+ can be used to create sets of different security profiles. For |
|
+ example, a policy class <userinput>normal</userinput> might specify |
|
+ 1024-bit key sizes, but a class <userinput>extra</userinput> might |
|
+ specify 2048 bits instead; <userinput>extra</userinput> would be |
|
+ used for zones that had unusually high security needs. |
|
+ </listitem> |
|
+ <listitem> |
|
+ Algorithm policies: |
|
+ (<option>algorithm-policy <replaceable>algorithm</replaceable> { ... };</option> ) |
|
+ override default per-algorithm settings. For example, by default, |
|
+ RSASHA256 keys use 2048-bit key sizes for both KSK and ZSK. This |
|
+ can be modified using <command>algorithm-policy</command>, and the |
|
+ new key sizes would then be used for any key of type RSASHA256. |
|
+ </listitem> |
|
+ <listitem> |
|
+ Zone policies: |
|
+ (<option>zone <replaceable>name</replaceable> { ... };</option> ) |
|
+ set policy for a single zone by name. A zone policy can inherit |
|
+ a policy class by including a <option>policy</option> option. |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ <para> |
|
+ Options that can be specified in policies: |
|
+ </para> |
|
+ <variablelist> |
|
+ <varlistentry> |
|
+ <term><command>directory</command></term> |
|
+ <listitem> |
|
+ Specifies the directory in which keys should be stored. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>algorithm</command></term> |
|
+ <listitem> |
|
+ The key algorithm. If no policy is defined, the default is |
|
+ RSASHA256. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>keyttl</command></term> |
|
+ <listitem> |
|
+ The key TTL. If no policy is defined, the default is one hour. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>coverage</command></term> |
|
+ <listitem> |
|
+ The length of time to ensure that keys will be correct; no action |
|
+ will be taken to create new keys to be activated after this time. |
|
+ This can be represented as a number of seconds, or as a duration using |
|
+ human-readable units (examples: "1y" or "6 months"). |
|
+ A default value for this option can be set in algorithm policies |
|
+ as well as in policy classes or zone policies. |
|
+ If no policy is configured, the default is six months. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>key-size</command></term> |
|
+ <listitem> |
|
+ Specifies the number of bits to use in creating keys. |
|
+ Takes two arguments: keytype (eihter "zsk" or "ksk") and size. |
|
+ A default value for this option can be set in algorithm policies |
|
+ as well as in policy classes or zone policies. If no policy is |
|
+ configured, the default is 1024 bits for DSA keys and 2048 for |
|
+ RSA. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>roll-period</command></term> |
|
+ <listitem> |
|
+ How frequently keys should be rolled over. |
|
+ Takes two arguments: keytype (eihter "zsk" or "ksk") and a duration. |
|
+ A default value for this option can be set in algorithm policies |
|
+ as well as in policy classes or zone policies. If no policy is |
|
+ configured, the default is one year for ZSK's. KSK's do not |
|
+ roll over by default. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>pre-publish</command></term> |
|
+ <listitem> |
|
+ How long before activation a key should be published. Note: If |
|
+ <option>roll-period</option> is not set, this value is ignored. |
|
+ Takes two arguments: keytype (either "zsk" or "ksk") and a duration. |
|
+ A default value for this option can be set in algorithm policies |
|
+ as well as in policy classes or zone policies. The default is |
|
+ one month. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>post-publish</command></term> |
|
+ <listitem> |
|
+ How long after inactivation a key should be deleted from the zone. |
|
+ Note: If <option>roll-period</option> is not set, this value is ignored. |
|
+ Takes two arguments: keytype (eihter "zsk" or "ksk") and a duration. |
|
+ A default value for this option can be set in algorithm policies |
|
+ as well as in policy classes or zone policies. The default is one |
|
+ month. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ <varlistentry> |
|
+ <term><command>standby</command></term> |
|
+ <listitem> |
|
+ Not yet implemented. |
|
+ </listitem> |
|
+ </varlistentry> |
|
+ </variablelist> |
|
+ </refsection> |
|
+ |
|
+ <refsection><info><title>REMAINING WORK</title></info> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ Enable scheduling of KSK rollovers using the <option>-P sync</option> |
|
+ and <option>-D sync</option> options to |
|
+ <command>dnssec-keygen</command> and |
|
+ <command>dnssec-settime</command>. Check the parent zone |
|
+ (as in <command>dnssec-checkds</command>) to determine when it's |
|
+ safe for the key to roll. |
|
+ </listitem> |
|
+ <listitem> |
|
+ Allow configuration of standby keys and use of the REVOKE bit, |
|
+ for keys that use RFC 5011 semantics. |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ </refsection> |
|
+ |
|
+ <refsection><info><title>SEE ALSO</title></info> |
|
+ <para> |
|
+ <citerefentry> |
|
+ <refentrytitle>dnssec-coverage</refentrytitle><manvolnum>8</manvolnum> |
|
+ </citerefentry>, |
|
+ <citerefentry> |
|
+ <refentrytitle>dnssec-keygen</refentrytitle><manvolnum>8</manvolnum> |
|
+ </citerefentry>, |
|
+ <citerefentry> |
|
+ <refentrytitle>dnssec-settime</refentrytitle><manvolnum>8</manvolnum> |
|
+ </citerefentry>, |
|
+ <citerefentry> |
|
+ <refentrytitle>dnssec-checkds</refentrytitle><manvolnum>8</manvolnum> |
|
+ </citerefentry> |
|
+ </para> |
|
+ </refsection> |
|
+ |
|
+</refentry> |
|
diff --git a/bin/python/dnssec-keymgr.py.in b/bin/python/dnssec-keymgr.py.in |
|
new file mode 100644 |
|
index 0000000..23d563d |
|
--- /dev/null |
|
+++ b/bin/python/dnssec-keymgr.py.in |
|
@@ -0,0 +1,27 @@ |
|
+#!@PYTHON@ |
|
+############################################################################ |
|
+# Copyright (C) 2012-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. |
|
+############################################################################ |
|
+ |
|
+import os |
|
+import sys |
|
+ |
|
+sys.path.insert(0, os.path.dirname(sys.argv[0])) |
|
+sys.path.insert(1, os.path.join('@prefix@', 'lib')) |
|
+ |
|
+import isc.keymgr |
|
+ |
|
+if __name__ == "__main__": |
|
+ isc.keymgr.main() |
|
diff --git a/bin/python/isc/.gitignore b/bin/python/isc/.gitignore |
|
new file mode 100644 |
|
index 0000000..84554b8 |
|
--- /dev/null |
|
+++ b/bin/python/isc/.gitignore |
|
@@ -0,0 +1,3 @@ |
|
+utils.py |
|
+parsetab.py |
|
+parser.out |
|
diff --git a/bin/python/isc/Makefile.in b/bin/python/isc/Makefile.in |
|
new file mode 100644 |
|
index 0000000..425d054 |
|
--- /dev/null |
|
+++ b/bin/python/isc/Makefile.in |
|
@@ -0,0 +1,67 @@ |
|
+# Copyright (C) 2012-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. |
|
+ |
|
+srcdir = @srcdir@ |
|
+VPATH = @srcdir@ |
|
+top_srcdir = @top_srcdir@ |
|
+ |
|
+@BIND9_MAKE_INCLUDES@ |
|
+ |
|
+SUBDIRS = tests |
|
+ |
|
+PYTHON = @PYTHON@ |
|
+ |
|
+PYSRCS = __init__.py dnskey.py eventlist.py keydict.py \ |
|
+ keyevent.py keyzone.py policy.py |
|
+TARGETS = parsetab.py parsetab.pyc \ |
|
+ __init__.pyc dnskey.pyc eventlist.py keydict.py \ |
|
+ keyevent.pyc keyzone.pyc policy.pyc |
|
+ |
|
+@BIND9_MAKE_RULES@ |
|
+ |
|
+%.pyc: %.py |
|
+ $(PYTHON) -m compileall . |
|
+ |
|
+parsetab.py parsetab.pyc: policy.py |
|
+ $(PYTHON) policy.py parse /dev/null > /dev/null |
|
+ $(PYTHON) -m parsetab |
|
+ |
|
+installdirs: |
|
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}/isc |
|
+ |
|
+install:: ${PYSRCS} installdirs |
|
+ ${INSTALL_SCRIPT} __init__.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} __init__.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} dnskey.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} dnskey.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} eventlist.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} eventlist.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} keydict.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} keydict.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} keyevent.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} keyevent.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} keyzone.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} keyzone.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} policy.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} policy.pyc ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} parsetab.py ${DESTDIR}${libdir} |
|
+ ${INSTALL_SCRIPT} parsetab.pyc ${DESTDIR}${libdir} |
|
+ |
|
+check test: subdirs |
|
+ |
|
+clean distclean:: |
|
+ rm -f *.pyc parser.out parsetab.py |
|
+ |
|
+distclean:: |
|
+ rm -Rf utils.py |
|
\ No newline at end of file |
|
diff --git a/bin/python/isc/__init__.py b/bin/python/isc/__init__.py |
|
new file mode 100644 |
|
index 0000000..0d79f35 |
|
--- /dev/null |
|
+++ b/bin/python/isc/__init__.py |
|
@@ -0,0 +1,25 @@ |
|
+# Copyright (C) 2015 Internet Systems Consortium. |
|
+# |
|
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM |
|
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL |
|
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL |
|
+# INTERNET SYSTEMS CONSORTIUM 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. |
|
+ |
|
+__all__ = ['dnskey', 'eventlist', 'keydict', 'keyevent', 'keyseries', |
|
+ 'keyzone', 'policy', 'parsetab', 'utils'] |
|
+from isc.dnskey import * |
|
+from isc.eventlist import * |
|
+from isc.keydict import * |
|
+from isc.keyevent import * |
|
+from isc.keyseries import * |
|
+from isc.keyzone import * |
|
+from isc.policy import * |
|
+from isc.utils import * |
|
diff --git a/bin/python/isc/checkds.py b/bin/python/isc/checkds.py |
|
new file mode 100644 |
|
index 0000000..64ca12e |
|
--- /dev/null |
|
+++ b/bin/python/isc/checkds.py |
|
@@ -0,0 +1,189 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2012-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. |
|
+############################################################################ |
|
+ |
|
+import argparse |
|
+import os |
|
+import sys |
|
+from subprocess import Popen, PIPE |
|
+ |
|
+from isc.utils import prefix,version |
|
+ |
|
+prog = 'dnssec-checkds' |
|
+ |
|
+ |
|
+############################################################################ |
|
+# SECRR class: |
|
+# Class for DS/DLV resource record |
|
+############################################################################ |
|
+class SECRR: |
|
+ hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384'} |
|
+ rrname = '' |
|
+ rrclass = 'IN' |
|
+ keyid = None |
|
+ keyalg = None |
|
+ hashalg = None |
|
+ digest = '' |
|
+ ttl = 0 |
|
+ |
|
+ def __init__(self, rrtext, dlvname = None): |
|
+ if not rrtext: |
|
+ raise Exception |
|
+ |
|
+ fields = rrtext.split() |
|
+ if len(fields) < 7: |
|
+ raise Exception |
|
+ |
|
+ if dlvname: |
|
+ self.rrtype = "DLV" |
|
+ 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 dlv: |
|
+ raise Exception |
|
+ parent.reverse() |
|
+ self.parent = '.'.join(parent) |
|
+ self.rrname = self.parent + '.' + self.dlvname + '.' |
|
+ else: |
|
+ self.rrtype = "DS" |
|
+ 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() != self.rrtype: |
|
+ raise Exception |
|
+ |
|
+ self.keyid, self.keyalg, self.hashalg = map(int, fields[1:4]) |
|
+ 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__() |
|
+ |
|
+ |
|
+############################################################################ |
|
+# check: |
|
+# Fetch DS/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 DS/DLV records from the DNSKEY RRset, |
|
+# and report on congruency. |
|
+############################################################################ |
|
+def check(zone, args, masterfile=None, lookaside=None): |
|
+ rrlist = [] |
|
+ cmd = [args.dig, "+noall", "+answer", "-t", "dlv" if lookaside else "ds", |
|
+ "-q", zone + "." + lookaside if lookaside else zone] |
|
+ fp, _ = Popen(cmd, stdout=PIPE).communicate() |
|
+ |
|
+ for line in fp.splitlines(): |
|
+ rrlist.append(SECRR(line, lookaside)) |
|
+ rrlist = sorted(rrlist, key=lambda rr: (rr.keyid, rr.keyalg, rr.hashalg)) |
|
+ |
|
+ klist = [] |
|
+ |
|
+ if masterfile: |
|
+ cmd = [args.dsfromkey, "-f", masterfile] |
|
+ if lookaside: |
|
+ cmd += ["-l", lookaside] |
|
+ cmd.append(zone) |
|
+ fp, _ = Popen(cmd, stdout=PIPE).communicate() |
|
+ else: |
|
+ intods, _ = Popen([args.dig, "+noall", "+answer", "-t", "dnskey", |
|
+ "-q", zone], stdout=PIPE).communicate() |
|
+ cmd = [args.dsfromkey, "-f", "-"] |
|
+ if lookaside: |
|
+ cmd += ["-l", lookaside] |
|
+ cmd.append(zone) |
|
+ fp, _ = Popen(cmd, stdin=PIPE, stdout=PIPE).communicate(intods) |
|
+ |
|
+ for line in fp.splitlines(): |
|
+ klist.append(SECRR(line, lookaside)) |
|
+ |
|
+ if len(klist) < 1: |
|
+ print ("No DNSKEY records found in zone apex") |
|
+ return False |
|
+ |
|
+ found = False |
|
+ for rr in klist: |
|
+ if rr in rrlist: |
|
+ print ("%s for KSK %s/%03d/%05d (%s) found in parent" % |
|
+ (rr.rrtype, rr.rrname.strip('.'), rr.keyalg, |
|
+ rr.keyid, SECRR.hashalgs[rr.hashalg])) |
|
+ found = True |
|
+ else: |
|
+ print ("%s for KSK %s/%03d/%05d (%s) missing from parent" % |
|
+ (rr.rrtype, rr.rrname.strip('.'), rr.keyalg, |
|
+ rr.keyid, SECRR.hashalgs[rr.hashalg])) |
|
+ |
|
+ if not found: |
|
+ print ("No %s records were found for any DNSKEY" % ("DLV" if lookaside else "DS")) |
|
+ |
|
+ return found |
|
+ |
|
+############################################################################ |
|
+# parse_args: |
|
+# Read command line arguments, set global 'args' structure |
|
+############################################################################ |
|
+def parse_args(): |
|
+ parser = argparse.ArgumentParser(description=prog + ': checks DS coverage') |
|
+ |
|
+ bindir = 'bin' |
|
+ sbindir = 'bin' if os.name == 'nt' else '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=version) |
|
+ args = parser.parse_args() |
|
+ |
|
+ args.zone = args.zone.strip('.') |
|
+ if args.lookaside: |
|
+ args.lookaside = args.lookaside.strip('.') |
|
+ |
|
+ return args |
|
+ |
|
+ |
|
+############################################################################ |
|
+# Main |
|
+############################################################################ |
|
+def main(): |
|
+ args = parse_args() |
|
+ found = check(args.zone, args, args.masterfile, args.lookaside) |
|
+ exit(0 if found else 1) |
|
diff --git a/bin/python/isc/coverage.py b/bin/python/isc/coverage.py |
|
new file mode 100644 |
|
index 0000000..c9e8959 |
|
--- /dev/null |
|
+++ b/bin/python/isc/coverage.py |
|
@@ -0,0 +1,292 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+ |
|
+from __future__ import print_function |
|
+import os |
|
+import sys |
|
+import argparse |
|
+import glob |
|
+import re |
|
+import time |
|
+import calendar |
|
+import pprint |
|
+from collections import defaultdict |
|
+ |
|
+prog = 'dnssec-coverage' |
|
+ |
|
+from isc import * |
|
+from isc.utils import prefix |
|
+ |
|
+ |
|
+############################################################################ |
|
+# print a fatal error and exit |
|
+############################################################################ |
|
+def fatal(*args, **kwargs): |
|
+ print(*args, **kwargs) |
|
+ sys.exit(1) |
|
+ |
|
+ |
|
+############################################################################ |
|
+# output: |
|
+############################################################################ |
|
+_firstline = True |
|
+def output(*args, **kwargs): |
|
+ """output text, adding a vertical space this is *not* the first |
|
+ first section being printed since a call to vreset()""" |
|
+ global _firstline |
|
+ if 'skip' in kwargs: |
|
+ skip = kwargs['skip'] |
|
+ kwargs.pop('skip', None) |
|
+ else: |
|
+ skip = True |
|
+ if _firstline: |
|
+ _firstline = False |
|
+ elif skip: |
|
+ print('') |
|
+ if args: |
|
+ print(*args, **kwargs) |
|
+ |
|
+ |
|
+def vreset(): |
|
+ """reset vertical spacing""" |
|
+ global _firstline |
|
+ _firstline = True |
|
+ |
|
+ |
|
+############################################################################ |
|
+# parse_time |
|
+############################################################################ |
|
+def parse_time(s): |
|
+ """ convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds |
|
+ :param s: String with some text representing a time interval |
|
+ :return: Integer with the number of seconds in the time interval |
|
+ """ |
|
+ s = s.strip() |
|
+ |
|
+ # if s is an integer, we're done already |
|
+ try: |
|
+ return int(s) |
|
+ except ValueError: |
|
+ pass |
|
+ |
|
+ # try to parse as a number with a suffix indicating unit of time |
|
+ r = re.compile('([0-9][0-9]*)\s*([A-Za-z]*)') |
|
+ m = r.match(s) |
|
+ if not m: |
|
+ raise ValueError("Cannot parse %s" % s) |
|
+ n, unit = m.groups() |
|
+ n = int(n) |
|
+ unit = unit.lower() |
|
+ if unit.startswith('y'): |
|
+ return n * 31536000 |
|
+ elif unit.startswith('mo'): |
|
+ return n * 2592000 |
|
+ elif unit.startswith('w'): |
|
+ return n * 604800 |
|
+ elif unit.startswith('d'): |
|
+ return n * 86400 |
|
+ elif unit.startswith('h'): |
|
+ return n * 3600 |
|
+ elif unit.startswith('mi'): |
|
+ return n * 60 |
|
+ elif unit.startswith('s'): |
|
+ return n |
|
+ else: |
|
+ raise ValueError("Invalid suffix %s" % unit) |
|
+ |
|
+ |
|
+############################################################################ |
|
+# set_path: |
|
+############################################################################ |
|
+def set_path(command, default=None): |
|
+ """ find the location of a specified command. if a default is supplied |
|
+ and it works, we use it; otherwise we search PATH for a match. |
|
+ :param command: string with a command to look for in the path |
|
+ :param default: default location to use |
|
+ :return: detected location for the desired command |
|
+ """ |
|
+ |
|
+ fpath = default |
|
+ if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK): |
|
+ path = os.environ["PATH"] |
|
+ if not path: |
|
+ path = os.path.defpath |
|
+ for directory in path.split(os.pathsep): |
|
+ fpath = os.path.join(directory, command) |
|
+ if os.path.isfile(fpath) and os.access(fpath, os.X_OK): |
|
+ break |
|
+ fpath = None |
|
+ |
|
+ return fpath |
|
+ |
|
+ |
|
+############################################################################ |
|
+# parse_args: |
|
+############################################################################ |
|
+def parse_args(): |
|
+ """Read command line arguments, set global 'args' structure""" |
|
+ compilezone = set_path('named-compilezone', |
|
+ os.path.join(prefix('sbin'), 'named-compilezone')) |
|
+ |
|
+ parser = argparse.ArgumentParser(description=prog + ': checks future ' + |
|
+ 'DNSKEY coverage for a zone') |
|
+ |
|
+ parser.add_argument('zone', type=str, nargs='*', default=None, |
|
+ help='zone(s) to check' + |
|
+ '(default: all zones in the directory)') |
|
+ parser.add_argument('-K', dest='path', default='.', type=str, |
|
+ help='a directory containing keys to process', |
|
+ metavar='dir') |
|
+ parser.add_argument('-f', dest='filename', type=str, |
|
+ help='zone master file', metavar='file') |
|
+ parser.add_argument('-m', dest='maxttl', type=str, |
|
+ help='the longest TTL in the zone(s)', |
|
+ metavar='time') |
|
+ parser.add_argument('-d', dest='keyttl', type=str, |
|
+ help='the DNSKEY TTL', metavar='time') |
|
+ parser.add_argument('-r', dest='resign', default='1944000', |
|
+ type=str, help='the RRSIG refresh interval ' |
|
+ 'in seconds [default: 22.5 days]', |
|
+ metavar='time') |
|
+ parser.add_argument('-c', dest='compilezone', |
|
+ default=compilezone, type=str, |
|
+ help='path to \'named-compilezone\'', |
|
+ metavar='path') |
|
+ parser.add_argument('-l', dest='checklimit', |
|
+ type=str, default='0', |
|
+ help='Length of time to check for ' |
|
+ 'DNSSEC coverage [default: 0 (unlimited)]', |
|
+ metavar='time') |
|
+ parser.add_argument('-z', dest='no_ksk', |
|
+ action='store_true', default=False, |
|
+ help='Only check zone-signing keys (ZSKs)') |
|
+ parser.add_argument('-k', dest='no_zsk', |
|
+ action='store_true', default=False, |
|
+ help='Only check key-signing keys (KSKs)') |
|
+ parser.add_argument('-D', '--debug', dest='debug_mode', |
|
+ action='store_true', default=False, |
|
+ help='Turn on debugging output') |
|
+ parser.add_argument('-v', '--version', action='version', |
|
+ version=utils.version) |
|
+ |
|
+ args = parser.parse_args() |
|
+ |
|
+ if args.no_zsk and args.no_ksk: |
|
+ fatal("ERROR: -z and -k cannot be used together.") |
|
+ elif args.no_zsk or args.no_ksk: |
|
+ args.keytype = "KSK" if args.no_zsk else "ZSK" |
|
+ else: |
|
+ args.keytype = None |
|
+ |
|
+ if args.filename and len(args.zone) > 1: |
|
+ fatal("ERROR: -f can only be used with one zone.") |
|
+ |
|
+ # convert from time arguments to seconds |
|
+ try: |
|
+ if args.maxttl: |
|
+ m = parse_time(args.maxttl) |
|
+ args.maxttl = m |
|
+ except ValueError: |
|
+ pass |
|
+ |
|
+ try: |
|
+ if args.keyttl: |
|
+ k = parse_time(args.keyttl) |
|
+ args.keyttl = k |
|
+ except ValueError: |
|
+ pass |
|
+ |
|
+ try: |
|
+ if args.resign: |
|
+ r = parse_time(args.resign) |
|
+ args.resign = r |
|
+ except ValueError: |
|
+ pass |
|
+ |
|
+ try: |
|
+ if args.checklimit: |
|
+ lim = args.checklimit |
|
+ r = parse_time(args.checklimit) |
|
+ if r == 0: |
|
+ args.checklimit = None |
|
+ else: |
|
+ args.checklimit = time.time() + r |
|
+ except ValueError: |
|
+ pass |
|
+ |
|
+ # if we've got the values we need from the command line, stop now |
|
+ if args.maxttl and args.keyttl: |
|
+ return args |
|
+ |
|
+ # load keyttl and maxttl data from zonefile |
|
+ if args.zone and args.filename: |
|
+ try: |
|
+ zone = keyzone(args.zone[0], args.filename, args.compilezone) |
|
+ args.maxttl = args.maxttl or zone.maxttl |
|
+ args.keyttl = args.maxttl or zone.keyttl |
|
+ except Exception as e: |
|
+ print("Unable to load zone data from %s: " % args.filename, e) |
|
+ |
|
+ if not args.maxttl: |
|
+ output("WARNING: Maximum TTL value was not specified. Using 1 week\n" |
|
+ "\t (604800 seconds); re-run with the -m option to get more\n" |
|
+ "\t accurate results.") |
|
+ args.maxttl = 604800 |
|
+ |
|
+ return args |
|
+ |
|
+############################################################################ |
|
+# Main |
|
+############################################################################ |
|
+def main(): |
|
+ args = parse_args() |
|
+ |
|
+ print("PHASE 1--Loading keys to check for internal timing problems") |
|
+ |
|
+ try: |
|
+ kd = keydict(path=args.path, zone=args.zone, keyttl=args.keyttl) |
|
+ except Exception as e: |
|
+ fatal('ERROR: Unable to build key dictionary: ' + str(e)) |
|
+ |
|
+ for key in kd: |
|
+ key.check_prepub(output) |
|
+ if key.sep: |
|
+ key.check_postpub(output) |
|
+ else: |
|
+ key.check_postpub(output, args.maxttl + args.resign) |
|
+ |
|
+ output("PHASE 2--Scanning future key events for coverage failures") |
|
+ vreset() |
|
+ |
|
+ try: |
|
+ elist = eventlist(kd) |
|
+ except Exception as e: |
|
+ fatal('ERROR: Unable to build event list: ' + str(e)) |
|
+ |
|
+ errors = False |
|
+ if not args.zone: |
|
+ if not elist.coverage(None, args.keytype, args.checklimit, output): |
|
+ errors = True |
|
+ else: |
|
+ for zone in args.zone: |
|
+ try: |
|
+ if not elist.coverage(zone, args.keytype, |
|
+ args.checklimit, output): |
|
+ errors = True |
|
+ except: |
|
+ output('ERROR: Coverage check failed for zone ' + zone) |
|
+ |
|
+ sys.exit(1 if errors else 0) |
|
diff --git a/bin/python/isc/dnskey.py b/bin/python/isc/dnskey.py |
|
new file mode 100644 |
|
index 0000000..f1559e7 |
|
--- /dev/null |
|
+++ b/bin/python/isc/dnskey.py |
|
@@ -0,0 +1,504 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+ |
|
+import os |
|
+import time |
|
+import calendar |
|
+from subprocess import Popen, PIPE |
|
+ |
|
+######################################################################## |
|
+# Class dnskey |
|
+######################################################################## |
|
+class TimePast(Exception): |
|
+ def __init__(self, key, prop, value): |
|
+ super(TimePast, self).__init__('%s time for key %s (%d) is already past' |
|
+ % (prop, key, value)) |
|
+ |
|
+class dnskey: |
|
+ """An individual DNSSEC key. Identified by path, name, algorithm, keyid. |
|
+ Contains a dictionary of metadata events.""" |
|
+ |
|
+ _PROPS = ('Created', 'Publish', 'Activate', 'Inactive', 'Delete', |
|
+ 'Revoke', 'DSPublish', 'SyncPublish', 'SyncDelete') |
|
+ _OPTS = (None, '-P', '-A', '-I', '-D', '-R', None, '-Psync', '-Dsync') |
|
+ |
|
+ _ALGNAMES = (None, 'RSAMD5', 'DH', 'DSA', 'ECC', 'RSASHA1', |
|
+ 'NSEC3DSA', 'NSEC3RSASHA1', 'RSASHA256', None, |
|
+ 'RSASHA512', None, 'ECCGOST', 'ECDSAP256SHA256', |
|
+ 'ECDSAP384SHA384') |
|
+ |
|
+ def __init__(self, key, directory=None, keyttl=None): |
|
+ # this makes it possible to use algname as a class or instance method |
|
+ if isinstance(key, tuple) and len(key) == 3: |
|
+ self._dir = directory or '.' |
|
+ (name, alg, keyid) = key |
|
+ self.fromtuple(name, alg, keyid, keyttl) |
|
+ |
|
+ self._dir = directory or os.path.dirname(key) or '.' |
|
+ key = os.path.basename(key) |
|
+ |
|
+ (name, alg, keyid) = key.split('+') |
|
+ name = name[1:-1] |
|
+ alg = int(alg) |
|
+ keyid = int(keyid.split('.')[0]) |
|
+ self.fromtuple(name, alg, keyid, keyttl) |
|
+ |
|
+ def fromtuple(self, name, alg, keyid, keyttl): |
|
+ if name.endswith('.'): |
|
+ fullname = name |
|
+ name = name.rstrip('.') |
|
+ else: |
|
+ fullname = name + '.' |
|
+ |
|
+ keystr = "K%s+%03d+%05d" % (fullname, alg, keyid) |
|
+ key_file = self._dir + (self._dir and os.sep or '') + keystr + ".key" |
|
+ private_file = (self._dir + (self._dir and os.sep or '') + |
|
+ keystr + ".private") |
|
+ |
|
+ self.keystr = keystr |
|
+ |
|
+ self.name = name |
|
+ self.alg = int(alg) |
|
+ self.keyid = int(keyid) |
|
+ self.fullname = fullname |
|
+ |
|
+ kfp = open(key_file, "r") |
|
+ for line in kfp: |
|
+ if line[0] == ';': |
|
+ continue |
|
+ tokens = line.split() |
|
+ if not tokens: |
|
+ continue |
|
+ |
|
+ if tokens[1].lower() in ('in', 'ch', 'hs'): |
|
+ septoken = 3 |
|
+ self.ttl = keyttl |
|
+ else: |
|
+ septoken = 4 |
|
+ self.ttl = int(tokens[1]) if not keyttl else keyttl |
|
+ |
|
+ if (int(tokens[septoken]) & 0x1) == 1: |
|
+ self.sep = True |
|
+ else: |
|
+ self.sep = False |
|
+ kfp.close() |
|
+ |
|
+ pfp = open(private_file, "rU") |
|
+ |
|
+ self.metadata = dict() |
|
+ self._changed = dict() |
|
+ self._delete = dict() |
|
+ self._times = dict() |
|
+ self._fmttime = dict() |
|
+ self._timestamps = dict() |
|
+ self._original = dict() |
|
+ self._origttl = None |
|
+ |
|
+ for line in pfp: |
|
+ line = line.strip() |
|
+ if not line or line[0] in ('!#'): |
|
+ continue |
|
+ punctuation = [line.find(c) for c in ':= '] + [len(line)] |
|
+ found = min([pos for pos in punctuation if pos != -1]) |
|
+ name = line[:found].rstrip() |
|
+ value = line[found:].lstrip(":= ").rstrip() |
|
+ self.metadata[name] = value |
|
+ |
|
+ for prop in dnskey._PROPS: |
|
+ self._changed[prop] = False |
|
+ if prop in self.metadata: |
|
+ t = self.parsetime(self.metadata[prop]) |
|
+ self._times[prop] = t |
|
+ self._fmttime[prop] = self.formattime(t) |
|
+ self._timestamps[prop] = self.epochfromtime(t) |
|
+ self._original[prop] = self._timestamps[prop] |
|
+ else: |
|
+ self._times[prop] = None |
|
+ self._fmttime[prop] = None |
|
+ self._timestamps[prop] = None |
|
+ self._original[prop] = None |
|
+ |
|
+ pfp.close() |
|
+ |
|
+ def commit(self, settime_bin, **kwargs): |
|
+ quiet = kwargs.get('quiet', False) |
|
+ cmd = [] |
|
+ first = True |
|
+ |
|
+ if self._origttl is not None: |
|
+ cmd += ["-L", str(self.ttl)] |
|
+ |
|
+ for prop, opt in zip(dnskey._PROPS, dnskey._OPTS): |
|
+ if not opt or not self._changed[prop]: |
|
+ continue |
|
+ |
|
+ delete = False |
|
+ if prop in self._delete and self._delete[prop]: |
|
+ delete = True |
|
+ |
|
+ when = 'none' if delete else self._fmttime[prop] |
|
+ cmd += [opt, when] |
|
+ first = False |
|
+ |
|
+ if cmd: |
|
+ fullcmd = [settime_bin, "-K", self._dir] + cmd + [self.keystr,] |
|
+ if not quiet: |
|
+ print('# ' + ' '.join(fullcmd)) |
|
+ try: |
|
+ p = Popen(fullcmd, stdout=PIPE, stderr=PIPE) |
|
+ stdout, stderr = p.communicate() |
|
+ if stderr: |
|
+ raise Exception(str(stderr)) |
|
+ except Exception as e: |
|
+ raise Exception('unable to run %s: %s' % |
|
+ (settime_bin, str(e))) |
|
+ self._origttl = None |
|
+ for prop in dnskey._PROPS: |
|
+ self._original[prop] = self._timestamps[prop] |
|
+ self._changed[prop] = False |
|
+ |
|
+ @classmethod |
|
+ def generate(cls, keygen_bin, keys_dir, name, alg, keysize, sep, |
|
+ ttl, publish=None, activate=None, **kwargs): |
|
+ quiet = kwargs.get('quiet', False) |
|
+ |
|
+ keygen_cmd = [keygen_bin, "-q", "-K", keys_dir, "-L", str(ttl)] |
|
+ |
|
+ if sep: |
|
+ keygen_cmd.append("-fk") |
|
+ |
|
+ if alg: |
|
+ keygen_cmd += ["-a", alg] |
|
+ |
|
+ if keysize: |
|
+ keygen_cmd += ["-b", str(keysize)] |
|
+ |
|
+ if publish: |
|
+ t = dnskey.timefromepoch(publish) |
|
+ keygen_cmd += ["-P", dnskey.formattime(t)] |
|
+ |
|
+ if activate: |
|
+ t = dnskey.timefromepoch(activate) |
|
+ keygen_cmd += ["-A", dnskey.formattime(activate)] |
|
+ |
|
+ keygen_cmd.append(name) |
|
+ |
|
+ if not quiet: |
|
+ print('# ' + ' '.join(keygen_cmd)) |
|
+ |
|
+ p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE) |
|
+ stdout, stderr = p.communicate() |
|
+ if stderr: |
|
+ raise Exception('unable to generate key: ' + str(stderr)) |
|
+ |
|
+ try: |
|
+ keystr = stdout.splitlines()[0] |
|
+ newkey = dnskey(keystr, keys_dir, ttl) |
|
+ return newkey |
|
+ except Exception as e: |
|
+ raise Exception('unable to generate key: %s' % str(e)) |
|
+ |
|
+ def generate_successor(self, keygen_bin, **kwargs): |
|
+ quiet = kwargs.get('quiet', False) |
|
+ |
|
+ if not self.inactive(): |
|
+ raise Exception("predecessor key %s has no inactive date" % self) |
|
+ |
|
+ keygen_cmd = [keygen_bin, "-q", "-K", self._dir, "-S", self.keystr] |
|
+ |
|
+ if self.ttl: |
|
+ keygen_cmd += ["-L", str(self.ttl)] |
|
+ |
|
+ if not quiet: |
|
+ print('# ' + ' '.join(keygen_cmd)) |
|
+ |
|
+ p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE) |
|
+ stdout, stderr = p.communicate() |
|
+ if stderr: |
|
+ raise Exception('unable to generate key: ' + stderr) |
|
+ |
|
+ try: |
|
+ keystr = stdout.splitlines()[0] |
|
+ newkey = dnskey(keystr, self._dir, self.ttl) |
|
+ return newkey |
|
+ except: |
|
+ raise Exception('unable to generate successor for key %s' % self) |
|
+ |
|
+ @staticmethod |
|
+ def algstr(alg): |
|
+ name = None |
|
+ if alg in range(len(dnskey._ALGNAMES)): |
|
+ name = dnskey._ALGNAMES[alg] |
|
+ return name if name else ("%03d" % alg) |
|
+ |
|
+ @staticmethod |
|
+ def algnum(alg): |
|
+ if not alg: |
|
+ return None |
|
+ alg = alg.upper() |
|
+ try: |
|
+ return dnskey._ALGNAMES.index(alg) |
|
+ except ValueError: |
|
+ return None |
|
+ |
|
+ def algname(self, alg=None): |
|
+ return self.algstr(alg or self.alg) |
|
+ |
|
+ @staticmethod |
|
+ def timefromepoch(secs): |
|
+ return time.gmtime(secs) |
|
+ |
|
+ @staticmethod |
|
+ def parsetime(string): |
|
+ return time.strptime(string, "%Y%m%d%H%M%S") |
|
+ |
|
+ @staticmethod |
|
+ def epochfromtime(t): |
|
+ return calendar.timegm(t) |
|
+ |
|
+ @staticmethod |
|
+ def formattime(t): |
|
+ return time.strftime("%Y%m%d%H%M%S", t) |
|
+ |
|
+ def setmeta(self, prop, secs, now, **kwargs): |
|
+ force = kwargs.get('force', False) |
|
+ |
|
+ if self._timestamps[prop] == secs: |
|
+ return |
|
+ |
|
+ if self._original[prop] is not None and \ |
|
+ self._original[prop] < now and not force: |
|
+ raise TimePast(self, prop, self._original[prop]) |
|
+ |
|
+ if secs is None: |
|
+ self._changed[prop] = False \ |
|
+ if self._original[prop] is None else True |
|
+ |
|
+ self._delete[prop] = True |
|
+ self._timestamps[prop] = None |
|
+ self._times[prop] = None |
|
+ self._fmttime[prop] = None |
|
+ return |
|
+ |
|
+ t = self.timefromepoch(secs) |
|
+ self._timestamps[prop] = secs |
|
+ self._times[prop] = t |
|
+ self._fmttime[prop] = self.formattime(t) |
|
+ self._changed[prop] = False if \ |
|
+ self._original[prop] == self._timestamps[prop] else True |
|
+ |
|
+ def gettime(self, prop): |
|
+ return self._times[prop] |
|
+ |
|
+ def getfmttime(self, prop): |
|
+ return self._fmttime[prop] |
|
+ |
|
+ def gettimestamp(self, prop): |
|
+ return self._timestamps[prop] |
|
+ |
|
+ def created(self): |
|
+ return self._timestamps["Created"] |
|
+ |
|
+ def syncpublish(self): |
|
+ return self._timestamps["SyncPublish"] |
|
+ |
|
+ def setsyncpublish(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("SyncPublish", secs, now, **kwargs) |
|
+ |
|
+ def publish(self): |
|
+ return self._timestamps["Publish"] |
|
+ |
|
+ def setpublish(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("Publish", secs, now, **kwargs) |
|
+ |
|
+ def activate(self): |
|
+ return self._timestamps["Activate"] |
|
+ |
|
+ def setactivate(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("Activate", secs, now, **kwargs) |
|
+ |
|
+ def revoke(self): |
|
+ return self._timestamps["Revoke"] |
|
+ |
|
+ def setrevoke(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("Revoke", secs, now, **kwargs) |
|
+ |
|
+ def inactive(self): |
|
+ return self._timestamps["Inactive"] |
|
+ |
|
+ def setinactive(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("Inactive", secs, now, **kwargs) |
|
+ |
|
+ def delete(self): |
|
+ return self._timestamps["Delete"] |
|
+ |
|
+ def setdelete(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("Delete", secs, now, **kwargs) |
|
+ |
|
+ def syncdelete(self): |
|
+ return self._timestamps["SyncDelete"] |
|
+ |
|
+ def setsyncdelete(self, secs, now=time.time(), **kwargs): |
|
+ self.setmeta("SyncDelete", secs, now, **kwargs) |
|
+ |
|
+ def setttl(self, ttl): |
|
+ if ttl is None or self.ttl == ttl: |
|
+ return |
|
+ elif self._origttl is None: |
|
+ self._origttl = self.ttl |
|
+ self.ttl = ttl |
|
+ elif self._origttl == ttl: |
|
+ self._origttl = None |
|
+ self.ttl = ttl |
|
+ else: |
|
+ self.ttl = ttl |
|
+ |
|
+ def keytype(self): |
|
+ return ("KSK" if self.sep else "ZSK") |
|
+ |
|
+ def __str__(self): |
|
+ return ("%s/%s/%05d" |
|
+ % (self.name, self.algname(), self.keyid)) |
|
+ |
|
+ def __repr__(self): |
|
+ return ("%s/%s/%05d (%s)" |
|
+ % (self.name, self.algname(), self.keyid, |
|
+ ("KSK" if self.sep else "ZSK"))) |
|
+ |
|
+ def date(self): |
|
+ return (self.activate() or self.publish() or self.created()) |
|
+ |
|
+ # keys are sorted first by zone name, then by algorithm. within |
|
+ # the same name/algorithm, they are sorted according to their |
|
+ # 'date' value: the activation date if set, OR the publication |
|
+ # if set, OR the creation date. |
|
+ def __lt__(self, other): |
|
+ if self.name != other.name: |
|
+ return self.name < other.name |
|
+ if self.alg != other.alg: |
|
+ return self.alg < other.alg |
|
+ return self.date() < other.date() |
|
+ |
|
+ def check_prepub(self, output=None): |
|
+ def noop(*args, **kwargs): pass |
|
+ if not output: |
|
+ output = noop |
|
+ |
|
+ now = int(time.time()) |
|
+ a = self.activate() |
|
+ p = self.publish() |
|
+ |
|
+ if not a: |
|
+ return False |
|
+ |
|
+ if not p: |
|
+ if a > now: |
|
+ output("WARNING: Key %s is scheduled for\n" |
|
+ "\t activation but not for publication." |
|
+ % repr(self)) |
|
+ return False |
|
+ |
|
+ if p <= now and a <= now: |
|
+ return True |
|
+ |
|
+ if p == a: |
|
+ output("WARNING: %s is scheduled to be\n" |
|
+ "\t published and activated at the same time. This\n" |
|
+ "\t could result in a coverage gap if the zone was\n" |
|
+ "\t previously signed. Activation should be at least\n" |
|
+ "\t %s after publication." |
|
+ % (repr(self), |
|
+ dnskey.duration(self.ttl) or 'one DNSKEY TTL')) |
|
+ return True |
|
+ |
|
+ if a < p: |
|
+ output("WARNING: Key %s is active before it is published" |
|
+ % repr(self)) |
|
+ return False |
|
+ |
|
+ if self.ttl is not None and a - p < self.ttl: |
|
+ output("WARNING: Key %s is activated too soon\n" |
|
+ "\t after publication; this could result in coverage \n" |
|
+ "\t gaps due to resolver caches containing old data.\n" |
|
+ "\t Activation should be at least %s after\n" |
|
+ "\t publication." |
|
+ % (repr(self), |
|
+ dnskey.duration(self.ttl) or 'one DNSKEY TTL')) |
|
+ return False |
|
+ |
|
+ return True |
|
+ |
|
+ def check_postpub(self, output = None, timespan = None): |
|
+ def noop(*args, **kwargs): pass |
|
+ if output is None: |
|
+ output = noop |
|
+ |
|
+ if timespan is None: |
|
+ timespan = self.ttl |
|
+ |
|
+ now = time.time() |
|
+ d = self.delete() |
|
+ i = self.inactive() |
|
+ |
|
+ if not d: |
|
+ return False |
|
+ |
|
+ if not i: |
|
+ if d > now: |
|
+ output("WARNING: Key %s is scheduled for\n" |
|
+ "\t deletion but not for inactivation." % repr(self)) |
|
+ return False |
|
+ |
|
+ if d < now and i < now: |
|
+ return True |
|
+ |
|
+ if d < i: |
|
+ output("WARNING: Key %s is scheduled for\n" |
|
+ "\t deletion before inactivation." |
|
+ % repr(self)) |
|
+ return False |
|
+ |
|
+ if d - i < timespan: |
|
+ output("WARNING: Key %s scheduled for\n" |
|
+ "\t deletion too soon after deactivation; this may \n" |
|
+ "\t result in coverage gaps due to resolver caches\n" |
|
+ "\t containing old data. Deletion should be at least\n" |
|
+ "\t %s after inactivation." |
|
+ % (repr(self), dnskey.duration(timespan))) |
|
+ return False |
|
+ |
|
+ return True |
|
+ |
|
+ @staticmethod |
|
+ def duration(secs): |
|
+ if not secs: |
|
+ return None |
|
+ |
|
+ units = [("year", 60*60*24*365), |
|
+ ("month", 60*60*24*30), |
|
+ ("day", 60*60*24), |
|
+ ("hour", 60*60), |
|
+ ("minute", 60), |
|
+ ("second", 1)] |
|
+ |
|
+ output = [] |
|
+ for unit in units: |
|
+ v, secs = secs // unit[1], secs % unit[1] |
|
+ if v > 0: |
|
+ output.append("%d %s%s" % (v, unit[0], "s" if v > 1 else "")) |
|
+ |
|
+ return ", ".join(output) |
|
+ |
|
diff --git a/bin/python/isc/eventlist.py b/bin/python/isc/eventlist.py |
|
new file mode 100644 |
|
index 0000000..4c91368 |
|
--- /dev/null |
|
+++ b/bin/python/isc/eventlist.py |
|
@@ -0,0 +1,171 @@ |
|
+############################################################################ |
|
+# 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. |
|
+############################################################################ |
|
+ |
|
+from collections import defaultdict |
|
+from .dnskey import * |
|
+from .keydict import * |
|
+from .keyevent import * |
|
+ |
|
+ |
|
+class eventlist: |
|
+ _K = defaultdict(lambda: defaultdict(list)) |
|
+ _Z = defaultdict(lambda: defaultdict(list)) |
|
+ _zones = set() |
|
+ _kdict = None |
|
+ |
|
+ def __init__(self, kdict): |
|
+ properties = ["SyncPublish", "Publish", "SyncDelete", |
|
+ "Activate", "Inactive", "Delete"] |
|
+ self._kdict = kdict |
|
+ for zone in kdict.zones(): |
|
+ self._zones.add(zone) |
|
+ for alg, keys in kdict[zone].items(): |
|
+ for k in keys.values(): |
|
+ for prop in properties: |
|
+ t = k.gettime(prop) |
|
+ if not t: |
|
+ continue |
|
+ e = keyevent(prop, k, t) |
|
+ if k.sep: |
|
+ self._K[zone][alg].append(e) |
|
+ else: |
|
+ self._Z[zone][alg].append(e) |
|
+ |
|
+ self._K[zone][alg] = sorted(self._K[zone][alg], |
|
+ key=lambda event: event.when) |
|
+ self._Z[zone][alg] = sorted(self._Z[zone][alg], |
|
+ key=lambda event: event.when) |
|
+ |
|
+ # scan events per zone, algorithm, and key type, in order of |
|
+ # occurrance, noting inconsistent states when found |
|
+ def coverage(self, zone, keytype, until, output = None): |
|
+ def noop(*args, **kwargs): pass |
|
+ if not output: |
|
+ output = noop |
|
+ |
|
+ no_zsk = True if (keytype and keytype == "KSK") else False |
|
+ no_ksk = True if (keytype and keytype == "ZSK") else False |
|
+ kok = zok = True |
|
+ found = False |
|
+ |
|
+ if zone and not zone in self._zones: |
|
+ output("ERROR: No key events found for %s" % zone) |
|
+ return False |
|
+ |
|
+ if zone: |
|
+ found = True |
|
+ if not no_ksk: |
|
+ kok = self.checkzone(zone, "KSK", until, output) |
|
+ if not no_zsk: |
|
+ zok = self.checkzone(zone, "ZSK", until, output) |
|
+ else: |
|
+ for z in self._zones: |
|
+ if not no_ksk and z in self._K.keys(): |
|
+ found = True |
|
+ kok = self.checkzone(z, "KSK", until, output) |
|
+ if not no_zsk and z in self._Z.keys(): |
|
+ found = True |
|
+ kok = self.checkzone(z, "ZSK", until, output) |
|
+ |
|
+ if not found: |
|
+ output("ERROR: No key events found") |
|
+ return False |
|
+ |
|
+ return (kok and zok) |
|
+ |
|
+ def checkzone(self, zone, keytype, until, output): |
|
+ allok = True |
|
+ if keytype == "KSK": |
|
+ kz = self._K[zone] |
|
+ else: |
|
+ kz = self._Z[zone] |
|
+ |
|
+ for alg in kz.keys(): |
|
+ output("Checking scheduled %s events for zone %s, " |
|
+ "algorithm %s..." % |
|
+ (keytype, zone, dnskey.algstr(alg))) |
|
+ ok = eventlist.checkset(kz[alg], keytype, until, output) |
|
+ if ok: |
|
+ output("No errors found") |
|
+ allok = allok and ok |
|
+ |
|
+ return allok |
|
+ |
|
+ @staticmethod |
|
+ def showset(eventset, output): |
|
+ if not eventset: |
|
+ return |
|
+ output(" " + eventset[0].showtime() + ":", skip=False) |
|
+ for event in eventset: |
|
+ output(" %s: %s" % (event.what, repr(event.key)), skip=False) |
|
+ |
|
+ @staticmethod |
|
+ def checkset(eventset, keytype, until, output): |
|
+ groups = list() |
|
+ group = list() |
|
+ |
|
+ # collect up all events that have the same time |
|
+ eventsfound = False |
|
+ for event in eventset: |
|
+ # we found an event |
|
+ eventsfound = True |
|
+ |
|
+ # add event to current group |
|
+ if (not group or group[0].when == event.when): |
|
+ group.append(event) |
|
+ |
|
+ # if we're at the end of the list, we're done. if |
|
+ # we've found an event with a later time, start a new group |
|
+ if (group[0].when != event.when): |
|
+ groups.append(group) |
|
+ group = list() |
|
+ group.append(event) |
|
+ |
|
+ if group: |
|
+ groups.append(group) |
|
+ |
|
+ if not eventsfound: |
|
+ output("ERROR: No %s events found" % keytype) |
|
+ return False |
|
+ |
|
+ active = published = None |
|
+ for group in groups: |
|
+ if (until and calendar.timegm(group[0].when) > until): |
|
+ output("Ignoring events after %s" % |
|
+ time.strftime("%a %b %d %H:%M:%S UTC %Y", |
|
+ time.gmtime(until))) |
|
+ return True |
|
+ |
|
+ for event in group: |
|
+ (active, published) = event.status(active, published) |
|
+ |
|
+ eventlist.showset(group, output) |
|
+ |
|
+ # and then check for inconsistencies: |
|
+ if not active: |
|
+ output("ERROR: No %s's are active after this event" % keytype) |
|
+ return False |
|
+ elif not published: |
|
+ output("ERROR: No %s's are published after this event" |
|
+ % keytype) |
|
+ return False |
|
+ elif not published.intersection(active): |
|
+ output("ERROR: No %s's are both active and published " |
|
+ "after this event" % keytype) |
|
+ return False |
|
+ |
|
+ return True |
|
+ |
|
diff --git a/bin/python/isc/keydict.py b/bin/python/isc/keydict.py |
|
new file mode 100644 |
|
index 0000000..cc73dc4 |
|
--- /dev/null |
|
+++ b/bin/python/isc/keydict.py |
|
@@ -0,0 +1,89 @@ |
|
+############################################################################ |
|
+# 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. |
|
+############################################################################ |
|
+ |
|
+from collections import defaultdict |
|
+from . import dnskey |
|
+import os |
|
+import glob |
|
+ |
|
+ |
|
+######################################################################## |
|
+# Class keydict |
|
+######################################################################## |
|
+class keydict: |
|
+ """ A dictionary of keys, indexed by name, algorithm, and key id """ |
|
+ |
|
+ _keydict = defaultdict(lambda: defaultdict(dict)) |
|
+ _defttl = None |
|
+ _missing = [] |
|
+ |
|
+ def __init__(self, dp=None, **kwargs): |
|
+ self._defttl = kwargs.get('keyttl', None) |
|
+ zones = kwargs.get('zones', None) |
|
+ |
|
+ if not zones: |
|
+ path = kwargs.get('path',None) or '.' |
|
+ self.readall(path) |
|
+ else: |
|
+ for zone in zones: |
|
+ if 'path' in kwargs and kwargs['path'] is not None: |
|
+ path = kwargs['path'] |
|
+ else: |
|
+ path = dp and dp.policy(zone).directory or '.' |
|
+ if not self.readone(path, zone): |
|
+ self._missing.append(zone) |
|
+ |
|
+ def readall(self, path): |
|
+ files = glob.glob(os.path.join(path, '*.private')) |
|
+ |
|
+ for infile in files: |
|
+ key = dnskey(infile, path, self._defttl) |
|
+ self._keydict[key.name][key.alg][key.keyid] = key |
|
+ |
|
+ def readone(self, path, zone): |
|
+ match='K' + zone + '.+*.private' |
|
+ files = glob.glob(os.path.join(path, match)) |
|
+ |
|
+ found = False |
|
+ for infile in files: |
|
+ key = dnskey(infile, path, self._defttl) |
|
+ if key.name != zone: # shouldn't ever happen |
|
+ continue |
|
+ self._keydict[key.name][key.alg][key.keyid] = key |
|
+ found = True |
|
+ |
|
+ return found |
|
+ |
|
+ def __iter__(self): |
|
+ for zone, algorithms in self._keydict.items(): |
|
+ for alg, keys in algorithms.items(): |
|
+ for key in keys.values(): |
|
+ yield key |
|
+ |
|
+ def __getitem__(self, name): |
|
+ return self._keydict[name] |
|
+ |
|
+ def zones(self): |
|
+ return (self._keydict.keys()) |
|
+ |
|
+ def algorithms(self, zone): |
|
+ return (self._keydict[zone].keys()) |
|
+ |
|
+ def keys(self, zone, alg): |
|
+ return (self._keydict[zone][alg].keys()) |
|
+ |
|
+ def missing(self): |
|
+ return (self._missing) |
|
diff --git a/bin/python/isc/keyevent.py b/bin/python/isc/keyevent.py |
|
new file mode 100644 |
|
index 0000000..9025fee |
|
--- /dev/null |
|
+++ b/bin/python/isc/keyevent.py |
|
@@ -0,0 +1,81 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+ |
|
+import time |
|
+ |
|
+ |
|
+######################################################################## |
|
+# Class keyevent |
|
+######################################################################## |
|
+class keyevent: |
|
+ """ A discrete key event, e.g., Publish, Activate, Inactive, Delete, |
|
+ etc. Stores the date of the event, and identifying information |
|
+ about the key to which the event will occur.""" |
|
+ |
|
+ def __init__(self, what, key, when=None): |
|
+ self.what = what |
|
+ self.when = when or key.gettime(what) |
|
+ self.key = key |
|
+ self.sep = key.sep |
|
+ self.zone = key.name |
|
+ self.alg = key.alg |
|
+ self.keyid = key.keyid |
|
+ |
|
+ def __repr__(self): |
|
+ return repr((self.when, self.what, self.keyid, self.sep, |
|
+ self.zone, self.alg)) |
|
+ |
|
+ def showtime(self): |
|
+ return time.strftime("%a %b %d %H:%M:%S UTC %Y", self.when) |
|
+ |
|
+ # update sets of active and published keys, based on |
|
+ # the contents of this keyevent |
|
+ def status(self, active, published, output = None): |
|
+ def noop(*args, **kwargs): pass |
|
+ if not output: |
|
+ output = noop |
|
+ |
|
+ if not active: |
|
+ active = set() |
|
+ if not published: |
|
+ published = set() |
|
+ |
|
+ if self.what == "Activate": |
|
+ active.add(self.keyid) |
|
+ elif self.what == "Publish": |
|
+ published.add(self.keyid) |
|
+ elif self.what == "Inactive": |
|
+ if self.keyid not in active: |
|
+ output("\tWARNING: %s scheduled to become inactive " |
|
+ "before it is active" |
|
+ % repr(self.key)) |
|
+ else: |
|
+ active.remove(self.keyid) |
|
+ elif self.what == "Delete": |
|
+ if self.keyid in published: |
|
+ published.remove(self.keyid) |
|
+ else: |
|
+ output("WARNING: key %s is scheduled for deletion " |
|
+ "before it is published" % repr(self.key)) |
|
+ elif self.what == "Revoke": |
|
+ # We don't need to worry about the logic of this one; |
|
+ # just stop counting this key as either active or published |
|
+ if self.keyid in published: |
|
+ published.remove(self.keyid) |
|
+ if self.keyid in active: |
|
+ active.remove(self.keyid) |
|
+ |
|
+ return active, published |
|
diff --git a/bin/python/isc/keymgr.py b/bin/python/isc/keymgr.py |
|
new file mode 100644 |
|
index 0000000..a3a9043 |
|
--- /dev/null |
|
+++ b/bin/python/isc/keymgr.py |
|
@@ -0,0 +1,152 @@ |
|
+############################################################################ |
|
+# 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. |
|
+############################################################################ |
|
+ |
|
+from __future__ import print_function |
|
+import os, sys, argparse, glob, re, time, calendar, pprint |
|
+from collections import defaultdict |
|
+ |
|
+prog='dnssec-keymgr' |
|
+ |
|
+from isc import * |
|
+from isc.utils import prefix |
|
+ |
|
+############################################################################ |
|
+# print a fatal error and exit |
|
+############################################################################ |
|
+def fatal(*args, **kwargs): |
|
+ print(*args, **kwargs) |
|
+ sys.exit(1) |
|
+ |
|
+############################################################################ |
|
+# find the location of an external command |
|
+############################################################################ |
|
+def set_path(command, default=None): |
|
+ """ find the location of a specified command. If a default is supplied, |
|
+ exists and it's an executable, we use it; otherwise we search PATH |
|
+ for an alternative. |
|
+ :param command: command to look for |
|
+ :param default: default value to use |
|
+ :return: PATH with the location of a suitable binary |
|
+ """ |
|
+ fpath = default |
|
+ if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK): |
|
+ path = os.environ["PATH"] |
|
+ if not path: |
|
+ path = os.path.defpath |
|
+ for directory in path.split(os.pathsep): |
|
+ fpath = directory + os.sep + command |
|
+ if os.path.isfile(fpath) and os.access(fpath, os.X_OK): |
|
+ break |
|
+ fpath = None |
|
+ |
|
+ return fpath |
|
+ |
|
+############################################################################ |
|
+# parse arguments |
|
+############################################################################ |
|
+def parse_args(): |
|
+ """ Read command line arguments, returns 'args' object |
|
+ :return: args object properly prepared |
|
+ """ |
|
+ |
|
+ keygen = set_path('dnssec-keygen', |
|
+ os.path.join(prefix('sbin'), 'dnssec-keygen')) |
|
+ settime = set_path('dnssec-settime', |
|
+ os.path.join(prefix('sbin'), 'dnssec-settime')) |
|
+ |
|
+ parser = argparse.ArgumentParser(description=prog + ': schedule ' |
|
+ 'DNSSEC key rollovers according to a ' |
|
+ 'pre-defined policy') |
|
+ |
|
+ parser.add_argument('zone', type=str, nargs='*', default=None, |
|
+ help='Zone(s) to which the policy should be applied ' + |
|
+ '(default: all zones in the directory)') |
|
+ parser.add_argument('-K', dest='path', type=str, |
|
+ help='Directory containing keys', metavar='dir') |
|
+ parser.add_argument('-c', dest='policyfile', type=str, |
|
+ help='Policy definition file', metavar='file') |
|
+ parser.add_argument('-g', dest='keygen', default=keygen, type=str, |
|
+ help='Path to \'dnssec-keygen\'', |
|
+ metavar='path') |
|
+ parser.add_argument('-s', dest='settime', default=settime, type=str, |
|
+ help='Path to \'dnssec-settime\'', |
|
+ metavar='path') |
|
+ parser.add_argument('-k', dest='no_zsk', |
|
+ action='store_true', default=False, |
|
+ help='Only apply policy to key-signing keys (KSKs)') |
|
+ parser.add_argument('-z', dest='no_ksk', |
|
+ action='store_true', default=False, |
|
+ help='Only apply policy to zone-signing keys (ZSKs)') |
|
+ parser.add_argument('-f', '--force', dest='force', action='store_true', |
|
+ default=False, help='Force updates to key events '+ |
|
+ 'even if they are in the past') |
|
+ parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', |
|
+ default=False, help='Update keys silently') |
|
+ parser.add_argument('-v', '--version', action='version', |
|
+ version=utils.version) |
|
+ |
|
+ args = parser.parse_args() |
|
+ |
|
+ if args.no_zsk and args.no_ksk: |
|
+ fatal("ERROR: -z and -k cannot be used together.") |
|
+ |
|
+ if args.keygen is None or args.settime is None: |
|
+ fatal("ERROR: dnssec-keygen/dnssec-settime not found") |
|
+ |
|
+ # if a policy file was specified, check that it exists. |
|
+ # if not, use the default file, unless it doesn't exist |
|
+ if args.policyfile is not None: |
|
+ if not os.path.exists(args.policyfile): |
|
+ fatal('ERROR: Policy file "%s" not found' % args.policyfile) |
|
+ else: |
|
+ args.policyfile = os.path.join(utils.sysconfdir, 'policy.conf') |
|
+ if not os.path.exists(args.policyfile): |
|
+ args.policyfile = None |
|
+ |
|
+ return args |
|
+ |
|
+############################################################################ |
|
+# main |
|
+############################################################################ |
|
+def main(): |
|
+ args = parse_args() |
|
+ |
|
+ # As we may have specific locations for the binaries, we put that info |
|
+ # into a context object that can be passed around |
|
+ context = {'keygen_path': args.keygen, |
|
+ 'settime_path': args.settime, |
|
+ 'keys_path': args.path} |
|
+ |
|
+ try: |
|
+ dp = policy.dnssec_policy(args.policyfile) |
|
+ except Exception as e: |
|
+ fatal('Unable to load DNSSEC policy: ' + str(e)) |
|
+ |
|
+ try: |
|
+ kd = keydict(dp, path=args.path, zones=args.zone) |
|
+ except Exception as e: |
|
+ fatal('Unable to build key dictionary: ' + str(e)) |
|
+ |
|
+ try: |
|
+ ks = keyseries(kd, context=context) |
|
+ except Exception as e: |
|
+ fatal('Unable to build key series: ' + str(e)) |
|
+ |
|
+ try: |
|
+ ks.enforce_policy(dp, ksk=args.no_zsk, zsk=args.no_ksk, |
|
+ force=args.force, quiet=args.quiet) |
|
+ except Exception as e: |
|
+ fatal('Unable to apply policy: ' + str(e)) |
|
diff --git a/bin/python/isc/keyseries.py b/bin/python/isc/keyseries.py |
|
new file mode 100644 |
|
index 0000000..ed09f71 |
|
--- /dev/null |
|
+++ b/bin/python/isc/keyseries.py |
|
@@ -0,0 +1,194 @@ |
|
+############################################################################ |
|
+# 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. |
|
+############################################################################ |
|
+ |
|
+from collections import defaultdict |
|
+from .dnskey import * |
|
+from .keydict import * |
|
+from .keyevent import * |
|
+from .policy import * |
|
+import time |
|
+ |
|
+ |
|
+class keyseries: |
|
+ _K = defaultdict(lambda: defaultdict(list)) |
|
+ _Z = defaultdict(lambda: defaultdict(list)) |
|
+ _zones = set() |
|
+ _kdict = None |
|
+ _context = None |
|
+ |
|
+ def __init__(self, kdict, now=time.time(), context=None): |
|
+ self._kdict = kdict |
|
+ self._context = context |
|
+ self._zones = set(kdict.missing()) |
|
+ |
|
+ for zone in kdict.zones(): |
|
+ self._zones.add(zone) |
|
+ for alg, keys in kdict[zone].items(): |
|
+ for k in keys.values(): |
|
+ if k.sep: |
|
+ self._K[zone][alg].append(k) |
|
+ else: |
|
+ self._Z[zone][alg].append(k) |
|
+ |
|
+ for group in [self._K[zone][alg], self._Z[zone][alg]]: |
|
+ group.sort() |
|
+ for k in group: |
|
+ if k.delete() and k.delete() < now: |
|
+ group.remove(k) |
|
+ |
|
+ def __iter__(self): |
|
+ for zone in self._zones: |
|
+ for collection in [self._K, self._Z]: |
|
+ if zone not in collection: |
|
+ continue |
|
+ for alg, keys in collection[zone].items(): |
|
+ for key in keys: |
|
+ yield key |
|
+ |
|
+ def dump(self): |
|
+ for k in self: |
|
+ print("%s" % repr(k)) |
|
+ |
|
+ def fixseries(self, keys, policy, now, **kwargs): |
|
+ force = kwargs.get('force', False) |
|
+ if not keys: |
|
+ return |
|
+ |
|
+ # handle the first key |
|
+ key = keys[0] |
|
+ if key.sep: |
|
+ rp = policy.ksk_rollperiod |
|
+ prepub = policy.ksk_prepublish or (30 * 86400) |
|
+ postpub = policy.ksk_postpublish or (30 * 86400) |
|
+ else: |
|
+ rp = policy.zsk_rollperiod |
|
+ prepub = policy.zsk_prepublish or (30 * 86400) |
|
+ postpub = policy.zsk_postpublish or (30 * 86400) |
|
+ |
|
+ # the first key should be published and active |
|
+ p = key.publish() |
|
+ a = key.activate() |
|
+ if not p or p > now: |
|
+ key.setpublish(now) |
|
+ if not a or a > now: |
|
+ key.setactivate(now) |
|
+ |
|
+ if not rp: |
|
+ key.setinactive(None, **kwargs) |
|
+ key.setdelete(None, **kwargs) |
|
+ else: |
|
+ key.setinactive(a + rp, **kwargs) |
|
+ key.setdelete(a + rp + postpub, **kwargs) |
|
+ |
|
+ if policy.keyttl != key.ttl: |
|
+ key.setttl(policy.keyttl) |
|
+ |
|
+ # handle all the subsequent keys |
|
+ prev = key |
|
+ for key in keys[1:]: |
|
+ # if no rollperiod, then all keys after the first in |
|
+ # the series kept inactive. |
|
+ # (XXX: we need to change this to allow standby keys) |
|
+ if not rp: |
|
+ key.setpublish(None, **kwargs) |
|
+ key.setactivate(None, **kwargs) |
|
+ key.setinactive(None, **kwargs) |
|
+ key.setdelete(None, **kwargs) |
|
+ if policy.keyttl != key.ttl: |
|
+ key.setttl(policy.keyttl) |
|
+ continue |
|
+ |
|
+ # otherwise, ensure all dates are set correctly based on |
|
+ # the initial key |
|
+ a = prev.inactive() |
|
+ p = a - prepub |
|
+ key.setactivate(a, **kwargs) |
|
+ key.setpublish(p, **kwargs) |
|
+ key.setinactive(a + rp, **kwargs) |
|
+ key.setdelete(a + rp + postpub, **kwargs) |
|
+ prev.setdelete(a + postpub, **kwargs) |
|
+ if policy.keyttl != key.ttl: |
|
+ key.setttl(policy.keyttl) |
|
+ prev = key |
|
+ |
|
+ # if we haven't got sufficient coverage, create successor key(s) |
|
+ while rp and prev.inactive() and \ |
|
+ prev.inactive() < now + policy.coverage: |
|
+ # commit changes to predecessor: a successor can only be |
|
+ # generated if Inactive has been set in the predecessor key |
|
+ prev.commit(self._context['settime_path'], **kwargs) |
|
+ key = prev.generate_successor(self._context['keygen_path'], |
|
+ **kwargs) |
|
+ |
|
+ key.setinactive(key.activate() + rp, **kwargs) |
|
+ key.setdelete(key.inactive() + postpub, **kwargs) |
|
+ keys.append(key) |
|
+ prev = key |
|
+ |
|
+ # last key? we already know we have sufficient coverage now, so |
|
+ # disable the inactivation of the final key (if it was set), |
|
+ # ensuring that if dnssec-keymgr isn't run again, the last key |
|
+ # in the series will at least remain usable. |
|
+ prev.setinactive(None, **kwargs) |
|
+ prev.setdelete(None, **kwargs) |
|
+ |
|
+ # commit changes |
|
+ for key in keys: |
|
+ key.commit(self._context['settime_path'], **kwargs) |
|
+ |
|
+ |
|
+ def enforce_policy(self, policies, now=time.time(), **kwargs): |
|
+ # If zones is provided as a parameter, use that list. |
|
+ # If not, use what we have in this object |
|
+ zones = kwargs.get('zones', self._zones) |
|
+ keys_dir = kwargs.get('dir', self._context.get('keys_path', None)) |
|
+ force = kwargs.get('force', False) |
|
+ |
|
+ for zone in zones: |
|
+ collections = [] |
|
+ policy = policies.policy(zone) |
|
+ keys_dir = keys_dir or policy.directory or '.' |
|
+ alg = policy.algorithm |
|
+ algnum = dnskey.algnum(alg) |
|
+ if 'ksk' not in kwargs or not kwargs['ksk']: |
|
+ if len(self._Z[zone][algnum]) == 0: |
|
+ k = dnskey.generate(self._context['keygen_path'], |
|
+ keys_dir, zone, alg, |
|
+ policy.zsk_keysize, False, |
|
+ policy.keyttl or 3600, |
|
+ **kwargs) |
|
+ self._Z[zone][algnum].append(k) |
|
+ collections.append(self._Z[zone]) |
|
+ |
|
+ if 'zsk' not in kwargs or not kwargs['zsk']: |
|
+ if len(self._K[zone][algnum]) == 0: |
|
+ k = dnskey.generate(self._context['keygen_path'], |
|
+ keys_dir, zone, alg, |
|
+ policy.ksk_keysize, True, |
|
+ policy.keyttl or 3600, |
|
+ **kwargs) |
|
+ self._K[zone][algnum].append(k) |
|
+ collections.append(self._K[zone]) |
|
+ |
|
+ for collection in collections: |
|
+ for algorithm, keys in collection.items(): |
|
+ if algorithm != algnum: |
|
+ continue |
|
+ try: |
|
+ self.fixseries(keys, policy, now, **kwargs) |
|
+ except Exception as e: |
|
+ raise Exception('%s/%s: %s' % |
|
+ (zone, dnskey.algstr(algnum), str(e))) |
|
diff --git a/bin/python/isc/keyzone.py b/bin/python/isc/keyzone.py |
|
new file mode 100644 |
|
index 0000000..7dfb31a |
|
--- /dev/null |
|
+++ b/bin/python/isc/keyzone.py |
|
@@ -0,0 +1,60 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+ |
|
+import os |
|
+import sys |
|
+import re |
|
+from subprocess import Popen, PIPE |
|
+ |
|
+######################################################################## |
|
+# Exceptions |
|
+######################################################################## |
|
+class KeyZoneException(Exception): |
|
+ pass |
|
+ |
|
+######################################################################## |
|
+# class keyzone |
|
+######################################################################## |
|
+class keyzone: |
|
+ """reads a zone file to find data relevant to keys""" |
|
+ |
|
+ def __init__(self, name, filename, czpath): |
|
+ self.maxttl = None |
|
+ self.keyttl = None |
|
+ |
|
+ if not name: |
|
+ return |
|
+ |
|
+ if not czpath or not os.path.isfile(czpath) \ |
|
+ or not os.access(czpath, os.X_OK): |
|
+ raise KeyZoneException('"named-compilezone" not found') |
|
+ return |
|
+ |
|
+ maxttl = keyttl = None |
|
+ |
|
+ fp, _ = Popen([czpath, "-o", "-", name, filename], |
|
+ stdout=PIPE, stderr=PIPE).communicate() |
|
+ for line in fp.splitlines(): |
|
+ if re.search('^[:space:]*;', line): |
|
+ continue |
|
+ fields = line.split() |
|
+ if not maxttl or int(fields[1]) > maxttl: |
|
+ maxttl = int(fields[1]) |
|
+ if fields[3] == "DNSKEY": |
|
+ keyttl = int(fields[1]) |
|
+ |
|
+ self.keyttl = keyttl |
|
+ self.maxttl = maxttl |
|
diff --git a/bin/python/isc/policy.py b/bin/python/isc/policy.py |
|
new file mode 100644 |
|
index 0000000..ed106c6 |
|
--- /dev/null |
|
+++ b/bin/python/isc/policy.py |
|
@@ -0,0 +1,690 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+# policy.py |
|
+# This module implements the parser for the dnssec.policy file. |
|
+############################################################################ |
|
+ |
|
+import re |
|
+import ply.lex as lex |
|
+import ply.yacc as yacc |
|
+from string import * |
|
+from copy import copy |
|
+ |
|
+ |
|
+############################################################################ |
|
+# PolicyLex: a lexer for the policy file syntax. |
|
+############################################################################ |
|
+class PolicyLex: |
|
+ reserved = ('POLICY', |
|
+ 'ALGORITHM_POLICY', |
|
+ 'ZONE', |
|
+ 'ALGORITHM', |
|
+ 'DIRECTORY', |
|
+ 'KEYTTL', |
|
+ 'KEY_SIZE', |
|
+ 'ROLL_PERIOD', |
|
+ 'PRE_PUBLISH', |
|
+ 'POST_PUBLISH', |
|
+ 'COVERAGE', |
|
+ 'STANDBY', |
|
+ 'NONE') |
|
+ |
|
+ tokens = reserved + ('DATESUFFIX', |
|
+ 'KEYTYPE', |
|
+ 'ALGNAME', |
|
+ 'STR', |
|
+ 'QSTRING', |
|
+ 'NUMBER', |
|
+ 'LBRACE', |
|
+ 'RBRACE', |
|
+ 'SEMI') |
|
+ reserved_map = {} |
|
+ |
|
+ t_ignore = ' \t' |
|
+ t_ignore_olcomment = r'(//|\#).*' |
|
+ |
|
+ t_LBRACE = r'\{' |
|
+ t_RBRACE = r'\}' |
|
+ t_SEMI = r';'; |
|
+ |
|
+ def t_newline(self, t): |
|
+ r'\n+' |
|
+ t.lexer.lineno += t.value.count("\n") |
|
+ |
|
+ def t_comment(self, t): |
|
+ r'/\*(.|\n)*?\*/' |
|
+ t.lexer.lineno += t.value.count('\n') |
|
+ |
|
+ def t_DATESUFFIX(self, t): |
|
+ r'(?i)(?<=[0-9 \t])(y(?:ears|ear|ea|e)?|mo(?:nths|nth|nt|n)?|w(?:eeks|eek|ee|e)?|d(?:ays|ay|a)?|h(?:ours|our|ou|o)?|mi(?:nutes|nute|nut|nu|n)?|s(?:econds|econd|econ|eco|ec|e)?)\b' |
|
+ t.value = re.match(r'(?i)(y|mo|w|d|h|mi|s)([a-z]*)', t.value).group(1).lower() |
|
+ return t |
|
+ |
|
+ def t_KEYTYPE(self, t): |
|
+ r'(?i)\b(KSK|ZSK)\b' |
|
+ t.value = t.value.upper() |
|
+ return t |
|
+ |
|
+ def t_ALGNAME(self, t): |
|
+ r'(?i)\b(RSAMD5|DH|DSA|NSEC3DSA|ECC|RSASHA1|NSEC3RSASHA1|RSASHA256|RSASHA512|ECCGOST|ECDSAP256SHA245|ECDSAP384SHA384)\b' |
|
+ t.value = t.value.upper() |
|
+ return t |
|
+ |
|
+ def t_STR(self, t): |
|
+ r'[A-Za-z._-][\w._-]*' |
|
+ t.type = self.reserved_map.get(t.value, "STR") |
|
+ return t |
|
+ |
|
+ def t_QSTRING(self, t): |
|
+ r'"([^"\n]|(\\"))*"' |
|
+ t.type = self.reserved_map.get(t.value, "QSTRING") |
|
+ t.value = t.value[1:-1] |
|
+ return t |
|
+ |
|
+ def t_NUMBER(self, t): |
|
+ r'\d+' |
|
+ t.value = int(t.value) |
|
+ return t |
|
+ |
|
+ def t_error(self, t): |
|
+ print("Illegal character '%s'" % t.value[0]) |
|
+ t.lexer.skip(1) |
|
+ |
|
+ def __init__(self, **kwargs): |
|
+ for r in self.reserved: |
|
+ self.reserved_map[r.lower().translate(maketrans('_', '-'))] = r |
|
+ self.lexer = lex.lex(object=self, **kwargs) |
|
+ |
|
+ def test(self, text): |
|
+ self.lexer.input(text) |
|
+ while True: |
|
+ t = self.lexer.token() |
|
+ if not t: |
|
+ break |
|
+ print(t) |
|
+ |
|
+############################################################################ |
|
+# Policy: this object holds a set of DNSSEC policy settings. |
|
+############################################################################ |
|
+class Policy: |
|
+ is_zone = False |
|
+ is_alg = False |
|
+ is_constructed = False |
|
+ ksk_rollperiod = None |
|
+ zsk_rollperiod = None |
|
+ ksk_prepublish = None |
|
+ zsk_prepublish = None |
|
+ ksk_postpublish = None |
|
+ zsk_postpublish = None |
|
+ ksk_keysize = None |
|
+ zsk_keysize = None |
|
+ ksk_standby = None |
|
+ zsk_standby = None |
|
+ keyttl = None |
|
+ coverage = None |
|
+ directory = None |
|
+ valid_key_sz_per_algo = {'DSA': [512, 1024], |
|
+ 'NSEC3DSA': [512, 1024], |
|
+ 'RSAMD5': [512, 4096], |
|
+ 'RSASHA1': [512, 4096], |
|
+ 'NSEC3RSASHA1': [512, 4096], |
|
+ 'RSASHA256': [512, 4096], |
|
+ 'RSASHA512': [512, 4096], |
|
+ 'ECCGOST': None, |
|
+ 'ECDSAP256SHA245': None, |
|
+ 'ECDSAP384SHA384': None} |
|
+ |
|
+ def __init__(self, name=None, algorithm=None, parent=None): |
|
+ self.name = name |
|
+ self.algorithm = algorithm |
|
+ self.parent = parent |
|
+ pass |
|
+ |
|
+ def __repr__(self): |
|
+ return ("%spolicy %s:\n" |
|
+ "\tinherits %s\n" |
|
+ "\tdirectory %s\n" |
|
+ "\talgorithm %s\n" |
|
+ "\tcoverage %s\n" |
|
+ "\tksk_keysize %s\n" |
|
+ "\tzsk_keysize %s\n" |
|
+ "\tksk_rollperiod %s\n" |
|
+ "\tzsk_rollperiod %s\n" |
|
+ "\tksk_prepublish %s\n" |
|
+ "\tksk_postpublish %s\n" |
|
+ "\tzsk_prepublish %s\n" |
|
+ "\tzsk_postpublish %s\n" |
|
+ "\tksk_standby %s\n" |
|
+ "\tzsk_standby %s\n" |
|
+ "\tkeyttl %s\n" |
|
+ % |
|
+ ((self.is_constructed and 'constructed ' or \ |
|
+ self.is_zone and 'zone ' or \ |
|
+ self.is_alg and 'algorithm ' or ''), |
|
+ self.name or 'UNKNOWN', |
|
+ self.parent and self.parent.name or 'None', |
|
+ self.directory and ('"' + str(self.directory) + '"') or 'None', |
|
+ self.algorithm or 'None', |
|
+ self.coverage and str(self.coverage) or 'None', |
|
+ self.ksk_keysize and str(self.ksk_keysize) or 'None', |
|
+ self.zsk_keysize and str(self.zsk_keysize) or 'None', |
|
+ self.ksk_rollperiod and str(self.ksk_rollperiod) or 'None', |
|
+ self.zsk_rollperiod and str(self.zsk_rollperiod) or 'None', |
|
+ self.ksk_prepublish and str(self.ksk_prepublish) or 'None', |
|
+ self.ksk_postpublish and str(self.ksk_postpublish) or 'None', |
|
+ self.zsk_prepublish and str(self.zsk_prepublish) or 'None', |
|
+ self.zsk_postpublish and str(self.zsk_postpublish) or 'None', |
|
+ self.ksk_standby and str(self.ksk_standby) or 'None', |
|
+ self.zsk_standby and str(self.zsk_standby) or 'None', |
|
+ self.keyttl and str(self.keyttl) or 'None')) |
|
+ |
|
+ def __verify_size(self, key_size, size_range): |
|
+ return (size_range[0] <= key_size <= size_range[1]) |
|
+ |
|
+ def get_name(self): |
|
+ return self.name |
|
+ |
|
+ def constructed(self): |
|
+ return self.is_constructed |
|
+ |
|
+ def validate(self): |
|
+ """ Check if the values in the policy make sense |
|
+ :return: True/False if the policy passes validation |
|
+ """ |
|
+ if self.ksk_rollperiod and \ |
|
+ self.ksk_prepublish is not None and \ |
|
+ self.ksk_prepublish > self.ksk_rollperiod: |
|
+ print(self.ksk_rollperiod) |
|
+ return (False, |
|
+ ('KSK pre-publish period (%d) exceeds rollover period %d' |
|
+ % (self.ksk_prepublish, self.ksk_rollperiod))) |
|
+ |
|
+ if self.ksk_rollperiod and \ |
|
+ self.ksk_postpublish is not None and \ |
|
+ self.ksk_postpublish > self.ksk_rollperiod: |
|
+ return (False, |
|
+ ('KSK post-publish period (%d) exceeds rollover period %d' |
|
+ % (self.ksk_postpublish, self.ksk_rollperiod))) |
|
+ |
|
+ if self.zsk_rollperiod and \ |
|
+ self.zsk_prepublish is not None and \ |
|
+ self.zsk_prepublish >= self.zsk_rollperiod: |
|
+ return (False, |
|
+ ('ZSK pre-publish period (%d) exceeds rollover period %d' |
|
+ % (self.zsk_prepublish, self.zsk_rollperiod))) |
|
+ |
|
+ if self.zsk_rollperiod and \ |
|
+ self.zsk_postpublish is not None and \ |
|
+ self.zsk_postpublish >= self.zsk_rollperiod: |
|
+ return (False, |
|
+ ('ZSK post-publish period (%d) exceeds rollover period %d' |
|
+ % (self.zsk_postpublish, self.zsk_rollperiod))) |
|
+ |
|
+ if self.ksk_rollperiod and \ |
|
+ self.ksk_prepublish and self.ksk_postpublish and \ |
|
+ self.ksk_prepublish + self.ksk_postpublish >= self.ksk_rollperiod: |
|
+ return (False, |
|
+ (('KSK pre/post-publish periods (%d/%d) ' + |
|
+ 'combined exceed rollover period %d') % |
|
+ (self.ksk_prepublish, |
|
+ self.ksk_postpublish, |
|
+ self.ksk_rollperiod))) |
|
+ |
|
+ if self.zsk_rollperiod and \ |
|
+ self.zsk_prepublish and self.zsk_postpublish and \ |
|
+ self.zsk_prepublish + self.zsk_postpublish >= self.zsk_rollperiod: |
|
+ return (False, |
|
+ (('ZSK pre/post-publish periods (%d/%d) ' + |
|
+ 'combined exceed rollover period %d') % |
|
+ (self.zsk_prepublish, |
|
+ self.zsk_postpublish, |
|
+ self.zsk_rollperiod))) |
|
+ |
|
+ if self.algorithm is not None: |
|
+ # Validate the key size |
|
+ key_sz_range = self.valid_key_sz_per_algo.get(self.algorithm) |
|
+ if key_sz_range is not None: |
|
+ # Verify KSK |
|
+ if not self.__verify_size(self.ksk_keysize, key_sz_range): |
|
+ return False, 'KSK key size %d outside valid range %s' \ |
|
+ % (self.ksk_keysize, key_sz_range) |
|
+ |
|
+ # Verify ZSK |
|
+ if not self.__verify_size(self.zsk_keysize, key_sz_range): |
|
+ return False, 'ZSK key size %d outside valid range %s' \ |
|
+ % (self.zsk_keysize, key_sz_range) |
|
+ |
|
+ # Specific check for DSA keys |
|
+ if self.algorithm in ['DSA', 'NSEC3DSA'] and \ |
|
+ self.ksk_keysize % 64 != 0: |
|
+ return False, \ |
|
+ ('KSK key size %d not divisible by 64 ' + |
|
+ 'as required for DSA') % self.ksk_keysize |
|
+ |
|
+ if self.algorithm in ['DSA', 'NSEC3DSA'] and \ |
|
+ self.zsk_keysize % 64 != 0: |
|
+ return False, \ |
|
+ ('ZSK key size %d not divisible by 64 ' + |
|
+ 'as required for DSA') % self.zsk_keysize |
|
+ |
|
+ return True, '' |
|
+ |
|
+############################################################################ |
|
+# dnssec_policy: |
|
+# This class reads a dnssec.policy file and creates a dictionary of |
|
+# DNSSEC policy rules from which a policy for a specific zone can |
|
+# be generated. |
|
+############################################################################ |
|
+class PolicyException(Exception): |
|
+ pass |
|
+ |
|
+class dnssec_policy: |
|
+ alg_policy = {} |
|
+ named_policy = {} |
|
+ zone_policy = {} |
|
+ current = None |
|
+ filename = None |
|
+ initial = True |
|
+ |
|
+ def __init__(self, filename=None, **kwargs): |
|
+ self.plex = PolicyLex() |
|
+ self.tokens = self.plex.tokens |
|
+ if 'debug' not in kwargs: |
|
+ kwargs['debug'] = False |
|
+ if 'write_tables' not in kwargs: |
|
+ kwargs['write_tables'] = False |
|
+ self.parser = yacc.yacc(module=self, **kwargs) |
|
+ |
|
+ # set defaults |
|
+ self.setup('''policy global { algorithm rsasha256; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 2048; |
|
+ roll-period ksk 0; |
|
+ roll-period zsk 1y; |
|
+ pre-publish ksk 1mo; |
|
+ pre-publish zsk 1mo; |
|
+ post-publish ksk 1mo; |
|
+ post-publish zsk 1mo; |
|
+ standby ksk 0; |
|
+ standby zsk 0; |
|
+ keyttl 1h; |
|
+ coverage 6mo; }; |
|
+ policy default { policy global; };''') |
|
+ |
|
+ p = Policy() |
|
+ p.algorithm = None |
|
+ p.is_alg = True |
|
+ p.ksk_keysize = 2048; |
|
+ p.zsk_keysize = 2048; |
|
+ |
|
+ # set default algorithm policies |
|
+ # these need a lower default key size: |
|
+ self.alg_policy['DSA'] = copy(p) |
|
+ self.alg_policy['DSA'].algorithm = "DSA" |
|
+ self.alg_policy['DSA'].name = "DSA" |
|
+ self.alg_policy['DSA'].ksk_keysize = 1024; |
|
+ |
|
+ self.alg_policy['NSEC3DSA'] = copy(p) |
|
+ self.alg_policy['NSEC3DSA'].algorithm = "NSEC3DSA" |
|
+ self.alg_policy['NSEC3DSA'].name = "NSEC3DSA" |
|
+ self.alg_policy['NSEC3DSA'].ksk_keysize = 1024; |
|
+ |
|
+ # these can use default settings |
|
+ self.alg_policy['RSAMD5'] = copy(p) |
|
+ self.alg_policy['RSAMD5'].algorithm = "RSAMD5" |
|
+ self.alg_policy['RSAMD5'].name = "RSAMD5" |
|
+ |
|
+ self.alg_policy['RSASHA1'] = copy(p) |
|
+ self.alg_policy['RSASHA1'].algorithm = "RSASHA1" |
|
+ self.alg_policy['RSASHA1'].name = "RSASHA1" |
|
+ |
|
+ self.alg_policy['NSEC3RSASHA1'] = copy(p) |
|
+ self.alg_policy['NSEC3RSASHA1'].algorithm = "NSEC3RSASHA1" |
|
+ self.alg_policy['NSEC3RSASHA1'].name = "NSEC3RSASHA1" |
|
+ |
|
+ self.alg_policy['RSASHA256'] = copy(p) |
|
+ self.alg_policy['RSASHA256'].algorithm = "RSASHA256" |
|
+ self.alg_policy['RSASHA256'].name = "RSASHA256" |
|
+ |
|
+ self.alg_policy['RSASHA512'] = copy(p) |
|
+ self.alg_policy['RSASHA512'].algorithm = "RSASHA512" |
|
+ self.alg_policy['RSASHA512'].name = "RSASHA512" |
|
+ |
|
+ self.alg_policy['ECCGOST'] = copy(p) |
|
+ self.alg_policy['ECCGOST'].algorithm = "ECCGOST" |
|
+ self.alg_policy['ECCGOST'].name = "ECCGOST" |
|
+ |
|
+ self.alg_policy['ECDSAP256SHA245'] = copy(p) |
|
+ self.alg_policy['ECDSAP256SHA245'].algorithm = "ECDSAP256SHA256" |
|
+ self.alg_policy['ECDSAP256SHA245'].name = "ECDSAP256SHA256" |
|
+ |
|
+ self.alg_policy['ECDSAP384SHA384'] = copy(p) |
|
+ self.alg_policy['ECDSAP384SHA384'].algorithm = "ECDSAP384SHA384" |
|
+ self.alg_policy['ECDSAP384SHA384'].name = "ECDSAP384SHA384" |
|
+ |
|
+ if filename: |
|
+ self.load(filename) |
|
+ |
|
+ def load(self, filename): |
|
+ self.filename = filename |
|
+ self.initial = True |
|
+ with open(filename) as f: |
|
+ text = f.read() |
|
+ self.plex.lexer.lineno = 0 |
|
+ self.parser.parse(text) |
|
+ |
|
+ self.filename = None |
|
+ |
|
+ def setup(self, text): |
|
+ self.initial = True |
|
+ self.plex.lexer.lineno = 0 |
|
+ self.parser.parse(text) |
|
+ |
|
+ def policy(self, zone, **kwargs): |
|
+ z = zone.lower() |
|
+ p = None |
|
+ |
|
+ if z in self.zone_policy: |
|
+ p = self.zone_policy[z] |
|
+ |
|
+ if p is None: |
|
+ p = copy(self.named_policy['default']) |
|
+ p.name = zone |
|
+ p.is_constructed = True |
|
+ |
|
+ if p.algorithm is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent and not parent.algorithm: |
|
+ parent = parent.parent |
|
+ p.algorithm = parent and parent.algorithm or None |
|
+ |
|
+ if p.algorithm in self.alg_policy: |
|
+ ap = self.alg_policy[p.algorithm] |
|
+ else: |
|
+ raise PolicyException('algorithm not found') |
|
+ |
|
+ if p.directory is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent is not None and not parent.directory: |
|
+ parent = parent.parent |
|
+ p.directory = parent and parent.directory |
|
+ |
|
+ if p.coverage is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent and not parent.coverage: |
|
+ parent = parent.parent |
|
+ p.coverage = parent and parent.coverage or ap.coverage |
|
+ |
|
+ if p.ksk_keysize is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.ksk_keysize: |
|
+ parent = parent.parent |
|
+ p.ksk_keysize = parent and parent.ksk_keysize or ap.ksk_keysize |
|
+ |
|
+ if p.zsk_keysize is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.zsk_keysize: |
|
+ parent = parent.parent |
|
+ p.zsk_keysize = parent and parent.zsk_keysize or ap.zsk_keysize |
|
+ |
|
+ if p.ksk_rollperiod is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.ksk_rollperiod: |
|
+ parent = parent.parent |
|
+ p.ksk_rollperiod = parent and \ |
|
+ parent.ksk_rollperiod or ap.ksk_rollperiod |
|
+ |
|
+ if p.zsk_rollperiod is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.zsk_rollperiod: |
|
+ parent = parent.parent |
|
+ p.zsk_rollperiod = parent and \ |
|
+ parent.zsk_rollperiod or ap.zsk_rollperiod |
|
+ |
|
+ if p.ksk_prepublish is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.ksk_prepublish: |
|
+ parent = parent.parent |
|
+ p.ksk_prepublish = parent and \ |
|
+ parent.ksk_prepublish or ap.ksk_prepublish |
|
+ |
|
+ if p.zsk_prepublish is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.zsk_prepublish: |
|
+ parent = parent.parent |
|
+ p.zsk_prepublish = parent and \ |
|
+ parent.zsk_prepublish or ap.zsk_prepublish |
|
+ |
|
+ if p.ksk_postpublish is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.ksk_postpublish: |
|
+ parent = parent.parent |
|
+ p.ksk_postpublish = parent and \ |
|
+ parent.ksk_postpublish or ap.ksk_postpublish |
|
+ |
|
+ if p.zsk_postpublish is None: |
|
+ parent = p.parent or self.named_policy['default'] |
|
+ while parent.parent and not parent.zsk_postpublish: |
|
+ parent = parent.parent |
|
+ p.zsk_postpublish = parent and \ |
|
+ parent.zsk_postpublish or ap.zsk_postpublish |
|
+ |
|
+ if 'novalidate' not in kwargs or not kwargs['novalidate']: |
|
+ (valid, msg) = p.validate() |
|
+ if not valid: |
|
+ raise PolicyException(msg) |
|
+ return None |
|
+ |
|
+ return p |
|
+ |
|
+ |
|
+ def p_policylist(self, p): |
|
+ '''policylist : init policy |
|
+ | policylist policy''' |
|
+ pass |
|
+ |
|
+ def p_init(self, p): |
|
+ "init :" |
|
+ self.initial = False |
|
+ |
|
+ def p_policy(self, p): |
|
+ '''policy : alg_policy |
|
+ | zone_policy |
|
+ | named_policy''' |
|
+ pass |
|
+ |
|
+ def p_name(self, p): |
|
+ '''name : STR |
|
+ | KEYTYPE |
|
+ | DATESUFFIX''' |
|
+ p[0] = p[1] |
|
+ pass |
|
+ |
|
+ def p_new_policy(self, p): |
|
+ "new_policy :" |
|
+ self.current = Policy() |
|
+ |
|
+ def p_alg_policy(self, p): |
|
+ "alg_policy : ALGORITHM_POLICY ALGNAME new_policy alg_option_group SEMI" |
|
+ self.current.name = p[2] |
|
+ self.current.is_alg = True |
|
+ self.alg_policy[p[2]] = self.current |
|
+ pass |
|
+ |
|
+ def p_zone_policy(self, p): |
|
+ "zone_policy : ZONE name new_policy policy_option_group SEMI" |
|
+ self.current.name = p[2] |
|
+ self.current.is_zone = True |
|
+ self.zone_policy[p[2].lower()] = self.current |
|
+ pass |
|
+ |
|
+ def p_named_policy(self, p): |
|
+ "named_policy : POLICY name new_policy policy_option_group SEMI" |
|
+ self.current.name = p[2] |
|
+ self.named_policy[p[2].lower()] = self.current |
|
+ pass |
|
+ |
|
+ def p_duration_1(self, p): |
|
+ "duration : NUMBER" |
|
+ p[0] = p[1] |
|
+ pass |
|
+ |
|
+ def p_duration_2(self, p): |
|
+ "duration : NONE" |
|
+ p[0] = None |
|
+ pass |
|
+ |
|
+ def p_duration_3(self, p): |
|
+ "duration : NUMBER DATESUFFIX" |
|
+ if p[2] == "y": |
|
+ p[0] = p[1] * 31536000 # year |
|
+ elif p[2] == "mo": |
|
+ p[0] = p[1] * 2592000 # month |
|
+ elif p[2] == "w": |
|
+ p[0] = p[1] * 604800 # week |
|
+ elif p[2] == "d": |
|
+ p[0] = p[1] * 86400 # day |
|
+ elif p[2] == "h": |
|
+ p[0] = p[1] * 3600 # hour |
|
+ elif p[2] == "mi": |
|
+ p[0] = p[1] * 60 # minute |
|
+ elif p[2] == "s": |
|
+ p[0] = p[1] # second |
|
+ else: |
|
+ raise PolicyException('invalid duration') |
|
+ |
|
+ def p_policy_option_group(self, p): |
|
+ "policy_option_group : LBRACE policy_option_list RBRACE" |
|
+ pass |
|
+ |
|
+ def p_policy_option_list(self, p): |
|
+ '''policy_option_list : policy_option SEMI |
|
+ | policy_option_list policy_option SEMI''' |
|
+ pass |
|
+ |
|
+ def p_policy_option(self, p): |
|
+ '''policy_option : parent_option |
|
+ | directory_option |
|
+ | coverage_option |
|
+ | rollperiod_option |
|
+ | prepublish_option |
|
+ | postpublish_option |
|
+ | keysize_option |
|
+ | algorithm_option |
|
+ | keyttl_option |
|
+ | standby_option''' |
|
+ pass |
|
+ |
|
+ def p_alg_option_group(self, p): |
|
+ "alg_option_group : LBRACE alg_option_list RBRACE" |
|
+ pass |
|
+ |
|
+ def p_alg_option_list(self, p): |
|
+ '''alg_option_list : alg_option SEMI |
|
+ | alg_option_list alg_option SEMI''' |
|
+ pass |
|
+ |
|
+ def p_alg_option(self, p): |
|
+ '''alg_option : coverage_option |
|
+ | rollperiod_option |
|
+ | prepublish_option |
|
+ | postpublish_option |
|
+ | keyttl_option |
|
+ | keysize_option |
|
+ | standby_option''' |
|
+ pass |
|
+ |
|
+ def p_parent_option(self, p): |
|
+ "parent_option : POLICY name" |
|
+ self.current.parent = self.named_policy[p[2].lower()] |
|
+ |
|
+ def p_directory_option(self, p): |
|
+ "directory_option : DIRECTORY QSTRING" |
|
+ self.current.directory = p[2] |
|
+ |
|
+ def p_coverage_option(self, p): |
|
+ "coverage_option : COVERAGE duration" |
|
+ self.current.coverage = p[2] |
|
+ |
|
+ def p_rollperiod_option(self, p): |
|
+ "rollperiod_option : ROLL_PERIOD KEYTYPE duration" |
|
+ if p[2] == "KSK": |
|
+ self.current.ksk_rollperiod = p[3] |
|
+ else: |
|
+ self.current.zsk_rollperiod = p[3] |
|
+ |
|
+ def p_prepublish_option(self, p): |
|
+ "prepublish_option : PRE_PUBLISH KEYTYPE duration" |
|
+ if p[2] == "KSK": |
|
+ self.current.ksk_prepublish = p[3] |
|
+ else: |
|
+ self.current.zsk_prepublish = p[3] |
|
+ |
|
+ def p_postpublish_option(self, p): |
|
+ "postpublish_option : POST_PUBLISH KEYTYPE duration" |
|
+ if p[2] == "KSK": |
|
+ self.current.ksk_postpublish = p[3] |
|
+ else: |
|
+ self.current.zsk_postpublish = p[3] |
|
+ |
|
+ def p_keysize_option(self, p): |
|
+ "keysize_option : KEY_SIZE KEYTYPE NUMBER" |
|
+ if p[2] == "KSK": |
|
+ self.current.ksk_keysize = p[3] |
|
+ else: |
|
+ self.current.zsk_keysize = p[3] |
|
+ |
|
+ def p_standby_option(self, p): |
|
+ "standby_option : STANDBY KEYTYPE NUMBER" |
|
+ if p[2] == "KSK": |
|
+ self.current.ksk_standby = p[3] |
|
+ else: |
|
+ self.current.zsk_standby = p[3] |
|
+ |
|
+ def p_keyttl_option(self, p): |
|
+ "keyttl_option : KEYTTL duration" |
|
+ self.current.keyttl = p[2] |
|
+ |
|
+ def p_algorithm_option(self, p): |
|
+ "algorithm_option : ALGORITHM ALGNAME" |
|
+ self.current.algorithm = p[2] |
|
+ |
|
+ def p_error(self, p): |
|
+ if p: |
|
+ print("%s%s%d:syntax error near '%s'" % |
|
+ (self.filename or "", ":" if self.filename else "", |
|
+ p.lineno, p.value)) |
|
+ else: |
|
+ if not self.initial: |
|
+ raise PolicyException("%s%s%d:unexpected end of input" % |
|
+ (self.filename or "", ":" if self.filename else "", |
|
+ p and p.lineno or 0)) |
|
+ |
|
+if __name__ == "__main__": |
|
+ import sys |
|
+ if sys.argv[1] == "lex": |
|
+ file = open(sys.argv[2]) |
|
+ text = file.read() |
|
+ file.close() |
|
+ plex = PolicyLex(debug=1) |
|
+ plex.test(text) |
|
+ elif sys.argv[1] == "parse": |
|
+ try: |
|
+ pp = dnssec_policy(sys.argv[2], write_tables=True, debug=True) |
|
+ print(pp.named_policy['default']) |
|
+ print(pp.policy("nonexistent.zone")) |
|
+ except Exception as e: |
|
+ print(e.args[0]) |
|
diff --git a/bin/python/isc/tests/Makefile.in b/bin/python/isc/tests/Makefile.in |
|
new file mode 100644 |
|
index 0000000..506f2cc |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/Makefile.in |
|
@@ -0,0 +1,33 @@ |
|
+# 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. |
|
+ |
|
+srcdir = @srcdir@ |
|
+VPATH = @srcdir@ |
|
+top_srcdir = @top_srcdir@ |
|
+ |
|
+@BIND9_MAKE_INCLUDES@ |
|
+ |
|
+PYTHON = @PYTHON@ |
|
+ |
|
+PYTESTS = dnskey_test.py policy_test.py |
|
+ |
|
+@BIND9_MAKE_RULES@ |
|
+ |
|
+check test: |
|
+ for test in $(PYTESTS); do \ |
|
+ $(PYTHON) $$test; \ |
|
+ done |
|
+ |
|
+clean distclean:: |
|
+ rm -f *.pyc |
|
diff --git a/bin/python/isc/tests/dnskey_test.py b/bin/python/isc/tests/dnskey_test.py |
|
new file mode 100644 |
|
index 0000000..2a63695 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/dnskey_test.py |
|
@@ -0,0 +1,57 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+ |
|
+import sys |
|
+import unittest |
|
+sys.path.append('../..') |
|
+from isc import * |
|
+ |
|
+kdict = None |
|
+ |
|
+ |
|
+def getkey(): |
|
+ global kdict |
|
+ if not kdict: |
|
+ kd = keydict(path='testdata') |
|
+ for key in kd: |
|
+ return key |
|
+ |
|
+ |
|
+class DnskeyTest(unittest.TestCase): |
|
+ def test_metdata(self): |
|
+ key = getkey() |
|
+ self.assertEqual(key.created(), 1448055647) |
|
+ self.assertEqual(key.publish(), 1445463714) |
|
+ self.assertEqual(key.activate(), 1448055714) |
|
+ self.assertEqual(key.revoke(), 1479591714) |
|
+ self.assertEqual(key.inactive(), 1511127714) |
|
+ self.assertEqual(key.delete(), 1542663714) |
|
+ self.assertEqual(key.syncpublish(), 1442871714) |
|
+ self.assertEqual(key.syncdelete(), 1448919714) |
|
+ |
|
+ def test_fmttime(self): |
|
+ key = getkey() |
|
+ self.assertEqual(key.getfmttime('Created'), '20151120214047') |
|
+ self.assertEqual(key.getfmttime('Publish'), '20151021214154') |
|
+ self.assertEqual(key.getfmttime('Activate'), '20151120214154') |
|
+ self.assertEqual(key.getfmttime('Revoke'), '20161119214154') |
|
+ self.assertEqual(key.getfmttime('Inactive'), '20171119214154') |
|
+ self.assertEqual(key.getfmttime('Delete'), '20181119214154') |
|
+ self.assertEqual(key.getfmttime('SyncPublish'), '20150921214154') |
|
+ self.assertEqual(key.getfmttime('SyncDelete'), '20151130214154') |
|
+ |
|
+if __name__ == "__main__": |
|
+ unittest.main() |
|
diff --git a/bin/python/isc/tests/policy_test.py b/bin/python/isc/tests/policy_test.py |
|
new file mode 100644 |
|
index 0000000..c35e023 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/policy_test.py |
|
@@ -0,0 +1,90 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+ |
|
+import sys |
|
+import unittest |
|
+sys.path.append('../..') |
|
+from isc import * |
|
+ |
|
+ |
|
+class PolicyTest(unittest.TestCase): |
|
+ def test_keysize(self): |
|
+ pol = policy.dnssec_policy() |
|
+ pol.load('test-policies/01-keysize.pol') |
|
+ |
|
+ p = pol.policy('good_rsa.test', novalidate=True) |
|
+ self.assertEqual(p.get_name(), "good_rsa.test") |
|
+ self.assertEqual(p.constructed(), False) |
|
+ self.assertEqual(p.validate(), (True, "")) |
|
+ |
|
+ p = pol.policy('good_dsa.test', novalidate=True) |
|
+ self.assertEqual(p.get_name(), "good_dsa.test") |
|
+ self.assertEqual(p.constructed(), False) |
|
+ self.assertEqual(p.validate(), (True, "")) |
|
+ |
|
+ p = pol.policy('bad_dsa.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), |
|
+ (False, 'ZSK key size 769 not divisible by 64 as required for DSA')) |
|
+ |
|
+ def test_prepublish(self): |
|
+ pol = policy.dnssec_policy() |
|
+ pol.load('test-policies/02-prepublish.pol') |
|
+ p = pol.policy('good_prepublish.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), (True, "")) |
|
+ |
|
+ p = pol.policy('bad_prepublish.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), |
|
+ (False, 'KSK pre/post-publish periods ' |
|
+ '(10368000/5184000) combined exceed ' |
|
+ 'rollover period 10368000')) |
|
+ |
|
+ def test_postpublish(self): |
|
+ pol = policy.dnssec_policy() |
|
+ pol.load('test-policies/03-postpublish.pol') |
|
+ |
|
+ p = pol.policy('good_postpublish.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), (True, "")) |
|
+ |
|
+ p = pol.policy('bad_postpublish.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), |
|
+ (False, 'KSK pre/post-publish periods ' |
|
+ '(10368000/5184000) combined exceed ' |
|
+ 'rollover period 10368000')) |
|
+ |
|
+ def test_combined_pre_post(self): |
|
+ pol = policy.dnssec_policy() |
|
+ pol.load('test-policies/04-combined-pre-post.pol') |
|
+ |
|
+ p = pol.policy('good_combined_pre_post_ksk.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), (True, "")) |
|
+ |
|
+ p = pol.policy('bad_combined_pre_post_ksk.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), |
|
+ (False, 'KSK pre/post-publish periods ' |
|
+ '(5184000/5184000) combined exceed ' |
|
+ 'rollover period 10368000')) |
|
+ |
|
+ p = pol.policy('good_combined_pre_post_zsk.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), |
|
+ (True, "")) |
|
+ p = pol.policy('bad_combined_pre_post_zsk.test', novalidate=True) |
|
+ self.assertEqual(p.validate(), |
|
+ (False, 'ZSK pre/post-publish periods ' |
|
+ '(5184000/5184000) combined exceed ' |
|
+ 'rollover period 7776000')) |
|
+ |
|
+if __name__ == "__main__": |
|
+ unittest.main() |
|
diff --git a/bin/python/isc/tests/test-policies/01-keysize.pol b/bin/python/isc/tests/test-policies/01-keysize.pol |
|
new file mode 100644 |
|
index 0000000..b54f1e3 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/test-policies/01-keysize.pol |
|
@@ -0,0 +1,41 @@ |
|
+policy keysize_rsa { |
|
+ algorithm rsasha1; |
|
+ coverage 1y; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish zsk 2w; |
|
+ post-publish zsk 2w; |
|
+ roll-period ksk 1y; |
|
+ pre-publish ksk 1mo; |
|
+ post-publish ksk 2mo; |
|
+ keyttl 1h; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 1024; |
|
+}; |
|
+ |
|
+policy keysize_dsa { |
|
+ algorithm dsa; |
|
+ coverage 1y; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 1024; |
|
+}; |
|
+ |
|
+zone good_rsa.test { |
|
+ policy keysize_rsa; |
|
+}; |
|
+ |
|
+zone bad_rsa.test { |
|
+ policy keysize_rsa; |
|
+ key-size ksk 511; |
|
+}; |
|
+ |
|
+zone good_dsa.test { |
|
+ policy keysize_dsa; |
|
+ key-size ksk 1024; |
|
+ key-size zsk 768; |
|
+}; |
|
+ |
|
+zone bad_dsa.test { |
|
+ policy keysize_dsa; |
|
+ key-size ksk 1024; |
|
+ key-size zsk 769; |
|
+}; |
|
diff --git a/bin/python/isc/tests/test-policies/02-prepublish.pol b/bin/python/isc/tests/test-policies/02-prepublish.pol |
|
new file mode 100644 |
|
index 0000000..e4d11c2 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/test-policies/02-prepublish.pol |
|
@@ -0,0 +1,31 @@ |
|
+policy prepublish_rsa { |
|
+ algorithm rsasha1; |
|
+ coverage 1y; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish zsk 2w; |
|
+ post-publish zsk 2w; |
|
+ roll-period ksk 1y; |
|
+ pre-publish ksk 1mo; |
|
+ post-publish ksk 2mo; |
|
+ keyttl 1h; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 1024; |
|
+}; |
|
+ |
|
+// Policy that defines a pre-publish period lower than the rollover period |
|
+zone good_prepublish.test { |
|
+ policy prepublish_rsa; |
|
+ coverage 6mo; |
|
+ roll-period ksk 4mo; |
|
+ pre-publish ksk 1mo; |
|
+}; |
|
+ |
|
+// Policy that defines a pre-publish period equal to the rollover period |
|
+zone bad_prepublish.test { |
|
+ policy prepublish_rsa; |
|
+ coverage 6mo; |
|
+ roll-period ksk 4mo; |
|
+ pre-publish ksk 4mo; |
|
+}; |
|
+ |
|
+ |
|
diff --git a/bin/python/isc/tests/test-policies/03-postpublish.pol b/bin/python/isc/tests/test-policies/03-postpublish.pol |
|
new file mode 100644 |
|
index 0000000..a4c3a99 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/test-policies/03-postpublish.pol |
|
@@ -0,0 +1,31 @@ |
|
+policy postpublish_rsa { |
|
+ algorithm rsasha1; |
|
+ coverage 1y; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish zsk 2w; |
|
+ post-publish zsk 2w; |
|
+ roll-period ksk 1y; |
|
+ pre-publish ksk 1mo; |
|
+ post-publish ksk 2mo; |
|
+ keyttl 1h; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 1024; |
|
+}; |
|
+ |
|
+// Policy that defines a post-publish period lower than the rollover period |
|
+zone good_postpublish.test { |
|
+ policy postpublish_rsa; |
|
+ coverage 6mo; |
|
+ roll-period ksk 4mo; |
|
+ pre-publish ksk 1mo; |
|
+}; |
|
+ |
|
+// Policy that defines a post-publish period equal to the rollover period |
|
+zone bad_postpublish.test { |
|
+ policy postpublish_rsa; |
|
+ coverage 6mo; |
|
+ roll-period ksk 4mo; |
|
+ pre-publish ksk 4mo; |
|
+}; |
|
+ |
|
+ |
|
diff --git a/bin/python/isc/tests/test-policies/04-combined-pre-post.pol b/bin/python/isc/tests/test-policies/04-combined-pre-post.pol |
|
new file mode 100644 |
|
index 0000000..5695559 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/test-policies/04-combined-pre-post.pol |
|
@@ -0,0 +1,55 @@ |
|
+policy combined_pre_post_rsa { |
|
+ algorithm rsasha1; |
|
+ coverage 1y; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish zsk 2w; |
|
+ post-publish zsk 2w; |
|
+ roll-period ksk 1y; |
|
+ pre-publish ksk 1mo; |
|
+ post-publish ksk 2mo; |
|
+ keyttl 1h; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 1024; |
|
+}; |
|
+ |
|
+// Policy that defines a combined pre-publish and post-publish period lower |
|
+// than the rollover period |
|
+zone good_combined_pre_post_ksk.test { |
|
+ policy combined_pre_post_rsa; |
|
+ coverage 6mo; |
|
+ roll-period ksk 4mo; |
|
+ pre-publish ksk 1mo; |
|
+ post-publish ksk 1mo; |
|
+}; |
|
+ |
|
+// Policy that defines a combined pre-publish and post-publish period higher |
|
+// than the rollover period |
|
+zone bad_combined_pre_post_ksk.test { |
|
+ policy combined_pre_post_rsa; |
|
+ coverage 6mo; |
|
+ roll-period ksk 4mo; |
|
+ pre-publish ksk 2mo; |
|
+ post-publish ksk 2mo; |
|
+}; |
|
+ |
|
+// Policy that defines a combined pre-publish and post-publish period lower |
|
+// than the rollover period |
|
+zone good_combined_pre_post_zsk.test { |
|
+ policy combined_pre_post_rsa; |
|
+ coverage 1y; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish zsk 1mo; |
|
+ post-publish zsk 1mo; |
|
+}; |
|
+ |
|
+// Policy that defines a combined pre-publish and post-publish period higher |
|
+// than the rollover period |
|
+zone bad_combined_pre_post_zsk.test { |
|
+ policy combined_pre_post_rsa; |
|
+ coverage 1y; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish zsk 2mo; |
|
+ post-publish zsk 2mo; |
|
+}; |
|
+ |
|
+ |
|
diff --git a/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key |
|
new file mode 100644 |
|
index 0000000..c5afbe2 |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key |
|
@@ -0,0 +1,8 @@ |
|
+; This is a key-signing key, keyid 35529, for example.com. |
|
+; Created: 20151120214047 (Fri Nov 20 13:40:47 2015) |
|
+; Publish: 20151021214154 (Wed Oct 21 14:41:54 2015) |
|
+; Activate: 20151120214154 (Fri Nov 20 13:41:54 2015) |
|
+; Revoke: 20161119214154 (Sat Nov 19 13:41:54 2016) |
|
+; Inactive: 20171119214154 (Sun Nov 19 13:41:54 2017) |
|
+; Delete: 20181119214154 (Mon Nov 19 13:41:54 2018) |
|
+example.com. IN DNSKEY 257 3 7 AwEAAbbJK96tY8d4sF6RLxh9SVIhho5s2ZhrcijT5j1SNLECen7QLutj VJPEiG8UgBLaJSGkxPDxOygYv4hwh4JXBSj89o9rNabAJtCa9XzIXSpt /cfiCfvqmcOZb9nepmDCXsC7gn/gbae/4Y5ym9XOiCp8lu+tlFWgRiJ+ kxDGN48rRPrGfpq+SfwM9NUtftVa7B0EFVzDkADKedRj0SSGYOqH+WYH CnWjhPFmgJoAw3/m4slTHW1l+mDwFvsCMjXopg4JV0CNnTybnOmyuIwO LWRhB3q8ze24sYBU1fpE9VAMxZ++4Kqh/2MZFeDAs7iPPKSmI3wkRCW5 pkwDLO5lJ9c= |
|
diff --git a/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private |
|
new file mode 100644 |
|
index 0000000..af22c6a |
|
--- /dev/null |
|
+++ b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private |
|
@@ -0,0 +1,18 @@ |
|
+Private-key-format: v1.3 |
|
+Algorithm: 7 (NSEC3RSASHA1) |
|
+Modulus: tskr3q1jx3iwXpEvGH1JUiGGjmzZmGtyKNPmPVI0sQJ6ftAu62NUk8SIbxSAEtolIaTE8PE7KBi/iHCHglcFKPz2j2s1psAm0Jr1fMhdKm39x+IJ++qZw5lv2d6mYMJewLuCf+Btp7/hjnKb1c6IKnyW762UVaBGIn6TEMY3jytE+sZ+mr5J/Az01S1+1VrsHQQVXMOQAMp51GPRJIZg6of5ZgcKdaOE8WaAmgDDf+biyVMdbWX6YPAW+wIyNeimDglXQI2dPJuc6bK4jA4tZGEHerzN7bixgFTV+kT1UAzFn77gqqH/YxkV4MCzuI88pKYjfCREJbmmTAMs7mUn1w== |
|
+PublicExponent: AQAB |
|
+PrivateExponent: jfiM6YU1Rd6Y5qrPsK7HP1Ko54DmNbvmzI1hfGmYYZAyQsNCXjQloix5aAW9QGdNhecrzJUhxJAMXFZC+lrKuD5a56R25JDE1Sw21nft3SHXhuQrqw5Z5hIMTWXhRrBR1lMOFnLj2PJxqCmenp+vJYjl1z20RBmbv/keE15SExFRJIJ3G0lI4V0KxprY5rgsT/vID0pS32f7rmXhgEzyWDyuxceTMidBooD5BSeEmSTYa4rvCVZ2vgnzIGSxjYDPJE2rGve2dpvdXQuujRFaf4+/FzjaOgg35rTtUmC9klfB4D6KJIfc1PNUwcH7V0VJ2fFlgZgMYi4W331QORl9sQ== |
|
+Prime1: 479rW3EeoBwHhUKDy5YeyfnMKjhaosrcYhW4resevLzatFrvS/n2KxJnsHoEzmGr2A13naI61RndgVBBOwNDWI3/tQ+aKvcr+V9m4omROV3xYa8s1FsDbEW0Z6G0UheaqRFir8WK98/Lj6Zht1uBXHSPPf91OW0qj+b5gbX7TK8= |
|
+Prime2: zXXlxgIq+Ih6kxsUw4Ith0nd/d2P3d42QYPjxYjsg4xYicPAjva9HltnbBQ2lr4JEG9Yyb8KalSnJUSuvXtn7bGfBzLu8W6omCeVWXQVH4NIu9AjpO16NpMKWGRfiHHbbSYJs1daTZKHC2FEmi18MKX/RauHGGOakFQ/3A/GMVk= |
|
+Exponent1: 0o9UQ1uHNAIWFedUEHJ/jr7LOrGVYnLpZCmu7+S0K0zzatGz8ets44+FnAyDywdUKFDzKSMm/4SFXRwE4vl2VzYZlp2RLG4PEuRYK9OCF6a6F1UsvjxTItQjIbjIDSnTjMINGnMps0lDa1EpgKsyI3eEQ46eI3TBZ//k6D6G0vM= |
|
+Exponent2: d+CYJgXRyJzo17fvT3s+0TbaHWsOq+chROyNEw4m4UIbzpW2XjO8eF/gYgERMLbEVyCAb4XVr+CgfXArfEbqhpciMHMZUyi7mbtOupiuUmqpH1v70Bj3O6xjVtuJmfTEkFSnSEppV+VsgclI26Q6V7Ai1yWTdzl2T0u4zs8tVlE= |
|
+Coefficient: E4EYw76gIChdQDn6+Uh44/xH9Uwmvq3OETR8w/kEZ0xQ8AkTdKFKUp84nlR6gN+ljb2mUxERKrVLwnBsU8EbUlo9UccMbBGkkZ/8MyfGCBb9nUyOFtOxdHY2M0MQadesRptXHt/m30XjdohwmT7qfSIENwtgUOHbwFnn7WPMc/k= |
|
+Created: 20151120214047 |
|
+Publish: 20151021214154 |
|
+Activate: 20151120214154 |
|
+Revoke: 20161119214154 |
|
+Inactive: 20171119214154 |
|
+Delete: 20181119214154 |
|
+SyncPublish: 20150921214154 |
|
+SyncDelete: 20151130214154 |
|
diff --git a/bin/python/isc/utils.py.in b/bin/python/isc/utils.py.in |
|
new file mode 100644 |
|
index 0000000..48b9685 |
|
--- /dev/null |
|
+++ b/bin/python/isc/utils.py.in |
|
@@ -0,0 +1,57 @@ |
|
+############################################################################ |
|
+# Copyright (C) 2013-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. |
|
+############################################################################ |
|
+# utils.py |
|
+# Grouping shared code in one place |
|
+############################################################################ |
|
+ |
|
+import os |
|
+ |
|
+# 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" |
|
+ h_key = None |
|
+ key_found = True |
|
+ try: |
|
+ h_key = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, bind_subkey) |
|
+ except: |
|
+ key_found = False |
|
+ if key_found: |
|
+ try: |
|
+ (named_base, _) = win32api.RegQueryValueEx(h_key, "InstallDir") |
|
+ except: |
|
+ key_found = False |
|
+ win32api.RegCloseKey(h_key) |
|
+ if key_found: |
|
+ return os.path.join(named_base, bindir) |
|
+ return os.path.join(win32api.GetSystemDirectory(), bindir) |
|
+ |
|
+ |
|
+def shellquote(s): |
|
+ if os.name == 'nt': |
|
+ return '"' + s.replace('"', '"\\"') + '"' |
|
+ return "'" + s.replace("'", "'\\''") + "'" |
|
+ |
|
+ |
|
+version = '@BIND9_VERSION@' |
|
+sysconfdir = '@expanded_sysconfdir@' |
|
diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in |
|
index 2bd42f9..930928b 100644 |
|
--- a/bin/tests/system/conf.sh.in |
|
+++ b/bin/tests/system/conf.sh.in |
|
@@ -46,6 +46,7 @@ DSFROMKEY=$TOP/bin/dnssec/dnssec-dsfromkey |
|
IMPORTKEY=$TOP/bin/dnssec/dnssec-importkey |
|
CHECKDS=$TOP/bin/python/dnssec-checkds |
|
COVERAGE=$TOP/bin/python/dnssec-coverage |
|
+KEYMGR=$TOP/bin/python/dnssec-keymgr |
|
CHECKZONE=$TOP/bin/check/named-checkzone |
|
CHECKCONF=$TOP/bin/check/named-checkconf |
|
PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -q -s ${SLOT:-0} -p ${HSMPIN:-1234}" |
|
@@ -60,7 +61,7 @@ SAMPLE=$TOP/lib/export/samples/sample |
|
# load on the machine to make it unusable to other users. |
|
# v6synth |
|
SUBDIRS="acl additional allow_query addzone autosign builtin |
|
- cacheclean checkconf @CHECKDS@ checknames checkzone @COVERAGE@ |
|
+ cacheclean checkconf @CHECKDS@ checknames checkzone @COVERAGE@ @KEYMGR@ |
|
database digdelv dlv dlvauto dlz dlzexternal dname dns64 dnssec dyndb |
|
ecdsa formerr forward glue gost ixfr inline limits logfileconfig |
|
lwresd masterfile masterformat metadata notify nsupdate pending |
|
@@ -70,6 +71,10 @@ SUBDIRS="acl additional allow_query addzone autosign builtin |
|
|
|
# PERL will be an empty string if no perl interpreter was found. |
|
PERL=@PERL@ |
|
+ |
|
+# PYTHON will be an empty string if no python interpreter was found. |
|
+PYTHON=@PYTHON@ |
|
+ |
|
if test -n "$PERL" |
|
then |
|
if $PERL -e "use IO::Socket::INET6;" 2> /dev/null |
|
@@ -83,5 +88,5 @@ else |
|
fi |
|
|
|
export NAMED LWRESD DIG NSUPDATE KEYGEN KEYFRLAB SIGNER KEYSIGNER KEYSETTOOL \ |
|
- PERL SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ |
|
+ PERL PYTHON SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ |
|
JOURNALPRINT ARPANAME SAMPLE |
|
diff --git a/bin/tests/system/keymgr/01-ksk-inactive/README b/bin/tests/system/keymgr/01-ksk-inactive/README |
|
new file mode 100644 |
|
index 0000000..b807029 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/01-ksk-inactive/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes one KSK rollover. The KSK is deactivated prior to |
|
+its replacement being activated. |
|
diff --git a/bin/tests/system/keymgr/01-ksk-inactive/expect b/bin/tests/system/keymgr/01-ksk-inactive/expect |
|
new file mode 100644 |
|
index 0000000..b076310 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/01-ksk-inactive/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1h -m 2h example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/02-zsk-inactive/README b/bin/tests/system/keymgr/02-zsk-inactive/README |
|
new file mode 100644 |
|
index 0000000..bf56562 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/02-zsk-inactive/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes one ZSK rollover. The first ZSK is deactivated |
|
+prior to its replacement being activated. |
|
diff --git a/bin/tests/system/keymgr/02-zsk-inactive/expect b/bin/tests/system/keymgr/02-zsk-inactive/expect |
|
new file mode 100644 |
|
index 0000000..b076310 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/02-zsk-inactive/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1h -m 2h example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/03-ksk-unpublished/README b/bin/tests/system/keymgr/03-ksk-unpublished/README |
|
new file mode 100644 |
|
index 0000000..0581c67 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/03-ksk-unpublished/README |
|
@@ -0,0 +1,2 @@ |
|
+This set contains one KSK rollover. The KSK is unpublished before its |
|
+successor is published. |
|
diff --git a/bin/tests/system/keymgr/03-ksk-unpublished/expect b/bin/tests/system/keymgr/03-ksk-unpublished/expect |
|
new file mode 100644 |
|
index 0000000..b076310 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/03-ksk-unpublished/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1h -m 2h example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/04-zsk-unpublished/README b/bin/tests/system/keymgr/04-zsk-unpublished/README |
|
new file mode 100644 |
|
index 0000000..589490d |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/04-zsk-unpublished/README |
|
@@ -0,0 +1,2 @@ |
|
+This set contains one ZSK rollover. The ZSK is unpublished before its |
|
+successor is published. |
|
diff --git a/bin/tests/system/keymgr/04-zsk-unpublished/expect b/bin/tests/system/keymgr/04-zsk-unpublished/expect |
|
new file mode 100644 |
|
index 0000000..b076310 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/04-zsk-unpublished/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1h -m 2h example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/05-ksk-unpub-active/README b/bin/tests/system/keymgr/05-ksk-unpub-active/README |
|
new file mode 100644 |
|
index 0000000..026028c |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/05-ksk-unpub-active/README |
|
@@ -0,0 +1,3 @@ |
|
+This set includes one KSK rollover. The first KSK is deleted |
|
+and its successor published prior to the first KSK being deactivated |
|
+and its successor activated. |
|
diff --git a/bin/tests/system/keymgr/05-ksk-unpub-active/expect b/bin/tests/system/keymgr/05-ksk-unpub-active/expect |
|
new file mode 100644 |
|
index 0000000..b076310 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/05-ksk-unpub-active/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1h -m 2h example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/06-zsk-unpub-active/README b/bin/tests/system/keymgr/06-zsk-unpub-active/README |
|
new file mode 100644 |
|
index 0000000..026028c |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/06-zsk-unpub-active/README |
|
@@ -0,0 +1,3 @@ |
|
+This set includes one KSK rollover. The first KSK is deleted |
|
+and its successor published prior to the first KSK being deactivated |
|
+and its successor activated. |
|
diff --git a/bin/tests/system/keymgr/06-zsk-unpub-active/expect b/bin/tests/system/keymgr/06-zsk-unpub-active/expect |
|
new file mode 100644 |
|
index 0000000..b076310 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/06-zsk-unpub-active/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1h -m 2h example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/07-ksk-ttl/README b/bin/tests/system/keymgr/07-ksk-ttl/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/07-ksk-ttl/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/07-ksk-ttl/expect b/bin/tests/system/keymgr/07-ksk-ttl/expect |
|
new file mode 100644 |
|
index 0000000..de792a9 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/07-ksk-ttl/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/08-zsk-ttl/README b/bin/tests/system/keymgr/08-zsk-ttl/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/08-zsk-ttl/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/08-zsk-ttl/expect b/bin/tests/system/keymgr/08-zsk-ttl/expect |
|
new file mode 100644 |
|
index 0000000..de792a9 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/08-zsk-ttl/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/09-no-keys/README b/bin/tests/system/keymgr/09-no-keys/README |
|
new file mode 100644 |
|
index 0000000..6295fa3 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/09-no-keys/README |
|
@@ -0,0 +1 @@ |
|
+This directory has no key set, but one will be initialized by dnssec-keymgr. |
|
diff --git a/bin/tests/system/keymgr/09-no-keys/expect b/bin/tests/system/keymgr/09-no-keys/expect |
|
new file mode 100644 |
|
index 0000000..de792a9 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/09-no-keys/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/10-change-roll/README b/bin/tests/system/keymgr/10-change-roll/README |
|
new file mode 100644 |
|
index 0000000..26073c3 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/10-change-roll/README |
|
@@ -0,0 +1,3 @@ |
|
+This directory has a key set which is valid, but has a ZSK rollover period |
|
+of only three months. It will be updated to have a ZSK rollover period of |
|
+one year. |
|
diff --git a/bin/tests/system/keymgr/10-change-roll/expect b/bin/tests/system/keymgr/10-change-roll/expect |
|
new file mode 100644 |
|
index 0000000..de792a9 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/10-change-roll/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/11-many-simul/README b/bin/tests/system/keymgr/11-many-simul/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/11-many-simul/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/11-many-simul/expect b/bin/tests/system/keymgr/11-many-simul/expect |
|
new file mode 100644 |
|
index 0000000..de792a9 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/11-many-simul/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/12-many-active/README b/bin/tests/system/keymgr/12-many-active/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/12-many-active/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/12-many-active/expect b/bin/tests/system/keymgr/12-many-active/expect |
|
new file mode 100644 |
|
index 0000000..f990a7a |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/12-many-active/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf -f example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/13-noroll/README b/bin/tests/system/keymgr/13-noroll/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/13-noroll/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/13-noroll/expect b/bin/tests/system/keymgr/13-noroll/expect |
|
new file mode 100644 |
|
index 0000000..40616e1 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/13-noroll/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-f -c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/14-wrongalg/README b/bin/tests/system/keymgr/14-wrongalg/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/14-wrongalg/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/14-wrongalg/expect b/bin/tests/system/keymgr/14-wrongalg/expect |
|
new file mode 100644 |
|
index 0000000..436f05f |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/14-wrongalg/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=4 |
|
diff --git a/bin/tests/system/keymgr/15-unspec/README b/bin/tests/system/keymgr/15-unspec/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/15-unspec/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/15-unspec/expect b/bin/tests/system/keymgr/15-unspec/expect |
|
new file mode 100644 |
|
index 0000000..b1ff4fc |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/15-unspec/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/16-wrongalg-unspec/README b/bin/tests/system/keymgr/16-wrongalg-unspec/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/16-wrongalg-unspec/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/16-wrongalg-unspec/expect b/bin/tests/system/keymgr/16-wrongalg-unspec/expect |
|
new file mode 100644 |
|
index 0000000..7a21dec |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/16-wrongalg-unspec/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf" |
|
+kmatch="" |
|
+kret=0 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=4 |
|
diff --git a/bin/tests/system/keymgr/17-noforce/README b/bin/tests/system/keymgr/17-noforce/README |
|
new file mode 100644 |
|
index 0000000..8b9dc02 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/17-noforce/README |
|
@@ -0,0 +1,2 @@ |
|
+This set includes a KSK rollover, with insufficient delay between |
|
+prepublication and rollover. |
|
diff --git a/bin/tests/system/keymgr/17-noforce/expect b/bin/tests/system/keymgr/17-noforce/expect |
|
new file mode 100644 |
|
index 0000000..a5bf1f1 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/17-noforce/expect |
|
@@ -0,0 +1,9 @@ |
|
+kargs="-c policy.conf example.com" |
|
+kmatch="" |
|
+kret=1 |
|
+cargs="-d 1w -m 2w example.com" |
|
+cmatch="" |
|
+cret=0 |
|
+warn=0 |
|
+error=0 |
|
+ok=2 |
|
diff --git a/bin/tests/system/keymgr/clean.sh b/bin/tests/system/keymgr/clean.sh |
|
new file mode 100644 |
|
index 0000000..66d3d08 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/clean.sh |
|
@@ -0,0 +1,21 @@ |
|
+#!/bin/sh |
|
+# |
|
+# Copyright (C) 2016 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. |
|
+ |
|
+rm -f */K*.key |
|
+rm -f */K*.private |
|
+rm -f coverage.* keymgr.* |
|
+rm -f policy.out |
|
+rm -f random.data |
|
diff --git a/bin/tests/system/keymgr/policy.conf b/bin/tests/system/keymgr/policy.conf |
|
new file mode 100644 |
|
index 0000000..e6b7d98 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/policy.conf |
|
@@ -0,0 +1,10 @@ |
|
+policy default { |
|
+ policy global; |
|
+ algorithm nsec3rsasha1; |
|
+ key-size zsk 1024; |
|
+ pre-publish zsk 6w; |
|
+ post-publish zsk 6w; |
|
+ roll-period zsk 6mo; |
|
+ roll-period ksk 0; |
|
+ coverage 364d; |
|
+}; |
|
diff --git a/bin/tests/system/keymgr/policy.good b/bin/tests/system/keymgr/policy.good |
|
new file mode 100644 |
|
index 0000000..0038a27 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/policy.good |
|
@@ -0,0 +1,170 @@ |
|
+policy default: |
|
+ inherits global |
|
+ directory None |
|
+ algorithm None |
|
+ coverage None |
|
+ ksk_keysize None |
|
+ zsk_keysize None |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod None |
|
+ ksk_prepublish None |
|
+ ksk_postpublish None |
|
+ zsk_prepublish None |
|
+ zsk_postpublish None |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl None |
|
+ |
|
+policy global: |
|
+ inherits None |
|
+ directory None |
|
+ algorithm RSASHA256 |
|
+ coverage 15552000 |
|
+ ksk_keysize 2048 |
|
+ zsk_keysize 2048 |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod 31536000 |
|
+ ksk_prepublish 2592000 |
|
+ ksk_postpublish 2592000 |
|
+ zsk_prepublish 2592000 |
|
+ zsk_postpublish 2592000 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl 3600 |
|
+ |
|
+constructed policy example.com: |
|
+ inherits global |
|
+ directory None |
|
+ algorithm RSASHA256 |
|
+ coverage 15552000 |
|
+ ksk_keysize 2048 |
|
+ zsk_keysize 2048 |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod 31536000 |
|
+ ksk_prepublish 2592000 |
|
+ ksk_postpublish 2592000 |
|
+ zsk_prepublish 2592000 |
|
+ zsk_postpublish 2592000 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl None |
|
+ |
|
+policy default: |
|
+ inherits None |
|
+ directory "keydir" |
|
+ algorithm RSASHA1 |
|
+ coverage 31536000 |
|
+ ksk_keysize None |
|
+ zsk_keysize None |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod 15552000 |
|
+ ksk_prepublish None |
|
+ ksk_postpublish None |
|
+ zsk_prepublish 3628800 |
|
+ zsk_postpublish 3628800 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl 3600 |
|
+ |
|
+zone policy example.com: |
|
+ inherits extra |
|
+ directory "keydir" |
|
+ algorithm NSEC3RSASHA1 |
|
+ coverage 12960000 |
|
+ ksk_keysize 2048 |
|
+ zsk_keysize 2048 |
|
+ ksk_rollperiod 31536000 |
|
+ zsk_rollperiod 7776000 |
|
+ ksk_prepublish 7776000 |
|
+ ksk_postpublish None |
|
+ zsk_prepublish 3628800 |
|
+ zsk_postpublish 604800 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl None |
|
+ |
|
+constructed policy example.org: |
|
+ inherits None |
|
+ directory "keydir" |
|
+ algorithm RSASHA1 |
|
+ coverage 31536000 |
|
+ ksk_keysize 2048 |
|
+ zsk_keysize 1024 |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod 15552000 |
|
+ ksk_prepublish None |
|
+ ksk_postpublish None |
|
+ zsk_prepublish 3628800 |
|
+ zsk_postpublish 3628800 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl 3600 |
|
+ |
|
+constructed policy example.net: |
|
+ inherits None |
|
+ directory "keydir" |
|
+ algorithm RSASHA1 |
|
+ coverage 31536000 |
|
+ ksk_keysize 2048 |
|
+ zsk_keysize 1024 |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod 15552000 |
|
+ ksk_prepublish None |
|
+ ksk_postpublish None |
|
+ zsk_prepublish 3628800 |
|
+ zsk_postpublish 3628800 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl 3600 |
|
+ |
|
+algorithm policy RSASHA1: |
|
+ inherits None |
|
+ directory None |
|
+ algorithm None |
|
+ coverage None |
|
+ ksk_keysize 2048 |
|
+ zsk_keysize 1024 |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod None |
|
+ ksk_prepublish None |
|
+ ksk_postpublish None |
|
+ zsk_prepublish None |
|
+ zsk_postpublish None |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl None |
|
+ |
|
+algorithm policy DSA: |
|
+ inherits None |
|
+ directory None |
|
+ algorithm DSA |
|
+ coverage None |
|
+ ksk_keysize 1024 |
|
+ zsk_keysize 2048 |
|
+ ksk_rollperiod None |
|
+ zsk_rollperiod None |
|
+ ksk_prepublish None |
|
+ ksk_postpublish None |
|
+ zsk_prepublish None |
|
+ zsk_postpublish None |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl None |
|
+ |
|
+policy extra: |
|
+ inherits default |
|
+ directory None |
|
+ algorithm None |
|
+ coverage 157680000 |
|
+ ksk_keysize None |
|
+ zsk_keysize None |
|
+ ksk_rollperiod 31536000 |
|
+ zsk_rollperiod 7776000 |
|
+ ksk_prepublish 7776000 |
|
+ ksk_postpublish None |
|
+ zsk_prepublish None |
|
+ zsk_postpublish 604800 |
|
+ ksk_standby None |
|
+ zsk_standby None |
|
+ keyttl 7200 |
|
+ |
|
diff --git a/bin/tests/system/keymgr/policy.sample b/bin/tests/system/keymgr/policy.sample |
|
new file mode 100644 |
|
index 0000000..d96a40d |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/policy.sample |
|
@@ -0,0 +1,40 @@ |
|
+# a comment which should be skipped |
|
+ |
|
+algorithm-policy rsasha1 { |
|
+ key-size ksk 2048; |
|
+ key-size zsk 1024; // this too |
|
+}; |
|
+ |
|
+// and this |
|
+ |
|
+policy default { |
|
+ directory "keydir"; |
|
+ algorithm rsasha1; |
|
+ coverage 1y; # another comment |
|
+ roll-period zsk 6mo; // and yet another |
|
+ pre-publish zsk 6w; |
|
+ post-publish zsk 6w; |
|
+ keyttl 1h; |
|
+}; |
|
+ |
|
+policy extra { |
|
+ policy default; |
|
+ coverage 5y; |
|
+ roll-period KSK 1 year; |
|
+ roll-period zsk 3mo; |
|
+ pre-publish ksk 3mo; |
|
+ post-publish zsk 1w; |
|
+ keyttl 2h; |
|
+}; |
|
+ |
|
+/* |
|
+ * and this is also a comment, |
|
+ * and it should be ignored like |
|
+ * the others. |
|
+ */ |
|
+ |
|
+zone example.com { |
|
+ policy extra; |
|
+ coverage 5 mon; |
|
+ algorithm nsec3rsasha1; |
|
+}; |
|
diff --git a/bin/tests/system/keymgr/prereq.sh b/bin/tests/system/keymgr/prereq.sh |
|
new file mode 100644 |
|
index 0000000..be2546e |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/prereq.sh |
|
@@ -0,0 +1,30 @@ |
|
+#!/bin/sh |
|
+# |
|
+# Copyright (C) 2016 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. |
|
+ |
|
+SYSTEMTESTTOP=.. |
|
+. $SYSTEMTESTTOP/conf.sh |
|
+ |
|
+../../../tools/genrandom 400 random.data |
|
+ |
|
+if $KEYGEN -q -a RSAMD5 -b 512 -n zone -r random.data foo > /dev/null 2>&1 |
|
+then |
|
+ rm -f Kfoo* |
|
+else |
|
+ echo "I:This test requires cryptography" >&2 |
|
+ echo "I:--with-openssl, or --with-pkcs11 and --enable-native-pkcs11" >&2 |
|
+ exit 1 |
|
+fi |
|
+#exec $SHELL ../testcrypto.sh |
|
diff --git a/bin/tests/system/keymgr/setup.sh b/bin/tests/system/keymgr/setup.sh |
|
new file mode 100644 |
|
index 0000000..0483f51 |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/setup.sh |
|
@@ -0,0 +1,214 @@ |
|
+#!/bin/sh |
|
+# |
|
+# Copyright (C) 2016 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. |
|
+ |
|
+SYSTEMTESTTOP=.. |
|
+. $SYSTEMTESTTOP/conf.sh |
|
+ |
|
+RANDFILE=random.data |
|
+KEYGEN="$KEYGEN -qr $RANDFILE" |
|
+ |
|
+$SHELL clean.sh |
|
+../../../tools/genrandom 400 $RANDFILE |
|
+ |
|
+# Test 1: KSK goes inactive before successor is active |
|
+dir=01-ksk-inactive |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 |
|
+ksk2=`$KEYGEN -K $dir -S $ksk1` |
|
+$SETTIME -K $dir -I +7mo $ksk1 > /dev/null 2>&1 |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+ |
|
+# Test 2: ZSK goes inactive before successor is active |
|
+dir=02-zsk-inactive |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 |
|
+zsk2=`$KEYGEN -K $dir -S $zsk1` |
|
+$SETTIME -K $dir -I +7mo $zsk1 > /dev/null 2>&1 |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+ |
|
+# Test 3: KSK is unpublished before its successor is published |
|
+dir=03-ksk-unpublished |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 |
|
+ksk2=`$KEYGEN -K $dir -S $ksk1` |
|
+$SETTIME -K $dir -D +6mo $ksk1 > /dev/null 2>&1 |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+ |
|
+# Test 4: ZSK is unpublished before its successor is published |
|
+dir=04-zsk-unpublished |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 |
|
+zsk2=`$KEYGEN -K $dir -S $zsk1` |
|
+$SETTIME -K $dir -D +6mo $zsk1 > /dev/null 2>&1 |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+ |
|
+# Test 5: KSK deleted and successor published before KSK is deactivated |
|
+# and successor activated. |
|
+dir=05-ksk-unpub-active |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+$SETTIME -K $dir -I +9mo -D +8mo $ksk1 > /dev/null 2>&1 |
|
+ksk2=`$KEYGEN -K $dir -S $ksk1` |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+ |
|
+# Test 6: ZSK deleted and successor published before ZSK is deactivated |
|
+# and successor activated. |
|
+dir=06-zsk-unpub-active |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+$SETTIME -K $dir -I +9mo -D +8mo $zsk1 > /dev/null 2>&1 |
|
+zsk2=`$KEYGEN -K $dir -S $zsk1` |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+ |
|
+# Test 7: KSK rolled with insufficient delay after prepublication. |
|
+dir=07-ksk-ttl |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 |
|
+ksk2=`$KEYGEN -K $dir -S $ksk1` |
|
+$SETTIME -K $dir -P +269d $ksk2 > /dev/null 2>&1 |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+ |
|
+# Test 8: ZSK rolled with insufficient delay after prepublication. |
|
+dir=08-zsk-ttl |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 |
|
+zsk2=`$KEYGEN -K $dir -S $zsk1` |
|
+# allow only 1 day between publication and activation |
|
+$SETTIME -K $dir -P +269d $zsk2 > /dev/null 2>&1 |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+ |
|
+# Test 9: No special preparation needed |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+ |
|
+# Test 10: Valid key set, but rollover period has changed |
|
+dir=10-change-roll |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+ksk1=`$KEYGEN -K $dir -3fk example.com` |
|
+zsk1=`$KEYGEN -K $dir -3 example.com` |
|
+$SETTIME -K $dir -I +3mo -D +4mo $zsk1 > /dev/null 2>&1 |
|
+zsk2=`$KEYGEN -K $dir -S $zsk1` |
|
+ |
|
+# Test 11: Many keys all simultaneously scheduled to be active in the future |
|
+dir=11-many-simul |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -q3fk -P now+1mo -A now+1mo example.com` |
|
+z1=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` |
|
+z2=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` |
|
+z3=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` |
|
+z4=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` |
|
+ |
|
+# Test 12: Many keys all simultaneously scheduled to be active in the past |
|
+dir=12-many-active |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -q3fk example.com` |
|
+z1=`$KEYGEN -K $dir -q3 example.com` |
|
+z2=`$KEYGEN -K $dir -q3 example.com` |
|
+z3=`$KEYGEN -K $dir -q3 example.com` |
|
+z4=`$KEYGEN -K $dir -q3 example.com` |
|
+ |
|
+# Test 13: Multiple simultaneous keys with no configured roll period |
|
+dir=13-noroll |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -q3fk example.com` |
|
+k2=`$KEYGEN -K $dir -q3fk example.com` |
|
+k3=`$KEYGEN -K $dir -q3fk example.com` |
|
+z1=`$KEYGEN -K $dir -q3 example.com` |
|
+ |
|
+# Test 14: Keys exist but have the wrong algorithm |
|
+dir=14-wrongalg |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -qfk example.com` |
|
+z1=`$KEYGEN -K $dir -q example.com` |
|
+$SETTIME -K $dir -I now+6mo -D now+8mo $z1 > /dev/null |
|
+z2=`$KEYGEN -K $dir -q -S ${z1}.key` |
|
+$SETTIME -K $dir -I now+1y -D now+14mo $z2 > /dev/null |
|
+z3=`$KEYGEN -K $dir -q -S ${z2}.key` |
|
+$SETTIME -K $dir -I now+18mo -D now+20mo $z3 > /dev/null |
|
+z4=`$KEYGEN -K $dir -q -S ${z3}.key` |
|
+ |
|
+# Test 15: No zones specified; just search the directory for keys |
|
+dir=15-unspec |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -q3fk example.com` |
|
+z1=`$KEYGEN -K $dir -q3 example.com` |
|
+$SETTIME -K $dir -I now+6mo -D now+8mo $z1 > /dev/null |
|
+z2=`$KEYGEN -K $dir -q -S ${z1}.key` |
|
+$SETTIME -K $dir -I now+1y -D now+14mo $z2 > /dev/null |
|
+z3=`$KEYGEN -K $dir -q -S ${z2}.key` |
|
+$SETTIME -K $dir -I now+18mo -D now+20mo $z3 > /dev/null |
|
+z4=`$KEYGEN -K $dir -q -S ${z3}.key` |
|
+ |
|
+# Test 16: No zones specified; search the directory for keys; |
|
+# keys have the wrong algorithm for their policies |
|
+dir=16-wrongalg-unspec |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -qfk example.com` |
|
+z1=`$KEYGEN -K $dir -q example.com` |
|
+$SETTIME -K $dir -I now+6mo -D now+8mo $z1 > /dev/null |
|
+z2=`$KEYGEN -K $dir -q -S ${z1}.key` |
|
+$SETTIME -K $dir -I now+1y -D now+14mo $z2 > /dev/null |
|
+z3=`$KEYGEN -K $dir -q -S ${z2}.key` |
|
+$SETTIME -K $dir -I now+18mo -D now+20mo $z3 > /dev/null |
|
+z4=`$KEYGEN -K $dir -q -S ${z3}.key` |
|
+ |
|
+# Test 17: Keys are simultaneously active but we run with no force |
|
+# flag (this should fail) |
|
+dir=17-noforce |
|
+echo I:set up $dir |
|
+rm -f $dir/K*.key |
|
+rm -f $dir/K*.private |
|
+k1=`$KEYGEN -K $dir -q3fk example.com` |
|
+z1=`$KEYGEN -K $dir -q3 example.com` |
|
+z2=`$KEYGEN -K $dir -q3 example.com` |
|
+z3=`$KEYGEN -K $dir -q3 example.com` |
|
+z4=`$KEYGEN -K $dir -q3 example.com` |
|
diff --git a/bin/tests/system/keymgr/testpolicy.py b/bin/tests/system/keymgr/testpolicy.py |
|
new file mode 100644 |
|
index 0000000..2dec7ff |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/testpolicy.py |
|
@@ -0,0 +1,29 @@ |
|
+#!/bin/python |
|
+import sys |
|
+sys.path.insert(0, '../../../python') |
|
+from isc import * |
|
+ |
|
+pp = policy.dnssec_policy() |
|
+# print the unmodified default and a generated zone policy |
|
+print pp.named_policy['default'] |
|
+print pp.named_policy['global'] |
|
+print pp.policy('example.com') |
|
+ |
|
+if len(sys.argv) > 0: |
|
+ for policy_file in sys.argv[1:]: |
|
+ pp.load(policy_file) |
|
+ |
|
+ # now print the modified default and generated zone policies |
|
+ print pp.named_policy['default'] |
|
+ print pp.policy('example.com') |
|
+ print pp.policy('example.org') |
|
+ print pp.policy('example.net') |
|
+ |
|
+ # print algorithm policies |
|
+ print pp.alg_policy['RSASHA1'] |
|
+ print pp.alg_policy['DSA'] |
|
+ |
|
+ # print another named policy |
|
+ print pp.named_policy['extra'] |
|
+else: |
|
+ print("ERROR: Please provide an input file") |
|
diff --git a/bin/tests/system/keymgr/tests.sh b/bin/tests/system/keymgr/tests.sh |
|
new file mode 100644 |
|
index 0000000..f598f0a |
|
--- /dev/null |
|
+++ b/bin/tests/system/keymgr/tests.sh |
|
@@ -0,0 +1,106 @@ |
|
+#!/bin/sh |
|
+# |
|
+# Copyright (C) 2016 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. |
|
+ |
|
+SYSTEMTESTTOP=.. |
|
+. $SYSTEMTESTTOP/conf.sh |
|
+ |
|
+status=0 |
|
+n=1 |
|
+ |
|
+matchall () { |
|
+ file=$1 |
|
+ echo "$2" | while read matchline; do |
|
+ grep "$matchline" $file > /dev/null 2>&1 || { |
|
+ echo "FAIL" |
|
+ return |
|
+ } |
|
+ done |
|
+} |
|
+ |
|
+echo "I:checking for DNSSEC key coverage issues" |
|
+ret=0 |
|
+for dir in [0-9][0-9]-*; do |
|
+ ret=0 |
|
+ echo "I:$dir ($n)" |
|
+ kargs= cargs= kmatch= cmatch= kret= cret=0 warn= error= ok= |
|
+ . $dir/expect |
|
+ |
|
+ # run keymgr to update keys |
|
+ $KEYMGR -K $dir -s $SETTIME $kargs > keymgr.$n 2>&1 |
|
+ # check that return code matches expectations |
|
+ found=$? |
|
+ if [ $found -ne $kret ]; then |
|
+ echo "keymgr retcode was $found expected $kret" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ found=`matchall keymgr.$n "$kmatch"` |
|
+ if [ "$found" = "FAIL" ]; then |
|
+ echo "no match on '$kmatch'" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ # now check coverage |
|
+ $COVERAGE -K $dir $cargs > coverage.$n 2>&1 |
|
+ # check that return code matches expectations |
|
+ found=$? |
|
+ if [ $found -ne $cret ]; then |
|
+ echo "coverage retcode was $found expected $cret" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ # check for correct number of errors |
|
+ found=`grep ERROR coverage.$n | wc -l` |
|
+ if [ $found -ne $error ]; then |
|
+ echo "error count was $found expected $error" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ # check for correct number of warnings |
|
+ found=`grep WARNING coverage.$n | wc -l` |
|
+ if [ $found -ne $warn ]; then |
|
+ echo "warning count was $found expected $warn" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ # check for correct number of OKs |
|
+ found=`grep "No errors found" coverage.$n | wc -l` |
|
+ if [ $found -ne $ok ]; then |
|
+ echo "good count was $found expected $ok" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ found=`matchall coverage.$n "$cmatch"` |
|
+ if [ "$found" = "FAIL" ]; then |
|
+ echo "no match on '$cmatch'" |
|
+ ret=1 |
|
+ fi |
|
+ |
|
+ n=`expr $n + 1` |
|
+ if [ $ret != 0 ]; then echo "I:failed"; fi |
|
+ status=`expr $status + $ret` |
|
+done |
|
+ |
|
+echo "I:checking policy.conf parser ($n)" |
|
+ret=0 |
|
+${PYTHON} testpolicy.py policy.sample > policy.out |
|
+cmp -s policy.good policy.out || ret=1 |
|
+if [ $ret != 0 ]; then echo "I:failed"; fi |
|
+status=`expr $status + $ret` |
|
+n=`expr $n + 1` |
|
+ |
|
+echo "I:exit status: $status" |
|
+exit $status |
|
diff --git a/configure b/configure |
|
index 31c518a..a299aac 100755 |
|
--- a/configure |
|
+++ b/configure |
|
@@ -1372,7 +1372,9 @@ ISC_PLATFORM_NORETURN_POST |
|
ISC_PLATFORM_NORETURN_PRE |
|
ISC_PLATFORM_HAVELONGLONG |
|
ISC_SOCKADDR_LEN_T |
|
+expanded_sysconfdir |
|
PYTHON_TOOLS |
|
+KEYMGR |
|
COVERAGE |
|
CHECKDS |
|
PYTHON |
|
@@ -12270,15 +12272,18 @@ esac |
|
PYTHON_TOOLS='' |
|
CHECKDS='' |
|
COVERAGE='' |
|
+KEYMGR='' |
|
if test "X$PYTHON" != "X"; then |
|
PYTHON_TOOLS=python |
|
CHECKDS=checkds |
|
COVERAGE=coverage |
|
+ KEYMGR=keymgr |
|
fi |
|
|
|
|
|
|
|
|
|
+ |
|
# |
|
# Special processing of paths depending on whether --prefix, |
|
# --sysconfdir or --localstatedir arguments were given. What's |
|
@@ -12313,6 +12318,8 @@ case "$prefix" in |
|
esac |
|
;; |
|
esac |
|
+expanded_sysconfdir=`eval echo $sysconfdir` |
|
+ |
|
|
|
# |
|
# Make sure INSTALL uses an absolute path, else it will be wrong in all |
|
@@ -22273,8 +22280,12 @@ do |
|
"bin/nsupdate/Makefile") CONFIG_FILES="$CONFIG_FILES bin/nsupdate/Makefile" ;; |
|
"bin/pkcs11/Makefile") CONFIG_FILES="$CONFIG_FILES bin/pkcs11/Makefile" ;; |
|
"bin/python/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/Makefile" ;; |
|
+ "bin/python/isc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/isc/Makefile" ;; |
|
+ "bin/python/isc/utils.py") CONFIG_FILES="$CONFIG_FILES bin/python/isc/utils.py" ;; |
|
+ "bin/python/isc/tests/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/isc/tests/Makefile" ;; |
|
"bin/python/dnssec-checkds.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-checkds.py" ;; |
|
"bin/python/dnssec-coverage.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-coverage.py" ;; |
|
+ "bin/python/dnssec-keymgr.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-keymgr.py" ;; |
|
"bin/rndc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/rndc/Makefile" ;; |
|
"bin/sdb_tools/Makefile") CONFIG_FILES="$CONFIG_FILES bin/sdb_tools/Makefile" ;; |
|
"bin/tests/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/Makefile" ;; |
|
diff --git a/configure.in b/configure.in |
|
index 529989d..fb2e53e 100644 |
|
--- a/configure.in |
|
+++ b/configure.in |
|
@@ -197,13 +197,16 @@ esac |
|
PYTHON_TOOLS='' |
|
CHECKDS='' |
|
COVERAGE='' |
|
+KEYMGR='' |
|
if test "X$PYTHON" != "X"; then |
|
PYTHON_TOOLS=python |
|
CHECKDS=checkds |
|
COVERAGE=coverage |
|
+ KEYMGR=keymgr |
|
fi |
|
AC_SUBST(CHECKDS) |
|
AC_SUBST(COVERAGE) |
|
+AC_SUBST(KEYMGR) |
|
AC_SUBST(PYTHON_TOOLS) |
|
|
|
# |
|
@@ -240,6 +243,8 @@ case "$prefix" in |
|
esac |
|
;; |
|
esac |
|
+expanded_sysconfdir=`eval echo $sysconfdir` |
|
+AC_SUBST(expanded_sysconfdir) |
|
|
|
# |
|
# Make sure INSTALL uses an absolute path, else it will be wrong in all |
|
@@ -4042,8 +4047,12 @@ AC_CONFIG_FILES([ |
|
bin/nsupdate/Makefile |
|
bin/pkcs11/Makefile |
|
bin/python/Makefile |
|
+ bin/python/isc/Makefile |
|
+ bin/python/isc/utils.py |
|
+ bin/python/isc/tests/Makefile |
|
bin/python/dnssec-checkds.py |
|
bin/python/dnssec-coverage.py |
|
+ bin/python/dnssec-keymgr.py |
|
bin/rndc/Makefile |
|
bin/tests/Makefile |
|
bin/tests/atomic/Makefile |
|
diff --git a/contrib/kasp/README b/contrib/kasp/README |
|
new file mode 100644 |
|
index 0000000..fb897f1 |
|
--- /dev/null |
|
+++ b/contrib/kasp/README |
|
@@ -0,0 +1,11 @@ |
|
+This directory is for tools and scripts related to the OpenDNSSEC KASP |
|
+("key and signature policy") format. Currently it only contains |
|
+"kasp2policy.py", a python script for converting KASP key policy |
|
+to the "dnssec.policy" format that is used by dnssec-keymgr. |
|
+ |
|
+This depends on PLY (python lex/yacc) and on the "isc.dnskey" module in |
|
+bin/python/isc. |
|
+ |
|
+Basic test: |
|
+$ python kasp2policy.py kasp.xml > policy.out |
|
+$ diff policy.out policy.good |
|
diff --git a/contrib/kasp/kasp.xml b/contrib/kasp/kasp.xml |
|
new file mode 100644 |
|
index 0000000..d94b084 |
|
--- /dev/null |
|
+++ b/contrib/kasp/kasp.xml |
|
@@ -0,0 +1,134 @@ |
|
+<?xml version="1.0" encoding="UTF-8"?> |
|
+ |
|
+<!-- Sample KASP file to use for testing kasp2policy.py. --> |
|
+<KASP> |
|
+ <Policy name="Policy1"> |
|
+ <Description>A default policy that will |
|
+ amaze you and your friends</Description> |
|
+ <Signatures> |
|
+ <Resign>PT5M</Resign> |
|
+ <Refresh>PT5M</Refresh> |
|
+ <Validity> |
|
+ <Default>PT15M</Default> |
|
+ <Denial>PT15M</Denial> |
|
+ </Validity> |
|
+ <Jitter>PT2M</Jitter> |
|
+ <InceptionOffset>PT1M</InceptionOffset> |
|
+ </Signatures> |
|
+ |
|
+ <Denial> |
|
+ <NSEC> |
|
+ </NSEC> |
|
+ </Denial> |
|
+ |
|
+ <Keys> |
|
+ <!-- Parameters for both KSK and ZSK --> |
|
+ <TTL>PT1M</TTL> |
|
+ <RetireSafety>PT0S</RetireSafety> |
|
+ <PublishSafety>PT0S</PublishSafety> |
|
+ |
|
+ <!-- Parameters for KSK only --> |
|
+ <KSK> |
|
+ <Algorithm length="2048">5</Algorithm> |
|
+ <Lifetime>PT40M</Lifetime> |
|
+ <Repository>softHSM</Repository> |
|
+ <Standby>1</Standby> |
|
+ </KSK> |
|
+ |
|
+ <!-- Parameters for ZSK only --> |
|
+ <ZSK> |
|
+ <Algorithm length="2048">5</Algorithm> |
|
+ <Lifetime>PT25M</Lifetime> |
|
+ <Repository>softHSM</Repository> |
|
+ <Standby>1</Standby> |
|
+ </ZSK> |
|
+ </Keys> |
|
+ |
|
+ <Zone> |
|
+ <PropagationDelay>PT0S</PropagationDelay> |
|
+ <SOA> |
|
+ <TTL>PT0S</TTL> |
|
+ <Minimum>PT0S</Minimum> |
|
+ <Serial>unixtime</Serial> |
|
+ </SOA> |
|
+ </Zone> |
|
+ |
|
+ <Parent> |
|
+ <PropagationDelay>PT8M</PropagationDelay> |
|
+ <DS> |
|
+ <TTL>PT0S</TTL> |
|
+ </DS> |
|
+ <SOA> |
|
+ <TTL>PT0S</TTL> |
|
+ <Minimum>PT0S</Minimum> |
|
+ </SOA> |
|
+ </Parent> |
|
+ </Policy> |
|
+ <Policy name="Policy2"> |
|
+ <Description>A default policy that will amaze you and your friends</Description> |
|
+ <Signatures> |
|
+ <Resign>PT7M</Resign> |
|
+ <Refresh>PT7M</Refresh> |
|
+ <Validity> |
|
+ <Default>PT15M</Default> |
|
+ <Denial>PT16M</Denial> |
|
+ </Validity> |
|
+ <Jitter>PT2M</Jitter> |
|
+ <InceptionOffset>PT1M</InceptionOffset> |
|
+ </Signatures> |
|
+ |
|
+ <Denial> |
|
+ <NSEC3> |
|
+ <Resalt>P120D</Resalt> |
|
+ <Hash> |
|
+ <Algorithm>1</Algorithm> |
|
+ <Iterations>5</Iterations> |
|
+ <Salt length="8"/> |
|
+ </Hash> |
|
+ </NSEC3> |
|
+ </Denial> |
|
+ |
|
+ <Keys> |
|
+ <!-- Parameters for both KSK and ZSK --> |
|
+ <TTL>PT15M</TTL> |
|
+ <RetireSafety>PT0S</RetireSafety> |
|
+ <PublishSafety>PT0S</PublishSafety> |
|
+ |
|
+ <!-- Parameters for KSK only --> |
|
+ <KSK> |
|
+ <Algorithm length="2048">7</Algorithm> |
|
+ <Lifetime>PT45M</Lifetime> |
|
+ <Repository>softHSM</Repository> |
|
+ <Standby>1</Standby> |
|
+ </KSK> |
|
+ |
|
+ <!-- Parameters for ZSK only --> |
|
+ <ZSK> |
|
+ <Algorithm length="2048">7</Algorithm> |
|
+ <Lifetime>PT25M</Lifetime> |
|
+ <Repository>softHSM</Repository> |
|
+ <Standby>1</Standby> |
|
+ </ZSK> |
|
+ </Keys> |
|
+ |
|
+ <Zone> |
|
+ <PropagationDelay>PT0S</PropagationDelay> |
|
+ <SOA> |
|
+ <TTL>PT0S</TTL> |
|
+ <Minimum>PT0S</Minimum> |
|
+ <Serial>unixtime</Serial> |
|
+ </SOA> |
|
+ </Zone> |
|
+ |
|
+ <Parent> |
|
+ <PropagationDelay>PT12M</PropagationDelay> |
|
+ <DS> |
|
+ <TTL>PT0S</TTL> |
|
+ </DS> |
|
+ <SOA> |
|
+ <TTL>PT0S</TTL> |
|
+ <Minimum>PT0S</Minimum> |
|
+ </SOA> |
|
+ </Parent> |
|
+ </Policy> |
|
+</KASP> |
|
diff --git a/contrib/kasp/kasp2policy.py b/contrib/kasp/kasp2policy.py |
|
new file mode 100644 |
|
index 0000000..b78a968 |
|
--- /dev/null |
|
+++ b/contrib/kasp/kasp2policy.py |
|
@@ -0,0 +1,209 @@ |
|
+#!/usr/bin/python |
|
+############################################################################ |
|
+# 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. |
|
+############################################################################ |
|
+# kasp2policy.py |
|
+# This translates the Keys section of a KASP XML file into a dnssec.policy |
|
+# file that can be used by dnssec-keymgr. |
|
+############################################################################ |
|
+ |
|
+from xml.etree import cElementTree as ET |
|
+from collections import defaultdict |
|
+from isc import dnskey |
|
+import ply.yacc as yacc |
|
+import ply.lex as lex |
|
+import re |
|
+ |
|
+############################################################################ |
|
+# Translate KASP duration values into seconds |
|
+############################################################################ |
|
+class kasptime: |
|
+ class ktlex: |
|
+ tokens = ( 'P', 'T', 'Y', 'M', 'D', 'H', 'S', 'NUM' ) |
|
+ |
|
+ t_P = r'(?i)P' |
|
+ t_T = r'(?i)T' |
|
+ t_Y = r'(?i)Y' |
|
+ t_M = r'(?i)M' |
|
+ t_D = r'(?i)D' |
|
+ t_H = r'(?i)H' |
|
+ t_S = r'(?i)S' |
|
+ |
|
+ def t_NUM(self, t): |
|
+ r'\d+' |
|
+ t.value = int(t.value) |
|
+ return t |
|
+ |
|
+ def t_error(self, t): |
|
+ print("Illegal character '%s'" % t.value[0]) |
|
+ t.lexer.skip(1) |
|
+ |
|
+ def __init__(self): |
|
+ self.lexer = lex.lex(object=self) |
|
+ |
|
+ def __init__(self): |
|
+ self.lexer = self.ktlex() |
|
+ self.tokens = self.lexer.tokens |
|
+ self.parser = yacc.yacc(debug=False, write_tables=False, module=self) |
|
+ |
|
+ def parse(self, text): |
|
+ self.lexer.lexer.lineno = 0 |
|
+ return self.parser.parse(text) |
|
+ |
|
+ def p_ktime_4(self, p): |
|
+ "ktime : P periods T times" |
|
+ p[0] = p[2] + p[4] |
|
+ |
|
+ def p_ktime_3(self, p): |
|
+ "ktime : P T times" |
|
+ p[0] = p[3] |
|
+ |
|
+ def p_ktime_2(self, p): |
|
+ "ktime : P periods" |
|
+ p[0] = p[2] |
|
+ |
|
+ def p_periods_1(self, p): |
|
+ "periods : period" |
|
+ p[0] = p[1] |
|
+ |
|
+ def p_periods_2(self, p): |
|
+ "periods : periods period" |
|
+ p[0] = p[1] + p[2] |
|
+ |
|
+ def p_times_1(self, p): |
|
+ "times : time" |
|
+ p[0] = p[1] |
|
+ |
|
+ def p_times_2(self, p): |
|
+ "times : times time" |
|
+ p[0] = p[1] + p[2] |
|
+ |
|
+ def p_period(self, p): |
|
+ '''period : NUM Y |
|
+ | NUM M |
|
+ | NUM D''' |
|
+ if p[2].lower() == 'y': |
|
+ p[0] = int(p[1]) * 31536000 |
|
+ elif p[2].lower() == 'm': |
|
+ p[0] = int(p[1]) * 2592000 |
|
+ elif p[2].lower() == 'd': |
|
+ p[0] += int(p[1]) * 86400 |
|
+ |
|
+ def p_time(self, p): |
|
+ '''time : NUM H |
|
+ | NUM M |
|
+ | NUM S''' |
|
+ if p[2].lower() == 'h': |
|
+ p[0] = int(p[1]) * 3600 |
|
+ elif p[2].lower() == 'm': |
|
+ p[0] = int(p[1]) * 60 |
|
+ elif p[2].lower() == 's': |
|
+ p[0] = int(p[1]) |
|
+ |
|
+ def p_error(self, p): |
|
+ print("Syntax error") |
|
+ |
|
+############################################################################ |
|
+# Load the contents of a KASP XML file as a python dictionary |
|
+############################################################################ |
|
+class kasp(): |
|
+ @staticmethod |
|
+ def _todict(t): |
|
+ d = {t.tag: {} if t.attrib else None} |
|
+ children = list(t) |
|
+ if children: |
|
+ dd = defaultdict(list) |
|
+ for dc in map(kasp._todict, children): |
|
+ for k, v in dc.iteritems(): |
|
+ dd[k].append(v) |
|
+ d = {t.tag: |
|
+ {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} |
|
+ if t.attrib: |
|
+ d[t.tag].update(('@' + k, v) for k, v in t.attrib.iteritems()) |
|
+ if t.text: |
|
+ text = t.text.strip() |
|
+ if children or t.attrib: |
|
+ if text: |
|
+ d[t.tag]['#text'] = text |
|
+ else: |
|
+ d[t.tag] = text |
|
+ return d |
|
+ |
|
+ def __init__(self, filename): |
|
+ self._dict = kasp._todict(ET.parse(filename).getroot()) |
|
+ |
|
+ def __getitem__(self, key): |
|
+ return self._dict[key] |
|
+ |
|
+ def __len__(self): |
|
+ return len(self._dict) |
|
+ |
|
+ def __iter__(self): |
|
+ return self._dict.__iter__() |
|
+ |
|
+ def __repr__(self): |
|
+ return repr(self._dict) |
|
+ |
|
+############################################################################ |
|
+# Load the contents of a KASP XML file as a python dictionary |
|
+############################################################################ |
|
+if __name__ == "__main__": |
|
+ from pprint import * |
|
+ import sys |
|
+ |
|
+ if len(sys.argv) < 2: |
|
+ print("Usage: kasp2policy <filename>") |
|
+ exit(1) |
|
+ |
|
+ try: |
|
+ kinfo = kasp(sys.argv[1]) |
|
+ except: |
|
+ print("%s: unable to load KASP file '%s'" % (sys.argv[0], sys.argv[1])) |
|
+ exit(1) |
|
+ |
|
+ kt = kasptime() |
|
+ first = True |
|
+ |
|
+ for p in kinfo['KASP']['Policy']: |
|
+ if not p['@name'] or not p['Keys']: continue |
|
+ if not first: |
|
+ print("") |
|
+ first = False |
|
+ if p['Description']: |
|
+ d = p['Description'].strip() |
|
+ print("# %s" % re.sub(r"\n\s*", "\n# ", d)) |
|
+ print("policy %s {" % p['@name']) |
|
+ ksk = p['Keys']['KSK'] |
|
+ zsk = p['Keys']['ZSK'] |
|
+ kalg = ksk['Algorithm'] |
|
+ zalg = zsk['Algorithm'] |
|
+ algnum = kalg['#text'] or zalg['#text'] |
|
+ if algnum: |
|
+ print("\talgorithm %s;" % dnskey.algstr(int(algnum))) |
|
+ if p['Keys']['TTL']: |
|
+ print("\tkeyttl %d;" % kt.parse(p['Keys']['TTL'])) |
|
+ if kalg['@length']: |
|
+ print("\tkey-size ksk %d;" % int(kalg['@length'])) |
|
+ if zalg['@length']: |
|
+ print("\tkey-size zsk %d;" % int(zalg['@length'])) |
|
+ if ksk['Lifetime']: |
|
+ print("\troll-period ksk %d;" % kt.parse(ksk['Lifetime'])) |
|
+ if zsk['Lifetime']: |
|
+ print("\troll-period zsk %d;" % kt.parse(zsk['Lifetime'])) |
|
+ if ksk['Standby']: |
|
+ print("\tstandby ksk %d;" % int(ksk['Standby'])) |
|
+ if zsk['Standby']: |
|
+ print("\tstandby zsk %d;" % int(zsk['Standby'])) |
|
+ print("};") |
|
diff --git a/contrib/kasp/policy.good b/contrib/kasp/policy.good |
|
new file mode 100644 |
|
index 0000000..18c6360 |
|
--- /dev/null |
|
+++ b/contrib/kasp/policy.good |
|
@@ -0,0 +1,24 @@ |
|
+# A default policy that will |
|
+# amaze you and your friends |
|
+policy Policy1 { |
|
+ algorithm RSASHA1; |
|
+ keyttl 60; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 2048; |
|
+ roll-period ksk 2400; |
|
+ roll-period zsk 1500; |
|
+ standby ksk 1; |
|
+ standby zsk 1; |
|
+}; |
|
+ |
|
+# A default policy that will amaze you and your friends |
|
+policy Policy2 { |
|
+ algorithm NSEC3RSASHA1; |
|
+ keyttl 900; |
|
+ key-size ksk 2048; |
|
+ key-size zsk 2048; |
|
+ roll-period ksk 2700; |
|
+ roll-period zsk 1500; |
|
+ standby ksk 1; |
|
+ standby zsk 1; |
|
+}; |
|
diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml |
|
new file mode 100644 |
|
index 0000000..07776b0 |
|
--- /dev/null |
|
+++ b/doc/arm/notes.xml |
|
@@ -0,0 +1,714 @@ |
|
+<!DOCTYPE book [ |
|
+<!ENTITY Scaron "Š"> |
|
+<!ENTITY ccaron "č"> |
|
+<!ENTITY aacute "á"> |
|
+<!ENTITY mdash "—"> |
|
+<!ENTITY ouml "ö">]> |
|
+<!-- |
|
+ - Copyright (C) 2014-2016 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. |
|
+--> |
|
+ |
|
+<section xmlns="http://docbook.org/ns/docbook" version="5.0"><info/> |
|
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="noteversion.xml"/> |
|
+ <section xml:id="relnotes_intro"><info><title>Introduction</title></info> |
|
+ <para> |
|
+ BIND 9.11.0 is a new feature release of BIND, still under development. |
|
+ This document summarizes new features and functional changes that |
|
+ have been introduced on this branch. With each development |
|
+ release leading up to the final BIND 9.11.0 release, this document |
|
+ will be updated with additional features added and bugs fixed. |
|
+ </para> |
|
+ </section> |
|
+ |
|
+ <section xml:id="relnotes_download"><info><title>Download</title></info> |
|
+ <para> |
|
+ The latest versions of BIND 9 software can always be found at |
|
+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://www.isc.org/downloads/">http://www.isc.org/downloads/</link>. |
|
+ There you will find additional information about each release, |
|
+ source code, and pre-compiled versions for Microsoft Windows |
|
+ operating systems. |
|
+ </para> |
|
+ </section> |
|
+ |
|
+ <section xml:id="relnotes_security"><info><title>Security Fixes</title></info> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <para> |
|
+ None. |
|
+ </para> |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ </section> |
|
+ |
|
+ <section xml:id="relnotes_features"><info><title>New Features</title></info> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <para> |
|
+ Added support for DynDB, a new interface for loading zone data |
|
+ from an external database, developed by Red Hat for the FreeIPA |
|
+ project. (Thanks in particular to Adam Tkac and Petr |
|
+ Spacek of Red Hat for the contribution.) |
|
+ </para> |
|
+ <para> |
|
+ Unlike the existing DLZ and SDB interfaces, which provide a |
|
+ limited subset of database functionality within BIND — |
|
+ translating DNS queries into real-time database lookups with |
|
+ relatively poor performance and with no ability to handle |
|
+ DNSSEC-signed data — DynDB is able to fully implement |
|
+ and extend the database API used natively by BIND. |
|
+ </para> |
|
+ <para> |
|
+ A DynDB module could pre-load data from an external data |
|
+ source, then serve it with the same performance and |
|
+ functionality as conventional BIND zones, and with the |
|
+ ability to take advantage of database features not |
|
+ available in BIND, such as multi-master replication. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ New quotas have been added to limit the queries that are |
|
+ sent by recursive resolvers to authoritative servers |
|
+ experiencing denial-of-service attacks. When configured, |
|
+ these options can both reduce the harm done to authoritative |
|
+ servers and also avoid the resource exhaustion that can be |
|
+ experienced by recursives when they are being used as a |
|
+ vehicle for such an attack. |
|
+ </para> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <para> |
|
+ <option>fetches-per-server</option> limits the number of |
|
+ simultaneous queries that can be sent to any single |
|
+ authoritative server. The configured value is a starting |
|
+ point; it is automatically adjusted downward if the server is |
|
+ partially or completely non-responsive. The algorithm used to |
|
+ adjust the quota can be configured via the |
|
+ <option>fetch-quota-params</option> option. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <option>fetches-per-zone</option> limits the number of |
|
+ simultaneous queries that can be sent for names within a |
|
+ single domain. (Note: Unlike "fetches-per-server", this |
|
+ value is not self-tuning.) |
|
+ </para> |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ <para> |
|
+ Statistics counters have also been added to track the number |
|
+ of queries affected by these quotas. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Added support for <command>dnstap</command>, a fast, |
|
+ flexible method for capturing and logging DNS traffic, |
|
+ developed by Robert Edmonds at Farsight Security, Inc., |
|
+ whose assistance is gratefully acknowledged. |
|
+ </para> |
|
+ <para> |
|
+ To enable <command>dnstap</command> at compile time, |
|
+ the <command>fstrm</command> and <command>protobuf-c</command> |
|
+ libraries must be available, and BIND must be configured with |
|
+ <option>--enable-dnstap</option>. |
|
+ </para> |
|
+ <para> |
|
+ A new utility <command>dnstap-read</command> has been added |
|
+ to allow <command>dnstap</command> data to be presented in |
|
+ a human-readable format. |
|
+ </para> |
|
+ <para> |
|
+ For more information on <command>dnstap</command>, see |
|
+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://dnstap.info">http://dnstap.info</link>. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ New statistics counters have been added to track traffic |
|
+ sizes, as specified in RSSAC002. Query and response |
|
+ message sizes are broken up into ranges of histogram buckets: |
|
+ TCP and UDP queries of size 0-15, 16-31, ..., 272-288, and 288+, |
|
+ and TCP and UDP responses of size 0-15, 16-31, ..., 4080-4095, |
|
+ and 4096+. These values can be accessed via the XML and JSON |
|
+ statistics channels at, for example, |
|
+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8888/xml/v3/traffic">http://localhost:8888/xml/v3/traffic</link> |
|
+ or |
|
+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8888/json/v1/traffic">http://localhost:8888/json/v1/traffic</link>. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ A new DNSSEC key management utility, |
|
+ <command>dnssec-keymgr</command>, has been added. This tool |
|
+ is meant to run unattended (e.g., under <command>cron</command>). |
|
+ It reads a policy definition file |
|
+ (default: <filename>/etc/dnssec.policy</command>) |
|
+ and creates or updates DNSSEC keys as necessary to ensure that a |
|
+ zone's keys match the defined policy for that zone. New keys are |
|
+ created whenever necessary to ensure rollovers occur correctly. |
|
+ Existing keys' timing metadata is adjusted as needed to set the |
|
+ correct rollover period, prepublication interval, etc. If |
|
+ the configured policy changes, keys are corrected automatically. |
|
+ See the <command>dnssec-keymgr</command> man page for full details. |
|
+ </para> |
|
+ <para> |
|
+ Note: <command>dnssec-keymgr</command> depends on Python and on |
|
+ the Python lex/yacc module, PLY. The other Python-based tools, |
|
+ <command>dnssec-coverage</command> and |
|
+ <command>dnssec-checkds</command>, have been |
|
+ refactored and updated as part of this work. |
|
+ </para> |
|
+ <para> |
|
+ (Many thanks to Sebastián |
|
+ Castro for his assistance in developing this tool at the IETF |
|
+ 95 Hackathon in Buenos Aires, April 2016.) |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The serial number of a dynamically updatable zone can |
|
+ now be set using |
|
+ <command>rndc signing -serial <replaceable>number</replaceable> <replaceable>zonename</replaceable></command>. |
|
+ This is particularly useful with <option>inline-signing</option> |
|
+ zones that have been reset. Setting the serial number to a value |
|
+ larger than that on the slaves will trigger an AXFR-style |
|
+ transfer. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ When answering recursive queries, SERVFAIL responses can now be |
|
+ cached by the server for a limited time; subsequent queries for |
|
+ the same query name and type will return another SERVFAIL until |
|
+ the cache times out. This reduces the frequency of retries |
|
+ when a query is persistently failing, which can be a burden |
|
+ on recursive serviers. The SERVFAIL cache timeout is controlled |
|
+ by <option>servfail-ttl</option>, which defaults to 1 second |
|
+ and has an upper limit of 30. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The new <command>rndc nta</command> command can now be used to |
|
+ set a "negative trust anchor" (NTA), disabling DNSSEC validation for |
|
+ a specific domain; this can be used when responses from a domain |
|
+ are known to be failing validation due to administrative error |
|
+ rather than because of a spoofing attack. NTAs are strictly |
|
+ temporary; by default they expire after one hour, but can be |
|
+ configured to last up to one week. The default NTA lifetime |
|
+ can be changed by setting the <option>nta-lifetime</option> in |
|
+ <filename>named.conf</filename>. When added, NTAs are stored in a |
|
+ file (<filename><replaceable>viewname</replaceable>.nta</filename>) |
|
+ in order to persist across restarts of the <command>named</command> server. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The EDNS Client Subnet (ECS) option is now supported for |
|
+ authoritative servers; if a query contains an ECS option then |
|
+ ACLs containing <option>geoip</option> or <option>ecs</option> |
|
+ elements can match against the address encoded in the option. |
|
+ This can be used to select a view for a query, so that different |
|
+ answers can be provided depending on the client network. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The EDNS EXPIRE option has been implemented on the client |
|
+ side, allowing a slave server to set the expiration timer |
|
+ correctly when transferring zone data from another slave |
|
+ server. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ A new <option>masterfile-style</option> zone option controls |
|
+ the formatting of text zone files: When set to |
|
+ <literal>full</literal>, the zone file will dumped in |
|
+ single-line-per-record format. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +ednsopt</command> can now be used to set |
|
+ arbitrary EDNS options in DNS requests. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +ednsflags</command> can now be used to set |
|
+ yet-to-be-defined EDNS flags in DNS requests. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +[no]ednsnegotiation</command> can now be used enable / |
|
+ disable EDNS version negotiation. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +header-only</command> can now be used to send |
|
+ queries without a question section. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +ttlunits</command> causes <command>dig</command> |
|
+ to print TTL values with time-unit suffixes: w, d, h, m, s for |
|
+ weeks, days, hours, minutes, and seconds. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +zflag</command> can be used to set the last |
|
+ unassigned DNS header flag bit. This bit is normally zero. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +dscp=<replaceable>value</replaceable></command> |
|
+ can now be used to set the DSCP code point in outgoing query |
|
+ packets. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dig +mapped</command> can now be used to determine |
|
+ if mapped IPv4 addresses can be used. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <option>serial-update-method</option> can now be set to |
|
+ <literal>date</literal>. On update, the serial number will |
|
+ be set to the current date in YYYYMMDDNN format. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>dnssec-signzone -N date</command> also sets the serial |
|
+ number to YYYYMMDDNN. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>named -L <replaceable>filename</replaceable></command> |
|
+ causes <command>named</command> to send log messages to the |
|
+ specified file by default instead of to the system log. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The rate limiter configured by the |
|
+ <option>serial-query-rate</option> option no longer covers |
|
+ NOTIFY messages; those are now separately controlled by |
|
+ <option>notify-rate</option> and |
|
+ <option>startup-notify-rate</option> (the latter of which |
|
+ controls the rate of NOTIFY messages sent when the server |
|
+ is first started up or reconfigured). |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The default number of tasks and client objects available |
|
+ for serving lightweight resolver queries have been increased, |
|
+ and are now configurable via the new <option>lwres-tasks</option> |
|
+ and <option>lwres-clients</option> options in |
|
+ <filename>named.conf</filename>. [RT #35857] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Log output to files can now be buffered by specifying |
|
+ <command>buffered yes;</command> when creating a channel. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>delv +tcp</command> will exclusively use TCP when |
|
+ sending queries. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>named</command> will now check to see whether |
|
+ other name server processes are running before starting up. |
|
+ This is implemented in two ways: 1) by refusing to start |
|
+ if the configured network interfaces all return "address |
|
+ in use", and 2) by attempting to acquire a lock on a file |
|
+ specified by the <option>lock-file</option> option or |
|
+ the <command>-X</command> command line option. The |
|
+ default lock file is |
|
+ <filename>/var/run/named/named.lock</filename>. |
|
+ Specifying <literal>none</literal> will disable the lock |
|
+ file check. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>rndc delzone</command> can now be applied to zones |
|
+ which were configured in <filename>named.conf</filename>; |
|
+ it is no longer restricted to zones which were added by |
|
+ <command>rndc addzone</command>. (Note, however, that |
|
+ this does not edit <filename>named.conf</filename>; the zone |
|
+ must be removed from the configuration or it will return |
|
+ when <command>named</command> is restarted or reloaded.) |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>rndc modzone</command> can be used to reconfigure |
|
+ a zone, using similar syntax to <command>rndc addzone</command>. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ <command>rndc showzone</command> displays the current |
|
+ configuration for a specified zone. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Added server-side support for pipelined TCP queries. Clients |
|
+ may continue sending queries via TCP while previous queries are |
|
+ processed in parallel. Responses are sent when they are |
|
+ ready, not necessarily in the order in which the queries were |
|
+ received. |
|
+ </para> |
|
+ <para> |
|
+ To revert to the former behavior for a particular |
|
+ client address or range of addresses, specify the address prefix |
|
+ in the "keep-response-order" option. To revert to the former |
|
+ behavior for all clients, use "keep-response-order { any; };". |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The new <command>mdig</command> command is a version of |
|
+ <command>dig</command> that sends multiple pipelined |
|
+ queries and then waits for responses, instead of sending one |
|
+ query and waiting the response before sending the next. [RT #38261] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ To enable better monitoring and troubleshooting of RFC 5011 |
|
+ trust anchor management, the new <command>rndc managed-keys</command> |
|
+ can be used to check status of trust anchors or to force keys |
|
+ to be refreshed. Also, the managed-keys data file now has |
|
+ easier-to-read comments. [RT #38458] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ An <command>--enable-querytrace</command> configure switch is |
|
+ now available to enable very verbose query tracelogging. This |
|
+ option can only be set at compile time. This option has a |
|
+ negative performance impact and should be used only for |
|
+ debugging. [RT #37520] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ A new <command>tcp-only</command> option can be specified |
|
+ in <command>server</command> statements to force |
|
+ <command>named</command> to connect to the specified |
|
+ server via TCP. [RT #37800] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The <command>nxdomain-redirect</command> option specifies |
|
+ a DNS namespace to use for NXDOMAIN redirection. When a |
|
+ recursive lookup returns NXDOMAIN, a second lookup is |
|
+ initiated with the specified name appended to the query |
|
+ name. This allows NXDOMAIN redirection data to be supplied |
|
+ by multiple zones configured on the server or by recursive |
|
+ queries to other servers. (The older method, using |
|
+ a single <command>type redirect</command> zone, has |
|
+ better average performance but is less flexible.) [RT #37989] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The following types have been implemented: CSYNC, NINFO, RKEY, |
|
+ SINK, TA, TALINK. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ A new <command>message-compression</command> option can be |
|
+ used to specify whether or not to use name compression when |
|
+ answering queries. Setting this to <userinput>no</userinput> |
|
+ results in larger responses, but reduces CPU consumption and |
|
+ may improve throughput. The default is <userinput>yes</userinput>. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ A <command>read-only</command> option is now available in the |
|
+ <command>controls</command> statement to grant non-destructive |
|
+ control channel access. In such cases, a restricted set of |
|
+ <command>rndc</command> commands are allowed, which can |
|
+ report information from <command>named</command>, but cannot |
|
+ reconfigure or stop the server. By default, the control channel |
|
+ access is <emphasis>not</emphasis> restricted to these |
|
+ read-only operations. [RT #40498] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ When loading a signed zone, <command>named</command> will |
|
+ now check whether an RRSIG's inception time is in the future, |
|
+ and if so, it will regenerate the RRSIG immediately. This helps |
|
+ when a system's clock needs to be reset backwards. |
|
+ </para> |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ </section> |
|
+ |
|
+ <section xml:id="relnotes_changes"><info><title>Feature Changes</title></info> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <para> |
|
+ The timers returned by the statistics channel (indicating current |
|
+ time, server boot time, and most recent reconfiguration time) are |
|
+ now reported with millisecond accuracy. [RT #40082] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Updated the compiled-in addresses for H.ROOT-SERVERS.NET |
|
+ and L.ROOT-SERVERS.NET. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ ACLs containing <command>geoip asnum</command> elements were |
|
+ not correctly matched unless the full organization name was |
|
+ specified in the ACL (as in |
|
+ <command>geoip asnum "AS1234 Example, Inc.";</command>). |
|
+ They can now match against the AS number alone (as in |
|
+ <command>geoip asnum "AS1234";</command>). |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ When using native PKCS#11 cryptography (i.e., |
|
+ <command>configure --enable-native-pkcs11</command>) HSM PINs |
|
+ of up to 256 characters can now be used. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ NXDOMAIN responses to queries of type DS are now cached separately |
|
+ from those for other types. This helps when using "grafted" zones |
|
+ of type forward, for which the parent zone does not contain a |
|
+ delegation, such as local top-level domains. Previously a query |
|
+ of type DS for such a zone could cause the zone apex to be cached |
|
+ as NXDOMAIN, blocking all subsequent queries. (Note: This |
|
+ change is only helpful when DNSSEC validation is not enabled. |
|
+ "Grafted" zones without a delegation in the parent are not a |
|
+ recommended configuration.) |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Update forwarding performance has been improved by allowing |
|
+ a single TCP connection to be shared between multiple updates. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ By default, <command>nsupdate</command> will now check |
|
+ the correctness of hostnames when adding records of type |
|
+ A, AAAA, MX, SOA, NS, SRV or PTR. This behavior can be |
|
+ disabled with <command>check-names no</command>. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Added support for OPENPGPKEY type. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The names of the files used to store managed keys and added |
|
+ zones for each view are no longer based on the SHA256 hash |
|
+ of the view name, except when this is necessary because the |
|
+ view name contains characters that would be incompatible with use |
|
+ as a file name. For views whose names do not contain forward |
|
+ slashes ('/'), backslashes ('\'), or capital letters - which |
|
+ could potentially cause namespace collision problems on |
|
+ case-insensitive filesystems - files will now be named |
|
+ after the view (for example, <filename>internal.mkeys</filename> |
|
+ or <filename>external.nzf</filename>). However, to ensure |
|
+ consistent behavior when upgrading, if a file using the old |
|
+ name format is found to exist, it will continue to be used. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ "rndc" can now return text output of arbitrary size to |
|
+ the caller. (Prior to this, certain commands such as |
|
+ "rndc tsig-list" and "rndc zonestatus" could return |
|
+ truncated output.) |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Errors reported when running <command>rndc addzone</command> |
|
+ (e.g., when a zone file cannot be loaded) have been clarified |
|
+ to make it easier to diagnose problems. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ When encountering an authoritative name server whose name is |
|
+ an alias pointing to another name, the resolver treats |
|
+ this as an error and skips to the next server. Previously |
|
+ this happened silently; now the error will be logged to |
|
+ the newly-created "cname" log category. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ If <command>named</command> is not configured to validate |
|
+ answers, then allow fallback to plain DNS on timeout even when |
|
+ we know the server supports EDNS. This will allow the server to |
|
+ potentially resolve signed queries when TCP is being |
|
+ blocked. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Large inline-signing changes should be less disruptive. |
|
+ Signature generation is now done incrementally; the number |
|
+ of signatures to be generated in each quantum is controlled |
|
+ by "sig-signing-signatures <replaceable>number</replaceable>;". |
|
+ [RT #37927] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The experimental SIT option (code point 65001) of BIND |
|
+ 9.10.0 through BIND 9.10.2 has been replaced with the COOKIE |
|
+ option (code point 10). It is no longer experimental, and |
|
+ is sent by default, by both <command>named</command> and |
|
+ <command>dig</command>. |
|
+ </para> |
|
+ <para> |
|
+ The SIT-related named.conf options have been marked as |
|
+ obsolete, and are otherwise ignored. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ When <command>dig</command> receives a truncated (TC=1) |
|
+ response or a BADCOOKIE response code from a server, it |
|
+ will automatically retry the query using the server COOKIE |
|
+ that was returned by the server in its initial response. |
|
+ [RT #39047] |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ A alternative NXDOMAIN redirect method (nxdomain-redirect) |
|
+ which allows the redirect information to be looked up from |
|
+ a namespace on the Internet rather than requiring a zone |
|
+ to be configured on the server is now available. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Retrieving the local port range from net.ipv4.ip_local_port_range |
|
+ on Linux is now supported. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Within the <option>response-policy</option> option, it is now |
|
+ possible to configure RPZ rewrite logging on a per-zone basis |
|
+ using the <option>log</option> clause. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ The default preferred glue is now the address type of the |
|
+ transport the query was received over. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ On machines with 2 or more processors (CPU), the default value |
|
+ for the number of UDP listeners has been changed to the number |
|
+ of detected processors minus one. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Zone transfers now use smaller message sizes to improve |
|
+ message compression. This results in reduced network usage. |
|
+ </para> |
|
+ </listitem> |
|
+ <listitem> |
|
+ <para> |
|
+ Added support for the AVC resource record type (Application |
|
+ Visibility and Control). |
|
+ </para> |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ </section> |
|
+ |
|
+ <section xml:id="relnotes_port"><info><title>Porting Changes</title></info> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <para> |
|
+ None. |
|
+ </para> |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ </section> |
|
+ |
|
+ <section xml:id="relnotes_bugs"><info><title>Bug Fixes</title></info> |
|
+ <itemizedlist> |
|
+ <listitem> |
|
+ <para> |
|
+ None. |
|
+ </para> |
|
+ </listitem> |
|
+ </itemizedlist> |
|
+ </section> |
|
+ <section xml:id="end_of_life"><info><title>End of Life</title></info> |
|
+ |
|
+ <para> |
|
+ The end of life for BIND 9.11 is yet to be determined but |
|
+ will not be before BIND 9.13.0 has been released for 6 months. |
|
+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://www.isc.org/downloads/software-support-policy/">https://www.isc.org/downloads/software-support-policy/</link> |
|
+ </para> |
|
+ </section> |
|
+ <section xml:id="relnotes_thanks"><info><title>Thank You</title></info> |
|
+ |
|
+ <para> |
|
+ Thank you to everyone who assisted us in making this release possible. |
|
+ If you would like to contribute to ISC to assist us in continuing to |
|
+ make quality open source software, please visit our donations page at |
|
+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://www.isc.org/donate/">http://www.isc.org/donate/</link>. |
|
+ </para> |
|
+ </section> |
|
+</section> |
|
-- |
|
2.14.3 |
|
|
|
|