diff options
author | Michael Elkins <melkins@tislabs.com> | 2012-11-14 23:36:21 +0000 |
---|---|---|
committer | Michael Elkins <melkins@tislabs.com> | 2012-11-14 23:36:21 +0000 |
commit | 01290083a9bb24091295e87d5f3a414cb8186422 (patch) | |
tree | fea8e536f823269021fd3e4173ffd6cf04f1b1d6 | |
parent | d77f7e5696aa3727cc4c9c4ab18e78d575e12b92 (diff) |
merge with /trunk
svn path=/branches/tk274/; revision=4866
-rw-r--r-- | doc/doc.RPKI.CA.UI | 16 | ||||
-rw-r--r-- | doc/doc.RPKI.CA.UI.GUI | 52 | ||||
-rw-r--r-- | doc/doc.RPKI.Installation | 8 | ||||
-rw-r--r-- | doc/manual.pdf | bin | 485324 -> 483113 bytes | |||
-rw-r--r-- | rpkid/portal-gui/rpki.wsgi.in | 16 | ||||
-rw-r--r-- | rpkid/portal-gui/scripts/rpkigui-rcynic.py | 23 | ||||
-rw-r--r-- | rpkid/portal-gui/settings.py.in | 3 | ||||
-rw-r--r-- | rpkid/rpki/gui/app/forms.py | 55 | ||||
-rw-r--r-- | rpkid/rpki/gui/app/templates/app/child_add_resource_form.html | 16 | ||||
-rw-r--r-- | rpkid/rpki/gui/app/templates/app/object_detail.html | 76 | ||||
-rw-r--r-- | rpkid/rpki/gui/app/views.py | 121 |
11 files changed, 210 insertions, 176 deletions
diff --git a/doc/doc.RPKI.CA.UI b/doc/doc.RPKI.CA.UI index 0643f9d0..ac180a39 100644 --- a/doc/doc.RPKI.CA.UI +++ b/doc/doc.RPKI.CA.UI @@ -85,20 +85,22 @@ the same: home underneath your parent in the publication tree. 6. Each of your parents sends (...) back the response XML file generated by the "configure_child" command. - 7. You feed the response message you just got into the IRBE using either - rpkic or the GUI rpkic using the. This registers the parent's information - in your database, handles BPKI cross-certification of your parent., and + 7. You feed the response message you just got into the IRBE using rpkic's + "configure_parent" command. This registers the parent's information in + your database, handles BPKI cross-certification of your parent., and processes the repository offer or referral to generate a publication request message. 8. You send (...) the publication request message to the repository. The contact_info element in the request message should (in theory) provide some clue as to where you should send this. - 9. The repository operator processes your request. This registers your - information, including BPKI cross-certification, and generates a response - message containing the repository's BPKI trust anchor and service URL. + 9. The repository operator processes your request using rpkic's + "configure_publication_client" command. This registers your information, + including BPKI cross-certification, and generates a response message + containing the repository's BPKI trust anchor and service URL. 10. Repository operator sends (...) the publication confirmation message back to you. - 11. You process the publication confirmation message. + 11. You process the publication confirmation message using rpkic's + "configure_repository" command. At this point you should, in theory, have established relationships, exchanged trust anchors, and obtained service URLs from all of your parents and diff --git a/doc/doc.RPKI.CA.UI.GUI b/doc/doc.RPKI.CA.UI.GUI index d52e0a6b..109890d6 100644 --- a/doc/doc.RPKI.CA.UI.GUI +++ b/doc/doc.RPKI.CA.UI.GUI @@ -42,6 +42,9 @@ Now bring your database up to date with the current release: $ rpki-manage migrate app +From this point on you will just need to run the latter command every time you +upgrade. + ***** New Installation ***** **** Create the initial tables **** @@ -122,55 +125,6 @@ In addition, your rcynic script should also have after the rcynic run. -****** GUI Installation to Work With rootd ****** - -Some of the commands depend on whether your are upgrading your existing -database, or starting a new installation from scratch. - -[All users] First step is you will need to install Django South. For FreeBSD -this is /usr/ports/databases/py-south. - -The code is currently in the tk316 branch, so in order to play, you will need -to check it out: - - $ svn co https://subvert-rpki.hactrn.net/branches/tk316 - $ cd tk316 - $ ./configure - $ make - $ make install - -[Upgrading users] You will need to edit /usr/local/etc/rpki/settings.py and add -'south' to the INSTALLED_APPS list. See /usr/local/etc/rpki/settings.py.new for -an example (we don't automatically overwrite settings.py). - -[All users] Run syncdb: - - $ django-admin syncdb --pythonpath=/usr/local/etc/rpki --settings=settings - -Verify that Django South is installed: - - $ django-admin migrate --list --pythonpath=/usr/local/etc/rpki -- - settings=settings - -[Upgrading Users] Since you already have an existing db, you need to fake doing -the initial migration step: - - $ django-admin migrate app 0001 --fake --pythonpath=/usr/local/etc/rpki -- - settings=settings - -[All users] Perform the database migrations new to this release: - - $ django-admin migrate app --pythonpath=/usr/local/etc/rpki -- - settings=settings - -[All users] Restart apache so that the web portal picks up the newly installed -code: - - $ apachectl restart - -Now head back to the gui. Click on the 'refresh' link when viewing the altCA -dashboard, and it should now pick up the resources from the root cert. - ****** Using the GUI ****** ****** GUI Examples ****** diff --git a/doc/doc.RPKI.Installation b/doc/doc.RPKI.Installation index 3fd3f8cf..20f42c2c 100644 --- a/doc/doc.RPKI.Installation +++ b/doc/doc.RPKI.Installation @@ -119,12 +119,12 @@ Packages you will need: o FreeBSD: /usr/ports/www/mod_wsgi3 -* http://south.aeracode.org/ Django South. This tool is used to ease the pain - of changes to the web portal database schema. +* http://south.aeracode.org/ Django South 0.7.6 or later. This tool is used to + ease the pain of changes to the web portal database schema. o FreeBSD: /usr/ports/databases/py-south - o Ubuntu: python-django-south - o CentOS/RHEL: Django-south + o Ubuntu: do not use the python-django-south 0.7.3 package in 12.04 LTS as it + is known not to work ***** Configure and build ***** diff --git a/doc/manual.pdf b/doc/manual.pdf Binary files differindex a01fdcaf..89bbebd5 100644 --- a/doc/manual.pdf +++ b/doc/manual.pdf diff --git a/rpkid/portal-gui/rpki.wsgi.in b/rpkid/portal-gui/rpki.wsgi.in index eb49fe05..3d198349 100644 --- a/rpkid/portal-gui/rpki.wsgi.in +++ b/rpkid/portal-gui/rpki.wsgi.in @@ -25,16 +25,22 @@ import sys old_sys_path = list(sys.path) +def walk_error(e): + 'This function is invoked when os.walk() needs to report an error' + print >>sys.stderr, 'error reading %s: %s' % (e.filename, e) + # When used with virtualenv, specify the location of the python modules to use if VIRTUAL_ENV: - import site + d = os.path.join(VIRTUAL_ENV, 'lib') # locate the site-packages directory - for (dp, dn, fn) in os.walk(VIRTUAL_ENV + '/lib'): + for dp, dn, fn in os.walk(os.path.join(VIRTUAL_ENV, 'lib'), + onerror=walk_error): if 'site-packages' in dn: - site.addsitedir(os.path.join(dp, 'site-packages')) + d = os.path.join(dp, 'site-packages') + import site + site.addsitedir(d) break -import sys os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' sys.path.insert(1, '@PYTHONPATH@') @@ -48,6 +54,8 @@ for elt in list(sys.path): sys.path.remove(elt) sys.path[:0] = new_sys_path +print >>sys.stderr, sys.path + import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() diff --git a/rpkid/portal-gui/scripts/rpkigui-rcynic.py b/rpkid/portal-gui/scripts/rpkigui-rcynic.py index 439a091a..b7f6c661 100644 --- a/rpkid/portal-gui/scripts/rpkigui-rcynic.py +++ b/rpkid/portal-gui/scripts/rpkigui-rcynic.py @@ -98,7 +98,6 @@ def rcynic_roa(roa, obj): def rcynic_gbr(gbr, obj): vcard = vobject.readOne(gbr.vcard) - logger.debug(vcard.prettyPrint()) obj.full_name = vcard.fn.value if hasattr(vcard, 'fn') else None obj.email_address = vcard.email.value if hasattr(vcard, 'email') else None obj.telephone = vcard.tel.value if hasattr(vcard, 'tel') else None @@ -123,21 +122,21 @@ def save_statuses(inst, statuses): # if this object is in our interest set, update with the current validation # status if inst.uri in uris: - x, y, z, q = uris[inst.repo.uri] + x, y, z, q = uris[inst.uri] uris[inst.uri] = x, y, valid, inst @transaction.commit_on_success def process_cache(root, xml_file): dispatch = { - 'rcynic_certificate': rcynic_cert, - 'rcynic_roa': rcynic_roa, - 'rcynic_ghostbuster': rcynic_gbr + 'rcynic_certificate': rcynic_cert, + 'rcynic_roa': rcynic_roa, + 'rcynic_ghostbuster': rcynic_gbr } model_class = { - 'rcynic_certificate': models.Cert, - 'rcynic_roa': models.ROA, - 'rcynic_ghostbuster': models.Ghostbuster + 'rcynic_certificate': models.Cert, + 'rcynic_roa': models.ROA, + 'rcynic_ghostbuster': models.Ghostbuster } last_uri = None @@ -176,7 +175,8 @@ def process_cache(root, xml_file): try: obj = vs.obj # causes object to be lazily loaded except rpki.POW._der.DerError, e: - logger.warning('Caught %s while processing %s: %s' % (type(e), vs.filename, e)) + logger.warning('Caught %s while processing %s: %s' % ( + type(e), vs.filename, e)) continue inst.not_before = obj.notBefore.to_sql() @@ -283,9 +283,9 @@ def fetch_published_objects(): qs = models.RepositoryObject.objects.filter(uri=pdu.uri) if qs: # get the current validity state - valid = obj.statuses.filter(status=object_accepted).exists() + valid = qs[0].statuses.filter(status=object_accepted).exists() uris[pdu.uri] = (pdu.self_handle, valid, False, None) - logger.debug('adding ' + ', '.join(uris[pdu.uri])) + logger.debug('adding ' + pdu.uri) else: # this object is not in the cache. it was either published # recently, or disappared previously. if it disappeared @@ -394,6 +394,7 @@ if __name__ == '__main__': start = time.time() process_labels(options.logfile) object_accepted = LABEL_CACHE['object_accepted'] + fetch_published_objects() process_cache(options.root, options.logfile) notify_invalid() diff --git a/rpkid/portal-gui/settings.py.in b/rpkid/portal-gui/settings.py.in index 10705ef8..46f6ca60 100644 --- a/rpkid/portal-gui/settings.py.in +++ b/rpkid/portal-gui/settings.py.in @@ -86,12 +86,13 @@ INSTALLED_APPS = ( #'django.contrib.admin', #'django.contrib.admindocs', 'django.contrib.contenttypes', + 'django.contrib.formtools', 'django.contrib.sessions', 'rpki.irdb', 'rpki.gui.app', 'rpki.gui.cacheview', 'rpki.gui.routeview', - 'south' + 'south', ) TEMPLATE_CONTEXT_PROCESSORS = ( diff --git a/rpkid/rpki/gui/app/forms.py b/rpkid/rpki/gui/app/forms.py index 7f7be61d..2246a03b 100644 --- a/rpkid/rpki/gui/app/forms.py +++ b/rpkid/rpki/gui/app/forms.py @@ -278,14 +278,13 @@ class ROARequestConfirm(forms.Form): return self.cleaned_data -def AddASNForm(qs): +def AddASNForm(child): """ - Generate a form class which only allows specification of ASNs contained - within the specified queryset. `qs` should be a QuerySet of - irdb.models.ChildASN. + Returns a forms.Form subclass which verifies that the entered ASN range + does not overlap with a previous allocation to the specified child, and + that the ASN range is within the range allocated to the parent. """ - class _wrapped(forms.Form): asns = forms.CharField(label='ASNs', help_text='single ASN or range') @@ -294,21 +293,30 @@ def AddASNForm(qs): r = resource_range_as.parse_str(self.cleaned_data.get('asns')) except: raise forms.ValidationError('invalid AS or range') - if not qs.filter(min__lte=r.min, max__gte=r.max).exists(): + + if not models.ResourceRangeAS.objects.filter( + cert__conf=child.issuer, + min__lte=r.min, + max__gte=r.max).exists(): raise forms.ValidationError('AS or range is not delegated to you') + + # determine if the entered range overlaps with any AS already + # allocated to this child + if child.asns.filter(end_as__gte=r.min, start_as__lte=r.max).exists(): + raise forms.ValidationError( + 'Overlap with previous allocation to this child') + return str(r) return _wrapped - -def AddNetForm(qsv4, qsv6): +def AddNetForm(child): """ - Generate a form class which only allows specification of prefixes contained - within the specified queryset. `qs` should be a QuerySet of - irdb.models.ChildNet. + Returns a forms.Form subclass which validates that the entered address + range is within the resources allocated to the parent, and does not overlap + with what is already allocated to the specified child. """ - class _wrapped(forms.Form): address_range = forms.CharField(help_text='CIDR or range') @@ -317,19 +325,32 @@ def AddNetForm(qsv4, qsv6): try: if ':' in address_range: r = resource_range_ipv6.parse_str(address_range) - if not qsv6.filter(prefix_min__lte=r.min, prefix_max__gte=r.max).exists(): - raise forms.ValidationError('IP address range is not delegated to you') + qs = models.ResourceRangeAddressV6 + version = 'IPv6' else: r = resource_range_ipv4.parse_str(address_range) - if not qsv4.filter(prefix_min__lte=r.min, prefix_max__gte=r.max).exists(): - raise forms.ValidationError('IP address range is not delegated to you') + qs = models.ResourceRangeAddressV4 + version = 'IPv4' except BadIPResource: raise forms.ValidationError('invalid IP address range') + + if not qs.objects.filter(cert__conf=child.issuer, + prefix_min__lte=r.min, + prefix_max__gte=r.max).exists(): + raise forms.ValidationError('IP address range is not delegated to you') + + # determine if the entered range overlaps with any prefix + # already allocated to this child + for n in child.address_ranges.filter(version=version): + rng = n.as_resource_range() + if r.max >= rng.min and r.min <= rng.max: + raise forms.ValidationError( + 'Overlap with previous allocation to this child') + return str(r) return _wrapped - def ChildForm(instance): """ Form for editing a Child model. diff --git a/rpkid/rpki/gui/app/templates/app/child_add_resource_form.html b/rpkid/rpki/gui/app/templates/app/child_add_resource_form.html deleted file mode 100644 index 98789191..00000000 --- a/rpkid/rpki/gui/app/templates/app/child_add_resource_form.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "app/app_base.html" %} - -{% block content %} -<div class='page-header'> - <h1>Add Resource: {{ object.handle }}</h1> -</div> - -<form method='POST' action='{{ request.get_full_path }}'> - {% csrf_token %} - {% include "app/bootstrap_form.html" %} - <div class='actions'> - <input class='btn primary' type='submit' value='Save'> - <a class='btn' href='{{ object.get_absolute_url }}'>Cancel</a> - </div> -</form> -{% endblock content %} diff --git a/rpkid/rpki/gui/app/templates/app/object_detail.html b/rpkid/rpki/gui/app/templates/app/object_detail.html index 6a93f644..3927bfd5 100644 --- a/rpkid/rpki/gui/app/templates/app/object_detail.html +++ b/rpkid/rpki/gui/app/templates/app/object_detail.html @@ -10,24 +10,68 @@ {{ object }} {% endblock object_detail %} -{% if confirm_delete %} -<div class='alert-message block-message warning'> - <p><strong>Please confirm</strong> that you would like to delete this object. - <div class='alert-actions'> - <form method='POST' action='{{ request.get_full_path }}'> - {% csrf_token %} - <input class='btn danger' type='submit' value='Delete'/> +{% if form %} + <h2>{{ form_label }}<h2> + + {% if is_preview %} + <h3>Preview</h3> + <table> + {% for field in form %} + <tr> + <th>{{ field.label }}:</th> + <td>{{ field.data }}</td> + </tr> + {% endfor %} + </table> + + <!-- <p>Security hash: {{ hash_value }}</p> --> + + <!-- this form is used by django.contrib.formtools --> + <form action="" method="post">{% csrf_token %} + {% for field in form %}{{ field.as_hidden }} + {% endfor %} + <input type="hidden" name="{{ stage_field }}" value="2" /> + <input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" /> + <div class='actions'> + <input class='btn primary' type="submit" value="Save" /></p> + </div> + </form> + + <h3>Or edit again</h3> + + {% endif %} + + <!-- this form is used by django.contrib.formtools --> + <form method='POST' action=''> + {% csrf_token %} + {% include "app/bootstrap_form.html" %} + <div class='actions'> + <input class='btn primary' type='submit' value='Preview'> <a class='btn' href='{{ object.get_absolute_url }}'>Cancel</a> - </form> - </div> -</div> + </div> + <input type="hidden" name="{{ stage_field }}" value="1" /> + </form> + {% else %} -<div class='actions'> - {% if can_edit %} - <a class='btn' href='{{ object.get_absolute_url }}/edit'>Edit</a> + {% if confirm_delete %} + <div class='alert-message block-message warning'> + <p><strong>Please confirm</strong> that you would like to delete this object. + <div class='alert-actions'> + <form method='POST' action='{{ request.get_full_path }}'> + {% csrf_token %} + <input class='btn danger' type='submit' value='Delete'/> + <a class='btn' href='{{ object.get_absolute_url }}'>Cancel</a> + </form> + </div> + </div> + {% else %} + <div class='actions'> + {% if can_edit %} + <a class='btn' href='{{ object.get_absolute_url }}/edit'>Edit</a> + {% endif %} + <a class='btn danger' href='{{ object.get_absolute_url }}/delete' title='Permanently delete this object'>Delete</a> + {% block actions %}{% endblock actions %} + </div> {% endif %} - <a class='btn danger' href='{{ object.get_absolute_url }}/delete' title='Permanently delete this object'>Delete</a> - {% block actions %}{% endblock actions %} -</div> {% endif %} {% endblock content %} diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py index 02f145b3..02830dd0 100644 --- a/rpkid/rpki/gui/app/views.py +++ b/rpkid/rpki/gui/app/views.py @@ -31,15 +31,14 @@ from django.utils.http import urlquote from django.template import RequestContext from django import http from django.views.generic.list_detail import object_list, object_detail -from django.views.generic.create_update import delete_object from django.core.urlresolvers import reverse from django.contrib.auth.models import User +from django.contrib.formtools.preview import FormPreview from rpki.irdb import Zookeeper, ChildASN, ChildNet from rpki.gui.app import models, forms, glue, range_list from rpki.resource_set import (resource_range_as, resource_range_ipv4, resource_range_ipv6, roa_prefix_ipv4) -from rpki.exceptions import BadIPResource from rpki import sundial from rpki.gui.cacheview.models import ROAPrefixV4, ROAPrefixV6, ROA @@ -339,61 +338,81 @@ def child_list(request): 'create_label': 'Import'}) -@handle_required -def child_add_resource(request, pk, form_class, unused_list, callback, - template_name='app/child_add_resource_form.html'): - conf = request.session['handle'] - child = models.Child.objects.get(issuer=conf, pk=pk) - log = request.META['wsgi.errors'] - if request.method == 'POST': - form = form_class(request.POST, request.FILES) - if form.is_valid(): - callback(child, form) - Zookeeper(handle=conf.handle, logstream=log).run_rpkid_now() - return http.HttpResponseRedirect(child.get_absolute_url()) - else: - form = form_class() - - return render(request, template_name, - {'object': child, 'form': form, 'unused': unused_list}) - - -def add_asn_callback(child, form): - asns = form.cleaned_data.get('asns') - r = resource_range_as.parse_str(asns) - child.asns.create(start_as=r.min, end_as=r.max) - - -def child_add_asn(request, pk): - conf = request.session['handle'] - get_object_or_404(models.Child, issuer=conf, pk=pk) - qs = models.ResourceRangeAS.objects.filter(cert__conf=conf) - return child_add_resource(request, pk, forms.AddASNForm(qs), [], - add_asn_callback) - +class ChildAddResourcePreview(FormPreview): + """ + Base class for handling preview of AS/Prefix additions to a child. + Subclasses implement the 'done' method to perform actual work on IRDB. -def add_address_callback(child, form): - address_range = form.cleaned_data.get('address_range') - if ':' in address_range: - r = resource_range_ipv6.parse_str(address_range) - version = 'IPv6' - else: - r = resource_range_ipv4.parse_str(address_range) - version = 'IPv4' - child.address_ranges.create(start_ip=str(r.min), end_ip=str(r.max), - version=version) + """ + form_template = 'app/child_detail.html' + preview_template = 'app/child_detail.html' + + def __init__(self, *args, **kwargs): + """ + The docstring for FormPreview says we should not redefine this method, but + I don't see how we can set extra information in this class otherwise. + + """ + self.child = kwargs.pop('child') + self.logstream = kwargs.pop('logstream') + super(ChildAddResourcePreview, self).__init__(*args, **kwargs) + + def get_context(self, *args, **kwargs): + """" + Override the superclass method to add context variables needed by the + form template. + + """ + d = super(ChildAddResourcePreview, self).get_context(*args, **kwargs) + d['object'] = self.child + d['form_label'] = 'Add Resource' + return d + + def process_preview(self, request, form, context): + # set a boolean flag so that the template knows this is a preview + context['is_preview'] = True + + +class ChildAddPrefixPreview(ChildAddResourcePreview): + def done(self, request, cleaned_data): + address_range = cleaned_data.get('address_range') + if ':' in address_range: + r = resource_range_ipv6.parse_str(address_range) + version = 'IPv6' + else: + r = resource_range_ipv4.parse_str(address_range) + version = 'IPv4' + self.child.address_ranges.create(start_ip=str(r.min), end_ip=str(r.max), + version=version) + Zookeeper(handle=self.child.issuer.handle, logstream=self.logstream).run_rpkid_now() + return http.HttpResponseRedirect(self.child.get_absolute_url()) +@handle_required def child_add_address(request, pk): + logstream = request.META['wsgi.errors'] conf = request.session['handle'] - get_object_or_404(models.Child, issuer=conf, pk=pk) - qsv4 = models.ResourceRangeAddressV4.objects.filter(cert__conf=conf) - qsv6 = models.ResourceRangeAddressV6.objects.filter(cert__conf=conf) - return child_add_resource(request, pk, - forms.AddNetForm(qsv4, qsv6), - [], - callback=add_address_callback) + child = get_object_or_404(models.Child, issuer=conf, pk=pk) + form = forms.AddNetForm(child) + preview = ChildAddPrefixPreview(form, child=child, logstream=logstream) + return preview(request) + +class ChildAddASNPreview(ChildAddResourcePreview): + def done(self, request, cleaned_data): + asns = cleaned_data.get('asns') + r = resource_range_as.parse_str(asns) + self.child.asns.create(start_as=r.min, end_as=r.max) + Zookeeper(handle=self.child.issuer.handle, logstream=self.logstream).run_rpkid_now() + return http.HttpResponseRedirect(self.child.get_absolute_url()) +@handle_required +def child_add_asn(request, pk): + logstream = request.META['wsgi.errors'] + conf = request.session['handle'] + child = get_object_or_404(models.Child, issuer=conf, pk=pk) + form = forms.AddASNForm(child) + preview = ChildAddASNPreview(form, child=child, logstream=logstream) + return preview(request) @handle_required def child_view(request, pk): |