Add keystone audit middleware API logging

This commit adds Keystone audit middleware API logging to the Nova-
Cloud-Contoller 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/nova/nova-api-wsgi.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: Ie09cc6775c13a2dba6a0f3d69a4a080f9fc484c8
This commit is contained in:
Myles Penner 2024-05-02 16:54:10 -07:00
parent 0094bf072e
commit 723515fd2b
6 changed files with 554 additions and 13 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
@ -187,7 +192,7 @@ options:
the nova-cloud-controller serving the request to be used.
console-keymap:
type: string
default: 'en-us'
default: "en-us"
description: |
Console keymap.
console-ssl-cert:

View File

@ -95,6 +95,7 @@ BASE_SERVICES = [
]
AWS_COMPAT_SERVICES = ['nova-api-ec2', 'nova-objectstore']
AUDIT_SERVICES = ['nova-api-os-compute']
SERVICE_BLACKLIST = {
'liberty': AWS_COMPAT_SERVICES,
'newton': ['nova-cert'],
@ -286,6 +287,18 @@ def resource_map(actual_services=True):
ssl_dir=NOVA_CONF_DIR)
)
if cmp_os_release >= 'yoga':
# Conditionally render audit middleware for yoga and later
NOVA_AUDIT_MAP = '%s/api_audit_map.conf' % NOVA_CONF_DIR
_resource_map[NOVA_CONF]['contexts'].append(
ch_context.KeystoneAuditMiddleware(service='nova'))
_resource_map[NOVA_API_PASTE]['contexts'].append(
ch_context.KeystoneAuditMiddleware(service='nova'))
_BASE_RESOURCE_MAP[NOVA_AUDIT_MAP] = {
'contexts': [ch_context.KeystoneAuditMiddleware(service='nova')],
'services': AUDIT_SERVICES,
}
if common.console_attributes('services'):
_resource_map[NOVA_CONF]['services'] += (
common.console_attributes('services'))

View File

