diff options
Diffstat (limited to 'rpkid/rpki/gui/scripts')
-rw-r--r-- | rpkid/rpki/gui/scripts/adduser.py | 115 | ||||
-rw-r--r-- | rpkid/rpki/gui/scripts/list_resources.py | 217 | ||||
-rwxr-xr-x | rpkid/rpki/gui/scripts/load_csv.py | 145 | ||||
-rwxr-xr-x | rpkid/rpki/gui/scripts/roa_check.py | 71 |
4 files changed, 548 insertions, 0 deletions
diff --git a/rpkid/rpki/gui/scripts/adduser.py b/rpkid/rpki/gui/scripts/adduser.py new file mode 100644 index 00000000..8b475c0c --- /dev/null +++ b/rpkid/rpki/gui/scripts/adduser.py @@ -0,0 +1,115 @@ +# $Id$ +# +# Copyright (C) 2010, 2011 SPARTA, Inc. dba Cobham Analytic Solutions +# +# 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 SPARTA DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL SPARTA 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. +# +# +# Helper script to quickly set up a new portal-gui user/handle. This script +# is designed to be safe to run multiple times for the same user. +# +# DO NOT EDIT! This script is automatically generated from adduser.py + +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'rpki.gui.settings' + +from django.contrib.auth.models import User +from django.conf import settings +from rpki.gui.app.models import Conf + +# The username that apache runs as. This is required so that we can chown +# the csv files that the portal-gui needs to write. +WEB_USER='@WEBUSER@' + +import os +import sys +import getpass +import pwd + +web_uid = pwd.getpwnam(WEB_USER)[2] + +if __name__ == '__main__': + if len(sys.argv) < 3: + print >>sys.stderr, 'usage: adduser <username> <user\'s email> <host handle>' + sys.exit(1) + + if os.getuid() != 0: + print >>sys.stderr, 'error: this script must be run as root so it can set file permissions.' + sys.exit(1) + + username = sys.argv[1] + email = sys.argv[2] + host = sys.argv[3] + print 'username=', username, 'email=', email, 'host=', host + + user_set = User.objects.filter(username=username) + if user_set: + print >>sys.stderr, 'user already exists' + user = user_set[0] + else: + print >>sys.stderr, 'creating user' + password = getpass.getpass() + user = User.objects.create_user(username, email, password) + + conf_set = Conf.objects.filter(handle=username) + if conf_set: + conf = conf_set[0] + else: + print >>sys.stderr, 'creating conf' + conf = Conf.objects.create(handle=username) + + # always try to add the user as owner just in case the Conf object was + # created previously by the "list_resources" script + conf.owner.add(user) + + if host != username: + host_set = Conf.objects.filter(handle=host) + if not host_set: + print >>sys.stderr, 'error: Conf object for host %s does not exist!' % host + sys.exit(1) + + conf.host = host_set[0] + else: + print >>sys.stderr, '%s is self-hosted' % username + conf.save() + + myrpki_dir = '%s/%s' % (settings.CONFDIR, username) + print 'myrpki_dir=', myrpki_dir + if not os.path.exists(myrpki_dir): + print 'creating ', myrpki_dir + os.mkdir(myrpki_dir) + os.chown(myrpki_dir, web_uid, -1) + + # create enought of rpki.conf enough to fool portal-gui + myrpki_conf = myrpki_dir + '/rpki.conf' + if not os.path.exists(myrpki_conf): + print 'creating ', myrpki_conf + with open(myrpki_conf, 'w') as f: + print >>f, """[myrpki] +run_rpkidemo=true +run_rpkid=false +asn_csv=%(path)s/asns.csv +roa_csv=%(path)s/roas.csv +prefix_csv=%(path)s/prefixes.csv""" % { 'path': myrpki_dir } + + # create empty csv files so portal-gui doesn't barf + for base in ['roas', 'asns', 'prefixes']: + fname = '%s/%s.csv' % (myrpki_dir, base) + if not os.path.exists(fname): + print 'creating ', fname + with open(fname, 'w') as f: + # just create an empty file + pass + os.chown(fname, web_uid, -1) + +# vim:sw=4 ts=8 diff --git a/rpkid/rpki/gui/scripts/list_resources.py b/rpkid/rpki/gui/scripts/list_resources.py new file mode 100644 index 00000000..77380505 --- /dev/null +++ b/rpkid/rpki/gui/scripts/list_resources.py @@ -0,0 +1,217 @@ +# $Id$ +# +# Copyright (C) 2010 SPARTA, Inc. dba Cobham Analytic Solutions +# +# 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 SPARTA DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL SPARTA 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. +# +# +# This script is reponsible for talking to rpkid and populating the +# portal-gui's sqlite database. It asks rpkid for the list of received +# resources, and the handles of any children. +# +# This script should be run in the directory containing the rpki.conf +# for the handle that is self-hosting rpkid. +# +# Exit values: +# 0 success, no errors +# 1 fatal error +# 2 usage error +# 3 did not receive all <list_received_resources/> responses, try again +# later + +import sys + +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'rpki.gui.settings' + +from datetime import datetime +import getopt +from os.path import basename + +from rpki.myrpki import CA +import rpki.config +import rpki.x509 +import rpki.http +import rpki.async +import rpki.left_right +import rpki.resource_set + +from rpki.gui.app import models + +verbose = False +version = '$Id$' + +def query_rpkid(): + """Fetch our received resources from the local rpkid using the rpki.conf + in the current directory.""" + cfg_file = os.getenv("RPKI_CONF", "rpki.conf") + cfg = rpki.config.parser(cfg_file, "myrpki") + bpki_servers = CA(cfg_file, cfg.get("bpki_servers_directory")) + rpkid_base = "http://%s:%s/" % (cfg.get("rpkid_server_host"), cfg.get("rpkid_server_port")) + + if verbose: + print 'current directory is', os.getcwd() + print 'cfg_file=', cfg_file + print 'bpki_servers=', bpki_servers.dir + print 'rpkid_base=', rpkid_base + + call_rpkid = rpki.async.sync_wrapper(rpki.http.caller( + proto = rpki.left_right, + client_key = rpki.x509.RSA(PEM_file = bpki_servers.dir + "/irbe.key"), + client_cert = rpki.x509.X509(PEM_file = bpki_servers.dir + "/irbe.cer"), + server_ta = rpki.x509.X509(PEM_file = bpki_servers.cer), + server_cert = rpki.x509.X509(PEM_file = bpki_servers.dir + "/rpkid.cer"), + url = rpkid_base + "left-right", + debug = verbose)) + + if verbose: + print 'retrieving the list of <self/> handles served by this rpkid' + rpkid_reply = call_rpkid(rpki.left_right.self_elt.make_pdu(action="list")) + + # retrieve info about each handle + pdus = [] + handles = [] + for h in rpkid_reply: + assert isinstance(h, rpki.left_right.self_elt) + if verbose: + print 'adding handle %s to query' % (h.self_handle,) + # keep a list of the handles served by rpkid so that we may check that + # all expected responses are received. + handles.append(h.self_handle) + pdus.extend( + [rpki.left_right.child_elt.make_pdu(action="list", self_handle=h.self_handle), + rpki.left_right.list_received_resources_elt.make_pdu(self_handle=h.self_handle) + #rpki.left_right.parent_elt.make_pdu(action="list", tag="parents", self_handle=handle), + #rpki.left_right.list_roa_requests_elt.make_pdu(tag='roas', self_handle=handle), + ]) + + if verbose: + print 'querying for children and resources' + return handles, call_rpkid(*pdus) + +def usage(rc): + print 'usage: %s [ -hvV ] [ --help ] [ --verbose ] [ --version ]' % basename(sys.argv[0],) + sys.exit(rc) + +try: + opts, args = getopt.getopt(sys.argv[1:], 'hvV', [ 'help', 'verbose', 'version']) +except getopt.GetoptError, err: + print str(err) + usage(2) + +for o,a in opts: + if o in ('-h', '--help'): + usage(0) + elif o in ('-v', '--verbose'): + verbose = True + elif o in ('-V', '--version'): + print basename(sys.argv[0]), version + sys.exit(0) + +handles, pdus = query_rpkid() +seen = set() # which handles we got <list_received_resources/> responses +for pdu in pdus: + conf_set = models.Conf.objects.filter(handle=pdu.self_handle) + if conf_set.count(): + conf = conf_set[0] + else: + if verbose: + print 'creating new conf for %s' % (pdu.self_handle,) + conf = models.Conf.objects.create(handle=pdu.self_handle) + + #if isinstance(pdu, rpki.left_right.parent_elt): +# print x.parent_handle, x.sia_base, x.sender_name, x.recipient_name, \ +# x.peer_contact_uri + if isinstance(pdu, rpki.left_right.child_elt): + # have we seen this child before? + child_set = conf.children.filter(handle=pdu.child_handle) + if not child_set: + if verbose: + print 'creating new child %s' % (pdu.child_handle,) + child = models.Child(conf=conf, handle=pdu.child_handle) + child.save() + #elif isinstance(x, rpki.left_right.list_roa_requests_elt): + # print x.asn, x.ipv4, x.ipv6 + elif isinstance(pdu, rpki.left_right.list_received_resources_elt): + # keep track of handles we got replies for + seen.add(pdu.self_handle) + # have we seen this parent before? + parent_set = conf.parents.filter(handle=pdu.parent_handle) + if not parent_set: + if verbose: + print 'creating new parent %s' % (pdu.parent_handle,) + parent = models.Parent(conf=conf, handle=pdu.parent_handle) + parent.save() + else: + parent = parent_set[0] + + not_before = datetime.strptime(pdu.notBefore, "%Y-%m-%dT%H:%M:%SZ") + not_after = datetime.strptime(pdu.notAfter, "%Y-%m-%dT%H:%M:%SZ") + + # have we seen this resource cert before? + cert_set = parent.resources.filter(uri=pdu.uri) + if cert_set.count() == 0: + cert = models.ResourceCert(uri=pdu.uri, parent=parent, + not_before=not_before, not_after=not_after) + else: + cert = cert_set[0] + # update timestamps since it could have been modified + cert.not_before = not_before + cert.not_after = not_after + cert.save() + + for asn in rpki.resource_set.resource_set_as(pdu.asn): + # see if this resource is already part of the cert + if cert.asn.filter(lo=asn.min, hi=asn.max).count() == 0: + # ensure this range wasn't seen from another of our parents + for v in models.Asn.objects.filter(lo=asn.min, hi=asn.max): + # determine if resource is delegated from another parent + if v.from_cert.filter(parent__in=conf.parents.all()).count(): + cert.asn.add(v) + break + else: + if verbose: + print 'adding AS %s' % (asn,) + cert.asn.create(lo=asn.min, hi=asn.max) + cert.save() + + # IPv4/6 - not separated in the django db + def add_missing_address(addr_set): + for ip in addr_set: + lo=str(ip.min) + hi=str(ip.max) + if cert.address_range.filter(lo=lo, hi=hi).count() == 0: + # ensure that this range wasn't previously seen from another of our parents + for v in models.AddressRange.objects.filter(lo=lo, hi=hi): + # determine if this resource is delegated from another parent as well + if v.from_cert.filter(parent__in=conf.parents.all()).count(): + cert.address_range.add(v) + break + else: + if verbose: + print 'adding address range %s' % (ip,) + cert.address_range.create(lo=lo, hi=hi) + cert.save() + + add_missing_address(rpki.resource_set.resource_set_ipv4(pdu.ipv4)) + add_missing_address(rpki.resource_set.resource_set_ipv6(pdu.ipv6)) + +# verify that we got responses for all expected handles +for h in handles: + if h not in seen: + print >>sys.stderr, 'error: did not receive response for handle %s' % (h,) + sys.exit(3) + +sys.exit(0) # success + +# vim:sw=4 expandtab ts=4 diff --git a/rpkid/rpki/gui/scripts/load_csv.py b/rpkid/rpki/gui/scripts/load_csv.py new file mode 100755 index 00000000..0ef49cce --- /dev/null +++ b/rpkid/rpki/gui/scripts/load_csv.py @@ -0,0 +1,145 @@ +# $Id$ +# +# Copyright (C) 2010 SPARTA, Inc. dba Cobham Analytic Solutions +# +# 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 SPARTA DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL SPARTA 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. +# +# +# Helper script to load existing data from csv into the Django DB. +# Primarly useful for the initial load, as the GUI does not sync changes +# made directly to the csv files back into the database. +# +# This script should be run from the directory containing the rpki.conf +# for the handle you are loading data +# + +import sys, os +os.environ['DJANGO_SETTINGS_MODULE'] = 'rpki.gui.settings' + +import csv +import socket # for socket.error + +import rpki +import rpki.resource_set +import rpki.ipaddrs +from rpki.myrpki import csv_reader + +from rpki.gui.app import models +from rpki.gui.app.views import add_roa_requests + +cfg_file = os.getenv("RPKI_CONF", "rpki.conf") +cfg = rpki.config.parser(cfg_file, "myrpki") +handle = cfg.get('handle') +asn_csv = cfg.get('asn_csv') +prefix_csv = cfg.get('prefix_csv') +roa_csv = cfg.get('roa_csv') + +print 'processing csv files for resource handle', handle + +conf = models.Conf.objects.get(handle=handle) + +# every parent has a favorite +def best_child(address_range, parent, parent_range): + '''Return the child address range that is the closest match, or + returns the arguments if no children.''' + if address_range == parent_range: + return (parent, parent_range) + for q in list(parent.children.all()): # force strict evaluation + t = q.as_resource_range() + if t.min <= address_range.min and t.max >= address_range.max: + return best_child(address_range, q, t) + # check for overlap + if t.min <= address_range.min <= t.max or t.min <= address_range.max <= t.max: + raise RuntimeError, \ + 'can not handle overlapping ranges: %s and %s' % (address_range, t) + return parent, parent_range + +def get_or_create_prefix(address_range): + '''Returns a AddressRange object for the resource_range_ip specified + as an argument. If no match is found, a new AddressRange object is + created as a child of the best matching received resource.''' + + # get all resources from our parents + prefix_set = models.AddressRange.objects.filter( + from_cert__parent__in=conf.parents.all()) + + # gross, since we store the address ranges as strings in the django + # db, we can't use the normal __lte and __gte filters, so we get to + # do it in python instead. + for prefix in prefix_set: + prefix_range = prefix.as_resource_range() + if (prefix_range.min <= address_range.min and + prefix_range.max >= address_range.max): + # there should only ever be a single matching prefix + break + else: + raise RuntimeError, '%s does not match any received address range.' % ( + address_range,) + + # find the best match among the children + grandchildren + prefix, prefix_range = best_child(address_range, prefix, prefix_range) + + print 'best match for %s is %s' % (address_range, prefix) + if prefix_range.min != address_range.min or prefix_range.max != address_range.max: + # create suballocation + print 'creating new range' + prefix = models.AddressRange.objects.create(lo=str(address_range.min), + hi=str(address_range.max), parent=prefix) + return prefix + +def get_or_create_asn(asn): + asn_set = models.Asn.objects.filter(lo__lte=asn.min, hi__gte=asn.max, + from_cert__parent__in=conf.parents.all()) + if not asn_set: + raise RuntimeError, '%s does not match any received AS range' % (asn,) + best = best_child(asn, asn_set[0], asn_set[0].as_resource_range())[0] + print 'best match for %s is %s' % (asn, best) + if best.lo != asn.min or best.hi != asn.max: + best = models.Asn.objects.create(lo=asn.min, hi=asn.max, parent=best) + return best + +def do_asns(): + print 'processing', asn_csv + for child_handle, asn in csv_reader(asn_csv, columns=2): + asn_range = rpki.resource_set.resource_range_as.parse_str(asn) + child = conf.children.get(handle=child_handle) + asn = get_or_create_asn(asn_range) + child.asn.add(asn) + +def do_prefixes(): + print 'processing', prefix_csv + for child_handle, prefix in csv_reader(prefix_csv, columns=2): + child = conf.children.get(handle=child_handle) + try: + rs = rpki.resource_set.resource_range_ipv4.parse_str(prefix) + except ValueError, err: + rs = rpki.resource_set.resource_range_ipv6.parse_str(prefix) + obj = get_or_create_prefix(rs) + obj.allocated = child + obj.save() + +def do_roas(): + print 'processing', roa_csv + for prefix, asn, group in csv_reader(roa_csv, columns=3): + try: + rs = rpki.resource_set.roa_prefix_ipv4.parse_str(prefix) + except ValueError, err: + rs = rpki.resource_set.roa_prefix_ipv6.parse_str(prefix) + + print str(rs.min()), str(rs.max()), rs.max_prefixlen + obj = get_or_create_prefix(rs.to_resource_range()) + add_roa_requests(conf, obj, [int(asn)], rs.max_prefixlen) + +do_asns() +do_prefixes() +do_roas() diff --git a/rpkid/rpki/gui/scripts/roa_check.py b/rpkid/rpki/gui/scripts/roa_check.py new file mode 100755 index 00000000..c280d935 --- /dev/null +++ b/rpkid/rpki/gui/scripts/roa_check.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# $Id$ +# +# Runs through all the published ROAs and updates the Django DB with the +# current active status of each defined ROA. +# + +import socket + +from rcynic_output_iterator import rcynic_xml_iterator, rcynic_roa +from rpki.resource_set import resource_set_ipv4, resource_set_ipv6 +from rpki.resource_set import roa_prefix_set_ipv4, roa_prefix_set_ipv6 +from rpki.resource_set import resource_range_ipv4, resource_range_ipv6 +from rpki.ipaddrs import v4addr, v6addr + +from rpki.gui.app.models import Roa + +# build up a list of all the authenticated roa's using the asn as the key +roaiter = rcynic_xml_iterator( + rcynic_root='/home/melkins/rcynic/rcynic-data/', + xml_file='/home/melkins/rcynic/rcynic.xml') + +# key is an ASN +# each element is a tuple of (resource_set_ipv4, resource_set_ipv6) +roaauth = {} + +for roa in roaiter: + if isinstance(roa, rcynic_roa): + k = roa.asID + if not roaauth.has_key(k): + v = [resource_set_ipv4(), resource_set_ipv6()] + roaauth[k] = v + else: + v = roaauth[k] + for pfx in roa.prefix_sets: + if isinstance(pfx, roa_prefix_set_ipv4): + v[0] = v[0].union(pfx.to_resource_set()) + elif isinstance(pfx, roa_prefix_set_ipv6): + v[1] = v[1].union(pfx.to_resource_set()) + +#for k, v in roaauth.iteritems(): +# print 'asn %d : prefixes %s' % (k, ' '.join(map(str,v))) + +# run through all the ROA's in the GUI's database +for roa in Roa.objects.all(): + k = int(roa.asn) + valid = False + if roaauth.has_key(k): + # ensure that all prefixes listed in the roa are present + # we convert the list of prefixes into prefix sets and use the + # resource_set class to perform set comparisons + ipv4_set = resource_set_ipv4() + ipv6_set = resource_set_ipv6() + for pfx in roa.prefix.all(): + # IP addresses are just stored as strings in the sqlite db + try: + ipv4_set.append(resource_range_ipv4(v4addr(str(pfx.lo)), v4addr(str(pfx.hi)))) + except socket.error: + ipv6_set.append(resource_range_ipv6(v6addr(str(pfx.lo)), v6addr(str(pfx.hi)))) + r = roaauth[k] + if ipv4_set.issubset(r[0]) and ipv6_set.issubset(r[1]): + valid = True + if valid: + if not roa.active: + roa.active = True + roa.save() + else: + print 'roa for asn %s is not valid' % (roa.asn, ) + if roa.active: + roa.active = False + roa.save() |