Add keystone audit middleware API logging

This commit adds Keystone audit middleware API logging to
the Glance charm in versions Yoga and newer to allow users to
configure their environment for CADF compliance. This feature can
be enabled/disabled and is set to 'disabled' by default to avoid
bloat in log files. The logging output writes to
/var/log/glance/glance-api.log.
This commit builds on previous discussions:
https://github.com/juju/charm-helpers/pull/808.

Related-Pr: https://github.com/juju/charm-helpers/pull/893
func-test-pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/1212
Closes-Bug: 1856555
Change-Id: Ied08b56cf3c4fa30827d43a50ca7b552db0fa82b
This commit is contained in:
Myles Penner 2024-05-31 10:25:16 -07:00
parent 75d36368f4
commit 66a167eb42
7 changed files with 242 additions and 3 deletions

View File

@ -12,6 +12,11 @@ options:
default: False
description: |
Setting this to True will allow supporting services to log to syslog.
audit-middleware:
type: boolean
default: False
description: |
Enable Keystone auditing middleware for logging API calls.
openstack-origin:
type: string
default: bobcat

View File

@ -121,13 +121,13 @@ SERVICES = [
CHARM = "glance"
GLANCE_CONF_DIR = "/etc/glance"
GLANCE_AUDIT_MAP = "%s/api_audit_map.conf" % GLANCE_CONF_DIR
GLANCE_REGISTRY_CONF = "%s/glance-registry.conf" % GLANCE_CONF_DIR
GLANCE_API_CONF = "%s/glance-api.conf" % GLANCE_CONF_DIR
GLANCE_SWIFT_CONF = "%s/glance-swift.conf" % GLANCE_CONF_DIR
GLANCE_REGISTRY_PASTE = os.path.join(GLANCE_CONF_DIR,
'glance-registry-paste.ini')
GLANCE_API_PASTE = os.path.join(GLANCE_CONF_DIR,
'glance-api-paste.ini')
GLANCE_API_PASTE = os.path.join(GLANCE_CONF_DIR, 'api-paste.ini')
GLANCE_POLICY_FILE = os.path.join(GLANCE_CONF_DIR, "policy.json")
# NOTE(ajkavanagh): from Ussuri, glance switched to policy-in-code; this is the
# policy.yaml file (as there is not packaged policy.json or .yaml) that is used
@ -204,6 +204,7 @@ CONFIG_FILES = OrderedDict([
config_file=GLANCE_API_CONF),
context.MemcacheContext(),
glance_contexts.GlanceImageImportContext(),
context.KeystoneAuditMiddleware(service=CHARM),
glance_contexts.ExternalS3Context()],
'services': ['glance-api']
}),
@ -218,6 +219,14 @@ CONFIG_FILES = OrderedDict([
'hook_contexts': [],
'services': ['glance-api', 'glance-registry']
}),
(GLANCE_AUDIT_MAP, {
'hook_contexts': [context.KeystoneAuditMiddleware(service=CHARM)],
'services': ['glance-api']
}),
(GLANCE_API_PASTE, {
'hook_contexts': [context.KeystoneAuditMiddleware(service=CHARM)],
'services': ['glance-api']
}),
(ceph_config_file(), {
'hook_contexts': [context.CephContext()],
'services': ['glance-api', 'glance-registry']
@ -257,7 +266,9 @@ def register_configs():
confs = [GLANCE_REGISTRY_CONF,
GLANCE_API_CONF,
HAPROXY_CONF]
HAPROXY_CONF,
GLANCE_API_PASTE,
GLANCE_AUDIT_MAP]
if relation_ids('ceph'):
mkdir(os.path.dirname(ceph_config_file()))
@ -403,6 +414,8 @@ def restart_map():
cmp_release = CompareOpenStackReleases(os_release('glance-common'))
for f, ctxt in CONFIG_FILES.items():
if f == GLANCE_AUDIT_MAP and cmp_release < 'yoga':
continue
svcs = []
for svc in ctxt['services']:
if cmp_release >= 'stein' and svc == 'glance-registry':

View File

@ -0,0 +1,86 @@
# Use this pipeline for no auth or image caching - DEFAULT
[pipeline:glance-api]
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler unauthenticated-context rootapp
# Use this pipeline for image caching and no auth
[pipeline:glance-api-caching]
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler unauthenticated-context cache rootapp
# Use this pipeline for caching w/ management interface but no auth
[pipeline:glance-api-cachemanagement]
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler unauthenticated-context cache cachemanage rootapp
# Use this pipeline for keystone auth
[pipeline:glance-api-keystone]
{% if audit_middleware and service_name -%}
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken audit context rootapp
{% else %}
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context rootapp
{% endif %}
# Use this pipeline for keystone auth with image caching
[pipeline:glance-api-keystone+caching]
{% if audit_middleware and service_name -%}
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken audit context cache rootapp
{% else %}
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context cache rootapp
{% endif %}
# Use this pipeline for keystone auth with caching and cache management
[pipeline:glance-api-keystone+cachemanagement]
{% if audit_middleware and service_name -%}
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken audit context cache cachemanage rootapp
{% else %}
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context cache cachemanage rootapp
{% endif %}
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v2: apiv2app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
backends = disable_by_file
disable_by_file_path = /etc/glance/healthcheck_disable
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cachemanage]
paste.filter_factory = glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
delay_auth_decision = true
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = glance
oslo_config_program = glance-api
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware:HTTPProxyToWSGI.factory
{% include "section-filter-audit" %}

View File

@ -0,0 +1,16 @@
[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_keywords]
detail = None
file = None
images = image
members = member
tags = tag
# map endpoint type defined in service catalog to CADF typeURI
[service_endpoints]
image = service/storage/image

