Files
mesh-catalog/modules/keycloak/cmd/keycloak-provider/provisioner.go
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

125 lines
4.4 KiB
Go

package main
// keycloak's provisioner — the adapter that makes keycloak a provider of the mesh `oidc-client`
// interface (novox/hq ADR 0039/0040/0048). The reconcile loop is harness.go's; this writes only how
// Keycloak creates, checks and removes a consumer's client. What a client is, is oidc.go's.
//
// **The realm is read out of the issuer**, the one value an assignment sets (settings reach both the
// served facts and this module's config.json): a realm set in one place and an issuer in another
// would let the consumer be told one realm while its client is made in another.
//
// **While the admin is refused, Keycloak is not asked** (admin.go): every attempt would be one more
// failed login against the admin. The harness still counts each pass as a failure of the consumer,
// classed credentials-rejected, so the standing it announces says what is wrong.
import (
"context"
"errors"
"fmt"
"git.novox.be/novox/mesh-sdk/go/provider/harness"
"os"
"time"
)
// Issuer is the issuer this assignment serves, from the settings-merged config the mesh delivers.
func Issuer(getenv func(string) string) (string, error) {
if said := cfgString(meshConfig(getenv("MESH_KEYCLOAK_CONFIG_FILE")), "issuer"); said != "" {
return said, nil
}
if said := getenv("MESH_KEYCLOAK_ISSUER"); said != "" {
return said, nil
}
return "", errors.New("no issuer — the module's config.json carries none and MESH_KEYCLOAK_ISSUER is unset")
}
// provisioner is the adapter.
type provisioner struct {
clients OidcClients
guard *Guard
announce func(event string, body map[string]any)
log func(format string, args ...any)
}
func (a provisioner) refused() error {
if a.guard != nil && a.guard.Refused() {
return ErrAdminRejected
}
return nil
}
func (a provisioner) Create(ctx context.Context, p harness.Provision) error {
if err := a.refused(); err != nil {
return err
}
done, err := a.clients.Ensure(ctx, p)
if err != nil {
return err
}
if done == "created" {
a.log("[provisioner:oidc-client] created client %s in realm %s", p.As, a.clients.Realm)
a.announce("client.created", map[string]any{"realm": a.clients.Realm, "clientId": p.As, "consumer": p.Consumer})
}
return nil
}
// Retire removes the consumer's client secret and disables and marks the client, never deletes it (novox/hq ADR 0570
// clause 5, ADR 0230).
func (a provisioner) Retire(ctx context.Context, as string, _ map[string]any, why string, at time.Time) error {
if err := a.refused(); err != nil {
return err
}
done, err := a.clients.Retire(ctx, as, why, at)
if err != nil {
return err
}
switch done {
case "not ours":
a.log("[provisioner:oidc-client] %s: a client of that id exists that the mesh did not make — left alone", as)
case "retired":
a.log("[provisioner:oidc-client] retired client %s in realm %s: its secret removed, disabled, kept, marked to delete", as, a.clients.Realm)
a.announce("client.retired", map[string]any{"realm": a.clients.Realm, "clientId": as})
}
return nil
}
// Inventory is every client in the realm the mesh made, active and retired.
func (a provisioner) Inventory(ctx context.Context) (harness.Inventory, error) {
if err := a.refused(); err != nil {
return harness.Inventory{}, err
}
return a.clients.Inventory(ctx)
}
// Delete removes a retired client, a person's act through `cleanup delete`. Nothing is freed that
// Keycloak counts in bytes.
func (a provisioner) Delete(ctx context.Context, r harness.Retired) (int64, error) {
if err := a.refused(); err != nil {
return 0, err
}
if err := a.clients.Delete(ctx, r.Consumer); err != nil {
return 0, err
}
a.log("[provisioner:oidc-client] deleted retired client %s from realm %s", r.Consumer, a.clients.Realm)
return -1, nil
}
// Holds is asked every minute by the harness: whether Keycloak still holds this consumer's client
// exactly as the mesh gave it, so one deleted or edited behind the mesh's back is made again
// (novox/hq issue 120).
func (a provisioner) Holds(ctx context.Context, p harness.Provision) (bool, error) {
if err := a.refused(); err != nil {
return false, err
}
return a.clients.Holds(ctx, p)
}
// Class says a refused admin is a credentials problem, whatever the words around it.
func (provisioner) Class(err error) string {
if errors.Is(err, ErrRejected) {
return harness.ClassCredentials
}
return ""
}
func logStderr(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) }