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 {