Skip to content

Commit

Permalink
feat(integration-tests): upgrade Indigo IAM to 1.10.2
Browse files Browse the repository at this point in the history
  • Loading branch information
aldbr committed Nov 28, 2024
1 parent 9f6126a commit 9bb76ce
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 42 deletions.
117 changes: 75 additions & 42 deletions integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
DEFAULT_HOST_OS = "cc7"
DEFAULT_MYSQL_VER = "mysql:8.0"
DEFAULT_ES_VER = "elasticsearch:7.9.1"
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,
Expand All @@ -45,7 +46,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"
Expand Down Expand Up @@ -202,7 +203,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()}
Expand Down Expand Up @@ -547,15 +548,15 @@ 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,
)
raise typer.Exit(code=1)
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,
)
Expand Down Expand Up @@ -656,25 +657,30 @@ 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)
time.sleep(10)
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)
# 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,
)
admin_client_config = _create_iam_client(
_create_iam_client(
issuer,
admin_access_token,
IAM_ADMIN_CLIENT_NAME,
Expand All @@ -687,7 +693,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")

Expand Down Expand Up @@ -718,7 +724,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
Expand All @@ -733,11 +739,61 @@ 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):
"""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"] = "Admin client (read-write)"
client_config["scope"] = " ".join(["scim:read", "scim:write", "iam:admin.read", "iam:admin.write"])
client_config["grant_types"] = ["client_credentials"]
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)
Expand Down Expand Up @@ -771,37 +827,14 @@ 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)],
)

if not ret.returncode == 0:
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)],
)

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)


Expand Down Expand Up @@ -838,7 +871,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)],
)

Expand All @@ -865,7 +898,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)],
)

Expand Down Expand Up @@ -902,7 +935,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)],
)

Expand Down Expand Up @@ -945,7 +978,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)],
)

Expand Down
32 changes: 32 additions & 0 deletions tests/CI/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
volumes:
# Volume used to store the jwks of the IAM service
diracx-iam-key-store:

services:
mysql:
image: ${MYSQL_VER}
Expand Down Expand Up @@ -35,6 +39,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
Expand All @@ -43,6 +51,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit 9bb76ce

Please sign in to comment.