Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.24.0

require (
github.com/fluxcd/pkg/apis/acl v0.7.0
github.com/fluxcd/pkg/apis/meta v1.11.0
github.com/fluxcd/pkg/apis/meta v1.12.0
k8s.io/apimachinery v0.33.0
sigs.k8s.io/controller-runtime v0.20.4
)
Expand Down
4 changes: 2 additions & 2 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0=
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
github.com/fluxcd/pkg/apis/meta v1.11.0 h1:h8q95k6ZEK1HCfsLkt8Np3i6ktb6ZzcWJ6hg++oc9w0=
github.com/fluxcd/pkg/apis/meta v1.11.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fluxcd/pkg/apis/meta v1.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ require (
github.com/elazarl/goproxy v1.7.2
github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/fluxcd/pkg/apis/event v0.17.0
github.com/fluxcd/pkg/apis/meta v1.11.0
github.com/fluxcd/pkg/auth v0.12.0
github.com/fluxcd/pkg/apis/meta v1.12.0
github.com/fluxcd/pkg/auth v0.14.0
github.com/fluxcd/pkg/cache v0.9.0
github.com/fluxcd/pkg/git v0.29.0
github.com/fluxcd/pkg/git/gogit v0.31.0
github.com/fluxcd/pkg/git v0.31.0
github.com/fluxcd/pkg/git/gogit v0.33.0
github.com/fluxcd/pkg/gittestserver v0.17.0
github.com/fluxcd/pkg/helmtestserver v0.24.0
github.com/fluxcd/pkg/lockedfile v0.6.0
github.com/fluxcd/pkg/masktoken v0.7.0
github.com/fluxcd/pkg/oci v0.48.0
github.com/fluxcd/pkg/oci v0.49.0
github.com/fluxcd/pkg/runtime v0.59.0
github.com/fluxcd/pkg/sourceignore v0.12.0
github.com/fluxcd/pkg/ssh v0.18.0
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -372,16 +372,16 @@ github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5o
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlbYi5NU7M=
github.com/fluxcd/pkg/apis/event v0.17.0/go.mod h1:0fLhLFiHlRTDKPDXdRnv+tS7mCMIQ0fJxnEfmvGM/5A=
github.com/fluxcd/pkg/apis/meta v1.11.0 h1:h8q95k6ZEK1HCfsLkt8Np3i6ktb6ZzcWJ6hg++oc9w0=
github.com/fluxcd/pkg/apis/meta v1.11.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fluxcd/pkg/auth v0.12.0 h1:35o0ziYMLZVgJwNvJBGsv/wd903B2fMagcrnm1ptUjc=
github.com/fluxcd/pkg/auth v0.12.0/go.mod h1:gQD2VT5OhIR1E8ZTEsTaho3bDQZidr9P10smH/awcew=
github.com/fluxcd/pkg/apis/meta v1.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fluxcd/pkg/auth v0.14.0 h1:AA9nmbFzTN5jcGROJK51LvQoDetMrXJLAo4Sd6WHpFI=
github.com/fluxcd/pkg/auth v0.14.0/go.mod h1:o91WIZZshLooBALXY/MVn0mmdUw3eATrqGXrG1M7nTE=
github.com/fluxcd/pkg/cache v0.9.0 h1:EGKfOLMG3fOwWnH/4Axl5xd425mxoQbZzlZoLfd8PDk=
github.com/fluxcd/pkg/cache v0.9.0/go.mod h1:jMwabjWfsC5lW8hE7NM3wtGNwSJ38Javx6EKbEi7INU=
github.com/fluxcd/pkg/git v0.29.0 h1:MHQ4F53e6Xt8a/POkd/fiChgysnd/XqiuK7vOWXAXLk=
github.com/fluxcd/pkg/git v0.29.0/go.mod h1:Ygn+LfrK6Ok+85uiq6s3NWG5LcHS4KY7mzES2JDJsGY=
github.com/fluxcd/pkg/git/gogit v0.31.0 h1:A56cmtgJBkWAj+gXSOdhPMQVTx0VF91S0PUaqpMXN4g=
github.com/fluxcd/pkg/git/gogit v0.31.0/go.mod h1:ya8z22xTvAAdW12HycxKYv4S+G+lqu5Kx/LyO/jWz8Y=
github.com/fluxcd/pkg/git v0.31.0 h1:hVUJcRujNa+GA5zrjrMpuVcgHbCBjfq0CZIZJqJl22I=
github.com/fluxcd/pkg/git v0.31.0/go.mod h1:rUgLXVQGBkBggHOLVMhHMHaweQ8Oc6HwZiN2Zm08Zxs=
github.com/fluxcd/pkg/git/gogit v0.33.0 h1:JYKa3XqA91AX7/sKEgARO9VzkwouXWjUgpwudEZEWq0=
github.com/fluxcd/pkg/git/gogit v0.33.0/go.mod h1:EvsVYcB3KjfhpdoyU1sO9HuMH5Xt0cVhW49kFlZcFLY=
github.com/fluxcd/pkg/gittestserver v0.17.0 h1:JlBvWZQTDOI+np5Z+084m3DkeAH1hMusEybyRUDF63k=
github.com/fluxcd/pkg/gittestserver v0.17.0/go.mod h1:E/40EmLoXcMqd6gLuLDC9F6KJxqHVGbBBeMNKk5XdxU=
github.com/fluxcd/pkg/helmtestserver v0.24.0 h1:9sSfRG17GnDIup4sI8V+fdvKROtunU4JyIo34uvXq3Q=
Expand All @@ -390,8 +390,8 @@ github.com/fluxcd/pkg/lockedfile v0.6.0 h1:64RRMiPv3ZK9Y4sjI8c78kZAdfEo+Sjr2iP8a
github.com/fluxcd/pkg/lockedfile v0.6.0/go.mod h1:gpdUVm7+05NIT1ZvzuNnHfnT81OhZtIySlxxkZ68pXk=
github.com/fluxcd/pkg/masktoken v0.7.0 h1:pitmyOg2pUVdW+nn2Lk/xqm2TaA08uxvOC0ns3sz6bM=
github.com/fluxcd/pkg/masktoken v0.7.0/go.mod h1:Lc1uoDjO1GY6+YdkK+ZqqBIBWquyV58nlSJ5S1N1IYU=
github.com/fluxcd/pkg/oci v0.48.0 h1:iSK4JDM0nx9plSlOGx2aI4td6aQdV/awrfXK/bzI35I=
github.com/fluxcd/pkg/oci v0.48.0/go.mod h1:rnUC8EOpzQp4rugpmopYFMnG3+CR1wqEV3356gHUtSY=
github.com/fluxcd/pkg/oci v0.49.0 h1:L8/dmNSIzqu6X8vzIkPLrW8NAF7Et/SnOuI8WJkXeq8=
github.com/fluxcd/pkg/oci v0.49.0/go.mod h1:iZkF4bQTpc6YOU5IJWMBp0Q8voGm7bkMYiAarJ9407U=
github.com/fluxcd/pkg/runtime v0.59.0 h1:3OrFkMJB39NcQ2vhhoxqls59sQVSn8U+thhyLbsQoA4=
github.com/fluxcd/pkg/runtime v0.59.0/go.mod h1:MFbfyNyyoYRgPxpdwC9/dCOkzo7Yxhu/cQ9NKyhvqc0=
github.com/fluxcd/pkg/sourceignore v0.12.0 h1:jCIe6d50rQ3wdXPF0+PhhqN0XrTRIq3upMomPelI8Mw=
Expand Down
85 changes: 58 additions & 27 deletions internal/controller/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

securejoin "github.com/cyphar/filepath-securejoin"
"github.com/fluxcd/pkg/auth"
authutils "github.com/fluxcd/pkg/auth/utils"
"github.com/fluxcd/pkg/git/github"
"github.com/fluxcd/pkg/runtime/logger"
"github.com/go-git/go-git/v5/plumbing/transport"
Expand Down Expand Up @@ -683,28 +684,28 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
return nil, e
}

