Add keystone audit middleware API logging

This commit adds Keystone audit middleware API logging to the Heat
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 is configured to /var/log/heat/heat-api.log.
This commit builds on previous discussions:
https://github.com/juju/charm-helpers/pull/808.

func-test-pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/1212
Closes-Bug: 1856555
Change-Id: Ic611b68f35a36489673e3430dd1abbd5aa752fa7
(cherry picked from commit 69886c1bcd)
This commit is contained in:
Myles Penner 2024-05-24 15:38:11 -07:00
parent 52d0a06868
commit 5f61266f26
7 changed files with 288 additions and 2 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

@ -120,6 +120,7 @@ SVC = 'heat'
HEAT_DIR = '/etc/heat'
HEAT_CONF = '/etc/heat/heat.conf'
HEAT_API_PASTE = '/etc/heat/api-paste.ini'
HEAT_AUDIT_CONF = '%s/api_audit_map.conf' % HEAT_DIR
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
APACHE_PORTS_CONF = '/etc/apache2/ports.conf'
HTTPS_APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
@ -147,17 +148,23 @@ CONFIG_FILES = OrderedDict([
context.WorkerConfigContext(),
context.BindHostContext(),
context.MemcacheContext(),
context.OSConfigFlagContext()],
context.OSConfigFlagContext(),
context.KeystoneAuditMiddleware(service=SVC)]
}),
(HEAT_API_PASTE, {
'services': [s for s in BASE_SERVICES if 'api' in s],
'contexts': [HeatIdentityServiceContext()],
'contexts': [HeatIdentityServiceContext(),
context.KeystoneAuditMiddleware(service=SVC)],
}),
(HAPROXY_CONF, {
'contexts': [context.HAProxyContext(singlenode_mode=True),
HeatHAProxyContext()],
'services': ['haproxy'],
}),
(HEAT_AUDIT_CONF, {
'contexts': [context.KeystoneAuditMiddleware(service=SVC)],
'services': ['heat-api']
}),
(HTTPS_APACHE_CONF, {
'contexts': [HeatApacheSSLContext()],
'services': ['apache2'],

View File

@ -0,0 +1,130 @@
# yoga
# heat-api pipeline
[pipeline:heat-api]
{% if audit_middleware and service_name %}
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation osprofiler authurl authtoken audit context apiv1app
{% else %}
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation osprofiler authurl authtoken context apiv1app
{% endif %}
# heat-api pipeline for standalone heat
# ie. uses alternative auth backend that authenticates users against keystone
# using username and password instead of validating token (which requires
# an admin/service token).
# To enable, in heat.conf:
# [paste_deploy]
# flavor = standalone
#
[pipeline:heat-api-standalone]
{% if audit_middleware and service_name %}
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword audit context apiv1app
{% else %}
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword context apiv1app
{% endif %}
# heat-api pipeline for custom cloud backends
# i.e. in heat.conf:
# [paste_deploy]
# flavor = custombackend
#
[pipeline:heat-api-custombackend]
pipeline = healthcheck cors request_id faultwrap versionnegotiation context custombackendauth apiv1app
# To enable, in heat.conf:
# [paste_deploy]
# flavor = noauth
#
[pipeline:heat-api-noauth]
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation noauth context apiv1app
# heat-api-cfn pipeline
[pipeline:heat-api-cfn]
pipeline = healthcheck cors http_proxy_to_wsgi cfnversionnegotiation osprofiler ec2authtoken authtoken context apicfnv1app
# heat-api-cfn pipeline for standalone heat
# relies exclusively on authenticating with ec2 signed requests
[pipeline:heat-api-cfn-standalone]
pipeline = healthcheck cors http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app
# heat-api-cloudwatch pipeline
[pipeline:heat-api-cloudwatch]
pipeline = healthcheck cors versionnegotiation osprofiler ec2authtoken authtoken context apicwapp
# heat-api-cloudwatch pipeline for standalone heat
# relies exclusively on authenticating with ec2 signed requests
[pipeline:heat-api-cloudwatch-standalone]
pipeline = healthcheck cors versionnegotiation ec2authtoken context apicwapp
[app:apiv1app]
paste.app_factory = heat.common.wsgi:app_factory
heat.app_factory = heat.api.openstack.v1:API
[app:apicfnv1app]
paste.app_factory = heat.common.wsgi:app_factory
heat.app_factory = heat.api.cfn.v1:API
[app:apicwapp]
paste.app_factory = heat.common.wsgi:app_factory
heat.app_factory = heat.api.cloudwatch:API
[filter:versionnegotiation]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:version_negotiation_filter
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = heat
[filter:faultwrap]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:faultwrap_filter
[filter:cfnversionnegotiation]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.cfn:version_negotiation_filter
[filter:cwversionnegotiation]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.cloudwatch:version_negotiation_filter
[filter:context]
paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
[filter:ec2authtoken]
paste.filter_factory = heat.api.aws.ec2token:EC2Token_filter_factory
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware:HTTPProxyToWSGI.factory
# Middleware to set auth_url header appropriately
[filter:authurl]
paste.filter_factory = heat.common.auth_url:filter_factory
# Auth middleware that validates token against keystone
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
# Auth middleware that validates username/password against keystone
[filter:authpassword]
paste.filter_factory = heat.common.auth_password:filter_factory
# Auth middleware that validates against custom backend
[filter:custombackendauth]
paste.filter_factory = heat.common.custom_backend_auth:filter_factory
# Auth middleware that accepts any auth
[filter:noauth]
paste.filter_factory = heat.common.noauth:filter_factory
# Middleware to set x-openstack-request-id in http response header
[filter:request_id]
paste.filter_factory = oslo_middleware.request_id:RequestId.factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
{% include "section-filter-audit" %}

View File

@ -0,0 +1,32 @@
[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]
stacks = stack
resources = resource
preview = None
detail = None
abandon = None
snapshots = snapshot
restore = None
outputs = output
metadata = server
signal = None
events = event
template = None
template_versions = template_version
functions = None
validate = None
resource_types = resource_type
build_info = None
actions = None
software_configs = software_config
software_deployments = software_deployment
services = None
# map endpoint type defined in service catalog to CADF typeURI
[service_endpoints]
orchestration = service/orchestration

107
templates/yoga/heat.conf Normal file
View File

@ -0,0 +1,107 @@
[DEFAULT]
use_syslog = {{ use_syslog }}
debug = {{ debug }}
verbose = {{ verbose }}
log_dir = /var/log/heat
instance_user = {{ instance_user }}
instance_driver = heat.engine.nova
{% if plugin_dirs -%}
plugin_dirs = {{ plugin_dirs }}
{% else -%}
plugin_dirs = /usr/lib64/heat,/usr/lib/heat
{% endif -%}
environment_dir = /etc/heat/environment.d
host = heat
auth_encryption_key = {{ encryption_key }}
deferred_auth_method = trusts
stack_domain_admin = heat_domain_admin
stack_domain_admin_password = {{ heat_domain_admin_passwd }}
stack_user_domain_name = heat
num_engine_workers = {{ workers }}
{%- if max_stacks_per_tenant %}
max_stacks_per_tenant = {{ max_stacks_per_tenant }}
{%- endif %}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif %}
{% if user_config_flags -%}
{% for key, value in user_config_flags.items() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if transport_url %}
transport_url = {{ transport_url }}
{% endif %}
{% if auth_host -%}
{% include "section-keystone-authtoken-mitaka" %}
[trustee]
auth_plugin = password
auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
username = {{ admin_user }}
password = {{ admin_password }}
user_domain_name = default
[clients_keystone]
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
[ec2_authtoken]
auth_uri = {{service_protocol }}://{{ service_host }}:{{ service_port }}
keystone_ec2_uri = {{service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0/ec2tokens
{% endif %}
{% if database_host -%}
[database]
connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
{% endif %}
[paste_deploy]
api_paste_config=/etc/heat/api-paste.ini
[heat_api]
bind_host = {{ bind_host }}
{% if api_listen_port -%}
bind_port={{ api_listen_port }}
{% else -%}
bind_port=8004
{% endif %}
workers = {{ workers }}
[heat_api_cfn]
bind_host = {{ bind_host }}
{% if api_cfn_listen_port -%}
bind_port={{ api_cfn_listen_port }}
{% else -%}
bind_port=8000
{% endif %}
workers = {{ workers }}
{% include "section-oslo-messaging-rabbit-ocata" %}
{% if use_internal_endpoints -%}
[clients]
endpoint_type = internalURL
[clients_heat]
# See LP 1770144
endpoint_type = publicURL
{%- endif %}
{% include "section-oslo-middleware" %}
{% if sections -%}
{% for section in sections if section != 'DEFAULT' -%}
[{{ section }}]
{% for key, value in sections[section] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endfor -%}
{% endif %}
{% include "section-audit-middleware-notifications" %}

View File

@ -16,9 +16,13 @@ configure:
tests:
- zaza.openstack.charm_tests.heat.tests.HeatBasicDeployment
- zaza.openstack.charm_tests.policyd.tests.HeatTests
- zaza.openstack.charm_tests.audit.tests.KeystoneAuditMiddlewareTest
tests_options:
policyd:
service: heat
audit-middleware:
service: heat
force_deploy:
- noble-caracal

View File

@ -50,6 +50,7 @@ RESTART_MAP = OrderedDict([
('/etc/heat/heat.conf', ['heat-api', 'heat-api-cfn', 'heat-engine']),
('/etc/heat/api-paste.ini', ['heat-api', 'heat-api-cfn']),
('/etc/haproxy/haproxy.cfg', ['haproxy']),
('/etc/heat/api_audit_map.conf', ['heat-api']),
('/etc/apache2/sites-available/openstack_https_frontend', ['apache2']),
('/etc/apache2/sites-available/openstack_https_frontend.conf',
['apache2']),