key-cape/scripts/test-scenario-b.sh

71 lines
2.9 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
# test-scenario-b.sh — Scenario B: IAM swap (KeyCape -> Keycloak, same LLDAP directory).
#
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
# Exports the canonical directory, transforms it into a Keycloak realm, starts
# Keycloak over that realm, and runs the external conformance suite against it.
#
Prove the migration against live directories and fix what that surfaced Finishes the unproven half of gap G04. Running the harness against real LLDAP, OpenLDAP and Keycloak found four defects that the full unit suite passed over. Every LLDAP user search pointed at a branch that does not exist. Config.userOU() defaulted to ou=users while LLDAP stores users under ou=people, and nothing set UserOU. LookupUser, ListUsers and ValidatePassword all derive from it, so all three silently found nothing against a stock LLDAP -- human login included, not only the export. Exposed by an export returning zero users while still emitting a membership referencing uid=admin, a snapshot the repo's own validator rejects. raw_attributes_well_formed, added one workplan earlier, rejected the LLDAP adapter's own _validation_warning annotation, so the exporter's output failed its own validation. Tooling annotations are exempt now, and a test proves the exemption does not weaken the rule. Migrated group memberships were dangling: resolveMemberDN passed a source DN through unchanged while entries were written to the target branch, so groups named entries the migrated directory does not contain. And empty groups could not load at all, since groupOfNames makes member a MUST -- they reference a placeholder entry the LDIF creates, an organizationalRole rather than a person, emitted only when some group needs it. The scenario-c compose file could not start: bitnami/openldap:2.6 does not exist, though it passed docker compose config. Pinned to the image the scenario was proved against. Proof: LLDAP -> export -> validate -> LDIF -> ldapadd into OpenLDAP 1.5.0, every entry added and every member resolving; a realm from the same export imports into Keycloak and serves discovery. KeyCape passes 5/5 conformance checks, a migrated Keycloak 4/5. Relying-party behaviour and MFA against a migrated realm remain unexercised, and credential/MFA migration is not supplied at all, so no harness can establish it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-08 00:29:34 +02:00
# Prerequisites: docker, docker compose, and `make -C src build` (root `make build` only compiles; the src target installs into bin/).
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
BUILD_DIR="$REPO_ROOT/build/scenario-b"
COMPOSE="docker compose -f docker-compose.scenario-b.yml"
ISSUER_URL="http://localhost:8080/realms/netkingdom"
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
die() { echo "scenario-b: $*" >&2; exit 1; }
# Check every prerequisite up front: failing half way through leaves a running
# stack and a half-written realm, which is harder to diagnose than not starting.
for binary in lldap-export keycape-to-keycloak; do
Prove the migration against live directories and fix what that surfaced Finishes the unproven half of gap G04. Running the harness against real LLDAP, OpenLDAP and Keycloak found four defects that the full unit suite passed over. Every LLDAP user search pointed at a branch that does not exist. Config.userOU() defaulted to ou=users while LLDAP stores users under ou=people, and nothing set UserOU. LookupUser, ListUsers and ValidatePassword all derive from it, so all three silently found nothing against a stock LLDAP -- human login included, not only the export. Exposed by an export returning zero users while still emitting a membership referencing uid=admin, a snapshot the repo's own validator rejects. raw_attributes_well_formed, added one workplan earlier, rejected the LLDAP adapter's own _validation_warning annotation, so the exporter's output failed its own validation. Tooling annotations are exempt now, and a test proves the exemption does not weaken the rule. Migrated group memberships were dangling: resolveMemberDN passed a source DN through unchanged while entries were written to the target branch, so groups named entries the migrated directory does not contain. And empty groups could not load at all, since groupOfNames makes member a MUST -- they reference a placeholder entry the LDIF creates, an organizationalRole rather than a person, emitted only when some group needs it. The scenario-c compose file could not start: bitnami/openldap:2.6 does not exist, though it passed docker compose config. Pinned to the image the scenario was proved against. Proof: LLDAP -> export -> validate -> LDIF -> ldapadd into OpenLDAP 1.5.0, every entry added and every member resolving; a realm from the same export imports into Keycloak and serves discovery. KeyCape passes 5/5 conformance checks, a migrated Keycloak 4/5. Relying-party behaviour and MFA against a migrated realm remain unexercised, and credential/MFA migration is not supplied at all, so no harness can establish it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-08 00:29:34 +02:00
[ -x "bin/$binary" ] || die "bin/$binary is missing; run 'make -C src build' first"
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
done
command -v docker >/dev/null || die "docker is not installed"
command -v go >/dev/null || die "go is not installed"
cleanup() { $COMPOSE down -v >/dev/null 2>&1 || true; }
trap cleanup EXIT
mkdir -p "$BUILD_DIR/realm"
echo "=== Scenario B: IAM replacement ==="
echo "--- Step 1: start the directory ---"
$COMPOSE up -d lldap
timeout 120 bash -c 'until (exec 3<>/dev/tcp/127.0.0.1/3890) 2>/dev/null; do sleep 2; done' \
|| die "LLDAP did not accept connections within 120s"
echo "--- Step 2: export the canonical directory ---"
Reconcile packaging, bootstrap and migration credential handling Closes gap G09: five loosely related defects. lldap-export took the service account password on argv, where ps exposes it to any local user and shell history and process accounting capture it. It now prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns, deprecated rather than removed because existing runbooks use it and breaking them silently would be worse than one more cycle of exposure. Conflicting sources are rejected instead of silently ranked, since an operator otherwise cannot tell which bind was attempted. Both migration scripts pass the password by environment now. The canonical export, generated LDIF and Keycloak realm were written 0644. None carries credential material, but the snapshot is every username, display name, email and group membership in the estate, and it tends to land in /tmp. All three are 0600. The image packaged keycape alone, so the validator and migration binaries needed a Go toolchain on the host -- which defeats shipping an image for the cutover work they exist to support. All five ship; the issuer stays the entrypoint. Verified by building the image and running each binary inside it. The publish workflow named 92.205.130.254:32166 while the cluster runs forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and stays overridable by a repository variable. This repository cannot verify that the runner resolves that hostname or that the registry credentials are valid for it; if the next publish fails, set the REGISTRY variable back to the address. docker-compose.dev.yml mounts a private key and Authelia material that are correctly absent from the checkout. scripts/bootstrap-dev.sh generates them locally under a restrictive umask rather than chmodding afterwards, so the key is never briefly world-readable. Everything it writes is git-ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-08 09:51:59 +02:00
# The password goes through the environment: argv is visible to any local
# user via ps (KEY-WP-0026).
KEYCAPE_LLDAP_BIND_PW="${LLDAP_BIND_PW:-adminpassword}" \
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
./bin/lldap-export \
--url "${LLDAP_URL:-ldap://localhost:3890}" \
--bind-dn "${LLDAP_BIND_DN:-cn=admin,ou=people,dc=netkingdom,dc=local}" \
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
--base-dn "${LLDAP_BASE_DN:-dc=netkingdom,dc=local}" \
--output "$BUILD_DIR/canonical-export.yaml"
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
echo "--- Step 3: transform into a Keycloak realm ---"
./bin/keycape-to-keycloak \
--input "$BUILD_DIR/canonical-export.yaml" \
--realm netkingdom \
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
--issuer "${ISSUER:-$ISSUER_URL}" \
--output "$BUILD_DIR/realm/netkingdom-realm.json"
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
echo "--- Step 4: start Keycloak over the migrated realm ---"
$COMPOSE up -d keycloak
timeout 180 bash -c "until curl -sf '$ISSUER_URL/.well-known/openid-configuration' >/dev/null; do sleep 3; done" \
|| die "Keycloak did not serve discovery within 180s"
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
echo "--- Step 5: run the external conformance suite against Keycloak ---"
# The suite reads KEYCAPE_TEST_ISSUER and talks to the running issuer over HTTP.
# Run it from the module directory: the Go module root is src/, not the repo root.
KEYCAPE_TEST_ISSUER="$ISSUER_URL" \
go -C src test ./tests/conformance/... -v -count=1
echo "=== Scenario B PASSED ==="
Make the replacement harness runnable and target a live issuer Closes the runnable half of gap G04. The Scenario B and C scripts could not execute: absent compose files, binaries sought at src/bin where the Makefile builds to bin/, --base-dn passed to a generator whose flag is --basedn, and a hardcoded workstation Go path invoked from outside the module. Repairing the shell alone would have proved nothing. Both scripts set KEYCAPE_TEST_ISSUER while the profile suite built its own httptest server and never read it, so they passed identically whether or not a provider was running. A harness that cannot fail for the reason it exists is worse than a missing one. src/tests/conformance targets the issuer named by KEYCAPE_TEST_ISSUER over HTTP: discovery, the profile authorization surface, published keys parsed under the runtime's own rules, excluded grants, and -- with credentials -- a real token exchange verified against those keys. It skips when the variable is unset, so make test is unchanged. Run against Keycloak 26.0 rather than asserted to work. Discovery, authorization surface and key checks passed, and a client_credentials exchange produced a token that verified against Keycloak's published JWKS through internal/jose. It also failed, correctly: stock Keycloak advertises the excluded implicit and password grants, and in Keycloak those are server capabilities rather than per-client toggles, so no emitted realm removes them. A migrated Keycloak has a wider grant surface than KeyCape, which substantiates with evidence what SCOPE previously asserted without it. Scenario B legitimately reports failure today. Directory migration, credential and MFA preservation and relying-party behaviour remain unexercised, and Scenario C has never been run end to end, so G04 does not fully close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:32:11 +02:00
echo "Note: this proves the migrated realm serves a conforming OIDC surface."
echo "It does not prove credential, MFA or relying-party behaviour was preserved."