var authOpts []auth.Option
// Configure provider authentication if specified.
var getCreds func() (*authutils.GitCredentials, error)
switch provider := obj.GetProvider(); provider {
case sourcev1.GitProviderAzure: // If AWS or GCP are added in the future they can be added here separated by a comma.
getCreds = func() (*authutils.GitCredentials, error) {
var opts []auth.Option

if r.TokenCache != nil {
involvedObject := cache.InvolvedObject{
Kind: sourcev1.GitRepositoryKind,
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
Operation: cache.OperationReconcile,
}
authOpts = append(authOpts, auth.WithCache(*r.TokenCache, involvedObject))
}
if r.TokenCache != nil {
involvedObject := cache.InvolvedObject{
Kind: sourcev1.GitRepositoryKind,
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
Operation: cache.OperationReconcile,
}
opts = append(opts, auth.WithCache(*r.TokenCache, involvedObject))
}

if proxyURL != nil {
authOpts = append(authOpts, auth.WithProxyURL(*proxyURL))
}
if proxyURL != nil {
opts = append(opts, auth.WithProxyURL(*proxyURL))
}

// Configure provider authentication if specified in spec
switch obj.GetProvider() {
case sourcev1.GitProviderAzure:
opts.ProviderOpts = &git.ProviderOptions{
Name: sourcev1.GitProviderAzure,
AuthOpts: authOpts,
return authutils.GetGitCredentials(ctx, provider, opts...)
}
case sourcev1.GitProviderGitHub:
// if provider is github, but secret ref is not specified
Expand All @@ -717,14 +718,30 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
return nil, e
}

opts.ProviderOpts = &git.ProviderOptions{
Name: sourcev1.GitProviderGitHub,
GitHubOpts: []github.OptFunc{
github.WithAppData(authData),
github.WithProxyURL(proxyURL),
github.WithCache(r.TokenCache, sourcev1.GitRepositoryKind,
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile),
},
getCreds = func() (*authutils.GitCredentials, error) {
var opts []github.OptFunc

if len(authData) > 0 {
opts = append(opts, github.WithAppData(authData))
}

if proxyURL != nil {
opts = append(opts, github.WithProxyURL(proxyURL))
}

if r.TokenCache != nil {
opts = append(opts, github.WithCache(r.TokenCache, sourcev1.GitRepositoryKind,
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile))
}

username, password, err := github.GetCredentials(ctx, opts...)
if err != nil {
return nil, err
}
return &authutils.GitCredentials{
Username: username,
Password: password,
}, nil
}
default:
// analyze secret, if it has github app data, perhaps provider should have been github.
Expand All @@ -737,6 +754,20 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
return nil, e
}
}
if getCreds != nil {
creds, err := getCreds()
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to configure authentication options: %w", err),
sourcev1.AuthenticationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
return nil, e
}
opts.BearerToken = creds.BearerToken
opts.Username = creds.Username
opts.Password = creds.Password
}
return opts, nil
}

