-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Somehow useful when using ISC Bind’s auto-maintain

I’ve been using for a while using DNSSEC for some of my domains, based on ISC Bind’s auto maintain functionnality. Everything with this works fine, when you have keys with appropriate dates set. The only annoying thing is to handle all dates between all the key files that you might have. This tiny script will help you dealing with that.

The problem, simply explained

It is not really a problem per se, but ISC Bind auto-dnssec maintain feature (4.9.3 Fully automatic zone signing) indiquates to following:

auto-dnssec maintain includes the above functionality, but will also automatically adjust the zone’s DNSKEY records on schedule according to the keys’ timing metadata. (See dnssec-keygen(8) and dnssec-settime(8) for more information.) named will periodically search the key directory for keys matching the zone, and if the keys’ metadata indicates that any change should be made the zone, such as adding, removing, or revoking a key, then that action will be carried out. By default, the key directory is checked for changes every 60 minutes; this period can be adjusted with the dnssec-loadkeys-interval, up to a maximum of 24 hours. The rndc loadkeys forces named to check for key updates immediately.

4.9.3 Fully automatic zone signing

As you can see, you are supposed to set keys validity with dnssec-settime(8), but they don’t tell you really what are the different steps of a rollover. Those are the following:

+---------+    +-----------+    +--------+    +----------+    +---------+
|         |    |           |    |        |    |          |    |         |
| Created +--> | Published +--> | Active +--> | Inactive +--> | Deleted |
|         |    |           |    |        |    |          |    |         |
+---------+    +-----------+    +--------+    +----------+    +---------+
  • Created: The key is taken in account by ISC Bind, but is shown nowhere and is not signing the zone.
  • Published: The key is present in the zone, but not used for signing it. It is being signed as any other type of RRSET.
  • Active: You could have guessed it. The key is used for signing the zone along with any other active key
  • Inactive: The key is still present in the zone, but is not used anymore for signing.
  • Deleted: The key is not present anymore in the zone.

What is important for a successful rollover, is that the key that will expire and the key that becomes active should be active at the same time. If you schematize a rollover, it would happen then this way:

       +---------+    +-----------+    +--------+    +----------+    +---------+              
       |         |    |           |    |        |    |          |    |         |              
Key1   | Created +--> | Published +--> | Active +--> | Inactive +--> | Deleted |              
       |         |    |           |    |        |    |          |    |         |              
       +---------+    +-----------+    +--------+    +----------+    +---------+              
              +---------+    +-----------+    +--------+    +----------+    +---------+
              |         |    |           |    |        |    |          |    |         |
       Key2   | Created +--> | Published +--> | Active +--> | Inactive +--> | Deleted |
              |         |    |           |    |        |    |          |    |         |
              +---------+    +-----------+    +--------+    +----------+    +---------+

So, this is what happened to me:

$ ll keys
total 208
- -rw-r--r--  1 bkraft  1175059581   707B Jun 20 07:19 Kbkraft.fr.+008+07749.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+07749.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+10203.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+10203.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+11295.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+11295.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+11994.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+11994.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+14574.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+14574.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+15585.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+15585.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+16578.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+16578.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+16899.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+16899.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+17781.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+17781.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+21549.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+21549.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+29091.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+29091.private
- -rw-r--r--  1 bkraft  1175059581   708B Jun 20 07:19 Kbkraft.fr.+008+29259.key
- -rw-r--r--  1 bkraft  1175059581   1.8K Jun 20 07:19 Kbkraft.fr.+008+29259.private
- -rw-r--r--  1 bkraft  1175059581   601B Jun 20 07:19 Kbkraft.fr.+008+64930.key
- -rw-r--r--  1 bkraft  1175059581   1.7K Jun 20 07:19 Kbkraft.fr.+008+64930.private
- -rw-r--r--  1 bkraft  1175059581     0B Oct 25 09:40 Kbkraft.fr.+008+broken.key
- -rw-r--r--  1 bkraft  1175059581     0B Oct 25 09:40 Kbkraft.fr.+008+broken.private

