diff --git a/.github/workflows/kubernetes_test.yaml b/.github/workflows/kubernetes_test.yaml index 44048d9c6a..e5a46a7516 100644 --- a/.github/workflows/kubernetes_test.yaml +++ b/.github/workflows/kubernetes_test.yaml @@ -122,8 +122,11 @@ jobs: - name: Get qhub-config.yaml full path run: echo "QHUB_CONFIG_PATH=`realpath ./local-deployment/qhub-config.yaml`" >> "$GITHUB_ENV" - - - name: Sleep to see if it fixes flaky Kubernetes Tests + + - name: Create example-user + run: qhub keycloak --config "${QHUB_CONFIG_PATH}" adduser example-user "${CYPRESS_EXAMPLE_USER_PASSWORD}" + + - name: Sleep to see if it fixes flaky Kubernetes Tests run: sleep 60 - name: Cypress run diff --git a/qhub/cli/__init__.py b/qhub/cli/__init__.py index b6f1517392..9ea84dcbff 100644 --- a/qhub/cli/__init__.py +++ b/qhub/cli/__init__.py @@ -9,6 +9,7 @@ from qhub.cli.validate import create_validate_subcommand from qhub.cli.destroy import create_destroy_subcommand from qhub.cli.upgrade import create_upgrade_subcommand +from qhub.cli.keycloak import create_keycloak_subcommand from qhub.provider.terraform import TerraformException from qhub.version import __version__ from qhub.utils import QHUB_GH_BRANCH @@ -17,7 +18,11 @@ def cli(args): parser = argparse.ArgumentParser(description="QHub command line") parser.add_argument( - "-v", "--version", action="version", version=__version__, help="QHub version" + "-v", + "--version", + action="version", + version=__version__, + help="QHub version number", ) parser.set_defaults(func=None) @@ -28,6 +33,7 @@ def cli(args): create_validate_subcommand(subparser) create_destroy_subcommand(subparser) create_upgrade_subcommand(subparser) + create_keycloak_subcommand(subparser) args = parser.parse_args(args) diff --git a/qhub/cli/keycloak.py b/qhub/cli/keycloak.py new file mode 100644 index 0000000000..60e694860e --- /dev/null +++ b/qhub/cli/keycloak.py @@ -0,0 +1,24 @@ +import pathlib +import logging +from qhub.keycloak import do_keycloak + +logger = logging.getLogger(__name__) + + +def create_keycloak_subcommand(subparser): + subparser = subparser.add_parser("keycloak") + subparser.add_argument("-c", "--config", help="qhub configuration", required=True) + subparser.add_argument( + "keycloak_action", nargs="+", help="adduser [password]" + ) + subparser.set_defaults(func=handle_keycloak) + + +def handle_keycloak(args): + config_filename = pathlib.Path(args.config) + if not config_filename.is_file(): + raise ValueError( + f"passed in configuration filename={config_filename} must exist" + ) + + do_keycloak(config_filename, *args.keycloak_action) diff --git a/qhub/keycloak.py b/qhub/keycloak.py new file mode 100644 index 0000000000..c0cc8a7a10 --- /dev/null +++ b/qhub/keycloak.py @@ -0,0 +1,63 @@ +import logging +import os + +import keycloak + +from .schema import verify +from .utils import load_yaml + +logger = logging.getLogger(__name__) + + +def do_keycloak(config_filename, *args): + + if len(args) < 2: + raise ValueError("keycloak command requires extra inputs") + + if args[0] != "adduser": + raise ValueError( + "Only keycloak command is 'keycloak adduser username [password]'" + ) + + config = load_yaml(config_filename) + + verify(config) + + keycloak_server_url = os.environ.get( + "KEYCLOAK_SERVER_URL", f"https://{config['domain']}/auth/" + ) + + keycloak_username = os.environ.get("KEYCLOAK_ADMIN_USERNAME", "root") + keycloak_password = os.environ.get( + "KEYCLOAK_ADMIN_PASSWORD", + config.get("security", {}).get("keycloak", {}).get("initial_root_password", ""), + ) + + should_verify_tls = config.get("certificate", {}).get("type", "") != "self-signed" + + try: + keycloak_admin = keycloak.KeycloakAdmin( + server_url=keycloak_server_url, + username=keycloak_username, + password=keycloak_password, + realm_name=os.environ.get("KEYCLOAK_REALM", "qhub"), + user_realm_name="master", + auto_refresh_token=("get", "put", "post", "delete"), + verify=should_verify_tls, + ) + except ( + keycloak.exceptions.KeycloakConnectionError, + keycloak.exceptions.KeycloakAuthenticationError, + ) as e: + raise ValueError(f"Failed to connect to Keycloak server: {e}") + + new_user_dict = {"username": args[1], "enabled": True} + if len(args) >= 3: + new_user_dict["credentials"] = [ + {"type": "password", "value": args[2], "temporary": False} + ] + else: + print("Not setting any password (none supplied)") + + print(f"Adding user {args[1]}") + keycloak_admin.create_user(new_user_dict) diff --git a/setup.py b/setup.py index d2ccf35140..c8b7487d88 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ "azure-identity==1.6.1", "azure-mgmt-containerservice==16.2.0", "packaging", + "python-keycloak", ], extras_require={ "dev": [