oci: refactors pusher auth

Signed-off-by: kapil <kapilsareen584@gmail.com>
This commit is contained in:
kapil 2025-08-20 00:12:50 +05:30
parent 61ecd623a2
commit b52ddfb7f2
14 changed files with 236 additions and 116 deletions

View File

@ -394,9 +394,14 @@ func (c buildConfig) clientOptions() ([]fn.Option, error) {
o := []fn.Option{fn.WithRegistry(c.Registry)} o := []fn.Option{fn.WithRegistry(c.Registry)}
switch c.Builder { switch c.Builder {
case builders.Host: case builders.Host:
t := newTransport(c.RegistryInsecure) // may provide a custom impl which proxies
creds := newCredentialsProvider(config.Dir(), t)
o = append(o, o = append(o,
fn.WithBuilder(oci.NewBuilder(builders.Host, c.Verbose)), fn.WithBuilder(oci.NewBuilder(builders.Host, c.Verbose)),
fn.WithPusher(oci.NewPusher(c.RegistryInsecure, false, c.Verbose))) fn.WithPusher(oci.NewPusher(c.RegistryInsecure, false, c.Verbose,
oci.WithCredentialsProvider(creds),
oci.WithVerbose(c.Verbose))),
)
case builders.Pack: case builders.Pack:
o = append(o, o = append(o,
fn.WithBuilder(pack.NewBuilder( fn.WithBuilder(pack.NewBuilder(

View File

@ -8,12 +8,13 @@ import (
"knative.dev/func/cmd/prompt" "knative.dev/func/cmd/prompt"
"knative.dev/func/pkg/builders/buildpacks" "knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/config" "knative.dev/func/pkg/config"
"knative.dev/func/pkg/creds"
"knative.dev/func/pkg/docker" "knative.dev/func/pkg/docker"
"knative.dev/func/pkg/docker/creds"
fn "knative.dev/func/pkg/functions" fn "knative.dev/func/pkg/functions"
fnhttp "knative.dev/func/pkg/http" fnhttp "knative.dev/func/pkg/http"
"knative.dev/func/pkg/k8s" "knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative" "knative.dev/func/pkg/knative"
"knative.dev/func/pkg/oci"
"knative.dev/func/pkg/pipelines/tekton" "knative.dev/func/pkg/pipelines/tekton"
) )
@ -100,19 +101,22 @@ func newTransport(insecureSkipVerify bool) fnhttp.RoundTripCloser {
// newCredentialsProvider returns a credentials provider which possibly // newCredentialsProvider returns a credentials provider which possibly
// has cluster-flavor specific additional credential loaders to take advantage // has cluster-flavor specific additional credential loaders to take advantage
// of features or configuration nuances of cluster variants. // of features or configuration nuances of cluster variants.
func newCredentialsProvider(configPath string, t http.RoundTripper) docker.CredentialsProvider { func newCredentialsProvider(configPath string, t http.RoundTripper) oci.CredentialsProvider {
additionalLoaders := append(k8s.GetOpenShiftDockerCredentialLoaders(), k8s.GetGoogleCredentialLoader()...)
additionalLoaders = append(additionalLoaders, k8s.GetECRCredentialLoader()...)
additionalLoaders = append(additionalLoaders, k8s.GetACRCredentialLoader()...)
options := []creds.Opt{ options := []creds.Opt{
creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, os.Stdout, os.Stderr)), creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, os.Stdout, os.Stderr)),
creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()), creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
creds.WithTransport(t), creds.WithTransport(t),
creds.WithAdditionalCredentialLoaders(k8s.GetOpenShiftDockerCredentialLoaders()...), creds.WithAdditionalCredentialLoaders(additionalLoaders...),
} }
// Other cluster variants can be supported here // Other cluster variants can be supported here
return creds.NewCredentialsProvider(configPath, options...) return creds.NewCredentialsProvider(configPath, options...)
} }
func newTektonPipelinesProvider(creds docker.CredentialsProvider, verbose bool) *tekton.PipelinesProvider { func newTektonPipelinesProvider(creds oci.CredentialsProvider, verbose bool) *tekton.PipelinesProvider {
options := []tekton.Opt{ options := []tekton.Opt{
tekton.WithCredentialsProvider(creds), tekton.WithCredentialsProvider(creds),
tekton.WithVerbose(verbose), tekton.WithVerbose(verbose),

View File

@ -11,14 +11,14 @@ import (
"github.com/AlecAivazis/survey/v2/terminal" "github.com/AlecAivazis/survey/v2/terminal"
"golang.org/x/term" "golang.org/x/term"
"knative.dev/func/pkg/docker" "knative.dev/func/pkg/creds"
"knative.dev/func/pkg/docker/creds" "knative.dev/func/pkg/oci"
) )
func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repository string) (docker.Credentials, error) { func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repository string) (oci.Credentials, error) {
firstTime := true firstTime := true
return func(repository string) (docker.Credentials, error) { return func(repository string) (oci.Credentials, error) {
var result docker.Credentials var result oci.Credentials
if firstTime { if firstTime {
firstTime = false firstTime = false
fmt.Fprintf(out, "Please provide credentials for image repository '%s'.\n", repository) fmt.Fprintf(out, "Please provide credentials for image repository '%s'.\n", repository)
@ -56,7 +56,7 @@ func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repositor
if isTerm { if isTerm {
err := survey.Ask(qs, &result, survey.WithStdio(fr, out.(terminal.FileWriter), errOut)) err := survey.Ask(qs, &result, survey.WithStdio(fr, out.(terminal.FileWriter), errOut))
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
} else { } else {
reader := bufio.NewReader(in) reader := bufio.NewReader(in)
@ -64,18 +64,18 @@ func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repositor
fmt.Fprintf(out, "Username: ") fmt.Fprintf(out, "Username: ")
u, err := reader.ReadString('\n') u, err := reader.ReadString('\n')
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
u = strings.Trim(u, "\r\n") u = strings.Trim(u, "\r\n")
fmt.Fprintf(out, "Password: ") fmt.Fprintf(out, "Password: ")
p, err := reader.ReadString('\n') p, err := reader.ReadString('\n')
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
p = strings.Trim(p, "\r\n") p = strings.Trim(p, "\r\n")
result = docker.Credentials{Username: u, Password: p} result = oci.Credentials{Username: u, Password: p}
} }
return result, nil return result, nil

View File

@ -12,8 +12,7 @@ import (
"github.com/Netflix/go-expect" "github.com/Netflix/go-expect"
"github.com/creack/pty" "github.com/creack/pty"
"github.com/hinshun/vt10x" "github.com/hinshun/vt10x"
"knative.dev/func/pkg/oci"
"knative.dev/func/pkg/docker"
) )
const ( const (
@ -21,7 +20,7 @@ const (
) )
func Test_NewPromptForCredentials(t *testing.T) { func Test_NewPromptForCredentials(t *testing.T) {
expectedCreds := docker.Credentials{ expectedCreds := oci.Credentials{
Username: "testuser", Username: "testuser",
Password: "testpwd", Password: "testpwd",
} }

View File

@ -22,10 +22,10 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/remote/transport"
"knative.dev/func/pkg/docker" "knative.dev/func/pkg/oci"
) )
type CredentialsCallback func(registry string) (docker.Credentials, error) type CredentialsCallback func(registry string) (oci.Credentials, error)
var ErrUnauthorized = errors.New("bad credentials") var ErrUnauthorized = errors.New("bad credentials")
@ -33,7 +33,7 @@ var ErrCredentialsNotFound = errors.New("credentials not found")
// VerifyCredentialsCallback checks if credentials are authorized for image push. // VerifyCredentialsCallback checks if credentials are authorized for image push.
// If credentials are incorrect this callback shall return ErrUnauthorized. // If credentials are incorrect this callback shall return ErrUnauthorized.
type VerifyCredentialsCallback func(ctx context.Context, image string, credentials docker.Credentials) error type VerifyCredentialsCallback func(ctx context.Context, image string, credentials oci.Credentials) error
type keyChain struct { type keyChain struct {
user string user string
@ -48,7 +48,7 @@ func (k keyChain) Resolve(resource authn.Resource) (authn.Authenticator, error)
} }
// CheckAuth verifies that credentials can be used for image push // CheckAuth verifies that credentials can be used for image push
func CheckAuth(ctx context.Context, image string, credentials docker.Credentials, trans http.RoundTripper) error { func CheckAuth(ctx context.Context, image string, credentials oci.Credentials, trans http.RoundTripper) error {
ref, err := name.ParseReference(image) ref, err := name.ParseReference(image)
if err != nil { if err != nil {
@ -142,9 +142,8 @@ func WithAdditionalCredentialLoaders(loaders ...CredentialsCallback) Opt {
// The picked value will be saved in the func config. // The picked value will be saved in the func config.
// //
// To verify that credentials are correct custom callback can be used (see WithVerifyCredentials). // To verify that credentials are correct custom callback can be used (see WithVerifyCredentials).
func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsProvider { func NewCredentialsProvider(configPath string, opts ...Opt) oci.CredentialsProvider {
var c credentialsProvider var c credentialsProvider
for _, o := range opts { for _, o := range opts {
o(&c) o(&c)
} }
@ -154,7 +153,7 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
} }
if c.verifyCredentials == nil { if c.verifyCredentials == nil {
c.verifyCredentials = func(ctx context.Context, registry string, credentials docker.Credentials) error { c.verifyCredentials = func(ctx context.Context, registry string, credentials oci.Credentials) error {
return CheckAuth(ctx, registry, credentials, c.transport) return CheckAuth(ctx, registry, credentials, c.transport)
} }
} }
@ -175,7 +174,7 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
if _, err := os.Stat(c.authFilePath); err == nil { if _, err := os.Stat(c.authFilePath); err == nil {
defaultCredentialLoaders = append(defaultCredentialLoaders, defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { func(registry string) (oci.Credentials, error) {
return getCredentialsByCredentialHelper(c.authFilePath, registry) return getCredentialsByCredentialHelper(c.authFilePath, registry)
}) })
} }
@ -185,40 +184,40 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
if err == nil { if err == nil {
dockerConfigPath := filepath.Join(home, ".docker", "config.json") dockerConfigPath := filepath.Join(home, ".docker", "config.json")
defaultCredentialLoaders = append(defaultCredentialLoaders, defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { func(registry string) (oci.Credentials, error) {
return getCredentialsByCredentialHelper(dockerConfigPath, registry) return getCredentialsByCredentialHelper(dockerConfigPath, registry)
}) })
} }
defaultCredentialLoaders = append(defaultCredentialLoaders, defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { func(registry string) (oci.Credentials, error) {
creds, err := dockerConfig.GetCredentials(sys, registry) creds, err := dockerConfig.GetCredentials(sys, registry)
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
if creds.Username == "" || creds.Password == "" { if creds.Username == "" || creds.Password == "" {
return docker.Credentials{}, ErrCredentialsNotFound return oci.Credentials{}, ErrCredentialsNotFound
} }
return docker.Credentials{ return oci.Credentials{
Username: creds.Username, Username: creds.Username,
Password: creds.Password, Password: creds.Password,
}, nil }, nil
}) })
defaultCredentialLoaders = append(defaultCredentialLoaders, defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { func(registry string) (oci.Credentials, error) {
// Fallback onto default docker config locations // Fallback onto default docker config locations
emptySys := &containersTypes.SystemContext{} emptySys := &containersTypes.SystemContext{}
creds, err := dockerConfig.GetCredentials(emptySys, registry) creds, err := dockerConfig.GetCredentials(emptySys, registry)
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
return docker.Credentials{ return oci.Credentials{
Username: creds.Username, Username: creds.Username,
Password: creds.Password, Password: creds.Password,
}, nil }, nil
}) })
defaultCredentialLoaders = append(defaultCredentialLoaders, defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { // empty credentials provider for unsecured registries func(registry string) (oci.Credentials, error) { // empty credentials provider for unsecured registries
return docker.Credentials{}, nil return oci.Credentials{}, nil
}) })
c.credentialLoaders = append(c.credentialLoaders, defaultCredentialLoaders...) c.credentialLoaders = append(c.credentialLoaders, defaultCredentialLoaders...)
@ -226,13 +225,13 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
return c.getCredentials return c.getCredentials
} }
func (c *credentialsProvider) getCredentials(ctx context.Context, image string) (docker.Credentials, error) { func (c *credentialsProvider) getCredentials(ctx context.Context, image string) (oci.Credentials, error) {
var err error var err error
result := docker.Credentials{} result := oci.Credentials{}
ref, err := name.ParseReference(image) ref, err := name.ParseReference(image)
if err != nil { if err != nil {
return docker.Credentials{}, fmt.Errorf("cannot parse the image reference: %w", err) return oci.Credentials{}, fmt.Errorf("cannot parse the image reference: %w", err)
} }
registry := ref.Context().RegistryStr() registry := ref.Context().RegistryStr()
@ -244,7 +243,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
if errors.Is(err, ErrCredentialsNotFound) { if errors.Is(err, ErrCredentialsNotFound) {
continue continue
} }
return docker.Credentials{}, err return oci.Credentials{}, err
} }
err = c.verifyCredentials(ctx, image, result) err = c.verifyCredentials(ctx, image, result)
@ -252,14 +251,14 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
return result, nil return result, nil
} else { } else {
if !errors.Is(err, ErrUnauthorized) { if !errors.Is(err, ErrUnauthorized) {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
} }
} }
if c.promptForCredentials == nil { if c.promptForCredentials == nil {
return docker.Credentials{}, ErrCredentialsNotFound return oci.Credentials{}, ErrCredentialsNotFound
} }
// this is [registry] / [repository] // this is [registry] / [repository]
@ -271,7 +270,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
// use repo here to print it out in prompt // use repo here to print it out in prompt
result, err = c.promptForCredentials(repository) result, err = c.promptForCredentials(repository)
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
err = c.verifyCredentials(ctx, image, result) err = c.verifyCredentials(ctx, image, result)
@ -282,21 +281,21 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
// This shouldn't be fatal error. // This shouldn't be fatal error.
if strings.Contains(err.Error(), "not implemented") { if strings.Contains(err.Error(), "not implemented") {
fmt.Fprintf(os.Stderr, "the cred-helper does not support write operation (consider changing the cred-helper it in auth.json)\n") fmt.Fprintf(os.Stderr, "the cred-helper does not support write operation (consider changing the cred-helper it in auth.json)\n")
return docker.Credentials{}, nil return oci.Credentials{}, nil
} }
if !errors.Is(err, errNoCredentialHelperConfigured) { if !errors.Is(err, errNoCredentialHelperConfigured) {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
helpers := listCredentialHelpers() helpers := listCredentialHelpers()
helper, err := c.promptForCredentialStore(helpers) helper, err := c.promptForCredentialStore(helpers)
if err != nil { if err != nil {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
helper = strings.TrimPrefix(helper, "docker-credential-") helper = strings.TrimPrefix(helper, "docker-credential-")
err = setCredentialHelperToConfig(c.authFilePath, helper) err = setCredentialHelperToConfig(c.authFilePath, helper)
if err != nil { if err != nil {
return docker.Credentials{}, fmt.Errorf("faild to set the helper to the config: %w", err) return oci.Credentials{}, fmt.Errorf("faild to set the helper to the config: %w", err)
} }
err = setCredentialsByCredentialHelper(c.authFilePath, registry, result.Username, result.Password) err = setCredentialsByCredentialHelper(c.authFilePath, registry, result.Username, result.Password)
if err != nil { if err != nil {
@ -304,11 +303,11 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
// This shouldn't be fatal error. // This shouldn't be fatal error.
if strings.Contains(err.Error(), "not implemented") { if strings.Contains(err.Error(), "not implemented") {
fmt.Fprintf(os.Stderr, "the cred-helper does not support write operation (consider changing the cred-helper it in auth.json)\n") fmt.Fprintf(os.Stderr, "the cred-helper does not support write operation (consider changing the cred-helper it in auth.json)\n")
return docker.Credentials{}, nil return oci.Credentials{}, nil
} }
if !errors.Is(err, errNoCredentialHelperConfigured) { if !errors.Is(err, errNoCredentialHelperConfigured) {
return docker.Credentials{}, err return oci.Credentials{}, err
} }
} }
} }
@ -317,7 +316,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
if errors.Is(err, ErrUnauthorized) { if errors.Is(err, ErrUnauthorized) {
continue continue
} }
return docker.Credentials{}, err return oci.Credentials{}, err
} }
} }
} }
@ -374,8 +373,8 @@ func setCredentialHelperToConfig(confFilePath, helper string) error {
return nil return nil
} }
func getCredentialsByCredentialHelper(confFilePath, registry string) (docker.Credentials, error) { func getCredentialsByCredentialHelper(confFilePath, registry string) (oci.Credentials, error) {
result := docker.Credentials{} result := oci.Credentials{}
helper, err := getCredentialHelperFromConfig(confFilePath) helper, err := getCredentialHelperFromConfig(confFilePath)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {

View File

@ -26,8 +26,8 @@ import (
"github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/credentials"
"knative.dev/func/pkg/docker" "knative.dev/func/pkg/creds"
"knative.dev/func/pkg/docker/creds" "knative.dev/func/pkg/oci"
. "knative.dev/func/pkg/testing" . "knative.dev/func/pkg/testing"
) )
@ -162,7 +162,7 @@ func TestCheckAuth(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := docker.Credentials{ c := oci.Credentials{
Username: tt.args.username, Username: tt.args.username,
Password: tt.args.password, Password: tt.args.password,
} }
@ -199,7 +199,7 @@ func TestCheckAuth(t *testing.T) {
func TestCheckAuthEmptyCreds(t *testing.T) { func TestCheckAuthEmptyCreds(t *testing.T) {
localhost, _, _ := startServer(t, "", "") localhost, _, _ := startServer(t, "", "")
err := creds.CheckAuth(context.Background(), localhost+"/someorg/someimage:sometag", docker.Credentials{}, http.DefaultTransport) err := creds.CheckAuth(context.Background(), localhost+"/someorg/someimage:sometag", oci.Credentials{}, http.DefaultTransport)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -334,7 +334,7 @@ const (
quayIoUserPwd = "goodPwd2" quayIoUserPwd = "goodPwd2"
) )
type Credentials = docker.Credentials type Credentials = oci.Credentials
func TestNewCredentialsProvider(t *testing.T) { func TestNewCredentialsProvider(t *testing.T) {
helperWithQuayIO := newInMemoryHelper() helperWithQuayIO := newInMemoryHelper()
@ -460,8 +460,8 @@ func TestNewCredentialsProvider(t *testing.T) {
func TestNewCredentialsProviderEmptyCreds(t *testing.T) { func TestNewCredentialsProviderEmptyCreds(t *testing.T) {
resetHomeDir(t) resetHomeDir(t)
credentialsProvider := creds.NewCredentialsProvider(testConfigPath(t), creds.WithVerifyCredentials(func(ctx context.Context, image string, credentials docker.Credentials) error { credentialsProvider := creds.NewCredentialsProvider(testConfigPath(t), creds.WithVerifyCredentials(func(ctx context.Context, image string, credentials oci.Credentials) error {
if image == "localhost:5555/someorg/someimage:sometag" && credentials == (docker.Credentials{}) { if image == "localhost:5555/someorg/someimage:sometag" && credentials == (oci.Credentials{}) {
return nil return nil
} }
t.Fatal("unreachable") t.Fatal("unreachable")
@ -471,7 +471,7 @@ func TestNewCredentialsProviderEmptyCreds(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if c != (docker.Credentials{}) { if c != (oci.Credentials{}) {
t.Error("unexpected credentials") t.Error("unexpected credentials")
} }
} }

View File

@ -14,6 +14,7 @@ import (
"strings" "strings"
fn "knative.dev/func/pkg/functions" fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
@ -33,13 +34,6 @@ import (
type Opt func(*Pusher) type Opt func(*Pusher)
type Credentials struct {
Username string
Password string
}
type CredentialsProvider func(ctx context.Context, image string) (Credentials, error)
// PusherDockerClient is sub-interface of client.CommonAPIClient required by pusher. // PusherDockerClient is sub-interface of client.CommonAPIClient required by pusher.
type PusherDockerClient interface { type PusherDockerClient interface {
daemon.Client daemon.Client
@ -52,12 +46,12 @@ type PusherDockerClientFactory func() (PusherDockerClient, error)
// Pusher of images from local to remote registry. // Pusher of images from local to remote registry.
type Pusher struct { type Pusher struct {
verbose bool // verbose logging. verbose bool // verbose logging.
credentialsProvider CredentialsProvider credentialsProvider oci.CredentialsProvider
transport http.RoundTripper transport http.RoundTripper
dockerClientFactory PusherDockerClientFactory dockerClientFactory PusherDockerClientFactory
} }
func WithCredentialsProvider(cp CredentialsProvider) Opt { func WithCredentialsProvider(cp oci.CredentialsProvider) Opt {
return func(p *Pusher) { return func(p *Pusher) {
p.credentialsProvider = cp p.credentialsProvider = cp
} }
@ -81,14 +75,10 @@ func WithVerbose(verbose bool) Opt {
} }
} }
func EmptyCredentialsProvider(ctx context.Context, registry string) (Credentials, error) {
return Credentials{}, nil
}
// NewPusher creates an instance of a docker-based image pusher. // NewPusher creates an instance of a docker-based image pusher.
func NewPusher(opts ...Opt) *Pusher { func NewPusher(opts ...Opt) *Pusher {
result := &Pusher{ result := &Pusher{
credentialsProvider: EmptyCredentialsProvider, credentialsProvider: oci.EmptyCredentialsProvider,
transport: http.DefaultTransport, transport: http.DefaultTransport,
dockerClientFactory: func() (PusherDockerClient, error) { dockerClientFactory: func() (PusherDockerClient, error) {
c, _, err := NewClient(client.DefaultDockerHost) c, _, err := NewClient(client.DefaultDockerHost)
@ -176,7 +166,7 @@ func (n *Pusher) Push(ctx context.Context, f fn.Function) (string, error) {
return d.String(), nil return d.String(), nil
} }
func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials Credentials) (digest string, err error) { func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials oci.Credentials) (digest string, err error) {
var output io.Writer var output io.Writer
@ -211,7 +201,7 @@ func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials Crede
return "", err return "", err
} }
func (n *Pusher) daemonPush(ctx context.Context, f fn.Function, credentials Credentials, output io.Writer) (digest string, err error) { func (n *Pusher) daemonPush(ctx context.Context, f fn.Function, credentials oci.Credentials, output io.Writer) (digest string, err error) {
cli, err := n.dockerClientFactory() cli, err := n.dockerClientFactory()
if err != nil { if err != nil {
return "", fmt.Errorf("failed to create docker api client: %w", err) return "", fmt.Errorf("failed to create docker api client: %w", err)
@ -267,7 +257,7 @@ func ParseDigest(output string) string {
return "" return ""
} }
func (n *Pusher) push(ctx context.Context, f fn.Function, credentials Credentials, output io.Writer) (digest string, err error) { func (n *Pusher) push(ctx context.Context, f fn.Function, credentials oci.Credentials, output io.Writer) (digest string, err error) {
auth := &authn.Basic{ auth := &authn.Basic{
Username: credentials.Username, Username: credentials.Username,
Password: credentials.Password, Password: credentials.Password,

View File

@ -38,6 +38,7 @@ import (
"knative.dev/func/pkg/docker" "knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions" fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
) )
func TestGetRegistry(t *testing.T) { func TestGetRegistry(t *testing.T) {
@ -77,8 +78,8 @@ const (
imageRepoDigest = "sha256:00af51d125f3092e157a7f8a717029412dc9d266c017e89cecdfeccb4cc3d7a7" imageRepoDigest = "sha256:00af51d125f3092e157a7f8a717029412dc9d266c017e89cecdfeccb4cc3d7a7"
) )
var testCredProvider = docker.CredentialsProvider(func(ctx context.Context, registry string) (docker.Credentials, error) { var testCredProvider = oci.CredentialsProvider(func(ctx context.Context, registry string) (oci.Credentials, error) {
return docker.Credentials{ return oci.Credentials{
Username: testUser, Username: testUser,
Password: testPwd, Password: testPwd,
}, nil }, nil

85
pkg/k8s/keychains.go Normal file
View File

@ -0,0 +1,85 @@
package k8s
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/google"
"knative.dev/func/pkg/creds"
"knative.dev/func/pkg/oci"
)
func GetGoogleCredentialLoader() []creds.CredentialsCallback {
return []creds.CredentialsCallback{
func(registry string) (oci.Credentials, error) {
if registry != "gcr.io" {
return oci.Credentials{}, nil // skip if not GCR
}
res, err := name.NewRegistry(registry)
if err != nil {
return oci.Credentials{}, fmt.Errorf("parse registry: %w", err)
}
authenticator, err := google.Keychain.Resolve(res)
if err != nil {
return oci.Credentials{}, fmt.Errorf("resolve google keychain: %w", err)
}
authCfg, err := authenticator.Authorization()
if err != nil {
return oci.Credentials{}, fmt.Errorf("get authorization: %w", err)
}
return oci.Credentials{
Username: authCfg.Username,
Password: authCfg.Password,
}, nil
},
}
}
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) {
if !strings.HasSuffix(registry, ".azurecr.io") {
return oci.Credentials{}, nil
}
f, err := os.Open(path.Join(os.Getenv("HOME"), ".azure", "accessTokens.json"))
if err != nil {
return oci.Credentials{}, fmt.Errorf("open Azure access tokens: %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
}
}
return oci.Credentials{}, nil
},
}
}

View File

@ -14,9 +14,9 @@ import (
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
"knative.dev/func/pkg/docker" "knative.dev/func/pkg/creds"
"knative.dev/func/pkg/docker/creds"
fn "knative.dev/func/pkg/functions" fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
) )
const ( const (
@ -106,7 +106,7 @@ func GetOpenShiftDockerCredentialLoaders() []creds.CredentialsCallback {
if !ok { if !ok {
return nil return nil
} }
var credentials docker.Credentials var credentials oci.Credentials
if authInfo := rawConf.AuthInfos[cc.AuthInfo]; authInfo != nil { if authInfo := rawConf.AuthInfos[cc.AuthInfo]; authInfo != nil {
credentials.Username = "openshift" credentials.Username = "openshift"
@ -114,11 +114,11 @@ func GetOpenShiftDockerCredentialLoaders() []creds.CredentialsCallback {
} }
return []creds.CredentialsCallback{ return []creds.CredentialsCallback{
func(registry string) (docker.Credentials, error) { func(registry string) (oci.Credentials, error) {
if registry == openShiftRegistryHostPort { if registry == openShiftRegistryHostPort {
return credentials, nil return credentials, nil
} }
return docker.Credentials{}, creds.ErrCredentialsNotFound return oci.Credentials{}, creds.ErrCredentialsNotFound
}, },
} }

View File

@ -13,7 +13,6 @@ import (
"github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/google"
"github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -22,29 +21,63 @@ import (
fn "knative.dev/func/pkg/functions" fn "knative.dev/func/pkg/functions"
) )
type Credentials struct {
Username string
Password string
}
type CredentialsProvider func(ctx context.Context, image string) (Credentials, error)
type Opt func(*Pusher)
// Pusher of OCI multi-arch layout directories. // Pusher of OCI multi-arch layout directories.
type Pusher struct { type Pusher struct {
Anonymous bool Anonymous bool
Insecure bool credentialsProvider CredentialsProvider
Token string
Username string Insecure bool
Verbose bool Token string
Username string
Verbose bool
updates chan v1.Update updates chan v1.Update
done chan bool done chan bool
} }
func NewPusher(insecure, anon, verbose bool) *Pusher { func EmptyCredentialsProvider(ctx context.Context, registry string) (Credentials, error) {
return &Pusher{ return Credentials{}, nil
Insecure: insecure, }
Anonymous: anon,
Verbose: verbose, func WithCredentialsProvider(cp CredentialsProvider) Opt {
updates: make(chan v1.Update, 10), return func(p *Pusher) {
done: make(chan bool, 1), p.credentialsProvider = cp
} }
} }
func WithVerbose(verbose bool) Opt {
return func(pusher *Pusher) {
pusher.Verbose = verbose
}
}
func NewPusher(insecure, anon, verbose bool, opts ...Opt) *Pusher {
result := &Pusher{
credentialsProvider: EmptyCredentialsProvider,
Insecure: insecure,
Anonymous: anon,
Verbose: verbose,
updates: make(chan v1.Update, 10),
done: make(chan bool, 1),
}
for _, opt := range opts {
opt(result)
}
return result
}
func (p *Pusher) Push(ctx context.Context, f fn.Function) (digest string, err error) { func (p *Pusher) Push(ctx context.Context, f fn.Function) (digest string, err error) {
credentials, _ := p.credentialsProvider(ctx, f.Build.Image)
go p.handleUpdates(ctx) go p.handleUpdates(ctx)
defer func() { p.done <- true }() defer func() { p.done <- true }()
buildDir, err := getLastBuildDir(f) buildDir, err := getLastBuildDir(f)
@ -67,7 +100,7 @@ func (p *Pusher) Push(ctx context.Context, f fn.Function) (digest string, err er
if err != nil { if err != nil {
return return
} }
if err = p.writeIndex(ctx, ref, ii); err != nil { if err = p.writeIndex(ctx, ref, ii, credentials); err != nil {
return return
} }
h, err := ii.Digest() h, err := ii.Digest()
@ -120,7 +153,7 @@ func getLastBuildDir(f fn.Function) (string, error) {
} }
// writeIndex to its defined registry. // writeIndex to its defined registry.
func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.ImageIndex) error { func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.ImageIndex, creds Credentials) error {
oo := []remote.Option{ oo := []remote.Option{
remote.WithContext(ctx), remote.WithContext(ctx),
remote.WithProgress(p.updates), remote.WithProgress(p.updates),
@ -135,10 +168,13 @@ func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.Image
} }
if !p.Anonymous { if !p.Anonymous {
a, err := p.authOption(ctx) a, err := p.authOption(ctx, creds)
if err != nil { if err != nil {
return err return err
} }
if a == nil {
return errors.New("no authentication option provided")
}
oo = append(oo, a) oo = append(oo, a)
} }
@ -148,13 +184,14 @@ func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.Image
// authOption selects an appropriate authentication option. // authOption selects an appropriate authentication option.
// If user provided = basic auth (secret is password) // If user provided = basic auth (secret is password)
// If only secret provided = bearer token auth // If only secret provided = bearer token auth
// If neither are provided = Returned is a cascading keychain auth mthod // If neither are provided = creds from credentials provider
// which performs the following in order: // which performs the following in order:
// - Default Keychain (docker and podman config files) // - Default Keychain (docker and podman config files)
// - Google Keychain // - Google Keychain
// - TODO: ECR Amazon // - TODO: ECR Amazon
// - TODO: ACR Azure // - TODO: ACR Azure
func (p *Pusher) authOption(ctx context.Context) (remote.Option, error) { // - interactive prompt for username and password
func (p *Pusher) authOption(ctx context.Context, creds Credentials) (remote.Option, error) {
// Basic Auth if provided // Basic Auth if provided
username, _ := ctx.Value(fn.PushUsernameKey{}).(string) username, _ := ctx.Value(fn.PushUsernameKey{}).(string)
@ -168,12 +205,10 @@ func (p *Pusher) authOption(ctx context.Context) (remote.Option, error) {
return remote.WithAuth(&authn.Basic{Username: username, Password: password}), nil return remote.WithAuth(&authn.Basic{Username: username, Password: password}), nil
} }
// Default chain // Use provided credentials if available or prompt for them
return remote.WithAuthFromKeychain(authn.NewMultiKeychain( if creds.Username != "" && creds.Password != "" {
authn.DefaultKeychain, // Podman and Docker config files return remote.WithAuth(&authn.Basic{Username: creds.Username, Password: creds.Password}), nil
google.Keychain, // Google }
// TODO: Integrate and test ECR and ACR credential helpers:
// authn.NewKeychainFromHelper(ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}), return nil, nil
// authn.NewKeychainFromHelper(acr.ACRCredHelper{}),
)), nil
} }

View File

@ -37,9 +37,9 @@ import (
"knative.dev/pkg/apis" "knative.dev/pkg/apis"
"knative.dev/func/pkg/builders/buildpacks" "knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions" fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s" "knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/oci"
"knative.dev/func/pkg/pipelines" "knative.dev/func/pkg/pipelines"
"knative.dev/func/pkg/pipelines/tekton" "knative.dev/func/pkg/pipelines/tekton"
"knative.dev/func/pkg/random" "knative.dev/func/pkg/random"
@ -102,8 +102,8 @@ func TestGitlab(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
credentialsProvider := func(ctx context.Context, image string) (docker.Credentials, error) { credentialsProvider := func(ctx context.Context, image string) (oci.Credentials, error) {
return docker.Credentials{ return oci.Credentials{
Username: "", Username: "",
Password: "", Password: "",
}, nil }, nil

View File

@ -23,6 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/func/pkg/k8s" "knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative" "knative.dev/func/pkg/knative"
"knative.dev/func/pkg/oci"
"knative.dev/func/pkg/builders/buildpacks" "knative.dev/func/pkg/builders/buildpacks"
pack "knative.dev/func/pkg/builders/buildpacks" pack "knative.dev/func/pkg/builders/buildpacks"
@ -34,8 +35,8 @@ import (
. "knative.dev/func/pkg/testing" . "knative.dev/func/pkg/testing"
) )
var testCP = func(_ context.Context, _ string) (docker.Credentials, error) { var testCP = func(_ context.Context, _ string) (oci.Credentials, error) {
return docker.Credentials{ return oci.Credentials{
Username: "", Username: "",
Password: "", Password: "",
}, nil }, nil

View File

@ -34,6 +34,7 @@ import (
"knative.dev/func/pkg/k8s" "knative.dev/func/pkg/k8s"
fnlabels "knative.dev/func/pkg/k8s/labels" fnlabels "knative.dev/func/pkg/k8s/labels"
"knative.dev/func/pkg/knative" "knative.dev/func/pkg/knative"
"knative.dev/func/pkg/oci"
"knative.dev/pkg/apis" "knative.dev/pkg/apis"
) )
@ -54,11 +55,11 @@ type pacURLCallback = func() (string, error)
type PipelinesProvider struct { type PipelinesProvider struct {
verbose bool verbose bool
getPacURL pacURLCallback getPacURL pacURLCallback
credentialsProvider docker.CredentialsProvider credentialsProvider oci.CredentialsProvider
decorator PipelineDecorator decorator PipelineDecorator
} }
func WithCredentialsProvider(credentialsProvider docker.CredentialsProvider) Opt { func WithCredentialsProvider(credentialsProvider oci.CredentialsProvider) Opt {
return func(pp *PipelinesProvider) { return func(pp *PipelinesProvider) {
pp.credentialsProvider = credentialsProvider pp.credentialsProvider = credentialsProvider
} }