Add size validations to token controller.

Updates token controller so that it explicitly checks the max
size of userId, username, tenantId, tenantname, token, and password
before continuing with a request.

Previously, when used with the SQL keystone backend an unauthenticated
user could send in *really* large requests which would ultimately log
large SQL exceptions and could thus fill up keystone logs on the
disk.

Change-Id: Ie7e3a958829f99f080e66582bdf558cded70248c
This commit is contained in:
Dan Prince 2013-01-10 13:25:18 -05:00
parent b3d667ab3b
commit 8ec247bf61
4 changed files with 96 additions and 14 deletions

View File

@ -141,6 +141,9 @@ register_str('policy_file', default='policy.json')
register_str('policy_default_rule', default=None)
#default max request size is 112k
register_int('max_request_body_size', default=114688)
register_int('max_param_size', default=64)
# we allow tokens to be a bit larger to accomidate PKI
register_int('max_token_size', default=8192)
# identity
register_str('default_domain_id', group='identity', default='default')

View File

@ -84,6 +84,19 @@ class StringLengthExceeded(ValidationError):
%(type)s(CHAR(%(length)d))."""
class ValidationSizeError(Error):
"""Request attribute %(attribute)s must be less than or equal to %(size)i.
The server could not comply with the request because the attribute
size is invalid (too large).
The client is assumed to be in error.
"""
code = 400
title = 'Bad Request'
class SecurityError(Error):
"""Avoids exposing details of security failures, unless in debug mode."""

View File

@ -5,12 +5,13 @@ from keystone.common import cms
from keystone.common import controller
from keystone.common import dependency
from keystone.common import logging
from keystone.common import utils
from keystone import config
from keystone import exception
from keystone.openstack.common import timeutils
from keystone.token import core
CONF = config.CONF
LOG = logging.getLogger(__name__)
@ -22,13 +23,13 @@ class ExternalAuthNotApplicable(Exception):
@dependency.requires('catalog_api')
class Auth(controller.V2Controller):
def ca_cert(self, context, auth=None):
ca_file = open(config.CONF.signing.ca_certs, 'r')
ca_file = open(CONF.signing.ca_certs, 'r')
data = ca_file.read()
ca_file.close()
return data
def signing_cert(self, context, auth=None):
cert_file = open(config.CONF.signing.certfile, 'r')
cert_file = open(CONF.signing.certfile, 'r')
data = cert_file.read()
cert_file.close()
return data
@ -134,17 +135,17 @@ class Auth(controller.V2Controller):
service_catalog = Auth.format_catalog(catalog_ref)
token_data['access']['serviceCatalog'] = service_catalog
if config.CONF.signing.token_format == 'UUID':
if CONF.signing.token_format == 'UUID':
token_id = uuid.uuid4().hex
elif config.CONF.signing.token_format == 'PKI':
elif CONF.signing.token_format == 'PKI':
token_id = cms.cms_sign_token(json.dumps(token_data),
config.CONF.signing.certfile,
config.CONF.signing.keyfile)
CONF.signing.certfile,
CONF.signing.keyfile)
else:
raise exception.UnexpectedError(
'Invalid value for token_format: %s.'
' Allowed values are PKI or UUID.' %
config.CONF.signing.token_format)
CONF.signing.token_format)
try:
self.token_api.create_token(
context, token_id, dict(key=token_id,
@ -180,6 +181,9 @@ class Auth(controller.V2Controller):
attribute="id", target="token")
old_token = auth['token']['id']
if len(old_token) > CONF.max_token_size:
raise exception.ValidationSizeError(attribute='token',
size=CONF.max_token_size)
try:
old_token_ref = self.token_api.get_token(context=context,
@ -228,6 +232,10 @@ class Auth(controller.V2Controller):
attribute='password', target='passwordCredentials')
password = auth['passwordCredentials']['password']
max_pw_size = utils.MAX_PASSWORD_LENGTH
if password and len(password) > max_pw_size:
raise exception.ValidationSizeError(attribute='password',
size=max_pw_size)
if ("userId" not in auth['passwordCredentials'] and
"username" not in auth['passwordCredentials']):
@ -236,7 +244,14 @@ class Auth(controller.V2Controller):
target='passwordCredentials')
user_id = auth['passwordCredentials'].get('userId', None)
if user_id and len(user_id) > CONF.max_param_size:
raise exception.ValidationSizeError(attribute='userId',
size=CONF.max_param_size)
username = auth['passwordCredentials'].get('username', '')
if len(username) > CONF.max_param_size:
raise exception.ValidationSizeError(attribute='username',
size=CONF.max_param_size)
if username:
try:
@ -323,7 +338,15 @@ class Auth(controller.V2Controller):
Returns a valid tenant_id if it exists, or None if not specified.
"""
tenant_id = auth.get('tenantId', None)
if tenant_id and len(tenant_id) > CONF.max_param_size:
raise exception.ValidationSizeError(attribute='tenantId',
size=CONF.max_param_size)
tenant_name = auth.get('tenantName', None)
if tenant_name and len(tenant_name) > CONF.max_param_size:
raise exception.ValidationSizeError(attribute='tenantName',
size=CONF.max_param_size)
if tenant_name:
try:
tenant_ref = self.identity_api.get_project_by_name(
@ -410,8 +433,8 @@ class Auth(controller.V2Controller):
if cms.is_ans1_token(token_id):
data = json.loads(cms.cms_verify(cms.token_to_cms(token_id),
config.CONF.signing.certfile,
config.CONF.signing.ca_certs))
CONF.signing.certfile,
CONF.signing.ca_certs))
data['access']['token']['user'] = data['access']['user']
data['access']['token']['metadata'] = data['access']['metadata']
if belongs_to:
@ -482,8 +505,8 @@ class Auth(controller.V2Controller):
data = {'revoked': tokens}
json_data = json.dumps(data)
signed_text = cms.cms_sign_text(json_data,
config.CONF.signing.certfile,
config.CONF.signing.keyfile)
CONF.signing.certfile,
CONF.signing.keyfile)
return {'signed': signed_text}

