Config option for insecure responses

oslo.log's "debug" option was co-opted to also indicate that the
responses should include more information. A separate config
option should be used instead so that deployers don't mistakenly
expose themselves to security issues.

The debug option still is used for what it does in oslo.log and
how it works on all other projects -- if you're not using a log
config file it sets the base logger to debug.

SecurityImpact

Change-Id: Icf8dd2f0b88abc89092d487bbcefb525960c4ec6
Closes-Bug: 1479523
This commit is contained in:
Brant Knudson 2015-07-29 16:29:42 -05:00
parent 40453931a9
commit 2afad4dc30
7 changed files with 41 additions and 25 deletions

View File

@ -110,6 +110,12 @@ FILE_OPTIONS = {
'original request, even if it was removed by an SSL '
'terminating proxy. Typical value is '
'"HTTP_X_FORWARDED_PROTO".'),
cfg.BoolOpt('insecure_debug', default=False,
help='If set to true the server will return information '
'in the response that may allow an unauthenticated '
'or authenticated user to get more information than '
'normal, such as why authentication failed. This may '
'be useful for debugging but is insecure.'),
],
'identity': [
cfg.StrOpt('default_domain_id', default='default',

View File

@ -161,13 +161,15 @@ class PKITokenExpected(Error):
class SecurityError(Error):
"""Avoids exposing details of security failures, unless in debug mode."""
"""Avoids exposing details of security failures, unless in insecure_debug
mode.
"""
amendment = _('(Disable debug mode to suppress these details.)')
amendment = _('(Disable insecure_debug mode to suppress these details.)')
def _build_message(self, message, **kwargs):
"""Only returns detailed messages in debug mode."""
if message and CONF.debug:
"""Only returns detailed messages in insecure_debug mode."""
if message and CONF.insecure_debug:
if isinstance(message, six.string_types):
# Only do replacement if message is string. The message is
# sometimes a different exception or bytes, which would raise
@ -381,7 +383,7 @@ class Conflict(Error):
class UnexpectedError(SecurityError):
"""Avoids exposing details of failures, unless in debug mode."""
"""Avoids exposing details of failures, unless in insecure_debug mode."""
message_format = _("An unexpected error prevented the server "
"from fulfilling your request.")

View File

@ -38,9 +38,9 @@ def configure(version=None, config_files=None,
pre_setup_logging_fn()
config.setup_logging()
if CONF.debug:
if CONF.insecure_debug:
LOG.warn(_LW(
'debug is enabled so responses may include sensitive '
'insecure_debug is enabled so responses may include sensitive '
'information.'))

View File

@ -286,7 +286,7 @@ class AuthWithToken(AuthTest):
def test_auth_scoped_token_bad_project_with_debug(self):
"""Authenticating with an invalid project fails."""
# Bug 1379952 reports poor user feedback, even in debug mode,
# Bug 1379952 reports poor user feedback, even in insecure_debug mode,
# when the user accidentally passes a project name as an ID.
# This test intentionally does exactly that.
body_dict = _build_user_auth(
@ -294,8 +294,8 @@ class AuthWithToken(AuthTest):
password=self.user_foo['password'],
tenant_id=self.tenant_bar['name'])
# with debug enabled, this produces a friendly exception.
self.config_fixture.config(debug=True)
# with insecure_debug enabled, this produces a friendly exception.
self.config_fixture.config(debug=True, insecure_debug=True)
e = self.assertRaises(
exception.Unauthorized,
self.controller.authenticate,
@ -308,7 +308,7 @@ class AuthWithToken(AuthTest):
def test_auth_scoped_token_bad_project_without_debug(self):
"""Authenticating with an invalid project fails."""
# Bug 1379952 reports poor user feedback, even in debug mode,
# Bug 1379952 reports poor user feedback, even in insecure_debug mode,
# when the user accidentally passes a project name as an ID.
# This test intentionally does exactly that.
body_dict = _build_user_auth(
@ -316,8 +316,8 @@ class AuthWithToken(AuthTest):
password=self.user_foo['password'],
tenant_id=self.tenant_bar['name'])
# with debug disabled, authentication failure details are suppressed.
self.config_fixture.config(debug=False)
# with insecure_debug disabled (the default), authentication failure
# details are suppressed.
e = self.assertRaises(
exception.Unauthorized,
self.controller.authenticate,

View File

@ -123,7 +123,7 @@ class UnexpectedExceptionTestCase(ExceptionTestCase):
self.assertNotIn(self.exc_str, six.text_type(e))
def test_unexpected_error_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
e = exception.UnexpectedError(exception=self.exc_str)
self.assertIn(self.exc_str, six.text_type(e))
@ -135,7 +135,7 @@ class UnexpectedExceptionTestCase(ExceptionTestCase):
six.text_type(e))
def test_unexpected_error_subclass_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
subclass = self.SubClassExc
e = subclass(debug_info=self.exc_str)
@ -151,14 +151,14 @@ class UnexpectedExceptionTestCase(ExceptionTestCase):
six.text_type(e))
def test_unexpected_error_custom_message_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
e = exception.UnexpectedError(self.exc_str)
self.assertEqual(
'%s %s' % (self.exc_str, exception.SecurityError.amendment),
six.text_type(e))
def test_unexpected_error_custom_message_exception_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
orig_e = exception.NotFound(target=uuid.uuid4().hex)
e = exception.UnexpectedError(orig_e)
self.assertEqual(
@ -167,7 +167,7 @@ class UnexpectedExceptionTestCase(ExceptionTestCase):
six.text_type(e))
def test_unexpected_error_custom_message_binary_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
binary_msg = b'something'
e = exception.UnexpectedError(binary_msg)
self.assertEqual(
@ -192,7 +192,7 @@ class SecurityErrorTestCase(ExceptionTestCase):
self.assertNotIn(risky_info, six.text_type(e))
def test_unauthorized_exposure_in_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
risky_info = uuid.uuid4().hex
e = exception.Unauthorized(message=risky_info)
@ -208,7 +208,7 @@ class SecurityErrorTestCase(ExceptionTestCase):
self.assertNotIn(risky_info, six.text_type(e))
def test_forbidden_exposure_in_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
risky_info = uuid.uuid4().hex
e = exception.Forbidden(message=risky_info)
@ -232,7 +232,7 @@ class SecurityErrorTestCase(ExceptionTestCase):
self.assertNotIn(exception.SecurityError.amendment, six.text_type(e))
def test_forbidden_action_exposure_in_debug(self):
self.config_fixture.config(debug=True)
self.config_fixture.config(debug=True, insecure_debug=True)
risky_info = uuid.uuid4().hex
action = uuid.uuid4().hex

View File

@ -294,12 +294,13 @@ class MiddlewareTest(BaseWSGITest):
self.assertEqual(exception.UnexpectedError.code, resp.status_int)
return resp
# Exception data should not be in the message when debug is False
self.config_fixture.config(debug=False)
# Exception data should not be in the message when insecure_debug is
# False
self.config_fixture.config(debug=False, insecure_debug=False)
self.assertNotIn(exception_str, do_request().body)
# Exception data should be in the message when debug is True
self.config_fixture.config(debug=True)
# Exception data should be in the message when insecure_debug is True
self.config_fixture.config(debug=True, insecure_debug=True)
self.assertIn(exception_str, do_request().body)

View File

@ -0,0 +1,7 @@
---
upgrade:
- A new config option, ``insecure_debug``, is added to control whether debug
information is returned to clients. This used to be controlled by the
``debug`` option. If you'd like to return extra information to clients
set the value to ``true``. This extra information may help an attacker.