github.com/argoproj/argo-cd/v2@v2.10.5/util/settings/settings.go (about)

     1  package settings
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"crypto/sha256"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"encoding/base64"
    10  	"fmt"
    11  	"math/big"
    12  	"net/url"
    13  	"path"
    14  	"reflect"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	log "github.com/sirupsen/logrus"
    21  	apiv1 "k8s.io/api/core/v1"
    22  	apierr "k8s.io/apimachinery/pkg/api/errors"
    23  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/fields"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	v1 "k8s.io/client-go/informers/core/v1"
    28  	"k8s.io/client-go/kubernetes"
    29  	v1listers "k8s.io/client-go/listers/core/v1"
    30  	"k8s.io/client-go/tools/cache"
    31  	"sigs.k8s.io/yaml"
    32  
    33  	enginecache "github.com/argoproj/gitops-engine/pkg/cache"
    34  	timeutil "github.com/argoproj/pkg/time"
    35  
    36  	"github.com/argoproj/argo-cd/v2/common"
    37  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    38  	"github.com/argoproj/argo-cd/v2/server/settings/oidc"
    39  	"github.com/argoproj/argo-cd/v2/util"
    40  	"github.com/argoproj/argo-cd/v2/util/crypto"
    41  	"github.com/argoproj/argo-cd/v2/util/kube"
    42  	"github.com/argoproj/argo-cd/v2/util/password"
    43  	tlsutil "github.com/argoproj/argo-cd/v2/util/tls"
    44  )
    45  
    46  // ArgoCDSettings holds in-memory runtime configuration options.
    47  type ArgoCDSettings struct {
    48  	// URL is the externally facing URL users will visit to reach Argo CD.
    49  	// The value here is used when configuring SSO. Omitting this value will disable SSO.
    50  	URL string `json:"url,omitempty"`
    51  	// Indicates if status badge is enabled or not.
    52  	StatusBadgeEnabled bool `json:"statusBadgeEnable"`
    53  	// Indicates if status badge custom root URL should be used.
    54  	StatusBadgeRootUrl string `json:"statusBadgeRootUrl,omitempty"`
    55  	// DexConfig contains portions of a dex config yaml
    56  	DexConfig string `json:"dexConfig,omitempty"`
    57  	// OIDCConfigRAW holds OIDC configuration as a raw string
    58  	OIDCConfigRAW string `json:"oidcConfig,omitempty"`
    59  	// ServerSignature holds the key used to generate JWT tokens.
    60  	ServerSignature []byte `json:"serverSignature,omitempty"`
    61  	// Certificate holds the certificate/private key for the Argo CD API server.
    62  	// If nil, will run insecure without TLS.
    63  	Certificate *tls.Certificate `json:"-"`
    64  	// CertificateIsExternal indicates whether Certificate was loaded from external secret
    65  	CertificateIsExternal bool `json:"-"`
    66  	// WebhookGitLabSecret holds the shared secret for authenticating GitHub webhook events
    67  	WebhookGitHubSecret string `json:"webhookGitHubSecret,omitempty"`
    68  	// WebhookGitLabSecret holds the shared secret for authenticating GitLab webhook events
    69  	WebhookGitLabSecret string `json:"webhookGitLabSecret,omitempty"`
    70  	// WebhookBitbucketUUID holds the UUID for authenticating Bitbucket webhook events
    71  	WebhookBitbucketUUID string `json:"webhookBitbucketUUID,omitempty"`
    72  	// WebhookBitbucketServerSecret holds the shared secret for authenticating BitbucketServer webhook events
    73  	WebhookBitbucketServerSecret string `json:"webhookBitbucketServerSecret,omitempty"`
    74  	// WebhookGogsSecret holds the shared secret for authenticating Gogs webhook events
    75  	WebhookGogsSecret string `json:"webhookGogsSecret,omitempty"`
    76  	// WebhookAzureDevOpsUsername holds the username for authenticating Azure DevOps webhook events
    77  	WebhookAzureDevOpsUsername string `json:"webhookAzureDevOpsUsername,omitempty"`
    78  	// WebhookAzureDevOpsPassword holds the password for authenticating Azure DevOps webhook events
    79  	WebhookAzureDevOpsPassword string `json:"webhookAzureDevOpsPassword,omitempty"`
    80  	// Secrets holds all secrets in argocd-secret as a map[string]string
    81  	Secrets map[string]string `json:"secrets,omitempty"`
    82  	// KustomizeBuildOptions is a string of kustomize build parameters
    83  	KustomizeBuildOptions string `json:"kustomizeBuildOptions,omitempty"`
    84  	// Indicates if anonymous user is enabled or not
    85  	AnonymousUserEnabled bool `json:"anonymousUserEnabled,omitempty"`
    86  	// Specifies token expiration duration
    87  	UserSessionDuration time.Duration `json:"userSessionDuration,omitempty"`
    88  	// UiCssURL local or remote path to user-defined CSS to customize ArgoCD UI
    89  	UiCssURL string `json:"uiCssURL,omitempty"`
    90  	// Content of UI Banner
    91  	UiBannerContent string `json:"uiBannerContent,omitempty"`
    92  	// URL for UI Banner
    93  	UiBannerURL string `json:"uiBannerURL,omitempty"`
    94  	// Make Banner permanent and not closeable
    95  	UiBannerPermanent bool `json:"uiBannerPermanent,omitempty"`
    96  	// Position of UI Banner
    97  	UiBannerPosition string `json:"uiBannerPosition,omitempty"`
    98  	// PasswordPattern for password regular expression
    99  	PasswordPattern string `json:"passwordPattern,omitempty"`
   100  	// BinaryUrls contains the URLs for downloading argocd binaries
   101  	BinaryUrls map[string]string `json:"binaryUrls,omitempty"`
   102  	// InClusterEnabled indicates whether to allow in-cluster server address
   103  	InClusterEnabled bool `json:"inClusterEnabled"`
   104  	// ServerRBACLogEnforceEnable temporary var indicates whether rbac will be enforced on logs
   105  	ServerRBACLogEnforceEnable bool `json:"serverRBACLogEnforceEnable"`
   106  	// ExecEnabled indicates whether the UI exec feature is enabled
   107  	ExecEnabled bool `json:"execEnabled"`
   108  	// ExecShells restricts which shells are allowed for `exec` and in which order they are tried
   109  	ExecShells []string `json:"execShells"`
   110  	// TrackingMethod defines the resource tracking method to be used
   111  	TrackingMethod string `json:"application.resourceTrackingMethod,omitempty"`
   112  	// OIDCTLSInsecureSkipVerify determines whether certificate verification is skipped when verifying tokens with the
   113  	// configured OIDC provider (either external or the bundled Dex instance). Setting this to `true` will cause JWT
   114  	// token verification to pass despite the OIDC provider having an invalid certificate. Only set to `true` if you
   115  	// understand the risks.
   116  	OIDCTLSInsecureSkipVerify bool `json:"oidcTLSInsecureSkipVerify"`
   117  	// AppsInAnyNamespaceEnabled indicates whether applications are allowed to be created in any namespace
   118  	AppsInAnyNamespaceEnabled bool `json:"appsInAnyNamespaceEnabled"`
   119  	// ExtensionConfig configurations related to ArgoCD proxy extensions. The value
   120  	// is a yaml string defined in extension.ExtensionConfigs struct.
   121  	ExtensionConfig string `json:"extensionConfig,omitempty"`
   122  }
   123  
   124  type GoogleAnalytics struct {
   125  	TrackingID     string `json:"trackingID,omitempty"`
   126  	AnonymizeUsers bool   `json:"anonymizeUsers,omitempty"`
   127  }
   128  
   129  type GlobalProjectSettings struct {
   130  	ProjectName   string               `json:"projectName,omitempty"`
   131  	LabelSelector metav1.LabelSelector `json:"labelSelector,omitempty"`
   132  }
   133  
   134  // Help settings
   135  type Help struct {
   136  	// the URL for getting chat help, this will typically be your Slack channel for support
   137  	ChatURL string `json:"chatUrl,omitempty"`
   138  	// the text for getting chat help, defaults to "Chat now!"
   139  	ChatText string `json:"chatText,omitempty"`
   140  	// the URLs for downloading argocd binaries
   141  	BinaryURLs map[string]string `json:"binaryUrl,omitempty"`
   142  }
   143  
   144  // oidcConfig is the same as the public OIDCConfig, except the public one excludes the AllowedAudiences and the
   145  // SkipAudienceCheckWhenTokenHasNoAudience fields.
   146  // AllowedAudiences should be accessed via ArgoCDSettings.OAuth2AllowedAudiences.
   147  // SkipAudienceCheckWhenTokenHasNoAudience should be accessed via ArgoCDSettings.SkipAudienceCheckWhenTokenHasNoAudience.
   148  type oidcConfig struct {
   149  	OIDCConfig
   150  	AllowedAudiences                        []string `json:"allowedAudiences,omitempty"`
   151  	SkipAudienceCheckWhenTokenHasNoAudience *bool    `json:"skipAudienceCheckWhenTokenHasNoAudience,omitempty"`
   152  }
   153  
   154  func (o *oidcConfig) toExported() *OIDCConfig {
   155  	if o == nil {
   156  		return nil
   157  	}
   158  	return &OIDCConfig{
   159  		Name:                     o.Name,
   160  		Issuer:                   o.Issuer,
   161  		ClientID:                 o.ClientID,
   162  		ClientSecret:             o.ClientSecret,
   163  		CLIClientID:              o.CLIClientID,
   164  		UserInfoPath:             o.UserInfoPath,
   165  		EnableUserInfoGroups:     o.EnableUserInfoGroups,
   166  		UserInfoCacheExpiration:  o.UserInfoCacheExpiration,
   167  		RequestedScopes:          o.RequestedScopes,
   168  		RequestedIDTokenClaims:   o.RequestedIDTokenClaims,
   169  		LogoutURL:                o.LogoutURL,
   170  		RootCA:                   o.RootCA,
   171  		EnablePKCEAuthentication: o.EnablePKCEAuthentication,
   172  	}
   173  }
   174  
   175  type OIDCConfig struct {
   176  	Name                     string                 `json:"name,omitempty"`
   177  	Issuer                   string                 `json:"issuer,omitempty"`
   178  	ClientID                 string                 `json:"clientID,omitempty"`
   179  	ClientSecret             string                 `json:"clientSecret,omitempty"`
   180  	CLIClientID              string                 `json:"cliClientID,omitempty"`
   181  	EnableUserInfoGroups     bool                   `json:"enableUserInfoGroups,omitempty"`
   182  	UserInfoPath             string                 `json:"userInfoPath,omitempty"`
   183  	UserInfoCacheExpiration  string                 `json:"userInfoCacheExpiration,omitempty"`
   184  	RequestedScopes          []string               `json:"requestedScopes,omitempty"`
   185  	RequestedIDTokenClaims   map[string]*oidc.Claim `json:"requestedIDTokenClaims,omitempty"`
   186  	LogoutURL                string                 `json:"logoutURL,omitempty"`
   187  	RootCA                   string                 `json:"rootCA,omitempty"`
   188  	EnablePKCEAuthentication bool                   `json:"enablePKCEAuthentication,omitempty"`
   189  }
   190  
   191  // DEPRECATED. Helm repository credentials are now managed using RepoCredentials
   192  type HelmRepoCredentials struct {
   193  	URL            string                   `json:"url,omitempty"`
   194  	Name           string                   `json:"name,omitempty"`
   195  	UsernameSecret *apiv1.SecretKeySelector `json:"usernameSecret,omitempty"`
   196  	PasswordSecret *apiv1.SecretKeySelector `json:"passwordSecret,omitempty"`
   197  	CertSecret     *apiv1.SecretKeySelector `json:"certSecret,omitempty"`
   198  	KeySecret      *apiv1.SecretKeySelector `json:"keySecret,omitempty"`
   199  }
   200  
   201  // KustomizeVersion holds information about additional Kustomize version
   202  type KustomizeVersion struct {
   203  	// Name holds Kustomize version name
   204  	Name string
   205  	// Path holds corresponding binary path
   206  	Path string
   207  	// BuildOptions that are specific to Kustomize version
   208  	BuildOptions string
   209  }
   210  
   211  // KustomizeSettings holds kustomize settings
   212  type KustomizeSettings struct {
   213  	BuildOptions string
   214  	Versions     []KustomizeVersion
   215  }
   216  
   217  var (
   218  	ByClusterURLIndexer     = "byClusterURL"
   219  	byClusterURLIndexerFunc = func(obj interface{}) ([]string, error) {
   220  		s, ok := obj.(*apiv1.Secret)
   221  		if !ok {
   222  			return nil, nil
   223  		}
   224  		if s.Labels == nil || s.Labels[common.LabelKeySecretType] != common.LabelValueSecretTypeCluster {
   225  			return nil, nil
   226  		}
   227  		if s.Data == nil {
   228  			return nil, nil
   229  		}
   230  		if url, ok := s.Data["server"]; ok {
   231  			return []string{strings.TrimRight(string(url), "/")}, nil
   232  		}
   233  		return nil, nil
   234  	}
   235  	ByClusterNameIndexer     = "byClusterName"
   236  	byClusterNameIndexerFunc = func(obj interface{}) ([]string, error) {
   237  		s, ok := obj.(*apiv1.Secret)
   238  		if !ok {
   239  			return nil, nil
   240  		}
   241  		if s.Labels == nil || s.Labels[common.LabelKeySecretType] != common.LabelValueSecretTypeCluster {
   242  			return nil, nil
   243  		}
   244  		if s.Data == nil {
   245  			return nil, nil
   246  		}
   247  		if name, ok := s.Data["name"]; ok {
   248  			return []string{string(name)}, nil
   249  		}
   250  		return nil, nil
   251  	}
   252  	ByProjectClusterIndexer = "byProjectCluster"
   253  	ByProjectRepoIndexer    = "byProjectRepo"
   254  	byProjectIndexerFunc    = func(secretType string) func(obj interface{}) ([]string, error) {
   255  		return func(obj interface{}) ([]string, error) {
   256  			s, ok := obj.(*apiv1.Secret)
   257  			if !ok {
   258  				return nil, nil
   259  			}
   260  			if s.Labels == nil || s.Labels[common.LabelKeySecretType] != secretType {
   261  				return nil, nil
   262  			}
   263  			if s.Data == nil {
   264  				return nil, nil
   265  			}
   266  			if project, ok := s.Data["project"]; ok {
   267  				return []string{string(project)}, nil
   268  			}
   269  			return nil, nil
   270  		}
   271  	}
   272  )
   273  
   274  func (ks *KustomizeSettings) GetOptions(source v1alpha1.ApplicationSource) (*v1alpha1.KustomizeOptions, error) {
   275  	binaryPath := ""
   276  	buildOptions := ""
   277  	if source.Kustomize != nil && source.Kustomize.Version != "" {
   278  		for _, ver := range ks.Versions {
   279  			if ver.Name == source.Kustomize.Version {
   280  				// add version specific path and build options
   281  				binaryPath = ver.Path
   282  				buildOptions = ver.BuildOptions
   283  				break
   284  			}
   285  		}
   286  		if binaryPath == "" {
   287  			return nil, fmt.Errorf("kustomize version %s is not registered", source.Kustomize.Version)
   288  		}
   289  	} else {
   290  		// add build options for the default version
   291  		buildOptions = ks.BuildOptions
   292  	}
   293  	return &v1alpha1.KustomizeOptions{
   294  		BuildOptions: buildOptions,
   295  		BinaryPath:   binaryPath,
   296  	}, nil
   297  }
   298  
   299  // Credentials for accessing a Git repository
   300  type Repository struct {
   301  	// The URL to the repository
   302  	URL string `json:"url,omitempty"`
   303  	// the type of the repo, "git" or "helm", assumed to be "git" if empty or absent
   304  	Type string `json:"type,omitempty"`
   305  	// helm only
   306  	Name string `json:"name,omitempty"`
   307  	// Name of the secret storing the username used to access the repo
   308  	UsernameSecret *apiv1.SecretKeySelector `json:"usernameSecret,omitempty"`
   309  	// Name of the secret storing the password used to access the repo
   310  	PasswordSecret *apiv1.SecretKeySelector `json:"passwordSecret,omitempty"`
   311  	// Name of the secret storing the SSH private key used to access the repo. Git only
   312  	SSHPrivateKeySecret *apiv1.SecretKeySelector `json:"sshPrivateKeySecret,omitempty"`
   313  	// Whether to connect the repository in an insecure way (deprecated)
   314  	InsecureIgnoreHostKey bool `json:"insecureIgnoreHostKey,omitempty"`
   315  	// Whether to connect the repository in an insecure way
   316  	Insecure bool `json:"insecure,omitempty"`
   317  	// Whether the repo is git-lfs enabled. Git only.
   318  	EnableLFS bool `json:"enableLfs,omitempty"`
   319  	// Name of the secret storing the TLS client cert data
   320  	TLSClientCertDataSecret *apiv1.SecretKeySelector `json:"tlsClientCertDataSecret,omitempty"`
   321  	// Name of the secret storing the TLS client cert's key data
   322  	TLSClientCertKeySecret *apiv1.SecretKeySelector `json:"tlsClientCertKeySecret,omitempty"`
   323  	// Whether the repo is helm-oci enabled. Git only.
   324  	EnableOci bool `json:"enableOci,omitempty"`
   325  	// Github App Private Key PEM data
   326  	GithubAppPrivateKeySecret *apiv1.SecretKeySelector `json:"githubAppPrivateKeySecret,omitempty"`
   327  	// Github App ID of the app used to access the repo
   328  	GithubAppId int64 `json:"githubAppID,omitempty"`
   329  	// Github App Installation ID of the installed GitHub App
   330  	GithubAppInstallationId int64 `json:"githubAppInstallationID,omitempty"`
   331  	// Github App Enterprise base url if empty will default to https://api.github.com
   332  	GithubAppEnterpriseBaseURL string `json:"githubAppEnterpriseBaseUrl,omitempty"`
   333  	// Proxy specifies the HTTP/HTTPS proxy used to access the repo
   334  	Proxy string `json:"proxy,omitempty"`
   335  	// GCPServiceAccountKey specifies the service account key in JSON format to be used for getting credentials to Google Cloud Source repos
   336  	GCPServiceAccountKey *apiv1.SecretKeySelector `json:"gcpServiceAccountKey,omitempty"`
   337  	// ForceHttpBasicAuth determines whether Argo CD should force use of basic auth for HTTP connected repositories
   338  	ForceHttpBasicAuth bool `json:"forceHttpBasicAuth,omitempty"`
   339  }
   340  
   341  // Credential template for accessing repositories
   342  type RepositoryCredentials struct {
   343  	// The URL pattern the repository URL has to match
   344  	URL string `json:"url,omitempty"`
   345  	// Name of the secret storing the username used to access the repo
   346  	UsernameSecret *apiv1.SecretKeySelector `json:"usernameSecret,omitempty"`
   347  	// Name of the secret storing the password used to access the repo
   348  	PasswordSecret *apiv1.SecretKeySelector `json:"passwordSecret,omitempty"`
   349  	// Name of the secret storing the SSH private key used to access the repo. Git only
   350  	SSHPrivateKeySecret *apiv1.SecretKeySelector `json:"sshPrivateKeySecret,omitempty"`
   351  	// Name of the secret storing the TLS client cert data
   352  	TLSClientCertDataSecret *apiv1.SecretKeySelector `json:"tlsClientCertDataSecret,omitempty"`
   353  	// Name of the secret storing the TLS client cert's key data
   354  	TLSClientCertKeySecret *apiv1.SecretKeySelector `json:"tlsClientCertKeySecret,omitempty"`
   355  	// Github App Private Key PEM data
   356  	GithubAppPrivateKeySecret *apiv1.SecretKeySelector `json:"githubAppPrivateKeySecret,omitempty"`
   357  	// Github App ID of the app used to access the repo
   358  	GithubAppId int64 `json:"githubAppID,omitempty"`
   359  	// Github App Installation ID of the installed GitHub App
   360  	GithubAppInstallationId int64 `json:"githubAppInstallationID,omitempty"`
   361  	// Github App Enterprise base url if empty will default to https://api.github.com
   362  	GithubAppEnterpriseBaseURL string `json:"githubAppEnterpriseBaseUrl,omitempty"`
   363  	// EnableOCI specifies whether helm-oci support should be enabled for this repo
   364  	EnableOCI bool `json:"enableOCI,omitempty"`
   365  	// the type of the repositoryCredentials, "git" or "helm", assumed to be "git" if empty or absent
   366  	Type string `json:"type,omitempty"`
   367  	// GCPServiceAccountKey specifies the service account key in JSON format to be used for getting credentials to Google Cloud Source repos
   368  	GCPServiceAccountKey *apiv1.SecretKeySelector `json:"gcpServiceAccountKey,omitempty"`
   369  	// ForceHttpBasicAuth determines whether Argo CD should force use of basic auth for HTTP connected repositories
   370  	ForceHttpBasicAuth bool `json:"forceHttpBasicAuth,omitempty"`
   371  }
   372  
   373  // DeepLink structure
   374  type DeepLink struct {
   375  	// URL that the deep link will redirect to
   376  	URL string `json:"url"`
   377  	// Title that will be displayed in the UI corresponding to that link
   378  	Title string `json:"title"`
   379  	// Description (optional) a description for what the deep link is about
   380  	Description *string `json:"description,omitempty"`
   381  	// IconClass (optional) a font-awesome icon class to be used when displaying the links in dropdown menus.
   382  	IconClass *string `json:"icon.class,omitempty"`
   383  	// Condition (optional) a conditional statement depending on which the deep link shall be rendered
   384  	Condition *string `json:"if,omitempty"`
   385  }
   386  
   387  const (
   388  	// settingServerSignatureKey designates the key for a server secret key inside a Kubernetes secret.
   389  	settingServerSignatureKey = "server.secretkey"
   390  	// gaTrackingID holds Google Analytics tracking id
   391  	gaTrackingID = "ga.trackingid"
   392  	// the URL for getting chat help, this will typically be your Slack channel for support
   393  	helpChatURL = "help.chatUrl"
   394  	// the text for getting chat help, defaults to "Chat now!"
   395  	helpChatText = "help.chatText"
   396  	// gaAnonymizeUsers specifies if user ids should be anonymized (hashed) before sending to Google Analytics. True unless value is set to 'false'
   397  	gaAnonymizeUsers = "ga.anonymizeusers"
   398  	// settingServerCertificate designates the key for the public cert used in TLS
   399  	settingServerCertificate = "tls.crt"
   400  	// settingServerPrivateKey designates the key for the private key used in TLS
   401  	settingServerPrivateKey = "tls.key"
   402  	// settingURLKey designates the key where Argo CD's external URL is set
   403  	settingURLKey = "url"
   404  	// repositoriesKey designates the key where ArgoCDs repositories list is set
   405  	repositoriesKey = "repositories"
   406  	// repositoryCredentialsKey designates the key where ArgoCDs repositories credentials list is set
   407  	repositoryCredentialsKey = "repository.credentials"
   408  	// helmRepositoriesKey designates the key where list of helm repositories is set
   409  	helmRepositoriesKey = "helm.repositories"
   410  	// settingDexConfigKey designates the key for the dex config
   411  	settingDexConfigKey = "dex.config"
   412  	// settingsOIDCConfigKey designates the key for OIDC config
   413  	settingsOIDCConfigKey = "oidc.config"
   414  	// statusBadgeEnabledKey holds the key which enables of disables status badge feature
   415  	statusBadgeEnabledKey = "statusbadge.enabled"
   416  	// statusBadgeRootUrlKey holds the key for the root badge URL override
   417  	statusBadgeRootUrlKey = "statusbadge.url"
   418  	// settingsWebhookGitHubSecret is the key for the GitHub shared webhook secret
   419  	settingsWebhookGitHubSecretKey = "webhook.github.secret"
   420  	// settingsWebhookGitLabSecret is the key for the GitLab shared webhook secret
   421  	settingsWebhookGitLabSecretKey = "webhook.gitlab.secret"
   422  	// settingsWebhookBitbucketUUID is the key for Bitbucket webhook UUID
   423  	settingsWebhookBitbucketUUIDKey = "webhook.bitbucket.uuid"
   424  	// settingsWebhookBitbucketServerSecret is the key for BitbucketServer webhook secret
   425  	settingsWebhookBitbucketServerSecretKey = "webhook.bitbucketserver.secret"
   426  	// settingsWebhookGogsSecret is the key for Gogs webhook secret
   427  	settingsWebhookGogsSecretKey = "webhook.gogs.secret"
   428  	// settingsWebhookAzureDevOpsUsernameKey is the key for Azure DevOps webhook username
   429  	settingsWebhookAzureDevOpsUsernameKey = "webhook.azuredevops.username"
   430  	// settingsWebhookAzureDevOpsPasswordKey is the key for Azure DevOps webhook password
   431  	settingsWebhookAzureDevOpsPasswordKey = "webhook.azuredevops.password"
   432  	// settingsApplicationInstanceLabelKey is the key to configure injected app instance label key
   433  	settingsApplicationInstanceLabelKey = "application.instanceLabelKey"
   434  	// settingsResourceTrackingMethodKey is the key to configure tracking method for application resources
   435  	settingsResourceTrackingMethodKey = "application.resourceTrackingMethod"
   436  	// resourcesCustomizationsKey is the key to the map of resource overrides
   437  	resourceCustomizationsKey = "resource.customizations"
   438  	// resourceExclusions is the key to the list of excluded resources
   439  	resourceExclusionsKey = "resource.exclusions"
   440  	// resourceInclusions is the key to the list of explicitly watched resources
   441  	resourceInclusionsKey = "resource.inclusions"
   442  	// resourceIgnoreResourceUpdatesEnabledKey is the key to a boolean determining whether the resourceIgnoreUpdates feature is enabled
   443  	resourceIgnoreResourceUpdatesEnabledKey = "resource.ignoreResourceUpdatesEnabled"
   444  	// resourceCustomLabelKey is the key to a custom label to show in node info, if present
   445  	resourceCustomLabelsKey = "resource.customLabels"
   446  	// kustomizeBuildOptionsKey is a string of kustomize build parameters
   447  	kustomizeBuildOptionsKey = "kustomize.buildOptions"
   448  	// kustomizeVersionKeyPrefix is a kustomize version key prefix
   449  	kustomizeVersionKeyPrefix = "kustomize.version"
   450  	// kustomizePathPrefixKey is a kustomize path for a specific version
   451  	kustomizePathPrefixKey = "kustomize.path"
   452  	// anonymousUserEnabledKey is the key which enables or disables anonymous user
   453  	anonymousUserEnabledKey = "users.anonymous.enabled"
   454  	// userSessionDurationKey is the key which specifies token expiration duration
   455  	userSessionDurationKey = "users.session.duration"
   456  	// diffOptions is the key where diff options are configured
   457  	resourceCompareOptionsKey = "resource.compareoptions"
   458  	// settingUiCssURLKey designates the key for user-defined CSS URL for UI customization
   459  	settingUiCssURLKey = "ui.cssurl"
   460  	// settingUiBannerContentKey designates the key for content of user-defined info banner for UI
   461  	settingUiBannerContentKey = "ui.bannercontent"
   462  	// settingUiBannerURLKey designates the key for the link for user-defined info banner for UI
   463  	settingUiBannerURLKey = "ui.bannerurl"
   464  	// settingUiBannerPermanentKey designates the key for whether the banner is permanent and not closeable
   465  	settingUiBannerPermanentKey = "ui.bannerpermanent"
   466  	// settingUiBannerPositionKey designates the key for the position of the banner
   467  	settingUiBannerPositionKey = "ui.bannerposition"
   468  	// settingsBinaryUrlsKey designates the key for the argocd binary URLs
   469  	settingsBinaryUrlsKey = "help.download"
   470  	// globalProjectsKey designates the key for global project settings
   471  	globalProjectsKey = "globalProjects"
   472  	// initialPasswordSecretName is the name of the secret that will hold the initial admin password
   473  	initialPasswordSecretName = "argocd-initial-admin-secret"
   474  	// initialPasswordSecretField is the name of the field in initialPasswordSecretName to store the password
   475  	initialPasswordSecretField = "password"
   476  	// initialPasswordLength defines the length of the generated initial password
   477  	initialPasswordLength = 16
   478  	// externalServerTLSSecretName defines the name of the external secret holding the server's TLS certificate
   479  	externalServerTLSSecretName = "argocd-server-tls"
   480  	// partOfArgoCDSelector holds label selector that should be applied to config maps and secrets used to manage Argo CD
   481  	partOfArgoCDSelector = "app.kubernetes.io/part-of=argocd"
   482  	// settingsPasswordPatternKey is the key to configure user password regular expression
   483  	settingsPasswordPatternKey = "passwordPattern"
   484  	// inClusterEnabledKey is the key to configure whether to allow in-cluster server address
   485  	inClusterEnabledKey = "cluster.inClusterEnabled"
   486  	// settingsServerRBACLogEnforceEnable is the key to configure whether logs RBAC enforcement is enabled
   487  	settingsServerRBACLogEnforceEnableKey = "server.rbac.log.enforce.enable"
   488  	// helmValuesFileSchemesKey is the key to configure the list of supported helm values file schemas
   489  	helmValuesFileSchemesKey = "helm.valuesFileSchemes"
   490  	// execEnabledKey is the key to configure whether the UI exec feature is enabled
   491  	execEnabledKey = "exec.enabled"
   492  	// execShellsKey is the key to configure which shells are allowed for `exec` and in what order they are tried
   493  	execShellsKey = "exec.shells"
   494  	// oidcTLSInsecureSkipVerifyKey is the key to configure whether TLS cert verification is skipped for OIDC connections
   495  	oidcTLSInsecureSkipVerifyKey = "oidc.tls.insecure.skip.verify"
   496  	// ApplicationDeepLinks is the application deep link key
   497  	ApplicationDeepLinks = "application.links"
   498  	// ProjectDeepLinks is the project deep link key
   499  	ProjectDeepLinks = "project.links"
   500  	// ResourceDeepLinks is the resource deep link key
   501  	ResourceDeepLinks = "resource.links"
   502  	extensionConfig   = "extension.config"
   503  	// RespectRBAC is the key to configure argocd to respect rbac while watching for resources
   504  	RespectRBAC            = "resource.respectRBAC"
   505  	RespectRBACValueStrict = "strict"
   506  	RespectRBACValueNormal = "normal"
   507  )
   508  
   509  var (
   510  	sourceTypeToEnableGenerationKey = map[v1alpha1.ApplicationSourceType]string{
   511  		v1alpha1.ApplicationSourceTypeKustomize: "kustomize.enable",
   512  		v1alpha1.ApplicationSourceTypeHelm:      "helm.enable",
   513  		v1alpha1.ApplicationSourceTypeDirectory: "jsonnet.enable",
   514  	}
   515  )
   516  
   517  // SettingsManager holds config info for a new manager with which to access Kubernetes ConfigMaps.
   518  type SettingsManager struct {
   519  	ctx             context.Context
   520  	clientset       kubernetes.Interface
   521  	secrets         v1listers.SecretLister
   522  	secretsInformer cache.SharedIndexInformer
   523  	configmaps      v1listers.ConfigMapLister
   524  	namespace       string
   525  	// subscribers is a list of subscribers to settings updates
   526  	subscribers []chan<- *ArgoCDSettings
   527  	// mutex protects concurrency sensitive parts of settings manager: access to subscribers list and initialization flag
   528  	mutex                 *sync.Mutex
   529  	initContextCancel     func()
   530  	reposCache            []Repository
   531  	repoCredsCache        []RepositoryCredentials
   532  	reposOrClusterChanged func()
   533  }
   534  
   535  type incompleteSettingsError struct {
   536  	message string
   537  }
   538  
   539  type IgnoreStatus string
   540  
   541  const (
   542  	// IgnoreResourceStatusInCRD ignores status changes for all CRDs
   543  	IgnoreResourceStatusInCRD IgnoreStatus = "crd"
   544  	// IgnoreResourceStatusInAll ignores status changes for all resources
   545  	IgnoreResourceStatusInAll IgnoreStatus = "all"
   546  	// IgnoreResourceStatusInNone ignores status changes for no resources
   547  	IgnoreResourceStatusInNone IgnoreStatus = "off"
   548  )
   549  
   550  type ArgoCDDiffOptions struct {
   551  	IgnoreAggregatedRoles bool `json:"ignoreAggregatedRoles,omitempty"`
   552  
   553  	// If set to true then differences caused by status are ignored.
   554  	IgnoreResourceStatusField IgnoreStatus `json:"ignoreResourceStatusField,omitempty"`
   555  
   556  	// If set to true then ignoreDifferences are applied to ignore application refresh on resource updates.
   557  	IgnoreDifferencesOnResourceUpdates bool `json:"ignoreDifferencesOnResourceUpdates,omitempty"`
   558  }
   559  
   560  func (e *incompleteSettingsError) Error() string {
   561  	return e.message
   562  }
   563  
   564  func (mgr *SettingsManager) onRepoOrClusterChanged() {
   565  	if mgr.reposOrClusterChanged != nil {
   566  		go mgr.reposOrClusterChanged()
   567  	}
   568  }
   569  
   570  func (mgr *SettingsManager) RespectRBAC() (int, error) {
   571  	cm, err := mgr.getConfigMap()
   572  	if err != nil {
   573  		return enginecache.RespectRbacDisabled, err
   574  	}
   575  	if cm.Data[RespectRBAC] != "" {
   576  		switch cm.Data[RespectRBAC] {
   577  		case RespectRBACValueNormal:
   578  			return enginecache.RespectRbacNormal, nil
   579  		case RespectRBACValueStrict:
   580  			return enginecache.RespectRbacStrict, nil
   581  		default:
   582  			return enginecache.RespectRbacDisabled, fmt.Errorf("invalid value for %s: %s", RespectRBAC, cm.Data[RespectRBAC])
   583  		}
   584  	}
   585  	return enginecache.RespectRbacDisabled, nil
   586  }
   587  
   588  func (mgr *SettingsManager) GetSecretsLister() (v1listers.SecretLister, error) {
   589  	err := mgr.ensureSynced(false)
   590  	if err != nil {
   591  		return nil, err
   592  	}
   593  	return mgr.secrets, nil
   594  }
   595  
   596  func (mgr *SettingsManager) GetSecretsInformer() (cache.SharedIndexInformer, error) {
   597  	err := mgr.ensureSynced(false)
   598  	if err != nil {
   599  		return nil, fmt.Errorf("error ensuring that the secrets manager is synced: %w", err)
   600  	}
   601  	return mgr.secretsInformer, nil
   602  }
   603  
   604  func (mgr *SettingsManager) updateSecret(callback func(*apiv1.Secret) error) error {
   605  	err := mgr.ensureSynced(false)
   606  	if err != nil {
   607  		return err
   608  	}
   609  	argoCDSecret, err := mgr.secrets.Secrets(mgr.namespace).Get(common.ArgoCDSecretName)
   610  	createSecret := false
   611  	if err != nil {
   612  		if !apierr.IsNotFound(err) {
   613  			return err
   614  		}
   615  		argoCDSecret = &apiv1.Secret{
   616  			ObjectMeta: metav1.ObjectMeta{
   617  				Name: common.ArgoCDSecretName,
   618  			},
   619  			Data: make(map[string][]byte),
   620  		}
   621  		createSecret = true
   622  	}
   623  	if argoCDSecret.Data == nil {
   624  		argoCDSecret.Data = make(map[string][]byte)
   625  	}
   626  
   627  	updatedSecret := argoCDSecret.DeepCopy()
   628  	err = callback(updatedSecret)
   629  	if err != nil {
   630  		return err
   631  	}
   632  
   633  	if !createSecret && reflect.DeepEqual(argoCDSecret.Data, updatedSecret.Data) {
   634  		return nil
   635  	}
   636  
   637  	if createSecret {
   638  		_, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Create(context.Background(), updatedSecret, metav1.CreateOptions{})
   639  	} else {
   640  		_, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Update(context.Background(), updatedSecret, metav1.UpdateOptions{})
   641  	}
   642  	if err != nil {
   643  		return err
   644  	}
   645  
   646  	return mgr.ResyncInformers()
   647  }
   648  
   649  func (mgr *SettingsManager) updateConfigMap(callback func(*apiv1.ConfigMap) error) error {
   650  	argoCDCM, err := mgr.getConfigMap()
   651  	createCM := false
   652  	if err != nil {
   653  		if !apierr.IsNotFound(err) {
   654  			return err
   655  		}
   656  		argoCDCM = &apiv1.ConfigMap{
   657  			ObjectMeta: metav1.ObjectMeta{
   658  				Name: common.ArgoCDConfigMapName,
   659  			},
   660  		}
   661  		createCM = true
   662  	}
   663  	if argoCDCM.Data == nil {
   664  		argoCDCM.Data = make(map[string]string)
   665  	}
   666  	beforeUpdate := argoCDCM.DeepCopy()
   667  	err = callback(argoCDCM)
   668  	if err != nil {
   669  		return err
   670  	}
   671  	if reflect.DeepEqual(beforeUpdate.Data, argoCDCM.Data) {
   672  		return nil
   673  	}
   674  
   675  	if createCM {
   676  		_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Create(context.Background(), argoCDCM, metav1.CreateOptions{})
   677  	} else {
   678  		_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(context.Background(), argoCDCM, metav1.UpdateOptions{})
   679  	}
   680  
   681  	if err != nil {
   682  		return err
   683  	}
   684  
   685  	mgr.invalidateCache()
   686  
   687  	return mgr.ResyncInformers()
   688  }
   689  
   690  func (mgr *SettingsManager) getConfigMap() (*apiv1.ConfigMap, error) {
   691  	err := mgr.ensureSynced(false)
   692  	if err != nil {
   693  		return nil, err
   694  	}
   695  	argoCDCM, err := mgr.configmaps.ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName)
   696  	if err != nil {
   697  		return nil, err
   698  	}
   699  	if argoCDCM.Data == nil {
   700  		argoCDCM.Data = make(map[string]string)
   701  	}
   702  	return argoCDCM, err
   703  }
   704  
   705  // Returns the ConfigMap with the given name from the cluster.
   706  // The ConfigMap must be labeled with "app.kubernetes.io/part-of: argocd" in
   707  // order to be retrievable.
   708  func (mgr *SettingsManager) GetConfigMapByName(configMapName string) (*apiv1.ConfigMap, error) {
   709  	err := mgr.ensureSynced(false)
   710  	if err != nil {
   711  		return nil, err
   712  	}
   713  	configMap, err := mgr.configmaps.ConfigMaps(mgr.namespace).Get(configMapName)
   714  	if err != nil {
   715  		return nil, err
   716  	}
   717  	return configMap, err
   718  }
   719  
   720  func (mgr *SettingsManager) GetResourcesFilter() (*ResourcesFilter, error) {
   721  	argoCDCM, err := mgr.getConfigMap()
   722  	if err != nil {
   723  		return nil, fmt.Errorf("error retrieving argocd-cm: %w", err)
   724  	}
   725  	rf := &ResourcesFilter{}
   726  	if value, ok := argoCDCM.Data[resourceInclusionsKey]; ok {
   727  		includedResources := make([]FilteredResource, 0)
   728  		err := yaml.Unmarshal([]byte(value), &includedResources)
   729  		if err != nil {
   730  			return nil, fmt.Errorf("error unmarshalling included resources %w", err)
   731  		}
   732  		rf.ResourceInclusions = includedResources
   733  	}
   734  
   735  	if value, ok := argoCDCM.Data[resourceExclusionsKey]; ok {
   736  		excludedResources := make([]FilteredResource, 0)
   737  		err := yaml.Unmarshal([]byte(value), &excludedResources)
   738  		if err != nil {
   739  			return nil, fmt.Errorf("error unmarshalling excluded resources %w", err)
   740  		}
   741  		rf.ResourceExclusions = excludedResources
   742  	}
   743  	return rf, nil
   744  }
   745  
   746  func (mgr *SettingsManager) GetAppInstanceLabelKey() (string, error) {
   747  	argoCDCM, err := mgr.getConfigMap()
   748  	if err != nil {
   749  		return "", err
   750  	}
   751  	label := argoCDCM.Data[settingsApplicationInstanceLabelKey]
   752  	if label == "" {
   753  		return common.LabelKeyAppInstance, nil
   754  	}
   755  	return label, nil
   756  }
   757  
   758  func (mgr *SettingsManager) GetTrackingMethod() (string, error) {
   759  	argoCDCM, err := mgr.getConfigMap()
   760  	if err != nil {
   761  		return "", err
   762  	}
   763  	return argoCDCM.Data[settingsResourceTrackingMethodKey], nil
   764  }
   765  
   766  func (mgr *SettingsManager) GetPasswordPattern() (string, error) {
   767  	argoCDCM, err := mgr.getConfigMap()
   768  	if err != nil {
   769  		return "", err
   770  	}
   771  	label := argoCDCM.Data[settingsPasswordPatternKey]
   772  	if label == "" {
   773  		return common.PasswordPatten, nil
   774  	}
   775  	return label, nil
   776  }
   777  
   778  func (mgr *SettingsManager) GetServerRBACLogEnforceEnable() (bool, error) {
   779  	argoCDCM, err := mgr.getConfigMap()
   780  	if err != nil {
   781  		return false, err
   782  	}
   783  
   784  	if argoCDCM.Data[settingsServerRBACLogEnforceEnableKey] == "" {
   785  		return false, nil
   786  	}
   787  
   788  	return strconv.ParseBool(argoCDCM.Data[settingsServerRBACLogEnforceEnableKey])
   789  }
   790  
   791  func (mgr *SettingsManager) GetDeepLinks(deeplinkType string) ([]DeepLink, error) {
   792  	argoCDCM, err := mgr.getConfigMap()
   793  	if err != nil {
   794  		return nil, fmt.Errorf("error retrieving argocd-cm: %w", err)
   795  	}
   796  	deepLinks := make([]DeepLink, 0)
   797  	if value, ok := argoCDCM.Data[deeplinkType]; ok {
   798  		err := yaml.Unmarshal([]byte(value), &deepLinks)
   799  		if err != nil {
   800  			return nil, fmt.Errorf("error unmarshalling deep links %w", err)
   801  		}
   802  	}
   803  	return deepLinks, nil
   804  }
   805  
   806  func (mgr *SettingsManager) GetEnabledSourceTypes() (map[string]bool, error) {
   807  	argoCDCM, err := mgr.getConfigMap()
   808  	if err != nil {
   809  		return nil, fmt.Errorf("failed to get argo-cd config map: %w", err)
   810  	}
   811  	res := map[string]bool{}
   812  	for sourceType := range sourceTypeToEnableGenerationKey {
   813  		res[string(sourceType)] = true
   814  	}
   815  	for sourceType, key := range sourceTypeToEnableGenerationKey {
   816  		if val, ok := argoCDCM.Data[key]; ok && val != "" {
   817  			res[string(sourceType)] = val == "true"
   818  		}
   819  	}
   820  	// plugin based manifest generation cannot be disabled
   821  	res[string(v1alpha1.ApplicationSourceTypePlugin)] = true
   822  	return res, nil
   823  }
   824  
   825  func (mgr *SettingsManager) GetIgnoreResourceUpdatesOverrides() (map[string]v1alpha1.ResourceOverride, error) {
   826  	compareOptions, err := mgr.GetResourceCompareOptions()
   827  	if err != nil {
   828  		return nil, fmt.Errorf("failed to get compare options: %w", err)
   829  	}
   830  
   831  	resourceOverrides, err := mgr.GetResourceOverrides()
   832  	if err != nil {
   833  		return nil, fmt.Errorf("failed to get resource overrides: %w", err)
   834  	}
   835  
   836  	for k, v := range resourceOverrides {
   837  		resourceUpdates := v.IgnoreResourceUpdates
   838  		if compareOptions.IgnoreDifferencesOnResourceUpdates {
   839  			resourceUpdates.JQPathExpressions = append(resourceUpdates.JQPathExpressions, v.IgnoreDifferences.JQPathExpressions...)
   840  			resourceUpdates.JSONPointers = append(resourceUpdates.JSONPointers, v.IgnoreDifferences.JSONPointers...)
   841  			resourceUpdates.ManagedFieldsManagers = append(resourceUpdates.ManagedFieldsManagers, v.IgnoreDifferences.ManagedFieldsManagers...)
   842  		}
   843  		// Set the IgnoreDifferences because these are the overrides used by Normalizers
   844  		v.IgnoreDifferences = resourceUpdates
   845  		v.IgnoreResourceUpdates = v1alpha1.OverrideIgnoreDiff{}
   846  		resourceOverrides[k] = v
   847  	}
   848  
   849  	if compareOptions.IgnoreDifferencesOnResourceUpdates {
   850  		log.Info("Using diffing customizations to ignore resource updates")
   851  	}
   852  
   853  	addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/resourceVersion")
   854  	addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/generation")
   855  	addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/managedFields")
   856  
   857  	return resourceOverrides, nil
   858  }
   859  
   860  func (mgr *SettingsManager) GetIsIgnoreResourceUpdatesEnabled() (bool, error) {
   861  	argoCDCM, err := mgr.getConfigMap()
   862  	if err != nil {
   863  		return false, fmt.Errorf("error retrieving config map: %w", err)
   864  	}
   865  
   866  	if argoCDCM.Data[resourceIgnoreResourceUpdatesEnabledKey] == "" {
   867  		return false, nil
   868  	}
   869  
   870  	return strconv.ParseBool(argoCDCM.Data[resourceIgnoreResourceUpdatesEnabledKey])
   871  }
   872  
   873  // GetResourceOverrides loads Resource Overrides from argocd-cm ConfigMap
   874  func (mgr *SettingsManager) GetResourceOverrides() (map[string]v1alpha1.ResourceOverride, error) {
   875  	argoCDCM, err := mgr.getConfigMap()
   876  	if err != nil {
   877  		return nil, fmt.Errorf("error retrieving config map: %w", err)
   878  	}
   879  	resourceOverrides := map[string]v1alpha1.ResourceOverride{}
   880  	if value, ok := argoCDCM.Data[resourceCustomizationsKey]; ok && value != "" {
   881  		err := yaml.Unmarshal([]byte(value), &resourceOverrides)
   882  		if err != nil {
   883  			return nil, err
   884  		}
   885  	}
   886  
   887  	err = mgr.appendResourceOverridesFromSplitKeys(argoCDCM.Data, resourceOverrides)
   888  	if err != nil {
   889  		return nil, err
   890  	}
   891  
   892  	var diffOptions ArgoCDDiffOptions
   893  	if value, ok := argoCDCM.Data[resourceCompareOptionsKey]; ok {
   894  		err := yaml.Unmarshal([]byte(value), &diffOptions)
   895  		if err != nil {
   896  			return nil, err
   897  		}
   898  	}
   899  
   900  	crdGK := "apiextensions.k8s.io/CustomResourceDefinition"
   901  	crdPrsvUnkn := "/spec/preserveUnknownFields"
   902  
   903  	switch diffOptions.IgnoreResourceStatusField {
   904  	case "", "crd":
   905  		addStatusOverrideToGK(resourceOverrides, crdGK)
   906  		addIgnoreDiffItemOverrideToGK(resourceOverrides, crdGK, crdPrsvUnkn)
   907  	case "all":
   908  		addStatusOverrideToGK(resourceOverrides, "*/*")
   909  		log.Info("Ignore status for all objects")
   910  
   911  	case "off", "false":
   912  		log.Info("Not ignoring status for any object")
   913  
   914  	default:
   915  		addStatusOverrideToGK(resourceOverrides, crdGK)
   916  		log.Warnf("Unrecognized value for ignoreResourceStatusField - %s, ignore status for CustomResourceDefinitions", diffOptions.IgnoreResourceStatusField)
   917  	}
   918  
   919  	return resourceOverrides, nil
   920  }
   921  
   922  func addStatusOverrideToGK(resourceOverrides map[string]v1alpha1.ResourceOverride, groupKind string) {
   923  	if val, ok := resourceOverrides[groupKind]; ok {
   924  		val.IgnoreDifferences.JSONPointers = append(val.IgnoreDifferences.JSONPointers, "/status")
   925  		resourceOverrides[groupKind] = val
   926  	} else {
   927  		resourceOverrides[groupKind] = v1alpha1.ResourceOverride{
   928  			IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/status"}},
   929  		}
   930  	}
   931  }
   932  
   933  func addIgnoreDiffItemOverrideToGK(resourceOverrides map[string]v1alpha1.ResourceOverride, groupKind, ignoreItem string) {
   934  	if val, ok := resourceOverrides[groupKind]; ok {
   935  		val.IgnoreDifferences.JSONPointers = append(val.IgnoreDifferences.JSONPointers, ignoreItem)
   936  		resourceOverrides[groupKind] = val
   937  	} else {
   938  		resourceOverrides[groupKind] = v1alpha1.ResourceOverride{
   939  			IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{ignoreItem}},
   940  		}
   941  	}
   942  }
   943  
   944  func (mgr *SettingsManager) appendResourceOverridesFromSplitKeys(cmData map[string]string, resourceOverrides map[string]v1alpha1.ResourceOverride) error {
   945  	for k, v := range cmData {
   946  		if !strings.HasPrefix(k, resourceCustomizationsKey) {
   947  			continue
   948  		}
   949  
   950  		// config map key should be of format resource.customizations.<type>.<group-kind>
   951  		parts := strings.SplitN(k, ".", 4)
   952  		if len(parts) < 4 {
   953  			continue
   954  		}
   955  
   956  		overrideKey, err := convertToOverrideKey(parts[3])
   957  		if err != nil {
   958  			return err
   959  		}
   960  
   961  		if overrideKey == "all" {
   962  			overrideKey = "*/*"
   963  		}
   964  
   965  		overrideVal, ok := resourceOverrides[overrideKey]
   966  		if !ok {
   967  			overrideVal = v1alpha1.ResourceOverride{}
   968  		}
   969  
   970  		customizationType := parts[2]
   971  		switch customizationType {
   972  		case "health":
   973  			overrideVal.HealthLua = v
   974  		case "useOpenLibs":
   975  			useOpenLibs, err := strconv.ParseBool(v)
   976  			if err != nil {
   977  				return err
   978  			}
   979  			overrideVal.UseOpenLibs = useOpenLibs
   980  		case "actions":
   981  			overrideVal.Actions = v
   982  		case "ignoreDifferences":
   983  			overrideIgnoreDiff := v1alpha1.OverrideIgnoreDiff{}
   984  			err := yaml.Unmarshal([]byte(v), &overrideIgnoreDiff)
   985  			if err != nil {
   986  				return err
   987  			}
   988  			overrideVal.IgnoreDifferences = overrideIgnoreDiff
   989  		case "ignoreResourceUpdates":
   990  			overrideIgnoreUpdate := v1alpha1.OverrideIgnoreDiff{}
   991  			err := yaml.Unmarshal([]byte(v), &overrideIgnoreUpdate)
   992  			if err != nil {
   993  				return err
   994  			}
   995  			overrideVal.IgnoreResourceUpdates = overrideIgnoreUpdate
   996  		case "knownTypeFields":
   997  			var knownTypeFields []v1alpha1.KnownTypeField
   998  			err := yaml.Unmarshal([]byte(v), &knownTypeFields)
   999  			if err != nil {
  1000  				return err
  1001  			}
  1002  			overrideVal.KnownTypeFields = knownTypeFields
  1003  		default:
  1004  			return fmt.Errorf("resource customization type %s not supported", customizationType)
  1005  		}
  1006  		resourceOverrides[overrideKey] = overrideVal
  1007  	}
  1008  	return nil
  1009  }
  1010  
  1011  // Convert group-kind format to <group/kind>, allowed key format examples
  1012  // resource.customizations.health.cert-manager.io_Certificate
  1013  // resource.customizations.health.Certificate
  1014  func convertToOverrideKey(groupKind string) (string, error) {
  1015  	parts := strings.Split(groupKind, "_")
  1016  	if len(parts) == 2 {
  1017  		return fmt.Sprintf("%s/%s", parts[0], parts[1]), nil
  1018  	} else if len(parts) == 1 && groupKind != "" {
  1019  		return groupKind, nil
  1020  	}
  1021  	return "", fmt.Errorf("group kind should be in format `resource.customizations.<type>.<group_kind>` or resource.customizations.<type>.<kind>`, got group kind: '%s'", groupKind)
  1022  }
  1023  
  1024  func GetDefaultDiffOptions() ArgoCDDiffOptions {
  1025  	return ArgoCDDiffOptions{IgnoreAggregatedRoles: false, IgnoreDifferencesOnResourceUpdates: false}
  1026  }
  1027  
  1028  // GetResourceCompareOptions loads the resource compare options settings from the ConfigMap
  1029  func (mgr *SettingsManager) GetResourceCompareOptions() (ArgoCDDiffOptions, error) {
  1030  	// We have a sane set of default diff options
  1031  	diffOptions := GetDefaultDiffOptions()
  1032  
  1033  	argoCDCM, err := mgr.getConfigMap()
  1034  	if err != nil {
  1035  		return diffOptions, err
  1036  	}
  1037  
  1038  	if value, ok := argoCDCM.Data[resourceCompareOptionsKey]; ok {
  1039  		err := yaml.Unmarshal([]byte(value), &diffOptions)
  1040  		if err != nil {
  1041  			return diffOptions, err
  1042  		}
  1043  	}
  1044  
  1045  	return diffOptions, nil
  1046  }
  1047  
  1048  // GetHelmSettings returns helm settings
  1049  func (mgr *SettingsManager) GetHelmSettings() (*v1alpha1.HelmOptions, error) {
  1050  	argoCDCM, err := mgr.getConfigMap()
  1051  	if err != nil {
  1052  		return nil, fmt.Errorf("failed to get argo-cd config map: %v", err)
  1053  	}
  1054  	helmOptions := &v1alpha1.HelmOptions{}
  1055  	if value, ok := argoCDCM.Data[helmValuesFileSchemesKey]; ok {
  1056  		for _, item := range strings.Split(value, ",") {
  1057  			if item := strings.TrimSpace(item); item != "" {
  1058  				helmOptions.ValuesFileSchemes = append(helmOptions.ValuesFileSchemes, item)
  1059  			}
  1060  		}
  1061  	} else {
  1062  		helmOptions.ValuesFileSchemes = []string{"https", "http"}
  1063  	}
  1064  	return helmOptions, nil
  1065  }
  1066  
  1067  // GetKustomizeSettings loads the kustomize settings from argocd-cm ConfigMap
  1068  func (mgr *SettingsManager) GetKustomizeSettings() (*KustomizeSettings, error) {
  1069  	argoCDCM, err := mgr.getConfigMap()
  1070  	if err != nil {
  1071  		return nil, fmt.Errorf("error retrieving argocd-cm: %w", err)
  1072  	}
  1073  	kustomizeVersionsMap := map[string]KustomizeVersion{}
  1074  	buildOptions := map[string]string{}
  1075  	settings := &KustomizeSettings{}
  1076  
  1077  	// extract build options for the default version
  1078  	if options, ok := argoCDCM.Data[kustomizeBuildOptionsKey]; ok {
  1079  		settings.BuildOptions = options
  1080  	}
  1081  
  1082  	// extract per-version binary paths and build options
  1083  	for k, v := range argoCDCM.Data {
  1084  		// extract version and path from kustomize.version.<version>
  1085  		if strings.HasPrefix(k, kustomizeVersionKeyPrefix) {
  1086  			err = addKustomizeVersion(kustomizeVersionKeyPrefix, k, v, kustomizeVersionsMap)
  1087  			if err != nil {
  1088  				return nil, fmt.Errorf("failed to add kustomize version from %q: %w", k, err)
  1089  			}
  1090  		}
  1091  
  1092  		// extract version and path from kustomize.path.<version>
  1093  		if strings.HasPrefix(k, kustomizePathPrefixKey) {
  1094  			err = addKustomizeVersion(kustomizePathPrefixKey, k, v, kustomizeVersionsMap)
  1095  			if err != nil {
  1096  				return nil, fmt.Errorf("failed to add kustomize version from %q: %w", k, err)
  1097  			}
  1098  		}
  1099  
  1100  		// extract version and build options from kustomize.buildOptions.<version>
  1101  		if strings.HasPrefix(k, kustomizeBuildOptionsKey) && k != kustomizeBuildOptionsKey {
  1102  			buildOptions[k[len(kustomizeBuildOptionsKey)+1:]] = v
  1103  		}
  1104  	}
  1105  
  1106  	for _, v := range kustomizeVersionsMap {
  1107  		if _, ok := buildOptions[v.Name]; ok {
  1108  			v.BuildOptions = buildOptions[v.Name]
  1109  		}
  1110  		settings.Versions = append(settings.Versions, v)
  1111  	}
  1112  	return settings, nil
  1113  }
  1114  
  1115  func addKustomizeVersion(prefix, name, path string, kvMap map[string]KustomizeVersion) error {
  1116  	version := name[len(prefix)+1:]
  1117  	if _, ok := kvMap[version]; ok {
  1118  		return fmt.Errorf("found duplicate kustomize version: %s", version)
  1119  	}
  1120  	kvMap[version] = KustomizeVersion{
  1121  		Name: version,
  1122  		Path: path,
  1123  	}
  1124  	return nil
  1125  }
  1126  
  1127  // DEPRECATED. Helm repository credentials are now managed using RepoCredentials
  1128  func (mgr *SettingsManager) GetHelmRepositories() ([]HelmRepoCredentials, error) {
  1129  	argoCDCM, err := mgr.getConfigMap()
  1130  	if err != nil {
  1131  		return nil, fmt.Errorf("error retrieving config map: %w", err)
  1132  	}
  1133  	helmRepositories := make([]HelmRepoCredentials, 0)
  1134  	helmRepositoriesStr := argoCDCM.Data[helmRepositoriesKey]
  1135  	if helmRepositoriesStr != "" {
  1136  		err := yaml.Unmarshal([]byte(helmRepositoriesStr), &helmRepositories)
  1137  		if err != nil {
  1138  			return nil, fmt.Errorf("error unmarshalling helm repositories: %w", err)
  1139  		}
  1140  	}
  1141  	return helmRepositories, nil
  1142  }
  1143  
  1144  func (mgr *SettingsManager) GetRepositories() ([]Repository, error) {
  1145  
  1146  	mgr.mutex.Lock()
  1147  	reposCache := mgr.reposCache
  1148  	mgr.mutex.Unlock()
  1149  	if reposCache != nil {
  1150  		return reposCache, nil
  1151  	}
  1152  
  1153  	// Get the config map outside of the lock
  1154  	argoCDCM, err := mgr.getConfigMap()
  1155  	if err != nil {
  1156  		return nil, fmt.Errorf("failed to get argo-cd config map: %w", err)
  1157  	}
  1158  
  1159  	mgr.mutex.Lock()
  1160  	defer mgr.mutex.Unlock()
  1161  	repositories := make([]Repository, 0)
  1162  	repositoriesStr := argoCDCM.Data[repositoriesKey]
  1163  	if repositoriesStr != "" {
  1164  		err := yaml.Unmarshal([]byte(repositoriesStr), &repositories)
  1165  		if err != nil {
  1166  			return nil, fmt.Errorf("failed to unmarshal repositories from config map key %q: %w", repositoriesKey, err)
  1167  		}
  1168  	}
  1169  	mgr.reposCache = repositories
  1170  
  1171  	return mgr.reposCache, nil
  1172  }
  1173  
  1174  func (mgr *SettingsManager) SaveRepositories(repos []Repository) error {
  1175  	return mgr.updateConfigMap(func(argoCDCM *apiv1.ConfigMap) error {
  1176  		if len(repos) > 0 {
  1177  			yamlStr, err := yaml.Marshal(repos)
  1178  			if err != nil {
  1179  				return err
  1180  			}
  1181  			argoCDCM.Data[repositoriesKey] = string(yamlStr)
  1182  		} else {
  1183  			delete(argoCDCM.Data, repositoriesKey)
  1184  		}
  1185  		return nil
  1186  	})
  1187  }
  1188  
  1189  func (mgr *SettingsManager) SaveRepositoryCredentials(creds []RepositoryCredentials) error {
  1190  	return mgr.updateConfigMap(func(argoCDCM *apiv1.ConfigMap) error {
  1191  		if len(creds) > 0 {
  1192  			yamlStr, err := yaml.Marshal(creds)
  1193  			if err != nil {
  1194  				return err
  1195  			}
  1196  			argoCDCM.Data[repositoryCredentialsKey] = string(yamlStr)
  1197  		} else {
  1198  			delete(argoCDCM.Data, repositoryCredentialsKey)
  1199  		}
  1200  		return nil
  1201  	})
  1202  }
  1203  
  1204  func (mgr *SettingsManager) GetRepositoryCredentials() ([]RepositoryCredentials, error) {
  1205  
  1206  	mgr.mutex.Lock()
  1207  	repoCredsCache := mgr.repoCredsCache
  1208  	mgr.mutex.Unlock()
  1209  	if repoCredsCache != nil {
  1210  		return repoCredsCache, nil
  1211  	}
  1212  
  1213  	// Get the config map outside of the lock
  1214  	argoCDCM, err := mgr.getConfigMap()
  1215  	if err != nil {
  1216  		return nil, fmt.Errorf("error retrieving config map: %w", err)
  1217  	}
  1218  
  1219  	mgr.mutex.Lock()
  1220  	defer mgr.mutex.Unlock()
  1221  	creds := make([]RepositoryCredentials, 0)
  1222  	credsStr := argoCDCM.Data[repositoryCredentialsKey]
  1223  	if credsStr != "" {
  1224  		err := yaml.Unmarshal([]byte(credsStr), &creds)
  1225  		if err != nil {
  1226  			return nil, err
  1227  		}
  1228  	}
  1229  	mgr.repoCredsCache = creds
  1230  
  1231  	return mgr.repoCredsCache, nil
  1232  }
  1233  
  1234  func (mgr *SettingsManager) GetGoogleAnalytics() (*GoogleAnalytics, error) {
  1235  	argoCDCM, err := mgr.getConfigMap()
  1236  	if err != nil {
  1237  		return nil, fmt.Errorf("error retrieving config map: %w", err)
  1238  	}
  1239  	return &GoogleAnalytics{
  1240  		TrackingID:     argoCDCM.Data[gaTrackingID],
  1241  		AnonymizeUsers: argoCDCM.Data[gaAnonymizeUsers] != "false",
  1242  	}, nil
  1243  }
  1244  
  1245  func (mgr *SettingsManager) GetHelp() (*Help, error) {
  1246  	argoCDCM, err := mgr.getConfigMap()
  1247  	if err != nil {
  1248  		return nil, fmt.Errorf("error retrieving config map: %w", err)
  1249  	}
  1250  	chatText, ok := argoCDCM.Data[helpChatText]
  1251  	if !ok {
  1252  		chatText = "Chat now!"
  1253  	}
  1254  	chatURL, ok := argoCDCM.Data[helpChatURL]
  1255  	if !ok {
  1256  		chatText = ""
  1257  	}
  1258  	return &Help{
  1259  		ChatURL:    chatURL,
  1260  		ChatText:   chatText,
  1261  		BinaryURLs: getDownloadBinaryUrlsFromConfigMap(argoCDCM),
  1262  	}, nil
  1263  }
  1264  
  1265  // GetSettings retrieves settings from the ArgoCDConfigMap and secret.
  1266  func (mgr *SettingsManager) GetSettings() (*ArgoCDSettings, error) {
  1267  	err := mgr.ensureSynced(false)
  1268  	if err != nil {
  1269  		return nil, err
  1270  	}
  1271  	argoCDCM, err := mgr.configmaps.ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName)
  1272  	if err != nil {
  1273  		return nil, fmt.Errorf("error retrieving argocd-cm: %w", err)
  1274  	}
  1275  	argoCDSecret, err := mgr.secrets.Secrets(mgr.namespace).Get(common.ArgoCDSecretName)
  1276  	if err != nil {
  1277  		return nil, fmt.Errorf("error retrieving argocd-secret: %w", err)
  1278  	}
  1279  	selector, err := labels.Parse(partOfArgoCDSelector)
  1280  	if err != nil {
  1281  		return nil, fmt.Errorf("error parsing Argo CD selector %w", err)
  1282  	}
  1283  	secrets, err := mgr.secrets.Secrets(mgr.namespace).List(selector)
  1284  	if err != nil {
  1285  		return nil, err
  1286  	}
  1287  	var settings ArgoCDSettings
  1288  	var errs []error
  1289  	updateSettingsFromConfigMap(&settings, argoCDCM)
  1290  	if err := mgr.updateSettingsFromSecret(&settings, argoCDSecret, secrets); err != nil {
  1291  		errs = append(errs, err)
  1292  	}
  1293  	if len(errs) > 0 {
  1294  		return &settings, errs[0]
  1295  	}
  1296  
  1297  	return &settings, nil
  1298  }
  1299  
  1300  // Clears cached settings on configmap/secret change
  1301  func (mgr *SettingsManager) invalidateCache() {
  1302  	mgr.mutex.Lock()
  1303  	defer mgr.mutex.Unlock()
  1304  
  1305  	mgr.reposCache = nil
  1306  	mgr.repoCredsCache = nil
  1307  }
  1308  
  1309  func (mgr *SettingsManager) initialize(ctx context.Context) error {
  1310  	tweakConfigMap := func(options *metav1.ListOptions) {
  1311  		cmLabelSelector := fields.ParseSelectorOrDie(partOfArgoCDSelector)
  1312  		options.LabelSelector = cmLabelSelector.String()
  1313  	}
  1314  
  1315  	eventHandler := cache.ResourceEventHandlerFuncs{
  1316  		UpdateFunc: func(oldObj, newObj interface{}) {
  1317  			mgr.invalidateCache()
  1318  			mgr.onRepoOrClusterChanged()
  1319  		},
  1320  		AddFunc: func(obj interface{}) {
  1321  			mgr.onRepoOrClusterChanged()
  1322  		},
  1323  		DeleteFunc: func(obj interface{}) {
  1324  			mgr.onRepoOrClusterChanged()
  1325  		},
  1326  	}
  1327  	indexers := cache.Indexers{
  1328  		cache.NamespaceIndex:    cache.MetaNamespaceIndexFunc,
  1329  		ByClusterURLIndexer:     byClusterURLIndexerFunc,
  1330  		ByClusterNameIndexer:    byClusterNameIndexerFunc,
  1331  		ByProjectClusterIndexer: byProjectIndexerFunc(common.LabelValueSecretTypeCluster),
  1332  		ByProjectRepoIndexer:    byProjectIndexerFunc(common.LabelValueSecretTypeRepository),
  1333  	}
  1334  	cmInformer := v1.NewFilteredConfigMapInformer(mgr.clientset, mgr.namespace, 3*time.Minute, indexers, tweakConfigMap)
  1335  	secretsInformer := v1.NewSecretInformer(mgr.clientset, mgr.namespace, 3*time.Minute, indexers)
  1336  	_, err := cmInformer.AddEventHandler(eventHandler)
  1337  	if err != nil {
  1338  		log.Error(err)
  1339  	}
  1340  
  1341  	_, err = secretsInformer.AddEventHandler(eventHandler)
  1342  	if err != nil {
  1343  		log.Error(err)
  1344  	}
  1345  
  1346  	log.Info("Starting configmap/secret informers")
  1347  	go func() {
  1348  		cmInformer.Run(ctx.Done())
  1349  		log.Info("configmap informer cancelled")
  1350  	}()
  1351  	go func() {
  1352  		secretsInformer.Run(ctx.Done())
  1353  		log.Info("secrets informer cancelled")
  1354  	}()
  1355  
  1356  	if !cache.WaitForCacheSync(ctx.Done(), cmInformer.HasSynced, secretsInformer.HasSynced) {
  1357  		return fmt.Errorf("Timed out waiting for settings cache to sync")
  1358  	}
  1359  	log.Info("Configmap/secret informer synced")
  1360  
  1361  	tryNotify := func() {
  1362  		newSettings, err := mgr.GetSettings()
  1363  		if err != nil {
  1364  			log.Warnf("Unable to parse updated settings: %v", err)
  1365  		} else {
  1366  			mgr.notifySubscribers(newSettings)
  1367  		}
  1368  	}
  1369  	now := time.Now()
  1370  	handler := cache.ResourceEventHandlerFuncs{
  1371  		AddFunc: func(obj interface{}) {
  1372  			if metaObj, ok := obj.(metav1.Object); ok {
  1373  				if metaObj.GetCreationTimestamp().After(now) {
  1374  					tryNotify()
  1375  				}
  1376  			}
  1377  
  1378  		},
  1379  		UpdateFunc: func(oldObj, newObj interface{}) {
  1380  			oldMeta, oldOk := oldObj.(metav1.Common)
  1381  			newMeta, newOk := newObj.(metav1.Common)
  1382  			if oldOk && newOk && oldMeta.GetResourceVersion() != newMeta.GetResourceVersion() {
  1383  				tryNotify()
  1384  			}
  1385  		},
  1386  	}
  1387  	_, err = secretsInformer.AddEventHandler(handler)
  1388  	if err != nil {
  1389  		log.Error(err)
  1390  	}
  1391  	_, err = cmInformer.AddEventHandler(handler)
  1392  	if err != nil {
  1393  		log.Error(err)
  1394  	}
  1395  	mgr.secrets = v1listers.NewSecretLister(secretsInformer.GetIndexer())
  1396  	mgr.secretsInformer = secretsInformer
  1397  	mgr.configmaps = v1listers.NewConfigMapLister(cmInformer.GetIndexer())
  1398  	return nil
  1399  }
  1400  
  1401  func (mgr *SettingsManager) ensureSynced(forceResync bool) error {
  1402  	mgr.mutex.Lock()
  1403  	defer mgr.mutex.Unlock()
  1404  	if !forceResync && mgr.secrets != nil && mgr.configmaps != nil {
  1405  		return nil
  1406  	}
  1407  
  1408  	if mgr.initContextCancel != nil {
  1409  		mgr.initContextCancel()
  1410  	}
  1411  	ctx, cancel := context.WithCancel(mgr.ctx)
  1412  	mgr.initContextCancel = cancel
  1413  	return mgr.initialize(ctx)
  1414  }
  1415  
  1416  func getDownloadBinaryUrlsFromConfigMap(argoCDCM *apiv1.ConfigMap) map[string]string {
  1417  	binaryUrls := map[string]string{}
  1418  	for _, archType := range []string{"darwin-amd64", "darwin-arm64", "windows-amd64", "linux-amd64", "linux-arm64", "linux-ppc64le", "linux-s390x"} {
  1419  		if val, ok := argoCDCM.Data[settingsBinaryUrlsKey+"."+archType]; ok {
  1420  			binaryUrls[archType] = val
  1421  		}
  1422  	}
  1423  	return binaryUrls
  1424  }
  1425  
  1426  // updateSettingsFromConfigMap transfers settings from a Kubernetes configmap into an ArgoCDSettings struct.
  1427  func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.ConfigMap) {
  1428  	settings.DexConfig = argoCDCM.Data[settingDexConfigKey]
  1429  	settings.OIDCConfigRAW = argoCDCM.Data[settingsOIDCConfigKey]
  1430  	settings.KustomizeBuildOptions = argoCDCM.Data[kustomizeBuildOptionsKey]
  1431  	settings.StatusBadgeEnabled = argoCDCM.Data[statusBadgeEnabledKey] == "true"
  1432  	settings.StatusBadgeRootUrl = argoCDCM.Data[statusBadgeRootUrlKey]
  1433  	settings.AnonymousUserEnabled = argoCDCM.Data[anonymousUserEnabledKey] == "true"
  1434  	settings.UiCssURL = argoCDCM.Data[settingUiCssURLKey]
  1435  	settings.UiBannerContent = argoCDCM.Data[settingUiBannerContentKey]
  1436  	settings.UiBannerPermanent = argoCDCM.Data[settingUiBannerPermanentKey] == "true"
  1437  	settings.UiBannerPosition = argoCDCM.Data[settingUiBannerPositionKey]
  1438  	settings.ServerRBACLogEnforceEnable = argoCDCM.Data[settingsServerRBACLogEnforceEnableKey] == "true"
  1439  	settings.BinaryUrls = getDownloadBinaryUrlsFromConfigMap(argoCDCM)
  1440  	if err := validateExternalURL(argoCDCM.Data[settingURLKey]); err != nil {
  1441  		log.Warnf("Failed to validate URL in configmap: %v", err)
  1442  	}
  1443  	settings.URL = argoCDCM.Data[settingURLKey]
  1444  	if err := validateExternalURL(argoCDCM.Data[settingUiBannerURLKey]); err != nil {
  1445  		log.Warnf("Failed to validate UI banner URL in configmap: %v", err)
  1446  	}
  1447  	settings.UiBannerURL = argoCDCM.Data[settingUiBannerURLKey]
  1448  	settings.UserSessionDuration = time.Hour * 24
  1449  	if userSessionDurationStr, ok := argoCDCM.Data[userSessionDurationKey]; ok {
  1450  		if val, err := timeutil.ParseDuration(userSessionDurationStr); err != nil {
  1451  			log.Warnf("Failed to parse '%s' key: %v", userSessionDurationKey, err)
  1452  		} else {
  1453  			settings.UserSessionDuration = *val
  1454  		}
  1455  	}
  1456  	settings.PasswordPattern = argoCDCM.Data[settingsPasswordPatternKey]
  1457  	if settings.PasswordPattern == "" {
  1458  		settings.PasswordPattern = common.PasswordPatten
  1459  	}
  1460  	settings.InClusterEnabled = argoCDCM.Data[inClusterEnabledKey] != "false"
  1461  	settings.ExecEnabled = argoCDCM.Data[execEnabledKey] == "true"
  1462  	execShells := argoCDCM.Data[execShellsKey]
  1463  	if execShells != "" {
  1464  		settings.ExecShells = strings.Split(execShells, ",")
  1465  	} else {
  1466  		// Fall back to default. If you change this list, also change docs/operator-manual/argocd-cm.yaml.
  1467  		settings.ExecShells = []string{"bash", "sh", "powershell", "cmd"}
  1468  	}
  1469  	settings.TrackingMethod = argoCDCM.Data[settingsResourceTrackingMethodKey]
  1470  	settings.OIDCTLSInsecureSkipVerify = argoCDCM.Data[oidcTLSInsecureSkipVerifyKey] == "true"
  1471  	settings.ExtensionConfig = argoCDCM.Data[extensionConfig]
  1472  }
  1473  
  1474  // validateExternalURL ensures the external URL that is set on the configmap is valid
  1475  func validateExternalURL(u string) error {
  1476  	if u == "" {
  1477  		return nil
  1478  	}
  1479  	URL, err := url.Parse(u)
  1480  	if err != nil {
  1481  		return fmt.Errorf("Failed to parse URL: %v", err)
  1482  	}
  1483  	if URL.Scheme != "http" && URL.Scheme != "https" {
  1484  		return fmt.Errorf("URL must include http or https protocol")
  1485  	}
  1486  	return nil
  1487  }
  1488  
  1489  // updateSettingsFromSecret transfers settings from a Kubernetes secret into an ArgoCDSettings struct.
  1490  func (mgr *SettingsManager) updateSettingsFromSecret(settings *ArgoCDSettings, argoCDSecret *apiv1.Secret, secrets []*apiv1.Secret) error {
  1491  	var errs []error
  1492  	secretKey, ok := argoCDSecret.Data[settingServerSignatureKey]
  1493  	if ok {
  1494  		settings.ServerSignature = secretKey
  1495  	} else {
  1496  		errs = append(errs, &incompleteSettingsError{message: "server.secretkey is missing"})
  1497  	}
  1498  	if githubWebhookSecret := argoCDSecret.Data[settingsWebhookGitHubSecretKey]; len(githubWebhookSecret) > 0 {
  1499  		settings.WebhookGitHubSecret = string(githubWebhookSecret)
  1500  	}
  1501  	if gitlabWebhookSecret := argoCDSecret.Data[settingsWebhookGitLabSecretKey]; len(gitlabWebhookSecret) > 0 {
  1502  		settings.WebhookGitLabSecret = string(gitlabWebhookSecret)
  1503  	}
  1504  	if bitbucketWebhookUUID := argoCDSecret.Data[settingsWebhookBitbucketUUIDKey]; len(bitbucketWebhookUUID) > 0 {
  1505  		settings.WebhookBitbucketUUID = string(bitbucketWebhookUUID)
  1506  	}
  1507  	if bitbucketserverWebhookSecret := argoCDSecret.Data[settingsWebhookBitbucketServerSecretKey]; len(bitbucketserverWebhookSecret) > 0 {
  1508  		settings.WebhookBitbucketServerSecret = string(bitbucketserverWebhookSecret)
  1509  	}
  1510  	if gogsWebhookSecret := argoCDSecret.Data[settingsWebhookGogsSecretKey]; len(gogsWebhookSecret) > 0 {
  1511  		settings.WebhookGogsSecret = string(gogsWebhookSecret)
  1512  	}
  1513  	if azureDevOpsUsername := argoCDSecret.Data[settingsWebhookAzureDevOpsUsernameKey]; len(azureDevOpsUsername) > 0 {
  1514  		settings.WebhookAzureDevOpsUsername = string(azureDevOpsUsername)
  1515  	}
  1516  	if azureDevOpsPassword := argoCDSecret.Data[settingsWebhookAzureDevOpsPasswordKey]; len(azureDevOpsPassword) > 0 {
  1517  		settings.WebhookAzureDevOpsPassword = string(azureDevOpsPassword)
  1518  	}
  1519  
  1520  	// The TLS certificate may be externally managed. We try to load it from an
  1521  	// external secret first. If the external secret doesn't exist, we either
  1522  	// load it from argocd-secret or generate (and persist) a self-signed one.
  1523  	cert, err := mgr.externalServerTLSCertificate()
  1524  	if err != nil {
  1525  		errs = append(errs, &incompleteSettingsError{message: fmt.Sprintf("could not read from secret %s/%s: %v", mgr.namespace, externalServerTLSSecretName, err)})
  1526  	} else {
  1527  		if cert != nil {
  1528  			settings.Certificate = cert
  1529  			settings.CertificateIsExternal = true
  1530  			log.Infof("Loading TLS configuration from secret %s/%s", mgr.namespace, externalServerTLSSecretName)
  1531  		} else {
  1532  			serverCert, certOk := argoCDSecret.Data[settingServerCertificate]
  1533  			serverKey, keyOk := argoCDSecret.Data[settingServerPrivateKey]
  1534  			if certOk && keyOk {
  1535  				cert, err := tls.X509KeyPair(serverCert, serverKey)
  1536  				if err != nil {
  1537  					errs = append(errs, &incompleteSettingsError{message: fmt.Sprintf("invalid x509 key pair %s/%s in secret: %s", settingServerCertificate, settingServerPrivateKey, err)})
  1538  				} else {
  1539  					settings.Certificate = &cert
  1540  					settings.CertificateIsExternal = false
  1541  				}
  1542  			}
  1543  		}
  1544  	}
  1545  	secretValues := make(map[string]string, len(argoCDSecret.Data))
  1546  	for _, s := range secrets {
  1547  		for k, v := range s.Data {
  1548  			secretValues[fmt.Sprintf("%s:%s", s.Name, k)] = string(v)
  1549  		}
  1550  	}
  1551  	for k, v := range argoCDSecret.Data {
  1552  		secretValues[k] = string(v)
  1553  	}
  1554  	settings.Secrets = secretValues
  1555  	if len(errs) > 0 {
  1556  		return errs[0]
  1557  	}
  1558  	return nil
  1559  }
  1560  
  1561  // externalServerTLSCertificate will try and load a TLS certificate from an
  1562  // external secret, instead of tls.crt and tls.key in argocd-secret. If both
  1563  // return values are nil, no external secret has been configured.
  1564  func (mgr *SettingsManager) externalServerTLSCertificate() (*tls.Certificate, error) {
  1565  	var cert tls.Certificate
  1566  	secret, err := mgr.secrets.Secrets(mgr.namespace).Get(externalServerTLSSecretName)
  1567  	if err != nil {
  1568  		if apierr.IsNotFound(err) {
  1569  			return nil, nil
  1570  		}
  1571  	}
  1572  	tlsCert, certOK := secret.Data[settingServerCertificate]
  1573  	tlsKey, keyOK := secret.Data[settingServerPrivateKey]
  1574  	if certOK && keyOK {
  1575  		cert, err = tls.X509KeyPair(tlsCert, tlsKey)
  1576  		if err != nil {
  1577  			return nil, err
  1578  		}
  1579  	}
  1580  	return &cert, nil
  1581  }
  1582  
  1583  // SaveSettings serializes ArgoCDSettings and upserts it into K8s secret/configmap
  1584  func (mgr *SettingsManager) SaveSettings(settings *ArgoCDSettings) error {
  1585  	err := mgr.updateConfigMap(func(argoCDCM *apiv1.ConfigMap) error {
  1586  		if settings.URL != "" {
  1587  			argoCDCM.Data[settingURLKey] = settings.URL
  1588  		} else {
  1589  			delete(argoCDCM.Data, settingURLKey)
  1590  		}
  1591  		if settings.DexConfig != "" {
  1592  			argoCDCM.Data[settingDexConfigKey] = settings.DexConfig
  1593  		} else {
  1594  			delete(argoCDCM.Data, settings.DexConfig)
  1595  		}
  1596  		if settings.OIDCConfigRAW != "" {
  1597  			argoCDCM.Data[settingsOIDCConfigKey] = settings.OIDCConfigRAW
  1598  		} else {
  1599  			delete(argoCDCM.Data, settingsOIDCConfigKey)
  1600  		}
  1601  		if settings.UiCssURL != "" {
  1602  			argoCDCM.Data[settingUiCssURLKey] = settings.UiCssURL
  1603  		}
  1604  		if settings.UiBannerContent != "" {
  1605  			argoCDCM.Data[settingUiBannerContentKey] = settings.UiBannerContent
  1606  		} else {
  1607  			delete(argoCDCM.Data, settingUiBannerContentKey)
  1608  		}
  1609  		if settings.UiBannerURL != "" {
  1610  			argoCDCM.Data[settingUiBannerURLKey] = settings.UiBannerURL
  1611  		} else {
  1612  			delete(argoCDCM.Data, settingUiBannerURLKey)
  1613  		}
  1614  		return nil
  1615  	})
  1616  
  1617  	if err != nil {
  1618  		return err
  1619  	}
  1620  
  1621  	return mgr.updateSecret(func(argoCDSecret *apiv1.Secret) error {
  1622  		argoCDSecret.Data[settingServerSignatureKey] = settings.ServerSignature
  1623  		if settings.WebhookGitHubSecret != "" {
  1624  			argoCDSecret.Data[settingsWebhookGitHubSecretKey] = []byte(settings.WebhookGitHubSecret)
  1625  		}
  1626  		if settings.WebhookGitLabSecret != "" {
  1627  			argoCDSecret.Data[settingsWebhookGitLabSecretKey] = []byte(settings.WebhookGitLabSecret)
  1628  		}
  1629  		if settings.WebhookBitbucketUUID != "" {
  1630  			argoCDSecret.Data[settingsWebhookBitbucketUUIDKey] = []byte(settings.WebhookBitbucketUUID)
  1631  		}
  1632  		if settings.WebhookBitbucketServerSecret != "" {
  1633  			argoCDSecret.Data[settingsWebhookBitbucketServerSecretKey] = []byte(settings.WebhookBitbucketServerSecret)
  1634  		}
  1635  		if settings.WebhookGogsSecret != "" {
  1636  			argoCDSecret.Data[settingsWebhookGogsSecretKey] = []byte(settings.WebhookGogsSecret)
  1637  		}
  1638  		if settings.WebhookAzureDevOpsUsername != "" {
  1639  			argoCDSecret.Data[settingsWebhookAzureDevOpsUsernameKey] = []byte(settings.WebhookAzureDevOpsUsername)
  1640  		}
  1641  		if settings.WebhookAzureDevOpsPassword != "" {
  1642  			argoCDSecret.Data[settingsWebhookAzureDevOpsPasswordKey] = []byte(settings.WebhookAzureDevOpsPassword)
  1643  		}
  1644  		// we only write the certificate to the secret if it's not externally
  1645  		// managed.
  1646  		if settings.Certificate != nil && !settings.CertificateIsExternal {
  1647  			cert, key := tlsutil.EncodeX509KeyPair(*settings.Certificate)
  1648  			argoCDSecret.Data[settingServerCertificate] = cert
  1649  			argoCDSecret.Data[settingServerPrivateKey] = key
  1650  		} else {
  1651  			delete(argoCDSecret.Data, settingServerCertificate)
  1652  			delete(argoCDSecret.Data, settingServerPrivateKey)
  1653  		}
  1654  		return nil
  1655  	})
  1656  }
  1657  
  1658  // Save the SSH known host data into the corresponding ConfigMap
  1659  func (mgr *SettingsManager) SaveSSHKnownHostsData(ctx context.Context, knownHostsList []string) error {
  1660  	err := mgr.ensureSynced(false)
  1661  	if err != nil {
  1662  		return err
  1663  	}
  1664  
  1665  	certCM, err := mgr.GetConfigMapByName(common.ArgoCDKnownHostsConfigMapName)
  1666  	if err != nil {
  1667  		return err
  1668  	}
  1669  
  1670  	if certCM.Data == nil {
  1671  		certCM.Data = make(map[string]string)
  1672  	}
  1673  
  1674  	sshKnownHostsData := strings.Join(knownHostsList, "\n") + "\n"
  1675  	certCM.Data["ssh_known_hosts"] = sshKnownHostsData
  1676  	_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(ctx, certCM, metav1.UpdateOptions{})
  1677  	if err != nil {
  1678  		return err
  1679  	}
  1680  
  1681  	return mgr.ResyncInformers()
  1682  }
  1683  
  1684  func (mgr *SettingsManager) SaveTLSCertificateData(ctx context.Context, tlsCertificates map[string]string) error {
  1685  	err := mgr.ensureSynced(false)
  1686  	if err != nil {
  1687  		return err
  1688  	}
  1689  
  1690  	certCM, err := mgr.GetConfigMapByName(common.ArgoCDTLSCertsConfigMapName)
  1691  	if err != nil {
  1692  		return err
  1693  	}
  1694  
  1695  	certCM.Data = tlsCertificates
  1696  	_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(ctx, certCM, metav1.UpdateOptions{})
  1697  	if err != nil {
  1698  		return err
  1699  	}
  1700  
  1701  	return mgr.ResyncInformers()
  1702  }
  1703  
  1704  func (mgr *SettingsManager) SaveGPGPublicKeyData(ctx context.Context, gpgPublicKeys map[string]string) error {
  1705  	err := mgr.ensureSynced(false)
  1706  	if err != nil {
  1707  		return err
  1708  	}
  1709  
  1710  	keysCM, err := mgr.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName)
  1711  	if err != nil {
  1712  		return err
  1713  	}
  1714  
  1715  	keysCM.Data = gpgPublicKeys
  1716  	_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(ctx, keysCM, metav1.UpdateOptions{})
  1717  	if err != nil {
  1718  		return err
  1719  	}
  1720  
  1721  	return mgr.ResyncInformers()
  1722  
  1723  }
  1724  
  1725  type SettingsManagerOpts func(mgs *SettingsManager)
  1726  
  1727  func WithRepoOrClusterChangedHandler(handler func()) SettingsManagerOpts {
  1728  	return func(mgr *SettingsManager) {
  1729  		mgr.reposOrClusterChanged = handler
  1730  	}
  1731  }
  1732  
  1733  // NewSettingsManager generates a new SettingsManager pointer and returns it
  1734  func NewSettingsManager(ctx context.Context, clientset kubernetes.Interface, namespace string, opts ...SettingsManagerOpts) *SettingsManager {
  1735  
  1736  	mgr := &SettingsManager{
  1737  		ctx:       ctx,
  1738  		clientset: clientset,
  1739  		namespace: namespace,
  1740  		mutex:     &sync.Mutex{},
  1741  	}
  1742  	for i := range opts {
  1743  		opts[i](mgr)
  1744  	}
  1745  
  1746  	return mgr
  1747  }
  1748  
  1749  func (mgr *SettingsManager) ResyncInformers() error {
  1750  	return mgr.ensureSynced(true)
  1751  }
  1752  
  1753  // IsSSOConfigured returns whether or not single-sign-on is configured
  1754  func (a *ArgoCDSettings) IsSSOConfigured() bool {
  1755  	if a.IsDexConfigured() {
  1756  		return true
  1757  	}
  1758  	if a.OIDCConfig() != nil {
  1759  		return true
  1760  	}
  1761  	return false
  1762  }
  1763  
  1764  func (a *ArgoCDSettings) IsDexConfigured() bool {
  1765  	if a.URL == "" {
  1766  		return false
  1767  	}
  1768  	dexCfg, err := UnmarshalDexConfig(a.DexConfig)
  1769  	if err != nil {
  1770  		log.Warnf("invalid dex yaml config: %s", err.Error())
  1771  		return false
  1772  	}
  1773  	return len(dexCfg) > 0
  1774  }
  1775  
  1776  // GetServerEncryptionKey generates a new server encryption key using the server signature as a passphrase
  1777  func (a *ArgoCDSettings) GetServerEncryptionKey() ([]byte, error) {
  1778  	return crypto.KeyFromPassphrase(string(a.ServerSignature))
  1779  }
  1780  
  1781  func UnmarshalDexConfig(config string) (map[string]interface{}, error) {
  1782  	var dexCfg map[string]interface{}
  1783  	err := yaml.Unmarshal([]byte(config), &dexCfg)
  1784  	return dexCfg, err
  1785  }
  1786  
  1787  func (a *ArgoCDSettings) oidcConfig() *oidcConfig {
  1788  	if a.OIDCConfigRAW == "" {
  1789  		return nil
  1790  	}
  1791  	configMap := map[string]interface{}{}
  1792  	err := yaml.Unmarshal([]byte(a.OIDCConfigRAW), &configMap)
  1793  	if err != nil {
  1794  		log.Warnf("invalid oidc config: %v", err)
  1795  		return nil
  1796  	}
  1797  
  1798  	configMap = ReplaceMapSecrets(configMap, a.Secrets)
  1799  	data, err := yaml.Marshal(configMap)
  1800  	if err != nil {
  1801  		log.Warnf("invalid oidc config: %v", err)
  1802  		return nil
  1803  	}
  1804  
  1805  	config, err := unmarshalOIDCConfig(string(data))
  1806  	if err != nil {
  1807  		log.Warnf("invalid oidc config: %v", err)
  1808  		return nil
  1809  	}
  1810  
  1811  	return &config
  1812  }
  1813  
  1814  func (a *ArgoCDSettings) OIDCConfig() *OIDCConfig {
  1815  	config := a.oidcConfig()
  1816  	if config == nil {
  1817  		return nil
  1818  	}
  1819  	return config.toExported()
  1820  }
  1821  
  1822  func unmarshalOIDCConfig(configStr string) (oidcConfig, error) {
  1823  	var config oidcConfig
  1824  	err := yaml.Unmarshal([]byte(configStr), &config)
  1825  	return config, err
  1826  }
  1827  
  1828  func ValidateOIDCConfig(configStr string) error {
  1829  	_, err := unmarshalOIDCConfig(configStr)
  1830  	return err
  1831  }
  1832  
  1833  // TLSConfig returns a tls.Config with the configured certificates
  1834  func (a *ArgoCDSettings) TLSConfig() *tls.Config {
  1835  	if a.Certificate == nil {
  1836  		return nil
  1837  	}
  1838  	certPool := x509.NewCertPool()
  1839  	pemCertBytes, _ := tlsutil.EncodeX509KeyPair(*a.Certificate)
  1840  	ok := certPool.AppendCertsFromPEM(pemCertBytes)
  1841  	if !ok {
  1842  		panic("bad certs")
  1843  	}
  1844  	return &tls.Config{
  1845  		RootCAs: certPool,
  1846  	}
  1847  }
  1848  
  1849  func (a *ArgoCDSettings) IssuerURL() string {
  1850  	if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
  1851  		return oidcConfig.Issuer
  1852  	}
  1853  	if a.DexConfig != "" {
  1854  		return a.URL + common.DexAPIEndpoint
  1855  	}
  1856  	return ""
  1857  }
  1858  
  1859  // UserInfoGroupsEnabled returns whether group claims should be fetch from UserInfo endpoint
  1860  func (a *ArgoCDSettings) UserInfoGroupsEnabled() bool {
  1861  	if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
  1862  		return oidcConfig.EnableUserInfoGroups
  1863  	}
  1864  	return false
  1865  }
  1866  
  1867  // UserInfoPath returns the sub-path on which the IDP exposes the UserInfo endpoint
  1868  func (a *ArgoCDSettings) UserInfoPath() string {
  1869  	if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
  1870  		return oidcConfig.UserInfoPath
  1871  	}
  1872  	return ""
  1873  }
  1874  
  1875  // UserInfoCacheExpiration returns the expiry time of the UserInfo cache
  1876  func (a *ArgoCDSettings) UserInfoCacheExpiration() time.Duration {
  1877  	if oidcConfig := a.OIDCConfig(); oidcConfig != nil && oidcConfig.UserInfoCacheExpiration != "" {
  1878  		userInfoCacheExpiration, err := time.ParseDuration(oidcConfig.UserInfoCacheExpiration)
  1879  		if err != nil {
  1880  			log.Warnf("Failed to parse 'oidc.config.userInfoCacheExpiration' key: %v", err)
  1881  		}
  1882  		return userInfoCacheExpiration
  1883  	}
  1884  	return 0
  1885  }
  1886  
  1887  func (a *ArgoCDSettings) OAuth2ClientID() string {
  1888  	if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
  1889  		return oidcConfig.ClientID
  1890  	}
  1891  	if a.DexConfig != "" {
  1892  		return common.ArgoCDClientAppID
  1893  	}
  1894  	return ""
  1895  }
  1896  
  1897  // OAuth2AllowedAudiences returns a list of audiences that are allowed for the OAuth2 client. If the user has not
  1898  // explicitly configured the list of audiences (or has configured an empty list), then the OAuth2 client ID is returned
  1899  // as the only allowed audience. When using the bundled Dex, that client ID is always "argo-cd".
  1900  func (a *ArgoCDSettings) OAuth2AllowedAudiences() []string {
  1901  	if config := a.oidcConfig(); config != nil {
  1902  		if len(config.AllowedAudiences) == 0 {
  1903  			allowedAudiences := []string{config.ClientID}
  1904  			if config.CLIClientID != "" {
  1905  				allowedAudiences = append(allowedAudiences, config.CLIClientID)
  1906  			}
  1907  			return allowedAudiences
  1908  		}
  1909  		return config.AllowedAudiences
  1910  	}
  1911  	if a.DexConfig != "" {
  1912  		return []string{common.ArgoCDClientAppID, common.ArgoCDCLIClientAppID}
  1913  	}
  1914  	return nil
  1915  }
  1916  
  1917  func (a *ArgoCDSettings) SkipAudienceCheckWhenTokenHasNoAudience() bool {
  1918  	if config := a.oidcConfig(); config != nil {
  1919  		if config.SkipAudienceCheckWhenTokenHasNoAudience != nil {
  1920  			return *config.SkipAudienceCheckWhenTokenHasNoAudience
  1921  		}
  1922  		return false
  1923  	}
  1924  	// When using the bundled Dex, the audience check is required. Dex will always send JWTs with an audience.
  1925  	return false
  1926  }
  1927  
  1928  func (a *ArgoCDSettings) OAuth2ClientSecret() string {
  1929  	if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
  1930  		return oidcConfig.ClientSecret
  1931  	}
  1932  	if a.DexConfig != "" {
  1933  		return a.DexOAuth2ClientSecret()
  1934  	}
  1935  	return ""
  1936  }
  1937  
  1938  // OIDCTLSConfig returns the TLS config for the OIDC provider. If an external provider is configured, returns a TLS
  1939  // config using the root CAs (if any) specified in the OIDC config. If an external OIDC provider is not configured,
  1940  // returns the API server TLS config, because the API server proxies requests to Dex.
  1941  func (a *ArgoCDSettings) OIDCTLSConfig() *tls.Config {
  1942  	var tlsConfig *tls.Config
  1943  
  1944  	oidcConfig := a.OIDCConfig()
  1945  	if oidcConfig != nil {
  1946  		tlsConfig = &tls.Config{}
  1947  		if oidcConfig.RootCA != "" {
  1948  			certPool := x509.NewCertPool()
  1949  			ok := certPool.AppendCertsFromPEM([]byte(oidcConfig.RootCA))
  1950  			if !ok {
  1951  				log.Warn("failed to append certificates from PEM: proceeding without custom rootCA")
  1952  			} else {
  1953  				tlsConfig.RootCAs = certPool
  1954  			}
  1955  		}
  1956  	} else {
  1957  		tlsConfig = a.TLSConfig()
  1958  	}
  1959  	if tlsConfig != nil && a.OIDCTLSInsecureSkipVerify {
  1960  		tlsConfig.InsecureSkipVerify = true
  1961  	}
  1962  	return tlsConfig
  1963  }
  1964  
  1965  func appendURLPath(inputURL string, inputPath string) (string, error) {
  1966  	u, err := url.Parse(inputURL)
  1967  	if err != nil {
  1968  		return "", err
  1969  	}
  1970  	u.Path = path.Join(u.Path, inputPath)
  1971  	return u.String(), nil
  1972  }
  1973  
  1974  func (a *ArgoCDSettings) RedirectURL() (string, error) {
  1975  	return appendURLPath(a.URL, common.CallbackEndpoint)
  1976  }
  1977  
  1978  func (a *ArgoCDSettings) DexRedirectURL() (string, error) {
  1979  	return appendURLPath(a.URL, common.DexCallbackEndpoint)
  1980  }
  1981  
  1982  // DexOAuth2ClientSecret calculates an arbitrary, but predictable OAuth2 client secret string derived
  1983  // from the server secret. This is called by the dex startup wrapper (argocd-dex rundex), as well
  1984  // as the API server, such that they both independently come to the same conclusion of what the
  1985  // OAuth2 shared client secret should be.
  1986  func (a *ArgoCDSettings) DexOAuth2ClientSecret() string {
  1987  	h := sha256.New()
  1988  	_, err := h.Write(a.ServerSignature)
  1989  	if err != nil {
  1990  		panic(err)
  1991  	}
  1992  	sha := h.Sum(nil)
  1993  	return base64.URLEncoding.EncodeToString(sha)[:40]
  1994  }
  1995  
  1996  // Subscribe registers a channel in which to subscribe to settings updates
  1997  func (mgr *SettingsManager) Subscribe(subCh chan<- *ArgoCDSettings) {
  1998  	mgr.mutex.Lock()
  1999  	defer mgr.mutex.Unlock()
  2000  	mgr.subscribers = append(mgr.subscribers, subCh)
  2001  	log.Infof("%v subscribed to settings updates", subCh)
  2002  }
  2003  
  2004  // Unsubscribe unregisters a channel from receiving of settings updates
  2005  func (mgr *SettingsManager) Unsubscribe(subCh chan<- *ArgoCDSettings) {
  2006  	mgr.mutex.Lock()
  2007  	defer mgr.mutex.Unlock()
  2008  	for i, ch := range mgr.subscribers {
  2009  		if ch == subCh {
  2010  			mgr.subscribers = append(mgr.subscribers[:i], mgr.subscribers[i+1:]...)
  2011  			log.Infof("%v unsubscribed from settings updates", subCh)
  2012  			return
  2013  		}
  2014  	}
  2015  }
  2016  
  2017  func (mgr *SettingsManager) notifySubscribers(newSettings *ArgoCDSettings) {
  2018  	mgr.mutex.Lock()
  2019  	defer mgr.mutex.Unlock()
  2020  	if len(mgr.subscribers) > 0 {
  2021  		subscribers := make([]chan<- *ArgoCDSettings, len(mgr.subscribers))
  2022  		copy(subscribers, mgr.subscribers)
  2023  		// make sure subscribes are notified in a separate thread to avoid potential deadlock
  2024  		go func() {
  2025  			log.Infof("Notifying %d settings subscribers: %v", len(subscribers), subscribers)
  2026  			for _, sub := range subscribers {
  2027  				sub <- newSettings
  2028  			}
  2029  		}()
  2030  	}
  2031  }
  2032  
  2033  func isIncompleteSettingsError(err error) bool {
  2034  	_, ok := err.(*incompleteSettingsError)
  2035  	return ok
  2036  }
  2037  
  2038  // InitializeSettings is used to initialize empty admin password, signature, certificate etc if missing
  2039  func (mgr *SettingsManager) InitializeSettings(insecureModeEnabled bool) (*ArgoCDSettings, error) {
  2040  	const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
  2041  
  2042  	cdSettings, err := mgr.GetSettings()
  2043  	if err != nil && !isIncompleteSettingsError(err) {
  2044  		return nil, err
  2045  	}
  2046  	if cdSettings == nil {
  2047  		cdSettings = &ArgoCDSettings{}
  2048  	}
  2049  	if cdSettings.ServerSignature == nil {
  2050  		// set JWT signature
  2051  		signature, err := util.MakeSignature(32)
  2052  		if err != nil {
  2053  			return nil, fmt.Errorf("error setting JWT signature: %w", err)
  2054  		}
  2055  		cdSettings.ServerSignature = signature
  2056  		log.Info("Initialized server signature")
  2057  	}
  2058  	err = mgr.UpdateAccount(common.ArgoCDAdminUsername, func(adminAccount *Account) error {
  2059  		if adminAccount.Enabled {
  2060  			now := time.Now().UTC()
  2061  			if adminAccount.PasswordHash == "" {
  2062  				randBytes := make([]byte, initialPasswordLength)
  2063  				for i := 0; i < initialPasswordLength; i++ {
  2064  					num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
  2065  					if err != nil {
  2066  						return err
  2067  					}
  2068  					randBytes[i] = letters[num.Int64()]
  2069  				}
  2070  				initialPassword := string(randBytes)
  2071  
  2072  				hashedPassword, err := password.HashPassword(initialPassword)
  2073  				if err != nil {
  2074  					return err
  2075  				}
  2076  				ku := kube.NewKubeUtil(mgr.clientset, mgr.ctx)
  2077  				err = ku.CreateOrUpdateSecretField(mgr.namespace, initialPasswordSecretName, initialPasswordSecretField, initialPassword)
  2078  				if err != nil {
  2079  					return err
  2080  				}
  2081  				adminAccount.PasswordHash = hashedPassword
  2082  				adminAccount.PasswordMtime = &now
  2083  				log.Info("Initialized admin password")
  2084  			}
  2085  			if adminAccount.PasswordMtime == nil || adminAccount.PasswordMtime.IsZero() {
  2086  				adminAccount.PasswordMtime = &now
  2087  				log.Info("Initialized admin mtime")
  2088  			}
  2089  		} else {
  2090  			log.Info("admin disabled")
  2091  		}
  2092  		return nil
  2093  	})
  2094  	if err != nil {
  2095  		return nil, err
  2096  	}
  2097  
  2098  	if cdSettings.Certificate == nil && !insecureModeEnabled {
  2099  		// generate TLS cert
  2100  		hosts := []string{
  2101  			"localhost",
  2102  			"argocd-server",
  2103  			fmt.Sprintf("argocd-server.%s", mgr.namespace),
  2104  			fmt.Sprintf("argocd-server.%s.svc", mgr.namespace),
  2105  			fmt.Sprintf("argocd-server.%s.svc.cluster.local", mgr.namespace),
  2106  		}
  2107  		certOpts := tlsutil.CertOptions{
  2108  			Hosts:        hosts,
  2109  			Organization: "Argo CD",
  2110  			IsCA:         false,
  2111  		}
  2112  		cert, err := tlsutil.GenerateX509KeyPair(certOpts)
  2113  		if err != nil {
  2114  			return nil, err
  2115  		}
  2116  		cdSettings.Certificate = cert
  2117  		log.Info("Initialized TLS certificate")
  2118  	}
  2119  
  2120  	err = mgr.SaveSettings(cdSettings)
  2121  	if apierrors.IsConflict(err) {
  2122  		// assume settings are initialized by another instance of api server
  2123  		log.Warnf("conflict when initializing settings. assuming updated by another replica")
  2124  		return mgr.GetSettings()
  2125  	}
  2126  	return cdSettings, nil
  2127  }
  2128  
  2129  // ReplaceMapSecrets takes a json object and recursively looks for any secret key references in the
  2130  // object and replaces the value with the secret value
  2131  func ReplaceMapSecrets(obj map[string]interface{}, secretValues map[string]string) map[string]interface{} {
  2132  	newObj := make(map[string]interface{})
  2133  	for k, v := range obj {
  2134  		switch val := v.(type) {
  2135  		case map[string]interface{}:
  2136  			newObj[k] = ReplaceMapSecrets(val, secretValues)
  2137  		case []interface{}:
  2138  			newObj[k] = replaceListSecrets(val, secretValues)
  2139  		case string:
  2140  			newObj[k] = ReplaceStringSecret(val, secretValues)
  2141  		default:
  2142  			newObj[k] = val
  2143  		}
  2144  	}
  2145  	return newObj
  2146  }
  2147  
  2148  func replaceListSecrets(obj []interface{}, secretValues map[string]string) []interface{} {
  2149  	newObj := make([]interface{}, len(obj))
  2150  	for i, v := range obj {
  2151  		switch val := v.(type) {
  2152  		case map[string]interface{}:
  2153  			newObj[i] = ReplaceMapSecrets(val, secretValues)
  2154  		case []interface{}:
  2155  			newObj[i] = replaceListSecrets(val, secretValues)
  2156  		case string:
  2157  			newObj[i] = ReplaceStringSecret(val, secretValues)
  2158  		default:
  2159  			newObj[i] = val
  2160  		}
  2161  	}
  2162  	return newObj
  2163  }
  2164  
  2165  // ReplaceStringSecret checks if given string is a secret key reference ( starts with $ ) and returns corresponding value from provided map
  2166  func ReplaceStringSecret(val string, secretValues map[string]string) string {
  2167  	if val == "" || !strings.HasPrefix(val, "$") {
  2168  		return val
  2169  	}
  2170  	secretKey := val[1:]
  2171  	secretVal, ok := secretValues[secretKey]
  2172  	if !ok {
  2173  		log.Warnf("config referenced '%s', but key does not exist in secret", val)
  2174  		return val
  2175  	}
  2176  	return strings.TrimSpace(secretVal)
  2177  }
  2178  
  2179  // GetGlobalProjectsSettings loads the global project settings from argocd-cm ConfigMap
  2180  func (mgr *SettingsManager) GetGlobalProjectsSettings() ([]GlobalProjectSettings, error) {
  2181  	argoCDCM, err := mgr.getConfigMap()
  2182  	if err != nil {
  2183  		return nil, fmt.Errorf("error retrieving argocd-cm: %w", err)
  2184  	}
  2185  	globalProjectSettings := make([]GlobalProjectSettings, 0)
  2186  	if value, ok := argoCDCM.Data[globalProjectsKey]; ok {
  2187  		if value != "" {
  2188  			err := yaml.Unmarshal([]byte(value), &globalProjectSettings)
  2189  			if err != nil {
  2190  				return nil, fmt.Errorf("error unmarshalling global project settings: %w", err)
  2191  			}
  2192  		}
  2193  	}
  2194  	return globalProjectSettings, nil
  2195  }
  2196  
  2197  func (mgr *SettingsManager) GetNamespace() string {
  2198  	return mgr.namespace
  2199  }
  2200  
  2201  func (mgr *SettingsManager) GetResourceCustomLabels() ([]string, error) {
  2202  	argoCDCM, err := mgr.getConfigMap()
  2203  	if err != nil {
  2204  		return []string{}, fmt.Errorf("failed getting configmap: %v", err)
  2205  	}
  2206  	labels := argoCDCM.Data[resourceCustomLabelsKey]
  2207  	if labels != "" {
  2208  		return strings.Split(labels, ","), nil
  2209  	}
  2210  	return []string{}, nil
  2211  }