Merge "Add support for the audit middleware"
This commit is contained in:
commit
88303cc56c
doc/source
etc/ironic
ironic
releasenotes/notes
110
doc/source/deploy/api-audit-support.rst
Normal file
110
doc/source/deploy/api-audit-support.rst
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
.. _api-audit-support:
|
||||||
|
|
||||||
|
API Audit Logging
|
||||||
|
=================
|
||||||
|
|
||||||
|
Audit middleware supports delivery of CADF audit events via Oslo messaging
|
||||||
|
notifier capability. Based on `notification_driver` configuration, audit events
|
||||||
|
can be routed to messaging infrastructure (notification_driver = messagingv2)
|
||||||
|
or can be routed to a log file (notification_driver = log).
|
||||||
|
|
||||||
|
Audit middleware creates two events per REST API interaction. First event has
|
||||||
|
information extracted from request data and the second one has request outcome
|
||||||
|
(response).
|
||||||
|
|
||||||
|
Enabling API Audit Logging
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Audit middleware is available as part of `keystonemiddleware` (>= 1.6) library.
|
||||||
|
For infomation regarding how audit middleware functions refer `here.
|
||||||
|
<http://docs.openstack.org/developer/keystonemiddleware/audit.html>`_
|
||||||
|
|
||||||
|
Auditing can be enabled for the Bare Metal service by making the following changes
|
||||||
|
to ``/etc/ironic/ironic.conf``.
|
||||||
|
|
||||||
|
#. To enable audit logging of API requests::
|
||||||
|
|
||||||
|
[audit]
|
||||||
|
...
|
||||||
|
enabled=true
|
||||||
|
|
||||||
|
#. To customize auditing API requests, the audit middleware requires the audit_map_file setting
|
||||||
|
to be defined. Update the value of configuration setting 'audit_map_file' to set its
|
||||||
|
location. Audit map file configuration options for the Bare Metal service are included
|
||||||
|
in the etc/ironic/ironic_api_audit_map.conf.sample file. To understand CADF format
|
||||||
|
specified in ironic_api_audit_map.conf file refer to `CADF Format.
|
||||||
|
<http://www.dmtf.org/sites/default/files/standards/documents/DSP2038_1.0.0.pdf>`_::
|
||||||
|
|
||||||
|
[audit]
|
||||||
|
...
|
||||||
|
audit_map_file=/etc/ironic/ironic_api_audit_map.conf
|
||||||
|
|
||||||
|
#. Comma separated list of Ironic REST API HTTP methods to be ignored during audit.
|
||||||
|
For example: GET,POST. It is used only when API audit is enabled.
|
||||||
|
|
||||||
|
[audit]
|
||||||
|
...
|
||||||
|
ignore_req_list=GET,POST
|
||||||
|
|
||||||
|
Sample Audit Event
|
||||||
|
==================
|
||||||
|
|
||||||
|
Following is the sample of audit event for ironic node list request.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"event_type":"audit.http.request",
|
||||||
|
"timestamp":"2016-06-15 06:04:30.904397",
|
||||||
|
"payload":{
|
||||||
|
"typeURI":"http://schemas.dmtf.org/cloud/audit/1.0/event",
|
||||||
|
"eventTime":"2016-06-15T06:04:30.903071+0000",
|
||||||
|
"target":{
|
||||||
|
"id":"ironic",
|
||||||
|
"typeURI":"unknown",
|
||||||
|
"addresses":[
|
||||||
|
{
|
||||||
|
"url":"http://{ironic_admin_host}:6385",
|
||||||
|
"name":"admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url":"http://{ironic_internal_host}:6385",
|
||||||
|
"name":"private"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url":"http://{ironic_public_host}:6385",
|
||||||
|
"name":"public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name":"ironic"
|
||||||
|
},
|
||||||
|
"observer":{
|
||||||
|
"id":"target"
|
||||||
|
},
|
||||||
|
"tags":[
|
||||||
|
"correlation_id?value=685f1abb-620e-5d5d-b74a-b4135fb32373"
|
||||||
|
],
|
||||||
|
"eventType":"activity",
|
||||||
|
"initiator":{
|
||||||
|
"typeURI":"service/security/account/user",
|
||||||
|
"name":"admin",
|
||||||
|
"credential":{
|
||||||
|
"token":"***",
|
||||||
|
"identity_status":"Confirmed"
|
||||||
|
},
|
||||||
|
"host":{
|
||||||
|
"agent":"python-ironicclient",
|
||||||
|
"address":"10.1.200.129"
|
||||||
|
},
|
||||||
|
"project_id":"d8f52dd7d9e1475dbbf3ba47a4a83313",
|
||||||
|
"id":"8c1a948bad3948929aa5d5b50627a174"
|
||||||
|
},
|
||||||
|
"action":"read",
|
||||||
|
"outcome":"pending",
|
||||||
|
"id":"061b7aa7-5879-5225-a331-c002cf23cb6c",
|
||||||
|
"requestPath":"/v1/nodes/?associated=True"
|
||||||
|
},
|
||||||
|
"priority":"INFO",
|
||||||
|
"publisher_id":"ironic-api",
|
||||||
|
"message_id":"2f61ebaa-2d3e-4023-afba-f9fca6f21fc2"
|
||||||
|
}
|
@ -42,6 +42,7 @@ Administrator's Guide
|
|||||||
deploy/inspection
|
deploy/inspection
|
||||||
deploy/security
|
deploy/security
|
||||||
deploy/adoption
|
deploy/adoption
|
||||||
|
deploy/api-audit-support
|
||||||
deploy/troubleshooting
|
deploy/troubleshooting
|
||||||
Release Notes <http://docs.openstack.org/releasenotes/ironic/>
|
Release Notes <http://docs.openstack.org/releasenotes/ironic/>
|
||||||
Dashboard (horizon) plugin <http://docs.openstack.org/developer/ironic-ui/>
|
Dashboard (horizon) plugin <http://docs.openstack.org/developer/ironic-ui/>
|
||||||
|
@ -487,6 +487,27 @@
|
|||||||
#enable_ssl_api = false
|
#enable_ssl_api = false
|
||||||
|
|
||||||
|
|
||||||
|
[audit]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From ironic
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable auditing of API requests (for ironic-api service).
|
||||||
|
# (boolean value)
|
||||||
|
#enabled = false
|
||||||
|
|
||||||
|
# Path to audit map file for ironic-api service. Used only
|
||||||
|
# when API audit is enabled. (string value)
|
||||||
|
#audit_map_file = /etc/ironic/ironic_api_audit_map.conf
|
||||||
|
|
||||||
|
# Comma separated list of Ironic REST API HTTP methods to be
|
||||||
|
# ignored during audit. For example: auditing will not be done
|
||||||
|
# on any GET or POST requests if this is set to "GET,POST". It
|
||||||
|
# is used only when API audit is enabled. (string value)
|
||||||
|
#ignore_req_list = <None>
|
||||||
|
|
||||||
|
|
||||||
[cimc]
|
[cimc]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
29
etc/ironic/ironic_api_audit_map.conf.sample
Normal file
29
etc/ironic/ironic_api_audit_map.conf.sample
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
# default target endpoint type
|
||||||
|
# should match the endpoint type defined in service catalog
|
||||||
|
target_endpoint_type = None
|
||||||
|
|
||||||
|
# possible end path of API requests
|
||||||
|
# path of api requests for CADF target typeURI
|
||||||
|
# Just need to include top resource path to identify class
|
||||||
|
# of resources. Ex: Log audit event for API requests
|
||||||
|
# path containing "nodes" keyword and node uuid.
|
||||||
|
[path_keywords]
|
||||||
|
nodes = node
|
||||||
|
drivers = driver
|
||||||
|
chassis = chassis
|
||||||
|
ports = port
|
||||||
|
states = state
|
||||||
|
power = None
|
||||||
|
provision = None
|
||||||
|
maintenance = None
|
||||||
|
validate = None
|
||||||
|
boot_device = None
|
||||||
|
supported = None
|
||||||
|
console = None
|
||||||
|
vendor_passthrus = vendor_passthru
|
||||||
|
|
||||||
|
|
||||||
|
# map endpoint type defined in service catalog to CADF typeURI
|
||||||
|
[service_endpoints]
|
||||||
|
baremetal = service/compute/baremetal
|
@ -15,6 +15,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import keystonemiddleware.audit as audit_middleware
|
||||||
|
from keystonemiddleware.audit import PycadfAuditApiConfigError
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import oslo_middleware.cors as cors_middleware
|
import oslo_middleware.cors as cors_middleware
|
||||||
import pecan
|
import pecan
|
||||||
@ -24,6 +26,7 @@ from ironic.api import config
|
|||||||
from ironic.api.controllers.base import Version
|
from ironic.api.controllers.base import Version
|
||||||
from ironic.api import hooks
|
from ironic.api import hooks
|
||||||
from ironic.api import middleware
|
from ironic.api import middleware
|
||||||
|
from ironic.common import exception
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
|
|
||||||
|
|
||||||
@ -60,6 +63,19 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
|||||||
wrap_app=middleware.ParsableErrorMiddleware,
|
wrap_app=middleware.ParsableErrorMiddleware,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if CONF.audit.enabled:
|
||||||
|
try:
|
||||||
|
app = audit_middleware.AuditMiddleware(
|
||||||
|
app,
|
||||||
|
audit_map_file=CONF.audit.audit_map_file,
|
||||||
|
ignore_req_list=CONF.audit.ignore_req_list
|
||||||
|
)
|
||||||
|
except (EnvironmentError, OSError, PycadfAuditApiConfigError) as e:
|
||||||
|
raise exception.InputFileError(
|
||||||
|
file_name=CONF.audit.audit_map_file,
|
||||||
|
reason=e
|
||||||
|
)
|
||||||
|
|
||||||
if pecan_config.app.enable_acl:
|
if pecan_config.app.enable_acl:
|
||||||
app = acl.install(app, cfg.CONF, pecan_config.app.acl_public_routes)
|
app = acl.install(app, cfg.CONF, pecan_config.app.acl_public_routes)
|
||||||
|
|
||||||
|
@ -255,6 +255,10 @@ class InstanceNotFound(NotFound):
|
|||||||
_msg_fmt = _("Instance %(instance)s could not be found.")
|
_msg_fmt = _("Instance %(instance)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class InputFileError(IronicException):
|
||||||
|
_msg_fmt = _("Error with file %(file_name)s. Reason: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
class NodeNotFound(NotFound):
|
class NodeNotFound(NotFound):
|
||||||
_msg_fmt = _("Node %(node)s could not be found.")
|
_msg_fmt = _("Node %(node)s could not be found.")
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from oslo_config import cfg
|
|||||||
|
|
||||||
from ironic.conf import agent
|
from ironic.conf import agent
|
||||||
from ironic.conf import api
|
from ironic.conf import api
|
||||||
|
from ironic.conf import audit
|
||||||
from ironic.conf import cimc
|
from ironic.conf import cimc
|
||||||
from ironic.conf import cisco_ucs
|
from ironic.conf import cisco_ucs
|
||||||
from ironic.conf import conductor
|
from ironic.conf import conductor
|
||||||
@ -44,6 +45,7 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
agent.register_opts(CONF)
|
agent.register_opts(CONF)
|
||||||
api.register_opts(CONF)
|
api.register_opts(CONF)
|
||||||
|
audit.register_opts(CONF)
|
||||||
cimc.register_opts(CONF)
|
cimc.register_opts(CONF)
|
||||||
cisco_ucs.register_opts(CONF)
|
cisco_ucs.register_opts(CONF)
|
||||||
conductor.register_opts(CONF)
|
conductor.register_opts(CONF)
|
||||||
|
38
ironic/conf/audit.py
Normal file
38
ironic/conf/audit.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
|
||||||
|
opts = [
|
||||||
|
cfg.BoolOpt('enabled',
|
||||||
|
default=False,
|
||||||
|
help=_('Enable auditing of API requests'
|
||||||
|
' (for ironic-api service).')),
|
||||||
|
|
||||||
|
cfg.StrOpt('audit_map_file',
|
||||||
|
default='/etc/ironic/ironic_api_audit_map.conf',
|
||||||
|
help=_('Path to audit map file for ironic-api service. '
|
||||||
|
'Used only when API audit is enabled.')),
|
||||||
|
|
||||||
|
cfg.StrOpt('ignore_req_list',
|
||||||
|
help=_('Comma separated list of Ironic REST API HTTP methods '
|
||||||
|
'to be ignored during audit. For example: auditing '
|
||||||
|
'will not be done on any GET or POST requests '
|
||||||
|
'if this is set to "GET,POST". It is used '
|
||||||
|
'only when API audit is enabled.')),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(conf):
|
||||||
|
conf.register_opts(opts, group='audit')
|
@ -37,6 +37,7 @@ _opts = [
|
|||||||
ironic.drivers.modules.amt.common.opts,
|
ironic.drivers.modules.amt.common.opts,
|
||||||
ironic.drivers.modules.amt.power.opts)),
|
ironic.drivers.modules.amt.power.opts)),
|
||||||
('api', ironic.conf.api.opts),
|
('api', ironic.conf.api.opts),
|
||||||
|
('audit', ironic.conf.audit.opts),
|
||||||
('cimc', ironic.conf.cimc.opts),
|
('cimc', ironic.conf.cimc.opts),
|
||||||
('cisco_ucs', ironic.conf.cisco_ucs.opts),
|
('cisco_ucs', ironic.conf.cisco_ucs.opts),
|
||||||
('conductor', ironic.conf.conductor.opts),
|
('conductor', ironic.conf.conductor.opts),
|
||||||
|
59
ironic/tests/unit/api/test_audit.py
Normal file
59
ironic/tests/unit/api/test_audit.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""
|
||||||
|
Tests to assert that audit middleware works as expected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from keystonemiddleware import audit
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.tests.unit.api import base
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuditMiddleware(base.BaseApiTest):
|
||||||
|
"""Provide a basic smoke test to ensure audit middleware is active.
|
||||||
|
|
||||||
|
The tests below provide minimal confirmation that the audit middleware
|
||||||
|
is called, and may be configured. For comprehensive tests, please consult
|
||||||
|
the test suite in keystone audit_middleware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAuditMiddleware, self).setUp()
|
||||||
|
|
||||||
|
@mock.patch.object(audit, 'AuditMiddleware')
|
||||||
|
def test_enable_audit_request(self, mock_audit):
|
||||||
|
CONF.audit.enabled = True
|
||||||
|
self._make_app(enable_acl=True)
|
||||||
|
mock_audit.assert_called_once_with(
|
||||||
|
mock.ANY,
|
||||||
|
audit_map_file=CONF.audit.audit_map_file,
|
||||||
|
ignore_req_list=CONF.audit.ignore_req_list)
|
||||||
|
|
||||||
|
@mock.patch.object(audit, 'AuditMiddleware')
|
||||||
|
def test_enable_audit_request_error(self, mock_audit):
|
||||||
|
CONF.audit.enabled = True
|
||||||
|
mock_audit.side_effect = IOError("file access error")
|
||||||
|
|
||||||
|
self.assertRaises(exception.InputFileError,
|
||||||
|
self._make_app, enable_acl=True)
|
||||||
|
|
||||||
|
@mock.patch.object(audit, 'AuditMiddleware')
|
||||||
|
def test_disable_audit_request(self, mock_audit):
|
||||||
|
CONF.audit.enabled = False
|
||||||
|
self._make_app(enable_acl=True)
|
||||||
|
self.assertFalse(mock_audit.called)
|
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ironic-api service now supports logging audit messages of
|
||||||
|
api calls. The following configuration parameters have been added.
|
||||||
|
By default auditing of ironic-api service is turned off.
|
||||||
|
|
||||||
|
* [audit]/enabled
|
||||||
|
* [audit]/ignore_req_list
|
||||||
|
* [audit]/audit_map_file
|
Loading…
x
Reference in New Issue
Block a user