diff --git a/integration_tests.py b/integration_tests.py index bcd0e2351ae..1aed8dae73f 100755 --- a/integration_tests.py +++ b/integration_tests.py @@ -25,7 +25,7 @@ DEFAULT_HOST_OS = "el9" DEFAULT_MYSQL_VER = "mysql:8.0.40" DEFAULT_ES_VER = "opensearchproject/opensearch:2.18.0" -DEFAULT_IAM_VER = "indigoiam/iam-login-service:v1.8.0" +DEFAULT_IAM_VER = "indigoiam/iam-login-service:v1.10.2" FEATURE_VARIABLES = { "DIRACOSVER": "master", "DIRACOS_TARBALL_PATH": None, @@ -47,7 +47,7 @@ DB_HOST = "mysql" DB_PORT = "3306" -IAM_INIT_CLIENT_ID = "password-grant" +IAM_INIT_CLIENT_ID = "admin-client-rw" IAM_INIT_CLIENT_SECRET = "secret" IAM_SIMPLE_CLIENT_NAME = "simple-client" IAM_SIMPLE_USER = "jane_doe" @@ -217,7 +217,7 @@ def prepare_environment( f"No value passed for --[no-]editable, automatically detected: {editable}", fg=c.YELLOW, ) - typer.echo(f"Preparing environment") + typer.echo("Preparing environment") modules = DEFAULT_MODULES | dict(f.split("=", 1) for f in extra_module) modules = {k: Path(v).absolute() for k, v in modules.items()} @@ -711,7 +711,7 @@ def _check_containers_running(*, is_up=True): if is_up: if not any(running_containers): typer.secho( - f"No running containers found, environment must be prepared first!", + "No running containers found, environment must be prepared first!", err=True, fg=c.RED, ) @@ -719,7 +719,7 @@ def _check_containers_running(*, is_up=True): else: if any(running_containers): typer.secho( - f"Running instance already found, it must be destroyed first!", + "Running instance already found, it must be destroyed first!", err=True, fg=c.RED, ) @@ -827,9 +827,7 @@ def _prepare_iam_instance(): # It sometimes takes a while for IAM to be ready so wait for a while if needed for _ in range(10): try: - tokens = _get_iam_token( - issuer, IAM_ADMIN_USER, IAM_ADMIN_PASSWORD, IAM_INIT_CLIENT_ID, IAM_INIT_CLIENT_SECRET - ) + tokens = _get_iam_token(issuer, IAM_INIT_CLIENT_ID, IAM_INIT_CLIENT_SECRET) break except typer.Exit: typer.secho("Failed to connect to IAM, will retry in 10 seconds", fg=c.YELLOW) @@ -837,19 +835,33 @@ def _prepare_iam_instance(): else: raise RuntimeError("All attempts to _get_iam_token failed") + initial_admin_access_token = tokens.get("access_token") + + # Update the configuration of the initial IAM client adding the necessary scopes + _update_init_iam_client( + issuer, + initial_admin_access_token, + IAM_INIT_CLIENT_ID, + "Admin client (read-write)", + " ".join(["scim:read", "scim:write", "iam:admin.read", "iam:admin.write"]), + ["client_credentials"], + ) + # Fetch a new token with the updated client + tokens = _get_iam_token(issuer, IAM_INIT_CLIENT_ID, IAM_INIT_CLIENT_SECRET) admin_access_token = tokens.get("access_token") typer.secho("Creating IAM clients", fg=c.GREEN) - user_client_config = _create_iam_client( + _create_iam_client( issuer, admin_access_token, IAM_SIMPLE_CLIENT_NAME, + grant_types=["password", "client_credentials"], ) - admin_client_config = _create_iam_client( + _create_iam_client( issuer, admin_access_token, IAM_ADMIN_CLIENT_NAME, - grant_types=["urn:ietf:params:oauth:grant-type:token-exchange"], + grant_types=["password", "urn:ietf:params:oauth:grant-type:token-exchange"], ) typer.secho("Creating IAM users", fg=c.GREEN) @@ -858,7 +870,7 @@ def _prepare_iam_instance(): typer.secho("Creating IAM groups", fg=c.GREEN) dirac_group_config = _create_iam_group(issuer, admin_access_token, "dirac") dirac_group_id = dirac_group_config["id"] - dirac_admin_group_config = _create_iam_subgroup(issuer, admin_access_token, "dirac", dirac_group_id, "admin") + _create_iam_subgroup(issuer, admin_access_token, "dirac", dirac_group_id, "admin") dirac_prod_group_config = _create_iam_subgroup(issuer, admin_access_token, "dirac", dirac_group_id, "prod") dirac_user_group_config = _create_iam_subgroup(issuer, admin_access_token, "dirac", dirac_group_id, "user") @@ -889,7 +901,7 @@ def _iam_curl( return subprocess.run(cmd, capture_output=True, check=False) -def _get_iam_token(issuer: str, user: str, password: str, client_id: str, client_secret: str) -> dict: +def _get_iam_token(issuer: str, client_id: str, client_secret: str) -> dict: """Get a token using the password flow :param str issuer: url of the issuer @@ -904,11 +916,63 @@ def _get_iam_token(issuer: str, user: str, password: str, client_id: str, client ret = _iam_curl( url, user=f"{client_id}:{client_secret}", - data=[f"grant_type=password", f"username={user}", f"password={password}"], + data=["grant_type=client_credentials"], ) if not ret.returncode == 0: - typer.secho(f"Failed to get an admin token: {ret.returncode} {ret.stderr}", err=True, fg=c.RED) + typer.secho(f"Failed to get an admin token: {ret.returncode} {ret.stdout} {ret.stderr}", err=True, fg=c.RED) + raise typer.Exit(code=1) + + return json.loads(ret.stdout) + + +def _update_init_iam_client( + issuer: str, admin_access_token: str, client_id: str, client_name: str, scope: str, grant_types: list[str] +) -> dict: + """Update the configuration of the initial IAM client adding the necessary scopes + + :param str issuer: url of the issuer + :param str admin_access_token: access token to register a client + :param str client_id: id of the client + """ + # Get the configuration of the client + url = os.path.join(issuer, "iam/api/clients", client_id) + ret = _iam_curl( + url, + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/json"], + ) + + if not ret.returncode == 0: + typer.secho( + f"Failed to get config for client {client_id}: {ret.returncode} {ret.stdout} {ret.stderr}", + err=True, + fg=c.RED, + ) + raise typer.Exit(code=1) + + # Update the configuration with the provided values + client_config = json.loads(ret.stdout) + client_config["client_name"] = client_name + client_config["scope"] = scope + client_config["grant_types"] = grant_types + client_config["redirect_uris"] = [] + client_config["code_challenge_method"] = "S256" + + # Update the client + url = os.path.join(issuer, "iam/api/clients", client_id) + ret = _iam_curl( + url, + verb="PUT", + data=[json.dumps(client_config)], + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/json"], + ) + + if not ret.returncode == 0: + typer.secho( + f"Failed to update config for client {client_id}: {ret.returncode} {ret.stdout} {ret.stderr}", + err=True, + fg=c.RED, + ) raise typer.Exit(code=1) return json.loads(ret.stdout) @@ -927,7 +991,16 @@ def _create_iam_client( """ scope = "openid profile offline_access " + scope - default_grant_types = ["refresh_token", "password"] + default_grant_types = ["refresh_token"] + + # Some grant types are privileged and cannot be added at the creation of the client, but can be added later + privileged_grant_types = ["password", "urn:ietf:params:oauth:grant-type:token-exchange", "client_credentials"] + requested_privileged_grant_types = [] + for grant_type in privileged_grant_types: + if grant_type in grant_types: + grant_types.remove(grant_type) + requested_privileged_grant_types.append(grant_type) + grant_types = list(set(default_grant_types + grant_types)) client_config = { @@ -942,7 +1015,7 @@ def _create_iam_client( ret = _iam_curl( url, verb="POST", - headers=[f"Authorization: Bearer {admin_access_token}", f"Content-Type: application/json"], + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/json"], data=[json.dumps(client_config)], ) @@ -950,30 +1023,18 @@ def _create_iam_client( typer.secho(f"Failed to create client {client_name}: {ret.returncode} {ret.stderr}", err=True, fg=c.RED) raise typer.Exit(code=1) - # FIX TO REMOVE WITH IAM:v1.8.2 - # ----------------------------- - # Because of an issue in IAM, a client dynamically registered using the password flow - # will provide invalid refresh token: https://github.com/indigo-iam/iam/issues/575 - # To cope with this problem, we have to update the client with the following params client_config = json.loads(ret.stdout) - client_config["grant_types"].append("client_credentials") - client_config["refresh_token_validity_seconds"] = 3600 - client_config["access_token_validity_seconds"] = 3600 - - url = os.path.join(issuer, "iam/api/clients", client_config["client_id"]) - ret = _iam_curl( - url, - verb="PUT", - headers=[f"Authorization: Bearer {admin_access_token}", f"Content-Type: application/json"], - data=[json.dumps(client_config)], + client_id = client_config["client_id"] + ret = _update_init_iam_client( + issuer, + admin_access_token, + client_id, + client_name, + scope, + list(set(requested_privileged_grant_types + grant_types)), ) - - if not ret.returncode == 0: - typer.secho(f"Failed to update client {client_name}: {ret.returncode} {ret.stderr}", err=True, fg=c.RED) - raise typer.Exit(code=1) - # ----------------------------- - - return json.loads(ret.stdout) + print(ret) + return ret def _create_iam_user(issuer: str, admin_access_token: str, username: str, password: str) -> dict: @@ -1009,7 +1070,7 @@ def _create_iam_user(issuer: str, admin_access_token: str, username: str, passwo ret = _iam_curl( url, verb="POST", - headers=[f"Authorization: Bearer {admin_access_token}", f"Content-Type: application/scim+json"], + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/scim+json"], data=[json.dumps(user_config)], ) @@ -1036,7 +1097,7 @@ def _create_iam_group(issuer: str, admin_access_token: str, group_name: str) -> ret = _iam_curl( url, verb="POST", - headers=[f"Authorization: Bearer {admin_access_token}", f"Content-Type: application/scim+json"], + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/scim+json"], data=[json.dumps(group_config)], ) @@ -1073,7 +1134,7 @@ def _create_iam_subgroup( ret = _iam_curl( url, verb="POST", - headers=[f"Authorization: Bearer {admin_access_token}", f"Content-Type: application/scim+json"], + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/scim+json"], data=[json.dumps(subgroup_config)], ) @@ -1116,7 +1177,7 @@ def _create_iam_group_membership( ret = _iam_curl( url, verb="PATCH", - headers=[f"Authorization: Bearer {admin_access_token}", f"Content-Type: application/scim+json"], + headers=[f"Authorization: Bearer {admin_access_token}", "Content-Type: application/scim+json"], data=[json.dumps(membership_config)], ) diff --git a/tests/CI/docker-compose.yml b/tests/CI/docker-compose.yml index 69a5d37cab0..f4e8b93bbdc 100644 --- a/tests/CI/docker-compose.yml +++ b/tests/CI/docker-compose.yml @@ -3,6 +3,8 @@ volumes: diracx-cs-store: # Volume used to store the pair of keys to sign the tokens diracx-key-store: + # Volume used to store the jwks of the IAM service + diracx-iam-key-store: services: mysql: @@ -41,6 +43,10 @@ services: ports: - 8080:8080 env_file: "${IAM_VER}.env" + volumes: + - diracx-iam-key-store:/etc/indigo-iam/keystore + depends_on: + - iam-init-jwks healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/.well-known/openid-configuration"] interval: 5s @@ -49,6 +55,30 @@ services: start_period: 60s pull_policy: always + iam-init-jwks: + image: alpine:latest + container_name: init-jwks + volumes: + - diracx-iam-key-store:/jwks + command: > + sh -c 'mkdir -p /jwks && echo "{ + \"keys\": [ + { + \"p\": \"1vffpIvQ67Bp1XmnxuuNhgHGoS4iCEbEJN9kV2oh39xRMw2L1Fx6RrgHb0t04KAE4IT_48Y9grta7OHUty4dMQ\", + \"kty\": \"RSA\", + \"q\": \"v673PmzSoiClcZ6U8Rcb4GyB1H76jfY3dTdZNBT5cSVEPhPCnGNWXFKPUj5qeT4CGneR9tdGU7U-_vRNPJg9yw\", + \"d\": \"XC1QH6W--Hh9fIsswXB2H0S44GvbrVD75XiJwrOgmrOhBK8MFR0X_eQ-9nBNPmZbAu9NKK5ixwIcE8J-OhQaOcDkepAf1DUo6iIlXgtbHvOtT3GHNgPHJ4C7XbnO9ieNDMrMr2tpmGnH2sebvXwLrzjKJCB09bS6yj71XGkyVKE\", + \"e\": \"AQAB\", + \"kid\": \"rsa1\", + \"qi\": \"P8KH-16jsDjJygzggeLxlJwHYFYPoie3hgB__aajO03GiRzYJojD5dBKEiQuo9SxJ43U5csHWYQeukz9X01-zw\", + \"dp\": \"VYF6_6RtkZI2RqeBSOpg_LCwJWSIPOqJEnGZI_wfRUAJPFljCTFPodmJe4d0EfUUe4nrjtpHlTyYyih5x_MbwQ\", + \"dq\": \"sxzUTZG0dOjaj8PmWy4Dz361BpIsoDC9e5tfkGo0-AQhs3wVcrrkPNqsr-ZA6dAGeSLX0vcv8RJArk4sSf3cZw\", + \"n\": \"oPXb81pZRmxmRJVHva49e5-NOToDdZ6XITpqt3RF-Ovehkd52Fm-t0FfKjJZxP7Q4d-nw1gk-r894uRJPAU9mx3yya9p7L5Xnr6rs8jmf_KF2buaYMUQ001wpsjJwznyGHWNqrBNB4_2-3U_uMGWyJB-C8Gy2-3aXjHRSQ-d0ts\" + } + ] + }" > /jwks/iam-keystore.jwks' + pull_policy: always + # Mock of an S3 storage s3-direct: image: adobe/s3mock diff --git a/tests/CI/envs/indigoiam/iam-login-service:v1.8.0.env b/tests/CI/envs/indigoiam/iam-login-service:v1.10.2.env similarity index 82% rename from tests/CI/envs/indigoiam/iam-login-service:v1.8.0.env rename to tests/CI/envs/indigoiam/iam-login-service:v1.10.2.env index 2d43c164414..70f5ae5faec 100644 --- a/tests/CI/envs/indigoiam/iam-login-service:v1.8.0.env +++ b/tests/CI/envs/indigoiam/iam-login-service:v1.10.2.env @@ -10,3 +10,5 @@ IAM_BASE_URL=http://${IAM_HOST}:8080 # The OpenID Connect issuer configured for this IAM instance. # This must be equal to IAM_BASE_URL IAM_ISSUER=http://${IAM_HOST}:8080 + +IAM_KEY_STORE_LOCATION="file:///etc/indigo-iam/keystore/iam-keystore.jwks" diff --git a/tests/Integration/Resources/IdProvider/Test_IdProvider.py b/tests/Integration/Resources/IdProvider/Test_IdProvider.py index 581c35d19a1..7233881770c 100644 --- a/tests/Integration/Resources/IdProvider/Test_IdProvider.py +++ b/tests/Integration/Resources/IdProvider/Test_IdProvider.py @@ -71,7 +71,7 @@ # Default parameters of the IAM container with old client credentials expiredParams = { "issuer": issuer, - "client_id": "5f70a267-636a-430a-81d5-f885cab1c208", + "client_id": "0544b93d-790e-43ae-81ae-cefb093b5411", "client_secret": "OuHPKoix1cZI-YylbTcc2tUJENlgn5nLINJ86RWOzqQykYf9zCCOqLBLogYdljCoITQ2AwNfGwfN3VjItk-UKg", "scope": "openid+profile+offline_access", } @@ -93,12 +93,12 @@ # Valid user tokens but the access token is expired expiredValidUserToken = { - "access_token": "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI0ZjQ3M2RmNy05OGI2LTRlZTAtYTY2Yy02MDYwYTcwOWJlYjUiLCJpc3MiOiJodHRwOlwvXC9pYW0tbG9naW4tc2VydmljZTo4MDgwIiwiZXhwIjoxNjc3ODQ1NDU1LCJpYXQiOjE2Nzc4NDE4NTUsImp0aSI6IjlhN2E1NTY0LTk3OTktNDcxYi05M2FmLTNkYzAyZmFjZWY5OCIsImNsaWVudF9pZCI6ImNiMzM2OWVjLTIxMTUtNDEwZi05OGYxLWExODQ4MTAwMzE5MCJ9.kw_g76eYh7Ay5a5EhR_OVOQR3yjgXxkqF0oaIv-udZaIzA2MHdhXodB9BDUZaZOW3-FgnG9ZOfSNp5Dl6VWNGpO77M6DkImNBWkCpvDeHfl1giq8xUQ_bDO9isZQkpoEVZgxyBK1J2lmr-Z1Ef0ZR_kTb_UsRE0Eze_y_W20jS_TlHVULFd7j4UPspgOzFGAlVFfAfKhWhJVI76XxqCugeMhEYs43qQa4-cIBgLOB6KBddk1tq7-Rqw6EfgtYDb5_zChizX_4dfE10WKyDI50CU-OtMT88S7IHRPWGjoZ7Z3RaNdp6gHXDY67VHzKzouYqKAiIGyIqkPionmZaguow", + "access_token": "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJlYTEyYWI2MS0yYWE2LTQ2NmYtOWQ2OC01YTAxODRlODI1NmIiLCJpc3MiOiJodHRwOlwvXC9pYW0tbG9naW4tc2VydmljZTo4MDgwIiwiZXhwIjoxNzMyODExNzg1LCJpYXQiOjE3MzI4MDgxODUsImp0aSI6IjQwZmVkM2ZlLTRlYTQtNGNkOC1iYjA5LWE2NjgxYjFhMzFmMyIsImNsaWVudF9pZCI6IjIwMTI4MTQ3LWU3ODEtNDY0Zi1hYjFmLTg4Y2JhODlhYTMwYSJ9.RjrUNrLqN0Ch-HHE1v2qBctV4qef_byURPttY7_QXxO-r0s_oAeXu2TQzq_IrkpUMo3_O10WgdrEb-LpqDXzEdpUWSdd6lWHl4bAFXw-otWaFgYjEiGinUx4bzmOYKI73xPDYPE_3KKMp8nM7OkTs9EC2yVQsp7rGEixlh1g6kU", "token_type": "Bearer", "refresh_token": None, "expires_in": 3599, "scope": "address phone openid email profile offline_access", - "id_token": "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI2OTI3ODJiYS01Y2ZlLTQxMzktYTU5Zi1iYWNiMDY3YTBmZWYiLCJraWQiOiJyc2ExIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcLyIsImdyb3VwcyI6WyJkaXJhY1wvdXNlciIsImRpcmFjXC9wcm9kIiwiZGlyYWMiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamFuZV9kb2UiLCJvcmdhbmlzYXRpb25fbmFtZSI6ImluZGlnby1kYyIsImF1ZCI6ImM0MWE0Yjg3LTAxNzAtNGQ5MC1iZWU2LTNlM2E2OGQyZTY0NiIsIm5hbWUiOiJKYW5lIERvZSIsImV4cCI6MTY3NzA4MTk4NywiaWF0IjoxNjc3MDgxMzg3LCJqdGkiOiIzZjQ2ZmVhMS0yMTI5LTRkZGUtODgwNS1kMTZhNTA1MGIwNDYiLCJlbWFpbCI6ImphbmUuZG9lQGRvbm90ZXhpc3QuZW1haWwifQ.rSWiaUDp7oUP1lSOleJ7ANbKfQBVVr3KHXFR3-HKLmwakDUdKDz_6My0tyzs88RU4de2v9RMGHzCl5rgi2MFY_0DDiB_SOngafwAkXIik_vG48cg7wfGSX1IttgN4IriYKyCbLFwIo89_zPNdMfjYP-xHFYefKyzVyGDsRlGwy50bUBgIBdXF1oN49xw2B_LWlg0YZAszvaCaul2dTfQmBoYoBCI88t7uKbZAYV-A4TGOnG_PutfMbFDiYljyq37ouHM5c6NIvp7n-_mqevM7vX_K2aoOXgFAs3yZ5RK-nVVOntMTU9sZfCSxg3ywCRa0_vloXhgbRjFp3TizRwNJQ", + "id_token": "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI3M2YxNmQ5My0yNDQxLTRhNTAtODhmZi04NTM2MGQ3OGM2YjUiLCJraWQiOiJyc2ExIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcLyIsImdyb3VwcyI6W10sInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwib3JnYW5pc2F0aW9uX25hbWUiOiJpbmRpZ28tZGMiLCJhdWQiOiI4YWI1ZmFkMS0zMTQwLTQyZjAtOGNiNi1kYTgzMzE1OTYyYmMiLCJuYW1lIjoiQWRtaW4gVXNlciIsImV4cCI6MTY3NjQ2NTM5OSwiaWF0IjoxNjc2NDY0Nzk5LCJqdGkiOiI3YzhlM2RhNC05OWEyLTQ4NjQtOWUyMy02NGVhOGNlOWRiOWYiLCJlbWFpbCI6IjFfYWRtaW5AaWFtLnRlc3QifQ.13i_HH8wwhxerwVP0l593Rzy0MmnPA3TivhAsqreBa5L0O7pxSDavsC10vaJyVQFiiib-a2qPnciY0VeWOreLtmAbud0i4KxWmn1MKG000nk0cIgftB0dbrgS6WRj61FtrSRMCPZuCkECNZ0BGH-Xx7qxfJoDtZ5ns_jwnAsBZn6As2xDBVhKfbMgjZtick3DwFRJK6hvGAgwrVFvPw9xVkSEJOv2fbB28TSLU_Cz9jYQFpptMLIj15JEV84gxpc5HFNaIVpBdAMLNIsOMOFsV5tnNy3VsW2IiMgDKc-DRNAmY4IWxC3BJgfxAGkAeLXdj31XaAne2PGafvlJA1HLQ", } @@ -123,7 +123,7 @@ # Valid client tokens but the access token is expired expiredValidClientToken = { - "access_token": "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOlwvXC9pYW0tbG9naW4tc2VydmljZTo4MDgwIiwic3ViIjoiNWY3MGEyNjctNjM2YS00MzBhLTgxZDUtZjg4NWNhYjFjMjA4IiwiaWF0IjoxNjc3ODQ5OTIxLCJqdGkiOiIxNzhkZmZiMy1jYzYzLTRhM2EtOWVhZC0wMWJhNjg0MTEwNDgiLCJjbGllbnRfaWQiOiI1ZjcwYTI2Ny02MzZhLTQzMGEtODFkNS1mODg1Y2FiMWMyMDgifQ.udyuzv4HB3rg7_0X2lQT8rR0EcAcg0GYMm1azQ6WwfFbvdRLQfQWIKVFFJ9bVbLtpc7w282pFeqc7bKdN2vxXSpUbx0mkg404-oN5MgXik_oKTMtOXpaRUZGeL0xTF8XliD42wrx_tcmGu1dSyTI26ZnweDzteOhQ7faPJjycB-Q-FjTJ-R_3zqGaFEUYiPcbzMsXqo33gPaUtMeenVAWzwp9ATXEmH5gwBXcmTMaZnpLkMr-hRh3ZOYXOid_flKCRZFN633kZV3uHqLVj_4By-vclX06ajHRfYHXY3TJ9LBN4JLxssbF3O1adjzeYQ_aQ2E732WTOztNHokaMnHNg", + "access_token": "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwNTQ0YjkzZC03OTBlLTQzYWUtODFhZS1jZWZiMDkzYjU0MTEiLCJpc3MiOiJodHRwOlwvXC9pYW0tbG9naW4tc2VydmljZTo4MDgwIiwiZXhwIjoxNzMyODEyNDA1LCJpYXQiOjE3MzI4MDg4MDUsImp0aSI6Ijk0ODU4MzBjLWU5OTUtNGQyMS04ZTUyLWQxZDkwNGExMWEwZCIsImNsaWVudF9pZCI6IjA1NDRiOTNkLTc5MGUtNDNhZS04MWFlLWNlZmIwOTNiNTQxMSJ9.OyNW5yG_Gs1Y1XM_4RC1JdRwsytlUoTPVxLDPNXdEMQmdbk4dNkx7Nx_2DqnSIdJmyPGgp8wgFqwvAW14b1XVcMC8nxLsYbF-qugO6FHFzMVHyPaLX3rJUpjY7EybBPSMnO594xTQkBJmvZr41h8tVy2zSLgAqfcA2kf7cnIfMw", "token_type": "Bearer", "expires_in": 3599, "scope": "address phone openid email profile offline_access", @@ -148,9 +148,7 @@ def iam_connection(): # Get an admin token query = os.path.join(issuer, "token") params = { - "grant_type": "password", - "username": os.environ["IAM_ADMIN_USER"], - "password": os.environ["IAM_ADMIN_PASSWORD"], + "grant_type": "client_credentials", } response = requests.post( query, params=params, auth=(os.environ["IAM_INIT_CLIENT_ID"], os.environ["IAM_INIT_CLIENT_SECRET"]), timeout=5