Skip to content
4 changes: 2 additions & 2 deletions pkg/apis/keycloak/v1alpha1/keycloak_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,6 @@ func (i *Keycloak) UpdateStatusSecondaryResources(kind string, resourceName stri
type KeycloakRelatedImages struct {
// If set, operator will use it instead of the default Keycloak image
// +optional
Keycloak string `json:"keycloak,omitempty"`
Keycloak string `json:"keycloak,omitempty"`
ImagePullSecrets []string `json:"imagePullSecrets,omitempty"`
}
}
105 changes: 105 additions & 0 deletions pkg/apis/monkeypatch/monkeypatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package monkeypatch

import "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1"

type KeycloakAPIClient struct {
// Client ID. If not specified, automatically generated.
// +optional
ID string `json:"id,omitempty"`
// Client ID.
// +kubebuilder:validation:Required
ClientID string `json:"clientId"`
// Client name.
// +optional
Name string `json:"name,omitempty"`
// Surrogate Authentication Required option.
// +optional
SurrogateAuthRequired bool `json:"surrogateAuthRequired,omitempty"`
// Client enabled flag.
// +optional
Enabled bool `json:"enabled,omitempty"`
// What Client authentication type to use.
// +optional
ClientAuthenticatorType string `json:"clientAuthenticatorType,omitempty"`
// Client Secret. The Operator will automatically create a Secret based on this value.
// +optional
Secret string `json:"secret,omitempty"`
// Application base URL.
// +optional
BaseURL string `json:"baseUrl,omitempty"`
// Application Admin URL.
// +optional
AdminURL string `json:"adminUrl,omitempty"`
// Application root URL.
// +optional
RootURL string `json:"rootUrl,omitempty"`
// Client description.
// +optional
Description string `json:"description,omitempty"`
// Default Client roles.
// +optional
DefaultRoles []string `json:"defaultRoles,omitempty"`
// Default Client scopes.
// optional
DefaultClientScopes []string `json:"defaultClientScopes,omitempty"`
// A list of valid Redirection URLs.
// +optional
RedirectUris []string `json:"redirectUris"`
// A list of valid Web Origins.
// +optional
WebOrigins []string `json:"webOrigins,omitempty"`
// Not Before setting.
// +optional
NotBefore int `json:"notBefore,omitempty"`
// True if a client supports only Bearer Tokens.
// +optional
BearerOnly bool `json:"bearerOnly"`
// True if Consent Screen is required.
// +optional
ConsentRequired bool `json:"consentRequired"`
// True if Standard flow is enabled.
// +optional
StandardFlowEnabled bool `json:"standardFlowEnabled"`
// True if Implicit flow is enabled.
// +optional
ImplicitFlowEnabled bool `json:"implicitFlowEnabled"`
// True if Direct Grant is enabled.
// +optional
DirectAccessGrantsEnabled bool `json:"directAccessGrantsEnabled"`
// True if Service Accounts are enabled.
// +optional
ServiceAccountsEnabled bool `json:"serviceAccountsEnabled"`
// True if this is a public Client.
// +optional
PublicClient bool `json:"publicClient"`
// True if this client supports Front Channel logout.
// +optional
FrontchannelLogout bool `json:"frontchannelLogout,omitempty"`
// Protocol used for this Client.
// +optional
Protocol string `json:"protocol,omitempty"`
// Client Attributes.
// +optional
Attributes map[string]string `json:"attributes,omitempty"`
// True if Full Scope is allowed.
// +optional
FullScopeAllowed bool `json:"fullScopeAllowed,omitempty"`
// Node registration timeout.
// +optional
NodeReRegistrationTimeout int `json:"nodeReRegistrationTimeout,omitempty"`
// Protocol Mappers.
// +optional
ProtocolMappers []v1alpha1.KeycloakProtocolMapper `json:"protocolMappers,omitempty"`
// True to use a Template Config.
// +optional
UseTemplateConfig bool `json:"useTemplateConfig,omitempty"`
// True to use Template Scope.
// +optional
UseTemplateScope bool `json:"useTemplateScope,omitempty"`
// True to use Template Mappers.
// +optional
UseTemplateMappers bool `json:"useTemplateMappers,omitempty"`
// Access options.
// +optional
Access map[string]bool `json:"access,omitempty"`
}
49 changes: 47 additions & 2 deletions pkg/common/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"strings"
"time"

