aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ansible/roles/keystone/defaults/main.yml7
-rw-r--r--ansible/roles/keystone/tasks/register_identity_providers.yml314
-rw-r--r--ansible/roles/keystone/templates/keystone-httpd.json.j26
-rw-r--r--ansible/roles/keystone/templates/keystone.json.j24
-rw-r--r--doc/source/reference/shared-services/keystone-guide.rst5
-rw-r--r--releasenotes/notes/bug-2134455-idp-fixes-aa7064473be5e44d.yaml20
-rw-r--r--roles/kolla-ansible-test-dashboard/tasks/main.yml1
-rw-r--r--tests/run.yml15
-rw-r--r--tests/templates/globals-default.j211
-rw-r--r--tests/templates/keystone-federation/attribute_mapping.json42
-rw-r--r--tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.client.j24
-rw-r--r--tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.conf.j21
-rw-r--r--tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.provider.j2324
-rw-r--r--tests/testinfra/test_horizon.py51
-rw-r--r--zuul.d/scenarios/keystone-federation.yaml2
15 files changed, 609 insertions, 198 deletions
diff --git a/ansible/roles/keystone/defaults/main.yml b/ansible/roles/keystone/defaults/main.yml
index 3727fdd3f..bb3b35286 100644
--- a/ansible/roles/keystone/defaults/main.yml
+++ b/ansible/roles/keystone/defaults/main.yml
@@ -48,7 +48,7 @@ keystone_services:
keystone-httpd:
container_name: "keystone_httpd"
group: "keystone"
- enabled: "{{ enable_keystone_federation | bool }}"
+ enabled: "{{ enable_keystone_federation | bool and keystone_wsgi_provider == 'uwsgi' }}"
image: "{{ keystone_httpd_image_full }}"
volumes: "{{ keystone_httpd_default_volumes + keystone_httpd_extra_volumes }}"
dimensions: "{{ keystone_httpd_dimensions }}"
@@ -265,7 +265,10 @@ keystone_remote_id_attribute_oidc: "HTTP_OIDC_ISS"
keystone_container_federation_oidc_metadata_folder: "{{ '/etc/apache2/metadata' if kolla_base_distro in ['debian', 'ubuntu'] else '/etc/httpd/metadata' }}"
keystone_container_federation_oidc_idp_certificate_folder: "{{ '/etc/apache2/cert' if kolla_base_distro in ['debian', 'ubuntu'] else '/etc/httpd/cert' }}"
keystone_container_federation_oidc_attribute_mappings_folder: "{{ container_config_directory }}/federation/oidc/attribute_maps"
-keystone_host_federation_base_folder: "{{ node_config_directory }}/keystone/federation"
+keystone_host_federation_base_folder: >-
+ {{ node_config_directory }}/{{
+ 'keystone-httpd' if keystone_wsgi_provider == 'uwsgi' else 'keystone'
+ }}/federation
keystone_host_federation_oidc_metadata_folder: "{{ keystone_host_federation_base_folder }}/oidc/metadata"
keystone_host_federation_oidc_idp_certificate_folder: "{{ keystone_host_federation_base_folder }}/oidc/cert"
keystone_host_federation_oidc_attribute_mappings_folder: "{{ keystone_host_federation_base_folder }}/oidc/attribute_maps"
diff --git a/ansible/roles/keystone/tasks/register_identity_providers.yml b/ansible/roles/keystone/tasks/register_identity_providers.yml
index 07ee8e436..ab00002fb 100644
--- a/ansible/roles/keystone/tasks/register_identity_providers.yml
+++ b/ansible/roles/keystone/tasks/register_identity_providers.yml
@@ -1,241 +1,167 @@
---
- name: List configured attribute mappings (that can be used by IdPs)
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }} {% endif %}
- mapping list -c ID --format value
- run_once: true
- changed_when: false
become: true
- register: existing_mappings_register
-
-- name: Register existing mappings
- ansible.builtin.set_fact:
- existing_mappings: "{{ existing_mappings_register.stdout_lines | map('trim') | list }}"
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.federation_mapping_info
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ run_once: true
+ register: existing_mappings
- name: Remove unmanaged attribute mappings
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-user-domain-name={{ default_user_domain_name }}
- --os-system-scope="all"
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }} {% endif %}
- mapping delete {{ item }}
- run_once: true
become: true
- with_items: "{{ existing_mappings }}"
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.federation_mapping
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ name: "{{ item.name }}"
+ state: absent
+ run_once: true
+ with_items: "{{ existing_mappings.mappings }}"
when:
- - item not in (keystone_identity_mappings | map(attribute='name') | list)
+ - item.name not in (keystone_identity_mappings | map(attribute='name') | list)
- keystone_should_remove_attribute_mappings
- name: Create unexisting domains
become: true
- changed_when: true
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
- module_name: "os_keystone_domain"
+ module_name: "openstack.cloud.identity_domain"
module_args:
- name: "{{ item.openstack_domain }}"
auth: "{{ openstack_auth }}"
cloud: "{{ openstack_auth_cloud }}"
- endpoint_type: "{{ openstack_interface }}"
- cacert: "{{ openstack_cacert }}"
region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ name: "{{ item.openstack_domain }}"
+ state: present
run_once: true
with_items: "{{ keystone_identity_providers }}"
- name: Register attribute mappings in OpenStack
become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface {{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }} {% endif %}
- mapping create
- --rules "{{ keystone_container_federation_oidc_attribute_mappings_folder }}/{{ item.file | basename }}"
- {{ item.name }}
- run_once: true
- when:
- - item.name not in existing_mappings
- with_items: "{{ keystone_identity_mappings }}"
-
-- name: Update existing attribute mappings in OpenStack
- become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }} {% endif %}
- mapping set
- --rules="{{ keystone_container_federation_oidc_attribute_mappings_folder }}/{{ item.file | basename }}"
- {{ item.name }}
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.federation_mapping
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ name: "{{ item.name }}"
+ rules: "{{ '{{ keystone_identity_mapping_rules | from_json }}' }}"
+ state: present
+ module_extra_vars:
+ keystone_identity_mapping_rules: "{{ lookup('ansible.builtin.file', item.file) }}"
run_once: true
- when:
- - item.name in existing_mappings
with_items: "{{ keystone_identity_mappings }}"
- name: List configured IdPs
become: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }} {% endif %}
- identity provider list -c ID --format value
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.federation_idp_info
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
run_once: true
- changed_when: false
- register: existing_idps_register
-
-- name: Register existing idps
- ansible.builtin.set_fact:
- existing_idps: "{{ existing_idps_register.stdout.split('\n') | map('trim') | list }}"
+ register: existing_idps
- name: Remove unmanaged identity providers
become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }}{% endif %}
- identity provider delete {{ item }}
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.federation_idp
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ name: "{{ item.name }}"
+ state: absent
run_once: true
- with_items: "{{ existing_idps }}"
+ with_items: "{{ existing_idps.identity_providers }}"
when:
- - item not in (keystone_identity_providers | map(attribute='name') | list)
+ - item.name not in (keystone_identity_providers | map(attribute='name') | list)
- keystone_should_remove_identity_providers
-- name: Register Identity Providers in OpenStack
+- name: Gather Identity Domains information
become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }}{% endif %}
- identity provider create
- --description "{{ item.public_name }}"
- --remote-id "{{ item.identifier }}"
- --domain "{{ item.openstack_domain }}"
- {{ item.name }}
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.identity_domain_info
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
run_once: true
- when:
- - item.name not in existing_idps
- with_items: "{{ keystone_identity_providers }}"
+ register: existing_identity_domains
-- name: Update Identity Providers in OpenStack according to Kolla-Ansible configurations
- become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface {{ openstack_interface }}
- --os-system-scope "all"
- --os-user-domain-name {{ default_user_domain_name }}
- --os-region-name {{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert {{ openstack_cacert }} {% endif %}
- identity provider set
- --description "{{ item.public_name }}"
- --remote-id "{{ item.identifier }}"
- "{{ item.name }}"
+- name: Verify that required Identity Domains exist
+ ansible.builtin.fail:
+ msg: >
+ Domain "{{ item.openstack_domain }}" was not found in OpenStack.
+ Please create it first or check your keystone_identity_providers configuration.
run_once: true
- when:
- - item.name in existing_idps
- with_items: "{{ keystone_identity_providers }}"
+ loop: "{{ keystone_identity_providers }}"
+ when: >
+ existing_identity_domains.domains |
+ selectattr('name', 'equalto', item.openstack_domain) |
+ list | length == 0
-- name: Configure attribute mappings for each Identity Provider. (We expect the mappings to be configured by the operator)
+- name: Register Identity Providers in OpenStack
become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }}{% endif %}
- federation protocol create
- --mapping {{ item.attribute_mapping }}
- --identity-provider {{ item.name }}
- {{ item.protocol }}
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.federation_idp
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ description: "{{ item.public_name }}"
+ remote_ids: "{{ '{{ keystone_identity_provider_remote_ids | from_json }}' }}"
+ domain_id: "{{ existing_identity_domains.domains | selectattr('name', 'equalto', item.openstack_domain) | map(attribute='id') | first }}"
+ name: "{{ item.name }}"
+ is_enabled: true
+ state: present
+ module_extra_vars:
+ keystone_identity_provider_remote_ids: "{{ [item.identifier] | to_json }}"
run_once: true
- when:
- - item.name not in existing_idps
with_items: "{{ keystone_identity_providers }}"
-- name: Update attribute mappings for each Identity Provider. (We expect the mappings to be configured by the operator).
+- name: Configure attribute mappings for each Identity Provider. (We expect the mappings to be configured by the operator)
become: true
- changed_when: true
- ansible.builtin.command: >
- {{ kolla_container_engine }} exec -t keystone openstack
- --os-auth-url={{ keystone_internal_url }}
- --os-password={{ keystone_admin_password }}
- --os-username={{ keystone_admin_user }}
- --os-identity-api-version=3
- --os-interface={{ openstack_interface }}
- --os-system-scope="all"
- --os-user-domain-name={{ default_user_domain_name }}
- --os-region-name={{ openstack_region_name }}
- {% if openstack_cacert != '' %}--os-cacert={{ openstack_cacert }}{% endif %}
- federation protocol set
- --identity-provider {{ item.name }}
- --mapping {{ item.attribute_mapping }}
- {{ item.protocol }}
+ kolla_toolbox:
+ container_engine: "{{ kolla_container_engine }}"
+ module_name: openstack.cloud.keystone_federation_protocol
+ module_args:
+ auth: "{{ openstack_auth }}"
+ cloud: "{{ openstack_auth_cloud }}"
+ region_name: "{{ openstack_region_name }}"
+ interface: "{{ openstack_interface }}"
+ ca_cert: "{{ openstack_cacert if openstack_cacert != '' else omit }}"
+ idp: "{{ item.name }}"
+ mapping: "{{ item.attribute_mapping }}"
+ name: "{{ item.protocol }}"
+ state: present
run_once: true
- register: result
- failed_when: result.rc not in [0, 1] # This command returns RC 1 on success, so we need to add this to avoid fails.
- when:
- - item.name in existing_idps
with_items: "{{ keystone_identity_providers }}"
diff --git a/ansible/roles/keystone/templates/keystone-httpd.json.j2 b/ansible/roles/keystone/templates/keystone-httpd.json.j2
index b82376afc..e16cf98be 100644
--- a/ansible/roles/keystone/templates/keystone-httpd.json.j2
+++ b/ansible/roles/keystone/templates/keystone-httpd.json.j2
@@ -36,6 +36,12 @@
"owner": "{{ apache_user }}:{{ apache_user }}",
"perm": "0600",
"merge": true
+ },
+ {
+ "source": "{{ container_config_directory }}/federation/modoidc-error-page.html",
+ "dest": "/var/www/html/modoidc-error-page.html",
+ "owner": "{{ apache_user }}:{{ apache_user }}",
+ "perm": "0600"
}{% endif %}{% if kolla_copy_ca_into_containers | bool %},
{
"source": "{{ container_config_directory }}/ca-certificates",
diff --git a/ansible/roles/keystone/templates/keystone.json.j2 b/ansible/roles/keystone/templates/keystone.json.j2
index dda23b82b..a4cf8b7fa 100644
--- a/ansible/roles/keystone/templates/keystone.json.j2
+++ b/ansible/roles/keystone/templates/keystone.json.j2
@@ -58,7 +58,7 @@
"dest": "/etc/keystone/certs/keystone-key.pem",
"owner": "keystone",
"perm": "0600"
- }{% endif %}{% if keystone_enable_federation_openid | bool %},
+ }{% endif %}{% if keystone_enable_federation_openid | bool and keystone_wsgi_provider == 'apache' %},
{
"source": "{{ container_config_directory }}/federation/oidc/metadata",
"dest": "{{ keystone_container_federation_oidc_metadata_folder }}",
@@ -95,7 +95,7 @@
{
"path": "/var/log/kolla/keystone/keystone.log",
"owner": "keystone:keystone"
- },{% if keystone_enable_federation_openid | bool %}
+ },{% if keystone_enable_federation_openid | bool and keystone_wsgi_provider == 'apache' %}
{
"path": "{{ keystone_container_federation_oidc_metadata_folder }}",
"owner": "{{ apache_user }}:{{ apache_user }}",
diff --git a/doc/source/reference/shared-services/keystone-guide.rst b/doc/source/reference/shared-services/keystone-guide.rst
index 5f5bc60ac..173766022 100644
--- a/doc/source/reference/shared-services/keystone-guide.rst
+++ b/doc/source/reference/shared-services/keystone-guide.rst
@@ -131,6 +131,11 @@ openstack_domain
The OpenStack domain that the Identity Provider belongs.
+.. note::
+ Kolla-Ansible does not support duplicate openstack_domain names,
+ where the ID of the domain is different, but the name is the same.
+ This is an edge case that is hard to take into account.
+
protocol
********
diff --git a/releasenotes/notes/bug-2134455-idp-fixes-aa7064473be5e44d.yaml b/releasenotes/notes/bug-2134455-idp-fixes-aa7064473be5e44d.yaml
new file mode 100644
index 000000000..6c82cc178
--- /dev/null
+++ b/releasenotes/notes/bug-2134455-idp-fixes-aa7064473be5e44d.yaml
@@ -0,0 +1,20 @@
+---
+deprecations:
+ - |
+ Remove option to set OIDCHTMLErrorTemplate in Debian 13 and Rocky 10.
+ As this option was removed in 2.4.14 version of modoidc auth plugin.
+ It is still present in Ubuntu Noble and can be used there. This applies
+ only when using `keystone_wsgi_provider: apache`, which is not the default.
+fixes:
+ - |
+ Fixes federation path for new keystone-httpd container. Previously
+ the normal keystone container running apache wsgi was used. After
+ migration to the uwsgi container the federation logic remained in
+ the keystone-httpd container, but the federation files were never
+ mounted there.
+ `LP#2134455 <https://launchpad.net/bugs/2134455>`__.
+other:
+ - |
+ Add more CI coverage for Federation codepath. Migrate ansible to use
+ `openstack.cloud` ansible collection modules for configuring federation.
+ Previously it was done using python-openstackclient.
diff --git a/roles/kolla-ansible-test-dashboard/tasks/main.yml b/roles/kolla-ansible-test-dashboard/tasks/main.yml
index 02b218212..12950d77c 100644
--- a/roles/kolla-ansible-test-dashboard/tasks/main.yml
+++ b/roles/kolla-ansible-test-dashboard/tasks/main.yml
@@ -88,6 +88,7 @@
- name: Run testinfra tests
environment:
HORIZON_PROTO: "{{ 'https' if tls_enabled | bool else 'http' }}"
+ SCENARIO: "{{ scenario }}"
ansible.builtin.shell:
cmd: >
. {{ kolla_ansible_venv_path }}/bin/activate &&
diff --git a/tests/run.yml b/tests/run.yml
index b7c8fc702..78d34d8eb 100644
--- a/tests/run.yml
+++ b/tests/run.yml
@@ -164,6 +164,8 @@
- neutron
- nova
- bifrost
+ - keystone-httpd/federation/oidc/metadata
+ - keystone-httpd/federation/oidc/attribute_maps
- name: Generate configuration files
template:
@@ -210,6 +212,19 @@
- src: "tests/templates/tenks-deploy-config.yml.j2"
dest: "{{ ansible_env.HOME }}/tenks.yml"
when: "{{ scenario == 'ironic' }}"
+ # keystone-federation config
+ - src: "tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.conf.j2"
+ dest: "/etc/kolla/config/keystone-httpd/federation/oidc/metadata/idp.example.org%2Frealms%2Fexample.conf"
+ when: "{{ scenario == 'federation' }}"
+ - src: "tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.provider.j2"
+ dest: "/etc/kolla/config/keystone-httpd/federation/oidc/metadata/idp.example.org%2Frealms%2Fexample.provider"
+ when: "{{ scenario == 'federation' }}"
+ - src: "tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.client.j2"
+ dest: "/etc/kolla/config/keystone-httpd/federation/oidc/metadata/idp.example.org%2Frealms%2Fexample.client"
+ when: "{{ scenario == 'federation' }}"
+ - src: "tests/templates/keystone-federation/attribute_mapping.json"
+ dest: "/etc/kolla/config/keystone-httpd/federation/oidc/attribute_maps/attribute_mapping.json"
+ when: "{{ scenario == 'federation' }}"
when: item.when | default(true)
- block:
diff --git a/tests/templates/globals-default.j2 b/tests/templates/globals-default.j2
index 80f34b0aa..313e7d5cf 100644
--- a/tests/templates/globals-default.j2
+++ b/tests/templates/globals-default.j2
@@ -282,6 +282,17 @@ enable_valkey: "yes"
{% if scenario == "federation" %}
enable_keystone_federation: true
+keystone_identity_providers:
+ - name: "example_provider"
+ openstack_domain: "Default"
+ protocol: "openid"
+ identifier: "https://idp.example.org/realms/example"
+ public_name: "Example Provider"
+ attribute_mapping: "sso_oidc_mapping"
+ metadata_folder: "/etc/kolla/config/keystone-httpd/federation/oidc/metadata"
+keystone_identity_mappings:
+ - name: "sso_oidc_mapping"
+ file: "/etc/kolla/config/keystone-httpd/federation/oidc/attribute_maps/attribute_mapping.json"
{% endif %}
mariadb_monitor_read_only_interval: "30000"
diff --git a/tests/templates/keystone-federation/attribute_mapping.json b/tests/templates/keystone-federation/attribute_mapping.json
new file mode 100644
index 000000000..022cbafd2
--- /dev/null
+++ b/tests/templates/keystone-federation/attribute_mapping.json
@@ -0,0 +1,42 @@
+[
+ {
+ "local": [
+ {
+ "user": {
+ "name": "{0}",
+ "email": "{1}",
+ "domain": {
+ "name": "{2}"
+ }
+ },
+ "domain": {
+ "name": "{2}"
+ },
+ "projects": [
+ {
+ "name": "{3}",
+ "roles": [
+ {
+ "name": "member"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "remote": [
+ {
+ "type": "OIDC-preferred_username"
+ },
+ {
+ "type": "OIDC-email"
+ },
+ {
+ "type": "OIDC-openstack-user-domain"
+ },
+ {
+ "type": "OIDC-openstack-default-project"
+ }
+ ]
+ }
+]
diff --git a/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.client.j2 b/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.client.j2
new file mode 100644
index 000000000..253898f3a
--- /dev/null
+++ b/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.client.j2
@@ -0,0 +1,4 @@
+{
+ "client_id":"keystone",
+ "client_secret":"example_secret"
+}
diff --git a/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.conf.j2 b/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.conf.j2
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.conf.j2
@@ -0,0 +1 @@
+{}
diff --git a/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.provider.j2 b/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.provider.j2
new file mode 100644
index 000000000..c45eb26f9
--- /dev/null
+++ b/tests/templates/keystone-federation/idp.example.org%2Frealms%2Fexample.provider.j2
@@ -0,0 +1,324 @@
+{
+ "issuer": "https://idp.example.org/realms/example",
+ "authorization_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/auth",
+ "token_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/token",
+ "introspection_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/token/introspect",
+ "userinfo_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/userinfo",
+ "end_session_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/logout",
+ "frontchannel_logout_session_supported": true,
+ "frontchannel_logout_supported": true,
+ "jwks_uri": "https://idp.example.org/realms/example/protocol/openid-connect/certs",
+ "check_session_iframe": "https://idp.example.org/realms/example/protocol/openid-connect/login-status-iframe.html",
+ "grant_types_supported": [
+ "authorization_code",
+ "implicit",
+ "refresh_token",
+ "password",
+ "client_credentials",
+ "urn:openid:params:grant-type:ciba",
+ "urn:ietf:params:oauth:grant-type:device_code"
+ ],
+ "acr_values_supported": [
+ "0",
+ "1"
+ ],
+ "response_types_supported": [
+ "code",
+ "none",
+ "id_token",
+ "token",
+ "id_token token",
+ "code id_token",
+ "code token",
+ "code id_token token"
+ ],
+ "subject_types_supported": [
+ "public",
+ "pairwise"
+ ],
+ "prompt_values_supported": [
+ "none",
+ "login",
+ "consent"
+ ],
+ "id_token_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512"
+ ],
+ "id_token_encryption_alg_values_supported": [
+ "ECDH-ES+A256KW",
+ "ECDH-ES+A192KW",
+ "ECDH-ES+A128KW",
+ "RSA-OAEP",
+ "RSA-OAEP-256",
+ "RSA1_5",
+ "ECDH-ES"
+ ],
+ "id_token_encryption_enc_values_supported": [
+ "A256GCM",
+ "A192GCM",
+ "A128GCM",
+ "A128CBC-HS256",
+ "A192CBC-HS384",
+ "A256CBC-HS512"
+ ],
+ "userinfo_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512",
+ "none"
+ ],
+ "userinfo_encryption_alg_values_supported": [
+ "ECDH-ES+A256KW",
+ "ECDH-ES+A192KW",
+ "ECDH-ES+A128KW",
+ "RSA-OAEP",
+ "RSA-OAEP-256",
+ "RSA1_5",
+ "ECDH-ES"
+ ],
+ "userinfo_encryption_enc_values_supported": [
+ "A256GCM",
+ "A192GCM",
+ "A128GCM",
+ "A128CBC-HS256",
+ "A192CBC-HS384",
+ "A256CBC-HS512"
+ ],
+ "request_object_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512",
+ "none"
+ ],
+ "request_object_encryption_alg_values_supported": [
+ "ECDH-ES+A256KW",
+ "ECDH-ES+A192KW",
+ "ECDH-ES+A128KW",
+ "RSA-OAEP",
+ "RSA-OAEP-256",
+ "RSA1_5",
+ "ECDH-ES"
+ ],
+ "request_object_encryption_enc_values_supported": [
+ "A256GCM",
+ "A192GCM",
+ "A128GCM",
+ "A128CBC-HS256",
+ "A192CBC-HS384",
+ "A256CBC-HS512"
+ ],
+ "response_modes_supported": [
+ "query",
+ "fragment",
+ "form_post",
+ "query.jwt",
+ "fragment.jwt",
+ "form_post.jwt",
+ "jwt"
+ ],
+ "registration_endpoint": "https://idp.example.org/realms/example/clients-registrations/openid-connect",
+ "token_endpoint_auth_methods_supported": [
+ "private_key_jwt",
+ "client_secret_basic",
+ "client_secret_post",
+ "tls_client_auth",
+ "client_secret_jwt"
+ ],
+ "token_endpoint_auth_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512"
+ ],
+ "introspection_endpoint_auth_methods_supported": [
+ "private_key_jwt",
+ "client_secret_basic",
+ "client_secret_post",
+ "tls_client_auth",
+ "client_secret_jwt"
+ ],
+ "introspection_endpoint_auth_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512"
+ ],
+ "authorization_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512"
+ ],
+ "authorization_encryption_alg_values_supported": [
+ "ECDH-ES+A256KW",
+ "ECDH-ES+A192KW",
+ "ECDH-ES+A128KW",
+ "RSA-OAEP",
+ "RSA-OAEP-256",
+ "RSA1_5",
+ "ECDH-ES"
+ ],
+ "authorization_encryption_enc_values_supported": [
+ "A256GCM",
+ "A192GCM",
+ "A128GCM",
+ "A128CBC-HS256",
+ "A192CBC-HS384",
+ "A256CBC-HS512"
+ ],
+ "claims_supported": [
+ "aud",
+ "sub",
+ "iss",
+ "auth_time",
+ "name",
+ "given_name",
+ "family_name",
+ "preferred_username",
+ "email",
+ "acr"
+ ],
+ "claim_types_supported": [
+ "normal"
+ ],
+ "claims_parameter_supported": true,
+ "scopes_supported": [
+ "openid",
+ "roles",
+ "acr",
+ "profile",
+ "address",
+ "basic",
+ "offline_access",
+ "groups",
+ "email",
+ "phone",
+ "service_account",
+ "web-origins",
+ "microprofile-jwt"
+ ],
+ "request_parameter_supported": true,
+ "request_uri_parameter_supported": true,
+ "require_request_uri_registration": true,
+ "code_challenge_methods_supported": [
+ "plain",
+ "S256"
+ ],
+ "tls_client_certificate_bound_access_tokens": true,
+ "revocation_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/revoke",
+ "revocation_endpoint_auth_methods_supported": [
+ "private_key_jwt",
+ "client_secret_basic",
+ "client_secret_post",
+ "tls_client_auth",
+ "client_secret_jwt"
+ ],
+ "revocation_endpoint_auth_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "HS256",
+ "HS512",
+ "ES256",
+ "RS256",
+ "HS384",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512"
+ ],
+ "backchannel_logout_supported": true,
+ "backchannel_logout_session_supported": true,
+ "device_authorization_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/auth/device",
+ "backchannel_token_delivery_modes_supported": [
+ "poll",
+ "ping"
+ ],
+ "backchannel_authentication_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/ext/ciba/auth",
+ "backchannel_authentication_request_signing_alg_values_supported": [
+ "PS384",
+ "RS384",
+ "EdDSA",
+ "ES384",
+ "ES256",
+ "RS256",
+ "ES512",
+ "PS256",
+ "PS512",
+ "RS512"
+ ],
+ "require_pushed_authorization_requests": false,
+ "pushed_authorization_request_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/ext/par/request",
+ "mtls_endpoint_aliases": {
+ "token_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/token",
+ "revocation_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/revoke",
+ "introspection_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/token/introspect",
+ "device_authorization_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/auth/device",
+ "registration_endpoint": "https://idp.example.org/realms/example/clients-registrations/openid-connect",
+ "userinfo_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/userinfo",
+ "pushed_authorization_request_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/ext/par/request",
+ "backchannel_authentication_endpoint": "https://idp.example.org/realms/example/protocol/openid-connect/ext/ciba/auth"
+ },
+ "authorization_response_iss_parameter_supported": true
+}
+
diff --git a/tests/testinfra/test_horizon.py b/tests/testinfra/test_horizon.py
index 9b27e4700..0c1527a9d 100644
--- a/tests/testinfra/test_horizon.py
+++ b/tests/testinfra/test_horizon.py
@@ -12,13 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
+import pytest
import time
import yaml
from pathlib import Path
from selenium.common.exceptions import TimeoutException
+from selenium.common.exceptions import WebDriverException
from selenium import webdriver
from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
home = Path.home()
@@ -122,3 +126,50 @@ def test_horizon_login(host):
raise e
finally:
driver.quit()
+
+
+@pytest.mark.skipif(
+ os.getenv("SCENARIO") != "federation",
+ reason=(
+ "Current scenario is not federation. "
+ "Not running test."
+ )
+)
+def test_horizon_login_federation(host):
+
+ firefox_options = webdriver.FirefoxOptions()
+ selenium_url = host.environment().get(
+ 'SELENIUM_REMOTE_URL', 'http://localhost:4500/wd/hub')
+
+ driver = webdriver.Remote(
+ command_executor=selenium_url,
+ options=firefox_options)
+
+ horizon_proto = host.environment().get('HORIZON_PROTO')
+ horizon_url = horizon_proto + "://192.0.2.10"
+ login_url = '/'.join((
+ horizon_url,
+ 'auth',
+ 'login'))
+
+ try:
+ driver.get(login_url)
+ auth_type_field = driver.find_element(By.ID, 'id_auth_type')
+ select = Select(auth_type_field)
+ select.select_by_value('example_provider_openid')
+ button = driver.find_element(By.CSS_SELECTOR, '.btn-primary')
+
+ try:
+ button.click()
+ except WebDriverException as e:
+ if "Reached error page" in str(e) or "dnsNotFound" in str(e):
+ pass # This is expected behavior, so we ignore the exception
+ else:
+ raise # Re-raise if it failed for an unexpected reason
+
+ assert "idp.example.org" in driver.current_url # nosec B101
+
+ except TimeoutException as e:
+ raise e
+ finally:
+ driver.quit()
diff --git a/zuul.d/scenarios/keystone-federation.yaml b/zuul.d/scenarios/keystone-federation.yaml
index 2f43e15ef..f319880e9 100644
--- a/zuul.d/scenarios/keystone-federation.yaml
+++ b/zuul.d/scenarios/keystone-federation.yaml
@@ -6,6 +6,8 @@
files: !inherit
- ^ansible/group_vars/all/keystone.yml
- ^ansible/roles/keystone/
+ - ^tests/templates/keystone-federation/
+ - ^tests/testinfra/test_horizon.py
vars:
scenario: federation
scenario_images_extra: