(chore): adds tls config for GitHub App auth

this commit ensures that if ca.crt or caFile is available in the github app secret, a tls config with user provided certs is appended to system cert pool and passed to the underlying http transport

Signed-off-by: abhijith-darshan <abhijith.darshan@hotmail.com>

(chore): keeps implementation in-sync with source-controller

Signed-off-by: abhijith-darshan <abhijith.darshan@hotmail.com>

(chore): inline proxy detection

This commit removes getProxyOpts(...) helper func and uses pkg/runtime/secrets to retrieve proxy information from secret reference

Signed-off-by: abhijith-darshan <abhijith.darshan@hotmail.com>

(chore): remove err formatting for secrets.ProxyURLFromSecretRef

Signed-off-by: abhijith-darshan <abhijith.darshan@hotmail.com>
This commit is contained in:
abhijith-darshan 2025-08-11 01:17:44 +02:00
parent f584731506
commit 57516b14f7
No known key found for this signature in database
GPG Key ID: D46C33C884B83DD3
4 changed files with 88 additions and 162 deletions

8
go.mod
View File

@ -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

16
go.sum
View File

@ -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=

View File

@ -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) {
key := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var secret corev1.Secret
if err := c.Get(ctx, key, &secret); err != nil {
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,
}
secret := &corev1.Secret{}
if err := c.Get(ctx, key, secret); err != nil {
return nil, err
}
return secret, nil
}

View File

@ -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)