package main // Retirement against a real Keycloak (novox/hq ADR 0230). Skipped unless MESH_KEYCLOAK_LIVE_URL reaches // a throwaway Keycloak whose master admin's password is MESH_KEYCLOAK_LIVE_PASSWORD, e.g.: // // docker run -d --rm --name kc-retire -p 127.0.0.1:18081:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ // -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.0.8 start-dev // MESH_KEYCLOAK_LIVE_URL=http://127.0.0.1:18081 MESH_KEYCLOAK_LIVE_PASSWORD=admin go test -run LiveRetire ./... // // It proves what the fake cannot: a retired client is refused by Keycloak itself at the authorization // endpoint, keeps its secret and redirects, is served again once enabled, and is deleted only once // retired. import ( "net/http" "net/url" "os" "testing" "time" ) func TestLiveRetirement(t *testing.T) { base, pw := os.Getenv("MESH_KEYCLOAK_LIVE_URL"), os.Getenv("MESH_KEYCLOAK_LIVE_PASSWORD") if base == "" || pw == "" { t.Skip("no MESH_KEYCLOAK_LIVE_URL / MESH_KEYCLOAK_LIVE_PASSWORD") } kc := NewClient(base, "admin", func() (string, error) { return pw, nil }, "master") o := OidcClients{KC: kc, Realm: "master"} p := grafana("live-secret", nil) p.As = "mesh_live_retire" t.Cleanup(func() { if found, _ := kc.FindClient(ctx, "master", p.As); found != nil { id, _ := found["id"].(string) kc.DeleteClientByID(ctx, "master", id) } }) if _, err := o.Ensure(ctx, p); err != nil { t.Fatal(err) } // The authorization endpoint is where a consumer's users arrive: 200 with a login page while the // client is enabled, refused while it is not. authorize := func() int { q := url.Values{"client_id": {p.As}, "response_type": {"code"}, "scope": {"openid"}, "redirect_uri": {"https://grafana.example.org/login/generic_oauth"}} resp, err := (&http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}). Get(base + "/realms/master/protocol/openid-connect/auth?" + q.Encode()) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } if code := authorize(); code != 200 { t.Fatalf("an enabled client is refused: %d", code) } mustHold(t, o, p, true) at := time.Now().UTC().Truncate(time.Second) if done, err := o.Retire(ctx, p.As, "the mesh stopped asking for it", at); done != "retired" || err != nil { t.Fatal(done, err) } if code := authorize(); code == 200 { t.Fatal("a retired client is still served") } // Retired, so a person may delete it. if err := o.Delete(ctx, p.As); err != nil { t.Fatal(err) } // Made again, retired, listed, and enabled again as it was. if _, err := o.Ensure(ctx, p); err != nil { t.Fatal(err) } o.Retire(ctx, p.As, "again", at) inv, err := o.Inventory(ctx) if err != nil { t.Fatal(err) } found := false for _, r := range inv.Retired { found = found || (r.Consumer == p.As && r.RetiredAt.Equal(at) && r.Why == "again") } if !found { t.Fatalf("not listed retired: %+v", inv) } secret, err := kc.ClientSecretByID(ctx, "master", clientID(t, kc, p.As)) if err != nil || secret != "live-secret" { t.Fatal("the secret was not kept:", secret, err) } if _, err := o.Ensure(ctx, p); err != nil { t.Fatal(err) } if code := authorize(); code != 200 { t.Fatalf("re-enabled and still refused: %d", code) } mustHold(t, o, p, true) if err := o.Delete(ctx, p.As); err == nil { t.Fatal("deleted an enabled client") } } func clientID(t *testing.T, kc *Client, clientId string) string { found, err := kc.FindClient(ctx, "master", clientId) if err != nil || found == nil { t.Fatal(found, err) } id, _ := found["id"].(string) return id }