Skip to content

Commit

Permalink
use oras-credentials-go
Browse files Browse the repository at this point in the history
Signed-off-by: Sylvia Lei <[email protected]>
  • Loading branch information
Wwwsylvia committed May 17, 2023
1 parent 3d78a89 commit 03617b0
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 716 deletions.
14 changes: 14 additions & 0 deletions cmd/notation/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

"github.com/spf13/pflag"
"oras.land/oras-go/v2/registry/remote/auth"
)

const (
Expand Down Expand Up @@ -55,3 +56,16 @@ func (opts *SecureFlagOpts) ApplyFlags(fs *pflag.FlagSet) {
opts.Username = os.Getenv(defaultUsernameEnv)
opts.Password = os.Getenv(defaultPasswordEnv)
}

// Credential returns an auth.Credential from opts.Username and opts.Password.
func (opts *SecureFlagOpts) Credential() auth.Credential {
if opts.Username == "" {
return auth.Credential{
RefreshToken: opts.Password,
}
}
return auth.Credential{
Username: opts.Username,
Password: opts.Password,
}
}
61 changes: 61 additions & 0 deletions cmd/notation/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"reflect"
"testing"

"oras.land/oras-go/v2/registry/remote/auth"
)

func TestSecureFlagOpts_Credential(t *testing.T) {
tests := []struct {
name string
opts *SecureFlagOpts
want auth.Credential
}{
{
name: "Username and password",
opts: &SecureFlagOpts{
Username: "username",
Password: "password",
},
want: auth.Credential{
Username: "username",
Password: "password",
},
},
{
name: "Username only",
opts: &SecureFlagOpts{
Username: "username",
},
want: auth.Credential{
Username: "username",
},
},
{
name: "Password only",
opts: &SecureFlagOpts{
Password: "token",
},
want: auth.Credential{
RefreshToken: "token",
},
},
{
name: "Empty username and password",
opts: &SecureFlagOpts{
Username: "",
Password: "",
},
want: auth.EmptyCredential,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.opts.Credential(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("SecureFlagOpts.Credential() = %v, want %v", got, tt.want)
}
})
}
}
108 changes: 61 additions & 47 deletions cmd/notation/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
"os"
"strings"

"github.com/notaryproject/notation/internal/auth"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/pkg/auth"
credentials "github.com/oras-project/oras-credentials-go"
"github.com/spf13/cobra"
"golang.org/x/term"
orasauth "oras.land/oras-go/v2/registry/remote/auth"
)

const urlDocHowToAuthenticate = "https://notaryproject.dev/docs/how-to/registry-authentication/"

