Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ func newTransport(insecureSkipVerify bool) fnhttp.RoundTripCloser {
func newCredentialsProvider(configPath string, t http.RoundTripper, authFilePath string) oci.CredentialsProvider {
additionalLoaders := append(k8s.GetOpenShiftDockerCredentialLoaders(), k8s.GetGoogleCredentialLoader()...)
additionalLoaders = append(additionalLoaders, k8s.GetECRCredentialLoader()...)
additionalLoaders = append(additionalLoaders, k8s.GetACRCredentialLoader()...)

additionalLoaders = append(additionalLoaders,
func(registry string) (oci.Credentials, error) {
Expand All @@ -126,11 +125,14 @@ func newCredentialsProvider(configPath string, t http.RoundTripper, authFilePath
},
)

contextLoaders := k8s.GetACRCredentialLoader()

options := []creds.Opt{
creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, os.Stdout, os.Stderr)),
creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
creds.WithTransport(t),
creds.WithAdditionalCredentialLoaders(additionalLoaders...),
creds.WithContextCredentialLoaders(contextLoaders...),
}

// If a custom auth file path is provided, use it
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ replace knative.dev/pkg => knative.dev/pkg v0.0.0-20250716115900-19d3cc2da0b9

require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/BurntSushi/toml v1.5.0
github.com/Masterminds/semver v1.5.0
github.com/Microsoft/go-winio v0.6.2
Expand Down Expand Up @@ -83,6 +85,7 @@ require (
contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.30 // indirect
Expand All @@ -92,6 +95,7 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.2 // indirect
github.com/Azure/go-autorest/tracing v0.6.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
github.com/GoogleContainerTools/kaniko v1.24.0 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
Expand Down Expand Up @@ -167,6 +171,7 @@ require (
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
Expand Down Expand Up @@ -199,6 +204,7 @@ require (
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
Expand Down Expand Up @@ -239,6 +245,7 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
Expand Down Expand Up @@ -96,6 +104,10 @@ github.com/Azure/go-autorest/logger v0.2.2/go.mod h1:I5fg9K52o+iuydlWfa9T5K6WFos
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsful9L/2nyR0=
github.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
Expand Down Expand Up @@ -464,6 +476,8 @@ github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -682,6 +696,8 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand Down Expand Up @@ -888,6 +904,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
41 changes: 36 additions & 5 deletions pkg/creds/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

type CredentialsCallback func(registry string) (oci.Credentials, error)

// ContextCredentialsCallback represents a credential retrieval callback that supports context for cancellation and timeouts.
// It should return ErrCredentialsNotFound if no credentials are available for the given registry.
type ContextCredentialsCallback func(ctx context.Context, registry string) (oci.Credentials, error)

var ErrUnauthorized = errors.New("bad credentials")

var ErrCredentialsNotFound = errors.New("credentials not found")
Expand Down Expand Up @@ -88,6 +92,7 @@
verifyCredentials VerifyCredentialsCallback
promptForCredentialStore ChooseCredentialHelperCallback
credentialLoaders []CredentialsCallback
contextCredentialLoaders []ContextCredentialsCallback
authFilePath string
transport http.RoundTripper
}
Expand Down Expand Up @@ -146,6 +151,22 @@
}
}

// WithContextCredentialLoaders adds custom context-aware callbacks for credential retrieval.
// These callbacks accept context for cancellation and timeout support,
// and must return ErrCredentialsNotFound if the credentials are not found.
// The callbacks are intended to be non-interactive, as opposed to WithPromptForCredentials.
//
// This is particularly useful when credential retrieval may need to be interrupted
// (for example, due to network delays, API timeouts, or user cancel actions).
//
// Example usage: Azure Container Registry loader supports context cancellation
// for credential acquisition routines.
func WithContextCredentialLoaders(loaders ...ContextCredentialsCallback) Opt {
return func(opts *credentialsProvider) {
opts.contextCredentialLoaders = append(opts.contextCredentialLoaders, loaders...)
}
}

// NewCredentialsProvider returns new CredentialsProvider that tries to get credentials from docker/func config files.
//
// In case getting credentials from the config files fails
Expand Down Expand Up @@ -261,6 +282,19 @@
return c.getCredentials
}

