Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] support notation login #218

Merged
merged 5 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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