github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/sso.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 "errors" 19 "fmt" 20 21 template "github.com/openshift/api/template/v1" 22 apiErrors "k8s.io/apimachinery/pkg/api/errors" 23 24 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 25 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 26 ) 27 28 const ( 29 ssoLegalUnknown string = "Unknown" 30 ssoLegalSuccess string = "Success" 31 ssoLegalFailed string = "Failed" 32 illegalSSOConfiguration string = "illegal SSO configuration: " 33 ) 34 35 var ( 36 templateAPIFound = false 37 ssoConfigLegalStatus string 38 ) 39 40 // IsTemplateAPIAvailable returns true if the template API is present. 41 func IsTemplateAPIAvailable() bool { 42 return templateAPIFound 43 } 44 45 // verifyTemplateAPI will verify that the template API is present. 46 func verifyTemplateAPI() error { 47 found, err := argoutil.VerifyAPI(template.GroupVersion.Group, template.GroupVersion.Version) 48 if err != nil { 49 return err 50 } 51 templateAPIFound = found 52 return nil 53 } 54 55 // The purpose of reconcileSSO is to try and catch as many illegal configuration edge cases at the highest level (that can lead to conflicts) 56 // as possible, that may arise from the operator supporting multiple SSO providers. 57 // The operator must support `.spec.sso.dex` fields for dex, and `.spec.sso.keycloak` fields for keycloak. 58 // The operator must identify edge cases involving partial configurations of specs, spec mismatch with 59 // active provider, contradicting configuration etc, and throw the appropriate errors. 60 func (r *ReconcileArgoCD) reconcileSSO(cr *argoproj.ArgoCD) error { 61 62 // reset ssoConfigLegalStatus at the beginning of each SSO reconciliation round 63 ssoConfigLegalStatus = ssoLegalUnknown 64 65 // case 1 66 if cr.Spec.SSO == nil { 67 // no SSO configured, nothing to do here 68 return nil 69 } 70 71 if cr.Spec.SSO != nil { 72 73 errMsg := "" 74 var err error 75 isError := false 76 77 // case 2 78 if cr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeDex { 79 // Relevant SSO settings at play are `.spec.sso.dex` fields, `.spec.sso.keycloak` 80 81 if cr.Spec.SSO.Dex == nil || (cr.Spec.SSO.Dex != nil && !cr.Spec.SSO.Dex.OpenShiftOAuth && cr.Spec.SSO.Dex.Config == "") { 82 // sso provider specified as dex but no dexconfig supplied. This will cause health probe to fail as per 83 // https://github.com/argoproj-labs/argocd-operator/pull/615 ==> conflict 84 errMsg = "must supply valid dex configuration when requested SSO provider is dex" 85 isError = true 86 } else if cr.Spec.SSO.Keycloak != nil { 87 // new keycloak spec fields are expressed when `.spec.sso.provider` is set to dex ==> conflict 88 errMsg = "cannot supply keycloak configuration in .spec.sso.keycloak when requested SSO provider is dex" 89 isError = true 90 } 91 92 if isError { 93 err = errors.New(illegalSSOConfiguration + errMsg) 94 log.Error(err, fmt.Sprintf("Illegal expression of SSO configuration detected for Argo CD %s in namespace %s. %s", cr.Name, cr.Namespace, errMsg)) 95 ssoConfigLegalStatus = ssoLegalFailed // set global indicator that SSO config has gone wrong 96 _ = r.reconcileStatusSSO(cr) 97 return err 98 } 99 } 100 101 // case 3 102 if cr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeKeycloak { 103 // Relevant SSO settings at play are `.spec.sso.keycloak` fields, `.spec.sso.dex` 104 105 if cr.Spec.SSO.Dex != nil { 106 // new dex spec fields are expressed when `.spec.sso.provider` is set to keycloak ==> conflict 107 errMsg = "cannot supply dex configuration when requested SSO provider is keycloak" 108 err = errors.New(illegalSSOConfiguration + errMsg) 109 isError = true 110 } 111 112 if isError { 113 log.Error(err, fmt.Sprintf("Illegal expression of SSO configuration detected for Argo CD %s in namespace %s. %s", cr.Name, cr.Namespace, errMsg)) 114 ssoConfigLegalStatus = ssoLegalFailed // set global indicator that SSO config has gone wrong 115 _ = r.reconcileStatusSSO(cr) 116 return err 117 } 118 } 119 120 // case 4 121 if cr.Spec.SSO.Provider.ToLower() == "" { 122 123 if cr.Spec.SSO.Dex != nil || 124 // `.spec.sso.dex` expressed without specifying SSO provider ==> conflict 125 cr.Spec.SSO.Keycloak != nil { 126 // `.spec.sso.keycloak` expressed without specifying SSO provider ==> conflict 127 128 errMsg = "Cannot specify SSO provider spec without specifying SSO provider type" 129 err = errors.New(illegalSSOConfiguration + errMsg) 130 log.Error(err, fmt.Sprintf("Cannot specify SSO provider spec without specifying SSO provider type for Argo CD %s in namespace %s.", cr.Name, cr.Namespace)) 131 ssoConfigLegalStatus = ssoLegalFailed // set global indicator that SSO config has gone wrong 132 _ = r.reconcileStatusSSO(cr) 133 return err 134 } 135 } 136 137 // case 5 138 if cr.Spec.SSO.Provider.ToLower() != argoproj.SSOProviderTypeDex && cr.Spec.SSO.Provider.ToLower() != argoproj.SSOProviderTypeKeycloak { 139 // `.spec.sso.provider` contains unsupported value 140 141 errMsg = fmt.Sprintf("Unsupported SSO provider type. Supported providers are %s and %s", argoproj.SSOProviderTypeDex, argoproj.SSOProviderTypeKeycloak) 142 err = errors.New(illegalSSOConfiguration + errMsg) 143 log.Error(err, fmt.Sprintf("Unsupported SSO provider type for Argo CD %s in namespace %s.", cr.Name, cr.Namespace)) 144 ssoConfigLegalStatus = ssoLegalFailed // set global indicator that SSO config has gone wrong 145 _ = r.reconcileStatusSSO(cr) 146 return err 147 } 148 } 149 150 // control reaching this point means that none of the illegal config combinations were detected. SSO is configured legally 151 // set global indicator that SSO config has been successful 152 ssoConfigLegalStatus = ssoLegalSuccess 153 154 // reconcile resources based on enabled provider 155 // keycloak 156 if cr.Spec.SSO != nil && cr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeKeycloak { 157 158 // Trigger reconciliation of any Dex resources so they get deleted 159 if err := r.reconcileDexResources(cr); err != nil && !apiErrors.IsNotFound(err) { 160 log.Error(err, "Unable to delete existing dex resources before configuring keycloak") 161 return err 162 } 163 164 if err := r.reconcileKeycloakConfiguration(cr); err != nil { 165 return err 166 } 167 } else if UseDex(cr) { 168 // dex 169 // Delete any lingering keycloak artifacts before Dex is configured as this is not handled by the reconcilliation loop 170 if err := deleteKeycloakConfiguration(cr); err != nil && !apiErrors.IsNotFound(err) { 171 log.Error(err, "Unable to delete existing SSO configuration before configuring Dex") 172 return err 173 } 174 175 if err := r.reconcileDexResources(cr); err != nil { 176 return err 177 } 178 } 179 180 _ = r.reconcileStatusSSO(cr) 181 182 return nil 183 } 184 185 func (r *ReconcileArgoCD) deleteSSOConfiguration(newCr *argoproj.ArgoCD, oldCr *argoproj.ArgoCD) error { 186 187 log.Info("uninstalling existing SSO configuration") 188 189 if oldCr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeKeycloak { 190 if err := deleteKeycloakConfiguration(newCr); err != nil { 191 log.Error(err, "Unable to delete existing keycloak configuration") 192 return err 193 } 194 } else if oldCr.Spec.SSO.Provider.ToLower() == argoproj.SSOProviderTypeDex { 195 // Trigger reconciliation of Dex resources so they get deleted 196 if err := r.deleteDexResources(newCr); err != nil { 197 log.Error(err, "Unable to reconcile necessary resources for uninstallation of Dex") 198 return err 199 } 200 } 201 202 _ = r.reconcileStatusSSO(newCr) 203 return nil 204 }