Make the Keycloak transform preserve or name every policy field
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s
keycape-to-keycloak called Transform, which passes no clients, so it wrote a realm with an empty clients array and nothing said the service-identity contract had not been migrated. Where clients were supplied, mapClient hardcoded standardFlowEnabled — silently giving every client_credentials registration the browser flow — and dropped audience, service subject, tenant, roles, lifetime, MFA policy, secret reference and handoff URLs. Realm roles and client scopes were emitted as empty containers. The defect was not the missing mapping but that a dropped field and an inapplicable one looked identical in the output. Add -clients, reading registrations through a new config.Registrations() that converts without resolving secrets, so migration tooling cannot load material it has no business holding. Derive flows from the declared grants. Carry the profile claims as protocol mappers, since Keycloak has no native concept for them, and lifetime, handoff URLs and the secret reference as attributes — the reference, never a value. Derive realm roles and client scopes from what is present. Report what cannot be carried, in UnpreservedReport, kept deliberately separate from ValidationReport: consistency with the snapshot and completeness of the migration are different questions and one list cannot answer both. It names the unmigrated secret, the unenforceable MFA policy, passwords and factor enrolment, and subject continuity. An incomplete transform emits partial telemetry. Closes the semantic-preservation half of G03; proof against a live provider is G04 and stays open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WAsfsfQmDu4vcBhiMcmQp Assistant: claude-code Assistant-Model: opus Assistant-Process: 867844@bnt-lap001 Assistant-Session: 3d45905e-0016-4b49-b828-231406881f7b
This commit is contained in:
parent
8707d375a2
commit
e9fc8544ab
12 changed files with 664 additions and 56 deletions
|
|
@ -11,6 +11,8 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"keycape/internal/config"
|
||||
"keycape/internal/domain"
|
||||
"keycape/internal/migration/lldapexport"
|
||||
"keycape/internal/migration/tokeycloak"
|
||||
"keycape/internal/server/telemetry"
|
||||
|
|
@ -21,6 +23,7 @@ func main() {
|
|||
outputFile := flag.String("output", "keycloak-realm.json", "Path to write Keycloak realm import JSON")
|
||||
realmName := flag.String("realm", "netkingdom", "Keycloak realm name")
|
||||
issuer := flag.String("issuer", "", "OIDC issuer URL")
|
||||
clientsFile := flag.String("clients", "", "Path to the KeyCape config whose client registrations to migrate")
|
||||
flag.Parse()
|
||||
|
||||
if *inputFile == "" {
|
||||
|
|
@ -41,6 +44,23 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Client registrations are read from the KeyCape config, not the directory
|
||||
// snapshot: the snapshot carries identities, while the service-identity,
|
||||
// audience, tenant/role, MFA and lifetime contract lives in the config.
|
||||
var clients []domain.Client
|
||||
if *clientsFile != "" {
|
||||
cfg, cfgErr := config.Load(*clientsFile)
|
||||
if cfgErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: read clients %q: %v\n", *clientsFile, cfgErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
clients, cfgErr = cfg.Registrations()
|
||||
if cfgErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: %v\n", cfgErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
log := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
em := telemetry.NewLogEmitter(log)
|
||||
tr := tokeycloak.New(tokeycloak.Config{
|
||||
|
|
@ -48,17 +68,26 @@ func main() {
|
|||
Issuer: *issuer,
|
||||
}, em)
|
||||
|
||||
realm, err := tr.Transform(&export)
|
||||
realm, err := tr.TransformWithClients(&export, clients)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: transform: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print validation report to stderr.
|
||||
report := tr.ValidationReport(&export, realm)
|
||||
for _, issue := range report {
|
||||
// Consistency problems and migration limits are printed separately: the
|
||||
// first says the generated realm disagrees with the snapshot, the second
|
||||
// says what this tool never carries across. Collapsing them would let a
|
||||
// reader mistake a complete-looking realm for a complete migration.
|
||||
for _, issue := range tr.ValidationReport(&export, realm) {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: %s\n", issue)
|
||||
}
|
||||
unpreserved := tr.UnpreservedReport()
|
||||
if len(unpreserved) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "\nNOT PRESERVED — this realm is an artifact, not a completed migration:")
|
||||
for _, item := range unpreserved {
|
||||
fmt.Fprintln(os.Stderr, " -", item)
|
||||
}
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(realm, "", " ")
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue