Skip to content

Commit cf0e9ac

Browse files
authored
Merge pull request #884 from souleb/fix-874
[OCI] Static credentials should take precedence over the OIDC provider
2 parents e22a664 + 869c73d commit cf0e9ac

8 files changed

+468
-70
lines changed

controllers/helmchart_controller.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,10 +516,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
516516
}
517517

518518
loginOpts = append([]helmreg.LoginOption{}, loginOpt)
519-
}
520-
521-
if repo.Spec.Provider != sourcev1.GenericOCIProvider && repo.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
522-
auth, authErr := oidcAuth(ctxTimeout, repo)
519+
} else if repo.Spec.Provider != sourcev1.GenericOCIProvider && repo.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
520+
auth, authErr := oidcAuthFromAdapter(ctxTimeout, repo.Spec.URL, repo.Spec.Provider)
523521
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
524522
e := &serror.Event{
525523
Err: fmt.Errorf("failed to get credential from %s: %w", repo.Spec.Provider, authErr),
@@ -991,10 +989,8 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
991989
}
992990

993991
loginOpts = append([]helmreg.LoginOption{}, loginOpt)
994-
}
995-
996-
if repo.Spec.Provider != sourcev1.GenericOCIProvider && repo.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
997-
auth, authErr := oidcAuth(ctxTimeout, repo)
992+
} else if repo.Spec.Provider != sourcev1.GenericOCIProvider && repo.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
993+
auth, authErr := oidcAuthFromAdapter(ctxTimeout, repo.Spec.URL, repo.Spec.Provider)
998994
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
999995
return nil, fmt.Errorf("failed to get credential from %s: %w", repo.Spec.Provider, authErr)
1000996
}

controllers/helmchart_controller_test.go

Lines changed: 219 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
4545
"sigs.k8s.io/controller-runtime/pkg/client"
4646
"sigs.k8s.io/controller-runtime/pkg/client/fake"
47+
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
4748
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4849

4950
"github.com/fluxcd/pkg/apis/meta"
@@ -893,21 +894,11 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
893894
chartPath = "testdata/charts/helmchart-0.1.0.tgz"
894895
)
895896

896-
// Login to the registry
897-
err := testRegistryServer.registryClient.Login(testRegistryServer.registryHost,
898-
helmreg.LoginOptBasicAuth(testRegistryUsername, testRegistryPassword),
899-
helmreg.LoginOptInsecure(true))
900-
g.Expect(err).NotTo(HaveOccurred())
901-
902897
// Load a test chart
903898
chartData, err := ioutil.ReadFile(chartPath)
904-
g.Expect(err).NotTo(HaveOccurred())
905-
metadata, err := extractChartMeta(chartData)
906-
g.Expect(err).NotTo(HaveOccurred())
907899

908900
// Upload the test chart
909-
ref := fmt.Sprintf("%s/testrepo/%s:%s", testRegistryServer.registryHost, metadata.Name, metadata.Version)
910-
_, err = testRegistryServer.registryClient.Push(chartData, ref)
901+
metadata, err := loadTestChartToOCI(chartData, chartPath, testRegistryServer)
911902
g.Expect(err).NotTo(HaveOccurred())
912903

913904
storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords)
@@ -2038,6 +2029,194 @@ func TestHelmChartReconciler_notify(t *testing.T) {
20382029
}
20392030
}
20402031

