From 77536bbf7c60467020cbde7c10d3f1dcb2f44b7c Mon Sep 17 00:00:00 2001 From: Scott Poore Date: Thu, 20 Nov 2025 17:22:00 -0600 Subject: [PATCH 1/4] Tests: Add GDM Smartcard tests --- src/tests/system/tests/test_gdm_smartcard.py | 228 +++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/tests/system/tests/test_gdm_smartcard.py diff --git a/src/tests/system/tests/test_gdm_smartcard.py b/src/tests/system/tests/test_gdm_smartcard.py new file mode 100644 index 0000000000..c1e8e101d7 --- /dev/null +++ b/src/tests/system/tests/test_gdm_smartcard.py @@ -0,0 +1,228 @@ +""" +SSSD Passwordless GDM Smart Card Tests + +:requirement: Passwordless GDM +""" + +from __future__ import annotations + +import time + +import pytest +from sssd_test_framework.roles.client import Client +from sssd_test_framework.roles.generic import GenericProvider +from sssd_test_framework.roles.ipa import IPA +from sssd_test_framework.roles.ldap import LDAP +from sssd_test_framework.topology import KnownTopology + + +def client_setup_for_smartcard(client, provider: IPA | LDAP | GenericProvider): + """ + Setup client for smart card authentication testing. + + This helper function configures the SSSD client with all necessary + settings for smart card authentication against an IPA server, including + certificate enrollment and authselect configuration. + + :param client: Client role object for SSSD configuration + :type client: Client + :param provider: Provider role object to determine some settings + :type provider: IPA | LDAP | GenericProvider + """ + + # Configure SSSD + client.authselect.select("sssd", ["with-mkhomedir", "with-smartcard", "with-switchable-auth"]) + client.sssd.import_domain(provider.domain, provider) + client.sssd.config.remove_section("domain/test") + client.sssd.default_domain = provider.domain + client.sssd.pam["pam_json_services"] = "gdm-switchable-auth" + client.sssd.pam["passkey_child_timeout"] = "30" + + if provider.name.lower() != "ldap": + client.sssd.pam["pam_cert_auth"] = "True" + else: + client.sssd.domain["local_auth_policy"] = "enable:passkey" + + client.sssd.start() + + +def enroll_smartcard(client, provider: IPA, username: str, id: str = "01", init: bool = True): + """ + Enroll smart card with IPA provider + + :param client: Client role object for setting up smart card + :type client: Client + :param provider: Provider role object to request certificate from CA + :type provider: IPA is the only type currently supported + :param username: Username for certificate request + :type username: str + :param id: Smart card certificate ID number + :type id: str + :param init: Determine if the smart card should be initialized + :type init: bool + """ + cert, key, _ = provider.ca.request(username) + cert_content = provider.fs.read(cert) + key_content = provider.fs.read(key) + client.fs.write(f"/opt/test_ca/{username}_{id}.crt", cert_content) + client.fs.write(f"/opt/test_ca/{username}_{id}.key", key_content) + if init: + client.smartcard.initialize_card() + client.smartcard.add_key(f"/opt/test_ca/{username}_{id}.key", key_id=id) + client.smartcard.add_cert(f"/opt/test_ca/{username}_{id}.crt", cert_id=id) + client.svc.restart("virt_cacard.service") + + +@pytest.mark.topology(KnownTopology.BareIPA) +@pytest.mark.builtwith(client="gdm") +def test_gdm__smartcard_login_with_pin(client: Client, ipa: IPA): + """ + :title: Login via GDM using smart card with PIN + :setup: + 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth + 2. Add user to domain + 3. Enroll smart card in domain for user + :steps: + 1. Login through GDM using smart card with PIN + :expectedresults: + 1. Login successful and user sees home screen + :customerscenario: False + """ + testuser = "ipacertuser1" + pin = "123456" + + # Configure SSSD and vfido + client_setup_for_smartcard(client, ipa) + + # Add IPA User + ipa.user(testuser).add() + + # Enroll smartcard with first key/cert issued from IPA + enroll_smartcard(client, ipa, testuser, id="01") + + # Authenticate to GDM with smart card + client.gdm.click_on("listed?") + client.gdm.kb_write(testuser) + client.gdm.kb_send("tab") + client.gdm.click_on("PIN") + client.gdm.kb_write(pin) + assert client.gdm.check_home_screen() + client.gdm.done() + + +@pytest.mark.builtwith(client="gdm") +@pytest.mark.builtwith(client="vfido") +@pytest.mark.topology(KnownTopology.BareIPA) +def test_gdm__smartcard_login_with_incorrect_pin(client: Client, ipa: IPA): + """ + :title: Login via GDM using smart card with incorrect PIN + :setup: + 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth + 2. Add user to domain + 3. Enroll smart card with domain for user + :steps: + 1. Attempt to login through GDM using smart card with incorrect PIN + :expectedresults: + 1. Authentication denied and user prompted to re-enter PIN + :customerscenario: False + """ + + testuser = "ipacertuser1" + pin = "123456" + + # Configure SSSD and vfido + client_setup_for_smartcard(client, ipa) + + # Add IPA User + ipa.user(testuser).add() + + # Enroll smartcard with key/cert issued from IPA + enroll_smartcard(client, ipa, testuser) + + # Authenticate to GDM with smart card + client.gdm.click_on("listed?") + client.gdm.kb_send("tab") + client.gdm.click_on("Username") + client.gdm.kb_write(testuser) + client.gdm.kb_send("tab") + client.gdm.kb_send("enter") + client.gdm.click_on("Smartcard") + + client.gdm.kb_write(pin[:-1]) + time.sleep(5) + assert client.gdm.assert_text("PIN"), "No new PIN prompt! User may have logged in with incorrect PIN!" + client.gdm.done() + + +@pytest.mark.topology(KnownTopology.BareIPA) +@pytest.mark.builtwith(client="gdm") +def test_gdm__smartcard_login_with_certs_and_passkey(client: Client, ipa: IPA): + """ + :title: Login via GDM using smart card and passkey + :setup: + 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth + 2. Start virtual passkey service + 3. Add user to domain and set auth type to passkey and pkinit + 4. Enroll smart card with domain for user + 5. Enroll smart card with second set of Key/Certs + 6. Register passkey with domain user + :steps: + 1. Login through GDM using smart card with PIN + 2. Login through GDM using Passkey with PIN + :expectedresults: + 1. Login successful and user sees home screen + 2. Login successful and user sees home screen + :customerscenario: False + """ + testuser = "ipacertuser1" + pin = "123456" + + # Configure SSSD and vfido + client_setup_for_smartcard(client, ipa) + + # Start virtual passkey service with PIN enabled and set + client.vfido.reset() + client.vfido.pin_set(pin) + client.vfido.pin_enable() + client.vfido.start() + + # Add IPA User + ipa.user(testuser).add() + + # Enroll smartcard with first key/cert issued from IPA + enroll_smartcard(client, ipa, testuser, id="01") + + # Enroll smartcard with second key/cert issued from IPA + enroll_smartcard(client, ipa, testuser, id="02", init=False) + + # Set user_auth_type to passkey + ipa.user(testuser).modify(user_auth_type=["passkey", "pkinit"]) + + # Register passkey with IPA User + ipa.user(testuser).passkey_add_register(client=client, pin=pin, virt_type="vfido") + + # Authenticate to GDM with smart card + client.gdm.click_on("listed?") + client.gdm.kb_write(testuser) + client.gdm.kb_send("tab") + client.gdm.click_on("CAC ID Certificate") + client.gdm.kb_write(pin) + client.gdm.wait_for_login(client) + assert client.gdm.check_home_screen() + client.gdm.done() + + # Authenticate to GDM with passkey + client.gdm.click_on("listed?") + client.gdm.kb_write(testuser) + client.gdm.kb_send("tab") + client.gdm.kb_send("tab") + client.gdm.kb_send("enter") + client.gdm.click_on("Passkey") + + client.gdm.click_on("Security key PIN") + client.gdm.kb_write(pin) + client.gdm.assert_text("Touch security key") + client.vfido.touch() + client.gdm.wait_for_login(client) + assert client.gdm.check_home_screen() + client.gdm.done() From d53f6d99d49b8262b765e8ede99472ed8cd0e990 Mon Sep 17 00:00:00 2001 From: Scott Poore Date: Sat, 13 Dec 2025 16:21:18 -0600 Subject: [PATCH 2/4] Tests: gdm passkey fixes for timing issues in c10s --- src/tests/system/tests/test_gdm_passkey.py | 104 ++++++++++----------- 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/src/tests/system/tests/test_gdm_passkey.py b/src/tests/system/tests/test_gdm_passkey.py index bddace602e..9d79bb3c45 100644 --- a/src/tests/system/tests/test_gdm_passkey.py +++ b/src/tests/system/tests/test_gdm_passkey.py @@ -6,8 +6,6 @@ from __future__ import annotations -import time - import pytest from sssd_test_framework.roles.client import Client from sssd_test_framework.roles.generic import GenericProvider @@ -17,20 +15,29 @@ def client_setup_for_passkey(client, provider: IPA | LDAP | GenericProvider, pin: str | int | None = None): + """ + Setup SSSD and virtual passkey for authentication testing + + :param client: Client role for SSSD and vfido setup + :type client: Client + :param provider: Provider role to determine some settings + :type provider: IPA | LDAP | GenericProvider + :param pin: passkey PIN. If None, disable in vfido, else set PIN + :type pin: str | int | None + """ # Configure SSSD client.authselect.select("sssd", ["with-mkhomedir", "with-smartcard", "with-switchable-auth"]) client.sssd.import_domain(provider.domain, provider) client.sssd.config.remove_section("domain/test") client.sssd.default_domain = provider.domain client.sssd.pam["pam_json_services"] = "gdm-switchable-auth" + client.sssd.pam["passkey_child_timeout"] = "30" if provider.name.lower() != "ldap": client.sssd.pam["pam_cert_auth"] = "True" else: client.sssd.domain["local_auth_policy"] = "enable:passkey" - client.sssd.start() - # Start virtual passkey service client.vfido.reset() if pin is not None: @@ -40,6 +47,8 @@ def client_setup_for_passkey(client, provider: IPA | LDAP | GenericProvider, pin client.vfido.pin_disable() client.vfido.start() + client.sssd.start() + @pytest.mark.builtwith(client=["gdm", "passkey", "vfido"]) @pytest.mark.topology(KnownTopology.BareIPA) @@ -48,10 +57,9 @@ def test_gdm__passkey_login_with_pin(client: Client, ipa: IPA): :title: Login via GDM using passkey with PIN :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to IPA and set auth_type to passkey - 5. Register passkey with IPA user + 2. Start virtual passkey service + 3. Add user to IPA and set auth_type to passkey + 4. Register passkey with IPA user :steps: 1. Login through GDM using Passkey with PIN :expectedresults: @@ -76,6 +84,7 @@ def test_gdm__passkey_login_with_pin(client: Client, ipa: IPA): client.gdm.kb_send("tab") client.gdm.click_on("Security key PIN") client.gdm.kb_write(pin) + client.gdm.assert_text("Touch security key") client.vfido.touch() client.gdm.wait_for_login(client) assert client.gdm.check_home_screen(), "User unable to login or see home screen" @@ -88,11 +97,10 @@ def test_gdm__passkey_login_no_pin(client: Client, ipa: IPA): :title: Login via GDM using passkey with no PIN set :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service with PIN disabled - 4. Add user to IPA and set auth_type to passkey - 5. Allow authentication without PIN for IPA users - 6. Register passkey with IPA user + 2. Start virtual passkey service with PIN disabled + 3. Add user to IPA and set auth_type to passkey + 4. Allow authentication without PIN for IPA users + 5. Register passkey with IPA user :steps: 1. Login through GDM using Passkey without PIN :expectedresults: @@ -119,7 +127,6 @@ def test_gdm__passkey_login_no_pin(client: Client, ipa: IPA): client.gdm.kb_send("tab") client.gdm.click_on("Security key PIN") client.gdm.kb_send("enter") - client.gdm.kb_send("tab") client.gdm.assert_text("Touch security key") client.vfido.touch() client.gdm.wait_for_login(client) @@ -137,10 +144,9 @@ def test_gdm__passkey_login_with_password(client: Client, ipa: IPA): :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to IPA and set auth_type to passkey and password - 6. Register passkey with IPA user + 2. Start virtual passkey service + 3. Add user to IPA and set auth_type to passkey and password + 4. Register passkey with IPA user :steps: 1. Login through GDM using Password :expectedresults: @@ -171,7 +177,6 @@ def test_gdm__passkey_login_with_password(client: Client, ipa: IPA): client.gdm.click_on("Password") client.gdm.kb_write(password) client.gdm.wait_for_login(client) - assert client.gdm.check_home_screen(), "User unable to login or see home screen" @@ -183,11 +188,10 @@ def test_gdm__passkey_login_with_multiple_keys(client: Client, ipa: IPA): :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to IPA and set auth_type to passkey - 5. Register passkey with IPA user - 6. Register another passkey with IPA user on same device + 2. Start virtual passkey service + 3. Add user to IPA and set auth_type to passkey + 4. Register passkey with IPA user + 5. Register another passkey with IPA user on same device :steps: 1. Login through GDM using Passkey with PIN :expectedresults: @@ -203,13 +207,9 @@ def test_gdm__passkey_login_with_multiple_keys(client: Client, ipa: IPA): # Add IPA User ipa.user(testuser).add(user_auth_type="passkey") - time.sleep(1) - # Register passkey with IPA User ipa.user(testuser).passkey_add_register(client=client, pin=pin, virt_type="vfido") - time.sleep(1) - # Register passkey with IPA User again to get second key ipa.user(testuser).passkey_add_register(client=client, pin=pin, virt_type="vfido") @@ -219,6 +219,7 @@ def test_gdm__passkey_login_with_multiple_keys(client: Client, ipa: IPA): client.gdm.kb_send("tab") client.gdm.click_on("Security key PIN") client.gdm.kb_write(pin) + client.gdm.assert_text("Touch security key") client.vfido.touch() client.gdm.wait_for_login(client) assert client.gdm.check_home_screen(), "User unable to login or see home screen" @@ -231,11 +232,10 @@ def test_gdm__passkey_login_remove_passkey_mapping(client: Client, ipa: IPA): :title: Login via GDM fails when passkey mapping removed from user :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to IPA and set auth_type to passkey - 5. Register passkey with IPA user - 6. Remove user passkey mapping from IPA + 2. Start virtual passkey service + 3. Add user to IPA and set auth_type to passkey + 4. Register passkey with IPA user + 5. Remove user passkey mapping from IPA :steps: 1. Login through GDM using Passkey with PIN :expectedresults: @@ -274,12 +274,11 @@ def test_gdm__passkey_login_with_unregistered_mapping(client: Client, ipa: IPA): :title: Login via GDM fails with unregistered passkey mapping :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to IPA and set auth_type to passkey - 5. Register passkey with IPA user - 6. Remove user passkey mapping from IPA - 7. Add bad passkey mapping to user in IPA + 2. Start virtual passkey service + 3. Add user to IPA and set auth_type to passkey + 4. Register passkey with IPA user + 5. Remove user passkey mapping from IPA + 6. Add bad passkey mapping to user in IPA :steps: 1. Login through GDM using Passkey with PIN :expectedresults: @@ -303,8 +302,6 @@ def test_gdm__passkey_login_with_unregistered_mapping(client: Client, ipa: IPA): # Register passkey with IPA User ipa.user(testuser).passkey_add_register(client=client, pin=pin, virt_type="vfido") - pytest.set_trace() - # Remove passkey mapping result = ipa.user(testuser).get(["ipapasskey"]) if result is not None: @@ -321,8 +318,6 @@ def test_gdm__passkey_login_with_unregistered_mapping(client: Client, ipa: IPA): client.gdm.kb_send("tab") client.gdm.click_on("Security key PIN") client.gdm.kb_write(pin) - - client.gdm.kb_send("tab") assert client.gdm.assert_text("Security key PIN"), "User was not prompted again for PIN as expected!" @@ -333,10 +328,9 @@ def test_gdm__passkey_local_with_pin(client: Client, ldap: LDAP): :title: Login via GDM using passkey with PIN with a local setup :setup: 1. Configure SSSD for gdm-switchable-auth and pam_cert_auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to IPA and set auth_type to passkey - 5. Register passkey with IPA user + 2. Start virtual passkey service + 3. Add user to IPA and set auth_type to passkey + 4. Register passkey with IPA user :steps: 1. Login through GDM using Passkey with PIN :expectedresults: @@ -364,9 +358,9 @@ def test_gdm__passkey_local_with_pin(client: Client, ldap: LDAP): client.gdm.kb_send("tab") client.gdm.click_on("Security key PIN") client.gdm.kb_write(pin) + client.gdm.assert_text("Touch security key") client.vfido.touch() client.gdm.wait_for_login(client) - assert client.gdm.check_home_screen(), "User unable to login or see home screen" @@ -378,11 +372,12 @@ def test_gdm__passkey_local_no_pin(client: Client, ldap: LDAP): :title: Login via GDM using passkey with no PIN set with a local setup :setup: 1. Configure SSSD for gdm-switchable-auth - 2. Start SSSD - 3. Start virtual passkey service - 4. Add user to LDAP - 5. Register passkey with sssctl for LDAP user - 6. Add passkey mapping to LDAP user + 2. Configure SSSD passkey user_verification to False + 3. Restart SSSD + 4. Start virtual passkey service + 5. Add user to LDAP + 6. Register passkey with sssctl for LDAP user + 7. Add passkey mapping to LDAP user :steps: 1. Login through GDM using Passkey with PIN :expectedresults: @@ -393,6 +388,8 @@ def test_gdm__passkey_local_no_pin(client: Client, ldap: LDAP): # Configure SSSD and vfido client_setup_for_passkey(client, ldap, pin=None) + client.sssd.sssd["passkey_verification"] = "user_verification=false" + client.sssd.restart() # Add IPA User ldap.user(testuser).add() @@ -409,7 +406,6 @@ def test_gdm__passkey_local_no_pin(client: Client, ldap: LDAP): client.gdm.kb_send("tab") client.gdm.click_on("Security key PIN") client.gdm.kb_send("enter") - client.gdm.kb_send("tab") client.gdm.assert_text("Touch security key") client.vfido.touch() client.gdm.wait_for_login(client) From 3f276c2ef5f01ed35ae24c5a0c5e64106b031676 Mon Sep 17 00:00:00 2001 From: Scott Poore Date: Sat, 13 Dec 2025 16:23:28 -0600 Subject: [PATCH 3/4] Tests: rename and update test_gdm to xidp Renaming test_gdm.py to test_gdm_xidp.py to align with the other test_gdm_* test modules. Also adding authselect for with-switchable-auth which is needed to configure the system for GDM to use the new switchable authentication mechanisms. --- src/tests/system/tests/{test_gdm.py => test_gdm_xidp.py} | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename src/tests/system/tests/{test_gdm.py => test_gdm_xidp.py} (95%) diff --git a/src/tests/system/tests/test_gdm.py b/src/tests/system/tests/test_gdm_xidp.py similarity index 95% rename from src/tests/system/tests/test_gdm.py rename to src/tests/system/tests/test_gdm_xidp.py index bb3dd6479d..f61df4c74d 100644 --- a/src/tests/system/tests/test_gdm.py +++ b/src/tests/system/tests/test_gdm_xidp.py @@ -1,5 +1,5 @@ """ -SSSD Passwordless GDM Tests +SSSD Passwordless GDM External IdP Tests :requirement: Passwordless GDM """ @@ -36,6 +36,7 @@ def test_gdm__xidp_login_rejected_for_invalid_password(client: Client, ipa: IPA, password = "Secret123" testuser_idp = f"{testuser}@{keycloak.host.hostname}" + client.authselect.select("sssd", ["with-switchable-auth"]) client.sssd.import_domain("ipa.test", ipa) client.sssd.pam["pam_json_services"] = "gdm-switchable-auth" client.sssd.start() @@ -73,6 +74,7 @@ def test_gdm__xidp_login_disabled(client: Client, ipa: IPA, keycloak: Keycloak): password = "Secret123" testuser_idp = f"{testuser}@{keycloak.host.hostname}" + client.authselect.select("sssd", ["with-switchable-auth"]) client.sssd.import_domain("ipa.test", ipa) client.sssd.pam["pam_json_services"] = "gdm-switchable-auth" client.sssd.start() @@ -109,6 +111,7 @@ def test_gdm__xidp_login_password_change(client: Client, ipa: IPA, keycloak: Key password = "Secret123" testuser_idp = f"{testuser}@{keycloak.host.hostname}" + client.authselect.select("sssd", ["with-switchable-auth"]) client.sssd.import_domain("ipa.test", ipa) client.sssd.pam["pam_json_services"] = "gdm-switchable-auth" client.sssd.start() @@ -149,6 +152,7 @@ def test_gdm__xidp_login_get_kerberos_ticket(client: Client, ipa: IPA, keycloak: password = "Secret123" testuser_idp = f"{testuser}@{keycloak.host.hostname}" + client.authselect.select("sssd", ["with-switchable-auth"]) client.sssd.import_domain("ipa.test", ipa) client.sssd.pam["pam_json_services"] = "gdm-switchable-auth" client.sssd.start() From f8399a44d4e6d8175bca38384b37785a99ec663f Mon Sep 17 00:00:00 2001 From: Scott Poore Date: Sat, 13 Dec 2025 16:28:24 -0600 Subject: [PATCH 4/4] TEMP: DELETE BEFORE MERGING requirements point to upstream branch for testing --- src/tests/system/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/system/requirements.txt b/src/tests/system/requirements.txt index 788c9285d5..365400981e 100644 --- a/src/tests/system/requirements.txt +++ b/src/tests/system/requirements.txt @@ -5,4 +5,5 @@ git+https://github.com/next-actions/pytest-mh git+https://github.com/next-actions/pytest-ticket git+https://github.com/next-actions/pytest-tier git+https://github.com/next-actions/pytest-output -git+https://github.com/SSSD/sssd-test-framework +#git+https://github.com/SSSD/sssd-test-framework +git+https://github.com/spoore1/sssd-test-framework@gdm_updates