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:
parent
b3d667ab3b
commit
8ec247bf61
@ -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')
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user