type loginOpts struct {
cmd.LoggingFlagOpts
SecureFlagOpts
Expand Down Expand Up @@ -70,61 +72,65 @@ func runLogin(ctx context.Context, opts *loginOpts) error {
// input username and password by prompt
reader := bufio.NewReader(os.Stdin)
var err error
if opts.Username == "" {
opts.Username, err = readUsernameFromPrompt(reader)
if err != nil {
return err
}
}

if opts.Password == "" {
opts.Password, err = readPasswordFromPrompt(reader)
if err != nil {
return err
if opts.Username == "" {
opts.Username, err = readUsernameFromPrompt(reader)
if err != nil {
return err
}
}
if opts.Username == "" {
// username is empty, prompt for token
opts.Password, err = readPasswordFromPrompt(reader, true)
if err != nil {
return err
}
if opts.Password == "" {
return errors.New("token required")
}
} else {
// username is not empty, prompt for password
opts.Password, err = readPasswordFromPrompt(reader, false)
if err != nil {
return err
}
if opts.Password == "" {
return errors.New("password required")
}
}
}
cred := opts.Credential()

if err := validateAuthConfig(ctx, opts, serverAddress); err != nil {
return err
credsStore, err := auth.NewCredentialsStore()
if err != nil {
return fmt.Errorf("failed to get credentials store: %v", err)
}

nativeStore, err := auth.GetCredentialsStore(ctx, serverAddress)
registry, err := getRegistryLoginClient(ctx, &opts.SecureFlagOpts, serverAddress)
if err != nil {
return fmt.Errorf("could not get the credentials store: %v", err)
return fmt.Errorf("failed to get registry client: %v", err)
}
if err := credentials.Login(ctx, credsStore, registry, cred); err != nil {
registryName := registry.Reference.Registry
if !errors.Is(err, credentials.ErrPlaintextPutDisabled) {
return fmt.Errorf("failed to log in to %s: %v", registryName, err)
}

// init creds
creds := newCredentialFromInput(
opts.Username,
opts.Password,
)
if err = nativeStore.Store(serverAddress, creds); err != nil {
return fmt.Errorf("failed to store credentials: %v", err)
// ErrPlaintextPutDisabled returned by Login() indicates that the credential is validated
// but is not saved because there is no native credentials store available
if savedCred, err := credsStore.Get(ctx, registryName); err == nil && savedCred == cred {
// there is an existing identical credential, ignore saving error
fmt.Fprintf(os.Stderr, "Warning: The credentials store is not set up. It is recommended to configure the credentials store to securely store your credentials. See %s.\n", urlDocHowToAuthenticate)
fmt.Println("Authenticated with existing credentials")
} else {
return fmt.Errorf("failed to log in to %s: the credential could not be saved because a credentials store is required to securely store the password. See %s",
registryName, urlDocHowToAuthenticate)
}
}

fmt.Println("Login Succeeded")
return nil
}

func validateAuthConfig(ctx context.Context, opts *loginOpts, serverAddress string) error {
registry, err := getRegistryClient(ctx, &opts.SecureFlagOpts, serverAddress)
if err != nil {
return err
}
return registry.Ping(ctx)
}

func newCredentialFromInput(username, password string) orasauth.Credential {
c := orasauth.Credential{
Username: username,
Password: password,
}
if c.Username == "" {
c.RefreshToken = password
}
return c
}

func readPassword(opts *loginOpts) error {
if opts.passwordStdin {
password, err := readLine(os.Stdin)
Expand Down Expand Up @@ -156,19 +162,27 @@ func readUsernameFromPrompt(reader *bufio.Reader) (string, error) {
return username, nil
}

func readPasswordFromPrompt(reader *bufio.Reader) (string, error) {
fmt.Print("Password: ")
func readPasswordFromPrompt(reader *bufio.Reader, isToken bool) (string, error) {
var passwordType string
if isToken {
passwordType = "token"
fmt.Print("Token: ")
} else {
passwordType = "password"
fmt.Print("Password: ")
}

if term.IsTerminal(int(os.Stdin.Fd())) {
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", fmt.Errorf("error reading password: %w", err)
return "", fmt.Errorf("error reading %s: %w", passwordType, err)
}
fmt.Println()
return string(bytePassword), nil
} else {
password, err := readLine(reader)
if err != nil {
return "", fmt.Errorf("error reading password: %w", err)
return "", fmt.Errorf("error reading %s: %w", passwordType, err)
}
fmt.Println()
return password, nil
Expand Down
15 changes: 6 additions & 9 deletions cmd/notation/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"errors"
"fmt"

"github.com/notaryproject/notation/internal/auth"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/pkg/auth"
credentials "github.com/oras-project/oras-credentials-go"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -40,16 +41,12 @@ func logoutCommand(opts *logoutOpts) *cobra.Command {
func runLogout(ctx context.Context, opts *logoutOpts) error {
// set log level
ctx = opts.LoggingFlagOpts.SetLoggerLevel(ctx)

// initialize
serverAddress := opts.server
nativeStore, err := auth.GetCredentialsStore(ctx, serverAddress)
credsStore, err := auth.NewCredentialsStore()
if err != nil {
return err
return fmt.Errorf("failed to get credentials store: %v", err)
}
err = nativeStore.Erase(serverAddress)
if err != nil {
return err
if err := credentials.Logout(ctx, credsStore, opts.server); err != nil {
return fmt.Errorf("failed to log out of %s: %v", opts.server, err)
}

fmt.Println("Logout Succeeded")
Expand Down
67 changes: 25 additions & 42 deletions cmd/notation/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package main
import (
"context"
"errors"
"fmt"
"net"
"net/http"

"github.com/notaryproject/notation-go/log"
notationregistry "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation/cmd/notation/internal/experimental"
notationauth "github.com/notaryproject/notation/internal/auth"
"github.com/notaryproject/notation/internal/trace"
"github.com/notaryproject/notation/internal/version"
loginauth "github.com/notaryproject/notation/pkg/auth"
"github.com/notaryproject/notation/pkg/configutil"
credentials "github.com/oras-project/oras-credentials-go"
"github.com/sirupsen/logrus"
"oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote"
Expand Down Expand Up @@ -80,7 +82,7 @@ func getRemoteRepository(ctx context.Context, opts *SecureFlagOpts, reference st
}

func getRepositoryClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Reference) (*remote.Repository, error) {
authClient, plainHTTP, err := getAuthClient(ctx, opts, ref)
authClient, plainHTTP, err := getAuthClient(ctx, opts, ref, true)
if err != nil {
return nil, err
}
Expand All @@ -92,13 +94,13 @@ func getRepositoryClient(ctx context.Context, opts *SecureFlagOpts, ref registry
}, nil
}

func getRegistryClient(ctx context.Context, opts *SecureFlagOpts, serverAddress string) (*remote.Registry, error) {
func getRegistryLoginClient(ctx context.Context, opts *SecureFlagOpts, serverAddress string) (*remote.Registry, error) {
reg, err := remote.NewRegistry(serverAddress)
if err != nil {
return nil, err
}

reg.Client, reg.PlainHTTP, err = getAuthClient(ctx, opts, reg.Reference)
reg.Client, reg.PlainHTTP, err = getAuthClient(ctx, opts, reg.Reference, false)
if err != nil {
return nil, err
}
Expand All @@ -118,9 +120,10 @@ func setHttpDebugLog(ctx context.Context, authClient *auth.Client) {
authClient.Client.Transport = trace.NewTransport(authClient.Client.Transport)
}

func getAuthClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Reference) (*auth.Client, bool, error) {
// getAuthClient returns an *auth.Client and a bool indicating if plain HTTP
// should be used.
func getAuthClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Reference, withCredential bool) (*auth.Client, bool, error) {
var plainHTTP bool

if opts.PlainHTTP {
plainHTTP = opts.PlainHTTP
} else {
Expand All @@ -131,49 +134,29 @@ func getAuthClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Refer
}
}
}
cred := auth.Credential{
Username: opts.Username,
Password: opts.Password,
}
if cred.Username == "" {
cred = auth.Credential{
RefreshToken: cred.Password,
}
}
if cred == auth.EmptyCredential {
var err error
cred, err = getSavedCreds(ctx, ref.Registry)
// local registry may not need credentials
if err != nil && !errors.Is(err, loginauth.ErrCredentialsConfigNotSet) {
return nil, false, err
}
}

// build authClient
authClient := &auth.Client{
Credential: func(ctx context.Context, registry string) (auth.Credential, error) {
switch registry {
case ref.Host():
return cred, nil
default:
return auth.EmptyCredential, nil
}
},
Cache: auth.NewCache(),
ClientID: "notation",
}
authClient.SetUserAgent("notation/" + version.GetVersion())

// update authClient
setHttpDebugLog(ctx, authClient)

return authClient, plainHTTP, nil
}

func getSavedCreds(ctx context.Context, serverAddress string) (auth.Credential, error) {
nativeStore, err := loginauth.GetCredentialsStore(ctx, serverAddress)
if err != nil {
return auth.EmptyCredential, err
if !withCredential {
return authClient, plainHTTP, nil
}

return nativeStore.Get(serverAddress)
cred := opts.Credential()
if cred != auth.EmptyCredential {
// use the specified credential
authClient.Credential = auth.StaticCredential(ref.Host(), cred)
} else {
// use saved credentials
credsStore, err := notationauth.NewCredentialsStore()
if err != nil {
return nil, false, fmt.Errorf("failed to get credentials store: %w", err)
}
authClient.Credential = credentials.Credential(credsStore)
}
return authClient, plainHTTP, nil
}
Loading

0 comments on commit 03617b0

Please sign in to comment.