diff --git a/config-generator.conf b/config-generator.conf index ac71fb450..054f5f6ae 100644 --- a/config-generator.conf +++ b/config-generator.conf @@ -7,3 +7,4 @@ namespace = ironic_inspector.plugins.discovery namespace = keystonemiddleware.auth_token namespace = oslo.db namespace = oslo.log +namespace = oslo.middleware.cors diff --git a/example.conf b/example.conf index 57fac5e94..c1291b93b 100644 --- a/example.conf +++ b/example.conf @@ -166,6 +166,66 @@ #fatal_deprecations = false +[cors] + +# +# From oslo.middleware.cors +# + +# Indicate whether this resource may be shared with the domain +# received in the requests "origin" header. (list value) +#allowed_origin = + +# Indicate that the actual request can include user credentials +# (boolean value) +#allow_credentials = true + +# Indicate which headers are safe to expose to the API. Defaults to +# HTTP Simple Headers. (list value) +#expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma + +# Maximum cache age of CORS preflight requests. (integer value) +#max_age = 3600 + +# Indicate which methods can be used during the actual request. (list +# value) +#allow_methods = GET,POST,PUT,HEAD,PATCH,DELETE,OPTIONS + +# Indicate which header field names may be used during the actual +# request. (list value) +#allow_headers = X-Auth-Token,X-OpenStack-Ironic-Inspector-API-Minimum-Version,X-OpenStack-Ironic-Inspector-API-Maximum-Version,X-OpenStack-Ironic-Inspector-API-Version + + +[cors.subdomain] + +# +# From oslo.middleware.cors +# + +# Indicate whether this resource may be shared with the domain +# received in the requests "origin" header. (list value) +#allowed_origin = + +# Indicate that the actual request can include user credentials +# (boolean value) +#allow_credentials = true + +# Indicate which headers are safe to expose to the API. Defaults to +# HTTP Simple Headers. (list value) +#expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma + +# Maximum cache age of CORS preflight requests. (integer value) +#max_age = 3600 + +# Indicate which methods can be used during the actual request. (list +# value) +#allow_methods = GET,POST,PUT,HEAD,PATCH,DELETE,OPTIONS + +# Indicate which header field names may be used during the actual +# request. (list value) +#allow_headers = X-Auth-Token,X-OpenStack-Ironic-Inspector-API-Minimum-Version,X-OpenStack-Ironic-Inspector-API-Maximum-Version,X-OpenStack-Ironic-Inspector-API-Version + + [database] # diff --git a/ironic_inspector/conf.py b/ironic_inspector/conf.py index be2b6f5d4..3860e5f20 100644 --- a/ironic_inspector/conf.py +++ b/ironic_inspector/conf.py @@ -12,8 +12,13 @@ # limitations under the License. from oslo_config import cfg +from oslo_middleware import cors +MIN_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Minimum-Version' +MAX_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Maximum-Version' +VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Version' + VALID_ADD_PORTS_VALUES = ('all', 'active', 'pxe') VALID_KEEP_PORTS_VALUES = ('all', 'present', 'added') VALID_STORE_DATA_VALUES = ('none', 'swift') @@ -215,3 +220,22 @@ def list_opts(): ('processing', PROCESSING_OPTS), ('discoverd', DISCOVERD_OPTS), ] + + +def set_config_defaults(): + """This method updates all configuration default values.""" + set_cors_middleware_defaults() + + +def set_cors_middleware_defaults(): + """Update default configuration options for oslo.middleware.""" + # TODO(krotscheck): Update with https://review.openstack.org/#/c/285368/ + cfg.set_defaults( + cors.CORS_OPTS, + allow_headers=['X-Auth-Token', + MIN_VERSION_HEADER, + MAX_VERSION_HEADER, + VERSION_HEADER], + allow_methods=['GET', 'POST', 'PUT', 'HEAD', + 'PATCH', 'DELETE', 'OPTIONS'] + ) diff --git a/ironic_inspector/main.py b/ironic_inspector/main.py index b124df1a9..1ac67bbd7 100644 --- a/ironic_inspector/main.py +++ b/ironic_inspector/main.py @@ -48,9 +48,6 @@ LOG = utils.getProcessingLogger(__name__) MINIMUM_API_VERSION = (1, 0) CURRENT_API_VERSION = (1, 3) -_MIN_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Minimum-Version' -_MAX_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Maximum-Version' -_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Version' _LOGGING_EXCLUDED_KEYS = ('logs',) @@ -89,7 +86,7 @@ def convert_exceptions(func): @app.before_request def check_api_version(): - requested = flask.request.headers.get(_VERSION_HEADER, + requested = flask.request.headers.get(conf.VERSION_HEADER, _DEFAULT_API_VERSION) try: requested = tuple(int(x) for x in requested.split('.')) @@ -108,8 +105,8 @@ def check_api_version(): @app.after_request def add_version_headers(res): - res.headers[_MIN_VERSION_HEADER] = '%s.%s' % MINIMUM_API_VERSION - res.headers[_MAX_VERSION_HEADER] = '%s.%s' % CURRENT_API_VERSION + res.headers[conf.MIN_VERSION_HEADER] = '%s.%s' % MINIMUM_API_VERSION + res.headers[conf.MAX_VERSION_HEADER] = '%s.%s' % CURRENT_API_VERSION return res @@ -383,6 +380,8 @@ class Service(object): LOG.info(_LI('Introspection data will be stored in Swift in the ' 'container %s'), CONF.swift.container) + utils.add_cors_middleware(app) + db.init() try: diff --git a/ironic_inspector/test/test_main.py b/ironic_inspector/test/test_main.py index 958bbbf9d..d400ae900 100644 --- a/ironic_inspector/test/test_main.py +++ b/ironic_inspector/test/test_main.py @@ -20,6 +20,7 @@ import mock from oslo_utils import uuidutils from ironic_inspector.common import ironic as ir_utils +from ironic_inspector import conf from ironic_inspector import db from ironic_inspector import firewall from ironic_inspector import introspect @@ -352,9 +353,9 @@ class TestApiMisc(BaseAPITest): class TestApiVersions(BaseAPITest): def _check_version_present(self, res): self.assertEqual('%d.%d' % main.MINIMUM_API_VERSION, - res.headers.get(main._MIN_VERSION_HEADER)) + res.headers.get(conf.MIN_VERSION_HEADER)) self.assertEqual('%d.%d' % main.CURRENT_API_VERSION, - res.headers.get(main._MAX_VERSION_HEADER)) + res.headers.get(conf.MAX_VERSION_HEADER)) def test_root_endpoint(self): res = self.app.get("/") @@ -420,14 +421,14 @@ class TestApiVersions(BaseAPITest): self.app.post('/v1/introspection/foobar')) def test_request_correct_version(self): - headers = {main._VERSION_HEADER: + headers = {conf.VERSION_HEADER: main._format_version(main.CURRENT_API_VERSION)} self._check_version_present(self.app.get('/', headers=headers)) def test_request_unsupported_version(self): bad_version = (main.CURRENT_API_VERSION[0], main.CURRENT_API_VERSION[1] + 1) - headers = {main._VERSION_HEADER: + headers = {conf.VERSION_HEADER: main._format_version(bad_version)} res = self.app.get('/', headers=headers) self._check_version_present(res) diff --git a/ironic_inspector/utils.py b/ironic_inspector/utils.py index 679f48315..d46bef9ad 100644 --- a/ironic_inspector/utils.py +++ b/ironic_inspector/utils.py @@ -18,6 +18,7 @@ import futurist from keystonemiddleware import auth_token from oslo_config import cfg from oslo_log import log +from oslo_middleware import cors as cors_middleware import six from ironic_inspector.common.i18n import _, _LE @@ -159,6 +160,17 @@ def add_auth_middleware(app): app.wsgi_app = auth_token.AuthProtocol(app.wsgi_app, auth_conf) +def add_cors_middleware(app): + """Create a CORS wrapper + + Attach ironic-inspector-specific defaults that must be included + in all CORS responses. + + :param app: application + """ + app.wsgi_app = cors_middleware.CORS(app.wsgi_app, CONF) + + def check_auth(request): """Check authentication on request. diff --git a/releasenotes/notes/cors-5f345c65da7f5c99.yaml b/releasenotes/notes/cors-5f345c65da7f5c99.yaml new file mode 100644 index 000000000..ec66fef1f --- /dev/null +++ b/releasenotes/notes/cors-5f345c65da7f5c99.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Added CORS support middleware to Ironic Inspector, allowing a deployer + to optionally configure rules under which a javascript client may + break the single-origin policy and access the API directly. + + OpenStack CrossProject Spec: + http://specs.openstack.org/openstack/openstack-specs/specs/cors-support.html + Oslo_Middleware Docs: + http://docs.openstack.org/developer/oslo.middleware/cors.html + OpenStack Cloud Admin Guide: + http://docs.openstack.org/admin-guide-cloud/cross_project_cors.html diff --git a/requirements.txt b/requirements.txt index fb076cf0d..ae3600211 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ oslo.config>=3.7.0 # Apache-2.0 oslo.db>=4.1.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.log>=1.14.0 # Apache-2.0 +oslo.middleware>=3.0.0 # Apache-2.0 oslo.rootwrap>=2.0.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 six>=1.9.0 # MIT diff --git a/setup.cfg b/setup.cfg index 583b22865..72b60d0be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,6 +58,8 @@ oslo.config.opts = ironic_inspector.common.ironic = ironic_inspector.common.ironic:list_opts ironic_inspector.common.swift = ironic_inspector.common.swift:list_opts ironic_inspector.plugins.discovery = ironic_inspector.plugins.discovery:list_opts +oslo.config.opts.defaults = + ironic_inspector = ironic_inspector.conf:set_config_defaults [compile_catalog] directory = ironic_inspector/locale