diff --git a/ironic_inspector/test/unit/test_main.py b/ironic_inspector/test/unit/test_main.py index b826f3b08..2f71e40fa 100644 --- a/ironic_inspector/test/unit/test_main.py +++ b/ironic_inspector/test/unit/test_main.py @@ -164,6 +164,14 @@ class TestBasicAuthApiIntrospect(TestApiIntrospect): headers=self.headers) self.assertEqual(401, res.status_code) + def test_unauthenticated_public_api(self): + res = self.app.get('/') + self.assertEqual(200, res.status_code) + res = self.app.get('/v1') + self.assertEqual(200, res.status_code) + res = self.app.get('/v1/introspection') + self.assertEqual(401, res.status_code) + class TestApiContinue(BaseAPITest): def test_continue(self): diff --git a/ironic_inspector/utils.py b/ironic_inspector/utils.py index 8b8fb4baf..19c1fecda 100644 --- a/ironic_inspector/utils.py +++ b/ironic_inspector/utils.py @@ -16,12 +16,14 @@ import logging as pylog import futurist from ironic_lib import auth_basic +from ironic_lib import exception from keystonemiddleware import auth_token from openstack.baremetal.v1 import node from oslo_config import cfg from oslo_log import log from oslo_middleware import cors as cors_middleware import pytz +import webob from ironic_inspector.common.i18n import _ from ironic_inspector import policy @@ -169,6 +171,42 @@ class NoAvailableConductor(Error): super(NoAvailableConductor, self).__init__(msg, code=503, **kwargs) +class DeferredBasicAuthMiddleware(object): + """Middleware which sets X-Identity-Status header based on authentication + + """ + def __init__(self, app, auth_file): + self.app = app + self.auth_file = auth_file + auth_basic.validate_auth_file(auth_file) + + @webob.dec.wsgify() + def __call__(self, req): + + headers = req.headers + try: + if 'Authorization' not in headers: + auth_basic.unauthorized() + + token = auth_basic.parse_header({ + 'HTTP_AUTHORIZATION': headers.get('Authorization') + }) + username, password = auth_basic.parse_token(token) + headers.update( + auth_basic.authenticate(self.auth_file, username, password)) + headers['X-Identity-Status'] = 'Confirmed' + + except exception.Unauthorized: + headers['X-Identity-Status'] = 'Invalid' + except exception.IronicException as e: + status = '%s %s' % (int(e.code), str(e)) + resp = webob.Response(status=status) + resp.headers.update(e.headers) + return resp + + return req.get_response(self.app) + + def executor(): """Return the current futures executor.""" global _EXECUTOR @@ -193,7 +231,7 @@ def add_basic_auth_middleware(app): :param app: application. """ - app.wsgi_app = auth_basic.BasicAuthMiddleware( + app.wsgi_app = DeferredBasicAuthMiddleware( app.wsgi_app, CONF.http_basic_auth_user_file) @@ -216,11 +254,13 @@ def check_auth(request, rule=None, target=None): :param target: dict-like structure to check rule against :raises: utils.Error if access is denied """ - if CONF.auth_strategy != 'keystone': + if CONF.auth_strategy not in ('keystone', 'http_basic'): return if not request.context.is_public_api: if request.headers.get('X-Identity-Status', '').lower() == 'invalid': raise Error(_('Authentication required'), code=401) + if CONF.auth_strategy != 'keystone': + return target = {} if target is None else target if not policy.authorize(rule, target, request.context.to_policy_values()): raise Error(_("Access denied by policy"), code=403) diff --git a/releasenotes/notes/http-basic-public-api-2cf0e206bea4b34e.yaml b/releasenotes/notes/http-basic-public-api-2cf0e206bea4b34e.yaml new file mode 100644 index 000000000..2ab6b977b --- /dev/null +++ b/releasenotes/notes/http-basic-public-api-2cf0e206bea4b34e.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Using auth_strategy=http_basic incorrectly required authentication for + public paths such as / and /v1. These paths are now public.