summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorJoe Gordon <jogo@cloudscaling.com>2011-12-21 20:52:13 -0500
committerJoe Gordon <jogo@cloudscaling.com>2012-01-19 15:38:59 -0800
commit64341eedf993c0738dcd42fb41fd193c184f3464 (patch)
tree4b0c7b3d2df9f90e213aa0c253dc5918b769e870 /nova/api
parent7265a71d998d5a6a4fc2a7f060178e365c882ca5 (diff)
downloadnova-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__.py45
-rw-r--r--nova/api/validator.py144
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