#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015, NORDUnet A/S.
# See LICENSE for licensing information.
import sys
import argparse
import json
import errno
import shutil
import base64
from datetime import datetime, timedelta, tzinfo
import time
from certtools import get_sth, create_ssl_context, check_sth_signature, get_public_key_from_file, get_consistency_proof, verify_consistency_proof
NAGIOS_OK = 0
NAGIOS_WARN = 1
NAGIOS_CRIT = 2
NAGIOS_UNKNOWN = 3
DEFAULT_CUR_FILE = 'cur-sth.json'
parser = argparse.ArgumentParser(description="")
parser.add_argument('--cur-sth',
metavar='file',
default=DEFAULT_CUR_FILE,
help="File containing current STH (default=%s)" % DEFAULT_CUR_FILE)
parser.add_argument('baseurl', help="Base URL for CT log")
parser.add_argument('--publickey', default=None, metavar='file', help='Public key for the CT log')
parser.add_argument('--cafile', default=None, metavar='file', help='File containing the CA cert')
parser.add_argument('--allow-lag', action='store_true', help='Allow node to lag behind previous STH')
parser.add_argument('--quiet-ok', action='store_true', help="Don't print status if OK")
def print_sth(sth):
if sth is None:
print "NONE"
else:
print sth['timestamp']
print sth['sha256_root_hash']
print sth['tree_size']
print sth['tree_head_signature']
def get_new_sth(baseurl):
try:
sth = get_sth(baseurl)
except Exception, e:
print e
sys.exit(NAGIOS_UNKNOWN)
return sth
def read_sth(fn):
try:
f = open(fn)
except IOError, e:
if e.errno == errno.ENOENT:
return None
raise e
return json.loads(f.read())
def mv_file(fromfn, tofn):
shutil.move(fromfn, tofn)
def write_file(fn, sth):
tempname = fn + ".new"
open(tempname, 'w').write(json.dumps(sth))
mv_file(tempname, fn)
class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=0)
def dst(self, dt):
return timedelta(0)
def check_age(sth):
age = time.time() - sth["timestamp"]/1000
sth_time = datetime.fromtimestamp(sth['timestamp'] / 1000, UTC()).strftime("%Y-%m-%d %H:%M:%S")
roothash = b64_to_b16(sth['sha256_root_hash'])
if age > 6 * 3600:
print "CRITICAL: %s is older than 6h: %s UTC" % (roothash, sth_time)
sys.exit(NAGIOS_CRIT)
if age > 2 * 3600:
print "WARNING: %s is older than 2h: %s UTC" % (roothash, sth_time)
sys.exit(NAGIOS_WARN)
return "%s UTC, %d minutes ago" % (sth_time, age/60)
def check_consistency(newsth, oldsth, baseurl):
consistency_proof = [base64.decodestring(entry) for entry in get_consistency_proof(baseurl, oldsth["tree_size"], newsth["tree_size"])]
(old_treehead, new_treehead) = verify_consistency_proof(consistency_proof, oldsth["tree_size"], newsth["tree_size"], base64.b64decode(oldsth["sha256_root_hash"]))
assert old_treehead == base64.b64decode(oldsth["sha256_root_hash"])
assert new_treehead == base64.b64decode(newsth["sha256_root_hash"])
def b64_to_b16(s):
return base64.b16encode(base64.decodestring(s))
def main(args):
if args.cur_sth is None:
args.cur_sth = "cur-sth.json"
create_ssl_context(cafile=args.cafile)
logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None
newsth = get_new_sth(args.baseurl)
check_sth_signature(args.baseurl, newsth, publickey=logpublickey)
oldsth = read_sth(args.cur_sth)
#print_sth(newsth)
#print_sth(oldsth)
if oldsth:
if newsth["tree_size"] == oldsth["tree_size"]:
if oldsth["sha256_root_hash"] != newsth["sha256_root_hash"]:
print "CRITICAL: root hash is different even though tree size is the same.",
print "tree size:", newsth["tree_size"],
print "old hash:", b64_to_b16(oldsth["sha256_root_hash"])
print "new hash:", b64_to_b16(newsth["sha256_root_hash"])
sys.exit(NAGIOS_CRIT)
elif newsth["tree_size"] < oldsth["tree_size"]:
if not args.allow_lag:
print "CRITICAL: new tree smaller than previous tree (%d < %d)" % \
(newsth["tree_size"], oldsth["tree_size"])
sys.exit(NAGIOS_CRIT)
if oldsth and oldsth["tree_size"] > 0 and oldsth["tree_size"] != newsth["tree_size"]:
check_consistency(newsth, oldsth, args.baseurl)
age = check_age(newsth)
write_file(args.cur_sth, newsth)
if not args.quiet_ok:
print "OK: size: %d hash: %s, %s" % (newsth["tree_size"], b64_to_b16(newsth["sha256_root_hash"])[:8], age)
sys.exit(NAGIOS_OK)
if __name__ == '__main__':
main(parser.parse_args())