2032+
func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
2033+
const (
2034+
chartPath = "testdata/charts/helmchart-0.1.0.tgz"
2035+
)
2036+
2037+
type secretOptions struct {
2038+
username string
2039+
password string
2040+
}
2041+
2042+
tests := []struct {
2043+
name string
2044+
url string
2045+
registryOpts registryOptions
2046+
secretOpts secretOptions
2047+
provider string
2048+
providerImg string
2049+
want sreconcile.Result
2050+
wantErr bool
2051+
assertConditions []metav1.Condition
2052+
}{
2053+
{
2054+
name: "HTTP without basic auth",
2055+
want: sreconcile.ResultSuccess,
2056+
assertConditions: []metav1.Condition{
2057+
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled '<helmchart>' chart with version '<version>'"),
2058+
},
2059+
},
2060+
{
2061+
name: "HTTP with basic auth secret",
2062+
want: sreconcile.ResultSuccess,
2063+
registryOpts: registryOptions{
2064+
withBasicAuth: true,
2065+
},
2066+
secretOpts: secretOptions{
2067+
username: testRegistryUsername,
2068+
password: testRegistryPassword,
2069+
},
2070+
assertConditions: []metav1.Condition{
2071+
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled '<helmchart>' chart with version '<version>'"),
2072+
},
2073+
},
2074+
{
2075+
name: "HTTP registry - basic auth with invalid secret",
2076+
want: sreconcile.ResultEmpty,
2077+
wantErr: true,
2078+
registryOpts: registryOptions{
2079+
withBasicAuth: true,
2080+
},
2081+
secretOpts: secretOptions{
2082+
username: "wrong-pass",
2083+
password: "wrong-pass",
2084+
},
2085+
assertConditions: []metav1.Condition{
2086+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to login to OCI registry"),
2087+
},
2088+
},
2089+
{
2090+
name: "with contextual login provider",
2091+
wantErr: true,
2092+
provider: "aws",
2093+
providerImg: "oci://123456789000.dkr.ecr.us-east-2.amazonaws.com/test",
2094+
assertConditions: []metav1.Condition{
2095+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to get credential from"),
2096+
},
2097+
},
2098+
{
2099+
name: "with contextual login provider and secretRef",
2100+
want: sreconcile.ResultSuccess,
2101+
registryOpts: registryOptions{
2102+
withBasicAuth: true,
2103+
},
2104+
secretOpts: secretOptions{
2105+
username: testRegistryUsername,
2106+
password: testRegistryPassword,
2107+
},
2108+
provider: "azure",
2109+
assertConditions: []metav1.Condition{
2110+
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled '<helmchart>' chart with version '<version>'"),
2111+
},
2112+
},
2113+
}
2114+
2115+
for _, tt := range tests {
2116+
t.Run(tt.name, func(t *testing.T) {
2117+
g := NewWithT(t)
2118+
2119+
builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
2120+
workspaceDir := t.TempDir()
2121+
server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts)
2122+
2123+
g.Expect(err).NotTo(HaveOccurred())
2124+
2125+
// Load a test chart
2126+
chartData, err := ioutil.ReadFile(chartPath)
2127+
2128+
// Upload the test chart
2129+
metadata, err := loadTestChartToOCI(chartData, chartPath, server)
2130+
g.Expect(err).NotTo(HaveOccurred())
2131+
g.Expect(err).ToNot(HaveOccurred())
2132+
2133+
repo := &sourcev1.HelmRepository{
2134+
ObjectMeta: metav1.ObjectMeta{
2135+
GenerateName: "auth-strategy-",
2136+
},
2137+
Spec: sourcev1.HelmRepositorySpec{
2138+
Interval: metav1.Duration{Duration: interval},
2139+
Timeout: &metav1.Duration{Duration: timeout},
2140+
Type: sourcev1.HelmRepositoryTypeOCI,
2141+
Provider: sourcev1.GenericOCIProvider,
2142+
URL: fmt.Sprintf("oci://%s/testrepo", server.registryHost),
2143+
},
2144+
}
2145+
2146+
if tt.provider != "" {
2147+
repo.Spec.Provider = tt.provider
2148+
}
2149+
// If a provider specific image is provided, overwrite existing URL
2150+
// set earlier. It'll fail but it's necessary to set them because
2151+
// the login check expects the URLs to be of certain pattern.
2152+
if tt.providerImg != "" {
2153+
repo.Spec.URL = tt.providerImg
2154+
}
2155+
2156+
if tt.secretOpts.username != "" && tt.secretOpts.password != "" {
2157+
secret := &corev1.Secret{
2158+
ObjectMeta: metav1.ObjectMeta{
2159+
Name: "auth-secretref",
2160+
},
2161+
Type: corev1.SecretTypeDockerConfigJson,
2162+
Data: map[string][]byte{
2163+
".dockerconfigjson": []byte(fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`,
2164+
server.registryHost, tt.secretOpts.username, tt.secretOpts.password)),
2165+
},
2166+
}
2167+
2168+
repo.Spec.SecretRef = &meta.LocalObjectReference{
2169+
Name: secret.Name,
2170+
}
2171+
builder.WithObjects(secret, repo)
2172+
} else {
2173+
builder.WithObjects(repo)
2174+
}
2175+
2176+
obj := &sourcev1.HelmChart{
2177+
ObjectMeta: metav1.ObjectMeta{
2178+
GenerateName: "auth-strategy-",
2179+
},
2180+
Spec: sourcev1.HelmChartSpec{
2181+
Chart: metadata.Name,
2182+
Version: metadata.Version,
2183+
SourceRef: sourcev1.LocalHelmChartSourceReference{
2184+
Kind: sourcev1.HelmRepositoryKind,
2185+
Name: repo.Name,
2186+
},
2187+
Interval: metav1.Duration{Duration: interval},
2188+
},
2189+
}
2190+
2191+
r := &HelmChartReconciler{
2192+
Client: builder.Build(),
2193+
EventRecorder: record.NewFakeRecorder(32),
2194+
Getters: testGetters,
2195+
RegistryClientGenerator: registry.ClientGenerator,
2196+
}
2197+
2198+
var b chart.Build
2199+
defer func() {
2200+
if _, err := os.Stat(b.Path); !os.IsNotExist(err) {
2201+
err := os.Remove(b.Path)
2202+
g.Expect(err).NotTo(HaveOccurred())
2203+
}
2204+
}()
2205+
2206+
assertConditions := tt.assertConditions
2207+
for k := range assertConditions {
2208+
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<helmchart>", metadata.Name)
2209+
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<version>", metadata.Version)
2210+
}
2211+
2212+
got, err := r.reconcileSource(ctx, obj, &b)
2213+
g.Expect(err != nil).To(Equal(tt.wantErr))
2214+
g.Expect(got).To(Equal(tt.want))
2215+
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
2216+
})
2217+
}
2218+
}
2219+
20412220
// extractChartMeta is used to extract a chart metadata from a byte array
20422221
func extractChartMeta(chartData []byte) (*hchart.Metadata, error) {
20432222
ch, err := loader.LoadArchive(bytes.NewReader(chartData))
@@ -2046,3 +2225,32 @@ func extractChartMeta(chartData []byte) (*hchart.Metadata, error) {
20462225
}
20472226
return ch.Metadata, nil
20482227
}
2228+
2229+
func loadTestChartToOCI(chartData []byte, chartPath string, server *registryClientTestServer) (*hchart.Metadata, error) {
2230+
// Login to the registry
2231+
err := server.registryClient.Login(server.registryHost,
2232+
helmreg.LoginOptBasicAuth(testRegistryUsername, testRegistryPassword),
2233+
helmreg.LoginOptInsecure(true))
2234+
if err != nil {
2235+
return nil, err
2236+
}
2237+
2238+
// Load a test chart
2239+
chartData, err = ioutil.ReadFile(chartPath)
2240+
if err != nil {
2241+
return nil, err
2242+
}
2243+
metadata, err := extractChartMeta(chartData)
2244+
if err != nil {
2245+
return nil, err
2246+
}
2247+
2248+
// Upload the test chart
2249+
ref := fmt.Sprintf("%s/testrepo/%s:%s", server.registryHost, metadata.Name, metadata.Version)
2250+
_, err = server.registryClient.Push(chartData, ref)
2251+
if err != nil {
2252+
return nil, err
2253+
}
2254+
2255+
return metadata, nil
2256+
}

controllers/helmrepository_controller_oci.go

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"fmt"
2323
"net/url"
2424
"os"
25-
"strings"
2625
"time"
2726

2827
helmgetter "helm.sh/helm/v3/pkg/getter"
@@ -42,12 +41,10 @@ import (
4241

4342
"github.com/fluxcd/pkg/apis/meta"
4443
"github.com/fluxcd/pkg/oci"
45-
"github.com/fluxcd/pkg/oci/auth/login"
4644
"github.com/fluxcd/pkg/runtime/conditions"
4745
helper "github.com/fluxcd/pkg/runtime/controller"
4846
"github.com/fluxcd/pkg/runtime/patch"
4947
"github.com/fluxcd/pkg/runtime/predicates"
50-
"github.com/google/go-containerregistry/pkg/name"
5148

5249
"github.com/fluxcd/source-controller/api/v1beta2"
5350
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -294,10 +291,8 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, obj *v1beta
294291
if loginOpt != nil {
295292
loginOpts = append(loginOpts, loginOpt)
296293
}
297-
}
298-
299-
if obj.Spec.Provider != sourcev1.GenericOCIProvider && obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
300-
auth, authErr := oidcAuth(ctxTimeout, obj)
294+
} else if obj.Spec.Provider != sourcev1.GenericOCIProvider && obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
295+
auth, authErr := oidcAuthFromAdapter(ctxTimeout, obj.Spec.URL, obj.Spec.Provider)
301296
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
302297
e := fmt.Errorf("failed to get credential from %s: %w", obj.Spec.Provider, authErr)
303298
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.AuthenticationFailedReason, e.Error())
@@ -380,41 +375,12 @@ func (r *HelmRepositoryOCIReconciler) eventLogf(ctx context.Context, obj runtime
380375
r.Eventf(obj, eventType, reason, msg)
381376
}
382377

383-
// oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
384-
func oidcAuth(ctx context.Context, obj *sourcev1.HelmRepository) (helmreg.LoginOption, error) {
385-
url := strings.TrimPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix)
386-
ref, err := name.ParseReference(url)
387-
if err != nil {
388-
return nil, fmt.Errorf("failed to parse URL '%s': %w", obj.Spec.URL, err)
389-
}
390-
391-
loginOpt, err := loginWithManager(ctx, obj.Spec.Provider, url, ref)
392-
if err != nil {
393-
return nil, fmt.Errorf("failed to login to registry '%s': %w", obj.Spec.URL, err)
394-
}
395-
396-
return loginOpt, nil
397-
}
398-
399-
func loginWithManager(ctx context.Context, provider, url string, ref name.Reference) (helmreg.LoginOption, error) {
400-
opts := login.ProviderOptions{}
401-
switch provider {
402-
case sourcev1.AmazonOCIProvider:
403-
opts.AwsAutoLogin = true
404-
case sourcev1.AzureOCIProvider:
405-
opts.AzureAutoLogin = true
406-
case sourcev1.GoogleOCIProvider:
407-
opts.GcpAutoLogin = true
408-
}
409-
410-
auth, err := login.NewManager().Login(ctx, url, ref, opts)
378+
// oidcAuthFromAdapter generates the OIDC credential authenticator based on the specified cloud provider.
379+
func oidcAuthFromAdapter(ctx context.Context, url, provider string) (helmreg.LoginOption, error) {
380+
auth, err := oidcAuth(ctx, url, provider)
411381
if err != nil {
412382
return nil, err
413383
}
414384

415-
if auth == nil {
416-
return nil, nil
417-
}
418-
419385
return registry.OIDCAdaptHelper(auth)
420386
}

0 commit comments

Comments
 (0)