func (c *credentialsProvider) getAllCredentialLoaders() []ContextCredentialsCallback {
// Unify all callbacks into a single slice
var allLoaders []ContextCredentialsCallback

Check failure on line 287 in pkg/creds/credentials.go

View workflow job for this annotation

GitHub Actions / Precheck

Consider pre-allocating `allLoaders` (prealloc)

Check failure on line 287 in pkg/creds/credentials.go

View workflow job for this annotation

GitHub Actions / style / Golang / Lint

Consider pre-allocating `allLoaders` (prealloc)
// Wrap non-context loaders to match the ContextCredentialsCallback signature
for _, load := range c.credentialLoaders {
allLoaders = append(allLoaders, func(ctx context.Context, registry string) (oci.Credentials, error) {
return load(registry)
})
}
allLoaders = append(allLoaders, c.contextCredentialLoaders...)
return allLoaders
}

func (c *credentialsProvider) getCredentials(ctx context.Context, image string) (oci.Credentials, error) {
var err error
result := oci.Credentials{}
Expand All @@ -271,10 +305,8 @@
}

registry := ref.Context().RegistryStr()
for _, load := range c.credentialLoaders {

result, err = load(registry)

for _, load := range c.getAllCredentialLoaders() {
result, err = load(ctx, registry)
if err != nil {
if errors.Is(err, ErrCredentialsNotFound) {
continue
Expand All @@ -290,7 +322,6 @@
return oci.Credentials{}, err
}
}

}

if c.promptForCredentials == nil {
Expand Down
48 changes: 19 additions & 29 deletions pkg/k8s/keychains.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package k8s

import (
"encoding/json"
"context"
"fmt"
"os"
"path"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/google"

Expand Down Expand Up @@ -48,38 +48,28 @@ func GetECRCredentialLoader() []creds.CredentialsCallback {
return []creds.CredentialsCallback{} // TODO: Implement ECR credentials loader
}

func GetACRCredentialLoader() []creds.CredentialsCallback {
return []creds.CredentialsCallback{
func(registry string) (oci.Credentials, error) {
func GetACRCredentialLoader() []creds.ContextCredentialsCallback {
return []creds.ContextCredentialsCallback{
func(ctx context.Context, registry string) (oci.Credentials, error) {
if !strings.HasSuffix(registry, ".azurecr.io") {
return oci.Credentials{}, creds.ErrCredentialsNotFound
}

f, err := os.Open(path.Join(os.Getenv("HOME"), ".azure", "accessTokens.json"))
// Use Azure SDK to get access token
azCredentials, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return oci.Credentials{}, fmt.Errorf("open Azure access tokens: %w", err)
return oci.Credentials{}, fmt.Errorf("failed to create default azure credentials: %w", err)
}
defer f.Close()

var tokens []struct {
AccessToken string `json:"accessToken"`
Resource string `json:"resource"`
}

if err := json.NewDecoder(f).Decode(&tokens); err != nil {
return oci.Credentials{}, fmt.Errorf("decode Azure access tokens: %w", err)
}

target := "https://" + registry
for _, t := range tokens {
if t.Resource == target {
return oci.Credentials{
Username: "00000000-0000-0000-0000-000000000000",
Password: t.AccessToken,
}, nil
}
scope := "https://containerregistry.azure.net/.default"
token, err := azCredentials.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{scope},
})
if err != nil {
return oci.Credentials{}, fmt.Errorf("failed to get azure access token: %w", err)
}
return oci.Credentials{}, creds.ErrCredentialsNotFound
return oci.Credentials{
Username: "00000000-0000-0000-0000-000000000000",
Password: token.Token,
}, nil
},
}
}
20 changes: 20 additions & 0 deletions pkg/k8s/keychains_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package k8s

import (
"context"
"testing"
)

func TestACRCredentialLoader_ContextCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
loader := GetACRCredentialLoader()[0]

registry := "example.azurecr.io"

_, err := loader(ctx, registry)
if err == nil {
t.Fatal("expected error due to context cancellation, got nil")
}
t.Logf("Successfully caught cancellation error: %v", err)
}
Loading