From 8579eed9fc70067747b8a8a76e8929960361f752 Mon Sep 17 00:00:00 2001 From: amarkdotdev <64331623+amarkdotdev@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:07:34 +0300 Subject: [PATCH] Add --insecure-allow-http flag to block insecure HTTP sources Wire helper.ConnectionOptions into GitRepository, HelmRepository, HelmChart, Bucket, and OCIRepository reconcilers so cluster admins can disallow plain HTTP URLs, HTTP proxies, and spec.insecure when the flag is set to false. Azure/GCP buckets with insecure stall as UnsupportedConnectionType. Fixes #806 Signed-off-by: amarkdotdev <64331623+amarkdotdev@users.noreply.github.com> --- internal/controller/bucket_controller.go | 27 ++- internal/controller/bucket_controller_test.go | 119 +++++++++++-- .../controller/gitrepository_controller.go | 23 ++- .../gitrepository_controller_fuzz_test.go | 5 +- .../gitrepository_controller_test.go | 158 +++++++++++++----- internal/controller/helmchart_controller.go | 36 +++- .../controller/helmchart_controller_test.go | 81 ++++++++- .../controller/helmrepository_controller.go | 15 +- .../helmrepository_controller_test.go | 75 ++++++--- .../controller/ocirepository_controller.go | 18 ++ .../ocirepository_controller_test.go | 88 ++++++++-- internal/controller/suite_test.go | 61 +++---- main.go | 54 +++--- 13 files changed, 599 insertions(+), 161 deletions(-) diff --git a/internal/controller/bucket_controller.go b/internal/controller/bucket_controller.go index 78af8f117..45615df7c 100644 --- a/internal/controller/bucket_controller.go +++ b/internal/controller/bucket_controller.go @@ -129,9 +129,10 @@ type BucketReconciler struct { kuberecorder.EventRecorder helper.Metrics - Storage *storage.Storage - ControllerName string - TokenCache *cache.TokenCache + Storage *storage.Storage + ControllerName string + TokenCache *cache.TokenCache + AllowInsecureHTTP bool patchOptions []patch.Option } @@ -860,6 +861,26 @@ func (r *BucketReconciler) setupCredentials(ctx context.Context, obj *sourcev1.B // createBucketProvider creates a provider-specific bucket client using the given credentials and configuration. // It handles different bucket providers (AWS, GCP, Azure, generic) and returns the appropriate client. func (r *BucketReconciler) createBucketProvider(ctx context.Context, obj *sourcev1.Bucket, creds *bucketCredentials) (BucketProvider, error) { + provider := obj.Spec.Provider + if (provider == sourcev1.BucketProviderAzure || provider == sourcev1.BucketProviderGoogle) && obj.Spec.Insecure { + return nil, serror.NewStalling( + fmt.Errorf("use of insecure HTTP connections isn't allowed for %s storage", provider), + meta.UnsupportedConnectionTypeReason, + ) + } + if obj.Spec.Insecure && !r.AllowInsecureHTTP { + return nil, serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + } + if creds.proxyURL != nil && creds.proxyURL.Scheme == "http" && !r.AllowInsecureHTTP { + return nil, serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + } + authOpts := []auth.Option{ auth.WithClient(r.Client), auth.WithServiceAccountNamespace(obj.GetNamespace()), diff --git a/internal/controller/bucket_controller_test.go b/internal/controller/bucket_controller_test.go index 00ed46cb7..292737d71 100644 --- a/internal/controller/bucket_controller_test.go +++ b/internal/controller/bucket_controller_test.go @@ -47,6 +47,7 @@ import ( "github.com/fluxcd/pkg/runtime/patch" sourcev1 "github.com/fluxcd/source-controller/api/v1" + serror "github.com/fluxcd/source-controller/internal/error" "github.com/fluxcd/source-controller/internal/index" gcsmock "github.com/fluxcd/source-controller/internal/mock/gcs" s3mock "github.com/fluxcd/source-controller/internal/mock/s3" @@ -84,9 +85,10 @@ func TestBucketReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, bucket)).NotTo(HaveOccurred()) r := &BucketReconciler{ - Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, + AllowInsecureHTTP: true, + Client: k8sClient, + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, } // NOTE: Only a real API server responds with an error in this scenario. _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(bucket)}) @@ -379,6 +381,7 @@ func TestBucketReconciler_reconcileStorage(t *testing.T) { }() r := &BucketReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.Bucket{}). @@ -918,10 +921,11 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) { } r := &BucketReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: record.NewFakeRecorder(32), + Client: clientBuilder.Build(), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } tmpDir := t.TempDir() @@ -977,6 +981,84 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) { } } +func TestBucketReconciler_reconcileSource_insecureHTTP(t *testing.T) { + tests := []struct { + name string + provider string + insecure bool + allowInsecureHTTP bool + wantReason string + wantMsg string + }{ + { + name: "generic insecure with AllowInsecureHTTP false stalls", + provider: sourcev1.BucketProviderGeneric, + insecure: true, + allowInsecureHTTP: false, + wantReason: meta.InsecureConnectionsDisallowedReason, + wantMsg: "use of insecure plain HTTP connections is blocked", + }, + { + name: "azure insecure is unsupported", + provider: sourcev1.BucketProviderAzure, + insecure: true, + allowInsecureHTTP: true, + wantReason: meta.UnsupportedConnectionTypeReason, + wantMsg: "use of insecure HTTP connections isn't allowed for azure storage", + }, + { + name: "gcp insecure is unsupported", + provider: sourcev1.BucketProviderGoogle, + insecure: true, + allowInsecureHTTP: true, + wantReason: meta.UnsupportedConnectionTypeReason, + wantMsg: "use of insecure HTTP connections isn't allowed for gcp storage", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + obj := &sourcev1.Bucket{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "insecure-http-", + Generation: 1, + }, + Spec: sourcev1.BucketSpec{ + Provider: tt.provider, + BucketName: "dummy", + Endpoint: "example.com", + Insecure: tt.insecure, + Timeout: &metav1.Duration{Duration: timeout}, + }, + } + + r := &BucketReconciler{ + AllowInsecureHTTP: tt.allowInsecureHTTP, + Client: fakeclient.NewClientBuilder(). + WithScheme(testEnv.GetScheme()). + WithStatusSubresource(&sourcev1.Bucket{}). + Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + } + + sp := patch.NewSerialPatcher(obj, r.Client) + _, err := r.reconcileSource(context.TODO(), sp, obj, index.NewDigester(), t.TempDir()) + g.Expect(err).To(HaveOccurred()) + var stalling *serror.Stalling + g.Expect(errors.As(err, &stalling)).To(BeTrue()) + g.Expect(stalling.Reason).To(Equal(tt.wantReason)) + g.Expect(err.Error()).To(ContainSubstring(tt.wantMsg)) + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(sourcev1.FetchFailedCondition, tt.wantReason, "%s", tt.wantMsg), + })) + }) + } +} + func TestBucketReconciler_reconcileSource_gcs(t *testing.T) { tests := []struct { name string @@ -1385,10 +1467,11 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) { } r := &BucketReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: record.NewFakeRecorder(32), + Client: clientBuilder.Build(), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } // Handle ObjectLevelWorkloadIdentity feature gate @@ -1588,10 +1671,11 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) { WithStatusSubresource(&sourcev1.Bucket{}) r := &BucketReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } obj := &sourcev1.Bucket{ @@ -1821,8 +1905,9 @@ func TestBucketReconciler_notify(t *testing.T) { } reconciler := &BucketReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: recorder, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } index := index.NewDigester(index.WithIndex(map[string]string{ "zzz": "qqq", diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go index a3c27c9b6..8a75f5865 100644 --- a/internal/controller/gitrepository_controller.go +++ b/internal/controller/gitrepository_controller.go @@ -168,9 +168,10 @@ type GitRepositoryReconciler struct { kuberecorder.EventRecorder helper.Metrics - Storage *storage.Storage - ControllerName string - TokenCache *cache.TokenCache + Storage *storage.Storage + ControllerName string + TokenCache *cache.TokenCache + AllowInsecureHTTP bool requeueDependency time.Duration features map[string]bool @@ -544,6 +545,22 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) return sreconcile.ResultEmpty, e } + if u.Scheme == "http" && !r.AllowInsecureHTTP { + e := serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } + if proxyURL != nil && proxyURL.Scheme == "http" && !r.AllowInsecureHTTP { + e := serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } authOpts, err := r.getAuthOpts(ctx, obj, *u, proxyURL) if err != nil { diff --git a/internal/controller/gitrepository_controller_fuzz_test.go b/internal/controller/gitrepository_controller_fuzz_test.go index c9c136820..50333d4c6 100644 --- a/internal/controller/gitrepository_controller_fuzz_test.go +++ b/internal/controller/gitrepository_controller_fuzz_test.go @@ -450,8 +450,9 @@ func ensureDependencies() error { startEnvServer(func(m manager.Manager) { utilruntime.Must((&GitRepositoryReconciler{ - Client: m.GetClient(), - Storage: storage, + AllowInsecureHTTP: true, + Client: m.GetClient(), + Storage: storage, }).SetupWithManagerAndOptions(m, GitRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), })) diff --git a/internal/controller/gitrepository_controller_test.go b/internal/controller/gitrepository_controller_test.go index 84b2074a8..fc5793443 100644 --- a/internal/controller/gitrepository_controller_test.go +++ b/internal/controller/gitrepository_controller_test.go @@ -231,9 +231,10 @@ func TestGitRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, gitRepo)).NotTo(HaveOccurred()) r := &GitRepositoryReconciler{ - Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, + AllowInsecureHTTP: true, + Client: k8sClient, + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, } // NOTE: Only a real API server responds with an error in this scenario. _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(gitRepo)}) @@ -344,10 +345,11 @@ func TestGitRepositoryReconciler_reconcileSource_emptyRepository(t *testing.T) { WithStatusSubresource(&sourcev1.GitRepository{}) r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } g.Expect(r.Client.Create(context.TODO(), obj)).ToNot(HaveOccurred()) @@ -369,6 +371,75 @@ func TestGitRepositoryReconciler_reconcileSource_emptyRepository(t *testing.T) { g.Expect(commit).ToNot(BeNil()) } +func TestGitRepositoryReconciler_reconcileSource_insecureHTTP(t *testing.T) { + tests := []struct { + name string + url string + allowInsecureHTTP bool + wantStalling bool + assertConditions []metav1.Condition + }{ + { + name: "HTTP URL with AllowInsecureHTTP false stalls with InsecureConnectionsDisallowed", + url: "https://github.com/example/repo", + allowInsecureHTTP: false, + wantStalling: true, + assertConditions: []metav1.Condition{ + *conditions.TrueCondition(sourcev1.FetchFailedCondition, meta.InsecureConnectionsDisallowedReason, "use of insecure plain HTTP connections is blocked"), + }, + }, + { + name: "HTTP URL with AllowInsecureHTTP true does not stall for insecure HTTP", + url: "https://github.com/example/repo", + allowInsecureHTTP: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + obj := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "insecure-http-", + Generation: 1, + }, + Spec: sourcev1.GitRepositorySpec{ + URL: tt.url, + Interval: metav1.Duration{Duration: interval}, + Timeout: &metav1.Duration{Duration: timeout}, + }, + } + + r := &GitRepositoryReconciler{ + AllowInsecureHTTP: tt.allowInsecureHTTP, + Client: fakeclient.NewClientBuilder(). + WithScheme(testEnv.GetScheme()). + WithStatusSubresource(&sourcev1.GitRepository{}). + Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + } + + sp := patch.NewSerialPatcher(obj, r.Client) + var commit git.Commit + var includes artifactSet + _, err := r.reconcileSource(context.TODO(), sp, obj, &commit, &includes, t.TempDir()) + g.Expect(err).To(HaveOccurred()) + var stalling *serror.Stalling + if tt.wantStalling { + g.Expect(errors.As(err, &stalling)).To(BeTrue()) + g.Expect(stalling.Reason).To(Equal(meta.InsecureConnectionsDisallowedReason)) + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions)) + } else { + g.Expect(errors.As(err, &stalling)).To(BeFalse()) + g.Expect(conditions.GetReason(obj, sourcev1.FetchFailedCondition)).ToNot(Equal(meta.InsecureConnectionsDisallowedReason)) + } + }) + } +} + func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) { type options struct { username string @@ -901,10 +972,11 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) { } r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } tmpDir := t.TempDir() @@ -1084,10 +1156,11 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) { obj := &sourcev1.GitRepository{} r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: record.NewFakeRecorder(32), + Client: clientBuilder.Build(), + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } url, err := url.Parse(tt.url) @@ -1301,6 +1374,7 @@ func TestGitRepositoryReconciler_reconcileSource_checkoutStrategy(t *testing.T) } r := &GitRepositoryReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.GitRepository{}). @@ -1509,10 +1583,11 @@ func TestGitRepositoryReconciler_reconcileArtifact(t *testing.T) { resetChmod(tt.dir, 0o750, 0o600) r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -1659,6 +1734,7 @@ func TestGitRepositoryReconciler_reconcileInclude(t *testing.T) { } r := &GitRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: storage, @@ -1913,6 +1989,7 @@ func TestGitRepositoryReconciler_reconcileStorage(t *testing.T) { }() r := &GitRepositoryReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.GitRepository{}). @@ -1971,10 +2048,11 @@ func TestGitRepositoryReconciler_reconcileDelete(t *testing.T) { g := NewWithT(t) r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -2762,10 +2840,11 @@ func TestGitRepositoryReconciler_verifySignature(t *testing.T) { } r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: record.NewFakeRecorder(32), + Client: clientBuilder.Build(), + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -2914,11 +2993,12 @@ func TestGitRepositoryReconciler_ConditionsUpdate(t *testing.T) { WithStatusSubresource(&sourcev1.GitRepository{}) r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } key := client.ObjectKeyFromObject(obj) @@ -3302,9 +3382,10 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { } reconciler := &GitRepositoryReconciler{ - EventRecorder: recorder, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: recorder, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } reconciler.notify(ctx, oldObj, newObj, tt.commit, tt.res, tt.resErr) @@ -3445,9 +3526,10 @@ func TestGitRepositoryReconciler_fetchIncludes(t *testing.T) { } r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ diff --git a/internal/controller/helmchart_controller.go b/internal/controller/helmchart_controller.go index 9197e11e7..1147d392c 100644 --- a/internal/controller/helmchart_controller.go +++ b/internal/controller/helmchart_controller.go @@ -135,6 +135,7 @@ type HelmChartReconciler struct { Getters helmgetter.Providers ControllerName string CosignVerifierFactory *scosign.CosignVerifierFactory + AllowInsecureHTTP bool Cache *cache.Cache TTL time.Duration @@ -487,7 +488,12 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, sp *patch.Ser // Handle any build error if retErr != nil { - if buildErr := new(chart.BuildError); errors.As(retErr, &buildErr) { + // Prefer Stalling (e.g. insecure HTTP blocked from dependency callback) + // so it is not rewritten as a generic/DependencyBuildError. + var stalling *serror.Stalling + if errors.As(retErr, &stalling) { + retErr = stalling + } else if buildErr := new(chart.BuildError); errors.As(retErr, &buildErr) { retErr = serror.NewGeneric( buildErr, buildErr.Reason.Reason, @@ -552,6 +558,15 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * return chartRepoConfigErrorReturn(err, obj) } + if repo.Spec.Insecure && !r.AllowInsecureHTTP { + e := serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } + registryClient, err := registry.NewClient(clientOpts.OCIAuth, clientOpts.TLSConfig, repo.Spec.Insecure) if err != nil { e := serror.NewGeneric( @@ -1027,6 +1042,12 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont var chartRepo repository.Downloader if helmreg.IsOCI(normalizedURL) { + if obj.Spec.Insecure && !r.AllowInsecureHTTP { + return nil, serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + } registryClient, err := registry.NewClient(clientOpts.OCIAuth, clientOpts.TLSConfig, obj.Spec.Insecure) if err != nil { return nil, fmt.Errorf("failed to create registry client: %w", err) @@ -1245,6 +1266,19 @@ func observeChartBuild(ctx context.Context, sp *patch.SerialPatcher, pOpts []pat } if err != nil { + // Propagate Stalling reasons (e.g. insecure HTTP) even when wrapped in BuildError. + var stalling *serror.Stalling + if errors.As(err, &stalling) { + conditions.Delete(obj, sourcev1.BuildFailedCondition) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, stalling.Reason, "%s", stalling) + return + } + if errors.Is(err, helper.ErrInsecureHTTPBlocked) { + conditions.Delete(obj, sourcev1.BuildFailedCondition) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, meta.InsecureConnectionsDisallowedReason, "%s", err) + return + } + var buildErr *chart.BuildError if ok := errors.As(err, &buildErr); !ok { buildErr = &chart.BuildError{ diff --git a/internal/controller/helmchart_controller_test.go b/internal/controller/helmchart_controller_test.go index 4101987b2..f2e39e638 100644 --- a/internal/controller/helmchart_controller_test.go +++ b/internal/controller/helmchart_controller_test.go @@ -111,6 +111,7 @@ func TestHelmChartReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, helmchart)).NotTo(HaveOccurred()) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: k8sClient, EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -516,6 +517,7 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) { }() r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmChart{}). @@ -793,6 +795,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) { } r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: st, @@ -1130,6 +1133,7 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Getters: testGetters, @@ -1383,6 +1387,7 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) { } r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Getters: testGetters, @@ -1623,6 +1628,7 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) { g := NewWithT(t) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.Scheme()). WithStatusSubresource(&sourcev1.HelmChart{}). @@ -1834,6 +1840,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) { g := NewWithT(t) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmChart{}). @@ -1911,6 +1918,7 @@ func TestHelmChartReconciler_getSource(t *testing.T) { WithObjects(mocks...) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), @@ -2028,6 +2036,7 @@ func TestHelmChartReconciler_reconcileDelete(t *testing.T) { g := NewWithT(t) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, @@ -2165,6 +2174,7 @@ func TestHelmChartReconciler_reconcileSubRecs(t *testing.T) { g := NewWithT(t) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmChart{}). @@ -2403,8 +2413,9 @@ func TestHelmChartReconciler_notify(t *testing.T) { } reconciler := &HelmChartReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: recorder, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } build := &chart.Build{ Name: "foo", @@ -2725,10 +2736,11 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { } r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } var b chart.Build @@ -2767,6 +2779,60 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { } } +func TestHelmChartReconciler_buildFromHelmRepository_insecureHTTP(t *testing.T) { + g := NewWithT(t) + + repo := &sourcev1.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "oci-insecure", + Namespace: "default", + }, + Spec: sourcev1.HelmRepositorySpec{ + URL: "oci://example.com/test/repo", + Type: sourcev1.HelmRepositoryTypeOCI, + Insecure: true, + Timeout: &metav1.Duration{Duration: timeout}, + }, + } + obj := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "insecure-http-", + Generation: 1, + }, + Spec: sourcev1.HelmChartSpec{ + Chart: "chart", + Version: "0.1.0", + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: repo.Name, + }, + Interval: metav1.Duration{Duration: interval}, + }, + } + + r := &HelmChartReconciler{ + AllowInsecureHTTP: false, + Client: fakeclient.NewClientBuilder(). + WithScheme(testEnv.GetScheme()). + WithStatusSubresource(&sourcev1.HelmChart{}). + WithObjects(repo). + Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + } + + var b chart.Build + _, err := r.buildFromHelmRepository(context.TODO(), obj, repo, &b) + g.Expect(err).To(HaveOccurred()) + var stalling *serror.Stalling + g.Expect(errors.As(err, &stalling)).To(BeTrue()) + g.Expect(stalling.Reason).To(Equal(meta.InsecureConnectionsDisallowedReason)) + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(sourcev1.FetchFailedCondition, meta.InsecureConnectionsDisallowedReason, "use of insecure plain HTTP connections is blocked"), + })) +} + func TestHelmChartRepository_reconcileSource_verifyOCISourceSignature_keyless(t *testing.T) { tests := []struct { name string @@ -2884,6 +2950,7 @@ func TestHelmChartRepository_reconcileSource_verifyOCISourceSignature_keyless(t clientBuilder.WithObjects(repository) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Getters: testGetters, @@ -3190,6 +3257,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignatureNotation(t *t clientBuilder.WithObjects(repository, secret, caSecret) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Getters: testGetters, @@ -3442,6 +3510,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignatureCosign(t *tes clientBuilder.WithObjects(repository, secret) r := &HelmChartReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Getters: testGetters, diff --git a/internal/controller/helmrepository_controller.go b/internal/controller/helmrepository_controller.go index 0caf5c9e3..a568fdff4 100644 --- a/internal/controller/helmrepository_controller.go +++ b/internal/controller/helmrepository_controller.go @@ -108,9 +108,10 @@ type HelmRepositoryReconciler struct { kuberecorder.EventRecorder helper.Metrics - Getters helmgetter.Providers - Storage *storage.Storage - ControllerName string + Getters helmgetter.Providers + Storage *storage.Storage + ControllerName string + AllowInsecureHTTP bool Cache *cache.Cache TTL time.Duration @@ -410,6 +411,14 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patc conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) return sreconcile.ResultEmpty, e } + if u, err := url.Parse(normalizedURL); err == nil && u.Scheme == "http" && !r.AllowInsecureHTTP { + e := serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } clientOpts, err := getter.GetClientOpts(ctx, r.Client, obj, normalizedURL) if err != nil { diff --git a/internal/controller/helmrepository_controller_test.go b/internal/controller/helmrepository_controller_test.go index f76d4f221..481cfa2ee 100644 --- a/internal/controller/helmrepository_controller_test.go +++ b/internal/controller/helmrepository_controller_test.go @@ -36,6 +36,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -85,9 +86,10 @@ func TestHelmRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, helmrepo)).NotTo(HaveOccurred()) r := &HelmRepositoryReconciler{ - Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, + AllowInsecureHTTP: true, + Client: k8sClient, + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, } // NOTE: Only a real API server responds with an error in this scenario. _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(helmrepo)}) @@ -350,6 +352,7 @@ func TestHelmRepositoryReconciler_reconcileStorage(t *testing.T) { g := NewWithT(t) r := &HelmRepositoryReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmRepository{}). @@ -414,17 +417,18 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) { } tests := []struct { - name string - protocol string - server options - url string - secret *corev1.Secret - beforeFunc func(t *WithT, obj *sourcev1.HelmRepository) - revFunc func(t *WithT, server *helmtestserver.HelmServer, secret *corev1.Secret) digest.Digest - afterFunc func(t *WithT, obj *sourcev1.HelmRepository, artifact meta.Artifact, chartRepo *repository.ChartRepository) - want sreconcile.Result - wantErr bool - assertConditions []metav1.Condition + name string + protocol string + server options + url string + secret *corev1.Secret + beforeFunc func(t *WithT, obj *sourcev1.HelmRepository) + revFunc func(t *WithT, server *helmtestserver.HelmServer, secret *corev1.Secret) digest.Digest + afterFunc func(t *WithT, obj *sourcev1.HelmRepository, artifact meta.Artifact, chartRepo *repository.ChartRepository) + want sreconcile.Result + wantErr bool + allowInsecureHTTP *bool + assertConditions []metav1.Condition }{ { name: "HTTPS with certSecretRef non-matching CA succeeds via system CA pool", @@ -782,6 +786,27 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) { t.Expect(artifact.Revision).To(BeEmpty()) }, }, + { + name: "HTTP URL with AllowInsecureHTTP false stalls with InsecureConnectionsDisallowed", + protocol: "http", + beforeFunc: func(t *WithT, obj *sourcev1.HelmRepository) { + conditions.MarkReconciling(obj, meta.ProgressingReason, "foo") + conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar") + }, + want: sreconcile.ResultEmpty, + wantErr: true, + allowInsecureHTTP: ptr.To(false), + assertConditions: []metav1.Condition{ + *conditions.TrueCondition(sourcev1.FetchFailedCondition, meta.InsecureConnectionsDisallowedReason, "use of insecure plain HTTP connections is blocked"), + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"), + *conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"), + }, + afterFunc: func(t *WithT, obj *sourcev1.HelmRepository, artifact meta.Artifact, chartRepo *repository.ChartRepository) { + t.Expect(chartRepo.Path).To(BeEmpty()) + t.Expect(chartRepo.Index).To(BeNil()) + t.Expect(artifact.Revision).To(BeEmpty()) + }, + }, { name: "Invalid URL makes FetchFailed=True and returns stalling error", protocol: "http", @@ -1027,12 +1052,17 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) { rev = tt.revFunc(g, server, secret) } + allowInsecureHTTP := true + if tt.allowInsecureHTTP != nil { + allowInsecureHTTP = *tt.allowInsecureHTTP + } r := &HelmRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - Storage: testStorage, - Getters: testGetters, - patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: allowInsecureHTTP, + EventRecorder: record.NewFakeRecorder(32), + Client: clientBuilder.Build(), + Storage: testStorage, + Getters: testGetters, + patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), } if tt.beforeFunc != nil { tt.beforeFunc(g, obj) @@ -1167,6 +1197,7 @@ func TestHelmRepositoryReconciler_reconcileArtifact(t *testing.T) { g := NewWithT(t) r := &HelmRepositoryReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmRepository{}). @@ -1334,6 +1365,7 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { g := NewWithT(t) r := &HelmRepositoryReconciler{ + AllowInsecureHTTP: true, Client: fakeclient.NewClientBuilder(). WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmRepository{}). @@ -1555,8 +1587,9 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { } reconciler := &HelmRepositoryReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: recorder, + patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), } chartRepo := repository.ChartRepository{ URL: "some-address", diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index ec941a1fd..5cbed88e8 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -145,6 +145,7 @@ type OCIRepositoryReconciler struct { ControllerName string TokenCache *cache.TokenCache CosignVerifierFactory *scosign.CosignVerifierFactory + AllowInsecureHTTP bool requeueDependency time.Duration patchOptions []patch.Option @@ -341,6 +342,15 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch conditions.Delete(obj, sourcev1.SourceVerifiedCondition) } + if obj.Spec.Insecure && !r.AllowInsecureHTTP { + e := serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } + // Generate the registry credential keychain either from static credentials or using cloud OIDC keychain, err := r.keychain(ctx, obj) if err != nil { @@ -367,6 +377,14 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) return sreconcile.ResultEmpty, e } + if proxyURL.Scheme == "http" && !r.AllowInsecureHTTP { + e := serror.NewStalling( + fmt.Errorf("%w", helper.ErrInsecureHTTPBlocked), + meta.InsecureConnectionsDisallowedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } } if _, ok := keychain.(soci.Anonymous); obj.Spec.Provider != "" && obj.Spec.Provider != sourcev1.GenericOCIProvider && ok { diff --git a/internal/controller/ocirepository_controller_test.go b/internal/controller/ocirepository_controller_test.go index 3b18e1fe1..7c7cecf05 100644 --- a/internal/controller/ocirepository_controller_test.go +++ b/internal/controller/ocirepository_controller_test.go @@ -109,6 +109,7 @@ func TestOCIRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, ocirepo)).NotTo(HaveOccurred()) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: k8sClient, EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -413,6 +414,45 @@ func TestOCIRepository_Reconcile_MediaType(t *testing.T) { } } +func TestOCIRepository_reconcileSource_insecureHTTP(t *testing.T) { + g := NewWithT(t) + + obj := &sourcev1.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "insecure-http-", + Generation: 1, + }, + Spec: sourcev1.OCIRepositorySpec{ + URL: "oci://example.com/org/repo", + Interval: metav1.Duration{Duration: interval}, + Timeout: &metav1.Duration{Duration: timeout}, + Insecure: true, + }, + } + + r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: false, + Client: fakeclient.NewClientBuilder(). + WithScheme(testEnv.GetScheme()). + WithStatusSubresource(&sourcev1.OCIRepository{}). + Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + CosignVerifierFactory: testCosignVerifierFactory, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + } + + sp := patch.NewSerialPatcher(obj, r.Client) + _, err := r.reconcileSource(ctx, sp, obj, &meta.Artifact{}, t.TempDir()) + g.Expect(err).To(HaveOccurred()) + var stalling *serror.Stalling + g.Expect(errors.As(err, &stalling)).To(BeTrue()) + g.Expect(stalling.Reason).To(Equal(meta.InsecureConnectionsDisallowedReason)) + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(sourcev1.FetchFailedCondition, meta.InsecureConnectionsDisallowedReason, "use of insecure plain HTTP connections is blocked"), + })) +} + func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) { type secretOptions struct { username string @@ -805,6 +845,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) { } r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -1265,6 +1306,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -1468,6 +1510,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignatureNotation(t *testi WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -1832,6 +1875,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceTrustPolicyNotation(t *tes WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -2129,6 +2173,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignatureCosign(t *testing WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -2399,6 +2444,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature_keyless(t *testi WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, Client: clientBuilder.Build(), EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, @@ -2585,10 +2631,11 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -2817,10 +2864,11 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -2982,10 +3030,11 @@ func TestOCIRepository_getArtifactRef(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -3316,10 +3365,11 @@ func TestOCIRepository_reconcileStorage(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -3380,6 +3430,7 @@ func TestOCIRepository_ReconcileDelete(t *testing.T) { g := NewWithT(t) r := &OCIRepositoryReconciler{ + AllowInsecureHTTP: true, EventRecorder: record.NewFakeRecorder(32), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, @@ -3515,8 +3566,9 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { } reconciler := &OCIRepositoryReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + AllowInsecureHTTP: true, + EventRecorder: recorder, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } reconciler.notify(ctx, oldObj, newObj, tt.res, tt.resErr) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 53da2f74e..7e147e06c 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -321,10 +321,11 @@ func TestMain(m *testing.M) { defer testRegistryServer.Close() if err := (&GitRepositoryReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Storage: testStorage, + AllowInsecureHTTP: true, + Client: testEnv, + EventRecorder: record.NewFakeRecorder(32), + Metrics: testMetricsH, + Storage: testStorage, }).SetupWithManager(testEnv, GitRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -332,10 +333,11 @@ func TestMain(m *testing.M) { } if err := (&BucketReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Storage: testStorage, + AllowInsecureHTTP: true, + Client: testEnv, + EventRecorder: record.NewFakeRecorder(32), + Metrics: testMetricsH, + Storage: testStorage, }).SetupWithManager(testEnv, BucketReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -346,10 +348,11 @@ func TestMain(m *testing.M) { cacheRecorder := cache.MustMakeMetrics() if err := (&OCIRepositoryReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Storage: testStorage, + AllowInsecureHTTP: true, + Client: testEnv, + EventRecorder: record.NewFakeRecorder(32), + Metrics: testMetricsH, + Storage: testStorage, }).SetupWithManager(testEnv, OCIRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -357,14 +360,15 @@ func TestMain(m *testing.M) { } if err := (&HelmRepositoryReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Getters: testGetters, - Storage: testStorage, - Cache: testCache, - TTL: 1 * time.Second, - CacheRecorder: cacheRecorder, + AllowInsecureHTTP: true, + Client: testEnv, + EventRecorder: record.NewFakeRecorder(32), + Metrics: testMetricsH, + Getters: testGetters, + Storage: testStorage, + Cache: testCache, + TTL: 1 * time.Second, + CacheRecorder: cacheRecorder, }).SetupWithManager(testEnv, HelmRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -372,14 +376,15 @@ func TestMain(m *testing.M) { } if err := (&HelmChartReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Getters: testGetters, - Storage: testStorage, - Cache: testCache, - TTL: 1 * time.Second, - CacheRecorder: cacheRecorder, + AllowInsecureHTTP: true, + Client: testEnv, + EventRecorder: record.NewFakeRecorder(32), + Metrics: testMetricsH, + Getters: testGetters, + Storage: testStorage, + Cache: testCache, + TTL: 1 * time.Second, + CacheRecorder: cacheRecorder, }).SetupWithManager(ctx, testEnv, HelmChartReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { diff --git a/main.go b/main.go index 75d897bd8..f8d98796c 100644 --- a/main.go +++ b/main.go @@ -117,6 +117,7 @@ func main() { helmCachePurgeInterval string tokenCacheOptions pkgcache.TokenFlags defaultServiceAccount string + connOptions helper.ConnectionOptions ) flag.StringVar(&metricsAddr, "metrics-addr", envOrDefault("METRICS_ADDR", ":8080"), @@ -155,6 +156,7 @@ func main() { watchOptions.BindFlags(flag.CommandLine) intervalJitterOptions.BindFlags(flag.CommandLine) tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize) + connOptions.BindFlags(flag.CommandLine) flag.Parse() @@ -169,6 +171,11 @@ func main() { os.Exit(1) } + if err := connOptions.CheckEnvironmentCompatibility(); err != nil { + setupLog.Error(err, "invalid connection options") + os.Exit(1) + } + switch enabled, err := features.Enabled(auth.FeatureGateObjectLevelWorkloadIdentity); { case err != nil: setupLog.Error(err, "unable to check feature gate "+auth.FeatureGateObjectLevelWorkloadIdentity) @@ -229,12 +236,13 @@ func main() { ctx := ctrl.SetupSignalHandler() if err := (&controller.GitRepositoryReconciler{ - Client: mgr.GetClient(), - EventRecorder: eventRecorder, - Metrics: metrics, - Storage: storage, - ControllerName: controllerName, - TokenCache: tokenCache, + Client: mgr.GetClient(), + EventRecorder: eventRecorder, + Metrics: metrics, + Storage: storage, + ControllerName: controllerName, + TokenCache: tokenCache, + AllowInsecureHTTP: connOptions.AllowHTTP, }).SetupWithManager(mgr, controller.GitRepositoryReconcilerOptions{ DependencyRequeueInterval: requeueDependency, RateLimiter: helper.GetRateLimiter(rateLimiterOptions), @@ -244,15 +252,16 @@ func main() { } if err := (&controller.HelmRepositoryReconciler{ - Client: mgr.GetClient(), - EventRecorder: eventRecorder, - Metrics: metrics, - Storage: storage, - Getters: getters, - ControllerName: controllerName, - Cache: helmIndexCache, - TTL: helmIndexCacheItemTTL, - CacheRecorder: cacheRecorder, + Client: mgr.GetClient(), + EventRecorder: eventRecorder, + Metrics: metrics, + Storage: storage, + Getters: getters, + ControllerName: controllerName, + Cache: helmIndexCache, + TTL: helmIndexCacheItemTTL, + CacheRecorder: cacheRecorder, + AllowInsecureHTTP: connOptions.AllowHTTP, }).SetupWithManager(mgr, controller.HelmRepositoryReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { @@ -271,6 +280,7 @@ func main() { Cache: helmIndexCache, TTL: helmIndexCacheItemTTL, CacheRecorder: cacheRecorder, + AllowInsecureHTTP: connOptions.AllowHTTP, }).SetupWithManager(ctx, mgr, controller.HelmChartReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { @@ -279,12 +289,13 @@ func main() { } if err := (&controller.BucketReconciler{ - Client: mgr.GetClient(), - EventRecorder: eventRecorder, - Metrics: metrics, - Storage: storage, - ControllerName: controllerName, - TokenCache: tokenCache, + Client: mgr.GetClient(), + EventRecorder: eventRecorder, + Metrics: metrics, + Storage: storage, + ControllerName: controllerName, + TokenCache: tokenCache, + AllowInsecureHTTP: connOptions.AllowHTTP, }).SetupWithManager(mgr, controller.BucketReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { @@ -300,6 +311,7 @@ func main() { TokenCache: tokenCache, CosignVerifierFactory: CosignVerifierFactory, Metrics: metrics, + AllowInsecureHTTP: connOptions.AllowHTTP, }).SetupWithManager(mgr, controller.OCIRepositoryReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil {