Documentation
¶
Index ¶
- Constants
- type ClientScopeSpec
- type ClientSpec
- type KubernetesSpec
- type NetBirdSpec
- type ProtocolMapperSpec
- type Reconciler
- func (r *Reconciler) AssignClientDefaultScopes(ctx context.Context, realm, clientID string, scopeNames []string) error
- func (r *Reconciler) AssignClientServiceAccountRole(ctx context.Context, realm, srcClientID, targetClientID, roleName string) error
- func (r *Reconciler) EnsureClientDeviceAuthorizationGrant(ctx context.Context, realm, clientID string, enabled bool) error
- func (r *Reconciler) ReconcileClient(ctx context.Context, realm string, spec ClientSpec) (string, error)
- func (r *Reconciler) ReconcileClientScope(ctx context.Context, realm string, spec ClientScopeSpec) error
- func (r *Reconciler) ReconcileKubernetes(ctx context.Context, spec KubernetesSpec) error
- func (r *Reconciler) ReconcileNetBird(ctx context.Context, spec NetBirdSpec) error
- func (r *Reconciler) ReconcileProtocolMapperOnClientScope(ctx context.Context, realm, clientScopeName string, spec ProtocolMapperSpec) error
- func (r *Reconciler) ReconcileRealm(ctx context.Context, name string) error
- func (r *Reconciler) ReconcileUser(ctx context.Context, realm string, spec UserSpec, initialPassword string) error
- type UserSpec
Constants ¶
const NetBirdAPIScopeName = "api"
NetBirdAPIScopeName is the ClientScope created in the realm and assigned as a default scope on netbird-client + netbird-backend. It carries the audience ProtocolMapper that asserts the token audience matches NetBird Mgmt's expected `aud` claim.
Exported because the kubernetes client created by ReconcileKubernetes inherits this scope (so kubelogin tokens share the NetBird audience mapping) — callers pass this in KubernetesSpec.DefaultScopes.
const NetBirdGroupsScopeName = "groups"
NetBirdGroupsScopeName is the ClientScope that carries a Group Membership ProtocolMapper, surfacing the user's Keycloak group memberships as a `groups` claim on every issued token. NetBird Mgmt's "Enable JWT Group Sync" Dashboard toggle reads this claim to drive group-based access policies — the toggle is a no-op without this scope being attached.
Exported because ReconcileKubernetes inherits it too, so kubelogin tokens carry the same groups claim and downstream RBAC can key off the same Keycloak groups. Caller passes this in KubernetesSpec.DefaultScopes alongside NetBirdAPIScopeName.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ClientScopeSpec ¶
type ClientScopeSpec struct {
Name string
Protocol string // typically "openid-connect"
Description string
// ConsentScreenText is the display label Keycloak shows on the
// "Grant Access to <client>" consent page in lieu of the raw
// scope Name. When unset Keycloak falls back to Name verbatim,
// which is fine for things like "email" but produces lowercase
// fragments like "api" / "groups" next to neatly cased built-in
// scopes ("Email address", "User profile") — set this to
// "API" / "Groups" / etc. to keep the consent screen tidy.
//
// Stored under the `consent.screen.text` attribute on the
// ClientScope representation, not a top-level field.
ConsentScreenText string
// IncludeInTokenScope controls whether the scope's name appears
// in the issued token's `scope` claim. NetBird's docs require
// this on for the `groups` scope so the operator's Keycloak
// groups land in the JWT — Keycloak stores it as the
// `include.in.token.scope` attribute on the ClientScope
// representation, not a top-level field.
IncludeInTokenScope bool
}
ClientScopeSpec describes a Keycloak client scope. Only Name and Protocol are required; description is optional.
type ClientSpec ¶
type ClientSpec struct {
// ClientID is the OIDC client_id (also Keycloak's display name
// in the admin UI's Clients list). Required.
ClientID string
// PublicClient = true means PKCE / no client secret (browser
// SSO, kubelogin). false means confidential — Keycloak issues
// (or accepts a pre-set) client secret.
PublicClient bool
// ServiceAccountsEnabled = true makes Keycloak auto-create a
// service-account user for this confidential client. Only
// valid when PublicClient is false.
ServiceAccountsEnabled bool
// StandardFlowEnabled controls authorization-code (browser)
// flow. DirectAccessGrantsEnabled controls Resource Owner
// Password Credentials. Default both off; callers opt in.
StandardFlowEnabled bool
DirectAccessGrantsEnabled bool
// RedirectURIs / WebOrigins are required for browser flows
// (PKCE). Ignored for purely backend confidential clients.
RedirectURIs []string
WebOrigins []string
// Secret pre-sets the confidential client's secret on creation.
// When empty, Keycloak generates a random one. Ignored for
// PublicClient = true.
Secret string
// DeviceAuthorizationGrantEnabled controls whether the client can
// initiate the OAuth 2.0 Device Authorization Grant flow
// (RFC 8628). Required for headless CLI tools — NetBird's
// `netbird up`, for instance — that can't host an
// authorization-code redirect URI. Keycloak stores this as the
// `oauth2.device.authorization.grant.enabled` client attribute,
// not a top-level Client field, so buildClient translates the
// bool onto the Attributes map.
DeviceAuthorizationGrantEnabled bool
}
ClientSpec describes a Keycloak client (OIDC application) in a shape that's natural for callers; ReconcileClient maps it onto gocloak's pointer-everywhere Client representation.
type KubernetesSpec ¶
type KubernetesSpec struct {
// Realm is the Keycloak realm name (typically derived from the
// public DNS — see cluster.keycloak.realm).
Realm string
// ClusterName is the Kubernetes cluster's name (cluster.name).
// The OIDC client created in Keycloak is named
// `kubernetes-<ClusterName>`.
ClusterName string
// RedirectURIs are the redirect URIs for kubelogin's localhost
// callback. Defaults to defaultKubeloginRedirectURIs when empty.
RedirectURIs []string
// DefaultScopes are extra ClientScope names to assign as defaults
// on the kubernetes client (e.g., the NetBird api scope so the
// audience mapper covers kubelogin tokens too). May be empty.
DefaultScopes []string
}
KubernetesSpec describes the OIDC client a Kubernetes cluster needs in a customer's Keycloak realm so kubelogin can authenticate kubectl users against that cluster's kube-apiserver.
One Keycloak realm typically hosts multiple Kubernetes clusters (the VPN cluster plus each workload cluster joined to it), each with its own `kubernetes-<ClusterName>` client.
type NetBirdSpec ¶
type NetBirdSpec struct {
// Realm is the Keycloak realm name (typically derived from the
// public DNS — see cluster.keycloak.realm).
Realm string
// NetBirdMgmtURL is the public URL of the NetBird Management
// UI (e.g., https://netbird.<vpn-dns>). Used as netbird-client's
// redirect URI base (browser SSO + dashboard) and Web Origin.
// NOT the JWT audience — that's the netbird-client client_id,
// keyed on a separate netBirdClientID const so the value can
// also surface in the api scope's audience ProtocolMapper.
NetBirdMgmtURL string
// NetBirdBackendSecret is the pre-generated client secret to
// assign to `netbird-backend` on create. Pre-setting avoids a
// second git push: kubeaid-cli renders the SealedSecret with
// this value before ever calling the Keycloak admin API, then
// hands the same value to ReconcileClient via spec.Secret.
NetBirdBackendSecret string
}
NetBirdSpec describes the realm-side resources NetBird needs in the customer's Keycloak realm. Populated from cluster.keycloak.* + the pre-generated netbird-backend secret kubeaid-cli also templates into the SealedSecret consumed by NetBird Mgmt.
type ProtocolMapperSpec ¶
type ProtocolMapperSpec struct {
Name string
Protocol string // "openid-connect"
ProtocolMapper string // e.g. "oidc-audience-mapper"
Config map[string]string
}
ProtocolMapperSpec describes a single protocol mapper attached to a client scope. Config carries the mapper-type-specific keys (e.g. "included.client.audience" for an audience mapper).
type Reconciler ¶
type Reconciler struct {
// contains filtered or unexported fields
}
Reconciler issues idempotent admin-API calls against a running Keycloak. Construct via NewReconciler with admin credentials; each Reconcile* method either creates the resource if missing or no-ops when it already exists.
func NewReconciler ¶
func NewReconciler(ctx context.Context, baseURL, adminUser, adminPassword string) (*Reconciler, error)
NewReconciler logs in as admin against Keycloak's master realm and returns a Reconciler holding the resulting access token. baseURL is Keycloak's HTTP root — kubeaid-cli's bootstrap passes the cluster's public https://<cluster.Keycloak.DNS>/auth (the keycloakx chart serves Keycloak under /auth).
func (*Reconciler) AssignClientDefaultScopes ¶
func (r *Reconciler) AssignClientDefaultScopes(ctx context.Context, realm, clientID string, scopeNames []string) error
AssignClientDefaultScopes ensures every name in scopeNames is set as a default scope on the client identified by clientID (the user-facing OIDC client_id). Existing assignments are preserved; missing ones are added. Unknown scope names return an error rather than silently being ignored.
func (*Reconciler) AssignClientServiceAccountRole ¶
func (r *Reconciler) AssignClientServiceAccountRole( ctx context.Context, realm, srcClientID, targetClientID, roleName string, ) error
AssignClientServiceAccountRole grants the named role from targetClientID's role catalogue to srcClientID's auto-created service-account user. Both client IDs are user-facing (clientId), not Keycloak's internal id.
Idempotent: when the service-account user already holds the role, no API write is performed.
Typical use: granting `view-users` from the built-in `realm-management` client to `netbird-backend`'s service-account so NetBird can resolve user identities through Keycloak.
func (*Reconciler) EnsureClientDeviceAuthorizationGrant ¶
func (r *Reconciler) EnsureClientDeviceAuthorizationGrant( ctx context.Context, realm, clientID string, enabled bool, ) error
EnsureClientDeviceAuthorizationGrant sets/unsets the OAuth 2.0 Device Authorization Grant (RFC 8628) capability on the named client. Idempotent — no-op when the attribute is already in the desired state.
Keycloak stores this as the `oauth2.device.authorization.grant.enabled` client attribute; there's no top-level Client field for it, so ReconcileClient's create path uses the Attributes map and existing clusters retro-fit the attribute through this method.
Called from ReconcileNetBird for the netbird-client OIDC client — NetBird's CLI (`netbird up`) uses this flow to authenticate from headless contexts. Without the attribute Keycloak rejects /protocol/openid-connect/auth/device with "Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client."
func (*Reconciler) ReconcileClient ¶
func (r *Reconciler) ReconcileClient(ctx context.Context, realm string, spec ClientSpec) (string, error)
ReconcileClient ensures a client matching spec.ClientID exists in the realm. For confidential clients the returned string is the effective client secret (either spec.Secret if pre-set on create, or the secret Keycloak generated and we read back). Public clients return "".
func (*Reconciler) ReconcileClientScope ¶
func (r *Reconciler) ReconcileClientScope(ctx context.Context, realm string, spec ClientScopeSpec) error
ReconcileClientScope ensures a client scope with spec.Name exists in the realm. Idempotent: if a scope with the same name already exists the function no-ops.
func (*Reconciler) ReconcileKubernetes ¶
func (r *Reconciler) ReconcileKubernetes(ctx context.Context, spec KubernetesSpec) error
ReconcileKubernetes upserts the `kubernetes-<ClusterName>` PUBLIC PKCE OIDC client in the given realm and assigns any DefaultScopes the caller asks for. Idempotent — calling with the same spec a second time is a no-op.
The realm and any DefaultScopes referenced here must already exist; this function does not create them. The typical caller runs ReconcileNetBird (which creates the realm + the netbird `api` ClientScope) immediately before, then passes that scope's name in DefaultScopes here.
func (*Reconciler) ReconcileNetBird ¶
func (r *Reconciler) ReconcileNetBird(ctx context.Context, spec NetBirdSpec) error
ReconcileNetBird creates everything NetBird (and the VPN cluster's own kube-apiserver) needs in the customer's Keycloak realm. Idempotent end-to-end: every step delegates to a Reconcile* / Assign* helper that no-ops when the resource already exists.
Order matters — clients depend on the realm; the api scope must exist before it can be assigned to clients; the audience mapper must be on the scope before kube-apiserver tokens get the expected `aud` claim; the service-account role grant requires netbird-backend to exist (so its service-account user has been auto-created).
func (*Reconciler) ReconcileProtocolMapperOnClientScope ¶
func (r *Reconciler) ReconcileProtocolMapperOnClientScope( ctx context.Context, realm, clientScopeName string, spec ProtocolMapperSpec, ) error
ReconcileProtocolMapperOnClientScope ensures a protocol mapper with spec.Name exists on the client scope identified by name and that its config matches spec.Config. Idempotent on Name + Config — missing mapper is created, drifted mapper is updated, otherwise no-op. Only the config keys the spec explicitly sets are diffed, so unrelated keys (operator customizations, Keycloak defaults) aren't overwritten.
func (*Reconciler) ReconcileRealm ¶
func (r *Reconciler) ReconcileRealm(ctx context.Context, name string) error
ReconcileRealm ensures a realm with the given name exists. The realm is created enabled (Realm.Enabled = true) — Keycloak's default leaves it disabled, which would silently break OIDC discovery and token issuance.
func (*Reconciler) ReconcileUser ¶
func (r *Reconciler) ReconcileUser( ctx context.Context, realm string, spec UserSpec, initialPassword string, ) error
ReconcileUser ensures a user with spec.Username exists in the realm. When the user is created and initialPassword is non-empty, the password is set as a non-temporary credential. On a re-run (user already present) the existing password is preserved — kubeaid-cli is not in the password-rotation business.
type UserSpec ¶
type UserSpec struct {
Username string
Email string
FirstName string
LastName string
// Enabled defaults to true. Set explicitly for clarity in
// callers, so a zero-value spec doesn't silently create a
// disabled user.
Enabled bool
}
UserSpec describes a Keycloak user in the realm. Username is required; the rest are optional.