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:
@@ -141,6 +141,9 @@ register_str('policy_file', default='policy.json')
|
|||||||
register_str('policy_default_rule', default=None)
|
register_str('policy_default_rule', default=None)
|
||||||
#default max request size is 112k
|
#default max request size is 112k
|
||||||
register_int('max_request_body_size', default=114688)
|
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
|
# identity
|
||||||
register_str('default_domain_id', group='identity', default='default')
|
register_str('default_domain_id', group='identity', default='default')
|
||||||
|
@@ -84,6 +84,19 @@ class StringLengthExceeded(ValidationError):
|
|||||||
%(type)s(CHAR(%(length)d))."""
|
%(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):
|
class SecurityError(Error):
|
||||||
"""Avoids exposing details of security failures, unless in debug mode."""
|
"""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 controller
|
||||||
from keystone.common import dependency
|
from keystone.common import dependency
|
||||||
from keystone.common import logging
|
from keystone.common import logging
|
||||||
|
from keystone.common import utils
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
from keystone.openstack.common import timeutils
|
from keystone.openstack.common import timeutils
|
||||||
from keystone.token import core
|
from keystone.token import core
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -22,13 +23,13 @@ class ExternalAuthNotApplicable(Exception):
|
|||||||
@dependency.requires('catalog_api')
|
@dependency.requires('catalog_api')
|
||||||
class Auth(controller.V2Controller):
|
class Auth(controller.V2Controller):
|
||||||
def ca_cert(self, context, auth=None):
|
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()
|
data = ca_file.read()
|
||||||
ca_file.close()
|
ca_file.close()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def signing_cert(self, context, auth=None):
|
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()
|
data = cert_file.read()
|
||||||
cert_file.close()
|
cert_file.close()
|
||||||
return data
|
return data
|
||||||
@@ -134,17 +135,17 @@ class Auth(controller.V2Controller):
|
|||||||
service_catalog = Auth.format_catalog(catalog_ref)
|
service_catalog = Auth.format_catalog(catalog_ref)
|
||||||
token_data['access']['serviceCatalog'] = service_catalog
|
token_data['access']['serviceCatalog'] = service_catalog
|
||||||
|
|
||||||
if config.CONF.signing.token_format == 'UUID':
|
if CONF.signing.token_format == 'UUID':
|
||||||
token_id = uuid.uuid4().hex
|
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),
|
token_id = cms.cms_sign_token(json.dumps(token_data),
|
||||||
config.CONF.signing.certfile,
|
CONF.signing.certfile,
|
||||||
config.CONF.signing.keyfile)
|
CONF.signing.keyfile)
|
||||||
else:
|
else:
|
||||||
raise exception.UnexpectedError(
|
raise exception.UnexpectedError(
|
||||||
'Invalid value for token_format: %s.'
|
'Invalid value for token_format: %s.'
|
||||||
' Allowed values are PKI or UUID.' %
|
' Allowed values are PKI or UUID.' %
|
||||||
config.CONF.signing.token_format)
|
CONF.signing.token_format)
|
||||||
try:
|
try:
|
||||||
self.token_api.create_token(
|
self.token_api.create_token(
|
||||||
context, token_id, dict(key=token_id,
|
context, token_id, dict(key=token_id,
|
||||||
@@ -180,6 +181,9 @@ class Auth(controller.V2Controller):
|
|||||||
attribute="id", target="token")
|
attribute="id", target="token")
|
||||||
|
|
||||||
old_token = auth['token']['id']
|
old_token = auth['token']['id']
|
||||||
|
if len(old_token) > CONF.max_token_size:
|
||||||
|
raise exception.ValidationSizeError(attribute='token',
|
||||||
|
size=CONF.max_token_size)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
old_token_ref = self.token_api.get_token(context=context,
|
old_token_ref = self.token_api.get_token(context=context,
|
||||||
@@ -228,6 +232,10 @@ class Auth(controller.V2Controller):
|
|||||||
attribute='password', target='passwordCredentials')
|
attribute='password', target='passwordCredentials')
|
||||||
|
|
||||||
password = auth['passwordCredentials']['password']
|
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
|
if ("userId" not in auth['passwordCredentials'] and
|
||||||
"username" not in auth['passwordCredentials']):
|
"username" not in auth['passwordCredentials']):
|
||||||
@@ -236,7 +244,14 @@ class Auth(controller.V2Controller):
|
|||||||
target='passwordCredentials')
|
target='passwordCredentials')
|
||||||
|
|
||||||
user_id = auth['passwordCredentials'].get('userId', None)
|
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', '')
|
username = auth['passwordCredentials'].get('username', '')
|
||||||
|
if len(username) > CONF.max_param_size:
|
||||||
|
raise exception.ValidationSizeError(attribute='username',
|
||||||
|
size=CONF.max_param_size)
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
try:
|
try:
|
||||||
@@ -323,7 +338,15 @@ class Auth(controller.V2Controller):
|
|||||||
Returns a valid tenant_id if it exists, or None if not specified.
|
Returns a valid tenant_id if it exists, or None if not specified.
|
||||||
"""
|
"""
|
||||||
tenant_id = auth.get('tenantId', None)
|
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)
|
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:
|
if tenant_name:
|
||||||
try:
|
try:
|
||||||
tenant_ref = self.identity_api.get_project_by_name(
|
tenant_ref = self.identity_api.get_project_by_name(
|
||||||
@@ -410,8 +433,8 @@ class Auth(controller.V2Controller):
|
|||||||
|
|
||||||
if cms.is_ans1_token(token_id):
|
if cms.is_ans1_token(token_id):
|
||||||
data = json.loads(cms.cms_verify(cms.token_to_cms(token_id),
|
data = json.loads(cms.cms_verify(cms.token_to_cms(token_id),
|
||||||
config.CONF.signing.certfile,
|
CONF.signing.certfile,
|
||||||
config.CONF.signing.ca_certs))
|
CONF.signing.ca_certs))
|
||||||
data['access']['token']['user'] = data['access']['user']
|
data['access']['token']['user'] = data['access']['user']
|
||||||
data['access']['token']['metadata'] = data['access']['metadata']
|
data['access']['token']['metadata'] = data['access']['metadata']
|
||||||
if belongs_to:
|
if belongs_to:
|
||||||
@@ -482,8 +505,8 @@ class Auth(controller.V2Controller):
|
|||||||
data = {'revoked': tokens}
|
data = {'revoked': tokens}
|
||||||
json_data = json.dumps(data)
|
json_data = json.dumps(data)
|
||||||
signed_text = cms.cms_sign_text(json_data,
|
signed_text = cms.cms_sign_text(json_data,
|
||||||
config.CONF.signing.certfile,
|
CONF.signing.certfile,
|
||||||
config.CONF.signing.keyfile)
|
CONF.signing.keyfile)
|
||||||
|
|
||||||
return {'signed': signed_text}
|
return {'signed': signed_text}
|
||||||
|
|
||||||
|
@@ -27,8 +27,8 @@ from keystone import token
|
|||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
def _build_user_auth(token=None, username=None,
|
def _build_user_auth(token=None, user_id=None, username=None,
|
||||||
password=None, tenant_name=None):
|
password=None, tenant_id=None, tenant_name=None):
|
||||||
"""Build auth dictionary.
|
"""Build auth dictionary.
|
||||||
|
|
||||||
It will create an auth dictionary based on all the arguments
|
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'] = {}
|
auth_json['passwordCredentials'] = {}
|
||||||
if username is not None:
|
if username is not None:
|
||||||
auth_json['passwordCredentials']['username'] = username
|
auth_json['passwordCredentials']['username'] = username
|
||||||
|
if user_id is not None:
|
||||||
|
auth_json['passwordCredentials']['userId'] = user_id
|
||||||
if password is not None:
|
if password is not None:
|
||||||
auth_json['passwordCredentials']['password'] = password
|
auth_json['passwordCredentials']['password'] = password
|
||||||
if tenant_name is not None:
|
if tenant_name is not None:
|
||||||
auth_json['tenantName'] = tenant_name
|
auth_json['tenantName'] = tenant_name
|
||||||
|
if tenant_id is not None:
|
||||||
|
auth_json['tenantId'] = tenant_id
|
||||||
return auth_json
|
return auth_json
|
||||||
|
|
||||||
|
|
||||||
@@ -121,6 +125,45 @@ class AuthBadRequests(AuthTest):
|
|||||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||||
{}, {'auth': 'abcd'})
|
{}, {'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):
|
class AuthWithToken(AuthTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Reference in New Issue
Block a user