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  }