GSSO — Implementation plan
| Version | 0.1, 2026-10-05 |
| Basis | docs/reports/00..05 (SPEC-GSSO-2026), gsso.jdl, test-scenarios.md |
| Decisions taken with the owner | Docs + working MVP · hybrid realms (gstack / cetatean / tenant-*) · develop against a local Keycloak first (production only with explicit approval) · DEV-PLAYBOOK stack (JHipster 9.1.0 microservice + gateway + separate Angular/GDS SPA) |
1. Why
Section titled “1. Why”Today “GSSO” is only a hand-configured Keycloak. That means:
- one shared realm (
interdictii) for every app; - only two global roles (
ROLE_ADMINandROLE_USER); - four inconsistent integration patterns;
- no approval or expiry of access;
- no durable audit;
- no configuration in git.
The goal is a real platform that can:
- synchronise a governed model into Keycloak;
- authorise per user × platform role;
- give every gStack PaaS and SaaS one login, through one integration standard.
2. Target in one picture
Section titled “2. Target in one picture”gsso-web (Angular/GDS) → gsso-gateway (BFF) → gsso (catalog · grants · reconciler · events · app API) │ Admin REST (gsso-reconciler) ▲ Kafka gsso.kc-events.v1 ▼ │ Keycloak 26.6.x + gsso extensions + GDS theme ─────┘ realms: gstack (staff SSO) · cetatean (MPass) · tenant-*apps ── OIDC/JWKS ──► Keycloak apps ── api/v1/app ──► gsso gsso ──► GLog, GNotify3. Work breakdown
Section titled “3. Work breakdown”Each line becomes one GitLab issue (estimate in days) and one commit/MR. Phases match report 05.
S0 — Foundation
Section titled “S0 — Foundation”| # | Task | Output | Est. |
|---|---|---|---|
| S0-1 | Review and accept GSSO-ADR-001..014; resolve Q-GSSO-1, 4, 5, 10, 11 | ADRs Acceptat | 1 |
| S0-2 | git init, remote govtech/gstack/gsso, .gitignore, .env.example, MR template | repo | 0.5 |
| S0-3 | keycloak/Dockerfile (26.6.3 + kc.sh build), deploy/docker-compose.yml, docker-compose.test.yml (Keycloak, postgres-keycloak, postgres-gsso, Kafka KRaft, SMTP mock) | local stack | 1.5 |
| S0-4 | Realm baselines gstack.json, cetatean.json, tenant-template.json + gsso-bootstrap (keycloak-config-cli); gsso-reconciler SA in master with minimal roles | AC-001 | 2 |
| S0-5 | GDS login, account and e-mail theme gstack (RO/RU/EN, IBM Plex, violet accent) | theme | 2 |
| S0-6 | Extensions Maven module skeleton (SPI 26.6.x), CI build | JAR | 0.5 |
| S0-7 | jhipster jdl gsso.jdl with 9.1.0 → gsso, gsso-gateway; pin versions; Liquibase jsonb changelog; append-only trigger changelog | generated apps | 1.5 |
| S0-8 | gsso-web skeleton: GDS shell (side bar Identity/Access/Governance, realm switcher, RO/RU/EN switch), BFF login | AC-002 | 2 |
| S0-9 | /gsast baseline, api_audit.py wiring | evidence | 0.5 |
S0 status — 2026-10-06
Section titled “S0 status — 2026-10-06”| # | State | Evidence / note |
|---|---|---|
| S0-1 | done | ADRs Acceptat (owner, 2026-10-06), report 01 v0.2 EN/RO/RU |
| S0-2 | done | local git, remote set, nothing pushed |
| S0-3 | done | deploy/docker-compose.yml: Keycloak 26.6.3, 2×PostgreSQL 17, Kafka 3.9.1 (KRaft), Mailpit, gsso, gateway, web; ports 76xx |
| S0-4 | done | keycloak/realms/generate.py → gstack, cetatean, master (gsso-reconciler), dev users; keycloak-config-cli no-delete; reconciler gets 403 on master users (NFR-SEC-004) |
| S0-5 | done (login + e-mail parent) | theme gstack on keycloak.v2; GDS HTML e-mail layout and account theme in S2 |
| S0-6 | done, ahead of plan | gsso-kafka listener implemented (not a skeleton): async, after-commit, secrets redacted; 6 unit tests; verified on Kafka (admin + user events, no secret in 54 events) |
| S0-7 | done | JHipster 9.1.0 → Spring Boot 4.0.6 / Java 21; jsonb + append-only trigger changelog (UPDATE/DELETE/TRUNCATE rejected, AC-018 DB part) |
| S0-8 | done | gsso-web Angular 20.3 + GDS, shell per GSSO.dc.html, RO/RU/EN, BFF via nginx; 8 unit tests; deploy/smoke/login_flow.py 22/22 (AC-001, AC-002) |
| S0-9 | open | /gsast baseline and api_audit.py not run yet |
Open items found in S0:
- GDS npm registry: the token in
glog/frontend/.npmrcis rejected (401); GDS 0.0.4/0.0.5 tarballs are vendored ingsso-web/vendor/until the owner provides an npm token (GITLAB_NPM_TOKEN). - Gateway and microservice share client
gsso-consolefor now; a dedicatedgsso-apiaudience client and the microservice’s own client-credentials client come in S1 with the reconciler. - JHipster
./mvnw verify(Testcontainers) not run yet in the container build; S1 adds it with the Docker socket mounted. - Re-running the bootstrap logs
invalidPasswordHistoryMessagefor the dev users (same password re-applied); harmless, dev-only.
S1 — Console & catalog MVP
Section titled “S1 — Console & catalog MVP”| # | Task | Reqs | Est. |
|---|---|---|---|
| S1-1 | KeycloakAdminGateway (keycloak-admin-client 26.x, natural-key lookups, managed marker, RFC 7807 translation) | FR-072, 076 | 2 |
| S1-2 | Outbox JobSincronizare + ReconcilerWorker (SKIP LOCKED, per-realm advisory lock, backoff, FAILED alert) | FR-071..073, 079 | 3 |
| S1-3 | Handlers: Realm, Client, Role (+composites), Group/Bundle, Policy | FR-001, 002, 012, 016 | 4 |
| S1-4 | RealmDiffer + drift policy REPORT/ENFORCE/IGNORE, periodic and on-demand, dry-run | FR-074, 075, 080 | 3 |
| S1-5 | Realm adoption (dry-run report + import with mapping file) | FR-007 | 2 |
| S1-6 | Facade api/v1/admin/* with realm/platform scoping, Idempotency-Key, RFC 7807 | FR-006, 097 | 3 |
| S1-7 | Users: list/search (projection), create/invite, edit, enable/disable, credentials, sessions, unlock; nightly sync | FR-023..033 | 4 |
| S1-8 | GSSO’s own append-only events (catalog/console) | FR-083, 084 | 1 |
| S1-9 | UI screens: Realms, Applications (clients), Platforms, Roles, Users + drawer, Sync | FR-004, 005, 026, 037, 077, 078, 109..116 | 6 |
| S1-10 | IT: TS-GSSO-02..08, 26..28; Cypress realm/users | tests | 3 |
S1 status — 2026-10-06
Section titled “S1 status — 2026-10-06”| # | State | Evidence / note |
|---|---|---|
| S1-1 | done | service/keycloak/KeycloakAdminClient (thin RestClient wrapper, GSSO-ADR-016), 403 → token refresh + retry |
| S1-2 | done | outbox SyncOutbox, ReconcilerWorker (SKIP LOCKED, one realm at a time, backoff, FAILED + event) |
| S1-3 | done for realm, client, role (+composites); bundles and auth policy move to S2 with grants and policies | service/sync/handlers/* |
| S1-4 | done | RealmDiffer: REPORT / ENFORCE / IGNORE, NEGUVERNAT, auto-close of resolved findings |
| S1-5 | done (clients, roles, composites; user role mappings → grants in S2) | RealmAdoptionService, dry run rolls back |
| S1-6 | done (Idempotency-Key store moves to S3 with the app zone) | web/rest/admin/*, AdminScope (realm/platform scoping from claims gsso_realm, gsso_platforma) |
| S1-7 | done | service/users/*: search, create + invitation, edit, enable/disable (ends sessions), required actions, credentials, sessions, unlock; IDNP AES-GCM + masked; GSSO-ADR-015 (proposed) |
| S1-8 | done | EventStore: append-only, per-realm SHA-256 chain, verification endpoint |
| S1-9 | done | console: Realms (+create, adopt with dry run, reconcile), Platforms (+clients, roles, composites), Applications, Roles, Users (+drawer, create), Sync (findings, enforce, accept), Events (+chain check); RO/RU/EN 186 keys |
| S1-10 | done | backend 160 unit + 630 IT green (ReconcilerIT 8, UserAdminIT 6, CatalogAdminResourceIT 6 against Keycloak 26.6.3 / isolated DB); web 9 unit; deploy/smoke/login_flow.py 23/23, deploy/smoke/s1_catalog.py 20/20 |
Found and fixed by the S1 tests (now regression-tested):
- the reconciler token lost rights on realms it had just created (403) and grew with every realm until HTTP 431 → lightweight access token (GSSO-ADR-016);
- tenant realms had no
ROLE_USERby default and failed to import once the template declared its default role → template declares Keycloak’s built-in roles and the default-role composites; - the template’s own
ROLE_USERwas markedcatalogand would have been deleted by ENFORCE → ownership valuesbaseline/catalog; - drift diffs and event payloads were serialised as Jackson-2 node internals by the Jackson-3 web layer → plain maps;
- generated CRUD was open to any authenticated user →
GSSO_ADMINonly.
S2 — Governance
Section titled “S2 — Governance”| # | Task | Reqs | Est. |
|---|---|---|---|
| S2-1 | GrantLifecycleService + ApprovalPolicy (four-eyes, self-approval ban), direct grant, bundles | FR-038..043, 046 | 3 |
| S2-2 | Expiry scheduler + GNotify warnings | FR-044, 045 | 1.5 |
| S2-3 | Ungoverned mapping detection | FR-049 | 1 |
| S2-4 | Org units + mappers org_unit, org_unit_path, roles_scoped (extension) | FR-053..055 | 2.5 |
| S2-5 | Event listener gsso-kafka (extension), consumer, hash chain, back-fill, GLog relay | FR-081..088 | 4 |
| S2-6 | Revocation publisher, gsso.user-changed.v1, gsso.grant.v1 | FR-089 | 1 |
| S2-7 | Auth policies: methods, browser flow view, conditional MFA authenticator, password/brute-force/session settings, LDAP config, MPass mock broker in cetatean, branding | FR-057..066, 069 | 5 |
| S2-8 | Dashboard aggregates + UI (KPIs, logins/24 h, events, auth mix), Grants inbox, Events, Authentication screens | FR-047, 088, 091..095 | 5 |
| S2-9 | IT and E2E: TS-GSSO-09..19, 21..25, 29..31 | tests | 4 |
S2 status — 2026-10-06
Section titled “S2 status — 2026-10-06”| # | State | Evidence / note |
|---|---|---|
| S2-1 | done (bundles open) | service/grants/*: lifecycle state machine, four-eyes ApprovalPolicy, request / direct / approve / reject / revoke; GrantIT 8, ApprovalPolicyTest 7 |
| S2-2 | expiry done; warnings open | hourly GrantExpiryScheduler; GNotify warnings (−14 d / −1 d) not yet sent |
| S2-3 | done | ungoverned mappings: REPORT finding / ENFORCE removal; adoption imports mappings as ACTIVA grants |
| S2-4 | done | org-unit tree, org_unit_path, roles_scoped (JSON claim); OrgUnitIT reads real tokens |
| S2-5 | done except GLog relay and back-fill | gsso-kafka → KcEventIngestion (idempotent, own event time, projection last login / failure); KcEventIngestionIT 6 |
| S2-6 | done | gsso.grant.v1, gsso.access-revoked.v1 (after commit, off the request thread); gsso.user-changed.v1 defined, not yet emitted |
| S2-7 | MFA + policy done; MPass mock and LDAP open | gsso-browser flow with conditional OTP (sensitive roles contain GSSO_MFA_REQUIRED); policy API with secure floors, read-only for baseline / adopted realms; MfaFlowIT 2, AuthPolicyIT 2 |
| S2-8 | done | dashboard (KPIs, logins 24 h, auth mix, recent events), Grants screen, Authentication screen, unit management |
| S2-9 | done for the delivered items | deploy/smoke/s2_grants.py 20/20, plus S0 23/23 and S1 20/20 regression |
Open items found in S2:
- console MFA for the
GSSO_*roles themselves (GSSO-NFR-SEC-008): to be switched on in the production baseline at the S4 takeover (it would put every dev login and smoke test behind TOTP); - GNotify (expiry warnings, alerts) and GLog (audit forwarding): need the target services and their client credentials;
- MPass mock broker for
cetatean(dev SAML IdP), role bundles (GSSO-FR-017), Keycloak events back-fill (GSSO-FR-087).
S3 — Integration kit & pilot
Section titled “S3 — Integration kit & pilot”| # | Task | Reqs | Est. |
|---|---|---|---|
| S3-1 | gsso-spring-boot-starter (resource server, audience, roles mapping + aliases, principal, client-credentials and exchange clients, revocation deny list, health, Testcontainers helper) | FR-104, 107 | 5 |
| S3-2 | @gstack/gsso-angular (PKCE/BFF, guard, directive, refresh, logout, locale) | FR-105 | 3 |
| S3-3 | api/v1/app/* (self-registration, lookups, roles, sessions, grant requests, role→users) | FR-098..103, 051 | 3 |
| S3-4 | Token-exchange permissions reconciliation | FR-019 | 2 |
| S3-5 | Sample app + onboarding runbook (report 03 §8) | FR-106 | 1 |
| S3-6 | Pilot gregistry (local branch): starter, realm gstack | AC-010, 020 | 2 |
| S3-7 | Pilot glog (local branch): starter, scopes, tenant claim; GSSO as ingest client | — | 2 |
| S3-8 | Tests TS-GSSO-20, 32..35; /gtestgen authorization matrix | tests | 2 |
S3 status — 2026-10-06
Section titled “S3 status — 2026-10-06”| # | State | Evidence / note |
|---|---|---|
| S3-1 | done (0.1.0) | sdk/gsso-spring-boot-starter: decoder (issuer, audience, revocation), roles → authorities + aliases, GssoPrincipal (hasRoleInUnit), GssoTokenClient (client credentials, exchange), deny list from gsso.access-revoked.v1, readiness; Spring Boot 3.2+ / Java 17; GssoPrincipalTest 5, GssoKeycloakIT 4 (stock Keycloak 26.6.3) |
| S3-2 | open | @gstack/gsso-angular |
| S3-3 | open | api/v1/app/* |
| S3-4 | done | exchange audiences per client → gsso-exchange-<target> audience mappers + standard.token.exchange.enabled; ReconcilerIT (mappers added/removed, no drift); console editor on Clients |
| S3-5 | open | sample app + runbook |
| S3-6 | done (local branch gsso-starter-pilot in gregistry, not pushed) | starter replaces the hand-written decoder / AudienceValidator / converter; GssoStarterIT; gregistry suite 298/302, the 4 errors (RegistryAuditResourceIT, append-only trigger vs generated CRUD tests) also fail on main; live: deploy/smoke/s3_pilot.py 24/24 first run, 21/21 re-runs |
| S3-7 | open | glog pilot |
| S3-8 | partly | TS-GSSO-32 (smoke), TS-GSSO-35 (GssoKeycloakIT); TS-GSSO-21 (cross-app SSO in a browser), 33, 34 open |
Found and fixed during S3:
- revocation gap: a re-login in the seconds before the reconciler removed the mapping got the role back; the
mapping is now removed synchronously at revocation, before sessions are ended (job kept as retry);
GrantITasserts it without draining the worker; - starter: the deny list depended on an optional Kafka dependency and silently did nothing without it;
kafka-clientsis now a regular dependency; - full backend verify had not been run since S2: generated
GssoKafkaResourceITwas broken by the S2 Kafka bindings (demo producer disabled, platform events retrying an absent broker); fixed withgsso.events.publish(off only in tests) and the test binder as default; modernizer findings fixed. Now 170 unit + 651 IT, no skips.
S4 — Migration (each step needs explicit owner approval)
Section titled “S4 — Migration (each step needs explicit owner approval)”- Production takeover (report 04 §11.1):
- fix TLS renewal;
- back up and export;
- swap in the GSSO image on 26.6.3;
- enable the listener;
- adopt
interdictiiasIGNORE; - apply the
gstackandcetateanbaselines.
- Rotate all secrets that were stored in plain-text notes.
- Migrate apps one by one, following report 03 §2.1:
- gregistry and glog (from pilot);
- interdictii, gdocs, gnotify (pattern A);
- drumuri (C + MPass);
- gstorage;
- cancelarie;
- platform and gportal.
- Retire
ROLE_ADMINand the obsolete realms, and create thetenant-*realms for the whitelabel sites.
S5 — Extensions
Section titled “S5 — Extensions”Access reviews, SoD rules, dormant accounts, QR login authenticator for gsso_mob, magic link, Organizations, gsso-cli/Terraform, HA Helm.
4. Effort and order
Section titled “4. Effort and order”- S0–S3: about 90 person-days, roughly 14–18 person-weeks with 2–2.5 people.
- The MVP (S0 + S1 + the S2 dashboard, events and grants) is the first demo target.
- CAP-GSSO-01 (CRM F0 blocker) is delivered at the end of S1.
5. Verification
Section titled “5. Verification”The detailed scenarios are in test-scenarios.md. The minimum demo proof:
docker compose up: Keycloak comes up withgstack/cetatean, the GDS theme and the extensions.- Console login through BFF + MFA.
- Register platform
crm→ clients and roles appear in Keycloak. - Request and approve
CRM_ADMIN(four-eyes) → the role is in the token → revoke it → the sample app denies within 60 s. - Make a manual change in the Keycloak console → drift is shown → enforce it.
- Login events appear on the dashboard and in GLog.
- SSO across the console and the gregistry pilot.
6. Open items before S0 coding
Section titled “6. Open items before S0 coding”- The ADR approval gate.
- Q-GSSO-1: migrate realm
interdictii→gstack, or rename it. - Q-GSSO-10: hosting target for staging.
- Shared Kafka availability (otherwise use a local single broker).
gsso.dc.html: copy from gstyle and add the Platforms, Grants, Sync and Events screens, keeping the existing 6 screens as designed. Also update the “Keycloak 24” label to 26.6.