diff --git a/go.mod b/go.mod index 4b37e71f..fd569d32 100644 --- a/go.mod +++ b/go.mod @@ -19,13 +19,13 @@ require ( github.com/fluxcd/image-reflector-controller/api v0.35.2 github.com/fluxcd/pkg/apis/acl v0.8.0 github.com/fluxcd/pkg/apis/event v0.18.0 - github.com/fluxcd/pkg/apis/meta v1.17.0 + github.com/fluxcd/pkg/apis/meta v1.18.0 github.com/fluxcd/pkg/auth v0.21.0 github.com/fluxcd/pkg/cache v0.10.0 - github.com/fluxcd/pkg/git v0.34.0 - github.com/fluxcd/pkg/git/gogit v0.37.0 + github.com/fluxcd/pkg/git v0.35.0 + github.com/fluxcd/pkg/git/gogit v0.38.0 github.com/fluxcd/pkg/gittestserver v0.18.0 - github.com/fluxcd/pkg/runtime v0.69.0 + github.com/fluxcd/pkg/runtime v0.79.0 github.com/fluxcd/pkg/ssh v0.20.0 github.com/fluxcd/source-controller/api v1.6.1 github.com/go-git/go-billy/v5 v5.6.2 diff --git a/go.sum b/go.sum index 68c10dd1..27af2fc0 100644 --- a/go.sum +++ b/go.sum @@ -132,20 +132,20 @@ github.com/fluxcd/pkg/apis/acl v0.8.0 h1:mZNl4mOQQf5/cdMCYgKcrZTZRndCtMtkI0BDfNO github.com/fluxcd/pkg/apis/acl v0.8.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ= github.com/fluxcd/pkg/apis/event v0.18.0 h1:PNbWk9gvX8gMIi6VsJapnuDO+giLEeY+6olLVXvXFkk= github.com/fluxcd/pkg/apis/event v0.18.0/go.mod h1:7S/DGboLolfbZ6stO6dcDhG1SfkPWQ9foCULvbiYpiA= -github.com/fluxcd/pkg/apis/meta v1.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E= -github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8= +github.com/fluxcd/pkg/apis/meta v1.18.0 h1:ACHrMIjlcioE9GKS7NGk62KX4NshqNewr8sBwMcXABs= +github.com/fluxcd/pkg/apis/meta v1.18.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8= github.com/fluxcd/pkg/auth v0.21.0 h1:ckAQqP12wuptXEkMY18SQKWEY09m9e6yI0mEMsDV15M= github.com/fluxcd/pkg/auth v0.21.0/go.mod h1:MXmpsXT97c874HCw5hnfqFUP7TsG8/Ss1vFrk8JccfM= github.com/fluxcd/pkg/cache v0.10.0 h1:M+OGDM4da1cnz7q+sZSBtkBJHpiJsLnKVmR9OdMWxEY= github.com/fluxcd/pkg/cache v0.10.0/go.mod h1:pPXRzQUDQagsCniuOolqVhnAkbNgYOg8d2cTliPs7ME= -github.com/fluxcd/pkg/git v0.34.0 h1:qTViWkfpEDnjzySyKRKliqUeGj/DznqlkmPhaDNIsFY= -github.com/fluxcd/pkg/git v0.34.0/go.mod h1:F9Asm3MlLW4uZx3FF92+bqho+oktdMdnTn/QmXe56NE= -github.com/fluxcd/pkg/git/gogit v0.37.0 h1:JINylFYpwrxS3MCu5Ei+g6XPgxbs5lv9PppIYYr07KY= -github.com/fluxcd/pkg/git/gogit v0.37.0/go.mod h1:X7YzW5mb4srA05h4SpL2OEGEHq02tbXQF5DPJen9hlc= +github.com/fluxcd/pkg/git v0.35.0 h1:mAauhsdfxNW4yQdXviVlvcN/uCGGG0+6p5D1+HFZI9w= +github.com/fluxcd/pkg/git v0.35.0/go.mod h1:F9Asm3MlLW4uZx3FF92+bqho+oktdMdnTn/QmXe56NE= +github.com/fluxcd/pkg/git/gogit v0.38.0 h1:222KmjpKf9pxqi8rAtm1omDcpGTY4JkahLrAwZ3AcwU= +github.com/fluxcd/pkg/git/gogit v0.38.0/go.mod h1:kHStdfd/AtkH5ED0UEWP2tmMGnfxg1GG92D29M+lRJ0= github.com/fluxcd/pkg/gittestserver v0.18.0 h1:jkuLmzWFfq+v1ziI0LspZrUzc5WzCO98BaWb8OVRPtk= github.com/fluxcd/pkg/gittestserver v0.18.0/go.mod h1:2wDLqUkPuixk/8pGQdef9ewaGJXf7Z+xHDVq8PIFG4E= -github.com/fluxcd/pkg/runtime v0.69.0 h1:5gPY95NSFI34GlQTj0+NHjOFpirSwviCUb9bM09b5nA= -github.com/fluxcd/pkg/runtime v0.69.0/go.mod h1:ug+pat+I4wfOBuCy2E/pLmBNd3kOOo4cP2jxnxefPwY= +github.com/fluxcd/pkg/runtime v0.79.0 h1:9tv79EiQDx/QJH9mYDd9kZ9WybCVWBUGoiBHij+eKkc= +github.com/fluxcd/pkg/runtime v0.79.0/go.mod h1:iGhdaEq+lMJQTJNAFEPOU4gUJ7kt3yeDcJPZy7O9IUw= github.com/fluxcd/pkg/ssh v0.20.0 h1:Ak0laIYIc/L8lEfqls/LDWRW8wYPESGaravQsCRGLb8= github.com/fluxcd/pkg/ssh v0.20.0/go.mod h1:sRfAAkxx1GwCGjYirKPnTKdNkNrJRo9kqzWLVFXKv7E= github.com/fluxcd/pkg/version v0.9.0 h1:pQBHMt9TbnnTUzj3EoMhRi5JUkNBqrTBSAaoLG1ovUA= diff --git a/internal/source/git.go b/internal/source/git.go index c24ce32b..5f93eb14 100644 --- a/internal/source/git.go +++ b/internal/source/git.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ProtonMail/go-crypto/openpgp" + "github.com/fluxcd/pkg/runtime/secrets" "github.com/go-git/go-git/v5/plumbing/transport" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -62,13 +63,14 @@ type gitSrcCfg struct { } func buildGitConfig(ctx context.Context, c client.Client, originKey, srcKey types.NamespacedName, gitSpec *imagev1.GitSpec, opts SourceOptions) (*gitSrcCfg, error) { + var err error cfg := &gitSrcCfg{ srcKey: srcKey, } // Get the repo. repo := &sourcev1.GitRepository{} - if err := c.Get(ctx, srcKey, repo); err != nil { + if err = c.Get(ctx, srcKey, repo); err != nil { if client.IgnoreNotFound(err) == nil { return nil, fmt.Errorf("referenced git repository does not exist: %w", err) } @@ -94,14 +96,26 @@ func buildGitConfig(ctx context.Context, c client.Client, originKey, srcKey type // Configure push first as the client options below depend on the push // configuration. - if err := configurePush(cfg, gitSpec, cfg.checkoutRef); err != nil { + if err = configurePush(cfg, gitSpec, cfg.checkoutRef); err != nil { return nil, err } - proxyOpts, proxyURL, err := getProxyOpts(ctx, c, repo) - if err != nil { - return nil, err + var proxyURL *url.URL + var proxyOpts *transport.ProxyOptions + // Check if a proxy secret reference is provided in the GitRepository spec. + if repo.Spec.ProxySecretRef != nil { + secretRef := types.NamespacedName{ + Name: repo.Spec.ProxySecretRef.Name, + Namespace: repo.GetNamespace(), + } + // Get the proxy URL from runtime/secret + proxyURL, err = secrets.ProxyURLFromSecretRef(ctx, c, secretRef) + if err != nil { + return nil, err + } + proxyOpts = &transport.ProxyOptions{URL: proxyURL.String()} } + cfg.authOpts, err = getAuthOpts(ctx, c, repo, opts, proxyURL) if err != nil { return nil, err @@ -165,13 +179,15 @@ func configurePush(cfg *gitSrcCfg, gitSpec *imagev1.GitSpec, checkoutRef *source func getAuthOpts(ctx context.Context, c client.Client, repo *sourcev1.GitRepository, srcOpts SourceOptions, proxyURL *url.URL) (*git.AuthOptions, error) { + var secret *corev1.Secret var data map[string][]byte var err error if repo.Spec.SecretRef != nil { - data, err = getSecretData(ctx, c, repo.Spec.SecretRef.Name, repo.GetNamespace()) + secret, err = getSecret(ctx, c, repo.Spec.SecretRef.Name, repo.GetNamespace()) if err != nil { return nil, fmt.Errorf("failed to get auth secret '%s/%s': %w", repo.GetNamespace(), repo.Spec.SecretRef.Name, err) } + data = secret.Data } u, err := url.Parse(repo.Spec.URL) @@ -211,24 +227,34 @@ func getAuthOpts(ctx context.Context, c client.Client, repo *sourcev1.GitReposit if repo.Spec.SecretRef == nil { return nil, fmt.Errorf("secretRef with github app data must be specified when provider is set to github: %w", ErrInvalidSourceConfiguration) } + targetURL := fmt.Sprintf("%s://%s", u.Scheme, u.Host) + authMethods, err := secrets.AuthMethodsFromSecret(ctx, secret, secrets.WithTargetURL(targetURL), secrets.WithTLSSystemCertPool()) + if err != nil { + return nil, err + } + if !authMethods.HasGitHubAppData() { + return nil, fmt.Errorf("secretRef with github app data must be specified when provider is set to github: %w", ErrInvalidSourceConfiguration) + } getCreds = func() (*authutils.GitCredentials, error) { - var opts []github.OptFunc + var appOpts []github.OptFunc - if len(data) > 0 { - opts = append(opts, github.WithAppData(data)) - } + appOpts = append(appOpts, github.WithAppData(authMethods.GitHubAppData)) if proxyURL != nil { - opts = append(opts, github.WithProxyURL(proxyURL)) + appOpts = append(appOpts, github.WithProxyURL(proxyURL)) } if srcOpts.tokenCache != nil { - opts = append(opts, github.WithCache(srcOpts.tokenCache, imagev1.ImageUpdateAutomationKind, + appOpts = append(appOpts, github.WithCache(srcOpts.tokenCache, imagev1.ImageUpdateAutomationKind, srcOpts.objName, srcOpts.objNamespace, cache.OperationReconcile)) } - username, password, err := github.GetCredentials(ctx, opts...) + if authMethods.HasTLS() { + appOpts = append(appOpts, github.WithTLSConfig(authMethods.TLS)) + } + + username, password, err := github.GetCredentials(ctx, appOpts...) if err != nil { return nil, err } @@ -255,45 +281,6 @@ func getAuthOpts(ctx context.Context, c client.Client, repo *sourcev1.GitReposit return opts, nil } -func getProxyOpts(ctx context.Context, c client.Client, repo *sourcev1.GitRepository) (*transport.ProxyOptions, *url.URL, error) { - if repo.Spec.ProxySecretRef == nil { - return nil, nil, nil - } - name := repo.Spec.ProxySecretRef.Name - namespace := repo.GetNamespace() - proxyData, err := getSecretData(ctx, c, name, namespace) - if err != nil { - return nil, nil, fmt.Errorf("failed to get proxy secret '%s/%s': %w", namespace, name, err) - } - b, ok := proxyData["address"] - if !ok { - return nil, nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", namespace, name) - } - - address := string(b) - username := string(proxyData["username"]) - password := string(proxyData["password"]) - - proxyOpts := &transport.ProxyOptions{ - URL: address, - Username: username, - Password: password, - } - - proxyURL, err := url.Parse(string(address)) - if err != nil { - return nil, nil, fmt.Errorf("invalid address in proxy secret '%s/%s': %w", namespace, name, err) - } - switch { - case username != "" && password == "": - proxyURL.User = url.User(username) - case username != "" && password != "": - proxyURL.User = url.UserPassword(username, password) - } - - return proxyOpts, proxyURL, nil -} - func getSigningEntity(ctx context.Context, c client.Client, namespace string, gitSpec *imagev1.GitSpec) (*openpgp.Entity, error) { secretName := gitSpec.Commit.SigningKey.SecretRef.Name secretData, err := getSecretData(ctx, c, secretName, namespace) @@ -330,13 +317,21 @@ func getSigningEntity(ctx context.Context, c client.Client, namespace string, gi } func getSecretData(ctx context.Context, c client.Client, name, namespace string) (map[string][]byte, error) { + secret, err := getSecret(ctx, c, name, namespace) + if err != nil { + return nil, err + } + return secret.Data, nil +} + +func getSecret(ctx context.Context, c client.Client, name, namespace string) (*corev1.Secret, error) { key := types.NamespacedName{ Namespace: namespace, Name: name, } - var secret corev1.Secret - if err := c.Get(ctx, key, &secret); err != nil { + secret := &corev1.Secret{} + if err := c.Get(ctx, key, secret); err != nil { return nil, err } - return secret.Data, nil + return secret, nil } diff --git a/internal/source/git_test.go b/internal/source/git_test.go index 6057f52e..ccda779d 100644 --- a/internal/source/git_test.go +++ b/internal/source/git_test.go @@ -19,11 +19,9 @@ package source import ( "context" "fmt" - "net/url" "testing" "time" - "github.com/go-git/go-git/v5/plumbing/transport" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,12 +30,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" - imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2" - "github.com/fluxcd/image-automation-controller/internal/testutil" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git/github" sourcev1 "github.com/fluxcd/source-controller/api/v1" + + imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2" + "github.com/fluxcd/image-automation-controller/internal/testutil" ) func Test_getAuthOpts(t *testing.T) { @@ -196,6 +195,26 @@ func Test_getAuthOpts_providerAuth(t *testing.T) { }, wantErr: "Key must be a PEM encoded PKCS1 or PKCS8 key", }, + { + name: "github provider with basic auth in secret", + url: "https://example.com/org/repo", + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic-auth-secret", + }, + Data: map[string][]byte{ + "username": []byte("abc"), + "password": []byte(""), + }, + }, + beforeFunc: func(obj *sourcev1.GitRepository) { + obj.Spec.Provider = sourcev1.GitProviderGitHub + obj.Spec.SecretRef = &meta.LocalObjectReference{ + Name: "basic-auth-secret", + } + }, + wantErr: "secretRef with github app data must be specified when provider is set to github", + }, { name: "generic provider with github app data in secret", url: "https://example.com/org/repo", @@ -266,94 +285,6 @@ func Test_getAuthOpts_providerAuth(t *testing.T) { } } -func Test_getProxyOpts(t *testing.T) { - namespace := "default" - invalidProxy := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid-proxy", - Namespace: namespace, - }, - Data: map[string][]byte{ - "url": []byte("https://example.com"), - }, - } - validProxy := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valid-proxy", - Namespace: namespace, - }, - Data: map[string][]byte{ - "address": []byte("https://example.com"), - "username": []byte("user"), - "password": []byte("pass"), - }, - } - - tests := []struct { - name string - secretName string - want *transport.ProxyOptions - wantProxyURL *url.URL - wantErr bool - }{ - { - name: "non-existing secret", - secretName: "non-existing", - want: nil, - wantProxyURL: nil, - wantErr: true, - }, - { - name: "invalid proxy secret", - secretName: "invalid-proxy", - want: nil, - wantProxyURL: nil, - wantErr: true, - }, - { - name: "valid proxy secret", - secretName: "valid-proxy", - want: &transport.ProxyOptions{ - URL: "https://example.com", - Username: "user", - Password: "pass", - }, - wantProxyURL: &url.URL{ - Scheme: "https", - Host: "example.com", - User: url.UserPassword("user", "pass"), - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) - - clientBuilder := fakeclient.NewClientBuilder(). - WithScheme(scheme.Scheme). - WithObjects(invalidProxy, validProxy) - c := clientBuilder.Build() - - gitRepo := &sourcev1.GitRepository{} - gitRepo.Namespace = namespace - if tt.secretName != "" { - gitRepo.Spec = sourcev1.GitRepositorySpec{ - ProxySecretRef: &meta.LocalObjectReference{Name: tt.secretName}, - } - } - - got, gotProxyURL, err := getProxyOpts(context.TODO(), c, gitRepo) - if (err != nil) != tt.wantErr { - g.Fail(fmt.Sprintf("unexpected error: %v", err)) - return - } - g.Expect(got).To(Equal(tt.want)) - g.Expect(gotProxyURL).To(Equal(tt.wantProxyURL)) - }) - } -} - func Test_getSigningEntity(t *testing.T) { g := NewWithT(t)