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.
6054 lines
213 KiB
6054 lines
213 KiB
6 years ago
|
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
|
||
|
|