Skip to content

Commit

Permalink
[Feature] support notation login (notaryproject#218)
Browse files Browse the repository at this point in the history
* feat: support notation login
* refactor: Refactor for readablity
* chore: refactor registry.go
* feat: update CMDs to be consistent with specs
* chore: refactor load config logic

Signed-off-by: Binbin Li <[email protected]>
  • Loading branch information
binbin-li authored Jul 20, 2022
1 parent 88de7d8 commit bb5b497
Show file tree
Hide file tree
Showing 19 changed files with 433 additions and 20 deletions.
3 changes: 0 additions & 3 deletions cmd/notation/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ var (
flagLocal,
flagUsername,
flagPassword,
flagPlainHTTP,
},
ArgsUsage: "[reference|manifest_digest]",
Action: listCachedSignatures,
Expand Down Expand Up @@ -62,7 +61,6 @@ var (
flagLocal,
flagUsername,
flagPassword,
flagPlainHTTP,
},
Action: pruneCachedSignatures,
}
Expand All @@ -76,7 +74,6 @@ var (
flagLocal,
flagUsername,
flagPassword,
flagPlainHTTP,
},
Action: removeCachedSignatures,
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/notation/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ var (
flagUsername = &cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "username for generic remote access",
Usage: "Username for registry operations",
EnvVars: []string{"NOTATION_USERNAME"},
}
flagPassword = &cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Usage: "password for generic remote access",
Usage: "Password for registry operations",
EnvVars: []string{"NOTATION_PASSWORD"},
}
flagPlainHTTP = &cli.BoolFlag{
Name: "plain-http",
Usage: "remote access via plain HTTP",
Usage: "Registry access via plain HTTP",
}
flagMediaType = &cli.StringFlag{
Name: "media-type",
Expand Down
1 change: 0 additions & 1 deletion cmd/notation/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ var listCommand = &cli.Command{
Flags: []cli.Flag{
flagUsername,
flagPassword,
flagPlainHTTP,
},
Action: runList,
}
Expand Down
102 changes: 102 additions & 0 deletions cmd/notation/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"errors"
"fmt"
"io"
"os"
"strings"

"github.com/notaryproject/notation/pkg/auth"
"github.com/urfave/cli/v2"
orasauth "oras.land/oras-go/v2/registry/remote/auth"
)

var loginCommand = &cli.Command{
Name: "login",
Usage: "Provides credentials for authenticated registry operations",
UsageText: `notation login [options] [server]
Example - Login with provided username and password:
notation login -u <user> -p <password> registry.example.com
Example - Login using $NOTATION_USERNAME $NOTATION_PASSWORD variables:
notation login registry.example.com`,
ArgsUsage: "[server]",
Flags: []cli.Flag{
flagUsername,
flagPassword,
&cli.BoolFlag{
Name: "password-stdin",
Usage: "Take the password from stdin",
},
},
Before: readPassword,
Action: runLogin,
}

func runLogin(ctx *cli.Context) error {
// initialize
if !ctx.Args().Present() {
return errors.New("no hostname specified")
}
serverAddress := ctx.Args().First()

if err := validateAuthConfig(ctx, serverAddress); err != nil {
return err
}

nativeStore, err := auth.GetCredentialsStore(serverAddress)
if err != nil {
return fmt.Errorf("could not get the credentials store: %v", err)
}
// init creds
creds := newCredentialFromInput(
ctx.String(flagUsername.Name),
ctx.String(flagPassword.Name),
)
if err = nativeStore.Store(serverAddress, creds); err != nil {
return fmt.Errorf("failed to store credentials: %v", err)
}
return nil
}

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

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

func readPassword(ctx *cli.Context) error {
if ctx.Bool("password-stdin") {
password, err := readLine()
if err != nil {
return err
}
ctx.Set(flagPassword.Name, password)
}
return nil
}

func readLine() (string, error) {
passwordBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", err
}
password := strings.TrimSuffix(string(passwordBytes), "\n")
password = strings.TrimSuffix(password, "\r")
return password, nil
}
28 changes: 28 additions & 0 deletions cmd/notation/logout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"errors"

"github.com/notaryproject/notation/pkg/auth"
"github.com/urfave/cli/v2"
)

var logoutCommand = &cli.Command{
Name: "logout",
Usage: "Log out the specified registry hostname",
ArgsUsage: "[server]",
Action: runLogout,
}