I hear somebody screaming Why in the world does he need so much keys ?. Well, there’s in the directory 14 different keys: 1 key signing key, 12 zone signing keys and one “broken” for the sake of demonstration that this tool works properly.
The 12 zone signing keys will be used for a rollover each month of the keys. No, you don’t need to do so, but I choosed to.

You might already see where this is going. There’s no way that I could find to easily answer the following questions: What are the currently active keys ? When is the next rollover happening (if ever) ? Which one of those is the KSK ? Which keys are unused now ?

My crappy script

This was the perfect occasion for me to learn how to use Python. And so did I write a script that prints out the different dates of all keys for a zone within a directory, and also providing you the ability to sort by date, and displaying the current status of each key. The only requirement of the script is that you use Python3.

You can find it here: /files/scripts/dnssec-keyrollover-tool/dnssec-keyrollover-tool.py.

Help:

$ python3 dnssec-keyrollover-tool.py --help
	usage: dnssec-keyrollover-tool.py [-h] -d DIRECTORY -z ZONE [-s {C,P,A,I,D}]
	                                  [-v]

	Helps you with handling key rollovers

	optional arguments:
	  -h, --help            show this help message and exit
	  -d DIRECTORY, --directory DIRECTORY
	                        Directory where key files reside
	  -z ZONE, --zone ZONE  Zone for which we will analyse key
	  -s {C,P,A,I,D}, --sortby {C,P,A,I,D}
	                        When displaying the keys, sort by key attribute.
	                        Attributes are Creation, Publication (default),
	                        Activation, Inactivate and Delete date.
	  -v, --verbose         Make the script spit whatever it does or finds

	For more information, please visit http://bkraft.fr

Annnd this is the result of running the tool:

$ python3 dnssec-keyrollover-tool.py -d keys -z bkraft.fr
[zone]	keys/Kbkraft.fr.+008+16578.key	16578	C:2013-11-18 11:24:12	P:2013-11-18 11:24:12	A:2013-11-18 11:24:12	I:2013-12-16 12:00:00	D:2013-12-17 12:00:00	Current status: deleted
[key]	keys/Kbkraft.fr.+008+64930.key	64930	C:2013-11-18 11:25:11	P:2013-11-18 11:25:11	A:2013-11-18 11:25:11	I:1970-01-01 00:00:00	D:1970-01-01 00:00:00	Current status: active
[zone]	keys/Kbkraft.fr.+008+11295.key	11295	C:2013-11-18 11:26:14	P:2013-12-14 12:00:00	A:2013-12-15 12:00:00	I:2014-01-16 12:00:00	D:2014-01-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+10203.key	10203	C:2013-11-18 11:26:37	P:2014-01-14 12:00:00	A:2014-01-15 12:00:00	I:2014-02-16 12:00:00	D:2014-02-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+14574.key	14574	C:2013-11-18 11:26:53	P:2014-02-14 12:00:00	A:2014-02-15 12:00:00	I:2014-03-16 12:00:00	D:2014-03-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+29259.key	29259	C:2013-11-18 11:27:44	P:2014-03-14 12:00:00	A:2014-03-15 12:00:00	I:2014-04-16 12:00:00	D:2014-04-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+17781.key	17781	C:2013-11-18 11:28:06	P:2014-04-14 12:00:00	A:2014-04-15 12:00:00	I:2014-05-16 12:00:00	D:2014-05-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+21549.key	21549	C:2014-05-07 11:58:38	P:2014-05-14 12:00:00	A:2014-05-15 12:00:00	I:2014-06-16 12:00:00	D:2014-06-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+07749.key	7749	C:2014-05-07 11:58:45	P:2014-06-14 12:00:00	A:2014-06-15 12:00:00	I:2014-07-16 12:00:00	D:2014-07-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+11994.key	11994	C:2014-05-07 11:58:48	P:2014-07-14 12:00:00	A:2014-07-15 12:00:00	I:2014-08-16 12:00:00	D:2014-08-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+29091.key	29091	C:2014-05-07 11:59:06	P:2014-08-14 12:00:00	A:2014-08-15 12:00:00	I:2014-09-16 12:00:00	D:2014-09-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+16899.key	16899	C:2014-05-07 11:59:12	P:2014-09-14 12:00:00	A:2014-09-15 12:00:00	I:2014-10-16 12:00:00	D:2014-10-17 12:00:00	Current status: deleted
[zone]	keys/Kbkraft.fr.+008+15585.key	15585	C:2014-05-07 11:59:17	P:2014-10-14 12:00:00	A:2014-10-15 12:00:00	I:2014-11-16 12:00:00	D:2014-11-17 12:00:00	Current status: active

