Twice the identity provider's admin kept an older password than the one the mesh minted (an adopted, then a moved database), and the provisioner failed every consumer until it was repaired by hand (hq issue 179). The module now checks the admin's login and repairs a refusal itself through the server's bootstrap command, verifies, brakes a failed repair and announces it, and stops asking the server while refused. Ported to Go to change it.
61 lines
2.3 KiB
Go
61 lines
2.3 KiB
Go
package main
|
|
|
|
// The repair against a real Keycloak (issue 179's procedure, run by the guard). Skipped unless
|
|
// MESH_KEYCLOAK_LIVE_CONTAINER names a throwaway Keycloak 26 container whose realm was created with
|
|
// another admin password than "new", and MESH_KEYCLOAK_LIVE_URL reaches it, e.g.:
|
|
//
|
|
// docker network create kc-live
|
|
// docker run -d --name kc-live-db --network kc-live -e POSTGRES_PASSWORD=pg -e POSTGRES_DB=keycloak postgres:17-alpine
|
|
// docker run -d --name kc-live --network kc-live -p 127.0.0.1:18080:8080 \
|
|
// -e KC_DB=postgres -e KC_DB_URL=jdbc:postgresql://kc-live-db/keycloak -e KC_DB_USERNAME=postgres \
|
|
// -e KC_DB_PASSWORD=pg -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=old \
|
|
// quay.io/keycloak/keycloak:26.0.8 start-dev
|
|
// MESH_KEYCLOAK_LIVE_CONTAINER=kc-live MESH_KEYCLOAK_LIVE_URL=http://127.0.0.1:18080 go test -run Live ./...
|
|
//
|
|
// A database whose admin kept an older password than the mesh's is exactly that container.
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLiveRepair(t *testing.T) {
|
|
container, base := os.Getenv("MESH_KEYCLOAK_LIVE_CONTAINER"), os.Getenv("MESH_KEYCLOAK_LIVE_URL")
|
|
if container == "" || base == "" {
|
|
t.Skip("no MESH_KEYCLOAK_LIVE_CONTAINER / MESH_KEYCLOAK_LIVE_URL")
|
|
}
|
|
kc := NewClient(base, "admin", func() (string, error) { return "new", nil }, "master")
|
|
var said, events []string
|
|
g := &Guard{KC: kc, Container: container,
|
|
Log: func(f string, a ...any) { said = append(said, f) },
|
|
Announce: func(e string, _ map[string]any) { events = append(events, e) }}
|
|
|
|
if s, err := g.Check(ctx); s != AdminRejected {
|
|
t.Fatalf("the container's admin should refuse the mesh's password first: %s %v", s, err)
|
|
}
|
|
start := time.Now()
|
|
r := g.Ensure(ctx, true, false)
|
|
if !r.Repaired {
|
|
t.Fatalf("not repaired after %s: %+v %+v", time.Since(start), r, r.LastRepair)
|
|
}
|
|
t.Logf("repaired in %s", time.Since(start).Round(time.Second))
|
|
users, err := kc.ListUsers(ctx, "master", "", 100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, u := range users {
|
|
if name, _ := u["username"].(string); strings.HasPrefix(name, "mesh-repair-") {
|
|
t.Fatalf("the temporary admin %s is still there", name)
|
|
}
|
|
}
|
|
if strings.Join(events, ",") != EventRepaired {
|
|
t.Fatal(events)
|
|
}
|
|
// And a second pass changes nothing.
|
|
if r := g.Ensure(ctx, true, false); r.Repaired || r.State != AdminOK {
|
|
t.Fatalf("%+v", r)
|
|
}
|
|
}
|