From 20b68791cecf1b77d42a5906fee0c8b2e97bcbd9 Mon Sep 17 00:00:00 2001 From: Yegor Tokmakov Date: Sat, 31 Jan 2026 13:20:17 +0100 Subject: [PATCH] OCISkipRegistryValidation flag Signed-off-by: Yegor Tokmakov --- auth/aws/provider.go | 14 +++++++++++++ auth/aws/provider_test.go | 33 +++++++++++++++++++++++++++++++ auth/azure/provider.go | 9 +++++++++ auth/azure/provider_test.go | 37 +++++++++++++++++++++++++++++++++++ auth/controller_flags.go | 19 ++++++++++++++++++ auth/controller_flags_test.go | 26 ++++++++++++++++++++++++ auth/gcp/provider.go | 5 +++++ auth/gcp/provider_test.go | 33 +++++++++++++++++++++++++++++++ 8 files changed, 176 insertions(+) diff --git a/auth/aws/provider.go b/auth/aws/provider.go index 9cec5c6b..0964f903 100644 --- a/auth/aws/provider.go +++ b/auth/aws/provider.go @@ -194,6 +194,8 @@ var registryRegex = regexp.MustCompile(registryPattern) // ParseArtifactRepository implements auth.Provider. // ParseArtifactRepository returns the ECR region, unless the registry // is public.ecr.aws, in which case it returns public.ecr.aws. +// When skip validation is enabled and the registry doesn't match, +// it returns the provider name. func (Provider) ParseArtifactRepository(artifactRepository string) (string, error) { registry, err := auth.GetRegistryFromArtifactRepository(artifactRepository) if err != nil { @@ -206,6 +208,10 @@ func (Provider) ParseArtifactRepository(artifactRepository string) (string, erro parts := registryRegex.FindAllStringSubmatch(registry, -1) if len(parts) < 1 || len(parts[0]) < 3 { + // Skip validation if configured (allows custom registry proxies) + if auth.GetOCISkipRegistryValidation() { + return ProviderName, nil + } return "", fmt.Errorf("invalid AWS registry: '%s'. must match %s", registry, registryPattern) } @@ -220,6 +226,14 @@ func getECRRegionFromRegistryInput(registryInput string) string { // https://docs.aws.amazon.com/AmazonECR/latest/public/public-registry-auth.html#public-registry-auth-token return "us-east-1" } + if registryInput == ProviderName { + // When using a proxy with skip validation, fall back to AWS_REGION. + // If AWS_REGION is not set, use us-east-1 as a default. + if region := os.Getenv("AWS_REGION"); region != "" { + return region + } + return "us-east-1" + } return registryInput } diff --git a/auth/aws/provider_test.go b/auth/aws/provider_test.go index 92b928f0..b2d30bf6 100644 --- a/auth/aws/provider_test.go +++ b/auth/aws/provider_test.go @@ -387,6 +387,39 @@ func TestProvider_ParseArtifactRepository(t *testing.T) { } } +func TestProvider_ParseArtifactRepository_SkipValidation(t *testing.T) { + g := NewWithT(t) + + auth.SetOCISkipRegistryValidation(true) + t.Cleanup(func() { auth.SetOCISkipRegistryValidation(false) }) + + // Test that invalid registries are accepted when skip validation is enabled + for _, tt := range []struct { + name string + artifactRepository string + }{ + { + name: "custom proxy", + artifactRepository: "oci-gateway.example.org/oci/charts/", + }, + { + name: "non-AWS registry", + artifactRepository: "gcr.io/foo/bar:baz", + }, + { + name: "private registry", + artifactRepository: "registry.internal.company.com/images", + }, + } { + t.Run(tt.name, func(t *testing.T) { + region, err := aws.Provider{}.ParseArtifactRepository(tt.artifactRepository) + + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(region).To(Equal("aws")) + }) + } +} + func TestProvider_NewRESTConfig(t *testing.T) { for _, tt := range []struct { name string diff --git a/auth/azure/provider.go b/auth/azure/provider.go index 84fe495e..919b86d7 100644 --- a/auth/azure/provider.go +++ b/auth/azure/provider.go @@ -183,10 +183,19 @@ func (Provider) ParseArtifactRepository(artifactRepository string) (string, erro if strings.HasSuffix(registry, registrySuffix) { return registry, nil } + // Skip validation if configured (allows custom registry proxies) + if auth.GetOCISkipRegistryValidation() { + return registry, nil + } return "", fmt.Errorf("invalid Azure registry: '%s'. must end with %s", registry, registrySuffix) } + // Skip validation if configured (allows custom registry proxies) + if auth.GetOCISkipRegistryValidation() { + return registry, nil + } + return "", fmt.Errorf("invalid Azure registry: '%s'. must match %s", registry, registryPattern) } diff --git a/auth/azure/provider_test.go b/auth/azure/provider_test.go index ea0909f8..8a010973 100644 --- a/auth/azure/provider_test.go +++ b/auth/azure/provider_test.go @@ -313,6 +313,43 @@ func TestProvider_ParseArtifactRegistry(t *testing.T) { } } +func TestProvider_ParseArtifactRegistry_SkipValidation(t *testing.T) { + g := NewWithT(t) + + auth.SetOCISkipRegistryValidation(true) + t.Cleanup(func() { auth.SetOCISkipRegistryValidation(false) }) + + // Test that invalid registries are accepted when skip validation is enabled + for _, tt := range []struct { + name string + artifactRepository string + expectedRegistry string + }{ + { + name: "custom proxy", + artifactRepository: "oci-gateway.example.org/oci/charts/", + expectedRegistry: "oci-gateway.example.org", + }, + { + name: "non-Azure registry", + artifactRepository: "gcr.io/foo/bar:baz", + expectedRegistry: "gcr.io", + }, + { + name: "private registry", + artifactRepository: "registry.internal.company.com/images", + expectedRegistry: "registry.internal.company.com", + }, + } { + t.Run(tt.name, func(t *testing.T) { + registry, err := azure.Provider{}.ParseArtifactRepository(tt.artifactRepository) + + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(registry).To(Equal(tt.expectedRegistry)) + }) + } +} + func TestProvider_GetAccessTokenOptionsForArtifactRepository(t *testing.T) { for _, tt := range []struct { name string diff --git a/auth/controller_flags.go b/auth/controller_flags.go index fa44a073..d2a474c9 100644 --- a/auth/controller_flags.go +++ b/auth/controller_flags.go @@ -34,6 +34,11 @@ const ( // service account name to be used when .spec.decryption.serviceAccountName is // not specified in the object. ControllerFlagDefaultDecryptionServiceAccount = "default-decryption-service-account" + + // ControllerFlagOCISkipRegistryValidation defines the flag for skipping OCI registry + // domain validation for cloud provider authentication. This allows using custom + // registry proxies/gateways with workload identity authentication. + ControllerFlagOCISkipRegistryValidation = "oci-skip-registry-validation" ) var ( @@ -48,6 +53,10 @@ var ( // defaultDecryptionServiceAccount stores the default decryption // service account name. defaultDecryptionServiceAccount string + + // ociSkipRegistryValidation stores whether to skip OCI registry + // domain validation for cloud provider authentication. + ociSkipRegistryValidation bool ) // ErrDefaultServiceAccountNotFound is returned when a default service account @@ -84,6 +93,16 @@ func GetDefaultDecryptionServiceAccount() string { return defaultDecryptionServiceAccount } +// SetOCISkipRegistryValidation sets whether to skip OCI registry domain validation. +func SetOCISkipRegistryValidation(skip bool) { + ociSkipRegistryValidation = skip +} + +// GetOCISkipRegistryValidation returns whether to skip OCI registry domain validation. +func GetOCISkipRegistryValidation() bool { + return ociSkipRegistryValidation +} + func getDefaultServiceAccount() string { // Here we can detect a default service account by checking either the default // service account or the default kubeconfig service account because these two diff --git a/auth/controller_flags_test.go b/auth/controller_flags_test.go index 05b44a21..b218526f 100644 --- a/auth/controller_flags_test.go +++ b/auth/controller_flags_test.go @@ -101,3 +101,29 @@ func TestGetDefaultDecryptionServiceAccount(t *testing.T) { g.Expect(auth.GetDefaultDecryptionServiceAccount()).To(Equal("")) }) } + +func TestSetOCISkipRegistryValidation(t *testing.T) { + g := NewWithT(t) + + auth.SetOCISkipRegistryValidation(true) + t.Cleanup(func() { auth.SetOCISkipRegistryValidation(false) }) + + g.Expect(auth.GetOCISkipRegistryValidation()).To(BeTrue()) +} + +func TestGetOCISkipRegistryValidation(t *testing.T) { + t.Run("returns true when set", func(t *testing.T) { + g := NewWithT(t) + + auth.SetOCISkipRegistryValidation(true) + t.Cleanup(func() { auth.SetOCISkipRegistryValidation(false) }) + + g.Expect(auth.GetOCISkipRegistryValidation()).To(BeTrue()) + }) + + t.Run("returns false when not set", func(t *testing.T) { + g := NewWithT(t) + + g.Expect(auth.GetOCISkipRegistryValidation()).To(BeFalse()) + }) +} diff --git a/auth/gcp/provider.go b/auth/gcp/provider.go index 71dc5bbc..fc979d78 100644 --- a/auth/gcp/provider.go +++ b/auth/gcp/provider.go @@ -174,6 +174,11 @@ func (Provider) ParseArtifactRepository(artifactRepository string) (string, erro return "", err } + // Skip registry validation if configured (allows custom registry proxies) + if auth.GetOCISkipRegistryValidation() { + return ProviderName, nil + } + if !registryRegex.MatchString(registry) { return "", fmt.Errorf("invalid GCP registry: '%s'. must match %s", registry, registryPattern) diff --git a/auth/gcp/provider_test.go b/auth/gcp/provider_test.go index 82f203ed..c70db422 100644 --- a/auth/gcp/provider_test.go +++ b/auth/gcp/provider_test.go @@ -331,6 +331,39 @@ func TestProvider_ParseArtifactRegistry(t *testing.T) { } } +func TestProvider_ParseArtifactRegistry_SkipValidation(t *testing.T) { + g := NewWithT(t) + + auth.SetOCISkipRegistryValidation(true) + t.Cleanup(func() { auth.SetOCISkipRegistryValidation(false) }) + + // Test that invalid registries are accepted when skip validation is enabled + for _, tt := range []struct { + name string + artifactRepository string + }{ + { + name: "custom proxy", + artifactRepository: "oci-gateway.example.org/oci/charts/", + }, + { + name: "non-GCP registry", + artifactRepository: "012345678901.dkr.ecr.us-east-1.amazonaws.com", + }, + { + name: "private registry", + artifactRepository: "registry.internal.company.com/images", + }, + } { + t.Run(tt.name, func(t *testing.T) { + cacheKey, err := gcp.Provider{}.ParseArtifactRepository(tt.artifactRepository) + + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(cacheKey).To(Equal("gcp")) + }) + } +} + func TestProvider_NewRESTConfig(t *testing.T) { for _, tt := range []struct { name string