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  }