For more readability, here’s a screenshot:

In this output, sorting of the keys is done by publication date. Within brackets is the key type and third column is the key ID.

The code

Don’t cry please, i’m no coder. Any enhancement welcome.

#!/usr/bin/python
# vim: set fileencoding=UTF-8 :
import sys
import argparse
import errno
import os
import re
from pathlib import Path
from datetime import datetime, timedelta
__author__ = 'Benjamin KRAFT http://bkraft.fr'

class MyKey:
	"""Object representation of a key"""
	inactive=datetime(1970, 1, 1)
	delete=datetime(1970, 1, 1)
	activate=datetime(1970, 1, 1)
	creation=datetime(1970, 1, 1)
	publish=datetime(1970, 1, 1)
	keytype = False
	keyid = False
	name = ""
	def status(self):
		bigben = datetime.now()
		if self.delete < bigben and self.delete != datetime(1970, 1, 1):
			return "deleted"
		if self.inactive < bigben and self.inactive != datetime(1970, 1, 1):
			return "inactive"
		if self.activate < bigben and self.activate != datetime(1970, 1, 1):
			return "active"
		if self.publish < bigben and self.publish != datetime(1970, 1, 1):
			return "published"
		return "not yet published"
	def readkey(self, key):
		debug("Reading key content: "+str(key), True, args.verbose)
		self.name = str(key)
		try:
			with key.open() as filedesc:
				for line in filedesc:
					matchresult = re.match("^; Created: (\d{14}).*", line)
					if matchresult:
						self.creation = datetime.strptime(matchresult.group(1), "%Y%m%d%H%M%S")
					matchresult = re.match("^; Publish: (\d{14}).*", line)
					if matchresult:
						self.publish = datetime.strptime(matchresult.group(1), "%Y%m%d%H%M%S")
					matchresult = re.match("^; Activate: (\d{14}).*", line)
					if matchresult:
						self.activate = datetime.strptime(matchresult.group(1), "%Y%m%d%H%M%S")
					matchresult = re.match("^; Inactive: (\d{14}).*", line)
					if matchresult:
						self.inactive = datetime.strptime(matchresult.group(1), "%Y%m%d%H%M%S")
					matchresult = re.match("^; Delete: (\d{14}).*", line)
					if matchresult:
						self.delete = datetime.strptime(matchresult.group(1), "%Y%m%d%H%M%S")
					matchresult = re.match("^; This is a (\w+)-signing key, keyid (\d+).*", line)
					if matchresult:
						self.keytype = matchresult.group(1)
						self.keyid = matchresult.group(2)
		except PermissionError:
			warning("Unable to read file "+str(key)+": ", errno.EACCES)
	def __init__(self, KeyFile):
		self.readkey(KeyFile)

def debug(message, is_debug, debug_wanted):
# message is printed if is_debug == debug_wanted == true
# or is_debug = false
        if debug_wanted == True and is_debug == True:
                print("[debug] "+message)
        elif is_debug== False:
                print(message)

def error(messageprefix, exitcode):
	print("[error]: "+messageprefix+os.strerror(exitcode))
	sys.exit(exitcode)

def warning(messageprefix, exitcode):
		print("[warning]: "+messageprefix+os.strerror(exitcode))

def getkeys(path, zone):
#Returns an "array" of files related to zone
	requestedpath = Path(path)
	debug("Checking if directory '"+path+"' exists", True, args.verbose)
	if requestedpath.exists() == False:
		error("Accessing directory '"+path+"': ", errno.ENOENT)
	debug("Checking if directory '"+path+"' is a directory", True, args.verbose)
	if requestedpath.is_dir() == False:
		error(path+": ",errno.ENOTDIR)
	debug("Getting list of related keys in directory '"+path+"'", True, args.verbose)
	keys = sorted(requestedpath.glob("K"+zone+".*.key"))
	debug("Checking if we did find some keys in directory '"+path+"'", True, args.verbose)
	if len(keys) == 0:
		error("Unable to get keys for zone "+zone+" in directory '"+path+"': ", errno.ENOENT)
	return keys

