Skip to content

Commit 0fe1e6a

Browse files
committed
Refined registry credential lookup
The Container Tools project defines a lookup for authentication from most-specific to least-specific[1]. This change implements that credential lookup. [1] https://github.com/containers/image/blob/main/docs/containers-auth.json.5.md#format
1 parent 1b4e407 commit 0fe1e6a

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

pkg/authn/keychain.go

+49-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ package authn
1616

1717
import (
1818
"context"
19+
"fmt"
1920
"os"
2021
"path/filepath"
22+
"strings"
2123
"sync"
2224
"time"
2325

@@ -145,10 +147,13 @@ func (dk *defaultKeychain) ResolveContext(ctx context.Context, target Resource)
145147
// https://github.com/google/ko/issues/90
146148
// https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404
147149
var cfg, empty types.AuthConfig
148-
for _, key := range []string{
149-
target.String(),
150-
target.RegistryStr(),
151-
} {
150+
151+
keys, err := authLookup(target)
152+
if err != nil {
153+
return nil, err
154+
}
155+
156+
for _, key := range keys {
152157
if key == name.DefaultRegistry {
153158
key = DefaultAuthKey
154159
}
@@ -178,6 +183,46 @@ func (dk *defaultKeychain) ResolveContext(ctx context.Context, target Resource)
178183
}), nil
179184
}
180185

186+
func authLookup(target any) ([]string, error) {
187+
switch t := target.(type) {
188+
case name.Registry:
189+
return []string{
190+
t.RegistryStr(),
191+
}, nil
192+
case name.Tag:
193+
return authLookup(t.Repository)
194+
case name.Digest:
195+
return authLookup(t.Repository)
196+
case name.Repository:
197+
return authLookupParts(t), nil
198+
case Resource:
199+
r, err := name.ParseReference(t.String())
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
return authLookup(r)
205+
default:
206+
return nil, fmt.Errorf("unsupported target resource type: %T", target)
207+
}
208+
}
209+
210+
func authLookupParts(target name.Repository) []string {
211+
segments := strings.Split(target.String(), "/")
212+
parts := make([]string, 0, len(segments))
213+
214+
part := ""
215+
for i, p := range segments {
216+
part = part + p
217+
parts = append([]string{part}, parts...)
218+
if i < len(segments) {
219+
part += "/"
220+
}
221+
}
222+
223+
return parts
224+
}
225+
181226
// fileExists returns true if the given path exists and is not a directory.
182227
func fileExists(path string) bool {
183228
fi, err := os.Stat(path)

pkg/authn/keychain_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path"
2424
"path/filepath"
2525
"reflect"
26+
"slices"
2627
"testing"
2728
"time"
2829

@@ -33,6 +34,7 @@ var (
3334
fresh = 0
3435
testRegistry, _ = name.NewRegistry("test.io", name.WeakValidation)
3536
testRepo, _ = name.NewRepository("test.io/my-repo", name.WeakValidation)
37+
testNestedRepo, _ = name.NewRepository("test.io/parent/my-repo", name.WeakValidation)
3638
defaultRegistry, _ = name.NewRegistry(name.DefaultRegistry, name.WeakValidation)
3739
)
3840

@@ -289,6 +291,19 @@ func TestVariousPaths(t *testing.T) {
289291
}`, encode("bar", "baz")),
290292
cfg: &AuthConfig{},
291293
anonymous: true,
294+
}, {
295+
desc: "inherit from parent",
296+
target: testNestedRepo,
297+
content: fmt.Sprintf(`{
298+
"auths": {
299+
"test.io/parent": {"auth": %q},
300+
"test.io": {}
301+
}
302+
}`, encode("bar", "baz")),
303+
cfg: &AuthConfig{
304+
Username: "bar",
305+
Password: "baz",
306+
},
292307
}}
293308

294309
for _, test := range tests {
@@ -463,3 +478,45 @@ func TestRefreshingAuth(t *testing.T) {
463478
t.Errorf("refreshed %d times, wanted %d", got, want)
464479
}
465480
}
481+
482+
func TestAuthLookup(t *testing.T) {
483+
tests := []struct {
484+
description string
485+
target string
486+
lookup []string
487+
}{
488+
{
489+
description: "remove tags",
490+
target: "test.io/my-repo:tag",
491+
lookup: []string{"test.io/my-repo", "test.io"},
492+
},
493+
{
494+
description: "remove digests",
495+
target: "test.io/my-repo@sha256:8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4",
496+
lookup: []string{"test.io/my-repo", "test.io"},
497+
},
498+
{
499+
description: "hierarchy",
500+
target: "test.io/a/b/c/d",
501+
lookup: []string{"test.io/a/b/c/d", "test.io/a/b/c", "test.io/a/b", "test.io/a", "test.io"},
502+
},
503+
}
504+
505+
for _, test := range tests {
506+
t.Run(test.description, func(t *testing.T) {
507+
target, err := name.ParseReference(test.target)
508+
if err != nil {
509+
t.Fatal(err)
510+
}
511+
512+
got, err := authLookup(target)
513+
if err != nil {
514+
t.Fatal(err)
515+
}
516+
517+
if !slices.Equal(test.lookup, got) {
518+
t.Errorf("got %+v, want %+v", got, test.lookup)
519+
}
520+
})
521+
}
522+
}

0 commit comments

Comments
 (0)