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

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	log "github.com/sirupsen/logrus"
     9  	"google.golang.org/grpc/codes"
    10  	"google.golang.org/grpc/status"
    11  	apiv1 "k8s.io/api/core/v1"
    12  	apierr "k8s.io/apimachinery/pkg/api/errors"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  
    15  	"github.com/argoproj/argo-cd/v2/common"
    16  	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    17  	"github.com/argoproj/argo-cd/v2/util/errors"
    18  	"github.com/argoproj/argo-cd/v2/util/git"
    19  	"github.com/argoproj/argo-cd/v2/util/settings"
    20  )
    21  
    22  var _ repositoryBackend = &legacyRepositoryBackend{}
    23  
    24  // legacyRepositoryBackend is a repository backend strategy that maintains backward compatibility with previous versions.
    25  // This can be removed in a future version, once the old "argocd-cm" storage for repositories is removed.
    26  type legacyRepositoryBackend struct {
    27  	db *db
    28  }
    29  
    30  func (l *legacyRepositoryBackend) CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) {
    31  	// This strategy only kept to preserve backward compatibility, but is deprecated.
    32  	// Therefore no new repositories can be added with this backend.
    33  	panic("creating new repositories is not supported for the legacy repository backend")
    34  }
    35  
    36  func (l *legacyRepositoryBackend) GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) {
    37  	repository, err := l.tryGetRepository(repoURL)
    38  	if err != nil {
    39  		return nil, fmt.Errorf("unable to get repository: %w", err)
    40  	}
    41  	return repository, nil
    42  }
    43  
    44  func (l *legacyRepositoryBackend) ListRepositories(ctx context.Context, repoType *string) ([]*appsv1.Repository, error) {
    45  	inRepos, err := l.db.settingsMgr.GetRepositories()
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	var repos []*appsv1.Repository
    51  	for _, inRepo := range inRepos {
    52  		if repoType == nil || *repoType == inRepo.Type {
    53  			r, err := l.tryGetRepository(inRepo.URL)
    54  			if err != nil {
    55  				if r != nil && errors.IsCredentialsConfigurationError(err) {
    56  					modifiedTime := metav1.Now()
    57  					r.ConnectionState = appsv1.ConnectionState{
    58  						Status:     appsv1.ConnectionStatusFailed,
    59  						Message:    "Configuration error - please check the server logs",
    60  						ModifiedAt: &modifiedTime,
    61  					}
    62  
    63  					log.Warnf("could not retrieve repo: %s", err.Error())
    64  				} else {
    65  					return nil, err
    66  				}
    67  			}
    68  			repos = append(repos, r)
    69  		}
    70  	}
    71  	return repos, nil
    72  }
    73  
    74  func (l *legacyRepositoryBackend) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) {
    75  	repos, err := l.db.settingsMgr.GetRepositories()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	index := l.getRepositoryIndex(repos, r.Repo)
    81  	if index < 0 {
    82  		return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo)
    83  	}
    84  
    85  	repoInfo := repos[index]
    86  	err = l.updateRepositorySecrets(&repoInfo, r)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	// Update boolean settings
    92  	repoInfo.InsecureIgnoreHostKey = r.IsInsecure()
    93  	repoInfo.Insecure = r.IsInsecure()
    94  	repoInfo.EnableLFS = r.EnableLFS
    95  	repoInfo.Proxy = r.Proxy
    96  
    97  	repos[index] = repoInfo
    98  	err = l.db.settingsMgr.SaveRepositories(repos)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return r, nil
   103  }
   104  
   105  func (l *legacyRepositoryBackend) DeleteRepository(ctx context.Context, repoURL string) error {
   106  	repos, err := l.db.settingsMgr.GetRepositories()
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	index := l.getRepositoryIndex(repos, repoURL)
   112  	if index < 0 {
   113  		return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
   114  	}
   115  	err = l.updateRepositorySecrets(&repos[index], &appsv1.Repository{
   116  		SSHPrivateKey:       "",
   117  		Password:            "",
   118  		Username:            "",
   119  		TLSClientCertData:   "",
   120  		TLSClientCertKey:    "",
   121  		GithubAppPrivateKey: "",
   122  	})
   123  	if err != nil {
   124  		return err
   125  	}
   126  	repos = append(repos[:index], repos[index+1:]...)
   127  	return l.db.settingsMgr.SaveRepositories(repos)
   128  }
   129  
   130  func (l *legacyRepositoryBackend) RepositoryExists(ctx context.Context, repoURL string) (bool, error) {
   131  	repos, err := l.db.settingsMgr.GetRepositories()
   132  	if err != nil {
   133  		return false, fmt.Errorf("unable to get repositories: %w", err)
   134  	}
   135  
   136  	index := l.getRepositoryIndex(repos, repoURL)
   137  	return index >= 0, nil
   138  }
   139  
   140  func (l *legacyRepositoryBackend) CreateRepoCreds(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) {
   141  	// This strategy only kept to preserve backward compatibility, but is deprecated.
   142  	// Therefore no new repositories can be added with this backend.
   143  	panic("creating new repository credentials is not supported for the legacy repository backend")
   144  }
   145  
   146  func (l *legacyRepositoryBackend) GetRepoCreds(ctx context.Context, repoURL string) (*appsv1.RepoCreds, error) {
   147  	var credential *appsv1.RepoCreds
   148  
   149  	repoCredentials, err := l.db.settingsMgr.GetRepositoryCredentials()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	index := getRepositoryCredentialIndex(repoCredentials, repoURL)
   154  	if index >= 0 {
   155  		credential, err = l.credentialsToRepositoryCredentials(repoCredentials[index])
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  	}
   160  
   161  	return credential, err
   162  }
   163  
   164  func (l *legacyRepositoryBackend) ListRepoCreds(ctx context.Context) ([]string, error) {
   165  	repos, err := l.db.settingsMgr.GetRepositoryCredentials()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	urls := make([]string, len(repos))
   171  	for i := range repos {
   172  		urls[i] = repos[i].URL
   173  	}
   174  
   175  	return urls, nil
   176  }
   177  
   178  func (l *legacyRepositoryBackend) UpdateRepoCreds(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) {
   179  	repos, err := l.db.settingsMgr.GetRepositoryCredentials()
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	index := getRepositoryCredentialIndex(repos, r.URL)
   185  	if index < 0 {
   186  		return nil, status.Errorf(codes.NotFound, "repository credentials '%s' not found", r.URL)
   187  	}
   188  
   189  	repoInfo := repos[index]
   190  	err = l.updateCredentialsSecret(&repoInfo, r)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	repos[index] = repoInfo
   196  	err = l.db.settingsMgr.SaveRepositoryCredentials(repos)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	return r, nil
   201  }
   202  
   203  func (l *legacyRepositoryBackend) DeleteRepoCreds(ctx context.Context, name string) error {
   204  	repos, err := l.db.settingsMgr.GetRepositoryCredentials()
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	index := getRepositoryCredentialIndex(repos, name)
   210  	if index < 0 {
   211  		return status.Errorf(codes.NotFound, "repository credentials '%s' not found", name)
   212  	}
   213  	err = l.updateCredentialsSecret(&repos[index], &appsv1.RepoCreds{
   214  		SSHPrivateKey:       "",
   215  		Password:            "",
   216  		Username:            "",
   217  		TLSClientCertData:   "",
   218  		TLSClientCertKey:    "",
   219  		GithubAppPrivateKey: "",
   220  	})
   221  	if err != nil {
   222  		return err
   223  	}
   224  	repos = append(repos[:index], repos[index+1:]...)
   225  	return l.db.settingsMgr.SaveRepositoryCredentials(repos)
   226  }
   227  
   228  func (l *legacyRepositoryBackend) RepoCredsExists(ctx context.Context, repoURL string) (bool, error) {
   229  	creds, err := l.db.settingsMgr.GetRepositoryCredentials()
   230  	if err != nil {
   231  		return false, err
   232  	}
   233  
   234  	index := getRepositoryCredentialIndex(creds, repoURL)
   235  	return index >= 0, nil
   236  }
   237  
   238  func (l *legacyRepositoryBackend) GetAllHelmRepoCreds(ctx context.Context) ([]*appsv1.RepoCreds, error) {
   239  	var allCredentials []*appsv1.RepoCreds
   240  	repoCredentials, err := l.db.settingsMgr.GetRepositoryCredentials()
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	for _, v := range repoCredentials {
   245  		if strings.EqualFold(v.Type, "helm") {
   246  			credential, err := l.credentialsToRepositoryCredentials(v)
   247  			if err != nil {
   248  				return nil, err
   249  			}
   250  			allCredentials = append(allCredentials, credential)
   251  		}
   252  	}
   253  	return allCredentials, err
   254  }
   255  
   256  func (l *legacyRepositoryBackend) updateRepositorySecrets(repoInfo *settings.Repository, r *appsv1.Repository) error {
   257  	secretsData := make(map[string]map[string][]byte)
   258  
   259  	repoInfo.UsernameSecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.UsernameSecret, r.Username, username)
   260  	repoInfo.PasswordSecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.PasswordSecret, r.Password, password)
   261  	repoInfo.SSHPrivateKeySecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.SSHPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey)
   262  	repoInfo.TLSClientCertDataSecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.TLSClientCertDataSecret, r.TLSClientCertData, tlsClientCertData)
   263  	repoInfo.TLSClientCertKeySecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.TLSClientCertKeySecret, r.TLSClientCertKey, tlsClientCertKey)
   264  	repoInfo.GithubAppPrivateKeySecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.GithubAppPrivateKeySecret, r.GithubAppPrivateKey, githubAppPrivateKey)
   265  	repoInfo.GCPServiceAccountKey = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.GCPServiceAccountKey, r.GCPServiceAccountKey, gcpServiceAccountKey)
   266  	for k, v := range secretsData {
   267  		err := l.upsertSecret(k, v)
   268  		if err != nil {
   269  			return err
   270  		}
   271  	}
   272  	return nil
   273  }
   274  
   275  func (l *legacyRepositoryBackend) updateCredentialsSecret(credsInfo *settings.RepositoryCredentials, c *appsv1.RepoCreds) error {
   276  	r := &appsv1.Repository{
   277  		Repo:                       c.URL,
   278  		Username:                   c.Username,
   279  		Password:                   c.Password,
   280  		SSHPrivateKey:              c.SSHPrivateKey,
   281  		TLSClientCertData:          c.TLSClientCertData,
   282  		TLSClientCertKey:           c.TLSClientCertKey,
   283  		GithubAppPrivateKey:        c.GithubAppPrivateKey,
   284  		GithubAppId:                c.GithubAppId,
   285  		GithubAppInstallationId:    c.GithubAppInstallationId,
   286  		GitHubAppEnterpriseBaseURL: c.GitHubAppEnterpriseBaseURL,
   287  		GCPServiceAccountKey:       c.GCPServiceAccountKey,
   288  	}
   289  	secretsData := make(map[string]map[string][]byte)
   290  
   291  	credsInfo.UsernameSecret = l.setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.UsernameSecret, r.Username, username)
   292  	credsInfo.PasswordSecret = l.setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.PasswordSecret, r.Password, password)
   293  	credsInfo.SSHPrivateKeySecret = l.setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.SSHPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey)
   294  	credsInfo.TLSClientCertDataSecret = l.setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.TLSClientCertDataSecret, r.TLSClientCertData, tlsClientCertData)
   295  	credsInfo.TLSClientCertKeySecret = l.setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.TLSClientCertKeySecret, r.TLSClientCertKey, tlsClientCertKey)
   296  	credsInfo.GithubAppPrivateKeySecret = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, credsInfo.GithubAppPrivateKeySecret, r.GithubAppPrivateKey, githubAppPrivateKey)
   297  	credsInfo.GCPServiceAccountKey = l.setSecretData(repoSecretPrefix, r.Repo, secretsData, credsInfo.GCPServiceAccountKey, r.GCPServiceAccountKey, gcpServiceAccountKey)
   298  	for k, v := range secretsData {
   299  		err := l.upsertSecret(k, v)
   300  		if err != nil {
   301  			return err
   302  		}
   303  	}
   304  	return nil
   305  }
   306  
   307  func (l *legacyRepositoryBackend) upsertSecret(name string, data map[string][]byte) error {
   308  	secret, err := l.db.kubeclientset.CoreV1().Secrets(l.db.ns).Get(context.Background(), name, metav1.GetOptions{})
   309  	if err != nil {
   310  		if apierr.IsNotFound(err) {
   311  			if len(data) == 0 {
   312  				return nil
   313  			}
   314  			_, err = l.db.kubeclientset.CoreV1().Secrets(l.db.ns).Create(context.Background(), &apiv1.Secret{
   315  				ObjectMeta: metav1.ObjectMeta{
   316  					Name: name,
   317  					Annotations: map[string]string{
   318  						common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD,
   319  					},
   320  				},
   321  				Data: data,
   322  			}, metav1.CreateOptions{})
   323  			if err != nil {
   324  				return err
   325  			}
   326  		}
   327  	} else {
   328  		for _, key := range []string{username, password, sshPrivateKey, tlsClientCertData, tlsClientCertKey, githubAppPrivateKey} {
   329  			if secret.Data == nil {
   330  				secret.Data = make(map[string][]byte)
   331  			}
   332  			if val, ok := data[key]; ok && len(val) > 0 {
   333  				secret.Data[key] = val
   334  			} else {
   335  				delete(secret.Data, key)
   336  			}
   337  		}
   338  		if len(secret.Data) == 0 {
   339  			isManagedByArgo := secret.Annotations != nil && secret.Annotations[common.AnnotationKeyManagedBy] == common.AnnotationValueManagedByArgoCD
   340  			if isManagedByArgo {
   341  				return l.db.kubeclientset.CoreV1().Secrets(l.db.ns).Delete(context.Background(), name, metav1.DeleteOptions{})
   342  			}
   343  			return nil
   344  		} else {
   345  			_, err = l.db.kubeclientset.CoreV1().Secrets(l.db.ns).Update(context.Background(), secret, metav1.UpdateOptions{})
   346  			if err != nil {
   347  				return err
   348  			}
   349  		}
   350  	}
   351  	return nil
   352  }
   353  
   354  // tryGetRepository returns a repository by URL.
   355  // It provides the same functionality as GetRepository, with the additional behaviour of still returning a repository,
   356  // even if an error occurred during the resolving of credentials for the repository. Otherwise this function behaves
   357  // just as one would expect.
   358  func (l *legacyRepositoryBackend) tryGetRepository(repoURL string) (*appsv1.Repository, error) {
   359  	repos, err := l.db.settingsMgr.GetRepositories()
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  
   364  	repo := &appsv1.Repository{Repo: repoURL}
   365  	index := l.getRepositoryIndex(repos, repoURL)
   366  	if index >= 0 {
   367  		repo, err = l.credentialsToRepository(repos[index])
   368  		if err != nil {
   369  			return repo, errors.NewCredentialsConfigurationError(err)
   370  		}
   371  	}
   372  
   373  	return repo, err
   374  }
   375  
   376  func (l *legacyRepositoryBackend) credentialsToRepository(repoInfo settings.Repository) (*appsv1.Repository, error) {
   377  	repo := &appsv1.Repository{
   378  		Repo:                       repoInfo.URL,
   379  		Type:                       repoInfo.Type,
   380  		Name:                       repoInfo.Name,
   381  		InsecureIgnoreHostKey:      repoInfo.InsecureIgnoreHostKey,
   382  		Insecure:                   repoInfo.Insecure,
   383  		EnableLFS:                  repoInfo.EnableLFS,
   384  		EnableOCI:                  repoInfo.EnableOci,
   385  		GithubAppId:                repoInfo.GithubAppId,
   386  		GithubAppInstallationId:    repoInfo.GithubAppInstallationId,
   387  		GitHubAppEnterpriseBaseURL: repoInfo.GithubAppEnterpriseBaseURL,
   388  		Proxy:                      repoInfo.Proxy,
   389  	}
   390  	err := l.db.unmarshalFromSecretsStr(map[*SecretMaperValidation]*apiv1.SecretKeySelector{
   391  		{Dest: &repo.Username, Transform: StripCRLFCharacter}:             repoInfo.UsernameSecret,
   392  		{Dest: &repo.Password, Transform: StripCRLFCharacter}:             repoInfo.PasswordSecret,
   393  		{Dest: &repo.SSHPrivateKey, Transform: StripCRLFCharacter}:        repoInfo.SSHPrivateKeySecret,
   394  		{Dest: &repo.TLSClientCertData, Transform: StripCRLFCharacter}:    repoInfo.TLSClientCertDataSecret,
   395  		{Dest: &repo.TLSClientCertKey, Transform: StripCRLFCharacter}:     repoInfo.TLSClientCertKeySecret,
   396  		{Dest: &repo.GithubAppPrivateKey, Transform: StripCRLFCharacter}:  repoInfo.GithubAppPrivateKeySecret,
   397  		{Dest: &repo.GCPServiceAccountKey, Transform: StripCRLFCharacter}: repoInfo.GCPServiceAccountKey,
   398  	}, make(map[string]*apiv1.Secret))
   399  	return repo, err
   400  }
   401  
   402  func (l *legacyRepositoryBackend) credentialsToRepositoryCredentials(repoInfo settings.RepositoryCredentials) (*appsv1.RepoCreds, error) {
   403  	creds := &appsv1.RepoCreds{
   404  		URL:                        repoInfo.URL,
   405  		GithubAppId:                repoInfo.GithubAppId,
   406  		GithubAppInstallationId:    repoInfo.GithubAppInstallationId,
   407  		GitHubAppEnterpriseBaseURL: repoInfo.GithubAppEnterpriseBaseURL,
   408  		EnableOCI:                  repoInfo.EnableOCI,
   409  	}
   410  	err := l.db.unmarshalFromSecretsStr(map[*SecretMaperValidation]*apiv1.SecretKeySelector{
   411  		{Dest: &creds.Username}:             repoInfo.UsernameSecret,
   412  		{Dest: &creds.Password}:             repoInfo.PasswordSecret,
   413  		{Dest: &creds.SSHPrivateKey}:        repoInfo.SSHPrivateKeySecret,
   414  		{Dest: &creds.TLSClientCertData}:    repoInfo.TLSClientCertDataSecret,
   415  		{Dest: &creds.TLSClientCertKey}:     repoInfo.TLSClientCertKeySecret,
   416  		{Dest: &creds.GithubAppPrivateKey}:  repoInfo.GithubAppPrivateKeySecret,
   417  		{Dest: &creds.GCPServiceAccountKey}: repoInfo.GCPServiceAccountKey,
   418  	}, make(map[string]*apiv1.Secret))
   419  	return creds, err
   420  }
   421  
   422  // Set data to be stored in a given secret used for repository credentials and templates.
   423  // The name of the secret is a combination of the prefix given, and a calculated value
   424  // from the repository or template URL.
   425  func (l *legacyRepositoryBackend) setSecretData(prefix string, url string, secretsData map[string]map[string][]byte, secretKey *apiv1.SecretKeySelector, value string, defaultKeyName string) *apiv1.SecretKeySelector {
   426  	if secretKey == nil && value != "" {
   427  		secretKey = &apiv1.SecretKeySelector{
   428  			LocalObjectReference: apiv1.LocalObjectReference{Name: RepoURLToSecretName(prefix, url)},
   429  			Key:                  defaultKeyName,
   430  		}
   431  	}
   432  
   433  	if secretKey != nil {
   434  		data, ok := secretsData[secretKey.Name]
   435  		if !ok {
   436  			data = map[string][]byte{}
   437  		}
   438  		if value != "" {
   439  			data[secretKey.Key] = []byte(value)
   440  		}
   441  		secretsData[secretKey.Name] = data
   442  	}
   443  
   444  	if value == "" {
   445  		secretKey = nil
   446  	}
   447  
   448  	return secretKey
   449  }
   450  
   451  func (l *legacyRepositoryBackend) getRepositoryIndex(repos []settings.Repository, repoURL string) int {
   452  	for i, repo := range repos {
   453  		if git.SameURL(repo.URL, repoURL) {
   454  			return i
   455  		}
   456  	}
   457  	return -1
   458  }
   459  
   460  // getRepositoryCredentialIndex returns the index of the best matching repository credential
   461  // configuration, i.e. the one with the longest match
   462  func getRepositoryCredentialIndex(repoCredentials []settings.RepositoryCredentials, repoURL string) int {
   463  	var max, idx int = 0, -1
   464  	repoURL = git.NormalizeGitURL(repoURL)
   465  	for i, cred := range repoCredentials {
   466  		credUrl := git.NormalizeGitURL(cred.URL)
   467  		if strings.HasPrefix(repoURL, credUrl) {
   468  			if len(credUrl) > max {
   469  				max = len(credUrl)
   470  				idx = i
   471  			}
   472  		}
   473  	}
   474  	return idx
   475  }