Add identity and target authorization package
OIDC validation, principal mapping, remora:v1 roles, and current-authz filtering with kind acceptance on Keycloak and ZITADEL.
This commit is contained in:
parent
91ac6dee85
commit
bf028161ae
1 changed files with 3153 additions and 21 deletions
|
|
@ -451,11 +451,26 @@ reset_lane_target_store() {
|
|||
aws --endpoint-url "$endpoint" s3api create-bucket --bucket "$bucket" >&2 || return
|
||||
}
|
||||
|
||||
# Remora storage role for fixture lanes: target-main / target-bucket / models.
|
||||
remora_fixture_storage_role() {
|
||||
local b64url
|
||||
b64url() {
|
||||
printf '%s' "$1" | openssl base64 -A | tr '+/' '-_' | tr -d '='
|
||||
}
|
||||
printf 'remora:v1:storage|%s|%s|%s\n' \
|
||||
"$(b64url target-main)" \
|
||||
"$(b64url target-bucket)" \
|
||||
"$(b64url models)"
|
||||
}
|
||||
|
||||
reset_keycloak_lane() {
|
||||
local lane="$1"
|
||||
local realm="remora-lane-$lane"
|
||||
local username="remora-user-$lane"
|
||||
local client_id="remora-client-$lane"
|
||||
local storage_role
|
||||
local client_uuid
|
||||
storage_role="$(remora_fixture_storage_role)"
|
||||
if kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh get "realms/$realm" \
|
||||
--no-config \
|
||||
|
|
@ -473,7 +488,26 @@ reset_keycloak_lane() {
|
|||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
--set "realm=$realm" \
|
||||
--set enabled=true >&2 || return
|
||||
--set enabled=true \
|
||||
--set registrationEmailAsUsername=false \
|
||||
--set verifyEmail=false \
|
||||
--set resetPasswordAllowed=false \
|
||||
--set loginWithEmailAllowed=true \
|
||||
--set duplicateEmailsAllowed=false \
|
||||
--set sslRequired=none >&2 || return
|
||||
# Keycloak 26 enables VERIFY_PROFILE by default; that blocks ROPC with
|
||||
# "Account is not fully set up".
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh update authentication/required-actions/VERIFY_PROFILE \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--set enabled=false \
|
||||
--set defaultAction=false >&2 || true
|
||||
# Public client with direct access grants so package Jobs can mint ROPC tokens.
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh create clients \
|
||||
--no-config \
|
||||
|
|
@ -485,7 +519,38 @@ reset_keycloak_lane() {
|
|||
--set "clientId=$client_id" \
|
||||
--set publicClient=true \
|
||||
--set standardFlowEnabled=true \
|
||||
--set 'redirectUris=["http://localhost/callback"]' >&2 || return
|
||||
--set directAccessGrantsEnabled=true \
|
||||
--set 'redirectUris=["http://localhost/callback"]' \
|
||||
--set 'attributes={"access.token.lifespan":"3600"}' >&2 || return
|
||||
client_uuid="$(kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh get clients \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--query "clientId=$client_id" \
|
||||
--fields id \
|
||||
--format csv \
|
||||
--noquotes)" || return
|
||||
client_uuid="$(printf '%s' "$client_uuid" | tr -d '\r' | head -n1)"
|
||||
[[ -n "$client_uuid" ]] || return
|
||||
# Include this client in access-token `aud` so mandatory audience checks pass.
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh create "clients/$client_uuid/protocol-mappers/models" \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--set name=remora-audience \
|
||||
--set protocol=openid-connect \
|
||||
--set protocolMapper=oidc-audience-mapper \
|
||||
--set 'config."included.client.audience"='"$client_id" \
|
||||
--set 'config."id.token.claim"=false' \
|
||||
--set 'config."access.token.claim"=true' >&2 || return
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh create roles \
|
||||
--no-config \
|
||||
|
|
@ -495,6 +560,26 @@ reset_keycloak_lane() {
|
|||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--set name=test-user >&2 || return
|
||||
# Client roles are the only Keycloak claim source Remora trusts (resource_access).
|
||||
# Use -b JSON so `|` in storage roles is not percent-encoded by --set.
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh create "clients/$client_uuid/roles" \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
-b "{\"name\":\"remora:v1:admin\"}" >&2 || return
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh create "clients/$client_uuid/roles" \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
-b "{\"name\":\"$storage_role\"}" >&2 || return
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh create users \
|
||||
--no-config \
|
||||
|
|
@ -506,7 +591,35 @@ reset_keycloak_lane() {
|
|||
--set "username=$username" \
|
||||
--set enabled=true \
|
||||
--set emailVerified=true \
|
||||
--set "email=$username@remora.test" >&2 || return
|
||||
--set "email=$username@remora.test" \
|
||||
--set 'requiredActions=[]' >&2 || return
|
||||
local user_id
|
||||
user_id="$(kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh get users \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--query "username=$username" \
|
||||
--fields id \
|
||||
--format csv \
|
||||
--noquotes)" || return
|
||||
user_id="$(printf '%s' "$user_id" | tr -d '\r' | head -n1)"
|
||||
[[ -n "$user_id" ]] || return
|
||||
# Clear any required actions and mark email verified for ROPC.
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh update "users/$user_id" \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--set emailVerified=true \
|
||||
--set 'requiredActions=[]' \
|
||||
--set enabled=true >&2 || return
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh set-password \
|
||||
--no-config \
|
||||
|
|
@ -515,8 +628,9 @@ reset_keycloak_lane() {
|
|||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--username "$username" \
|
||||
--new-password Synthetic-OIDC-1! >&2 || return
|
||||
--userid "$user_id" \
|
||||
--new-password Synthetic-OIDC-1! \
|
||||
--temporary=false >&2 || return
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh add-roles \
|
||||
--no-config \
|
||||
|
|
@ -527,11 +641,30 @@ reset_keycloak_lane() {
|
|||
-r "$realm" \
|
||||
--uusername "$username" \
|
||||
--rolename test-user >&2 || return
|
||||
printf '%s\t%s\t%s\t%s\n' \
|
||||
kube --namespace "$FIXTURE_NAMESPACE" exec deployment/keycloak -- \
|
||||
/opt/keycloak/bin/kcadm.sh add-roles \
|
||||
--no-config \
|
||||
--server http://127.0.0.1:8080 \
|
||||
--realm master \
|
||||
--user remora-admin \
|
||||
--password synthetic-keycloak \
|
||||
-r "$realm" \
|
||||
--uusername "$username" \
|
||||
--cclientid "$client_id" \
|
||||
--rolename 'remora:v1:admin' \
|
||||
--rolename "$storage_role" >&2 || return
|
||||
# issuer \t tenant \t client_id \t username \t storage_role \t project_id \t user_id \t mgmt_token
|
||||
# issuer \t tenant \t client_id \t username \t storage_role \t project_id \t user_id \t mgmt_token \t user_key_b64
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"http://keycloak.remora.test:8080/realms/$realm" \
|
||||
"$realm" \
|
||||
"$client_id" \
|
||||
"$username"
|
||||
"$username" \
|
||||
"$storage_role" \
|
||||
"" \
|
||||
"" \
|
||||
"" \
|
||||
""
|
||||
}
|
||||
|
||||
zitadel_request() {
|
||||
|
|
@ -551,7 +684,7 @@ zitadel_request() {
|
|||
--silent \
|
||||
--show-error \
|
||||
--request "$method" \
|
||||
--header "Host: zitadel.remora.test" \
|
||||
--header "Host: zitadel.remora.test:8080" \
|
||||
--header "Authorization: Bearer $token" \
|
||||
--header "Content-Type: application/json"
|
||||
if test -n "$organization"; then
|
||||
|
|
@ -577,11 +710,15 @@ reset_zitadel_lane() {
|
|||
local lane="$1"
|
||||
local tenant="remora-lane-$lane"
|
||||
local username="remora-user-$lane"
|
||||
local storage_role
|
||||
local response
|
||||
local organization_id
|
||||
local project_id
|
||||
local user_id
|
||||
local client_id
|
||||
local mgmt_token
|
||||
local grant_id
|
||||
storage_role="$(remora_fixture_storage_role)"
|
||||
cleanup_zitadel_lane "$lane" || return
|
||||
response="$(zitadel_request /management/v1/orgs "{\"name\":\"$tenant\"}")" || return
|
||||
organization_id="$(json_string_field "$response" id)" || return
|
||||
|
|
@ -591,25 +728,74 @@ reset_zitadel_lane() {
|
|||
"/management/v1/projects/$project_id/roles" \
|
||||
'{"roleKey":"test-user","displayName":"Test User","group":"fixture"}' \
|
||||
"$organization_id" >/dev/null || return
|
||||
zitadel_request \
|
||||
"/management/v1/projects/$project_id/roles" \
|
||||
'{"roleKey":"remora:v1:admin","displayName":"Remora Admin","group":"remora"}' \
|
||||
"$organization_id" >/dev/null || return
|
||||
zitadel_request \
|
||||
"/management/v1/projects/$project_id/roles" \
|
||||
"{\"roleKey\":\"$storage_role\",\"displayName\":\"Remora Storage\",\"group\":\"remora\"}" \
|
||||
"$organization_id" >/dev/null || return
|
||||
# Native app: PKCE public client with JWT access tokens and project role assertion.
|
||||
response="$(zitadel_request \
|
||||
"/management/v1/projects/$project_id/apps/oidc" \
|
||||
"{\"name\":\"$tenant\",\"redirectUris\":[\"http://localhost/callback\"],\"postLogoutRedirectUris\":[\"http://localhost/logout\"],\"responseTypes\":[\"OIDC_RESPONSE_TYPE_CODE\"],\"grantTypes\":[\"OIDC_GRANT_TYPE_AUTHORIZATION_CODE\",\"OIDC_GRANT_TYPE_REFRESH_TOKEN\"],\"appType\":\"OIDC_APP_TYPE_USER_AGENT\",\"authMethodType\":\"OIDC_AUTH_METHOD_TYPE_NONE\",\"version\":\"OIDC_VERSION_1_0\",\"devMode\":true,\"accessTokenType\":\"OIDC_TOKEN_TYPE_JWT\",\"accessTokenRoleAssertion\":true}" \
|
||||
"{\"name\":\"$tenant\",\"redirectUris\":[\"http://localhost/callback\"],\"postLogoutRedirectUris\":[\"http://localhost/logout\"],\"responseTypes\":[\"OIDC_RESPONSE_TYPE_CODE\"],\"grantTypes\":[\"OIDC_GRANT_TYPE_AUTHORIZATION_CODE\",\"OIDC_GRANT_TYPE_REFRESH_TOKEN\"],\"appType\":\"OIDC_APP_TYPE_NATIVE\",\"authMethodType\":\"OIDC_AUTH_METHOD_TYPE_NONE\",\"version\":\"OIDC_VERSION_1_0\",\"devMode\":true,\"accessTokenType\":\"OIDC_TOKEN_TYPE_JWT\",\"accessTokenRoleAssertion\":true,\"idTokenRoleAssertion\":true,\"idTokenUserinfoAssertion\":true}" \
|
||||
"$organization_id")" || return
|
||||
client_id="$(json_string_field "$response" clientId)" || return
|
||||
response="$(zitadel_request \
|
||||
/management/v1/users/human \
|
||||
"{\"userName\":\"$username\",\"profile\":{\"firstName\":\"Remora\",\"lastName\":\"Lane $lane\",\"displayName\":\"Remora Lane $lane\",\"preferredLanguage\":\"en\",\"gender\":\"GENDER_UNSPECIFIED\"},\"email\":{\"email\":\"$username@remora.test\",\"isEmailVerified\":true},\"password\":\"Synthetic-OIDC-1!\",\"passwordChangeRequired\":false}" \
|
||||
"{\"userName\":\"$username\",\"profile\":{\"firstName\":\"Remora\",\"lastName\":\"Lane $lane\",\"displayName\":\"Remora Lane $lane\",\"preferredLanguage\":\"en\",\"gender\":\"GENDER_UNSPECIFIED\"},\"email\":{\"email\":\"$username@remora.test\",\"isEmailVerified\":true},\"initialPassword\":\"Synthetic-OIDC-1!\"}" \
|
||||
"$organization_id")" || return
|
||||
user_id="$(json_string_field "$response" userId)" || return
|
||||
zitadel_request \
|
||||
# Set a permanent password for Session API checks (initialPassword alone is not enough on all versions).
|
||||
if ! zitadel_request \
|
||||
"/management/v1/users/$user_id/password" \
|
||||
'{"password":"Synthetic-OIDC-1!","noChangeRequired":true}' \
|
||||
"$organization_id" >/dev/null; then
|
||||
# Fallback field name used by older management API payloads.
|
||||
zitadel_request \
|
||||
"/management/v1/users/$user_id/password" \
|
||||
'{"password":"Synthetic-OIDC-1!","changeRequired":false}' \
|
||||
"$organization_id" >/dev/null || return
|
||||
fi
|
||||
response="$(zitadel_request \
|
||||
"/management/v1/users/$user_id/grants" \
|
||||
"{\"projectId\":\"$project_id\",\"roleKeys\":[\"test-user\"]}" \
|
||||
"{\"projectId\":\"$project_id\",\"roleKeys\":[\"test-user\",\"remora:v1:admin\",\"$storage_role\"]}" \
|
||||
"$organization_id")" || return
|
||||
# Machine user + application key for JWT-bearer access tokens with project roles.
|
||||
local machine_name="remora-machine-$lane"
|
||||
# Bearer defaults to opaque tokens; Remora validates only JWKS-signed JWTs.
|
||||
response="$(zitadel_request \
|
||||
/management/v1/users/machine \
|
||||
"{\"userName\":\"$machine_name\",\"name\":\"Remora Machine $lane\",\"description\":\"JWT bearer minting\",\"accessTokenType\":\"ACCESS_TOKEN_TYPE_JWT\"}" \
|
||||
"$organization_id")" || return
|
||||
local machine_id
|
||||
machine_id="$(json_string_field "$response" userId)" || return
|
||||
zitadel_request \
|
||||
"/management/v1/users/$machine_id/grants" \
|
||||
"{\"projectId\":\"$project_id\",\"roleKeys\":[\"remora:v1:admin\",\"$storage_role\"]}" \
|
||||
"$organization_id" >/dev/null || return
|
||||
printf '%s\t%s\t%s\t%s\n' \
|
||||
response="$(zitadel_request \
|
||||
"/management/v1/users/$machine_id/keys" \
|
||||
'{"type":"KEY_TYPE_JSON","expirationDate":"2099-01-01T00:00:00Z"}' \
|
||||
"$organization_id")" || return
|
||||
local key_details_b64
|
||||
key_details_b64="$(json_string_field "$response" keyDetails)" || return
|
||||
mgmt_token="$(kube --namespace "$FIXTURE_NAMESPACE" exec deployment/zitadel --container provisioner -- \
|
||||
cat /bootstrap/admin.pat)" || return
|
||||
mgmt_token="$(printf '%s' "$mgmt_token" | tr -d '\r\n')"
|
||||
# issuer \t tenant \t client_id \t username \t storage_role \t project_id \t user_id \t mgmt_token \t key_details_b64
|
||||
# user_id is the machine user used for JWT-bearer minting (and grant loss).
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
http://zitadel.remora.test:8080 \
|
||||
"$organization_id" \
|
||||
"$client_id" \
|
||||
"$username"
|
||||
"$username" \
|
||||
"$storage_role" \
|
||||
"$project_id" \
|
||||
"$machine_id" \
|
||||
"$mgmt_token" \
|
||||
"$key_details_b64"
|
||||
}
|
||||
|
||||
reset_lane_oidc_provider() {
|
||||
|
|
@ -767,6 +953,11 @@ write_lane_profile() {
|
|||
local oidc_tenant=
|
||||
local oidc_client_id=
|
||||
local oidc_username=
|
||||
local oidc_storage_role=
|
||||
local oidc_project_id=
|
||||
local oidc_user_id=
|
||||
local oidc_mgmt_token=
|
||||
local oidc_user_key_b64=
|
||||
local oidc_profile
|
||||
local target_endpoint=
|
||||
local target_bucket=
|
||||
|
|
@ -787,7 +978,9 @@ write_lane_profile() {
|
|||
fi
|
||||
if [[ "$oidc" != - ]]; then
|
||||
oidc_profile="$(reset_lane_oidc_provider "$oidc" "$lane")" || return
|
||||
IFS=$'\t' read -r oidc_issuer oidc_tenant oidc_client_id oidc_username <<< "$oidc_profile"
|
||||
IFS=$'\t' read -r oidc_issuer oidc_tenant oidc_client_id oidc_username \
|
||||
oidc_storage_role oidc_project_id oidc_user_id oidc_mgmt_token oidc_user_key_b64 \
|
||||
<<< "$oidc_profile"
|
||||
fi
|
||||
if [[ "$target" != - ]]; then
|
||||
target_bucket="remora-lane-$lane"
|
||||
|
|
@ -820,6 +1013,11 @@ write_lane_profile() {
|
|||
"REMORA_OIDC_CLIENT_ID=$oidc_client_id" \
|
||||
"REMORA_OIDC_USERNAME=$oidc_username" \
|
||||
"REMORA_OIDC_PASSWORD=Synthetic-OIDC-1!" \
|
||||
"REMORA_OIDC_STORAGE_ROLE=$oidc_storage_role" \
|
||||
"REMORA_OIDC_PROJECT_ID=$oidc_project_id" \
|
||||
"REMORA_OIDC_USER_ID=$oidc_user_id" \
|
||||
"REMORA_OIDC_MGMT_TOKEN=$oidc_mgmt_token" \
|
||||
"REMORA_OIDC_USER_KEY_B64=$oidc_user_key_b64" \
|
||||
"REMORA_TARGET_PROVIDER=$target" \
|
||||
"REMORA_TARGET_ENDPOINT=$target_endpoint" \
|
||||
"REMORA_TARGET_BUCKET=$target_bucket" \
|
||||
|
|
@ -847,6 +1045,11 @@ write_lane_profile() {
|
|||
--from-literal="REMORA_OIDC_CLIENT_ID=$oidc_client_id" \
|
||||
--from-literal="REMORA_OIDC_USERNAME=$oidc_username" \
|
||||
--from-literal=REMORA_OIDC_PASSWORD=Synthetic-OIDC-1! \
|
||||
--from-literal="REMORA_OIDC_STORAGE_ROLE=$oidc_storage_role" \
|
||||
--from-literal="REMORA_OIDC_PROJECT_ID=$oidc_project_id" \
|
||||
--from-literal="REMORA_OIDC_USER_ID=$oidc_user_id" \
|
||||
--from-literal="REMORA_OIDC_MGMT_TOKEN=$oidc_mgmt_token" \
|
||||
--from-literal="REMORA_OIDC_USER_KEY_B64=$oidc_user_key_b64" \
|
||||
--from-literal="REMORA_TARGET_PROVIDER=$target" \
|
||||
--from-literal="REMORA_TARGET_ENDPOINT=$target_endpoint" \
|
||||
--from-literal="REMORA_TARGET_BUCKET=$target_bucket" \
|
||||
|
|
|
|||
Loading…
Reference in a new issue