@ -0,0 +1,137 @@
############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: meta
[pipeline:meta]
pipeline = cors metaapp
[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory
#############
# OpenStack #
#############
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
# starting in Liberty the v21 implementation replaces the v2
# implementation and is suggested that you use it as the default. If
# this causes issues with your clients you can rollback to the
# *frozen* v2 api by commenting out the above stanza and using the
# following instead::
# /v2: openstack_compute_api_legacy_v2
# if rolling back to v2 fixes your issue please file a critical bug
# at - https://bugs.launchpad.net/nova/+bugs
#
# v21 is an exactly feature match for v2, except it has more stringent
# input validation on the wsgi surface (prevents fuzzing early on the
# API). It also provides new features via API microversions which are
# opt into for clients. Unaware clients will receive the same frozen
# v2 API feature set, but with some relaxed validation
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
# NOTE: this is deprecated in favor of openstack_compute_api_v21_legacy_v2_compatible
[composite:openstack_compute_api_legacy_v2]
use = call:nova.api.auth:pipeline_factory
noauth2 = cors compute_req_id faultwrap sizelimit noauth2 legacy_ratelimit osapi_compute_app_legacy_v2
{% if audit_middleware and service_name -%}
keystone = cors compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_ratelimit audit osapi_compute_app_legacy_v2
keystone_nolimit = cors compute_req_id faultwrap sizelimit authtoken keystonecontext audit osapi_compute_app_legacy_v2
{% else -%}
keystone = cors compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_ratelimit osapi_compute_app_legacy_v2
keystone_nolimit = cors compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_legacy_v2
{% endif %}
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit noauth2 osapi_compute_app_v21
{% if audit_middleware and service_name -%}
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext audit osapi_compute_app_v21
{% else -%}
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21
{% endif %}
[composite:openstack_compute_api_v21_legacy_v2_compatible]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit noauth2 legacy_v2_compatible osapi_compute_app_v21
{% if audit_middleware and service_name -%}
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_v2_compatible audit osapi_compute_app_v21
{% else -%}
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21
{% endif %}
[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory
[filter:compute_req_id]
paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:noauth2]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:legacy_ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
{% if api_rate_limit_rules -%}
limits = {{ api_rate_limit_rules }}
{% endif -%}
[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory
[filter:legacy_v2_compatible]
paste.filter_factory = nova.api.openstack:LegacyV2CompatibleWrapper.factory
[app:osapi_compute_app_legacy_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
[pipeline:oscomputeversions]
pipeline = faultwrap http_proxy_to_wsgi oscomputeversionapp
[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory
##########
# Shared #
##########
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = nova
[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
{% include "section-filter-audit" %}
{% if service_host -%}
# NOTE(jamespage) - not used - but required for relation to nova-compute
service_protocol = {{ service_protocol }}
service_host = {{ service_host }}
service_port = {{ service_port }}
auth_host = {{ auth_host }}
auth_port = {{ auth_port }}
auth_protocol = {{ auth_protocol }}
admin_tenant_name = {{ admin_tenant_name }}
admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
{% if admin_domain_name -%}
admin_domain_name = {{ admin_domain_name }}
{% endif -%}
{% endif -%}

View File

@ -0,0 +1,72 @@
[DEFAULT]
# default target endpoint type
# should match the endpoint type defined in service catalog
target_endpoint_type = None
[custom_actions]
enable = enable
disable = disable
delete = delete
startup = start/startup
shutdown = stop/shutdown
reboot = start/reboot
os-migrations/get = read
os-server-password/post = update
# possible end path of api requests
[path_keywords]
add = None
action = None
enable = None
disable = None
configure-project = None
defaults = None
delete = None
detail = None
diagnostics = None
entries = entry
extensions = alias
flavors = flavor
images = image
ips = label
limits = None
metadata = key
os-agents = os-agent
os-aggregates = os-aggregate
os-availability-zone = None
os-certificates = None
os-cloudpipe = None
os-fixed-ips = ip
os-extra_specs = key
os-flavor-access = None
os-floating-ip-dns = domain
os-floating-ips-bulk = host
os-floating-ip-pools = None
os-floating-ips = floating-ip
os-hosts = host
os-hypervisors = hypervisor
os-instance-actions = instance-action
os-keypairs = keypair
os-migrations = None
os-networks = network
os-quota-sets = tenant
os-security-groups = security_group
os-security-group-rules = rule
os-server-password = None
os-services = None
os-simple-tenant-usage = tenant
os-virtual-interfaces = None
os-volume_attachments = attachment
os-volumes_boot = None
os-volumes = volume
os-volume-types = volume-type
os-snapshots = snapshot
reboot = None
servers = server
shutdown = None
startup = None
statistics = None
# map endpoint type defined in service catalog to CADF typeURI
[service_endpoints]
compute = service/compute

310
templates/yoga/nova.conf Normal file
View File

@ -0,0 +1,310 @@
# yoga
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
verbose={{ verbose }}
debug={{ debug }}
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
logdir=/var/log/nova
state_path=/var/lib/nova
iscsi_helper=tgtadm
libvirt_use_virtio_for_bridges=True
connection_type=libvirt
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
volumes_path=/var/lib/nova/volumes
enabled_apis=osapi_compute,metadata
compute_driver=libvirt.LibvirtDriver
use_ipv6 = {{ use_ipv6 }}
osapi_compute_listen = {{ bind_host }}
{% if unique_server_names -%}
osapi_compute_unique_unique_server_names = {{ unique_server_names }}
{% endif -%}
metadata_host = {{ bind_host }}
s3_listen = {{ bind_host }}
enable_new_services = {{ enable_new_services }}
{% if debug -%}
default_log_levels = "amqp=WARN, amqplib=WARN, boto=WARN, qpid=WARN, sqlalchemy=WARN, suds=INFO, oslo.messaging=INFO, oslo_messaging=DEBUG, iso8601=WARN, requests.packages.urllib3.connectionpool=WARN, urllib3.connectionpool=WARN, websocket=WARN, requests.packages.urllib3.util.retry=WARN, urllib3.util.retry=WARN, keystonemiddleware=WARN, routes.middleware=WARN, stevedore=WARN, taskflow=WARN, keystoneauth=WARN, oslo.cache=INFO, dogpile.core.dogpile=INFO, glanceclient=WARN, oslo.privsep.daemon=INFO"
glance.debug = True
{% endif -%}
{% if transport_url %}
transport_url = {{ transport_url }}
{% endif %}
{% if dns_domain -%}
# Per LP#1805645, dhcp_domain needs to be configured for nova-metadata-api
# It gets this information from neutron.
dhcp_domain = {{ dns_domain }}
{% endif -%}
osapi_compute_workers = {{ workers }}
cpu_allocation_ratio = {{ cpu_allocation_ratio }}
ram_allocation_ratio = {{ ram_allocation_ratio }}
disk_allocation_ratio = {{ disk_allocation_ratio }}
use_syslog={{ use_syslog }}
my_ip = {{ host_ip }}
{% include "parts/novnc" %}
{% if max_local_block_devices is not none -%}
max_local_block_devices = {{ max_local_block_devices }}
{% endif -%}
{% if rbd_pool -%}
rbd_pool = {{ rbd_pool }}
rbd_user = {{ rbd_user }}
rbd_secret_uuid = {{ rbd_secret_uuid }}
{% endif -%}
{% if neutron_plugin and neutron_plugin in ('ovs', 'midonet') -%}
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver
libvirt_user_virtio_for_bridges = True
{% if neutron_security_groups -%}
security_group_api = {{ network_manager }}
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'vsp' -%}
neutron_ovs_bridge = alubr0
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'nvp' -%}
security_group_api = neutron
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'Calico' -%}
security_group_api = neutron
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'plumgrid' -%}
security_group_api=neutron
firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if network_manager_config -%}
{% for key, value in network_manager_config.items() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if network_manager and network_manager == 'neutron' -%}
network_api_class = nova.network.neutronv2.api.API
{% else -%}
network_manager = nova.network.manager.FlatDHCPManager
{% endif -%}
{% if default_floating_pool -%}
default_floating_pool = {{ default_floating_pool }}
{% endif -%}
{% if volume_service -%}
volume_api_class=nova.volume.cinder.API
{% endif -%}
{% if allow_resize_to_same_host -%}
allow_resize_to_same_host = True
{% endif -%}
{% if user_config_flags -%}
{% for key, value in user_config_flags.items() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if listen_ports -%}
{% for key, value in listen_ports.items() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif %}
[upgrade_levels]
compute = auto
{% include "section-zeromq" %}
{% include "parts/database-v2" %}
{% include "parts/database-api" %}
{% if glance_api_servers -%}
[glance]
api_servers = {{ glance_api_servers }}
{% endif -%}
{% if network_manager and network_manager == 'neutron' %}
{% include "parts/section-neutron" %}
{% endif %}
{% include "section-keystone-authtoken-mitaka" %}
{% include "section-service-user" %}
{% include "parts/section-cinder" %}
[osapi_v3]
enabled=True
{% include "parts/cell" %}
[conductor]
workers = {{ workers }}
{% include "section-oslo-messaging-rabbit" %}
{% include "section-oslo-notifications" %}
[oslo_concurrency]
lock_path=/var/lock/nova
[vnc]
{% if console_access_port and console_access_protocol == 'novnc' -%}
novncproxy_port = {{ console_access_port }}
{% endif %}
{% if console_access_port and console_access_protocol == 'xvpvnc' -%}
xvpvncproxy_port = {{ console_access_port }}
{% endif %}
[spice]
{% include "parts/spice" %}
{% if console_access_port and console_access_protocol == 'spice' -%}
html5proxy_port = {{ console_access_port }}
{% endif %}
{% include "parts/section-serial-console" %}
{% include "parts/section-console" %}
{% if memcached_servers %}
[cache]
enabled = true
backend = oslo_cache.memcache_pool
memcache_servers = {{ memcached_servers }}
{% endif %}
{% include "section-placement" %}
[scheduler]
# NOTE(jamespage): perform automatic host cell mapping
# until we can orchestrate this better
# using the nova-cc <--> nova-compute
# relation
discover_hosts_in_cells_interval = 30
workers = {{ workers }}
{% if limit_tenants_to_placement_aggregate -%}
limit_tenants_to_placement_aggregate = True
{%- endif %}
{%- if placement_aggregate_required_for_tenants %}
placement_aggregate_required_for_tenants = True
{%- endif %}
{%- if enable_isolated_aggregate_filtering %}
enable_isolated_aggregate_filtering = True
{%- endif %}
[filter_scheduler]
{% if additional_neutron_filters is defined %}
enabled_filters = {{ scheduler_default_filters }},{{ additional_neutron_filters }}
{% else %}
enabled_filters = {{ scheduler_default_filters }}
{% endif %}
{% if not skip_hosts_with_build_failures %}
# Disable BuildFailureWeigher as any failed build will result
# in a very low weighting for the hypervisor, resulting in
# instances all being scheduled to hypervisors with no build
# failures.
# https://bugs.launchpad.net/charm-nova-cloud-controller/+bug/1818239
build_failure_weight_multiplier = 0.0
{% endif %}
{%- if scheduler_host_subset_size %}
host_subset_size = {{ scheduler_host_subset_size }}
{%- endif %}
{%- if scheduler_max_attempts %}
max_attempts = {{ scheduler_max_attempts }}
{%- endif %}
[api]
auth_strategy=keystone
{% if vendor_data or vendor_data_url -%}
vendordata_providers = {{ vendordata_providers }}
{% if vendor_data -%}
vendordata_jsonfile_path = /etc/nova/vendor_data.json
{% endif -%}
{% if vendor_data_url -%}
vendordata_dynamic_targets = {{ vendor_data_url }}
{% endif -%}
{% endif -%}
[wsgi]
api_paste_config=/etc/nova/api-paste.ini
[pci]
{% if pci_alias %}
alias = {{ pci_alias }}
{% endif %}
{% for alias in pci_aliases -%}
alias = {{ alias }}
{% endfor -%}
{% include "section-oslo-middleware" %}
{% include "section-audit-middleware-notifications" %}
[quota]
{% if quota_instances is not none -%}
instances = {{ quota_instances }}
{% endif -%}
{% if quota_cores is not none -%}
cores = {{ quota_cores }}
{% endif -%}
{% if quota_ram is not none -%}
ram = {{ quota_ram }}
{% endif -%}
{% if quota_metadata_items is not none -%}
metadata_items = {{ quota_metadata_items }}
{% endif -%}
{% if quota_injected_files is not none -%}
injected_files = {{ quota_injected_files }}
{% endif -%}
{% if quota_injected_file_content_bytes is not none -%}
injected_file_content_bytes = {{ quota_injected_file_content_bytes }}
{% endif -%}
{% if quota_injected_file_path_length is not none -%}
injected_file_path_length = {{ quota_injected_file_path_length }}
{% endif -%}
{% if quota_key_pairs is not none -%}
key_pairs = {{ quota_key_pairs }}
{% endif -%}
{% if quota_server_groups is not none -%}
server_groups = {{ quota_server_groups }}
{% endif -%}
{% if quota_server_group_members is not none -%}
server_group_members = {{ quota_server_group_members }}
{% endif -%}
{% if quota_count_usage_from_placement is sameas true -%}
count_usage_from_placement = {{ quota_count_usage_from_placement }}
{% endif -%}

View File

@ -26,7 +26,11 @@ tests:
- zaza.openstack.charm_tests.nova.tests.SecurityTests
- zaza.openstack.charm_tests.nova.tests.NovaCloudController
- zaza.openstack.charm_tests.nova.tests.NovaCloudControllerActionTest
- zaza.openstack.charm_tests.audit.tests.KeystoneAuditMiddlewareTest
tests_options:
audit-middleware:
service: nova
application: nova-cloud-controller
force_deploy:
- noble-caracal