diff options
| author | Taavi Ansper <taaviansperr@gmail.com> | 2026-02-06 14:07:48 +0200 |
|---|---|---|
| committer | Taavi Ansper <taaviansperr@gmail.com> | 2026-04-15 12:23:10 +0000 |
| commit | 1264c46c67690abb900d74552d100746fcfa921e (patch) | |
| tree | e79c5afa68bd258c6cf163fd7b5b0b6a06a5497c /tests | |
| parent | a80b14c1e9ddc39ee7cf0e6050400d6daf43fe24 (diff) | |
| download | kolla-ansible-1264c46c67690abb900d74552d100746fcfa921e.tar.gz kolla-ansible-1264c46c67690abb900d74552d100746fcfa921e.zip | |
Fix keystone with IDP configured.
1. Add CI coverage for keystone_identity_providers under federation
scenario.
2. Add fix for federation config in keystone role if keystone_identity_providers is
defined.
3. Update Horizon tests with federation example.
4. Use openstack.cloud ansible modules instead of python-openstackclient
in register_identity_providers.yml
Closes-Bug: #2134455
Change-Id: Id03bc5770c3856c7c09a34e327b33786d7b7d859
Signed-off-by: Taavi Ansper <taaviansperr@gmail.com>
Diffstat (limited to 'tests')
7 files changed, 448 insertions, 0 deletions
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() |
