github.com/argoproj/argo-cd@v1.8.7/util/db/repository.go (about) 1 package db 2 3 import ( 4 "fmt" 5 "hash/fnv" 6 "strings" 7 8 "golang.org/x/net/context" 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 log "github.com/sirupsen/logrus" 16 17 "github.com/argoproj/argo-cd/common" 18 appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 19 "github.com/argoproj/argo-cd/util/git" 20 "github.com/argoproj/argo-cd/util/settings" 21 ) 22 23 const ( 24 // Prefix to use for naming repository secrets 25 repoSecretPrefix = "repo" 26 // Prefix to use for naming credential template secrets 27 credSecretPrefix = "creds" 28 // The name of the key storing the username in the secret 29 username = "username" 30 // The name of the key storing the password in the secret 31 password = "password" 32 // The name of the key storing the SSH private in the secret 33 sshPrivateKey = "sshPrivateKey" 34 // The name of the key storing the TLS client cert data in the secret 35 tlsClientCertData = "tlsClientCertData" 36 // The name of the key storing the TLS client cert key in the secret 37 tlsClientCertKey = "tlsClientCertKey" 38 ) 39 40 func (db *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) { 41 repos, err := db.settingsMgr.GetRepositories() 42 if err != nil { 43 return nil, err 44 } 45 46 index := getRepositoryIndex(repos, r.Repo) 47 if index > -1 { 48 return nil, status.Errorf(codes.AlreadyExists, "repository '%s' already exists", r.Repo) 49 } 50 51 data := make(map[string][]byte) 52 if r.Username != "" { 53 data[username] = []byte(r.Username) 54 } 55 if r.Password != "" { 56 data[password] = []byte(r.Password) 57 } 58 if r.SSHPrivateKey != "" { 59 data[sshPrivateKey] = []byte(r.SSHPrivateKey) 60 } 61 62 repoInfo := settings.Repository{ 63 URL: r.Repo, 64 Type: r.Type, 65 Name: r.Name, 66 InsecureIgnoreHostKey: r.IsInsecure(), 67 Insecure: r.IsInsecure(), 68 EnableLFS: r.EnableLFS, 69 EnableOci: r.EnableOCI, 70 } 71 err = db.updateRepositorySecrets(&repoInfo, r) 72 if err != nil { 73 return nil, err 74 } 75 76 repos = append(repos, repoInfo) 77 err = db.settingsMgr.SaveRepositories(repos) 78 if err != nil { 79 return nil, err 80 } 81 return r, nil 82 } 83 84 // GetRepository returns a repository by URL. If the repository doesn't have any 85 // credentials attached to it, checks if a credential set for the repo's URL is 86 // configured and copies them to the returned repository data. 87 func (db *db) GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) { 88 repos, err := db.settingsMgr.GetRepositories() 89 if err != nil { 90 return nil, err 91 } 92 93 repo := &appsv1.Repository{Repo: repoURL} 94 index := getRepositoryIndex(repos, repoURL) 95 if index >= 0 { 96 repo, err = db.credentialsToRepository(repos[index]) 97 if err != nil { 98 return nil, err 99 } 100 } 101 102 // Check for and copy repository credentials, if repo has none configured. 103 if !repo.HasCredentials() { 104 creds, err := db.GetRepositoryCredentials(ctx, repoURL) 105 if err == nil { 106 if creds != nil { 107 repo.CopyCredentialsFrom(creds) 108 repo.InheritedCreds = true 109 } 110 } else { 111 return nil, err 112 } 113 } else { 114 log.Debugf("%s has credentials", repo.Repo) 115 } 116 117 return repo, err 118 } 119 120 func (db *db) ListRepositories(ctx context.Context) ([]*appsv1.Repository, error) { 121 return db.listRepositories(ctx, nil) 122 } 123 124 func (db *db) listRepositories(ctx context.Context, repoType *string) ([]*appsv1.Repository, error) { 125 inRepos, err := db.settingsMgr.GetRepositories() 126 if err != nil { 127 return nil, err 128 } 129 130 var repos []*appsv1.Repository 131 for _, inRepo := range inRepos { 132 if repoType == nil || *repoType == inRepo.Type { 133 r, err := db.GetRepository(ctx, inRepo.URL) 134 if err != nil { 135 return nil, err 136 } 137 repos = append(repos, r) 138 } 139 } 140 return repos, nil 141 } 142 143 func (db *db) credentialsToRepository(repoInfo settings.Repository) (*appsv1.Repository, error) { 144 repo := &appsv1.Repository{ 145 Repo: repoInfo.URL, 146 Type: repoInfo.Type, 147 Name: repoInfo.Name, 148 InsecureIgnoreHostKey: repoInfo.InsecureIgnoreHostKey, 149 Insecure: repoInfo.Insecure, 150 EnableLFS: repoInfo.EnableLFS, 151 EnableOCI: repoInfo.EnableOci, 152 } 153 err := db.unmarshalFromSecretsStr(map[*string]*apiv1.SecretKeySelector{ 154 &repo.Username: repoInfo.UsernameSecret, 155 &repo.Password: repoInfo.PasswordSecret, 156 &repo.SSHPrivateKey: repoInfo.SSHPrivateKeySecret, 157 &repo.TLSClientCertData: repoInfo.TLSClientCertDataSecret, 158 &repo.TLSClientCertKey: repoInfo.TLSClientCertKeySecret, 159 }, make(map[string]*apiv1.Secret)) 160 return repo, err 161 } 162 163 func (db *db) credentialsToRepositoryCredentials(repoInfo settings.RepositoryCredentials) (*appsv1.RepoCreds, error) { 164 creds := &appsv1.RepoCreds{ 165 URL: repoInfo.URL, 166 } 167 err := db.unmarshalFromSecretsStr(map[*string]*apiv1.SecretKeySelector{ 168 &creds.Username: repoInfo.UsernameSecret, 169 &creds.Password: repoInfo.PasswordSecret, 170 &creds.SSHPrivateKey: repoInfo.SSHPrivateKeySecret, 171 &creds.TLSClientCertData: repoInfo.TLSClientCertDataSecret, 172 &creds.TLSClientCertKey: repoInfo.TLSClientCertKeySecret, 173 }, make(map[string]*apiv1.Secret)) 174 return creds, err 175 } 176 177 // UpdateRepository updates a repository 178 func (db *db) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) { 179 repos, err := db.settingsMgr.GetRepositories() 180 if err != nil { 181 return nil, err 182 } 183 184 index := getRepositoryIndex(repos, r.Repo) 185 if index < 0 { 186 return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo) 187 } 188 189 repoInfo := repos[index] 190 err = db.updateRepositorySecrets(&repoInfo, r) 191 if err != nil { 192 return nil, err 193 } 194 195 // Update boolean settings 196 repoInfo.InsecureIgnoreHostKey = r.IsInsecure() 197 repoInfo.Insecure = r.IsInsecure() 198 repoInfo.EnableLFS = r.EnableLFS 199 200 repos[index] = repoInfo 201 err = db.settingsMgr.SaveRepositories(repos) 202 if err != nil { 203 return nil, err 204 } 205 return r, nil 206 } 207 208 // Delete updates a repository 209 func (db *db) DeleteRepository(ctx context.Context, repoURL string) error { 210 repos, err := db.settingsMgr.GetRepositories() 211 if err != nil { 212 return err 213 } 214 215 index := getRepositoryIndex(repos, repoURL) 216 if index < 0 { 217 return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL) 218 } 219 err = db.updateRepositorySecrets(&repos[index], &appsv1.Repository{ 220 SSHPrivateKey: "", 221 Password: "", 222 Username: "", 223 TLSClientCertData: "", 224 TLSClientCertKey: "", 225 }) 226 if err != nil { 227 return err 228 } 229 repos = append(repos[:index], repos[index+1:]...) 230 return db.settingsMgr.SaveRepositories(repos) 231 } 232 233 // ListRepositoryCredentials returns a list of URLs that contain repo credential sets 234 func (db *db) ListRepositoryCredentials(ctx context.Context) ([]string, error) { 235 repos, err := db.settingsMgr.GetRepositoryCredentials() 236 if err != nil { 237 return nil, err 238 } 239 240 urls := make([]string, len(repos)) 241 for i := range repos { 242 urls[i] = repos[i].URL 243 } 244 245 return urls, nil 246 } 247 248 // GetRepositoryCredentials retrieves a repository credential set 249 func (db *db) GetRepositoryCredentials(ctx context.Context, repoURL string) (*appsv1.RepoCreds, error) { 250 var credential *appsv1.RepoCreds 251 252 repoCredentials, err := db.settingsMgr.GetRepositoryCredentials() 253 if err != nil { 254 return nil, err 255 } 256 index := getRepositoryCredentialIndex(repoCredentials, repoURL) 257 if index >= 0 { 258 credential, err = db.credentialsToRepositoryCredentials(repoCredentials[index]) 259 if err != nil { 260 return nil, err 261 } 262 } 263 264 return credential, err 265 } 266 267 // CreateRepositoryCredentials creates a repository credential set 268 func (db *db) CreateRepositoryCredentials(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) { 269 creds, err := db.settingsMgr.GetRepositoryCredentials() 270 if err != nil { 271 return nil, err 272 } 273 274 index := getRepositoryCredentialIndex(creds, r.URL) 275 if index > -1 { 276 return nil, status.Errorf(codes.AlreadyExists, "repository credentials for '%s' already exists", r.URL) 277 } 278 279 repoInfo := settings.RepositoryCredentials{ 280 URL: r.URL, 281 } 282 283 err = db.updateCredentialsSecret(&repoInfo, r) 284 if err != nil { 285 return nil, err 286 } 287 288 creds = append(creds, repoInfo) 289 err = db.settingsMgr.SaveRepositoryCredentials(creds) 290 if err != nil { 291 return nil, err 292 } 293 return r, nil 294 } 295 296 // UpdateRepositoryCredentials updates a repository credential set 297 func (db *db) UpdateRepositoryCredentials(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) { 298 repos, err := db.settingsMgr.GetRepositoryCredentials() 299 if err != nil { 300 return nil, err 301 } 302 303 index := getRepositoryCredentialIndex(repos, r.URL) 304 if index < 0 { 305 return nil, status.Errorf(codes.NotFound, "repository credentials '%s' not found", r.URL) 306 } 307 308 repoInfo := repos[index] 309 err = db.updateCredentialsSecret(&repoInfo, r) 310 if err != nil { 311 return nil, err 312 } 313 314 repos[index] = repoInfo 315 err = db.settingsMgr.SaveRepositoryCredentials(repos) 316 if err != nil { 317 return nil, err 318 } 319 return r, nil 320 321 } 322 323 // DeleteRepositoryCredentials deletes a repository credential set from config, and 324 // also all the secrets which actually contained the credentials. 325 func (db *db) DeleteRepositoryCredentials(ctx context.Context, name string) error { 326 repos, err := db.settingsMgr.GetRepositoryCredentials() 327 if err != nil { 328 return err 329 } 330 331 index := getRepositoryCredentialIndex(repos, name) 332 if index < 0 { 333 return status.Errorf(codes.NotFound, "repository credentials '%s' not found", name) 334 } 335 err = db.updateCredentialsSecret(&repos[index], &appsv1.RepoCreds{ 336 SSHPrivateKey: "", 337 Password: "", 338 Username: "", 339 TLSClientCertData: "", 340 TLSClientCertKey: "", 341 }) 342 if err != nil { 343 return err 344 } 345 repos = append(repos[:index], repos[index+1:]...) 346 return db.settingsMgr.SaveRepositoryCredentials(repos) 347 } 348 349 func (db *db) updateCredentialsSecret(credsInfo *settings.RepositoryCredentials, c *appsv1.RepoCreds) error { 350 r := &appsv1.Repository{ 351 Repo: c.URL, 352 Username: c.Username, 353 Password: c.Password, 354 SSHPrivateKey: c.SSHPrivateKey, 355 TLSClientCertData: c.TLSClientCertData, 356 TLSClientCertKey: c.TLSClientCertKey, 357 } 358 secretsData := make(map[string]map[string][]byte) 359 360 credsInfo.UsernameSecret = setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.UsernameSecret, r.Username, username) 361 credsInfo.PasswordSecret = setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.PasswordSecret, r.Password, password) 362 credsInfo.SSHPrivateKeySecret = setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.SSHPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey) 363 credsInfo.TLSClientCertDataSecret = setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.TLSClientCertDataSecret, r.TLSClientCertData, tlsClientCertData) 364 credsInfo.TLSClientCertKeySecret = setSecretData(credSecretPrefix, r.Repo, secretsData, credsInfo.TLSClientCertKeySecret, r.TLSClientCertKey, tlsClientCertKey) 365 for k, v := range secretsData { 366 err := db.upsertSecret(k, v) 367 if err != nil { 368 return err 369 } 370 } 371 return nil 372 } 373 374 func (db *db) updateRepositorySecrets(repoInfo *settings.Repository, r *appsv1.Repository) error { 375 secretsData := make(map[string]map[string][]byte) 376 377 repoInfo.UsernameSecret = setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.UsernameSecret, r.Username, username) 378 repoInfo.PasswordSecret = setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.PasswordSecret, r.Password, password) 379 repoInfo.SSHPrivateKeySecret = setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.SSHPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey) 380 repoInfo.TLSClientCertDataSecret = setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.TLSClientCertDataSecret, r.TLSClientCertData, tlsClientCertData) 381 repoInfo.TLSClientCertKeySecret = setSecretData(repoSecretPrefix, r.Repo, secretsData, repoInfo.TLSClientCertKeySecret, r.TLSClientCertKey, tlsClientCertKey) 382 for k, v := range secretsData { 383 err := db.upsertSecret(k, v) 384 if err != nil { 385 return err 386 } 387 } 388 return nil 389 } 390 391 // Set data to be stored in a given secret used for repository credentials and templates. 392 // The name of the secret is a combination of the prefix given, and a calculated value 393 // from the repository or template URL. 394 func setSecretData(prefix string, url string, secretsData map[string]map[string][]byte, secretKey *apiv1.SecretKeySelector, value string, defaultKeyName string) *apiv1.SecretKeySelector { 395 if secretKey == nil && value != "" { 396 secretKey = &apiv1.SecretKeySelector{ 397 LocalObjectReference: apiv1.LocalObjectReference{Name: repoURLToSecretName(prefix, url)}, 398 Key: defaultKeyName, 399 } 400 } 401 402 if secretKey != nil { 403 data, ok := secretsData[secretKey.Name] 404 if !ok { 405 data = map[string][]byte{} 406 } 407 if value != "" { 408 data[secretKey.Key] = []byte(value) 409 } 410 secretsData[secretKey.Name] = data 411 } 412 413 if value == "" { 414 secretKey = nil 415 } 416 417 return secretKey 418 } 419 420 func (db *db) upsertSecret(name string, data map[string][]byte) error { 421 secret, err := db.kubeclientset.CoreV1().Secrets(db.ns).Get(context.Background(), name, metav1.GetOptions{}) 422 if err != nil { 423 if apierr.IsNotFound(err) { 424 if len(data) == 0 { 425 return nil 426 } 427 _, err = db.kubeclientset.CoreV1().Secrets(db.ns).Create(context.Background(), &apiv1.Secret{ 428 ObjectMeta: metav1.ObjectMeta{ 429 Name: name, 430 Annotations: map[string]string{ 431 common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, 432 }, 433 }, 434 Data: data, 435 }, metav1.CreateOptions{}) 436 if err != nil { 437 return err 438 } 439 } 440 } else { 441 for _, key := range []string{username, password, sshPrivateKey, tlsClientCertData, tlsClientCertKey} { 442 if secret.Data == nil { 443 secret.Data = make(map[string][]byte) 444 } 445 if val, ok := data[key]; ok && len(val) > 0 { 446 secret.Data[key] = val 447 } else { 448 delete(secret.Data, key) 449 } 450 } 451 if len(secret.Data) == 0 { 452 isManagedByArgo := (secret.Annotations != nil && secret.Annotations[common.AnnotationKeyManagedBy] == common.AnnotationValueManagedByArgoCD) || 453 (secret.Labels != nil && secret.Labels[common.LabelKeySecretType] == "repository") 454 if isManagedByArgo { 455 return db.kubeclientset.CoreV1().Secrets(db.ns).Delete(context.Background(), name, metav1.DeleteOptions{}) 456 } 457 return nil 458 } else { 459 _, err = db.kubeclientset.CoreV1().Secrets(db.ns).Update(context.Background(), secret, metav1.UpdateOptions{}) 460 if err != nil { 461 return err 462 } 463 } 464 } 465 return nil 466 } 467 468 func getRepositoryIndex(repos []settings.Repository, repoURL string) int { 469 for i, repo := range repos { 470 if git.SameURL(repo.URL, repoURL) { 471 return i 472 } 473 } 474 return -1 475 } 476 477 // getRepositoryCredentialIndex returns the index of the best matching repository credential 478 // configuration, i.e. the one with the longest match 479 func getRepositoryCredentialIndex(repoCredentials []settings.RepositoryCredentials, repoURL string) int { 480 var max, idx int = 0, -1 481 repoURL = git.NormalizeGitURL(repoURL) 482 for i, cred := range repoCredentials { 483 credUrl := git.NormalizeGitURL(cred.URL) 484 if strings.HasPrefix(repoURL, credUrl) { 485 if len(credUrl) > max { 486 max = len(credUrl) 487 idx = i 488 } 489 } 490 } 491 return idx 492 } 493 494 // repoURLToSecretName hashes repo URL to a secret name using a formula. This is used when 495 // repositories are _imperatively_ created and need its credentials to be stored in a secret. 496 // NOTE: this formula should not be considered stable and may change in future releases. 497 // Do NOT rely on this formula as a means of secret lookup, only secret creation. 498 func repoURLToSecretName(prefix string, repo string) string { 499 h := fnv.New32a() 500 _, _ = h.Write([]byte(repo)) 501 return fmt.Sprintf("%s-%v", prefix, h.Sum32()) 502 }