diff options
| author | Joe Gordon <jogo@cloudscaling.com> | 2011-12-21 20:52:13 -0500 |
|---|---|---|
| committer | Joe Gordon <jogo@cloudscaling.com> | 2012-01-19 15:38:59 -0800 |
| commit | 64341eedf993c0738dcd42fb41fd193c184f3464 (patch) | |
| tree | 4b0c7b3d2df9f90e213aa0c253dc5918b769e870 /nova/api | |
| parent | 7265a71d998d5a6a4fc2a7f060178e365c882ca5 (diff) | |
| download | nova-64341eedf993c0738dcd42fb41fd193c184f3464.tar.gz nova-64341eedf993c0738dcd42fb41fd193c184f3464.tar.xz nova-64341eedf993c0738dcd42fb41fd193c184f3464.zip | |
aws/ec2 api validation
Adds middleware to validate user-input to the aws/ec2 api.
This patch is a port to gerrit of this launchpad merge request:
https://code.launchpad.net/~u-matt-h/nova/aws-api-validation/+merge/71962
blueprint aws-api-validation
bug 813685
Code started by Matthew Hooker, fixes by Joe Gordon
Change-Id: I9346ecd5e5051cb0126c13f7c771173bc23959b9
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/ec2/__init__.py | 45 | ||||
| -rw-r--r-- | nova/api/validator.py | 144 |
2 files changed, 187 insertions, 2 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index db4fedebb..0dd97f8de 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -28,6 +28,7 @@ import webob.exc from nova.api.ec2 import apirequest from nova.api.ec2 import ec2utils from nova.api.ec2 import faults +from nova.api import validator from nova.auth import manager from nova import context from nova import exception @@ -340,12 +341,52 @@ class Authorizer(wsgi.Middleware): return any(role in context.roles for role in roles) +class Validator(wsgi.Middleware): + + def validate_ec2_id(val): + if not validator.validate_str()(val): + return False + try: + ec2utils.ec2_id_to_id(val) + except exception.InvalidEc2Id: + return False + return True + + validator.validate_ec2_id = validate_ec2_id + + validator.DEFAULT_VALIDATOR = { + 'instance_id': validator.validate_ec2_id, + 'volume_id': validator.validate_ec2_id, + 'image_id': validator.validate_ec2_id, + 'attribute': validator.validate_str(), + 'image_location': validator.validate_image_path, + 'public_ip': validator.validate_ipv4, + 'region_name': validator.validate_str(), + 'group_name': validator.validate_str(max_length=255), + 'group_description': validator.validate_str(max_length=255), + 'size': validator.validate_int(), + 'user_data': validator.validate_user_data + } + + def __init__(self, application): + super(Validator, self).__init__(application) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + if validator.validate(req.environ['ec2.request'].args, + validator.DEFAULT_VALIDATOR): + return self.application + else: + raise webob.exc.HTTPBadRequest() + + class Executor(wsgi.Application): """Execute an EC2 API request. - Executes 'ec2.request', passing 'nova.context' (both variables in WSGI - environ.) Returns an XML response, or a 400 upon failure. + Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and + 'ec2.action_args' (all variables in WSGI environ.) Returns an XML + response, or a 400 upon failure. """ @webob.dec.wsgify(RequestClass=wsgi.Request) diff --git a/nova/api/validator.py b/nova/api/validator.py new file mode 100644 index 000000000..e3179b2c5 --- /dev/null +++ b/nova/api/validator.py @@ -0,0 +1,144 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Cloudscaling, Inc. +# Author: Matthew Hooker <matt@cloudscaling.com> +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import base64 +import logging +import re +import socket + +from nova import exception + +LOG = logging.getLogger("nova.api.validator") + + +def _get_path_validator_regex(): + # rfc3986 path validator regex from + # http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + pchar = "([A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})" + path = "((/{pchar}*)*|" + path += "/({pchar}+(/{pchar}*)*)?|" + path += "{pchar}+(/{pchar}*)*|" + path += "{pchar}+(/{pchar}*)*|)" + path = path.format(pchar=pchar) + return re.compile(path) + + +VALIDATE_PATH_RE = _get_path_validator_regex() + + +def validate_str(max_length=None): + + def _do(val): + if not isinstance(val, basestring): + return False + if max_length and len(val) > max_length: + return False + return True + + return _do + + +def validate_int(max_value=None): + + def _do(val): + if not isinstance(val, int): + return False + if max_value and val > max_value: + return False + return True + + return _do + + +def validate_url_path(val): + """True if val is matched by the path component grammar in rfc3986.""" + + if not validate_str()(val): + return False + + return VALIDATE_PATH_RE.match(val).end() == len(val) + + +def validate_image_path(val): + if not validate_str()(val): + return False + + bucket_name = val.split('/')[0] + manifest_path = val[len(bucket_name) + 1:] + if not len(bucket_name) or not len(manifest_path): + return False + + if val[0] == '/': + return False + + # make sure the image path if rfc3986 compliant + # prepend '/' to make input validate + if not validate_url_path('/' + val): + return False + + return True + + +def validate_ipv4(addr): + try: + socket.inet_aton(addr) + except (socket.error, TypeError): + return False + return True + + +def validate_user_data(user_data): + """Check if the user_data is encoded properly""" + try: + user_data = base64.b64decode(user_data) + except TypeError: + return False + return True + + +def validate(args, validator): + """Validate values of args against validators in validator. + + args Dict of values to be validated. + validator A dict where the keys map to keys in args + and the values are validators. + Applies each validator to args[key] + + A validator should be a callable which accepts 1 argument and which + returns True if the argument passes validation. False otherwise. + A validator should not raise an exception to indicate validity of the + argument. + + Only validates keys which show up in both args and validator. + + returns True if validation succeeds. Otherwise False. + """ + + for key in validator: + if key not in args: + continue + + f = validator[key] + assert callable(f) + + if not f(args[key]): + msg = "%s with value %s failed validator %s" % ( + key, args[key], f.__name__) + LOG.debug(_(msg)) + return False + return True |
