github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/sso_test.go (about) 1 // Copyright 2021 ArgoCD Operator Developers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package argocd 16 17 import ( 18 "context" 19 "errors" 20 "testing" 21 22 oappsv1 "github.com/openshift/api/apps/v1" 23 routev1 "github.com/openshift/api/route/v1" 24 templatev1 "github.com/openshift/api/template/v1" 25 "github.com/stretchr/testify/assert" 26 k8sappsv1 "k8s.io/api/apps/v1" 27 corev1 "k8s.io/api/core/v1" 28 networkingv1 "k8s.io/api/networking/v1" 29 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 logf "sigs.k8s.io/controller-runtime/pkg/log" 34 35 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 36 ) 37 38 func TestReconcile_testKeycloakTemplateInstance(t *testing.T) { 39 logf.SetLogger(ZapLogger(true)) 40 a := makeTestArgoCDForKeycloak() 41 42 templateAPIFound = true 43 44 resObjs := []client.Object{a} 45 subresObjs := []client.Object{a} 46 runtimeObjs := []runtime.Object{} 47 sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install) 48 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 49 r := makeTestReconciler(cl, sch) 50 51 assert.NoError(t, createNamespace(r, a.Namespace, "")) 52 53 assert.NoError(t, r.reconcileSSO(a)) 54 55 templateInstance := &templatev1.TemplateInstance{} 56 assert.NoError(t, r.Client.Get( 57 context.TODO(), 58 types.NamespacedName{ 59 Name: "rhsso", 60 Namespace: a.Namespace, 61 }, 62 templateInstance)) 63 } 64 65 func TestReconcile_noTemplateInstance(t *testing.T) { 66 logf.SetLogger(ZapLogger(true)) 67 a := makeTestArgoCDForKeycloak() 68 69 resObjs := []client.Object{a} 70 subresObjs := []client.Object{a} 71 runtimeObjs := []runtime.Object{} 72 sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install) 73 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 74 r := makeTestReconciler(cl, sch) 75 76 assert.NoError(t, createNamespace(r, a.Namespace, "")) 77 78 assert.NoError(t, r.reconcileSSO(a)) 79 } 80 81 func TestReconcile_illegalSSOConfiguration(t *testing.T) { 82 logf.SetLogger(ZapLogger(true)) 83 84 tests := []struct { 85 name string 86 argoCD *argoproj.ArgoCD 87 wantErr bool 88 Err error 89 wantSSOConfigLegalStatus string 90 }{ 91 { 92 name: "no conflicts - no sso configured", 93 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) {}), 94 wantErr: false, 95 wantSSOConfigLegalStatus: "Unknown", 96 }, 97 { 98 name: "no conflict - case insensitive sso provider value", 99 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 100 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 101 Provider: "DEX", 102 Dex: &argoproj.ArgoCDDexSpec{ 103 Config: "test-config", 104 }, 105 } 106 }), 107 wantErr: false, 108 wantSSOConfigLegalStatus: "Success", 109 }, 110 { 111 name: "no conflict - valid dex sso configurations", 112 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 113 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 114 Provider: "dex", 115 Dex: &argoproj.ArgoCDDexSpec{ 116 Config: "test-config", 117 OpenShiftOAuth: false, 118 }, 119 } 120 }), 121 wantErr: false, 122 wantSSOConfigLegalStatus: "Success", 123 }, 124 { 125 name: "no conflict - valid keycloak sso configurations", 126 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 127 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 128 Provider: "keycloak", 129 } 130 }), 131 wantErr: false, 132 wantSSOConfigLegalStatus: "Success", 133 }, 134 { 135 name: "sso provider dex but no .spec.sso.dex provided", 136 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 137 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 138 Provider: argoproj.SSOProviderTypeDex, 139 } 140 }), 141 wantErr: true, 142 Err: errors.New("illegal SSO configuration: must supply valid dex configuration when requested SSO provider is dex"), 143 wantSSOConfigLegalStatus: "Failed", 144 }, 145 { 146 name: "sso provider dex + `.spec.sso.keycloak`", 147 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 148 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 149 Keycloak: &argoproj.ArgoCDKeycloakSpec{ 150 Image: "test-image", 151 Version: "test-image-version", 152 }, 153 Provider: argoproj.SSOProviderTypeDex, 154 Dex: &argoproj.ArgoCDDexSpec{ 155 Config: "test", 156 }, 157 } 158 }), 159 wantErr: true, 160 Err: errors.New("illegal SSO configuration: cannot supply keycloak configuration in .spec.sso.keycloak when requested SSO provider is dex"), 161 wantSSOConfigLegalStatus: "Failed", 162 }, 163 { 164 name: "sso provider keycloak + `.spec.sso.dex`", 165 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 166 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 167 Provider: argoproj.SSOProviderTypeKeycloak, 168 Dex: &argoproj.ArgoCDDexSpec{ 169 Config: "test-config", 170 OpenShiftOAuth: true, 171 }, 172 } 173 }), 174 wantErr: true, 175 Err: errors.New("illegal SSO configuration: cannot supply dex configuration when requested SSO provider is keycloak"), 176 wantSSOConfigLegalStatus: "Failed", 177 }, 178 { 179 name: "sso provider missing but sso.dex/keycloak supplied", 180 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 181 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 182 Dex: &argoproj.ArgoCDDexSpec{ 183 Config: "test-config", 184 OpenShiftOAuth: true, 185 }, 186 Keycloak: &argoproj.ArgoCDKeycloakSpec{ 187 Image: "test-image", 188 }, 189 } 190 }), 191 wantErr: true, 192 Err: errors.New("illegal SSO configuration: Cannot specify SSO provider spec without specifying SSO provider type"), 193 wantSSOConfigLegalStatus: "Failed", 194 }, 195 { 196 name: "unsupported sso provider but sso.dex/keycloak supplied", 197 argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) { 198 ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 199 Provider: "Unsupported", 200 Dex: &argoproj.ArgoCDDexSpec{ 201 Config: "test-config", 202 OpenShiftOAuth: true, 203 }, 204 } 205 }), 206 wantErr: true, 207 Err: errors.New("illegal SSO configuration: Unsupported SSO provider type. Supported providers are dex and keycloak"), 208 wantSSOConfigLegalStatus: "Failed", 209 }, 210 } 211 212 for _, test := range tests { 213 t.Run(test.name, func(t *testing.T) { 214 215 resObjs := []client.Object{test.argoCD} 216 subresObjs := []client.Object{test.argoCD} 217 runtimeObjs := []runtime.Object{} 218 sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install) 219 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 220 r := makeTestReconciler(cl, sch) 221 222 assert.NoError(t, createNamespace(r, test.argoCD.Namespace, "")) 223 224 err := r.reconcileSSO(test.argoCD) 225 assert.Equal(t, test.wantSSOConfigLegalStatus, ssoConfigLegalStatus) 226 if err != nil { 227 if !test.wantErr { 228 // ignore unexpected errors for legal sso configurations. 229 // keycloak reconciliation code expects a live cluster & 230 // therefore throws unexpected errors during unit testing 231 if ssoConfigLegalStatus != ssoLegalSuccess { 232 t.Errorf("Got unexpected error") 233 } 234 } else { 235 assert.Equal(t, test.Err, err) 236 } 237 } else { 238 if test.wantErr { 239 t.Errorf("expected error but didn't get one") 240 } 241 } 242 }) 243 } 244 245 } 246 247 func TestReconcile_testKeycloakK8sInstance(t *testing.T) { 248 logf.SetLogger(ZapLogger(true)) 249 a := makeTestArgoCDForKeycloak() 250 251 // Cluster does not have a template instance 252 templateAPIFound = false 253 254 resObjs := []client.Object{a} 255 subresObjs := []client.Object{a} 256 runtimeObjs := []runtime.Object{} 257 sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install) 258 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 259 r := makeTestReconciler(cl, sch) 260 261 assert.NoError(t, createNamespace(r, a.Namespace, "")) 262 263 assert.NoError(t, r.reconcileSSO(a)) 264 } 265 266 func TestReconcile_testKeycloakInstanceResources(t *testing.T) { 267 logf.SetLogger(ZapLogger(true)) 268 a := makeTestArgoCDForKeycloak() 269 270 // Cluster does not have a template instance 271 templateAPIFound = false 272 273 resObjs := []client.Object{a} 274 subresObjs := []client.Object{a} 275 runtimeObjs := []runtime.Object{} 276 sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install) 277 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 278 r := makeTestReconciler(cl, sch) 279 280 assert.NoError(t, createNamespace(r, a.Namespace, "")) 281 282 assert.NoError(t, r.reconcileSSO(a)) 283 284 // Keycloak Deployment 285 deployment := &k8sappsv1.Deployment{} 286 err := r.Client.Get(context.TODO(), types.NamespacedName{Name: defaultKeycloakIdentifier, Namespace: a.Namespace}, deployment) 287 assert.NoError(t, err) 288 289 assert.Equal(t, deployment.Name, defaultKeycloakIdentifier) 290 assert.Equal(t, deployment.Namespace, a.Namespace) 291 292 testLabels := map[string]string{ 293 "app": defaultKeycloakIdentifier, 294 } 295 assert.Equal(t, deployment.Labels, testLabels) 296 297 testSelector := &v1.LabelSelector{ 298 MatchLabels: map[string]string{ 299 "app": defaultKeycloakIdentifier, 300 }, 301 } 302 assert.Equal(t, deployment.Spec.Selector, testSelector) 303 304 assert.Equal(t, deployment.Spec.Template.ObjectMeta.Labels, testLabels) 305 assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Name, 306 defaultKeycloakIdentifier) 307 assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Image, 308 getKeycloakContainerImage(a)) 309 310 testEnv := []corev1.EnvVar{ 311 {Name: "KEYCLOAK_USER", Value: defaultKeycloakAdminUser}, 312 {Name: "KEYCLOAK_PASSWORD", Value: defaultKeycloakAdminPassword}, 313 {Name: "PROXY_ADDRESS_FORWARDING", Value: "true"}, 314 } 315 assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Env, 316 testEnv) 317 318 // Keycloak Service 319 svc := &corev1.Service{} 320 err = r.Client.Get(context.TODO(), types.NamespacedName{Name: defaultKeycloakIdentifier, Namespace: a.Namespace}, svc) 321 assert.NoError(t, err) 322 323 assert.Equal(t, svc.Name, defaultKeycloakIdentifier) 324 assert.Equal(t, svc.Namespace, a.Namespace) 325 assert.Equal(t, svc.Labels, testLabels) 326 327 assert.Equal(t, svc.Spec.Selector, testLabels) 328 assert.Equal(t, svc.Spec.Type, corev1.ServiceType("LoadBalancer")) 329 330 // Keycloak Ingress 331 ing := &networkingv1.Ingress{} 332 testPathType := networkingv1.PathTypeImplementationSpecific 333 err = r.Client.Get(context.TODO(), types.NamespacedName{Name: defaultKeycloakIdentifier, Namespace: a.Namespace}, ing) 334 assert.NoError(t, err) 335 336 assert.Equal(t, ing.Name, defaultKeycloakIdentifier) 337 assert.Equal(t, ing.Namespace, a.Namespace) 338 339 testTLS := []networkingv1.IngressTLS{ 340 { 341 Hosts: []string{keycloakIngressHost}, 342 }, 343 } 344 assert.Equal(t, ing.Spec.TLS, testTLS) 345 346 testRules := []networkingv1.IngressRule{ 347 { 348 Host: keycloakIngressHost, 349 IngressRuleValue: networkingv1.IngressRuleValue{ 350 HTTP: &networkingv1.HTTPIngressRuleValue{ 351 Paths: []networkingv1.HTTPIngressPath{ 352 { 353 Path: "/", 354 Backend: networkingv1.IngressBackend{ 355 Service: &networkingv1.IngressServiceBackend{ 356 Name: defaultKeycloakIdentifier, 357 Port: networkingv1.ServiceBackendPort{ 358 Name: "http", 359 }, 360 }, 361 }, 362 PathType: &testPathType, 363 }, 364 }, 365 }, 366 }, 367 }, 368 } 369 370 assert.Equal(t, ing.Spec.Rules, testRules) 371 }