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 }