View File

@ -0,0 +1,89 @@
[DEFAULT]
verbose = {{ verbose }}
use_syslog = {{ use_syslog }}
debug = {{ debug }}
workers = {{ workers }}
bind_host = {{ bind_host }}
{% if ext -%}
bind_port = {{ ext }}
{% elif bind_port -%}
bind_port = {{ bind_port }}
{% else -%}
bind_port = 9292
{% endif -%}
{% if transport_url %}
transport_url = {{ transport_url }}
{% endif %}
log_file = /var/log/glance/api.log
backlog = 4096
{% if expose_image_locations -%}
show_multiple_locations = {{ expose_image_locations }}
show_image_direct_url = {{ expose_image_locations }}
{% endif -%}
{% if api_config_flags -%}
{% for key, value in api_config_flags.items() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
delayed_delete = False
scrub_time = 43200
scrubber_datadir = /var/lib/glance/scrubber
image_cache_dir = /var/lib/glance/image-cache/
db_enforce_mysql_charset = False
{% if image_size_cap -%}
image_size_cap = {{ image_size_cap }}
{% endif -%}
{% if enabled_backends %}
enabled_backends = {{ enabled_backends }}
{% endif %}
[glance_store]
{% if default_store_backend %}
default_backend = {{ default_store_backend }}
{% endif %}
[image_format]
disk_formats = {{ disk_formats }}
{% if container_formats -%}
container_formats = {{ container_formats }}
{% endif -%}
{% include "section-keystone-authtoken-v3only" %}
{% if auth_host -%}
[paste_deploy]
flavor = keystone
config_file = /etc/glance/api-paste.ini
{% endif %}
[barbican]
auth_endpoint = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v3
{% include "parts/section-database" %}
{% include "section-oslo-messaging-rabbit" %}
{% include "section-oslo-notifications" %}
{% include "section-oslo-middleware" %}
{% include "parts/section-storage" %}
{% for name, cfg in enabled_backend_configs.items() %}
[{{name}}]
{% for key, val in cfg.items() -%}
{{ key }} = {{ val }}
{% endfor -%}
{% endfor%}
{% include "parts/section-image-import" %}
{% include "section-audit-middleware-notifications" %}

View File

@ -26,6 +26,7 @@ tests:
- zaza.openstack.charm_tests.glance.tests.GlanceCephRGWBackendTest
- zaza.openstack.charm_tests.glance.tests.GlanceExternalS3Test
- zaza.openstack.charm_tests.glance.tests.GlanceCinderBackendTest
- zaza.openstack.charm_tests.audit.tests.KeystoneAuditMiddlewareTest
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
- zaza.openstack.charm_tests.ceph.tests.CheckPoolTypes
- zaza.openstack.charm_tests.ceph.tests.BlueStoreCompressionCharmOperation
@ -35,6 +36,8 @@ tests:
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
tests_options:
audit-middleware:
service: glance
tempest:
full_run:
smoke: true

View File

@ -159,6 +159,7 @@ class TestGlanceUtils(CharmTestCase):
(utils.GLANCE_API_CONF, ['glance-api']),
(utils.GLANCE_SWIFT_CONF, ['glance-api']),
(utils.GLANCE_POLICY_FILE, ['glance-api', 'glance-registry']),
(utils.GLANCE_API_PASTE, ['glance-api']),
(utils.ceph_config_file(), ['glance-api', 'glance-registry']),
(utils.HAPROXY_CONF, ['haproxy']),
(utils.HTTPS_APACHE_CONF, ['apache2']),
@ -181,6 +182,31 @@ class TestGlanceUtils(CharmTestCase):
(utils.GLANCE_API_CONF, ['glance-api']),
(utils.GLANCE_SWIFT_CONF, ['glance-api']),
(utils.GLANCE_POLICY_FILE, ['glance-api']),
(utils.GLANCE_API_PASTE, ['glance-api']),
(utils.ceph_config_file(), ['glance-api']),
(utils.HAPROXY_CONF, ['haproxy']),
(utils.HTTPS_APACHE_CONF, ['apache2']),
(utils.HTTPS_APACHE_24_CONF, ['apache2']),
(utils.APACHE_PORTS_CONF, ['apache2']),
(utils.MEMCACHED_CONF, ['memcached']),
])
self.assertEqual(ex_map, utils.restart_map())
self.enable_memcache.return_value = False
del ex_map[utils.MEMCACHED_CONF]
self.assertEqual(ex_map, utils.restart_map())
def test_restart_map_yoga(self):
self.enable_memcache.return_value = True
self.config.side_effect = None
self.service_name.return_value = 'glance'
self.os_release.return_value = 'yoga'
ex_map = OrderedDict([
(utils.GLANCE_API_CONF, ['glance-api']),
(utils.GLANCE_SWIFT_CONF, ['glance-api']),
(utils.GLANCE_POLICY_FILE, ['glance-api']),
(utils.GLANCE_AUDIT_MAP, ['glance-api']),
(utils.GLANCE_API_PASTE, ['glance-api']),
(utils.ceph_config_file(), ['glance-api']),
(utils.HAPROXY_CONF, ['haproxy']),
(utils.HTTPS_APACHE_CONF, ['apache2']),
@ -205,6 +231,7 @@ class TestGlanceUtils(CharmTestCase):
(utils.GLANCE_API_CONF, ['glance-api']),
(utils.GLANCE_SWIFT_CONF, ['glance-api']),
(utils.GLANCE_POLICY_FILE, ['glance-api']),
(utils.GLANCE_API_PASTE, ['glance-api']),
(utils.ceph_config_file(), ['glance-api']),
(utils.HAPROXY_CONF, ['haproxy']),
(utils.HTTPS_APACHE_CONF, ['apache2']),