diff options
Diffstat (limited to 'portal-gui/rpkigui/myrpki')
-rw-r--r-- | portal-gui/rpkigui/myrpki/admin.py | 4 | ||||
-rw-r--r-- | portal-gui/rpkigui/myrpki/forms.py | 67 | ||||
-rw-r--r-- | portal-gui/rpkigui/myrpki/glue.py | 10 | ||||
-rw-r--r-- | portal-gui/rpkigui/myrpki/misc.py | 10 | ||||
-rw-r--r-- | portal-gui/rpkigui/myrpki/models.py | 64 | ||||
-rw-r--r-- | portal-gui/rpkigui/myrpki/urls.py | 1 | ||||
-rw-r--r-- | portal-gui/rpkigui/myrpki/views.py | 69 |
7 files changed, 173 insertions, 52 deletions
diff --git a/portal-gui/rpkigui/myrpki/admin.py b/portal-gui/rpkigui/myrpki/admin.py index 3d843bb0..b3932770 100644 --- a/portal-gui/rpkigui/myrpki/admin.py +++ b/portal-gui/rpkigui/myrpki/admin.py @@ -23,10 +23,14 @@ class RoaAdmin( admin.ModelAdmin ): class ResourceCertAdmin(admin.ModelAdmin): pass +class RoaRequestAdmin(admin.ModelAdmin): + pass + admin.site.register(models.Conf, ConfAdmin) admin.site.register(models.Child, ChildAdmin) admin.site.register(models.AddressRange, AddressRangeAdmin) admin.site.register(models.Asn, AsnAdmin) admin.site.register(models.Parent, ParentAdmin) admin.site.register(models.Roa, RoaAdmin) +admin.site.register(models.RoaRequest, RoaRequestAdmin) admin.site.register(models.ResourceCert, ResourceCertAdmin) diff --git a/portal-gui/rpkigui/myrpki/forms.py b/portal-gui/rpkigui/myrpki/forms.py index 866be7dd..d0ddc462 100644 --- a/portal-gui/rpkigui/myrpki/forms.py +++ b/portal-gui/rpkigui/myrpki/forms.py @@ -3,6 +3,7 @@ from django import forms import models from rpkigui.myrpki.misc import str_to_addr +from rpkigui.myrpki.asnset import asnset def ConfCertForm(request): class CertForm(forms.ModelForm): @@ -62,7 +63,8 @@ def RangeForm(field_type, *args, **kwargs): hi = self.cleaned_data.get('hi') if lo > hi: # should we just fix it? - raise forms.ValidationError, 'Lower bound is higher than upper.' + raise forms.ValidationError, \ + 'Lower bound is higher than upper.' return self.cleaned_data return wrapped(*args, **kwargs) @@ -92,8 +94,8 @@ def get_pk(child): def SubOrAssignForm(handle, addr, field_type, *args, **kwargs): '''Closure to select child of the specified handle.''' class Wrapper(forms.Form): - '''Form for the address view to allow the user to subdivide or assign - the block to a child.''' + '''Form for the address view to allow the user to subdivide or + assign the block to a child.''' lo = field_type(required=False, label='Lower bound') hi = field_type(required=False, label='Upper bound') child = forms.ModelChoiceField(required=False, label='Assign to child', @@ -107,7 +109,8 @@ def SubOrAssignForm(handle, addr, field_type, *args, **kwargs): lo = None if lo != None: if lo < addr.lo or lo > addr.hi: - raise forms.ValidationError, 'Value is out of range of parent.' + raise forms.ValidationError, \ + 'Value is out of range of parent.' # ensure there is no overlap with other children for c in addr.children.all(): if lo >= c.lo and lo <= c.hi: @@ -162,7 +165,8 @@ def SubOrAssignAsnForm(handle, asn, *args, **kwargs): return SubOrAssignForm(handle, asn, forms.IntegerField, *args, **kwargs) def RoaForm(handle, pk=None, initval=[], *args, **kwargs): - vals = models.AddressRange.objects.filter(from_parent__in=handle.parents.all()) + vals = models.AddressRange.objects.filter( + from_parent__in=handle.parents.all()) class Wrapped(forms.Form): asn = AsnField(initial=pk) @@ -186,9 +190,11 @@ def PrefixSplitForm(prefix, *args, **kwargs): pfx_loaddr = str_to_addr(prefix.lo) pfx_hiaddr = str_to_addr(prefix.hi) if type(loaddr) != type(pfx_hiaddr): - raise forms.ValidationError, 'Not the same IP address type as parent' + raise forms.ValidationError, \ + 'Not the same IP address type as parent' if loaddr < pfx_loaddr or loaddr > pfx_hiaddr: - raise forms.ValidationError, 'Value out of range of parent prefix' + raise forms.ValidationError, \ + 'Value out of range of parent prefix' return lo def clean_hi(self): @@ -201,9 +207,11 @@ def PrefixSplitForm(prefix, *args, **kwargs): pfx_loaddr = str_to_addr(prefix.lo) pfx_hiaddr = str_to_addr(prefix.hi) if type(hiaddr) != type(pfx_loaddr): - raise forms.ValidationError, 'Not the same IP address type as parent' + raise forms.ValidationError, \ + 'Not the same IP address type as parent' if hiaddr < pfx_loaddr or hiaddr > pfx_hiaddr: - raise forms.ValidationError, 'Value out of range of parent prefix' + raise forms.ValidationError, \ + 'Value out of range of parent prefix' return hi def clean(self): @@ -224,21 +232,35 @@ def PrefixSplitForm(prefix, *args, **kwargs): def PrefixAllocateForm(iv, child_set, *args, **kwargs): class _wrapper(forms.Form): - child = forms.ModelChoiceField(initial=iv, queryset=child_set, required=False) + child = forms.ModelChoiceField(initial=iv, queryset=child_set, + required=False) return _wrapper(*args, **kwargs) -class PrefixRoaForm(forms.Form): - asns = forms.CharField(max_length=200, required=False) +def PrefixRoaForm(prefix, *args, **kwargs): + prefix_range = prefix.as_resource_range() - def clean_asns(self): - try: - v = [int(d) for d in self.cleaned_data['asns'].split(',') if d.strip() != ''] - if any([x for x in v if x < 0]): - raise forms.ValidationError, 'must be a positive integer' - return ','.join(str(x) for x in sorted(v)) - except ValueError: - raise forms.ValidationError, 'must be a list of integers separated by commas' - return self.cleaned_data['asns'] + class _wrapper(forms.Form): + asns = forms.CharField(max_length=200, required=False, help_text='Comma-separated list of ASNs') + max_length = forms.IntegerField(required=False, + min_value=prefix_range._prefixlen(), + max_value=prefix_range.datum_type.bits) + + def clean_max_length(self): + v = self.cleaned_data.get('max_length') + if not v: + v = prefix_range._prefixlen() + return v + + def clean_asns(self): + try: + v = asnset(self.cleaned_data.get('asns')) + return ','.join(str(x) for x in sorted(v)) + except ValueError: + raise forms.ValidationError, \ + 'Must be a list of integers separated by commas.' + return self.cleaned_data['asns'] + + return _wrapper(*args, **kwargs) def PrefixDeleteForm(prefix, *args, **kwargs): class _wrapped(forms.Form): @@ -248,7 +270,8 @@ def PrefixDeleteForm(prefix, *args, **kwargs): v = self.cleaned_data.get('delete') if v: if not prefix.parent: - raise forms.ValidationError, 'Can not delete prefix received from parent' + raise forms.ValidationError, \ + 'Can not delete prefix received from parent' if prefix.allocated: raise forms.ValidationError, 'Prefix is allocated to child' if prefix.asns: diff --git a/portal-gui/rpkigui/myrpki/glue.py b/portal-gui/rpkigui/myrpki/glue.py index 1417131b..eececa7a 100644 --- a/portal-gui/rpkigui/myrpki/glue.py +++ b/portal-gui/rpkigui/myrpki/glue.py @@ -64,12 +64,10 @@ def output_prefixes(path, handle): def output_roas(path, handle): f = csv_writer(path) - for r in handle.roas.all(): - for addr in r.prefix.all(): - f.writerow([resource_range_ipv4(v4addr(str(addr.lo)), - v4addr(str(addr.hi))), - r.asn, - '%s-group-%d' % (handle.handle, r.pk)]) + for roa in handle.roas.all(): + for req in roa.from_roa_request.all(): + f.writerow([req.as_roa_prefix(), roa.asn, + '%s-group-%d' % (handle.handle, roa.pk)]) def configure_resources(handle): '''Write out the csv files and invoke the myrpki.py command line tool.''' diff --git a/portal-gui/rpkigui/myrpki/misc.py b/portal-gui/rpkigui/myrpki/misc.py index 4e0970a6..3b54107f 100644 --- a/portal-gui/rpkigui/myrpki/misc.py +++ b/portal-gui/rpkigui/myrpki/misc.py @@ -23,4 +23,14 @@ def str_to_range(lo, hi): else: return rpki.resource_set.resource_range_ipv6(x, y) +#def str_to_roa(lo, hi): +# """Convert IP address strings to a subclass of roa_prefix.""" +# x = str_to_addr(lo) +# y = str_to_addr(hi) +# assert type(x) == type(y) +# if isinstance(x, rpki.ipaddrs.v4addr): +# return rpki.resource_set.roa_prefix_ipv4(x, y) +# else: +# return rpki.resource_set.roa_prefix_ipv6(x, y) + # vim:sw=4 ts=8 expandtab diff --git a/portal-gui/rpkigui/myrpki/models.py b/portal-gui/rpkigui/myrpki/models.py index 9a6952ce..cf062b3c 100644 --- a/portal-gui/rpkigui/myrpki/models.py +++ b/portal-gui/rpkigui/myrpki/models.py @@ -4,8 +4,11 @@ import socket from django.db import models from django.contrib.auth.models import User -from rpkigui.myrpki.misc import str_to_range + +from rpkigui.myrpki.misc import str_to_range, str_to_addr + import rpki.resource_set +import rpki.exceptions class HandleField(models.CharField): def __init__(self, **kwargs): @@ -15,10 +18,6 @@ class IPAddressField(models.CharField): def __init__( self, **kwargs ): models.CharField.__init__(self, max_length=40, **kwargs) -class ASNListField(models.CharField): - def __init__( self, **kwargs ): - models.CharField.__init__(self, max_length=255, **kwargs) - class Conf(models.Model): '''This is the center of the universe, also known as a place to have a handle on a resource-holding entity. It's the <self> @@ -39,8 +38,6 @@ class AddressRange(models.Model): # child to which this resource is delegated allocated = models.ForeignKey('Child', related_name='address_range', blank=True, null=True) - # who can originate routes for this prefix - asns = ASNListField(null=True, blank=True) def __unicode__(self): if self.lo == self.hi: @@ -48,7 +45,7 @@ class AddressRange(models.Model): try: # pretty print cidr - return unicode(str_to_range(self.lo, self.hi)) + return unicode(self.as_resource_range()) except socket.error, err: print err # work around for bug when hi/lo get reversed @@ -59,6 +56,43 @@ class AddressRange(models.Model): def get_absolute_url(self): return u'/myrpki/address/%d' % (self.pk,) + def as_resource_range(self): + '''Convert to rpki.resource_set.resource_range_ip.''' + return str_to_range(self.lo, self.hi) + + def is_prefix(self): + '''Returns True if this address range can be represented as a + prefix.''' + try: + self.as_resource_range()._prefixlen() + except rpki.exceptions.MustBePrefix, err: + print err + return False + return True + +class RoaRequest(models.Model): + roa = models.ForeignKey('Roa', related_name='from_roa_request') + max_length = models.IntegerField() + prefix = models.ForeignKey('AddressRange', related_name='roa_requests') + + def __unicode__(self): + return u'roa request for asn %d on %s-%d' % (self.roa.asn, self.prefix, + self.max_length) + + def as_roa_prefix(self): + '''Convert to a rpki.resouce_set.roa_prefix subclass.''' + + r = self.prefix.as_resource_range() + if isinstance(r, rpki.resource_set.resource_set_ipv4): + return rpki.resource_set.roa_prefix_ipv4(r.min, r._prefixlen(), + self.max_length) + else: + return rpki.resource_set.roa_prefix_ipv6(r.min, r._prefixlen(), + self.max_length) + + def get_absolute_url(self): + return u'/myrpki/roa/%d' % (self.pk,) + class Asn(models.Model): '''An ASN or range thereof.''' lo = models.IntegerField(blank=False) @@ -114,8 +148,8 @@ class ResourceCert(models.Model): # resources granted from my parent asn = models.ManyToManyField(Asn, related_name='from_cert', blank=True, null=True) - address_range = models.ManyToManyField(AddressRange, related_name='from_cert', - blank=True, null=True) + address_range = models.ManyToManyField(AddressRange, + related_name='from_cert', blank=True, null=True) # unique id for this resource certificate # FIXME: URLField(verify_exists=False) doesn't seem to work - the admin @@ -134,13 +168,13 @@ class ResourceCert(models.Model): self.parent.handle) class Roa(models.Model): - '''Maps an ASN to the set of prefixes it can originate routes for. This - differs from a real ROA in that prefixes from multiple parents/resource - certs can be selected. The glue module contains code to split the ROAs - into groups by common resource certs.''' + '''Maps an ASN to the set of prefixes it can originate routes for. + This differs from a real ROA in that prefixes from multiple + parents/resource certs can be selected. The glue module contains + code to split the ROAs into groups by common resource certs.''' + conf = models.ForeignKey(Conf, related_name='roas') asn = models.IntegerField() - prefix = models.ManyToManyField(AddressRange, related_name='from_roa') active = models.BooleanField() def __unicode__(self): diff --git a/portal-gui/rpkigui/myrpki/urls.py b/portal-gui/rpkigui/myrpki/urls.py index be6277d3..4ee97984 100644 --- a/portal-gui/rpkigui/myrpki/urls.py +++ b/portal-gui/rpkigui/myrpki/urls.py @@ -23,6 +23,7 @@ urlpatterns = patterns('', (r'^address/(?P<pk>\d+)/delete$', views.prefix_delete_view), (r'^asn/(?P<pk>\d+)$', views.asn_view), (r'^asn/(?P<pk>\d+)/allocate$', views.asn_allocate_view), + (r'^roa/(?P<pk>\d+)/delete$', views.roa_request_delete_view) # (r'^roa/$', views.roa_edit ), # (r'^roa/(?P<pk>\d+)$', views.roa_edit ), ) diff --git a/portal-gui/rpkigui/myrpki/views.py b/portal-gui/rpkigui/myrpki/views.py index d78eec33..33502631 100644 --- a/portal-gui/rpkigui/myrpki/views.py +++ b/portal-gui/rpkigui/myrpki/views.py @@ -17,6 +17,7 @@ import forms import glue from asnset import asnset from rpkigui.myrpki.misc import str_to_range +from rpkigui.myrpki.asnset import asnset # For each type of object, we have a detail view, a create view and # an update view. We heavily leverage the generic views, only @@ -87,7 +88,7 @@ def unallocated_resources(handle, roa_asns, roa_prefixes, asns, prefixes): child_prefixes = [] for p in prefixes: - child_prefixes.extend(o for o in p.children.filter(allocated__isnull=True).exclude(from_roa__in=roa_prefixes)) + child_prefixes.extend(o for o in p.children.filter(allocated__isnull=True, roa_requests__isnull=True)) if child_asns or child_prefixes: x, y = unallocated_resources(handle, roa_asns, roa_prefixes, @@ -111,14 +112,14 @@ def dashboard(request): # get list of ASNs used in my ROAs roa_asns = [r.asn for r in handle.roas.all()] # get list of address ranges included in ROAs - roa_addrs = [p for r in handle.roas.all() for p in r.prefix.all()] + roa_addrs = [p.prefix for r in handle.roas.all() for p in r.from_roa_request.all()] asns=[] prefixes=[] for p in handle.parents.all(): for c in p.resources.all(): asns.extend(c.asn.filter(allocated__isnull=True).exclude(lo__in=roa_asns)) - prefixes.extend(c.address_range.filter(allocated__isnull=True).exclude(from_roa__in=roa_addrs)) + prefixes.extend(c.address_range.filter(allocated__isnull=True, roa_requests__isnull=True)) asns, prefixes = unallocated_resources(handle, roa_asns, roa_addrs, asns, prefixes) @@ -491,9 +492,18 @@ def prefix_allocate_view(request, pk): return render('myrpki/prefix_view.html', { 'form': form, 'addr': prefix, 'form': form, 'parent': parent_set }, request) +def parent_prefix(prefix): + '''Returns the top-most parent prefix for the given prefix.''' + while prefix.parent: + prefix = prefix.parent + return prefix + def common_cert(prefix, prefix_set): '''Return true if prefix is derived from the same resource cert as all the addresses in prefix_set.''' + while prefix.parent: + prefix = prefix.parent + # list of certs for the target prefix certs = prefix.from_cert.all() # all prefixes will have the same cert, so just check the first one @@ -530,10 +540,34 @@ def update_roas(handle, prefix): else: # no roa is present for this ASN, create a new one print 'creating new roa for asn %d with %s' % (asid, prefix) - roa = models.Roa.objects.create(asn=asid, conf=handle, active=False) + roa = models.Roa.objects.create(asn=asid, conf=handle, + active=False) roa.save() roa.prefix.add(prefix) +def add_roa_requests(handle, prefix, asns, max_length): + for asid in asns: + req_set = prefix.roa_requests.filter(roa__asn=asid, + max_length=max_length) + if not req_set: + # no req is present for this (ASN, prefix, max_length). + + # find all roas with prefixes from the same resource cert + roa_set = handle.roas.filter(asn=asid, + from_roa_request__prefix__from_cert__in=prefix.from_cert.all()) + if roa_set: + roa = roa_set[0] + else: + # no roa is present for this ASN, create a new one + print 'creating new roa for asn %d' % (asid,) + roa = models.Roa.objects.create(asn=asid, conf=handle, + active=False) + roa.save() + + req = models.RoaRequest.objects.create(prefix=prefix, roa=roa, + max_length=max_length) + req.save() + @handle_required def prefix_roa_view(request, pk): handle = request.session['handle'] @@ -542,15 +576,15 @@ def prefix_roa_view(request, pk): parent_set = get_parents_or_404(handle, obj) if request.method == 'POST': - form = forms.PrefixRoaForm(request.POST) + form = forms.PrefixRoaForm(obj, request.POST) if form.is_valid(): - obj.asns = form.cleaned_data['asns'] - obj.save() - update_roas(handle, obj) + asns = asnset(form.cleaned_data['asns']) + add_roa_requests(handle, obj, asns, + form.cleaned_data['max_length']) glue.configure_resources(handle) return http.HttpResponseRedirect(obj.get_absolute_url()) else: - form = forms.PrefixRoaForm(initial={ 'asns': obj.asns }) + form = forms.PrefixRoaForm(obj) return render('myrpki/prefix_view.html', { 'form': form, 'addr': obj, 'form': form, 'parent': parent_set }, request) @@ -575,6 +609,23 @@ def prefix_delete_view(request, pk): 'addr': obj, 'form': form, 'parent': parent_set }, request) @handle_required +def roa_request_delete_view(request, pk): + '''Remove a roa request from a particular prefix.''' + handle = request.session['handle'] + obj = get_object_or_404(models.RoaRequest.objects, pk=pk) + prefix = obj.prefix + # ensure this resource range belongs to a parent of the current conf + parent_set = get_parents_or_404(handle, prefix) + + roa = obj.roa + obj.delete() + if not roa.from_roa_request.all(): + print 'removing empty roa for asn %d' % (roa.asn,) + roa.delete() + + return http.HttpResponseRedirect(prefix.get_absolute_url()) + +@handle_required def asn_allocate_view(request, pk): handle = request.session['handle'] obj = get_object_or_404(models.Asn.objects, pk=pk) |