Added CORS support to Ironic Inspector
This adds the 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 DocImpact: Add link to CORS configuration in admin cloud guide. Change-Id: I467d4e14b27f1d4808786d431aff66808c707a99
This commit is contained in:
parent
aea60cdc4a
commit
19fe16fd42
@ -7,3 +7,4 @@ namespace = ironic_inspector.plugins.discovery
|
|||||||
namespace = keystonemiddleware.auth_token
|
namespace = keystonemiddleware.auth_token
|
||||||
namespace = oslo.db
|
namespace = oslo.db
|
||||||
namespace = oslo.log
|
namespace = oslo.log
|
||||||
|
namespace = oslo.middleware.cors
|
||||||
|
60
example.conf
60
example.conf
@ -166,6 +166,66 @@
|
|||||||
#fatal_deprecations = false
|
#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 = <None>
|
||||||
|
|
||||||
|
# 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 = <None>
|
||||||
|
|
||||||
|
# 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]
|
[database]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -12,8 +12,13 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
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_ADD_PORTS_VALUES = ('all', 'active', 'pxe')
|
||||||
VALID_KEEP_PORTS_VALUES = ('all', 'present', 'added')
|
VALID_KEEP_PORTS_VALUES = ('all', 'present', 'added')
|
||||||
VALID_STORE_DATA_VALUES = ('none', 'swift')
|
VALID_STORE_DATA_VALUES = ('none', 'swift')
|
||||||
@ -215,3 +220,22 @@ def list_opts():
|
|||||||
('processing', PROCESSING_OPTS),
|
('processing', PROCESSING_OPTS),
|
||||||
('discoverd', DISCOVERD_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']
|
||||||
|
)
|
||||||
|
@ -48,9 +48,6 @@ LOG = utils.getProcessingLogger(__name__)
|
|||||||
|
|
||||||
MINIMUM_API_VERSION = (1, 0)
|
MINIMUM_API_VERSION = (1, 0)
|
||||||
CURRENT_API_VERSION = (1, 3)
|
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',)
|
_LOGGING_EXCLUDED_KEYS = ('logs',)
|
||||||
|
|
||||||
|
|
||||||
@ -89,7 +86,7 @@ def convert_exceptions(func):
|
|||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def check_api_version():
|
def check_api_version():
|
||||||
requested = flask.request.headers.get(_VERSION_HEADER,
|
requested = flask.request.headers.get(conf.VERSION_HEADER,
|
||||||
_DEFAULT_API_VERSION)
|
_DEFAULT_API_VERSION)
|
||||||
try:
|
try:
|
||||||
requested = tuple(int(x) for x in requested.split('.'))
|
requested = tuple(int(x) for x in requested.split('.'))
|
||||||
@ -108,8 +105,8 @@ def check_api_version():
|
|||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def add_version_headers(res):
|
def add_version_headers(res):
|
||||||
res.headers[_MIN_VERSION_HEADER] = '%s.%s' % MINIMUM_API_VERSION
|
res.headers[conf.MIN_VERSION_HEADER] = '%s.%s' % MINIMUM_API_VERSION
|
||||||
res.headers[_MAX_VERSION_HEADER] = '%s.%s' % CURRENT_API_VERSION
|
res.headers[conf.MAX_VERSION_HEADER] = '%s.%s' % CURRENT_API_VERSION
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@ -383,6 +380,8 @@ class Service(object):
|
|||||||
LOG.info(_LI('Introspection data will be stored in Swift in the '
|
LOG.info(_LI('Introspection data will be stored in Swift in the '
|
||||||
'container %s'), CONF.swift.container)
|
'container %s'), CONF.swift.container)
|
||||||
|
|
||||||
|
utils.add_cors_middleware(app)
|
||||||
|
|
||||||
db.init()
|
db.init()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -20,6 +20,7 @@ import mock
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from ironic_inspector.common import ironic as ir_utils
|
from ironic_inspector.common import ironic as ir_utils
|
||||||
|
from ironic_inspector import conf
|
||||||
from ironic_inspector import db
|
from ironic_inspector import db
|
||||||
from ironic_inspector import firewall
|
from ironic_inspector import firewall
|
||||||
from ironic_inspector import introspect
|
from ironic_inspector import introspect
|
||||||
@ -352,9 +353,9 @@ class TestApiMisc(BaseAPITest):
|
|||||||
class TestApiVersions(BaseAPITest):
|
class TestApiVersions(BaseAPITest):
|
||||||
def _check_version_present(self, res):
|
def _check_version_present(self, res):
|
||||||
self.assertEqual('%d.%d' % main.MINIMUM_API_VERSION,
|
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,
|
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):
|
def test_root_endpoint(self):
|
||||||
res = self.app.get("/")
|
res = self.app.get("/")
|
||||||
@ -420,14 +421,14 @@ class TestApiVersions(BaseAPITest):
|
|||||||
self.app.post('/v1/introspection/foobar'))
|
self.app.post('/v1/introspection/foobar'))
|
||||||
|
|
||||||
def test_request_correct_version(self):
|
def test_request_correct_version(self):
|
||||||
headers = {main._VERSION_HEADER:
|
headers = {conf.VERSION_HEADER:
|
||||||
main._format_version(main.CURRENT_API_VERSION)}
|
main._format_version(main.CURRENT_API_VERSION)}
|
||||||
self._check_version_present(self.app.get('/', headers=headers))
|
self._check_version_present(self.app.get('/', headers=headers))
|
||||||
|
|
||||||
def test_request_unsupported_version(self):
|
def test_request_unsupported_version(self):
|
||||||
bad_version = (main.CURRENT_API_VERSION[0],
|
bad_version = (main.CURRENT_API_VERSION[0],
|
||||||
main.CURRENT_API_VERSION[1] + 1)
|
main.CURRENT_API_VERSION[1] + 1)
|
||||||
headers = {main._VERSION_HEADER:
|
headers = {conf.VERSION_HEADER:
|
||||||
main._format_version(bad_version)}
|
main._format_version(bad_version)}
|
||||||
res = self.app.get('/', headers=headers)
|
res = self.app.get('/', headers=headers)
|
||||||
self._check_version_present(res)
|
self._check_version_present(res)
|
||||||
|
@ -18,6 +18,7 @@ import futurist
|
|||||||
from keystonemiddleware import auth_token
|
from keystonemiddleware import auth_token
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
from oslo_middleware import cors as cors_middleware
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ironic_inspector.common.i18n import _, _LE
|
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)
|
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):
|
def check_auth(request):
|
||||||
"""Check authentication on request.
|
"""Check authentication on request.
|
||||||
|
|
||||||
|
13
releasenotes/notes/cors-5f345c65da7f5c99.yaml
Normal file
13
releasenotes/notes/cors-5f345c65da7f5c99.yaml
Normal file
@ -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
|
@ -19,6 +19,7 @@ oslo.config>=3.7.0 # Apache-2.0
|
|||||||
oslo.db>=4.1.0 # Apache-2.0
|
oslo.db>=4.1.0 # Apache-2.0
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
oslo.i18n>=2.1.0 # Apache-2.0
|
||||||
oslo.log>=1.14.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.rootwrap>=2.0.0 # Apache-2.0
|
||||||
oslo.utils>=3.5.0 # Apache-2.0
|
oslo.utils>=3.5.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
|
@ -58,6 +58,8 @@ oslo.config.opts =
|
|||||||
ironic_inspector.common.ironic = ironic_inspector.common.ironic:list_opts
|
ironic_inspector.common.ironic = ironic_inspector.common.ironic:list_opts
|
||||||
ironic_inspector.common.swift = ironic_inspector.common.swift:list_opts
|
ironic_inspector.common.swift = ironic_inspector.common.swift:list_opts
|
||||||
ironic_inspector.plugins.discovery = ironic_inspector.plugins.discovery: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]
|
[compile_catalog]
|
||||||
directory = ironic_inspector/locale
|
directory = ironic_inspector/locale
|
||||||
|
Loading…
Reference in New Issue
Block a user