View File

@ -27,8 +27,8 @@ from keystone import token
CONF = config.CONF
def _build_user_auth(token=None, username=None,
password=None, tenant_name=None):
def _build_user_auth(token=None, user_id=None, username=None,
password=None, tenant_id=None, tenant_name=None):
"""Build auth dictionary.
It will create an auth dictionary based on all the arguments
@ -41,10 +41,14 @@ def _build_user_auth(token=None, username=None,
auth_json['passwordCredentials'] = {}
if username is not None:
auth_json['passwordCredentials']['username'] = username
if user_id is not None:
auth_json['passwordCredentials']['userId'] = user_id
if password is not None:
auth_json['passwordCredentials']['password'] = password
if tenant_name is not None:
auth_json['tenantName'] = tenant_name
if tenant_id is not None:
auth_json['tenantId'] = tenant_id
return auth_json
@ -121,6 +125,45 @@ class AuthBadRequests(AuthTest):
self.assertRaises(exception.ValidationError, self.api.authenticate,
{}, {'auth': 'abcd'})
def test_authenticate_user_id_too_large(self):
"""Verify sending large 'userId' raises the right exception."""
body_dict = _build_user_auth(user_id='0' * 65, username='FOO',
password='foo2')
self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
{}, body_dict)
def test_authenticate_username_too_large(self):
"""Verify sending large 'username' raises the right exception."""
body_dict = _build_user_auth(username='0' * 65, password='foo2')
self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
{}, body_dict)
def test_authenticate_tenant_id_too_large(self):
"""Verify sending large 'tenantId' raises the right exception."""
body_dict = _build_user_auth(username='FOO', password='foo2',
tenant_id='0' * 65)
self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
{}, body_dict)
def test_authenticate_tenant_name_too_large(self):
"""Verify sending large 'tenantName' raises the right exception."""
body_dict = _build_user_auth(username='FOO', password='foo2',
tenant_name='0' * 65)
self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
{}, body_dict)
def test_authenticate_token_too_large(self):
"""Verify sending large 'token' raises the right exception."""
body_dict = _build_user_auth(token={'id': '0' * 8193})
self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
{}, body_dict)
def test_authenticate_password_too_large(self):
"""Verify sending large 'password' raises the right exception."""
body_dict = _build_user_auth(username='FOO', password='0' * 8193)
self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
{}, body_dict)
class AuthWithToken(AuthTest):
def setUp(self):