From b979dd91f149fd1f4fc1f48466a26f575eae0ae4 Mon Sep 17 00:00:00 2001 From: Anuja More Date: Wed, 30 Mar 2022 18:27:44 +0530 Subject: [PATCH] ipatests: Add integration tests for External IdP support Tests for [RFE]: Added integration tests for external IdP authentication with keycloak-17 as identity provider. Related : https://pagure.io/freeipa/issue/8805 Related: https://pagure.io/freeipa/issue/8803 Related: https://pagure.io/freeipa/issue/8804 Signed-off-by: Anuja More Reviewed-By: Alexander Bokovoy Reviewed-By: Mohammad Rizwan Yusuf Signed-off-by: Anuja More Reviewed-By: Alexander Bokovoy Reviewed-By: Mohammad Rizwan Yusuf --- .../pytest_ipa/integration/create_quarkus.py | 159 ++++++++++++++++++ ipatests/test_integration/test_idp.py | 116 +++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 ipatests/pytest_ipa/integration/create_quarkus.py create mode 100644 ipatests/test_integration/test_idp.py diff --git a/ipatests/pytest_ipa/integration/create_quarkus.py b/ipatests/pytest_ipa/integration/create_quarkus.py new file mode 100644 index 000000000..3e8301e3a --- /dev/null +++ b/ipatests/pytest_ipa/integration/create_quarkus.py @@ -0,0 +1,159 @@ +import os +import textwrap +import time + +from ipaplatform.paths import paths +from ipatests.pytest_ipa.integration import tasks + + +def setup_keycloakserver(host, version='17.0.0'): + dir = "/opt/keycloak" + password = host.config.admin_password + tasks.install_packages(host, ["unzip", "java-11-openjdk-headless", + "openssl", "maven", "wget", + "firefox", "xorg-x11-server-Xvfb"]) + # add keycloak system user/group and folder + url = "https://github.com/keycloak/keycloak/releases/download/{0}/keycloak-{0}.zip".format(version) # noqa: E501 + host.run_command(["wget", url, "-O", "{0}-{1}.zip".format(dir, version)]) + host.run_command( + ["unzip", "{0}-{1}.zip".format(dir, version), "-d", "/opt/"] + ) + host.run_command(["mv", "{0}-{1}".format(dir, version), dir]) + host.run_command(["groupadd", "keycloak"]) + host.run_command( + ["useradd", "-r", "-g", "keycloak", "-d", dir, "keycloak"] + ) + host.run_command(["chown", "-R", "keycloak:", dir]) + host.run_command(["chmod", "o+x", "{0}/bin/".format(dir)]) + host.run_command(["restorecon", "-R", dir]) + + # setup TLS certificate using IPA CA + host.run_command(["kinit", "-k"]) + host.run_command(["ipa", "service-add", "HTTP/{0}".format(host.hostname)]) + + key = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.key") + crt = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.crt") + keystore = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.store") + + host.run_command(["ipa-getcert", "request", "-K", + "HTTP/{0}".format(host.hostname), + "-D", host.hostname, "-o", "keycloak", + "-O", "keycloak", "-m", "0600", + "-M", "0644", + "-k", key, "-f", crt, "-w"]) + host.run_command(["keytool", "-import", "-keystore", keystore, + "-file", "/etc/ipa/ca.crt", + "-alias", "ipa_ca", + "-trustcacerts", "-storepass", password, "-noprompt"]) + host.run_command(["chown", "keycloak:keycloak", keystore]) + + # Setup keycloak service and config files + contents = textwrap.dedent(""" + KEYCLOAK_ADMIN=admin + KEYCLOAK_ADMIN_PASSWORD={admin_pswd} + KC_HOSTNAME={host}:8443 + KC_HTTPS_CERTIFICATE_FILE={crt} + KC_HTTPS_CERTIFICATE_KEY_FILE={key} + KC_HTTPS_TRUST_STORE_FILE={store} + KC_HTTPS_TRUST_STORE_PASSWORD={store_pswd} + KC_HTTP_RELATIVE_PATH=/auth + """).format(admin_pswd=password, host=host.hostname, crt=crt, key=key, + store=keystore, store_pswd=password) + host.put_file_contents("/etc/sysconfig/keycloak", contents) + + contents = textwrap.dedent(""" + [Unit] + Description=Keycloak Server + After=network.target + + [Service] + Type=idle + EnvironmentFile=/etc/sysconfig/keycloak + + User=keycloak + Group=keycloak + ExecStart=/opt/keycloak/bin/kc.sh start + TimeoutStartSec=600 + TimeoutStopSec=600 + + [Install] + WantedBy=multi-user.target + """) + host.put_file_contents("/etc/systemd/system/keycloak.service", contents) + host.run_command(["systemctl", "daemon-reload"]) + + # Run build stage first + env_vars = textwrap.dedent(""" + export KEYCLOAK_ADMIN=admin + export KC_HOSTNAME={hostname}:8443 + export KC_HTTPS_CERTIFICATE_FILE=/etc/pki/tls/certs/keycloak.crt + export KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/pki/tls/private/keycloak.key + export KC_HTTPS_TRUST_STORE_FILE=/etc/pki/tls/private/keycloak.store + export KC_HTTPS_TRUST_STORE_PASSWORD={STORE_PASS} + export KEYCLOAK_ADMIN_PASSWORD={ADMIN_PASS} + export KC_HTTP_RELATIVE_PATH=/auth + """).format(hostname=host.hostname, STORE_PASS=password, + ADMIN_PASS=password) + + content = host.get_file_contents('/etc/bashrc', + encoding='utf-8') + new_content = content + "\n{}".format(env_vars) + host.put_file_contents('/etc/bashrc', new_content) + host.run_command(['bash']) + host.run_command( + ['su', '-', 'keycloak', '-c', '/opt/keycloak/bin/kc.sh build']) + host.run_command(["systemctl", "start", "keycloak"]) + host.run_command(["/opt/keycloak/bin/kc.sh", "show-config"]) + + # Setup keycloak for use: + kcadmin_sh = "/opt/keycloak/bin/kcadm.sh" + + host.run_command([kcadmin_sh, "config", "truststore", + "--trustpass", password, keystore]) + kcadmin = [kcadmin_sh, "config", "credentials", "--server", + "https://{0}:8443/auth/".format(host.hostname), + "--realm", "master", "--user", "admin", + "--password", password + ] + tasks.run_repeatedly( + host, kcadmin, timeout=60) + host.run_command( + [kcadmin_sh, "create", "users", "-r", "master", + "-s", "username=testuser1", "-s", "enabled=true", + "-s", "email=testuser1@ipa.test"] + ) + host.run_command( + [kcadmin_sh, "set-password", "-r", "master", + "--username", "testuser1", "--new-password", password] + ) + + +def setup_keycloak_client(host): + password = host.config.admin_password + host.run_command(["/opt/keycloak/bin/kcreg.sh", + "config", "credentials", "--server", + "https://{0}:8443/auth/".format(host.hostname), + "--realm", "master", "--user", "admin", + "--password", password] + ) + + client_json = textwrap.dedent(""" + {{ + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "https://ipa-ca.{redirect}/ipa/idp/*" ], + "webOrigins" : [ "https://ipa-ca.{web}" ], + "protocol" : "openid-connect", + "attributes" : {{ + "oauth2.device.authorization.grant.enabled" : "true", + "oauth2.device.polling.interval": "5" + }} + }} + """).format(redirect=host.domain.name, web=host.domain.name) + host.put_file_contents("/tmp/ipa_client.json", client_json) + host.run_command(["/opt/keycloak/bin/kcreg.sh", "create", + "-f", "/tmp/ipa_client.json", + "-s", "clientId=ipa_oidc_client", + "-s", "secret={0}".format(password)] + ) + time.sleep(60) diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/test_idp.py new file mode 100644 index 000000000..8f9e92e6a --- /dev/null +++ b/ipatests/test_integration/test_idp.py @@ -0,0 +1,116 @@ +from __future__ import absolute_import + +import time + +import textwrap +from ipaplatform.paths import paths +from ipatests.test_integration.base import IntegrationTest +from ipatests.pytest_ipa.integration import tasks, create_quarkus + +user_code_script = textwrap.dedent(""" +from selenium import webdriver +from datetime import datetime +from selenium.webdriver.firefox.options import Options +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +options = Options() +options.headless = True +driver = webdriver.Firefox(executable_path="/opt/geckodriver", options=options) +verification_uri = "{uri}" +driver.get(verification_uri) +try: + element = WebDriverWait(driver, 90).until( + EC.presence_of_element_located((By.ID, "username"))) + driver.find_element_by_id("username").send_keys("testuser1") + driver.find_element_by_id("password").send_keys("{passwd}") + driver.find_element_by_id("kc-login").click() + element = WebDriverWait(driver, 90).until( + EC.presence_of_element_located((By.ID, "kc-login"))) + driver.find_element_by_id("kc-login").click() + assert "Device Login Successful" in driver.page_source +finally: + now = datetime.now().strftime("%M-%S") + driver.get_screenshot_as_file("/var/log/httpd/screenshot-%s.png" % now) + driver.quit() +""") + + +def add_user_code(host, verification_uri): + contents = user_code_script.format(uri=verification_uri, + passwd=host.config.admin_password) + host.put_file_contents("/tmp/add_user_code.py", contents) + tasks.run_repeatedly( + host, ['python3', '/tmp/add_user_code.py']) + + +def get_verification_uri(host, since, keycloak_server_name): + command = textwrap.dedent(""" + journalctl -u ipa-otpd\\* --since="%s" | grep "user_code:" | awk '{ print substr($7,2,9) }'""" % since) # noqa: E501 + user_code = host.run_command(command).stdout_text.rstrip("\r\n") + uri = ("https://{0}:8443/auth/realms/master/device?user_code={1}".format( + keycloak_server_name, user_code)) + return uri + + +def kinit_idp(host, user, keycloak_server): + ARMOR = "/tmp/armor" + tasks.kdestroy_all(host) + # create armor for FAST + host.run_command(["kinit", "-n", "-c", ARMOR]) + since = time.strftime('%Y-%m-%d %H:%M:%S') + cmd = ["kinit", "-T", ARMOR, user] + with host.spawn_expect(cmd, default_timeout=100) as e: + e.expect('Authenticate at .+: ') + uri = get_verification_uri(host, since, keycloak_server.hostname) + if uri: + add_user_code(keycloak_server, uri) + e.sendline('\n') + e.expect_exit() + + test_idp = host.run_command(["klist", "-C"]) + assert "152" in test_idp.stdout_text + + +class TestIDPKeycloak(IntegrationTest): + + num_replicas = 1 + topology = 'line' + + @classmethod + def install(cls, mh): + tasks.install_master(cls.master, setup_dns=True) + tasks.install_client(cls.master, cls.replicas[0]) + content = cls.master.get_file_contents(paths.IPA_DEFAULT_CONF, + encoding='utf-8') + new_content = content + "\noidc_child_debug_level = 10" + cls.master.put_file_contents(paths.IPA_DEFAULT_CONF, new_content) + with tasks.remote_sssd_config(cls.master) as sssd_config: + sssd_config.edit_domain( + cls.master.domain, 'krb5_auth_timeout', 1100) + tasks.clear_sssd_cache(cls.master) + tasks.kinit_admin(cls.master) + cls.master.run_command(["ipa", "config-mod", "--user-auth-type=idp", + "--user-auth-type=password"]) + xvfb = ("nohup /usr/bin/Xvfb :99 -ac -noreset -screen 0 1400x1200x8 " + "/dev/null &") + cls.replicas[0].run_command(xvfb) + + def test_auth_keycloak_idp(self): + keycloak_srv = self.replicas[0] + create_quarkus.setup_keycloakserver(keycloak_srv) + time.sleep(60) + create_quarkus.setup_keycloak_client(keycloak_srv) + tasks.kinit_admin(self.master) + cmd = ["ipa", "idp-add", "keycloak", "--provider=keycloak", + "--client-id=ipa_oidc_client", "--org=master", + "--base-url={0}:8443/auth".format(keycloak_srv.hostname)] + self.master.run_command(cmd, stdin_text="{0}\n{0}".format( + keycloak_srv.config.admin_password)) + tasks.user_add(self.master, 'keycloakuser', + extra_args=["--user-auth-type=idp", + "--idp-user-id=testuser1@ipa.test", + "--idp=keycloak"] + ) + tasks.clear_sssd_cache(self.master) + kinit_idp(self.master, 'keycloakuser', keycloak_srv)