Working client and Realm controller
This commit is contained in:
parent
fc6f1632da
commit
f949a523be
6 changed files with 339 additions and 27 deletions
|
@ -20,33 +20,29 @@ type Keycloak struct {
|
|||
user, pass, realm string
|
||||
}
|
||||
|
||||
func (kc *Keycloak) CreateRealmIfNotExists(ctx context.Context, realm gocloak.RealmRepresentation) error {
|
||||
func (kc *Keycloak) CreateRealm(ctx context.Context, realm gocloak.RealmRepresentation) error {
|
||||
_, err := kc.client.CreateRealm(ctx, kc.getToken(), realm)
|
||||
if isConflict(err) {
|
||||
log.Printf("Realm '%s' already exists, not updated", *realm.Realm)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (kc *Keycloak) CreateProviderIfNotExists(ctx context.Context, realm string, provider gocloak.IdentityProviderRepresentation) error {
|
||||
func (kc *Keycloak) UpdateRealm(ctx context.Context, realm gocloak.RealmRepresentation) error {
|
||||
return kc.client.UpdateRealm(ctx, kc.getToken(), realm)
|
||||
}
|
||||
|
||||
func (kc *Keycloak) CreateProvider(ctx context.Context, realm string, provider gocloak.IdentityProviderRepresentation) error {
|
||||
_, err := kc.client.CreateIdentityProvider(ctx, kc.getToken(), realm, provider)
|
||||
if isConflict(err) {
|
||||
log.Printf("Provider '%s/%s' already exists, not updated", realm, *provider.Alias)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (kc *Keycloak) CreateClientIfNotExists(ctx context.Context, realm string, c gocloak.Client) error {
|
||||
func (kc *Keycloak) CreateClient(ctx context.Context, realm string, c gocloak.Client) error {
|
||||
_, err := kc.client.CreateClient(ctx, kc.getToken(), realm, c)
|
||||
if isConflict(err) {
|
||||
log.Printf("Client '%s/%s' already exists, not updated", realm, *c.ClientID)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (kc *Keycloak) UpdateClient(ctx context.Context, realm string, c gocloak.Client) error {
|
||||
return kc.client.UpdateClient(ctx, kc.getToken(), realm, c)
|
||||
}
|
||||
|
||||
func New(url, user, pass, realm string) (*Keycloak, error) {
|
||||
kc := &Keycloak{}
|
||||
kc.client = gocloak.NewClient(url)
|
||||
|
|
62
controllers/keycloakclient.go
Normal file
62
controllers/keycloakclient.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
keycloakv1alpha1 "git.zom.bi/images/keycloak-operator/api/v1alpha1"
|
||||
"github.com/Nerzal/gocloak/v7"
|
||||
)
|
||||
|
||||
// ConvertToClient takes a CRD representation and converts it into a datatype
|
||||
// that can be understood by GoCloak.
|
||||
func ConvertToClient(clientCrd keycloakv1alpha1.KeycloakClient) (gocloak.Client, error) {
|
||||
var client gocloak.Client
|
||||
|
||||
clientSpec := clientCrd.Spec
|
||||
|
||||
// Mandatory Properties
|
||||
client.ClientID = &clientSpec.ClientID
|
||||
|
||||
// Optional Properties
|
||||
client.Enabled = clientSpec.Enabled
|
||||
client.Name = clientSpec.Name
|
||||
client.Description = clientSpec.Description
|
||||
client.Protocol = clientSpec.Protocol
|
||||
client.ClientAuthenticatorType = clientSpec.ClientAuthenticatorType
|
||||
client.DirectAccessGrantsEnabled = clientSpec.DirectAccessGrantsEnabled
|
||||
client.PublicClient = clientSpec.PublicClient
|
||||
client.ImplicitFlowEnabled = clientSpec.ImplicitFlowEnabled
|
||||
client.StandardFlowEnabled = clientSpec.StandardFlowEnabled
|
||||
client.ServiceAccountsEnabled = clientSpec.ServiceAccountsEnabled
|
||||
client.RegistrationAccessToken = clientSpec.RegistrationAccessToken
|
||||
client.SurrogateAuthRequired = clientSpec.SurrogateAuthRequired
|
||||
client.BearerOnly = clientSpec.BearerOnly
|
||||
client.ConsentRequired = clientSpec.ConsentRequired
|
||||
client.DefaultClientScopes = clientSpec.DefaultClientScopes
|
||||
client.OptionalClientScopes = clientSpec.OptionalClientScopes
|
||||
client.BaseURL = clientSpec.BaseURL
|
||||
client.RootURL = clientSpec.RootURL
|
||||
client.AdminURL = clientSpec.AdminURL
|
||||
client.RedirectURIs = clientSpec.RedirectURIs
|
||||
client.WebOrigins = clientSpec.WebOrigins
|
||||
|
||||
if clientSpec.Secret != nil {
|
||||
// TODO
|
||||
// client.Secret = ""
|
||||
}
|
||||
|
||||
// client.Access = ""
|
||||
// client.Attributes = ""
|
||||
// client.AuthenticationFlowBindingOverrides = ""
|
||||
// client.AuthorizationServicesEnabled = ""
|
||||
// client.AuthorizationSettings = ""
|
||||
// client.DefaultRoles = ""
|
||||
// client.FrontChannelLogout = ""
|
||||
// client.FullScopeAllowed = ""
|
||||
// client.ID = ""
|
||||
// client.NodeReRegistrationTimeout = ""
|
||||
// client.NotBefore = ""
|
||||
// client.Origin = ""
|
||||
// client.ProtocolMappers = ""
|
||||
// client.RegisteredNodes = ""
|
||||
|
||||
return client, nil
|
||||
}
|
|
@ -19,7 +19,9 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Nerzal/gocloak/v7"
|
||||
"github.com/go-logr/logr"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
@ -50,9 +52,79 @@ type KeycloakClientReconciler struct {
|
|||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile
|
||||
func (r *KeycloakClientReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = r.Log.WithValues("keycloakclient", req.NamespacedName)
|
||||
log := r.Log.WithValues("keycloakclient", req.NamespacedName)
|
||||
|
||||
// your logic here
|
||||
log.Info("reconciling")
|
||||
|
||||
// We get the information from the CRD
|
||||
var client keycloakv1alpha1.KeycloakClient
|
||||
if err := r.Get(ctx, req.NamespacedName, &client); err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
// Client is already deleted via finalizer.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if !client.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
// is in the process of being deleted
|
||||
if containsString(client.ObjectMeta.Finalizers, FinalizerName) {
|
||||
// our finalizer is present, so lets handle any external dependency
|
||||
|
||||
// We do not want to delete anything, so we just disable the client.
|
||||
disabled := gocloak.Client{ClientID: &client.Spec.ClientID, Enabled: gocloak.BoolP(false)}
|
||||
err := r.Keycloak.UpdateClient(ctx, client.Spec.RealmName, disabled)
|
||||
if err != nil {
|
||||
// if fail to delete the external dependency here, return with error
|
||||
// so that it can be retried
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// remove our finalizer from the list and update it.
|
||||
client.ObjectMeta.Finalizers = removeString(client.ObjectMeta.Finalizers, FinalizerName)
|
||||
if err := r.Update(ctx, &client); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
log.Info("Deleted the client")
|
||||
}
|
||||
|
||||
// done
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Its not being deleted, so we seize the moment to take ownership.
|
||||
if !containsString(client.ObjectMeta.Finalizers, FinalizerName) {
|
||||
typeMeta := client.TypeMeta
|
||||
client.ObjectMeta.Finalizers = append(client.ObjectMeta.Finalizers, FinalizerName)
|
||||
if err := r.Update(ctx, &client); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
// restore the TypeMeta object as it is removed during Update, but need to be accessed later
|
||||
client.TypeMeta = typeMeta
|
||||
}
|
||||
|
||||
// Convert Client
|
||||
keycloakClient, err := ConvertToClient(client)
|
||||
if err != nil {
|
||||
log.Error(err, "Could not convert client")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
err = r.Keycloak.CreateClient(ctx, client.Spec.RealmName, keycloakClient)
|
||||
if err != nil {
|
||||
// try updating instead
|
||||
err := r.Keycloak.UpdateClient(ctx, client.Spec.RealmName, keycloakClient)
|
||||
if err != nil {
|
||||
log.Error(err, "Could not create/update client")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
log.Info("Updated the client")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
client.Status.Available = true
|
||||
r.Status().Update(ctx, &client)
|
||||
log.Info("Successfully created client")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
|
131
controllers/keycloakrealm.go
Normal file
131
controllers/keycloakrealm.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
keycloakv1alpha1 "git.zom.bi/images/keycloak-operator/api/v1alpha1"
|
||||
"github.com/Nerzal/gocloak/v7"
|
||||
)
|
||||
|
||||
// ConvertToRealm takes a CRD representation and converts it into a datatype
|
||||
// that can be understood by GoCloak.
|
||||
func ConvertToRealm(realmCrd keycloakv1alpha1.KeycloakRealm) (gocloak.RealmRepresentation, error) {
|
||||
var realm gocloak.RealmRepresentation
|
||||
|
||||
realmSpec := realmCrd.Spec
|
||||
|
||||
// Mandatory Properties
|
||||
realm.Realm = &realmSpec.RealmName
|
||||
|
||||
// Optional Properties
|
||||
realm.Enabled = realmSpec.Enabled
|
||||
realm.DisplayName = realmSpec.DisplayName
|
||||
realm.DisplayNameHTML = realmSpec.DisplayNameHTML
|
||||
realm.LoginTheme = realmSpec.LoginTheme
|
||||
realm.LoginWithEmailAllowed = realmSpec.LoginWithEmailAllowed
|
||||
realm.RegistrationAllowed = realmSpec.RegistrationAllowed
|
||||
realm.EditUsernameAllowed = realmSpec.EditUsernameAllowed
|
||||
realm.RegistrationEmailAsUsername = realmSpec.RegistrationEmailAsUsername
|
||||
realm.ResetPasswordAllowed = realmSpec.ResetPasswordAllowed
|
||||
realm.DuplicateEmailsAllowed = realmSpec.DuplicateEmailsAllowed
|
||||
realm.VerifyEmail = realmSpec.VerifyEmail
|
||||
realm.RememberMe = realmSpec.RememberMe
|
||||
|
||||
if realmSpec.SMTP != nil {
|
||||
smtp := map[string]string{}
|
||||
if realmSpec.SMTP.Auth {
|
||||
smtp["auth"] = "true"
|
||||
}
|
||||
|
||||
if realmSpec.SMTP.Secret != nil {
|
||||
// TODO
|
||||
}
|
||||
|
||||
if realmSpec.SMTP.From != "" {
|
||||
smtp["from"] = realmSpec.SMTP.From
|
||||
}
|
||||
|
||||
realm.SMTPServer = &smtp
|
||||
}
|
||||
|
||||
//realm.AccessCodeLifespan = ""
|
||||
//realm.AccessCodeLifespanLogin = ""
|
||||
//realm.AccessCodeLifespanUserAction = ""
|
||||
//realm.AccessTokenLifespan = ""
|
||||
//realm.AccessTokenLifespanForImplicitFlow = ""
|
||||
//realm.AccountTheme = ""
|
||||
//realm.ActionTokenGeneratedByAdminLifespan = ""
|
||||
//realm.ActionTokenGeneratedByUserLifespan = ""
|
||||
//realm.AdminEventsDetailsEnabled = ""
|
||||
//realm.AdminEventsEnabled = ""
|
||||
//realm.AdminTheme = ""
|
||||
//realm.Attributes = ""
|
||||
//realm.AuthenticationFlows = ""
|
||||
//realm.AuthenticatorConfig = ""
|
||||
//realm.BrowserFlow = ""
|
||||
//realm.BrowserSecurityHeaders = ""
|
||||
//realm.BruteForceProtected = ""
|
||||
//realm.ClientAuthenticationFlow = ""
|
||||
//realm.ClientScopeMappings = ""
|
||||
//realm.ClientScopes = ""
|
||||
//realm.Clients = ""
|
||||
//realm.Components = ""
|
||||
//realm.DefaultDefaultClientScopes = ""
|
||||
//realm.DefaultGroups = ""
|
||||
//realm.DefaultLocale = ""
|
||||
//realm.DefaultOptionalClientScopes = ""
|
||||
//realm.DefaultRoles = ""
|
||||
//realm.DefaultSignatureAlgorithm = ""
|
||||
//realm.DirectGrantFlow = ""
|
||||
//realm.DockerAuthenticationFlow = ""
|
||||
//realm.EmailTheme = ""
|
||||
//realm.EnabledEventTypes = ""
|
||||
//realm.EventsEnabled = ""
|
||||
//realm.EventsExpiration = ""
|
||||
//realm.EventsListeners = ""
|
||||
//realm.FailureFactor = ""
|
||||
//realm.FederatedUsers = ""
|
||||
//realm.Groups = ""
|
||||
//realm.ID = ""
|
||||
//realm.IdentityProviderMappers = ""
|
||||
//realm.IdentityProviders = ""
|
||||
//realm.InternationalizationEnabled = ""
|
||||
//realm.KeycloakVersion = ""
|
||||
//realm.MaxDeltaTimeSeconds = ""
|
||||
//realm.MaxFailureWaitSeconds = ""
|
||||
//realm.MinimumQuickLoginWaitSeconds = ""
|
||||
//realm.NotBefore = ""
|
||||
//realm.OfflineSessionIdleTimeout = ""
|
||||
//realm.OfflineSessionMaxLifespan = ""
|
||||
//realm.OfflineSessionMaxLifespanEnabled = ""
|
||||
//realm.OtpPolicyAlgorithm = ""
|
||||
//realm.OtpPolicyDigits = ""
|
||||
//realm.OtpPolicyInitialCounter = ""
|
||||
//realm.OtpPolicyLookAheadWindow = ""
|
||||
//realm.OtpPolicyPeriod = ""
|
||||
//realm.OtpPolicyType = ""
|
||||
//realm.OtpSupportedApplications = ""
|
||||
//realm.PasswordPolicy = ""
|
||||
//realm.PermanentLockout = ""
|
||||
//realm.ProtocolMappers = ""
|
||||
//realm.QuickLoginCheckMilliSeconds = ""
|
||||
//realm.RefreshTokenMaxReuse = ""
|
||||
//realm.RegistrationFlow = ""
|
||||
//realm.RequiredActions = ""
|
||||
//realm.ResetCredentialsFlow = ""
|
||||
//realm.RevokeRefreshToken = ""
|
||||
//realm.Roles = ""
|
||||
//realm.ScopeMappings = ""
|
||||
//realm.SMTPServer = ""
|
||||
//realm.SslRequired = ""
|
||||
//realm.SsoSessionIdleTimeout = ""
|
||||
//realm.SsoSessionIdleTimeoutRememberMe = ""
|
||||
//realm.SsoSessionMaxLifespan = ""
|
||||
//realm.SsoSessionMaxLifespanRememberMe = ""
|
||||
//realm.SupportedLocales = ""
|
||||
//realm.UserFederationMappers = ""
|
||||
//realm.UserFederationProviders = ""
|
||||
//realm.UserManagedAccessAllowed = ""
|
||||
//realm.Users = ""
|
||||
//realm.WaitIncrementSeconds = ""
|
||||
|
||||
return realm, nil
|
||||
}
|
|
@ -19,6 +19,7 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Nerzal/gocloak/v7"
|
||||
"github.com/go-logr/logr"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -55,29 +56,75 @@ func (r *KeycloakRealmReconciler) Reconcile(ctx context.Context, req ctrl.Reques
|
|||
|
||||
log.Info("reconciling")
|
||||
|
||||
// We get the information from the CRD
|
||||
var realm keycloakv1alpha1.KeycloakRealm
|
||||
if err := r.Get(ctx, req.NamespacedName, &realm); err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
log.Info("I would now unregister the realm")
|
||||
// Realm is already deleted via finalizer.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if realm.Status.Available {
|
||||
// try to get existing realm
|
||||
log.Info("Would try to fetch the realm by its id.",
|
||||
"id", realm.Spec.RealmName)
|
||||
// if found {
|
||||
log.Info("will act like i found it, updating.")
|
||||
// update()
|
||||
if !realm.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
// is in the process of being deleted
|
||||
if containsString(realm.ObjectMeta.Finalizers, FinalizerName) {
|
||||
// our finalizer is present, so lets handle any external dependency
|
||||
|
||||
// We do not want to delete anything, so we just disable the realm.
|
||||
disabled := gocloak.RealmRepresentation{Realm: &realm.Spec.RealmName, Enabled: gocloak.BoolP(false)}
|
||||
err := r.Keycloak.UpdateRealm(ctx, disabled)
|
||||
if err != nil {
|
||||
// if fail to delete the external dependency here, return with error
|
||||
// so that it can be retried
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// remove our finalizer from the list and update it.
|
||||
realm.ObjectMeta.Finalizers = removeString(realm.ObjectMeta.Finalizers, FinalizerName)
|
||||
if err := r.Update(ctx, &realm); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
log.Info("Deleted the realm")
|
||||
}
|
||||
|
||||
// done
|
||||
return ctrl.Result{}, nil
|
||||
// }
|
||||
}
|
||||
|
||||
log.Info("Would now create the realm.")
|
||||
// Its not being deleted, so we seize the moment to take ownership.
|
||||
if !containsString(realm.ObjectMeta.Finalizers, FinalizerName) {
|
||||
typeMeta := realm.TypeMeta
|
||||
realm.ObjectMeta.Finalizers = append(realm.ObjectMeta.Finalizers, FinalizerName)
|
||||
if err := r.Update(ctx, &realm); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
// restore the TypeMeta object as it is removed during Update, but need to be accessed later
|
||||
realm.TypeMeta = typeMeta
|
||||
}
|
||||
|
||||
// Convert Realm
|
||||
keycloakRealm, err := ConvertToRealm(realm)
|
||||
if err != nil {
|
||||
log.Error(err, "Could not convert realm")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
err = r.Keycloak.CreateRealm(ctx, keycloakRealm)
|
||||
if err != nil {
|
||||
// try updating instead
|
||||
err := r.Keycloak.UpdateRealm(ctx, keycloakRealm)
|
||||
if err != nil {
|
||||
log.Error(err, "Could not create/update realm")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
log.Info("Updated the realm")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
realm.Status.Available = true
|
||||
r.Status().Update(ctx, &realm)
|
||||
log.Info("Successfully created realm")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue