From d2e42f1a6fe1a7a58ebb7eccea48f9df6b11a149 Mon Sep 17 00:00:00 2001
From: James Page <james.page@canonical.com>
Date: Tue, 14 Jan 2025 15:46:02 +0000
Subject: [PATCH] Disable caching by default

Due to the architecture of how memcache gets deployed with each
unit of keystone, its possible for the cache on different units
to become consistent.

This results in non-deterministic behaviours depending on which
keystone unit services an API request.

Disable caching by default to ensure consistency and security
within a deployment; Operators may choose to enable caching and
can reduce the impact of cache unit inconsistency by tuning the
expiration configuration options from the default of 600 seconds.

Change-Id: I3601a8db6a0cf56d5482bba3bc35046d64cca452
Closes-Bug: #2089616
---
 config.yaml                     |   9 +++
 hooks/keystone_context.py       |   1 +
 templates/caracal/keystone.conf | 135 ++++++++++++++++++++++++++++++++
 3 files changed, 145 insertions(+)
 create mode 100644 templates/caracal/keystone.conf

diff --git a/config.yaml b/config.yaml
index e7914378..03bb57ff 100644
--- a/config.yaml
+++ b/config.yaml
@@ -134,6 +134,15 @@ options:
       Amount of time (in seconds) to cache items in the dogpile.cache. This
       only applies to cached methods that do not have an explicitly defined
       cache expiration time.
+  enable-cache:
+    type: boolean
+    default: False
+    description: |
+      Enable caching across Keystone using unit localised memcache; enabling
+      this feature may cause caching inconsistency in multi-unit deployments
+      as mutation/invalidation API operations will only apply to the local unit.
+      Potential impact of inconsistency can also be reduced using the expiration
+      TTL options in the charm.
   fernet-max-active-keys:
     type: int
     default: 3
diff --git a/hooks/keystone_context.py b/hooks/keystone_context.py
index a0cac97b..1b6258bf 100644
--- a/hooks/keystone_context.py
+++ b/hooks/keystone_context.py
@@ -221,6 +221,7 @@ class KeystoneContext(context.OSContextGenerator):
                 'identity-cache-expiration')
 
         ctxt['dogpile_cache_expiration'] = config('dogpile-cache-expiration')
+        ctxt['enable_cache'] = config('enable-cache')
 
         ctxt['identity_backend'] = config('identity-backend')
         ctxt['assignment_backend'] = config('assignment-backend')
diff --git a/templates/caracal/keystone.conf b/templates/caracal/keystone.conf
new file mode 100644
index 00000000..3ff77e1f
--- /dev/null
+++ b/templates/caracal/keystone.conf
@@ -0,0 +1,135 @@
+# caracal
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+###############################################################################
+[DEFAULT]
+use_syslog = {{ use_syslog }}
+log_config_append = {{ log_config }}
+debug = {{ debug }}
+public_endpoint = {{ public_endpoint }}
+admin_endpoint = {{ admin_endpoint }}
+
+[database]
+{% if database_host -%}
+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 %}
+{% else -%}
+connection = sqlite:////var/lib/keystone/keystone.db
+{% endif -%}
+connection_recycle_time = 200
+
+[identity]
+driver = {{ identity_backend }}
+{% if identity_cache_expiration -%}
+cache_time = {{ identity_cache_expiration }}
+{% endif -%}
+{% if default_domain_id -%}
+default_domain_id = {{ default_domain_id }}
+{% endif -%}
+
+{% if api_version == 3 -%}
+domain_specific_drivers_enabled = True
+domain_config_dir = {{ domain_config_dir }}
+{% endif -%}
+
+[credential]
+driver = sql
+auth_ttl = {{ ec2_auth_ttl }}
+
+[trust]
+driver = sql
+
+[catalog]
+cache_time = {{ catalog_cache_expiration }}
+driver = sql
+
+{% if role_cache_expiration -%}
+[role]
+cache_time = {{ role_cache_expiration }}
+{% endif -%}
+
+[endpoint_filter]
+
+[token]
+expiration = {{ token_expiration }}
+
+[fernet_tokens]
+max_active_keys = {{ fernet_max_active_keys }}
+
+{% include "parts/section-signing" %}
+
+[cache]
+enabled = {{ enable_cache }}
+{% if memcache_url %}
+backend = oslo_cache.memcache_pool
+memcache_servers = {{ memcache_url }}
+expiration_time = {{ dogpile_cache_expiration }}
+{% endif %}
+
+[policy]
+driver = sql
+
+[assignment]
+driver = {{ assignment_backend }}
+
+[auth]
+methods = {{ auth_methods }}
+
+[paste_deploy]
+config_file = {{ paste_config_file }}
+
+[extra_headers]
+Distribution = Ubuntu
+
+[ldap]
+{% if identity_backend == 'ldap' -%}
+url = {{ ldap_server }}
+user = {{ ldap_user }}
+password = {{ ldap_password }}
+suffix = {{ ldap_suffix }}
+
+{% if ldap_config_flags -%}
+{% for key, value in ldap_config_flags.iteritems() -%}
+{{ key }} = {{ value }}
+{% endfor -%}
+{% endif -%}
+
+{% if ldap_readonly -%}
+user_allow_create = False
+user_allow_update = False
+user_allow_delete = False
+
+tenant_allow_create = False
+tenant_allow_update = False
+tenant_allow_delete = False
+
+role_allow_create = False
+role_allow_update = False
+role_allow_delete = False
+
+group_allow_create = False
+group_allow_update = False
+group_allow_delete = False
+{% endif -%}
+{% endif -%}
+
+{% if api_version == 3 %}
+[resource]
+admin_project_domain_name = {{ admin_domain_name }}
+admin_project_name = admin
+{% endif -%}
+
+{% if password_security_compliance %}
+[security_compliance]
+{% for k, v in password_security_compliance.items() -%}
+{{ k }} = {{ v }}
+{% endfor -%}
+{% endif -%}
+
+{% include "parts/section-federation" %}
+
+{% include "section-oslo-middleware" %}
+# This goes in the section above, selectively
+# Bug #1819134
+max_request_body_size = 114688
+