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)}
switch c.Builder {
case builders.Host:
t := newTransport(c.RegistryInsecure) // may provide a custom impl which proxies
creds := newCredentialsProvider(config.Dir(), t)
o = append(o,
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:
o = append(o,
fn.WithBuilder(pack.NewBuilder(

View File

@ -8,12 +8,13 @@ import (
"knative.dev/func/cmd/prompt"
"knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/config"
"knative.dev/func/pkg/creds"
"knative.dev/func/pkg/docker"
"knative.dev/func/pkg/docker/creds"
fn "knative.dev/func/pkg/functions"
fnhttp "knative.dev/func/pkg/http"
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative"
"knative.dev/func/pkg/oci"
"knative.dev/func/pkg/pipelines/tekton"
)
@ -100,19 +101,22 @@ func newTransport(insecureSkipVerify bool) fnhttp.RoundTripCloser {
// newCredentialsProvider returns a credentials provider which possibly
// has cluster-flavor specific additional credential loaders to take advantage
// 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{
creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, os.Stdout, os.Stderr)),
creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
creds.WithTransport(t),
creds.WithAdditionalCredentialLoaders(k8s.GetOpenShiftDockerCredentialLoaders()...),
creds.WithAdditionalCredentialLoaders(additionalLoaders...),
}
// Other cluster variants can be supported here
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{
tekton.WithCredentialsProvider(creds),
tekton.WithVerbose(verbose),

View File

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

View File

@ -12,8 +12,7 @@ import (
"github.com/Netflix/go-expect"
"github.com/creack/pty"
"github.com/hinshun/vt10x"
"knative.dev/func/pkg/docker"
"knative.dev/func/pkg/oci"
)
const (
@ -21,7 +20,7 @@ const (
)
func Test_NewPromptForCredentials(t *testing.T) {
expectedCreds := docker.Credentials{
expectedCreds := oci.Credentials{
Username: "testuser",
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/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")
@ -33,7 +33,7 @@ var ErrCredentialsNotFound = errors.New("credentials not found")
// VerifyCredentialsCallback checks if credentials are authorized for image push.
// 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 {
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
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)
if err != nil {
@ -142,9 +142,8 @@ func WithAdditionalCredentialLoaders(loaders ...CredentialsCallback) Opt {
// The picked value will be saved in the func config.
//
// 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
for _, o := range opts {
o(&c)
}
@ -154,7 +153,7 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
}
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)
}
}
@ -175,7 +174,7 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
if _, err := os.Stat(c.authFilePath); err == nil {
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
func(registry string) (oci.Credentials, error) {
return getCredentialsByCredentialHelper(c.authFilePath, registry)
})
}
@ -185,40 +184,40 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
if err == nil {
dockerConfigPath := filepath.Join(home, ".docker", "config.json")
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
func(registry string) (oci.Credentials, error) {
return getCredentialsByCredentialHelper(dockerConfigPath, registry)
})
}
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
func(registry string) (oci.Credentials, error) {
creds, err := dockerConfig.GetCredentials(sys, registry)
if err != nil {
return docker.Credentials{}, err
return oci.Credentials{}, err
}
if creds.Username == "" || creds.Password == "" {
return docker.Credentials{}, ErrCredentialsNotFound
return oci.Credentials{}, ErrCredentialsNotFound
}
return docker.Credentials{
return oci.Credentials{
Username: creds.Username,
Password: creds.Password,
}, nil
})
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
func(registry string) (oci.Credentials, error) {
// Fallback onto default docker config locations
emptySys := &containersTypes.SystemContext{}
creds, err := dockerConfig.GetCredentials(emptySys, registry)
if err != nil {
return docker.Credentials{}, err
return oci.Credentials{}, err
}
return docker.Credentials{
return oci.Credentials{
Username: creds.Username,
Password: creds.Password,
}, nil
})
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { // empty credentials provider for unsecured registries
return docker.Credentials{}, nil
func(registry string) (oci.Credentials, error) { // empty credentials provider for unsecured registries
return oci.Credentials{}, nil
})
c.credentialLoaders = append(c.credentialLoaders, defaultCredentialLoaders...)
@ -226,13 +225,13 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
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
result := docker.Credentials{}
result := oci.Credentials{}
ref, err := name.ParseReference(image)
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()
@ -244,7 +243,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
if errors.Is(err, ErrCredentialsNotFound) {
continue
}
return docker.Credentials{}, err
return oci.Credentials{}, err
}
err = c.verifyCredentials(ctx, image, result)
@ -252,14 +251,14 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
return result, nil
} else {
if !errors.Is(err, ErrUnauthorized) {
return docker.Credentials{}, err
return oci.Credentials{}, err
}
}
}
if c.promptForCredentials == nil {
return docker.Credentials{}, ErrCredentialsNotFound
return oci.Credentials{}, ErrCredentialsNotFound
}
// 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
result, err = c.promptForCredentials(repository)
if err != nil {
return docker.Credentials{}, err
return oci.Credentials{}, err
}
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.
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")
return docker.Credentials{}, nil
return oci.Credentials{}, nil
}
if !errors.Is(err, errNoCredentialHelperConfigured) {
return docker.Credentials{}, err
return oci.Credentials{}, err
}
helpers := listCredentialHelpers()
helper, err := c.promptForCredentialStore(helpers)
if err != nil {
return docker.Credentials{}, err
return oci.Credentials{}, err
}
helper = strings.TrimPrefix(helper, "docker-credential-")
err = setCredentialHelperToConfig(c.authFilePath, helper)
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)
if err != nil {
@ -304,11 +303,11 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
// This shouldn't be fatal error.
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")
return docker.Credentials{}, nil
return oci.Credentials{}, nil
}
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) {
continue
}
return docker.Credentials{}, err
return oci.Credentials{}, err
}
}
}
@ -374,8 +373,8 @@ func setCredentialHelperToConfig(confFilePath, helper string) error {
return nil
}
func getCredentialsByCredentialHelper(confFilePath, registry string) (docker.Credentials, error) {
result := docker.Credentials{}
func getCredentialsByCredentialHelper(confFilePath, registry string) (oci.Credentials, error) {
result := oci.Credentials{}
helper, err := getCredentialHelperFromConfig(confFilePath)
if err != nil && !os.IsNotExist(err) {

View File

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

View File

@ -14,6 +14,7 @@ import (
"strings"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
@ -33,13 +34,6 @@ import (
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.
type PusherDockerClient interface {
daemon.Client
@ -52,12 +46,12 @@ type PusherDockerClientFactory func() (PusherDockerClient, error)
// Pusher of images from local to remote registry.
type Pusher struct {
verbose bool // verbose logging.
credentialsProvider CredentialsProvider
credentialsProvider oci.CredentialsProvider
transport http.RoundTripper
dockerClientFactory PusherDockerClientFactory
}
func WithCredentialsProvider(cp CredentialsProvider) Opt {
func WithCredentialsProvider(cp oci.CredentialsProvider) Opt {
return func(p *Pusher) {
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.
func NewPusher(opts ...Opt) *Pusher {
result := &Pusher{
credentialsProvider: EmptyCredentialsProvider,
credentialsProvider: oci.EmptyCredentialsProvider,
transport: http.DefaultTransport,
dockerClientFactory: func() (PusherDockerClient, error) {
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
}
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
@ -211,7 +201,7 @@ func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials Crede
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()
if err != nil {
return "", fmt.Errorf("failed to create docker api client: %w", err)
@ -267,7 +257,7 @@ func ParseDigest(output string) string {
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{
Username: credentials.Username,
Password: credentials.Password,

View File

@ -38,6 +38,7 @@ import (
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
)
func TestGetRegistry(t *testing.T) {
@ -77,8 +78,8 @@ const (
imageRepoDigest = "sha256:00af51d125f3092e157a7f8a717029412dc9d266c017e89cecdfeccb4cc3d7a7"
)
var testCredProvider = docker.CredentialsProvider(func(ctx context.Context, registry string) (docker.Credentials, error) {
return docker.Credentials{
var testCredProvider = oci.CredentialsProvider(func(ctx context.Context, registry string) (oci.Credentials, error) {
return oci.Credentials{
Username: testUser,
Password: testPwd,
}, 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/util/rand"
"knative.dev/func/pkg/docker"
"knative.dev/func/pkg/docker/creds"
"knative.dev/func/pkg/creds"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci"
)
const (
@ -106,7 +106,7 @@ func GetOpenShiftDockerCredentialLoaders() []creds.CredentialsCallback {
if !ok {
return nil
}
var credentials docker.Credentials
var credentials oci.Credentials
if authInfo := rawConf.AuthInfos[cc.AuthInfo]; authInfo != nil {
credentials.Username = "openshift"
@ -114,11 +114,11 @@ func GetOpenShiftDockerCredentialLoaders() []creds.CredentialsCallback {
}
return []creds.CredentialsCallback{
func(registry string) (docker.Credentials, error) {
func(registry string) (oci.Credentials, error) {
if registry == openShiftRegistryHostPort {
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/name"
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/remote"
"github.com/pkg/errors"
@ -22,29 +21,63 @@ import (
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.
type Pusher struct {
Anonymous bool
Insecure bool
Token string
Username string
Verbose bool
Anonymous bool
credentialsProvider CredentialsProvider
Insecure bool
Token string
Username string
Verbose bool
updates chan v1.Update
done chan bool
}
func NewPusher(insecure, anon, verbose bool) *Pusher {
return &Pusher{
Insecure: insecure,
Anonymous: anon,
Verbose: verbose,
updates: make(chan v1.Update, 10),
done: make(chan bool, 1),
func EmptyCredentialsProvider(ctx context.Context, registry string) (Credentials, error) {
return Credentials{}, nil
}
func WithCredentialsProvider(cp CredentialsProvider) Opt {
return func(p *Pusher) {
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) {
credentials, _ := p.credentialsProvider(ctx, f.Build.Image)
go p.handleUpdates(ctx)
defer func() { p.done <- true }()
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 {
return
}
if err = p.writeIndex(ctx, ref, ii); err != nil {
if err = p.writeIndex(ctx, ref, ii, credentials); err != nil {
return
}
h, err := ii.Digest()
@ -120,7 +153,7 @@ func getLastBuildDir(f fn.Function) (string, error) {
}
// 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{
remote.WithContext(ctx),
remote.WithProgress(p.updates),
@ -135,10 +168,13 @@ func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.Image
}
if !p.Anonymous {
a, err := p.authOption(ctx)
a, err := p.authOption(ctx, creds)
if err != nil {
return err
}
if a == nil {
return errors.New("no authentication option provided")
}
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.
// If user provided = basic auth (secret is password)
// 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:
// - Default Keychain (docker and podman config files)
// - Google Keychain
// - TODO: ECR Amazon
// - 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
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
}
// Default chain
return remote.WithAuthFromKeychain(authn.NewMultiKeychain(
authn.DefaultKeychain, // Podman and Docker config files
google.Keychain, // Google
// TODO: Integrate and test ECR and ACR credential helpers:
// authn.NewKeychainFromHelper(ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}),
// authn.NewKeychainFromHelper(acr.ACRCredHelper{}),
)), nil
// Use provided credentials if available or prompt for them
if creds.Username != "" && creds.Password != "" {
return remote.WithAuth(&authn.Basic{Username: creds.Username, Password: creds.Password}), nil
}
return nil, nil
}

View File

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

View File

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

View File

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