Skip to content

Commit

Permalink
Add an auth command to login and logout to OCI registries (#47)
Browse files Browse the repository at this point in the history
Add `dt auth login` and `dt auth logout` to login and logout from OCI registries
  • Loading branch information
alemorcuq authored Feb 2, 2024
1 parent d93f43b commit ed2ba08
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 43 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,22 @@ $ helm dt charts carvelize examples/postgresql
🎉 Carvel bundle created successfully
```

### Login and logout from OCI registries (EXPERIMENTAL)

It is also possible to login and logout from OCI registries using the `dt` command. For example:

```console
$ helm dt auth login 127.0.0.1:5000 -u testuser -p testpassword
✔ log in to 127.0.0.1:5000 as user testuser
🎉 logged in via /Users/home/.docker/config.json
```

```console
$ helm dt auth logout 127.0.0.1:5000
✔ logout from 127.0.0.1:5000
🎉 logged out via /Users/home/.docker/config.json
```

## Frequently Asked Questions

### I cannot install the plugin due to `Error: Unable to update repository: exit status 1`
Expand Down
21 changes: 21 additions & 0 deletions cmd/dt/auth.go
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))
}
38 changes: 38 additions & 0 deletions cmd/dt/auth_test.go
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")
})
}
98 changes: 98 additions & 0 deletions cmd/dt/login/login.go
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
}
62 changes: 62 additions & 0 deletions cmd/dt/logout/logout.go
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
}
1 change: 1 addition & 0 deletions cmd/dt/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func newRootCmd() *cobra.Command {
// Do not show completion command
cmd.CompletionOptions.DisableDefaultCmd = true

cmd.AddCommand(authCmd)
cmd.AddCommand(chartCmd)
cmd.AddCommand(imagesCmd)
cmd.AddCommand(versionCmd)
Expand Down
45 changes: 31 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/vmware-labs/yaml-jsonpath v0.3.2
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.13.1
helm.sh/helm/v3 v3.14.0
oras.land/oras-go/v2 v2.3.1
)

Expand Down Expand Up @@ -44,6 +44,7 @@ require (
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
Expand Down Expand Up @@ -73,6 +74,10 @@ require (
github.com/aws/smithy-go v1.15.0 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/buildkite/agent/v3 v3.58.0 // indirect
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
Expand All @@ -87,7 +92,10 @@ require (
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
github.com/digitorus/timestamp v0.0.0-20230902153158-687734543647 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.5.0 // indirect
github.com/emicklei/proto v1.12.1 // indirect
Expand All @@ -112,15 +120,19 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/certificate-transparency-go v1.1.7 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gowebpki/jcs v1.0.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect
Expand All @@ -139,6 +151,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/oleiade/reflections v1.0.1 // indirect
Expand All @@ -147,6 +160,7 @@ require (
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
Expand Down Expand Up @@ -178,6 +192,9 @@ require (
github.com/transparency-dev/merkle v0.0.2 // indirect
github.com/xanzy/go-gitlab v0.93.2 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
github.com/zeebo/errs v1.3.0 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
Expand Down Expand Up @@ -224,7 +241,7 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v24.0.7+incompatible // indirect
github.com/docker/cli v24.0.7+incompatible
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
Expand All @@ -233,7 +250,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
Expand Down Expand Up @@ -298,7 +315,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/pflag v1.0.5 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/vmware-tanzu/carvel-imgpkg v0.37.3
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
Expand All @@ -322,20 +339,20 @@ require (
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.28.3 // indirect
k8s.io/apiextensions-apiserver v0.28.2 // indirect
k8s.io/apimachinery v0.28.3 // indirect
k8s.io/apiserver v0.28.2 // indirect
k8s.io/cli-runtime v0.28.2 // indirect
k8s.io/client-go v0.28.3 // indirect
k8s.io/component-base v0.28.2 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/api v0.29.0 // indirect
k8s.io/apiextensions-apiserver v0.29.0 // indirect
k8s.io/apimachinery v0.29.0 // indirect
k8s.io/apiserver v0.29.0 // indirect
k8s.io/cli-runtime v0.29.0 // indirect
k8s.io/client-go v0.29.0 // indirect
k8s.io/component-base v0.29.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/kubectl v0.28.2 // indirect
k8s.io/kubectl v0.29.0 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
Loading

0 comments on commit ed2ba08

Please sign in to comment.