Skip to content

Commit

Permalink
Add keystone audit middleware API logging
Browse files Browse the repository at this point in the history
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:
juju/charm-helpers#808.

func-test-pr: openstack-charmers/zaza-openstack-tests#1212
Closes-Bug: 1856555
Change-Id: Ic611b68f35a36489673e3430dd1abbd5aa752fa7
  • Loading branch information
MylesJP committed Jun 12, 2024
1 parent 7cec7db commit 69886c1
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 2 deletions.
5 changes: 5 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions hooks/heat_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
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'
Expand Down Expand Up @@ -147,17 +148,23 @@
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'],
Expand Down
130 changes: 130 additions & 0 deletions templates/yoga/api-paste.ini
Original file line number Diff line number Diff line change
@@ -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" %}
32 changes: 32 additions & 0 deletions templates/yoga/api_audit_map.conf
Original file line number Diff line number Diff line change
@@ -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 changes: 107 additions & 0 deletions templates/yoga/heat.conf
Original file line number Diff line number Diff line change
@@ -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" %}
4 changes: 4 additions & 0 deletions tests/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions unit_tests/test_heat_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
('/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']),
Expand Down

0 comments on commit 69886c1

Please sign in to comment.