"github.com/keycloak/keycloak-operator/pkg/apis/monkeypatch"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -50,6 +52,9 @@ func (c *Client) create(obj T, resourcePath, resourceName string) error {
return nil
}

logrus.Infof("Calling POST %s", fmt.Sprintf("%s/auth/admin/%s", c.URL, resourcePath))
logrus.Debugf("Calling POST with body: %s", string(jsonValue))

req, err := http.NewRequest(
"POST",
fmt.Sprintf("%s/auth/admin/%s", c.URL, resourcePath),
Expand Down Expand Up @@ -87,7 +92,14 @@ func (c *Client) CreateRealm(realm *v1alpha1.KeycloakRealm) error {
}

func (c *Client) CreateClient(client *v1alpha1.KeycloakAPIClient, realmName string) error {
return c.create(client, fmt.Sprintf("realms/%s/clients", realmName), "client")
// Pace Keycloak 15 Workaround Fix
client.DefaultRoles = nil

if client.RedirectUris == nil {
client.RedirectUris = make([]string, 1)
}

return c.create(c.MonkeyPatchKeycloakAPIClient(client), fmt.Sprintf("realms/%s/clients", realmName), "client")
}

func (c *Client) CreateUser(user *v1alpha1.KeycloakAPIUser, realmName string) error {
Expand Down Expand Up @@ -273,6 +285,14 @@ func (c *Client) GetClient(clientID, realmName string) (*v1alpha1.KeycloakAPICli
result, err := c.get(fmt.Sprintf("realms/%s/clients/%s", realmName, clientID), "client", func(body []byte) (T, error) {
client := &v1alpha1.KeycloakAPIClient{}
err := json.Unmarshal(body, client)

// Pace Keycloak 15 Workaround Fix
client.DefaultRoles = nil

if client.RedirectUris == nil {
client.RedirectUris = make([]string, 0)
}

return client, err
})
if err != nil {
Expand Down Expand Up @@ -368,6 +388,8 @@ func (c *Client) update(obj T, resourcePath, resourceName string) error {
}

logrus.Infof("Calling PUT %s", fmt.Sprintf("%s/auth/admin/%s", c.URL, resourcePath))
logrus.Debugf("Calling PUT with body: %s", string(jsonValue))

req, err := http.NewRequest(
"PUT",
fmt.Sprintf("%s/auth/admin/%s", c.URL, resourcePath),
Expand Down Expand Up @@ -452,6 +474,12 @@ func (c *Client) updateClientScope(method, clientID, scopeID, realmName string)
return nil
}

// MonkeyPatchKeycloakAPIClient does monkey patching to adjust the tags.
func (c *Client) MonkeyPatchKeycloakAPIClient(specClient *v1alpha1.KeycloakAPIClient) *monkeypatch.KeycloakAPIClient {
var patchClient monkeypatch.KeycloakAPIClient = monkeypatch.KeycloakAPIClient(*specClient)
return &patchClient
}

func (c *Client) UpdateClientScopes(specClient *v1alpha1.KeycloakAPIClient, realmName string) error {
// First get the available scopes for the current realm
availableScopes, err := c.ListAllClientScopes(realmName)
Expand All @@ -468,7 +496,15 @@ func (c *Client) UpdateClientScopes(specClient *v1alpha1.KeycloakAPIClient, real
scopeNameToID[scope.Name] = scope.ID
}

// Pace Keycloak 15 Workaround Fix
specClient.DefaultRoles = nil

if specClient.RedirectUris == nil {
specClient.RedirectUris = make([]string, 0)
}

logrus.Infof("Syncing scopes for client with clientID %s/%s", realmName, specClient.ClientID)

for _, resourceScope := range specClient.DefaultClientScopes {
addScope := true
for _, scope := range currentClientScopes {
Expand All @@ -490,6 +526,7 @@ func (c *Client) UpdateClientScopes(specClient *v1alpha1.KeycloakAPIClient, real
return errors.Wrapf(err, "Could not add scope %s to client %s on realm %s", id, specClient.ID, realmName)
}
}

for _, scope := range currentClientScopes {
removeScope := true
for _, resourceScope := range specClient.DefaultClientScopes {
Expand All @@ -515,10 +552,18 @@ func (c *Client) UpdateRealm(realm *v1alpha1.KeycloakRealm) error {
}

func (c *Client) UpdateClient(specClient *v1alpha1.KeycloakAPIClient, realmName string) error {
err := c.update(specClient, fmt.Sprintf("realms/%s/clients/%s", realmName, specClient.ID), "client")
// Pace Keycloak 15 Workaround Fix
specClient.DefaultRoles = nil

if specClient.RedirectUris == nil {
specClient.RedirectUris = make([]string, 0)
}

err := c.update(c.MonkeyPatchKeycloakAPIClient(specClient), fmt.Sprintf("realms/%s/clients/%s", realmName, specClient.ID), "client")
if err != nil {
return err
}

// Updating (default)ClientScopes requires separate API calls.
return c.UpdateClientScopes(specClient, realmName)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/common/client_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package common
import (
"context"

"github.com/sirupsen/logrus"

kc "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1"
"github.com/keycloak/keycloak-operator/pkg/model"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -35,6 +37,8 @@ func (i *ClientState) Read(context context.Context, cr *kc.KeycloakClient, realm
return nil
}

logrus.Infof("Reading Client-State: %+v", client)

clientSecret, err := realmClient.GetClientSecret(cr.Spec.Client.ID, i.Realm.Spec.Realm.Realm)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/common/cluster_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type ClusterState struct {
KeycloakDeployment *v12.StatefulSet
KeycloakAdminSecret *v1.Secret
KeycloakIngress *v1beta1.Ingress
KeycloakStartupConfigMap *v1.ConfigMap
KeycloakStartupConfigMap *v1.ConfigMap
KeycloakRoute *v13.Route
PostgresqlServiceEndpoints *v1.Endpoints
PodDisruptionBudget *v1beta12.PodDisruptionBudget
Expand Down
1 change: 0 additions & 1 deletion pkg/controller/keycloak/keycloak_migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ func needsMigration(cr *v1alpha1.Keycloak, currentState *common.ClusterState) bo
desiredImage = cr.Spec.ImageOverrides.Keycloak
}


if cr.Spec.Profile == common.RHSSOProfile {
desiredImage = model.RHSSOImage
}
Expand Down
22 changes: 9 additions & 13 deletions pkg/controller/keycloak/keycloak_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,18 +312,15 @@ func (i *KeycloakReconciler) getKeycloakDeploymentOrRHSSODesiredState(clusterSta
}
}

//deploymentReconciled := model.KeycloakDeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret)
//if isRHSSO {
// deploymentReconciled = model.RHSSODeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret)
//}


//return common.GenericUpdateAction{
// Ref: deploymentReconciled,
// Msg: "Update " + deploymentName + " Deployment (StatefulSet)",
//}
deploymentReconciled := model.KeycloakDeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret)
if isRHSSO {
deploymentReconciled = model.RHSSODeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret)
}

return nil
return common.GenericUpdateAction{
Ref: deploymentReconciled,
Msg: "Update " + deploymentName + " Deployment (StatefulSet)",
}
}

func (i *KeycloakReconciler) getKeycloakRouteDesiredState(clusterState *common.ClusterState, cr *kc.Keycloak) common.ClusterAction {
Expand Down Expand Up @@ -381,7 +378,6 @@ func (i *KeycloakReconciler) getPodDisruptionBudgetDesiredState(clusterState *co
return nil
}


func (i *KeycloakReconciler) getKeycloakStartupScriptDesiredState(clusterState *common.ClusterState, cr *kc.Keycloak) common.ClusterAction {
if clusterState.KeycloakStartupConfigMap == nil {
return common.GenericCreateAction{
Expand All @@ -394,4 +390,4 @@ func (i *KeycloakReconciler) getKeycloakStartupScriptDesiredState(clusterState *
Ref: model.KeycloakConfigMapStartupReconiled(cr, clusterState.KeycloakStartupConfigMap),
Msg: "Update Keycloak Startup ConfigMap",
}
}
}
2 changes: 1 addition & 1 deletion pkg/controller/keycloak/keycloak_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,4 @@ func TestKeycloakReconciler_Test_Should_Update_PDB(t *testing.T) {
assert.Equal(t, len(desiredState), 10)
assert.IsType(t, common.GenericUpdateAction{}, desiredState[9])
assert.IsType(t, model.PodDisruptionBudget(cr), desiredState[9].(common.GenericUpdateAction).Ref)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package keycloakclient

import (
Expand Down Expand Up @@ -210,4 +209,4 @@ func TestKeycloakClientReconciler_Test_Marshal_Client_directAccessGrantsEnabled(
// then
assert.Nil(t, err, "Client couldn't be marshalled")
assert.True(t, strings.Contains(s, "\"directAccessGrantsEnabled\":false"), "Element directAccessGrantsEnabled should not be omitted if false, as keycloaks default is true")
}
}
54 changes: 27 additions & 27 deletions pkg/model/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,40 @@ package model

// Constants for a community Keycloak installation
const (
ApplicationName = "keycloak"
MonitoringKey = "middleware"
DatabaseSecretName = ApplicationName + "-db-secret"
PostgresqlPersistentVolumeName = ApplicationName + "-postgresql-claim"
PostgresqlBackupPersistentVolumeName = ApplicationName + "-backup"
PostgresqlDeploymentName = ApplicationName + "-postgresql"
KeycloakProbesName = ApplicationName + "-probes"
PostgresqlDeploymentComponent = "database"
PostgresqlServiceName = ApplicationName + "-postgresql"
PostgresqlImage = "postgres:11.5"
KeycloakImage = "quay.io/keycloak/keycloak:8.0.2"
KeycloakInitContainerImage = "quay.io/keycloak/keycloak-init-container:master"
RHSSOImage = "registry.access.redhat.com/redhat-sso-7/sso73-openshift:1.0-15"
BackupImage = "quay.io/integreatly/backup-container:1.0.10"
KeycloakDiscoveryServiceName = ApplicationName + "-discovery"
KeycloakDeploymentName = ApplicationName
KeycloakDeploymentComponent = "keycloak"
PostgresqlBackupComponent = "database-backup"
PostgresqlDatabase = "root"
PostgresqlPersistentVolumeCapacity = "1Gi"
DatabaseSecretUsernameProperty = "POSTGRES_USERNAME" // nolint
DatabaseSecretPasswordProperty = "POSTGRES_PASSWORD" // nolint
ApplicationName = "keycloak"
MonitoringKey = "middleware"
DatabaseSecretName = ApplicationName + "-db-secret"
PostgresqlPersistentVolumeName = ApplicationName + "-postgresql-claim"
PostgresqlBackupPersistentVolumeName = ApplicationName + "-backup"
PostgresqlDeploymentName = ApplicationName + "-postgresql"
KeycloakProbesName = ApplicationName + "-probes"
PostgresqlDeploymentComponent = "database"
PostgresqlServiceName = ApplicationName + "-postgresql"
PostgresqlImage = "postgres:11.5"
KeycloakImage = "quay.io/keycloak/keycloak:8.0.2"
KeycloakInitContainerImage = "quay.io/keycloak/keycloak-init-container:master"
RHSSOImage = "registry.access.redhat.com/redhat-sso-7/sso73-openshift:1.0-15"
BackupImage = "quay.io/integreatly/backup-container:1.0.10"
KeycloakDiscoveryServiceName = ApplicationName + "-discovery"
KeycloakDeploymentName = ApplicationName
KeycloakDeploymentComponent = "keycloak"
PostgresqlBackupComponent = "database-backup"
PostgresqlDatabase = "root"
PostgresqlPersistentVolumeCapacity = "1Gi"
DatabaseSecretUsernameProperty = "POSTGRES_USERNAME" // nolint
DatabaseSecretPasswordProperty = "POSTGRES_PASSWORD" // nolint
// Required by the Integreately Backup Image
DatabaseSecretHostProperty = "POSTGRES_HOST" // nolint
DatabaseSecretHostProperty = "POSTGRES_HOST" // nolint
// Required by the Integreately Backup Image
DatabaseSecretDatabaseProperty = "POSTGRES_DATABASE" // nolint
DatabaseSecretDatabaseProperty = "POSTGRES_DATABASE" // nolint
// Required by the Integreately Backup Image
DatabaseSecretSuperuserProperty = "POSTGRES_SUPERUSER" // nolint
DatabaseSecretExternalAddressProperty = "POSTGRES_EXTERNAL_ADDRESS" // nolint
DatabaseSecretExternalPortProperty = "POSTGRES_EXTERNAL_PORT" // nolint
KeycloakServicePort = 8080
KeycloakServicePortSSL = 8443
KeycloakPodPort = 8080
KeycloakPodPortSSL = 8443
KeycloakServicePortSSL = 8443
KeycloakPodPort = 8080
KeycloakPodPortSSL = 8443
PostgresDefaultPort = 5432
AdminUsernameProperty = "ADMIN_USERNAME" // nolint
AdminPasswordProperty = "ADMIN_PASSWORD" // nolint
Expand Down
Loading