diff --git a/pkg/common/cluster_state.go b/pkg/common/cluster_state.go index 1ba56cc91..d6530b7da 100644 --- a/pkg/common/cluster_state.go +++ b/pkg/common/cluster_state.go @@ -52,6 +52,7 @@ type ClusterState struct { KeycloakPrometheusRule *monitoringv1.PrometheusRule KeycloakGrafanaDashboard *grafanav1alpha1.GrafanaDashboard DatabaseSecret *v1.Secret + DatabaseSSLCert *v1.Secret PostgresqlPersistentVolumeClaim *v1.PersistentVolumeClaim PostgresqlService *v1.Service PostgresqlDeployment *v12.Deployment @@ -98,6 +99,11 @@ func (i *ClusterState) Read(context context.Context, cr *kc.Keycloak, controller return err } + err = i.readDatabaseSSLSecretCurrentState(context, cr, controllerClient) + if err != nil { + return err + } + err = i.readProbesCurrentState(context, cr, controllerClient) if err != nil { return err @@ -351,6 +357,29 @@ func (i *ClusterState) readKeycloakGrafanaDashboardCurrentState(context context. return nil } +func (i *ClusterState) readDatabaseSSLSecretCurrentState(context context.Context, cr *kc.Keycloak, controllerClient client.Client) error { + databaseSSLSecret := &v1.Secret{} + databaseSSLSecretSelector := client.ObjectKey{ + Name: model.DatabaseSecretSslCert, + Namespace: cr.Namespace, + } + + err := controllerClient.Get(context, databaseSSLSecretSelector, databaseSSLSecret) + + if err != nil { + // If the resource type doesn't exist on the cluster or does exist but is not found + if meta.IsNoMatchError(err) || apiErrors.IsNotFound(err) { + i.DatabaseSSLCert = nil + } else { + return err + } + } else { + i.DatabaseSSLCert = databaseSSLSecret.DeepCopy() + cr.UpdateStatusSecondaryResources(i.DatabaseSSLCert.Kind, i.DatabaseSSLCert.Name) + } + return nil +} + func (i *ClusterState) readDatabaseSecretCurrentState(context context.Context, cr *kc.Keycloak, controllerClient client.Client) error { databaseSecret := model.DatabaseSecret(cr) databaseSecretSelector := model.DatabaseSecretSelector(cr) @@ -394,10 +423,10 @@ func (i *ClusterState) readProbesCurrentState(context context.Context, cr *kc.Ke func (i *ClusterState) readKeycloakOrRHSSODeploymentCurrentState(context context.Context, cr *kc.Keycloak, controllerClient client.Client) error { isRHSSO := model.Profiles.IsRHSSO(cr) - deployment := model.KeycloakDeployment(cr, nil) + deployment := model.KeycloakDeployment(cr, nil, nil) selector := model.KeycloakDeploymentSelector(cr) if isRHSSO { - deployment = model.RHSSODeployment(cr, nil) + deployment = model.RHSSODeployment(cr, nil, nil) selector = model.RHSSODeploymentSelector(cr) } diff --git a/pkg/controller/keycloak/keycloak_migrations_test.go b/pkg/controller/keycloak/keycloak_migrations_test.go index 6873de7cb..70e7286b7 100644 --- a/pkg/controller/keycloak/keycloak_migrations_test.go +++ b/pkg/controller/keycloak/keycloak_migrations_test.go @@ -31,7 +31,7 @@ func TestKeycloakMigration_Test_No_Need_For_Migration_On_Missing_Deployment_In_D cr := &v1alpha1.Keycloak{} migrator, _ := GetMigrator(cr) - keycloakDeployment := model.KeycloakDeployment(cr, nil) + keycloakDeployment := model.KeycloakDeployment(cr, nil, nil) SetDeployment(keycloakDeployment, 5, "old_image") currentState := common.ClusterState{ @@ -56,10 +56,10 @@ func TestKeycloakMigration_Test_Migrating_Image(t *testing.T) { cr := &v1alpha1.Keycloak{} migrator, _ := GetMigrator(cr) - keycloakCurrentDeployment := model.KeycloakDeployment(cr, model.DatabaseSecret(cr)) + keycloakCurrentDeployment := model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil) SetDeployment(keycloakCurrentDeployment, 5, "old_image") - keycloakDesiredDeployment := model.KeycloakDeployment(cr, nil) + keycloakDesiredDeployment := model.KeycloakDeployment(cr, nil, nil) SetDeployment(keycloakDesiredDeployment, 5, "") currentState := common.ClusterState{ @@ -91,10 +91,10 @@ func TestKeycloakMigration_Test_Migrating_RHSSO_Image(t *testing.T) { } migrator, _ := GetMigrator(cr) - keycloakCurrentDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr)) + keycloakCurrentDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil) SetDeployment(keycloakCurrentDeployment, 5, "old_image") - keycloakDesiredDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr)) + keycloakDesiredDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil) SetDeployment(keycloakDesiredDeployment, 5, "") currentState := common.ClusterState{ @@ -131,10 +131,10 @@ func TBackup(t *testing.T, backupEnabled bool) { cr.Spec.Migration.Backups.Enabled = backupEnabled migrator, _ := GetMigrator(cr) - keycloakCurrentDeployment := model.KeycloakDeployment(cr, nil) + keycloakCurrentDeployment := model.KeycloakDeployment(cr, nil, nil) SetDeployment(keycloakCurrentDeployment, 0, "old_image") - keycloakDesiredDeployment := model.KeycloakDeployment(cr, nil) + keycloakDesiredDeployment := model.KeycloakDeployment(cr, nil, nil) SetDeployment(keycloakDesiredDeployment, 0, "") currentState := common.ClusterState{ @@ -164,10 +164,10 @@ func TestKeycloakMigration_Test_No_Migration_Happens_With_Rolling_Migrator(t *te cr.Spec.Migration.MigrationStrategy = v1alpha1.StrategyRolling migrator, _ := GetMigrator(cr) - keycloakCurrentDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr)) + keycloakCurrentDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil) SetDeployment(keycloakCurrentDeployment, 5, "old_image") - keycloakDesiredDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr)) + keycloakDesiredDeployment := model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil) SetDeployment(keycloakDesiredDeployment, 5, "") currentState := common.ClusterState{ diff --git a/pkg/controller/keycloak/keycloak_reconciler.go b/pkg/controller/keycloak/keycloak_reconciler.go index 26b5ad1e4..0e5a2f7b1 100644 --- a/pkg/controller/keycloak/keycloak_reconciler.go +++ b/pkg/controller/keycloak/keycloak_reconciler.go @@ -298,11 +298,11 @@ func (i *KeycloakReconciler) getDatabaseSecretDesiredState(clusterState *common. func (i *KeycloakReconciler) getKeycloakDeploymentOrRHSSODesiredState(clusterState *common.ClusterState, cr *kc.Keycloak) common.ClusterAction { isRHSSO := model.Profiles.IsRHSSO(cr) - deployment := model.KeycloakDeployment(cr, clusterState.DatabaseSecret) + deployment := model.KeycloakDeployment(cr, clusterState.DatabaseSecret, clusterState.DatabaseSSLCert) deploymentName := "Keycloak" if isRHSSO { - deployment = model.RHSSODeployment(cr, clusterState.DatabaseSecret) + deployment = model.RHSSODeployment(cr, clusterState.DatabaseSecret, clusterState.DatabaseSSLCert) deploymentName = model.RHSSOProfile } @@ -313,9 +313,9 @@ func (i *KeycloakReconciler) getKeycloakDeploymentOrRHSSODesiredState(clusterSta } } - deploymentReconciled := model.KeycloakDeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret) + deploymentReconciled := model.KeycloakDeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret, clusterState.DatabaseSSLCert) if isRHSSO { - deploymentReconciled = model.RHSSODeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret) + deploymentReconciled = model.RHSSODeploymentReconciled(cr, clusterState.KeycloakDeployment, clusterState.DatabaseSecret, clusterState.DatabaseSSLCert) } return common.GenericUpdateAction{ diff --git a/pkg/controller/keycloak/keycloak_reconciler_test.go b/pkg/controller/keycloak/keycloak_reconciler_test.go index 2699fe6c3..0be4f2ec4 100644 --- a/pkg/controller/keycloak/keycloak_reconciler_test.go +++ b/pkg/controller/keycloak/keycloak_reconciler_test.go @@ -3,6 +3,7 @@ package keycloak import ( "reflect" "strconv" + "strings" "testing" "k8s.io/apimachinery/pkg/api/resource" @@ -86,7 +87,7 @@ func TestKeycloakReconciler_Test_Creating_All(t *testing.T) { assert.IsType(t, model.KeycloakDiscoveryService(cr), desiredState[9].(common.GenericCreateAction).Ref) assert.IsType(t, model.KeycloakMonitoringService(cr), desiredState[10].(common.GenericCreateAction).Ref) assert.IsType(t, model.KeycloakProbes(cr), desiredState[11].(common.GenericCreateAction).Ref) - assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr)), desiredState[12].(common.GenericCreateAction).Ref) + assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil), desiredState[12].(common.GenericCreateAction).Ref) assert.IsType(t, model.KeycloakRoute(cr), desiredState[13].(common.GenericCreateAction).Ref) } @@ -114,7 +115,7 @@ func TestKeycloakReconciler_Test_Creating_RHSSO(t *testing.T) { if reflect.TypeOf(v) != reflect.TypeOf(common.GenericCreateAction{}) { allCreateActions = false } - if reflect.TypeOf(v.(common.GenericCreateAction).Ref) == reflect.TypeOf(model.RHSSODeployment(cr, model.DatabaseSecret(cr))) { + if reflect.TypeOf(v.(common.GenericCreateAction).Ref) == reflect.TypeOf(model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil)) { deployment = v.(common.GenericCreateAction).Ref.(*v13.StatefulSet) } if reflect.TypeOf(v.(common.GenericCreateAction).Ref) == reflect.TypeOf(model.KeycloakIngress(cr)) { @@ -124,7 +125,7 @@ func TestKeycloakReconciler_Test_Creating_RHSSO(t *testing.T) { assert.True(t, allCreateActions) assert.NotNil(t, deployment) assert.NotNil(t, ingress) - assert.Equal(t, model.RHSSODeployment(cr, nil), deployment) + assert.Equal(t, model.RHSSODeployment(cr, nil, nil), deployment) } func TestKeycloakReconciler_Test_Updating_RHSSO(t *testing.T) { @@ -148,7 +149,7 @@ func TestKeycloakReconciler_Test_Updating_RHSSO(t *testing.T) { PostgresqlDeployment: model.PostgresqlDeployment(cr, true), KeycloakService: model.KeycloakService(cr), KeycloakDiscoveryService: model.KeycloakDiscoveryService(cr), - KeycloakDeployment: model.RHSSODeployment(cr, model.DatabaseSecret(cr)), + KeycloakDeployment: model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil), KeycloakAdminSecret: model.KeycloakAdminSecret(cr), KeycloakIngress: model.KeycloakIngress(cr), KeycloakProbes: model.KeycloakProbes(cr), @@ -165,13 +166,13 @@ func TestKeycloakReconciler_Test_Updating_RHSSO(t *testing.T) { if reflect.TypeOf(v) != reflect.TypeOf(common.GenericUpdateAction{}) { allUpdateActions = false } - if reflect.TypeOf(v.(common.GenericUpdateAction).Ref) == reflect.TypeOf(model.RHSSODeployment(cr, model.DatabaseSecret(cr))) { + if reflect.TypeOf(v.(common.GenericUpdateAction).Ref) == reflect.TypeOf(model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil)) { deployment = v.(common.GenericUpdateAction).Ref.(*v13.StatefulSet) } } assert.True(t, allUpdateActions) assert.NotNil(t, deployment) - assert.Equal(t, model.RHSSODeployment(cr, model.DatabaseSecret(cr)), deployment) + assert.Equal(t, model.RHSSODeployment(cr, model.DatabaseSecret(cr), nil), deployment) } func TestKeycloakReconciler_Test_Updating_All(t *testing.T) { @@ -192,7 +193,7 @@ func TestKeycloakReconciler_Test_Updating_All(t *testing.T) { KeycloakService: model.KeycloakService(cr), KeycloakDiscoveryService: model.KeycloakDiscoveryService(cr), KeycloakMonitoringService: model.KeycloakMonitoringService(cr), - KeycloakDeployment: model.KeycloakDeployment(cr, model.DatabaseSecret(cr)), + KeycloakDeployment: model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil), KeycloakAdminSecret: model.KeycloakAdminSecret(cr), KeycloakRoute: model.KeycloakRoute(cr), KeycloakMetricsRoute: model.KeycloakMetricsRoute(cr, model.KeycloakRoute(cr)), @@ -253,7 +254,7 @@ func TestKeycloakReconciler_Test_Updating_All(t *testing.T) { assert.IsType(t, model.KeycloakService(cr), desiredState[8].(common.GenericUpdateAction).Ref) assert.IsType(t, model.KeycloakDiscoveryService(cr), desiredState[9].(common.GenericUpdateAction).Ref) assert.IsType(t, model.KeycloakMonitoringService(cr), desiredState[10].(common.GenericUpdateAction).Ref) - assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr)), desiredState[11].(common.GenericUpdateAction).Ref) + assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil), desiredState[11].(common.GenericUpdateAction).Ref) assert.IsType(t, model.KeycloakMetricsRoute(cr, model.KeycloakRoute(cr)), desiredState[12].(common.GenericUpdateAction).Ref) } @@ -306,7 +307,7 @@ func TestKeycloakReconciler_Test_Creating_All_With_External_Database(t *testing. assert.IsType(t, model.KeycloakService(cr), desiredState[2].(common.GenericCreateAction).Ref) assert.IsType(t, model.KeycloakDiscoveryService(cr), desiredState[3].(common.GenericCreateAction).Ref) assert.IsType(t, model.KeycloakProbes(cr), desiredState[4].(common.GenericCreateAction).Ref) - assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr)), desiredState[5].(common.GenericCreateAction).Ref) + assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil), desiredState[5].(common.GenericCreateAction).Ref) } func TestKeycloakReconciler_Test_Updating_External_Database_WithIPAddress(t *testing.T) { @@ -382,6 +383,109 @@ func TestKeycloakReconciler_Test_Updating_External_Database_URI(t *testing.T) { assert.Nil(t, service.Spec.Selector) } +func TestKeycloakReconciler_Test_Given_SSLMODE_When_Reconcile_Then_NewEnvVarsAndMountedVolume(t *testing.T) { + // given + cr := &v1alpha1.Keycloak{} + cr.Spec.ExternalDatabase.Enabled = true + + currentState := common.NewClusterState() + currentState.DatabaseSecret = model.DatabaseSecret(cr) + currentState.DatabaseSecret.Data[model.DatabaseSecretSslModeProperty] = []byte("required") + currentState.DatabaseSSLCert = model.DatabaseSecret(cr) + + // when + reconciler := NewKeycloakReconciler() + desiredState := reconciler.Reconcile(currentState, cr) + // element 5 is the KeycloakDeployment + keycloakSpec := desiredState[5].(common.GenericCreateAction).Ref.(*v13.StatefulSet).Spec.Template.Spec + + // then + envVarOk := false + for _, a := range keycloakSpec.Containers[0].Env { + if a.Name == model.KeycloakDatabaseConnectionParamsProperty && strings.Contains(a.Value, "sslmode=required") { + envVarOk = true + } + } + assert.True(t, envVarOk) + + sslVolumeExists := false + for _, volume := range keycloakSpec.Volumes { + if strings.Contains(volume.Name, model.DatabaseSecretSslCert+"-vol") { + sslVolumeExists = true + } + } + assert.True(t, sslVolumeExists) +} + +func TestKeycloakReconciler_Test_Given_SSLMODE_And_RHSSO_When_Reconcile_Then_NewEnvVarsAndMountedVolume(t *testing.T) { + // given + cr := &v1alpha1.Keycloak{} + cr.Spec.ExternalDatabase.Enabled = true + cr.Spec.Profile = "RHSSO" + + currentState := common.NewClusterState() + currentState.DatabaseSecret = model.DatabaseSecret(cr) + currentState.DatabaseSecret.Data[model.DatabaseSecretSslModeProperty] = []byte("required") + currentState.DatabaseSSLCert = model.DatabaseSecret(cr) + + // when + reconciler := NewKeycloakReconciler() + desiredState := reconciler.Reconcile(currentState, cr) + // element 5 is the KeycloakDeployment + keycloakSpec := desiredState[5].(common.GenericCreateAction).Ref.(*v13.StatefulSet).Spec.Template.Spec + + // then + envVarFound := 0 + for _, a := range keycloakSpec.Containers[0].Env { + if strings.Contains(a.Name, model.RhssoDatabaseNONXAConnectionParamsProperty) && strings.EqualFold(a.Value, "required") { + envVarFound++ + } else if strings.Contains(a.Name, model.RhssoDatabaseXAConnectionParamsProperty) && strings.EqualFold(a.Value, "required") { + envVarFound++ + } + } + assert.Equal(t, 2, envVarFound) + + sslVolumeExists := false + for _, volume := range keycloakSpec.Volumes { + if strings.Contains(volume.Name, model.DatabaseSecretSslCert+"-vol") { + sslVolumeExists = true + } + } + assert.True(t, sslVolumeExists) +} + +func TestKeycloakReconciler_Test_Given_NoSSLMODE_When_Reconcile_Then_NoNewEnvVarsAndMountedVolume(t *testing.T) { + // given + cr := &v1alpha1.Keycloak{} + cr.Spec.ExternalDatabase.Enabled = true + + currentState := common.NewClusterState() + currentState.DatabaseSecret = model.DatabaseSecret(cr) + + // when + reconciler := NewKeycloakReconciler() + desiredState := reconciler.Reconcile(currentState, cr) + // element 5 is the KeycloakDeployment + keycloakSpec := desiredState[5].(common.GenericCreateAction).Ref.(*v13.StatefulSet).Spec.Template.Spec + + // then + sslVolumeExists := false + for _, volume := range keycloakSpec.Volumes { + if strings.Contains(volume.Name, model.DatabaseSecretSslCert+"-vol") { + sslVolumeExists = true + } + } + assert.False(t, sslVolumeExists) + + envVarOk := false + for _, a := range keycloakSpec.Containers[0].Env { + if a.Name == model.KeycloakDatabaseConnectionParamsProperty && strings.Contains(a.Value, "sslmode") { + envVarOk = true + } + } + assert.False(t, envVarOk) +} + func TestKeycloakReconciler_Test_Updating_External_Database_URI_From_IP_To_ExternalName(t *testing.T) { // given const ( @@ -612,7 +716,7 @@ func TestKeycloakReconciler_Test_Setting_Resources(t *testing.T) { // 12) Keycloak StatefulSets assert.Equal(t, 14, len(desiredState)) assert.IsType(t, model.PostgresqlDeployment(cr, false), desiredState[6].(common.GenericCreateAction).Ref) - assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr)), desiredState[12].(common.GenericCreateAction).Ref) + assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil), desiredState[12].(common.GenericCreateAction).Ref) keycloakContainer := desiredState[12].(common.GenericCreateAction).Ref.(*v13.StatefulSet).Spec.Template.Spec.Containers[0] assert.Equal(t, &resource700Mi, keycloakContainer.Resources.Requests.Memory(), "Keycloak Deployment: Memory-Requests should be: "+resource700Mi.String()+" but is "+keycloakContainer.Resources.Requests.Memory().String()) assert.Equal(t, &resource1900m, keycloakContainer.Resources.Requests.Cpu(), "Keycloak Deployment: Cpu-Requests should be: "+resource1900m.String()+" but is "+keycloakContainer.Resources.Requests.Cpu().String()) @@ -651,7 +755,7 @@ func TestKeycloakReconciler_Test_No_Resources_Specified(t *testing.T) { // 12) Keycloak StatefulSets assert.Equal(t, 14, len(desiredState)) assert.IsType(t, model.PostgresqlDeployment(cr, true), desiredState[6].(common.GenericCreateAction).Ref) - assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr)), desiredState[12].(common.GenericCreateAction).Ref) + assert.IsType(t, model.KeycloakDeployment(cr, model.DatabaseSecret(cr), nil), desiredState[12].(common.GenericCreateAction).Ref) keycloakContainer := desiredState[12].(common.GenericCreateAction).Ref.(*v13.StatefulSet).Spec.Template.Spec.Containers[0] assert.Equal(t, 0, len(keycloakContainer.Resources.Requests), "Requests-List should be empty") assert.Equal(t, 0, len(keycloakContainer.Resources.Limits), "Limits-List should be empty") diff --git a/pkg/model/constants.go b/pkg/model/constants.go index ed248c73d..745dea94f 100644 --- a/pkg/model/constants.go +++ b/pkg/model/constants.go @@ -31,29 +31,36 @@ const ( // Required by the Integreately Backup Image DatabaseSecretDatabaseProperty = "POSTGRES_DATABASE" // nolint // Required by the Integreately Backup Image - DatabaseSecretVersionProperty = "POSTGRES_VERSION" // nolint - DatabaseSecretExternalAddressProperty = "POSTGRES_EXTERNAL_ADDRESS" // nolint - DatabaseSecretExternalPortProperty = "POSTGRES_EXTERNAL_PORT" // nolint - KeycloakServicePort = 8443 - PostgresDefaultPort = 5432 - AdminUsernameProperty = "ADMIN_USERNAME" - AdminPasswordProperty = "ADMIN_PASSWORD" - ServingCertSecretName = "sso-x509-https-secret" // nolint - LivenessProbeProperty = "liveness_probe.sh" - ReadinessProbeProperty = "readiness_probe.sh" - RouteLoadBalancingStrategy = "source" - IngressDefaultHost = "keycloak.local" - PostgresqlBackupServiceAccountName = "keycloak-operator" - KeycloakExtensionEnvVar = "KEYCLOAK_EXTENSIONS" - KeycloakExtensionPath = "/opt/jboss/keycloak/standalone/deployments" - KeycloakExtensionsInitContainerPath = "/opt/extensions" - RhssoExtensionPath = "/opt/eap/standalone/deployments" - ClientSecretName = ApplicationName + "-client-secret" - ClientSecretClientIDProperty = "CLIENT_ID" - ClientSecretClientSecretProperty = "CLIENT_SECRET" - MaxUnavailableNumberOfPods = 1 - ServiceMonitorName = ApplicationName + "-service-monitor" - MigrateBackupName = "migrate-backup" + DatabaseSecretVersionProperty = "POSTGRES_VERSION" // nolint + DatabaseSecretExternalAddressProperty = "POSTGRES_EXTERNAL_ADDRESS" // nolint + DatabaseSecretExternalPortProperty = "POSTGRES_EXTERNAL_PORT" // nolint + KeycloakServicePort = 8443 + PostgresDefaultPort = 5432 + AdminUsernameProperty = "ADMIN_USERNAME" + AdminPasswordProperty = "ADMIN_PASSWORD" + ServingCertSecretName = "sso-x509-https-secret" // nolint + LivenessProbeProperty = "liveness_probe.sh" + ReadinessProbeProperty = "readiness_probe.sh" + RouteLoadBalancingStrategy = "source" + IngressDefaultHost = "keycloak.local" + PostgresqlBackupServiceAccountName = "keycloak-operator" + KeycloakExtensionEnvVar = "KEYCLOAK_EXTENSIONS" + KeycloakExtensionPath = "/opt/jboss/keycloak/standalone/deployments" + KeycloakExtensionsInitContainerPath = "/opt/extensions" + RhssoExtensionPath = "/opt/eap/standalone/deployments" + ClientSecretName = ApplicationName + "-client-secret" + ClientSecretClientIDProperty = "CLIENT_ID" + ClientSecretClientSecretProperty = "CLIENT_SECRET" + MaxUnavailableNumberOfPods = 1 + ServiceMonitorName = ApplicationName + "-service-monitor" + MigrateBackupName = "migrate-backup" + DatabaseSecretSslModeProperty = "SSLMODE" + DatabaseSecretSslCert = ApplicationName + "-db-ssl-cert-secret" + RhssoDatabaseXAConnectionParamsProperty = "DB_XA_CONNECTION_PROPERTY" + RhssoDatabaseNONXAConnectionParamsProperty = "DB_CONNECTION_PROPERTY" + KeycloakDatabaseConnectionParamsProperty = "JDBC_PARAMS" + KeycloakCertificatePath = "/opt/jboss/.postgresql" + RhssoCertificatePath = "/home/jboss/.postgresql" ) var PodLabels = map[string]string{} diff --git a/pkg/model/database_secret.go b/pkg/model/database_secret.go index cb224f735..6b1f54da6 100644 --- a/pkg/model/database_secret.go +++ b/pkg/model/database_secret.go @@ -23,6 +23,7 @@ func DatabaseSecret(cr *v1alpha1.Keycloak) *v1.Secret { DatabaseSecretDatabaseProperty: []byte(PostgresqlDatabase), DatabaseSecretHostProperty: []byte(PostgresqlServiceName), DatabaseSecretVersionProperty: []byte("10"), + DatabaseSecretSslModeProperty: []byte(nil), }, } } diff --git a/pkg/model/keycloak_deployment.go b/pkg/model/keycloak_deployment.go index 5fef06f3a..e803895f7 100644 --- a/pkg/model/keycloak_deployment.go +++ b/pkg/model/keycloak_deployment.go @@ -174,10 +174,35 @@ func getKeycloakEnv(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) []v1.EnvVar { env = MergeEnvs(cr.Spec.KeycloakDeploymentSpec.Experimental.Env, env) } + env = KeycloakSslEnvVariables(dbSecret, env) + return env } -func KeycloakDeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSet { +func KeycloakSslEnvVariables(dbSecret *v1.Secret, env []v1.EnvVar) []v1.EnvVar { + if dbSecret != nil { + sslMode := string(dbSecret.Data[DatabaseSecretSslModeProperty]) + + if sslMode != "" { + dbParams := "" + // is the deployment already having JDBC_PARAMS set ? + for _, element := range env { + if element.Name == KeycloakDatabaseConnectionParamsProperty { + dbParams = element.Value + "&" + break + } + } + // append env variable + env = append(env, v1.EnvVar{ + Name: KeycloakDatabaseConnectionParamsProperty, + Value: dbParams + "sslmode=" + sslMode + "&sslrootcert=" + KeycloakCertificatePath + "/root.crt", + }) + } + } + return env +} + +func KeycloakDeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret, dbSSLSecret *v1.Secret) *v13.StatefulSet { labels := map[string]string{ "app": ApplicationName, "component": KeycloakDeploymentComponent, @@ -202,7 +227,7 @@ func KeycloakDeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.Statefu }, Spec: v1.PodSpec{ InitContainers: KeycloakExtensionsInitContainers(cr), - Volumes: KeycloakVolumes(cr), + Volumes: KeycloakVolumes(cr, dbSSLSecret), Containers: []v1.Container{ { Name: KeycloakDeploymentName, @@ -221,7 +246,7 @@ func KeycloakDeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.Statefu Protocol: "TCP", }, }, - VolumeMounts: KeycloakVolumeMounts(cr, KeycloakExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, KeycloakExtensionPath, dbSSLSecret, KeycloakCertificatePath), LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getKeycloakEnv(cr, dbSecret), @@ -251,7 +276,7 @@ func KeycloakDeploymentSelector(cr *v1alpha1.Keycloak) client.ObjectKey { } } -func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.StatefulSet, dbSecret *v1.Secret) *v13.StatefulSet { +func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.StatefulSet, dbSecret *v1.Secret, dbSSLSecret *v1.Secret) *v13.StatefulSet { reconciled := currentState.DeepCopy() reconciled.ObjectMeta.Labels = AddPodLabels(cr, reconciled.ObjectMeta.Labels) @@ -259,7 +284,7 @@ func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.State reconciled.ResourceVersion = currentState.ResourceVersion reconciled.Spec.Replicas = SanitizeNumberOfReplicas(cr.Spec.Instances, false) - reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes(cr) + reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes(cr, dbSSLSecret) reconciled.Spec.Template.Spec.Containers = []v1.Container{ { Name: KeycloakDeploymentName, @@ -280,7 +305,7 @@ func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.State Protocol: "TCP", }, }, - VolumeMounts: KeycloakVolumeMounts(cr, KeycloakExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, KeycloakExtensionPath, dbSSLSecret, KeycloakCertificatePath), LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getKeycloakEnv(cr, dbSecret), @@ -295,7 +320,7 @@ func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.State return reconciled } -func KeycloakVolumeMounts(cr *v1alpha1.Keycloak, extensionsPath string) []v1.VolumeMount { +func KeycloakVolumeMounts(cr *v1alpha1.Keycloak, extensionsPath string, dbSSLSecret *v1.Secret, certificatePath string) []v1.VolumeMount { mountedVolumes := []v1.VolumeMount{ { Name: ServingCertSecretName, @@ -312,6 +337,14 @@ func KeycloakVolumeMounts(cr *v1alpha1.Keycloak, extensionsPath string) []v1.Vol }, } + if dbSSLSecret != nil { + mountedVolumes = append(mountedVolumes, v1.VolumeMount{ + Name: DatabaseSecretSslCert + "-vol", + ReadOnly: true, + MountPath: certificatePath, + }) + } + mountedVolumes = addVolumeMountsFromKeycloakCR(cr, mountedVolumes) return mountedVolumes @@ -330,7 +363,7 @@ func addVolumeMountsFromKeycloakCR(cr *v1alpha1.Keycloak, mountedVolumes []v1.Vo return mountedVolumes } -func KeycloakVolumes(cr *v1alpha1.Keycloak) []v1.Volume { +func KeycloakVolumes(cr *v1alpha1.Keycloak, dbSSLSecret *v1.Secret) []v1.Volume { volumes := []v1.Volume{ { Name: ServingCertSecretName, @@ -359,6 +392,17 @@ func KeycloakVolumes(cr *v1alpha1.Keycloak) []v1.Volume { }, }, } + if dbSSLSecret != nil { + volumes = append(volumes, v1.Volume{ + Name: DatabaseSecretSslCert + "-vol", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: DatabaseSecretSslCert, + Optional: &[]bool{false}[0], + }, + }, + }) + } volumes = addVolumesFromKeycloakCR(cr, volumes) diff --git a/pkg/model/keycloak_deployment_test.go b/pkg/model/keycloak_deployment_test.go index d38e5a852..37a030859 100644 --- a/pkg/model/keycloak_deployment_test.go +++ b/pkg/model/keycloak_deployment_test.go @@ -11,7 +11,7 @@ import ( v12 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type createDeploymentStatefulSet func(*v1alpha1.Keycloak, *v1.Secret) *v13.StatefulSet +type createDeploymentStatefulSet func(*v1alpha1.Keycloak, *v1.Secret, *v1.Secret) *v13.StatefulSet func TestKeycloakDeployment_testExperimentalEnvs(t *testing.T) { testExperimentalEnvs(t, KeycloakDeployment) @@ -82,7 +82,7 @@ func testExperimentalEnvs(t *testing.T, deploymentFunction createDeploymentState } //when - envs := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Env + envs := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.Containers[0].Env //then hasTestNameKey := false @@ -117,7 +117,7 @@ func testExperimentalArgs(t *testing.T, deploymentFunction createDeploymentState } //when - args := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Args + args := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.Containers[0].Args //then assert.Equal(t, []string{"test"}, args) @@ -137,7 +137,7 @@ func testExperimentalCommand(t *testing.T, deploymentFunction createDeploymentSt } //when - command := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Command + command := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.Containers[0].Command //then assert.Equal(t, []string{"test"}, command) @@ -170,7 +170,7 @@ func testExperimentalVolumesWithConfigMaps(t *testing.T, deploymentFunction crea } //when - template := deploymentFunction(cr, dbSecret).Spec.Template.Spec + template := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec volumeMount := template.Containers[0].VolumeMounts[3] volume := template.Volumes[3] @@ -213,7 +213,7 @@ func testExperimentalVolumesWithSecrets(t *testing.T, deploymentFunction createD } //when - template := deploymentFunction(cr, dbSecret).Spec.Template.Spec + template := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec volumeMount := template.Containers[0].VolumeMounts[3] volume := template.Volumes[3] @@ -256,7 +256,7 @@ func testExperimentalVolumesWithConfigMapsAndSecrets(t *testing.T, deploymentFun } //when - template := deploymentFunction(cr, dbSecret).Spec.Template.Spec + template := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec volumeMount := template.Containers[0].VolumeMounts[3] volume := template.Volumes[3] @@ -283,7 +283,7 @@ func testPostgresEnvs(t *testing.T, deploymentFunction createDeploymentStatefulS cr := &v1alpha1.Keycloak{} //when - envs := deploymentFunction(cr, nil).Spec.Template.Spec.Containers[0].Env + envs := deploymentFunction(cr, nil, nil).Spec.Template.Spec.Containers[0].Env //then assert.Equal(t, getEnvValueByName(envs, "DB_VENDOR"), "POSTGRES") @@ -310,7 +310,7 @@ func testPostgresEnvs(t *testing.T, deploymentFunction createDeploymentStatefulS DatabaseSecretExternalPortProperty: []byte("12345"), }, } - envs = deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Env + envs = deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.Containers[0].Env //then assert.Equal(t, "POSTGRES", getEnvValueByName(envs, "DB_VENDOR")) @@ -338,7 +338,7 @@ func testAffinityDefaultMultiAZ(t *testing.T, deploymentFunction createDeploymen cr.Spec.MultiAvailablityZones.Enabled = true //when - affinity := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Affinity + affinity := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.Affinity weight0 := affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Weight matchExprKey0 := affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.LabelSelector.MatchExpressions[0].Key @@ -417,7 +417,7 @@ func testAffinityExperimentalAffinitySet(t *testing.T, deploymentFunction create } //when - affinity := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Affinity + affinity := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.Affinity weight0 := affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Weight matchExprKey0 := affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.LabelSelector.MatchExpressions[0].Key @@ -454,7 +454,7 @@ func testServiceAccountSet(t *testing.T, deploymentFunction createDeploymentStat cr.Spec.KeycloakDeploymentSpec.Experimental.ServiceAccountName = "test" //when - serviceAccountName := deploymentFunction(cr, dbSecret).Spec.Template.Spec.ServiceAccountName + serviceAccountName := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.ServiceAccountName assert.Equal(t, "test", serviceAccountName) } @@ -467,7 +467,7 @@ func testServiceAccountDefault(t *testing.T, deploymentFunction createDeployment //If serviceAccountName is not set in the cr, then the serviceAccountName should be default //when - serviceAccountName := deploymentFunction(cr, dbSecret).Spec.Template.Spec.ServiceAccountName + serviceAccountName := deploymentFunction(cr, dbSecret, nil).Spec.Template.Spec.ServiceAccountName assert.Equal(t, "default", serviceAccountName) } diff --git a/pkg/model/rhsso_deployment.go b/pkg/model/rhsso_deployment.go index 7982edfd2..a96f067da 100644 --- a/pkg/model/rhsso_deployment.go +++ b/pkg/model/rhsso_deployment.go @@ -121,10 +121,33 @@ func getRHSSOEnv(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) []v1.EnvVar { env = MergeEnvs(cr.Spec.KeycloakDeploymentSpec.Experimental.Env, env) } + env = RHSSOSslEnvVariables(dbSecret, env) + return env } -func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSet { +func RHSSOSslEnvVariables(dbSecret *v1.Secret, env []v1.EnvVar) []v1.EnvVar { + if dbSecret != nil { + sslMode := string(dbSecret.Data[DatabaseSecretSslModeProperty]) + + if sslMode != "" { + // append env variable + env = append(env, + v1.EnvVar{ + Name: RhssoDatabaseXAConnectionParamsProperty + "_sslMode", + Value: sslMode, + }, + v1.EnvVar{ + Name: RhssoDatabaseNONXAConnectionParamsProperty + "_sslmode", + Value: sslMode, + }, + ) + } + } + return env +} + +func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret, dbSSLSecret *v1.Secret) *v13.StatefulSet { labels := map[string]string{ "app": ApplicationName, "component": KeycloakDeploymentComponent, @@ -148,7 +171,7 @@ func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSe Labels: podLabels, }, Spec: v1.PodSpec{ - Volumes: KeycloakVolumes(cr), + Volumes: KeycloakVolumes(cr, dbSSLSecret), InitContainers: KeycloakExtensionsInitContainers(cr), Affinity: KeycloakPodAffinity(cr), Containers: []v1.Container{ @@ -178,7 +201,7 @@ func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSe Env: getRHSSOEnv(cr, dbSecret), Args: cr.Spec.KeycloakDeploymentSpec.Experimental.Args, Command: cr.Spec.KeycloakDeploymentSpec.Experimental.Command, - VolumeMounts: KeycloakVolumeMounts(cr, RhssoExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, RhssoExtensionPath, dbSSLSecret, RhssoCertificatePath), Resources: getResources(cr), ImagePullPolicy: "Always", }, @@ -193,6 +216,7 @@ func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSe } else if cr.Spec.MultiAvailablityZones.Enabled { rhssoStatefulSet.Spec.Template.Spec.Affinity = KeycloakPodAffinity(cr) } + return rhssoStatefulSet } @@ -203,7 +227,7 @@ func RHSSODeploymentSelector(cr *v1alpha1.Keycloak) client.ObjectKey { } } -func RHSSODeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.StatefulSet, dbSecret *v1.Secret) *v13.StatefulSet { +func RHSSODeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.StatefulSet, dbSecret *v1.Secret, dbSSLSecret *v1.Secret) *v13.StatefulSet { reconciled := currentState.DeepCopy() reconciled.ObjectMeta.Labels = AddPodLabels(cr, reconciled.ObjectMeta.Labels) @@ -211,7 +235,7 @@ func RHSSODeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.Stateful reconciled.ResourceVersion = currentState.ResourceVersion reconciled.Spec.Replicas = SanitizeNumberOfReplicas(cr.Spec.Instances, false) - reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes(cr) + reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes(cr, dbSSLSecret) reconciled.Spec.Template.Spec.Containers = []v1.Container{ { Name: KeycloakDeploymentName, @@ -236,7 +260,7 @@ func RHSSODeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.Stateful Protocol: "TCP", }, }, - VolumeMounts: KeycloakVolumeMounts(cr, RhssoExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, RhssoExtensionPath, dbSSLSecret, RhssoCertificatePath), LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getRHSSOEnv(cr, dbSecret), diff --git a/test/e2e/constants.go b/test/e2e/constants.go index 79f76a56a..74dde7294 100644 --- a/test/e2e/constants.go +++ b/test/e2e/constants.go @@ -17,4 +17,5 @@ const ( cleanupTimeout = time.Minute * 2 pollRetryInterval = time.Second * 10 pollTimeout = time.Minute * 10 + externalPostgresClaim = "external-postgres-claim" ) diff --git a/test/e2e/keycloak_main_test.go b/test/e2e/keycloak_main_test.go index 41e97b354..530663db8 100644 --- a/test/e2e/keycloak_main_test.go +++ b/test/e2e/keycloak_main_test.go @@ -30,7 +30,6 @@ func TestKeycloakCRDS(t *testing.T) { if err != nil { t.Fatalf("failed to add custom resource scheme to framework: %v", err) } - t.Run("KeycloaksCRDTest", func(t *testing.T) { runTestsFromCRDInterface(t, NewKeycloaksCRDTestStruct()) runTestsFromCRDInterface(t, NewUnmanagedKeycloaksCRDTestStruct()) @@ -48,6 +47,9 @@ func TestKeycloakCRDS(t *testing.T) { t.Run("KeycloakClientsCRDTest", func(t *testing.T) { runTestsFromCRDInterface(t, NewKeycloakClientsCRDTestStruct()) }) + t.Run("KeycloaksSSLTest", func(t *testing.T) { + runTestsFromCRDInterface(t, NewKeycloaksSSLTestStruct()) + }) } func runTestsFromCRDInterface(t *testing.T, crd *CRDTestStruct) { diff --git a/test/e2e/keycloaks_test.go b/test/e2e/keycloaks_test.go index 0740266d9..b6f01d031 100644 --- a/test/e2e/keycloaks_test.go +++ b/test/e2e/keycloaks_test.go @@ -3,7 +3,9 @@ package e2e import ( "context" "crypto/tls" + "io/ioutil" "net/http" + "strings" "testing" "k8s.io/client-go/kubernetes" @@ -17,6 +19,7 @@ import ( keycloakv1alpha1 "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1" "github.com/keycloak/keycloak-operator/pkg/model" framework "github.com/operator-framework/operator-sdk/pkg/test" + v1apps "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -53,6 +56,201 @@ func NewUnmanagedKeycloaksCRDTestStruct() *CRDTestStruct { } } +func NewKeycloaksSSLTestStruct() *CRDTestStruct { + return &CRDTestStruct{ + prepareEnvironmentSteps: []environmentInitializationStep{ + prepareKeycloaksSSLWithDB, + }, + testSteps: map[string]deployedOperatorTestStep{ + "keycloakSSLDBTest": {testFunction: keycloakSSLDBTest}, + }, + } +} + +func keycloakSSLDBTest(t *testing.T, f *framework.Framework, ctx *framework.Context, namespace string) error { + // get the Keycloak Statefulset + keycloakStatefulset := v1apps.StatefulSet{} + err := GetNamespacedObject(f, namespace, model.KeycloakDeploymentName, &keycloakStatefulset) + if err != nil { + return err + } + + // check pod has the env var JDBC_PARAMS + envExists := false + for _, envvar := range keycloakStatefulset.Spec.Template.Spec.Containers[0].Env { + if envvar.Name == "JDBC_PARAMS" && strings.Contains(envvar.Value, "sslmode") { + envExists = true + break + } + } + if !envExists { + return errors.Errorf("test Failed : Env var JDBC_PARAMS and value sslMode not found") + } + + // check the volume to the crt exists too + volumeExists := false + for _, vol := range keycloakStatefulset.Spec.Template.Spec.Volumes { + if vol.Name == model.DatabaseSecretSslCert+"-vol" { + volumeExists = true + break + } + } + if !volumeExists { + return errors.Errorf("test Failed : Volume to the secret not found") + } + return err +} + +func prepareKeycloaksSSLWithDB(t *testing.T, f *framework.Framework, ctx *framework.Context, namespace string) error { + cr := getKeycloakCR(namespace) + + // create secret with crt + secretWithSSLCertForPostgres := getSecretWithSSLCertForPostgres(namespace) + err := f.Client.Create(context.TODO(), secretWithSSLCertForPostgres, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + return err + } + + // get secret with Postgres parameters + secret := model.DatabaseSecret(cr) + + // deploy PostgreSQL + postgresvc, err := deployPostgreSQLWithSSLon(secretWithSSLCertForPostgres, cr, secret, f, ctx) + if err != nil { + return err + } + + // deploying the Keycloak CR + cr.Spec.ExternalDatabase.Enabled = true + secret.Data["SSLMODE"] = []byte("verify-ca") + secret.Data["POSTGRES_EXTERNAL_ADDRESS"] = []byte(postgresvc.Name + "." + namespace + ".svc.cluster.local") + + err = f.Client.Create(context.TODO(), secret, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + return err + } + + err = deployKeycloaksCR(t, f, ctx, namespace, cr) + + return err +} + +func deployPostgreSQLWithSSLon(secretWithSSLCertForPostgres *v1.Secret, cr *keycloakv1alpha1.Keycloak, secret *v1.Secret, f *framework.Framework, ctx *framework.Context) (*v1.Service, error) { + // create postgre deployment + // Create config map with config for Postgresql to start with SSL + postgresqlConfFile, _ := ioutil.ReadFile("testdata/postgresql.conf") + + postgreSQLConfig := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "postgre-sql-config", + Namespace: cr.Namespace, + Labels: CreateLabel(cr.Namespace), + }, + Data: map[string]string{ + "custom.conf": string(postgresqlConfFile), + }, + } + err := f.Client.Create(context.TODO(), &postgreSQLConfig, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + return nil, err + } + // add a volume+volumemount in postgresql to the secret with the crt + modeCrt := int32(0444) + modeKey := int32(0440) + volume := v1.Volume{ + Name: model.DatabaseSecretSslCert + "-vol", + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: secretWithSSLCertForPostgres.Name, + }, + Items: []v1.KeyToPath{ + {Key: "server.crt", Path: "server.crt", Mode: &modeCrt}, + {Key: "server.key", Path: "server.key", Mode: &modeKey}, + }, + }, + }, + }, + }, + }, + } + volumeConfig := v1.Volume{ + Name: "postgre-sql-config-vol", + VolumeSource: v1.VolumeSource{ + ConfigMap: &v1.ConfigMapVolumeSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "postgre-sql-config", + }, + }, + }, + } + volumeMount := v1.VolumeMount{ + Name: model.DatabaseSecretSslCert + "-vol", + MountPath: "/opt/app-root/src/certificates/", + } + volumeMountConfig := v1.VolumeMount{ + Name: "postgre-sql-config-vol", + MountPath: "/opt/app-root/src/postgresql-cfg/", + } + + pvc := model.PostgresqlPersistentVolumeClaim(cr) + // changing the name as apparently another one is created by the operator + pvc.Name = externalPostgresClaim + err = f.Client.Create(context.TODO(), pvc, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + return nil, err + } + postgresql := model.PostgresqlDeployment(cr, false) + //postgresql.Spec.Template.Spec.Containers[0].Image = "postgres:10.5-alpine" + postgresql.Spec.Template.Spec.Volumes = append(postgresql.Spec.Template.Spec.Volumes, volume, volumeConfig) + postgresql.Spec.Template.Spec.Containers[0].VolumeMounts = append(postgresql.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount, volumeMountConfig) + runasuser := int64(26) + fsgroup := int64(26) + postgresql.Spec.Template.Spec.SecurityContext = &v1.PodSecurityContext{ + RunAsUser: &runasuser, + FSGroup: &fsgroup, + SupplementalGroups: []int64{999, 1000}, + } + for _, vol := range postgresql.Spec.Template.Spec.Volumes { + if vol.PersistentVolumeClaim != nil && vol.PersistentVolumeClaim.ClaimName == "keycloak-postgresql-claim" { + vol.PersistentVolumeClaim.ClaimName = externalPostgresClaim + } + } + + err = f.Client.Create(context.TODO(), postgresql, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + return nil, err + } + + postgresvc := model.PostgresqlService(cr, secret, false) + postgresvc.Name = "keycloak-postgresql-svc" + err = f.Client.Create(context.TODO(), postgresvc, &framework.CleanupOptions{TestContext: ctx, Timeout: cleanupTimeout, RetryInterval: cleanupRetryInterval}) + if err != nil { + return nil, err + } + return postgresvc, nil +} + +func getSecretWithSSLCertForPostgres(namespace string) *v1.Secret { + serverCrt, _ := ioutil.ReadFile("testdata/server.crt") + serverKey, _ := ioutil.ReadFile("testdata/server.key") + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: model.DatabaseSecretSslCert, + Namespace: namespace, + Labels: CreateLabel(namespace), + }, + Data: map[string][]byte{ + "server.crt": serverCrt, + "server.key": serverKey, + "root.crt": serverCrt, + }, + } +} + func getKeycloakCR(namespace string) *keycloakv1alpha1.Keycloak { return &keycloakv1alpha1.Keycloak{ ObjectMeta: metav1.ObjectMeta{ diff --git a/testdata/postgresql.conf b/testdata/postgresql.conf new file mode 100644 index 000000000..368376674 --- /dev/null +++ b/testdata/postgresql.conf @@ -0,0 +1,3 @@ +ssl = on +ssl_cert_file = '/opt/app-root/src/certificates/server.crt' +ssl_key_file = '/opt/app-root/src/certificates/server.key' diff --git a/testdata/server.crt b/testdata/server.crt new file mode 100644 index 000000000..543ea93de --- /dev/null +++ b/testdata/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZDCCAc2gAwIBAgIJALbul005SMJvMA0GCSqGSIb3DQEBCwUAMEsxCzAJBgNV +BAYTAkNBMRAwDgYDVQQHDAdUb3JvbnRvMRIwEAYDVQQKDAlTeXNnZW5pdXMxFjAU +BgNVBAMMDXN5c2dlbml1cy5jb20wHhcNMTgwOTIyMTU0OTU0WhcNMjgwOTE5MTU0 +OTU0WjBLMQswCQYDVQQGEwJDQTEQMA4GA1UEBwwHVG9yb250bzESMBAGA1UECgwJ +U3lzZ2VuaXVzMRYwFAYDVQQDDA1zeXNnZW5pdXMuY29tMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDhLS5wof+IaZNWqo8HnBFZRrhztkYlMTg/nqJJdeSXNQ8H +ATFOqKSi5+eBqSbwSkB5uG3TdbCxqCANPr3YqRI/uSS0UjLDVHkV4c8rwCMd5TGe +sCTfu7/IxRfZ1J+v3gntJiwL8UHnHnYDWKY3MCBfK869KmQ/OQWR+4yH294E1QID +AQABo1AwTjAdBgNVHQ4EFgQUCmrfb5hADVuNCeetckwzdDGvliAwHwYDVR0jBBgw +FoAUCmrfb5hADVuNCeetckwzdDGvliAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQA3dAfg2UpDr/d6IEv7n3NiGyBBXWIEVHH6XngmQTQC5OI1hDaRza/0 +QYgvuY7htr+W6gn5T5jgvUSmfnMn+LIbkulPAlhTnY82+qvLg9QD6e0na9+hkE1s +P7cPutZpcI+sOSQT0UvtNmw9JnbYem0url/oi4fwUAkLUAK4TOb3Iw== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/testdata/server.key b/testdata/server.key new file mode 100644 index 000000000..f55222694 --- /dev/null +++ b/testdata/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDhLS5wof+IaZNWqo8HnBFZRrhztkYlMTg/nqJJdeSXNQ8HATFO +qKSi5+eBqSbwSkB5uG3TdbCxqCANPr3YqRI/uSS0UjLDVHkV4c8rwCMd5TGesCTf +u7/IxRfZ1J+v3gntJiwL8UHnHnYDWKY3MCBfK869KmQ/OQWR+4yH294E1QIDAQAB +AoGAZYwNnGfCKE+MTZI/TGw6eqtk9XHm8lDfD/nHMB/Fhdc8vqjwBowRKxIwGg4h +bE3gA8SDatBxbbznL3HzBalBe7idUMfPmJTyOPGqXnpN132QZk9AxxDYU1a8TyTi +olVYOiMYL37TBvTYpXrLbEYQUQNbQRIcfG03o3hQGlD1+sECQQD5D3zoD7I+gg5j +37q8aKRMZVoXvD+zHWzfA2xPQmBe3YZJVsUrppOiQr0aBojszZlSVMv7baeKRLls +FCpr+CAFAkEA53NVFImi9qMJXSXLCicYkE03rv96AMHaDIYkJR/92/0WN+RaAETJ +rT83WeRZ26dR90VKueTba6z2C7FoAQP6kQJBAMdykbI8r57DA8cTKTUsDzZmi4kq +ZVCIpeDJmvi1zPrrJ1iftfHIyb1M+KZYyVnpI3j9EwTbwSlG874LQBWKdAkCQDy6 +ulxFMQleBhr2y6vCTmOunym/xxdjxf77Q4iqOEX8xlOsCyF+6xwWNkPT8u/7mzoy +aEUWk6xRGc4/onaBwMECQQC4HpKakvWpCqb1euX0pFi7wMbLx1VfiusTb1xplEof +fFMpI1/9t7Qy5xULz4X56OdSrvdMGRAT8GgeRm+SIm3Y +-----END RSA PRIVATE KEY-----