Expand Down
39 changes: 18 additions & 21 deletions internal/controller/gitrepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,29 +787,27 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {

func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
tests := []struct {
name string
url string
secret *corev1.Secret
beforeFunc func(obj *sourcev1.GitRepository)
wantProviderOptsName string
wantErr error
name string
url string
secret *corev1.Secret
beforeFunc func(obj *sourcev1.GitRepository)
wantErr string
}{
{
name: "azure provider",
url: "https://dev.azure.com/foo/bar/_git/baz",
beforeFunc: func(obj *sourcev1.GitRepository) {
obj.Spec.Provider = sourcev1.GitProviderAzure
},
wantProviderOptsName: sourcev1.GitProviderAzure,
wantErr: "ManagedIdentityCredential",
},
{
name: "github provider with no secret ref",
url: "https://github.com/org/repo.git",
beforeFunc: func(obj *sourcev1.GitRepository) {
obj.Spec.Provider = sourcev1.GitProviderGitHub
},
wantProviderOptsName: sourcev1.GitProviderGitHub,
wantErr: errors.New("secretRef with github app data must be specified when provider is set to github"),
wantErr: "secretRef with github app data must be specified when provider is set to github",
},
{
name: "github provider with github app data in secret",
Expand All @@ -830,7 +828,7 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
Name: "githubAppSecret",
}
},
wantProviderOptsName: sourcev1.GitProviderGitHub,
wantErr: "Key must be a PEM encoded PKCS1 or PKCS8 key",
},
{
name: "generic provider with github app data in secret",
Expand All @@ -849,7 +847,7 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
Name: "githubAppSecret",
}
},
wantErr: errors.New("secretRef '/githubAppSecret' has github app data but provider is not set to github"),
wantErr: "secretRef '/githubAppSecret' has github app data but provider is not set to github",
},
{
name: "generic provider",
Expand All @@ -866,7 +864,7 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
Name: "authSecret",
}
},
wantErr: errors.New("failed to get secret '/authSecret': secrets \"authSecret\" not found"),
wantErr: "failed to get secret '/authSecret': secrets \"authSecret\" not found",
},
{
url: "https://example.com/org/repo",
Expand Down Expand Up @@ -899,20 +897,19 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
if tt.beforeFunc != nil {
tt.beforeFunc(obj)
}
opts, err := r.getAuthOpts(context.TODO(), obj, *url, nil)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
opts, err := r.getAuthOpts(ctx, obj, *url, nil)

if tt.wantErr != nil {
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr.Error()))
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
} else {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(opts).ToNot(BeNil())
if tt.wantProviderOptsName != "" {
g.Expect(opts.ProviderOpts).ToNot(BeNil())
g.Expect(opts.ProviderOpts.Name).To(Equal(tt.wantProviderOptsName))
} else {
g.Expect(opts.ProviderOpts).To(BeNil())
}
g.Expect(opts.BearerToken).To(BeEmpty())
g.Expect(opts.Username).To(BeEmpty())
g.Expect(opts.Password).To(BeEmpty())
}
})
}
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
if _, ok := keychain.(soci.Anonymous); obj.Spec.Provider != ociv1.GenericOCIProvider && ok {
var opts []auth.Option
if obj.Spec.ServiceAccountName != "" {
// Check object-level workload identity feature gate.
if !auth.IsObjectLevelWorkloadIdentityEnabled() {
const gate = auth.FeatureGateObjectLevelWorkloadIdentity
const msgFmt = "to use spec.serviceAccountName for provider authentication please enable the %s feature gate in the controller"
err := fmt.Errorf(msgFmt, gate)
return sreconcile.ResultEmpty, serror.NewStalling(err, meta.FeatureGateDisabledReason)
}
serviceAccount := client.ObjectKey{
Name: obj.Spec.ServiceAccountName,
Namespace: obj.GetNamespace(),
Expand Down
73 changes: 71 additions & 2 deletions internal/controller/ocirepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import (

kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/oci"
"github.com/fluxcd/pkg/runtime/conditions"
Expand Down Expand Up @@ -2971,10 +2972,10 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
}
}

func TestOCIRepository_stalled(t *testing.T) {
func TestOCIRepository_invalidURL(t *testing.T) {
g := NewWithT(t)

ns, err := testEnv.CreateNamespace(ctx, "ocirepository-stalled-test")
ns, err := testEnv.CreateNamespace(ctx, "ocirepository-invalid-url-test")
g.Expect(err).ToNot(HaveOccurred())
defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()

Expand Down Expand Up @@ -3013,6 +3014,74 @@ func TestOCIRepository_stalled(t *testing.T) {
g.Expect(stalledCondition.Reason).Should(Equal(sourcev1.URLInvalidReason))
}

func TestOCIRepository_objectLevelWorkloadIdentityFeatureGate(t *testing.T) {
g := NewWithT(t)

ns, err := testEnv.CreateNamespace(ctx, "ocirepository-olwifg-test")
g.Expect(err).ToNot(HaveOccurred())
defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()

err = testEnv.Create(ctx, &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.Name,
Name: "test",
},
})
g.Expect(err).NotTo(HaveOccurred())

obj := &ociv1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "ocirepository-reconcile",
Namespace: ns.Name,
},
Spec: ociv1.OCIRepositorySpec{
URL: "oci://ghcr.io/stefanprodan/manifests/podinfo",
Interval: metav1.Duration{Duration: 60 * time.Minute},
Provider: "aws",
ServiceAccountName: "test",
},
}

g.Expect(testEnv.Create(ctx, obj)).To(Succeed())

key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
resultobj := &ociv1.OCIRepository{}

g.Eventually(func() bool {
if err := testEnv.Get(ctx, key, resultobj); err != nil {
return false
}
return conditions.IsStalled(resultobj)
}).Should(BeTrue())

stalledCondition := conditions.Get(resultobj, meta.StalledCondition)
g.Expect(stalledCondition).ToNot(BeNil())
g.Expect(stalledCondition.Reason).Should(Equal(meta.FeatureGateDisabledReason))
g.Expect(stalledCondition.Message).Should(Equal("to use spec.serviceAccountName for provider authentication please enable the ObjectLevelWorkloadIdentity feature gate in the controller"))

t.Setenv(auth.EnvVarEnableObjectLevelWorkloadIdentity, "true")

g.Eventually(func() bool {
if err := testEnv.Get(ctx, key, resultobj); err != nil {
return false
}
resultobj.Annotations = map[string]string{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339),
}
return testEnv.Update(ctx, resultobj) == nil
}).Should(BeTrue())

g.Expect(testEnv.Update(ctx, resultobj)).To(Succeed())
g.Eventually(func() bool {
if err := testEnv.Get(ctx, key, resultobj); err != nil {
return false
}
logOCIRepoStatus(t, resultobj)
return !conditions.IsReady(resultobj) &&
conditions.GetReason(resultobj, meta.ReadyCondition) == sourcev1.AuthenticationFailedReason
}).Should(BeTrue())
}

func TestOCIRepository_reconcileStorage(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading