Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion aws/keycloak/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ matches. When the hash changes, it fetches
path, writes `/run/glab/keycloak/admin.env`, then runs pinned
`keycloak-config-cli` to create the `lab` realm, local admin user, and
touch-only WebAuthn policy. It also creates the public `incus` OIDC client with
OAuth 2.0 Device Authorization Grant enabled. The service writes the new
OAuth 2.0 Device Authorization Grant enabled and the confidential
`glab-keycloak-config` service account used for later imports after the
temporary master bootstrap admin is disabled. The service writes the new
realm-config hash only after a successful import.

The follow-up Incus-side OIDC values are:
Expand Down
26 changes: 26 additions & 0 deletions aws/keycloak/templates/keycloak/lab-realm.json.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@
}
],
"clients": [
{
"clientId": "glab-keycloak-config",
"name": "glab Keycloak Config",
"description": "Confidential automation client for applying declarative lab realm configuration.",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"bearerOnly": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": false,
"frontchannelLogout": false,
"secret": "$(env:KEYCLOAK_CONFIG_CLIENT_SECRET)"
},
{
"clientId": "incus",
"name": "Incus",
Expand Down Expand Up @@ -138,6 +154,16 @@
}
],
"users": [
{
"username": "service-account-glab-keycloak-config",
"enabled": true,
"serviceAccountClientId": "glab-keycloak-config",
"clientRoles": {
"realm-management": [
"realm-admin"
]
}
},
{
"username": "$(env:KEYCLOAK_LOCAL_ADMIN_USERNAME)",
"email": "$(env:KEYCLOAK_LOCAL_ADMIN_EMAIL)",
Expand Down
48 changes: 31 additions & 17 deletions aws/keycloak/templates/scripts/run-keycloak-config.sh.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fi
--aws-region '${aws_region}' \
--broker-function '${github_token_broker_function_name}'
chmod 0600 '${config_output_path}'
. '${config_output_path}'

deadline=$(( $(date +%s) + 300 ))
until /usr/bin/curl -fsS http://127.0.0.1:9000/health/ready >/dev/null; do
Expand All @@ -43,23 +44,36 @@ until /usr/bin/curl -fsS http://127.0.0.1:9000/health/ready >/dev/null; do
sleep 5
done

/usr/bin/docker rm -f keycloak-config-cli >/dev/null 2>&1 || true
/usr/bin/docker run --rm \
--name keycloak-config-cli \
--network keycloak \
--user 0:0 \
--env-file '${config_output_path}' \
-e KEYCLOAK_URL='http://keycloak:8080' \
-e KEYCLOAK_USER="$KC_BOOTSTRAP_ADMIN_USERNAME" \
-e KEYCLOAK_PASSWORD="$KC_BOOTSTRAP_ADMIN_PASSWORD" \
-e KEYCLOAK_LOGINREALM='master' \
-e KEYCLOAK_AVAILABILITYCHECK_ENABLED='true' \
-e KEYCLOAK_AVAILABILITYCHECK_TIMEOUT='120s' \
-e IMPORT_FILES_LOCATIONS='/config/lab-realm.json' \
-e IMPORT_VARSUBSTITUTION_ENABLED='true' \
-e IMPORT_VARSUBSTITUTION_UNDEFINEDISERROR='true' \
-v '${keycloak_realm_config_path}:/config/lab-realm.json:ro' \
'${keycloak_config_cli_image}'
run_import() {
/usr/bin/docker rm -f keycloak-config-cli >/dev/null 2>&1 || true
/usr/bin/docker run --rm \
--name keycloak-config-cli \
--network keycloak \
--user 0:0 \
--env-file '${config_output_path}' \
"$@" \
-e KEYCLOAK_URL='http://keycloak:8080' \
-e KEYCLOAK_AVAILABILITYCHECK_ENABLED='true' \
-e KEYCLOAK_AVAILABILITYCHECK_TIMEOUT='120s' \
-e IMPORT_FILES_LOCATIONS='/config/lab-realm.json' \
-e IMPORT_VARSUBSTITUTION_ENABLED='true' \
-e IMPORT_VARSUBSTITUTION_UNDEFINEDISERROR='true' \
-v '${keycloak_realm_config_path}:/config/lab-realm.json:ro' \
'${keycloak_config_cli_image}'
}

if [ -f '${keycloak_config_marker_path}' ]; then
run_import \
-e KEYCLOAK_LOGINREALM='lab' \
-e KEYCLOAK_CLIENTID='glab-keycloak-config' \
-e KEYCLOAK_CLIENTSECRET="$KEYCLOAK_CONFIG_CLIENT_SECRET" \
-e KEYCLOAK_GRANTTYPE='client_credentials'
else
run_import \
-e KEYCLOAK_LOGINREALM='master' \
-e KEYCLOAK_USER="$KC_BOOTSTRAP_ADMIN_USERNAME" \
-e KEYCLOAK_PASSWORD="$KC_BOOTSTRAP_ADMIN_PASSWORD"
fi

printf '%s\n' "$desired_hash" >'${keycloak_config_marker_path}'
chmod 0600 '${keycloak_config_marker_path}'
Expand Down
15 changes: 15 additions & 0 deletions aws/keycloak/tests/main.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ run "plan_defaults" {
error_message = "The config script should use a rendered realm hash marker instead of a permanent first-import marker."
}

assert {
condition = strcontains(local.run_config_script, "KEYCLOAK_LOGINREALM='lab'") && strcontains(local.run_config_script, "KEYCLOAK_CLIENTID='glab-keycloak-config'") && strcontains(local.run_config_script, "KEYCLOAK_CLIENTSECRET=\"$KEYCLOAK_CONFIG_CLIENT_SECRET\"") && strcontains(local.run_config_script, "KEYCLOAK_GRANTTYPE='client_credentials'")
error_message = "The config script should use the dedicated config service account after the first successful hash-marked import."
}

assert {
condition = !strcontains(local.config_unit, "ConditionPathExists")
error_message = "The config unit should run on boot and let the config script decide whether the realm config changed."
Expand All @@ -166,6 +171,16 @@ run "plan_defaults" {
error_message = "The realm config should create the local admin from runtime env and require WebAuthn registration."
}

assert {
condition = strcontains(local.lab_realm_config, "\"clientId\": \"glab-keycloak-config\"") && strcontains(local.lab_realm_config, "\"serviceAccountsEnabled\": true") && strcontains(local.lab_realm_config, "\"secret\": \"$(env:KEYCLOAK_CONFIG_CLIENT_SECRET)\"")
error_message = "The realm config should define a confidential service account client for future config imports."
}

assert {
condition = strcontains(local.lab_realm_config, "\"username\": \"service-account-glab-keycloak-config\"") && strcontains(local.lab_realm_config, "\"serviceAccountClientId\": \"glab-keycloak-config\"") && strcontains(local.lab_realm_config, "\"realm-admin\"")
error_message = "The config service account should receive realm-admin privileges through realm-management."
}

assert {
condition = strcontains(local.lab_realm_config, "\"clientId\": \"incus\"") && strcontains(local.lab_realm_config, "\"publicClient\": true") && strcontains(local.lab_realm_config, "\"oauth2.device.authorization.grant.enabled\": \"true\"")
error_message = "The realm config should define a public Incus OIDC client with device authorization enabled."
Expand Down
Loading