func runLogout(ctx *cli.Context) error {
// initialize
if !ctx.Args().Present() {
return errors.New("no hostname specified")
}
serverAddress := ctx.Args().First()
nativeStore, err := auth.GetCredentialsStore(serverAddress)
if err != nil {
return err
}
return nativeStore.Erase(serverAddress)
}
5 changes: 5 additions & 0 deletions cmd/notation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func main() {
Name: "CNCF Notary Project",
},
},
Flags: []cli.Flag{
flagPlainHTTP,
},
Commands: []*cli.Command{
signCommand,
verifyCommand,
Expand All @@ -28,6 +31,8 @@ func main() {
keyCommand,
cacheCommand,
pluginCommand,
loginCommand,
logoutCommand,
},
}
if err := app.Run(os.Args); err != nil {
Expand Down
5 changes: 4 additions & 1 deletion cmd/notation/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ func getManifestDescriptorFromReference(ctx *cli.Context, reference string) (not
if err != nil {
return notation.Descriptor{}, err
}
repo := getRepositoryClient(ctx, ref)
repo, err := getRepositoryClient(ctx, ref)
if err != nil {
return notation.Descriptor{}, err
}
return repo.Resolve(ctx.Context, ref.ReferenceOrDefault())
}

Expand Down
1 change: 0 additions & 1 deletion cmd/notation/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ var pullCommand = &cli.Command{
flagOutput,
flagUsername,
flagPassword,
flagPlainHTTP,
},
Action: runPull,
}
Expand Down
1 change: 0 additions & 1 deletion cmd/notation/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ var pushCommand = &cli.Command{
flagSignature,
flagUsername,
flagPassword,
flagPlainHTTP,
},
Action: runPush,
}
Expand Down
45 changes: 42 additions & 3 deletions cmd/notation/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (

notationregistry "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation/internal/version"
loginauth "github.com/notaryproject/notation/pkg/auth"
"github.com/notaryproject/notation/pkg/config"
"github.com/urfave/cli/v2"
"oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
)

Expand All @@ -17,10 +19,31 @@ func getSignatureRepository(ctx *cli.Context, reference string) (notationregistr
if err != nil {
return nil, err
}
return getRepositoryClient(ctx, ref), nil
return getRepositoryClient(ctx, ref)
}

func getRepositoryClient(ctx *cli.Context, ref registry.Reference) *notationregistry.RepositoryClient {
func getRegistryClient(ctx *cli.Context, serverAddress string) (*remote.Registry, error) {
reg, err := remote.NewRegistry(serverAddress)
if err != nil {
return nil, err
}

reg.Client, reg.PlainHTTP, err = getAuthClient(ctx, reg.Reference)
if err != nil {
return nil, err
}
return reg, nil
}

func getRepositoryClient(ctx *cli.Context, ref registry.Reference) (*notationregistry.RepositoryClient, error) {
authClient, plainHTTP, err := getAuthClient(ctx, ref)
if err != nil {
return nil, err
}
return notationregistry.NewRepositoryClient(authClient, ref, plainHTTP), nil
}

func getAuthClient(ctx *cli.Context, ref registry.Reference) (*auth.Client, bool, error) {
var plainHTTP bool
if ctx.IsSet(flagPlainHTTP.Name) {
plainHTTP = ctx.Bool(flagPlainHTTP.Name)
Expand All @@ -42,6 +65,13 @@ func getRepositoryClient(ctx *cli.Context, ref registry.Reference) *notationregi
RefreshToken: cred.Password,
}
}
if cred == auth.EmptyCredential {
var err error
if cred, err = getSavedCreds(ref.Registry); err != nil {
return nil, false, err
}
}

authClient := &auth.Client{
Credential: func(ctx context.Context, registry string) (auth.Credential, error) {
switch registry {
Expand All @@ -56,5 +86,14 @@ func getRepositoryClient(ctx *cli.Context, ref registry.Reference) *notationregi
}
authClient.SetUserAgent("notation/" + version.GetVersion())

return notationregistry.NewRepositoryClient(authClient, ref, plainHTTP)
return authClient, plainHTTP, nil
}

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

return nativeStore.Get(serverAddress)
}
1 change: 0 additions & 1 deletion cmd/notation/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ var signCommand = &cli.Command{
},
flagUsername,
flagPassword,
flagPlainHTTP,
flagMediaType,
cmd.FlagPluginConfig,
},
Expand Down
1 change: 0 additions & 1 deletion cmd/notation/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ var verifyCommand = &cli.Command{
flagLocal,
flagUsername,
flagPassword,
flagPlainHTTP,
flagMediaType,
},
Action: runVerify,
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ go 1.18
require (
github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3
github.com/docker/cli v20.10.17+incompatible
github.com/docker/docker-credential-helpers v0.6.4
github.com/notaryproject/notation-core-go v0.0.0-20220712013708-3c4b3efa03c5
github.com/notaryproject/notation-go v0.9.0-alpha.1.0.20220712175603-962d79cd4090
github.com/opencontainers/go-digest v1.0.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/urfave/cli/v2 v2.11.0
oras.land/oras-go/v2 v2.0.0-20220620164807-8b2a54608a94
oras.land/oras-go/v2 v2.0.0-20220620164807-8b2a54608a94 // TODO: upgrade to v2.0.0-rc.1 in the next PR
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/docker/docker v20.10.8+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/oras-project/artifacts-spec v1.0.0-rc.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ github.com/notaryproject/notation-go v0.9.0-alpha.1.0.20220712175603-962d79cd409
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/oras-project/artifacts-spec v1.0.0-rc.1 h1:bCHf9mPbrgiNwQFyVzBX79BYZVAl0OUrmvICZOCOwts=
github.com/oras-project/artifacts-spec v1.0.0-rc.1/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
13 changes: 13 additions & 0 deletions pkg/auth/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package auth

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

// CredentialStore is the interface that any credentials store must implement.
type CredentialStore interface {
// Store saves credentials into the store
Store(serverAddress string, credsConf auth.Credential) error
// Erase removes credentials from the store for the given server
Erase(serverAddress string) error
// Get retrieves credentials from the store for the given server
Get(serverAddress string) (auth.Credential, error)
}
Loading

0 comments on commit bb5b497

Please sign in to comment.