-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an auth command to login and logout to OCI registries (#47)
Add `dt auth login` and `dt auth logout` to login and logout from OCI registries
- Loading branch information
Showing
8 changed files
with
304 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
"github.com/vmware-labs/distribution-tooling-for-helm/cmd/dt/login" | ||
"github.com/vmware-labs/distribution-tooling-for-helm/cmd/dt/logout" | ||
) | ||
|
||
var authCmd = &cobra.Command{ | ||
Use: "auth", | ||
Short: "Authentication commands", | ||
SilenceUsage: true, | ||
SilenceErrors: true, | ||
Run: func(cmd *cobra.Command, _ []string) { | ||
_ = cmd.Help() | ||
}, | ||
} | ||
|
||
func init() { | ||
authCmd.AddCommand(login.NewCmd(mainConfig), logout.NewCmd(mainConfig)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-containerregistry/pkg/crane" | ||
"github.com/stretchr/testify/require" | ||
"helm.sh/helm/v3/pkg/repo/repotest" | ||
) | ||
|
||
func TestLoginLogout(t *testing.T) { | ||
srv, err := repotest.NewTempServerWithCleanup(t, "") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer srv.Stop() | ||
|
||
ociSrv, err := repotest.NewOCIServer(t, srv.Root()) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
go ociSrv.ListenAndServe() | ||
|
||
t.Run("can't get catalog without login", func(t *testing.T) { | ||
_, err := crane.Catalog(ociSrv.RegistryURL) | ||
require.ErrorContains(t, err, "UNAUTHORIZED") | ||
}) | ||
|
||
t.Run("can get catalog after login", func(t *testing.T) { | ||
dt("auth", "login", ociSrv.RegistryURL, "-u", "username", "-p", "password").AssertSuccessMatch(t, "logged in via") | ||
_, err := crane.Catalog(ociSrv.RegistryURL) | ||
require.NoError(t, err) | ||
|
||
dt("auth", "logout", ociSrv.RegistryURL).AssertSuccessMatch(t, "logged out via") | ||
_, err = crane.Catalog(ociSrv.RegistryURL) | ||
require.ErrorContains(t, err, "UNAUTHORIZED") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Package login implements the command to login to OCI registries | ||
package login | ||
|
||
import ( | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
dockercfg "github.com/docker/cli/cli/config" | ||
"github.com/docker/cli/cli/config/types" | ||
"github.com/google/go-containerregistry/pkg/authn" | ||
"github.com/google/go-containerregistry/pkg/name" | ||
"github.com/spf13/cobra" | ||
"github.com/vmware-labs/distribution-tooling-for-helm/cmd/dt/config" | ||
"github.com/vmware-labs/distribution-tooling-for-helm/pkg/log" | ||
) | ||
|
||
type loginOptions struct { | ||
serverAddress string | ||
user string | ||
password string | ||
passwordStdin bool | ||
} | ||
|
||
// NewCmd returns a new dt login command | ||
func NewCmd(cfg *config.Config) *cobra.Command { | ||
var opts loginOptions | ||
|
||
cmd := &cobra.Command{ | ||
Use: "login REGISTRY", | ||
Short: "Log in to an OCI registry (Experimental)", | ||
Long: "Experimental. Log in to an OCI registry using the Docker configuration file", | ||
Example: ` # Log in to index.docker.io | ||
$ dt auth login index.docker.io -u my_username -p my_password | ||
# Log in to index.docker.io with a password from stdin | ||
$ dt auth login index.docker.io -u my_username --password-stdin < <(echo my_password)`, | ||
Args: cobra.ExactArgs(1), | ||
SilenceUsage: true, | ||
SilenceErrors: true, | ||
RunE: func(_ *cobra.Command, args []string) error { | ||
l := cfg.Logger() | ||
|
||
reg, err := name.NewRegistry(args[0]) | ||
if err != nil { | ||
return l.Failf("failed to load registry %s: %v", args[0], err) | ||
} | ||
opts.serverAddress = reg.Name() | ||
|
||
return login(opts, l) | ||
}, | ||
} | ||
|
||
flags := cmd.Flags() | ||
|
||
flags.StringVarP(&opts.user, "username", "u", "", "Username") | ||
flags.StringVarP(&opts.password, "password", "p", "", "Password") | ||
flags.BoolVarP(&opts.passwordStdin, "password-stdin", "", false, "Take the password from stdin") | ||
|
||
return cmd | ||
} | ||
|
||
// from https://github.com/google/go-containerregistry/blob/main/cmd/crane/cmd/auth.go | ||
func login(opts loginOptions, l log.SectionLogger) error { | ||
if opts.passwordStdin { | ||
contents, err := io.ReadAll(os.Stdin) | ||
if err != nil { | ||
return l.Failf("failed to read from stdin: %v", err) | ||
} | ||
|
||
opts.password = strings.TrimRight(string(contents), "\r\n") | ||
} | ||
if opts.user == "" && opts.password == "" { | ||
return l.Failf("username and password required") | ||
} | ||
l.Infof("log in to %s as user %s", opts.serverAddress, opts.user) | ||
cf, err := dockercfg.Load(os.Getenv("DOCKER_CONFIG")) | ||
if err != nil { | ||
return l.Failf("failed to load configuration: %v", err) | ||
} | ||
creds := cf.GetCredentialsStore(opts.serverAddress) | ||
if opts.serverAddress == name.DefaultRegistry { | ||
opts.serverAddress = authn.DefaultAuthKey | ||
} | ||
if err := creds.Store(types.AuthConfig{ | ||
ServerAddress: opts.serverAddress, | ||
Username: opts.user, | ||
Password: opts.password, | ||
}); err != nil { | ||
return l.Failf("failed to store credentials: %v", err) | ||
} | ||
|
||
if err := cf.Save(); err != nil { | ||
return l.Failf("failed to save authorization information: %v", err) | ||
} | ||
l.Successf("logged in via %s", cf.Filename) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Package logout implements the command to logout from OCI registries | ||
package logout | ||
|
||
import ( | ||
"os" | ||
|
||
dockercfg "github.com/docker/cli/cli/config" | ||
"github.com/google/go-containerregistry/pkg/authn" | ||
"github.com/google/go-containerregistry/pkg/name" | ||
"github.com/spf13/cobra" | ||
"github.com/vmware-labs/distribution-tooling-for-helm/cmd/dt/config" | ||
"github.com/vmware-labs/distribution-tooling-for-helm/pkg/log" | ||
) | ||
|
||
// NewCmd returns a new dt logout command | ||
func NewCmd(cfg *config.Config) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "logout REGISTRY", | ||
Short: "Logout from an OCI registry (Experimental)", | ||
Long: "Experimental. Logout from an OCI registry using the Docker configuration file", | ||
Args: cobra.ExactArgs(1), | ||
Example: ` # Log out from index.docker.io | ||
$ dt auth logout index.docker.io`, | ||
SilenceUsage: true, | ||
SilenceErrors: true, | ||
RunE: func(_ *cobra.Command, args []string) error { | ||
l := cfg.Logger() | ||
|
||
reg, err := name.NewRegistry(args[0]) | ||
if err != nil { | ||
return l.Failf("failed to load registry %s: %v", args[0], err) | ||
} | ||
serverAddress := reg.Name() | ||
|
||
return logout(serverAddress, l) | ||
}, | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
// from https://github.com/google/go-containerregistry/blob/main/cmd/crane/cmd/auth.go | ||
func logout(serverAddress string, l log.SectionLogger) error { | ||
l.Infof("logout from %s", serverAddress) | ||
cf, err := dockercfg.Load(os.Getenv("DOCKER_CONFIG")) | ||
if err != nil { | ||
return l.Failf("failed to load configuration: %v", err) | ||
} | ||
creds := cf.GetCredentialsStore(serverAddress) | ||
if serverAddress == name.DefaultRegistry { | ||
serverAddress = authn.DefaultAuthKey | ||
} | ||
if err := creds.Erase(serverAddress); err != nil { | ||
return l.Failf("failed to store credentials: %v", err) | ||
} | ||
|
||
if err := cf.Save(); err != nil { | ||
return l.Failf("failed to save authorization information: %v", err) | ||
} | ||
l.Successf("logged out via %s", cf.Filename) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.