diff options
| author | Zuul <zuul@review.opendev.org> | 2026-04-15 21:41:19 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2026-04-15 21:41:19 +0000 |
| commit | 88aecb6009d2ef6e543849cd9930d1bd80cfca2f (patch) | |
| tree | f5046b005ae3bdd767049e37005ce5e4bfb9ee39 | |
| parent | fb5f1b5c8be8cad9f8e165f9cbb83a3b7ed177a0 (diff) | |
| parent | 1264c46c67690abb900d74552d100746fcfa921e (diff) | |
| download | kolla-ansible-88aecb6009d2ef6e543849cd9930d1bd80cfca2f.tar.gz kolla-ansible-88aecb6009d2ef6e543849cd9930d1bd80cfca2f.zip | |
Merge "Fix keystone with IDP configured."
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: |
