diff --git a/keystone/common/config.py b/keystone/common/config.py index 6c2445a1a8..5770d1a73f 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -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', diff --git a/keystone/exception.py b/keystone/exception.py index ea238d39e9..17812a346a 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -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.") diff --git a/keystone/server/common.py b/keystone/server/common.py index 7bc5958ed8..15fac8bfee 100644 --- a/keystone/server/common.py +++ b/keystone/server/common.py @@ -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.')) diff --git a/keystone/tests/unit/test_auth.py b/keystone/tests/unit/test_auth.py index d34054a4a8..a3fd90732e 100644 --- a/keystone/tests/unit/test_auth.py +++ b/keystone/tests/unit/test_auth.py @@ -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, diff --git a/keystone/tests/unit/test_exception.py b/keystone/tests/unit/test_exception.py index 9641428ed2..25ca2c095f 100644 --- a/keystone/tests/unit/test_exception.py +++ b/keystone/tests/unit/test_exception.py @@ -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 diff --git a/keystone/tests/unit/test_wsgi.py b/keystone/tests/unit/test_wsgi.py index e295210bf9..d1ad6e42b7 100644 --- a/keystone/tests/unit/test_wsgi.py +++ b/keystone/tests/unit/test_wsgi.py @@ -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) diff --git a/releasenotes/notes/insecure_reponse-2a168230709bc8e7.yaml b/releasenotes/notes/insecure_reponse-2a168230709bc8e7.yaml new file mode 100644 index 0000000000..00f3b4a4cf --- /dev/null +++ b/releasenotes/notes/insecure_reponse-2a168230709bc8e7.yaml @@ -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. +