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