# Command line argument parsing
parser = argparse.ArgumentParser(
description="Helps you with handling key rollovers",
epilog="For more information, please visit http://bkraft.fr")
parser.add_argument('-d','--directory',help='Directory where key files reside',required=True)
parser.add_argument('-z','--zone',help='Zone for which we will analyse key',required=True)
parser.add_argument('-s','--sortby',help='When displaying the keys, sort by key attribute. Attributes are Creation, Publication (default), Activation, Inactivate and Delete date.', type=str, choices =['C', 'P', 'A', 'I', 'D'], default='P' )
parser.add_argument('-v','--verbose',help='Make the script spit whatever it does or finds',required=False,action="store_true")
args=parser.parse_args()

# Main program
sort_arg = {'C': 'creation', 'D': 'delete', 'A': 'activate', 'I': 'inactive', 'P': 'publish' }

debug("Specified key Directory (-d/--directory): "+args.directory, True, args.verbose)
debug("Specified zone (-z/--zone): "+args.zone, True, args.verbose)
debug("Specified sortby attribute (-s/--sortby): "+args.sortby, True, args.verbose)
keys = getkeys(args.directory, args.zone)
sorting_key = sort_arg[args.sortby]

zonekeys = []
for key in keys:
	k = MyKey(key)
	if k.keytype != False:
		zonekeys.append(k)

keyinfo = sorted(zonekeys, key=lambda k: eval("k."+sorting_key))
for i in keyinfo:
	print("["+i.keytype+"]\t"+i.name+"\t"+str(i.keyid)+"\tC:"+str(i.creation)+"\tP:"+str(i.publish)+"\tA:"+str(i.activate)+"\tI:"+str(i.inactive)+"\tD:"+str(i.delete) + "\tCurrent status: "+str(i.status()))
-----BEGIN PGP SIGNATURE-----
Comment: GPGTools - http://gpgtools.org

iQIcBAEBCAAGBQJW5eRSAAoJEBeKS2x6xuR7GNoP/19ekp1mTKI1/OcKcjC3r8mV
sfY8TSg1rdE/qsRuUgKyjM0lCMBtG3GBcuUzTuCbCAAaJQGISGLxYMyo5aqZmjeI
YjzHtfff/bl0Spay3Tws9OP7ueBPPBjDQqR4NYdT1SFW0KzN6MFchwlL0vysX92F
fN3oy4eM6f8oKDlTIVby7846uaAMYjim8vriYTohUYsCF5+7ZBXP9ZtjaK0jW+du
3Ufa5Foq5iwufV+18nnCjPXad1KeDUQVZqXmgVl2j8G5GOditAy0O9BNOwe5ykuL
oNeMzBvG4rdj95CkB0kzWkUgMdgvHUDI8P4h8FVAP64m/18l83ZwNdgS5uM/YpqV
coi3cLHpVdttJvaLkRkKy73j3Gb3kwFxTmcnDE+1DgAfn+YlkQqviuorcNRl+OE4
RjnJirlIS/1R3tSd7raIcCTupC/D/wuw5+N25U4e4XBrBr9sd3UjHCOqtjaNsFG+
y1knLdqXSLyDiuAdKbJFE39hEGT5UCgi9MlbcDIBxGsegK/zpFrhr08J5zbjMotE
GkwZsx+VrWjysHPvmCtKoJ2Wi6LSY9QutW5m0QYkejy46sSa28tjfYwzyv3CwDrI
7kjkAlT4ykio0ixzExNA46gVh5354CsP74slM99QDJ0w7cD7Q4V3Y894r8IQh892
haUssW03Ew0mqnIahp0G
=Wpx4
-----END PGP SIGNATURE-----

Hint: To validate signature, please view page source and copy html code between BEGIN PGP Signed message and END PGP Signature anchors.

Created the 2014-10-25

Share this


Resources

10 last articles

blog comments powered by Disqus