summaryrefslogtreecommitdiffstats
path: root/cmd/crane/cmd/auth.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/crane/cmd/auth.go')
-rw-r--r--cmd/crane/cmd/auth.go205
1 files changed, 205 insertions, 0 deletions
diff --git a/cmd/crane/cmd/auth.go b/cmd/crane/cmd/auth.go
new file mode 100644
index 0000000..1964ade
--- /dev/null
+++ b/cmd/crane/cmd/auth.go
@@ -0,0 +1,205 @@
+// Copyright 2020 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "strings"
+
+ "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/crane"
+ "github.com/google/go-containerregistry/pkg/name"
+ "github.com/spf13/cobra"
+)
+
+// NewCmdAuth creates a new cobra.Command for the auth subcommand.
+func NewCmdAuth(options []crane.Option, argv ...string) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "auth",
+ Short: "Log in or access credentials",
+ Args: cobra.NoArgs,
+ RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Usage() },
+ }
+ cmd.AddCommand(NewCmdAuthGet(options, argv...), NewCmdAuthLogin(argv...))
+ return cmd
+}
+
+type credentials struct {
+ Username string
+ Secret string
+}
+
+// https://github.com/docker/cli/blob/2291f610ae73533e6e0749d4ef1e360149b1e46b/cli/config/credentials/native_store.go#L100-L109
+func toCreds(config *authn.AuthConfig) credentials {
+ creds := credentials{
+ Username: config.Username,
+ Secret: config.Password,
+ }
+
+ if config.IdentityToken != "" {
+ creds.Username = "<token>"
+ creds.Secret = config.IdentityToken
+ }
+ return creds
+}
+
+// NewCmdAuthGet creates a new `crane auth get` command.
+func NewCmdAuthGet(options []crane.Option, argv ...string) *cobra.Command {
+ if len(argv) == 0 {
+ argv = []string{os.Args[0]}
+ }
+
+ baseCmd := strings.Join(argv, " ")
+ eg := fmt.Sprintf(` # Read configured credentials for reg.example.com
+ $ echo "reg.example.com" | %s get
+ {"username":"AzureDiamond","password":"hunter2"}
+ # or
+ $ %s get reg.example.com
+ {"username":"AzureDiamond","password":"hunter2"}`, baseCmd, baseCmd)
+
+ return &cobra.Command{
+ Use: "get [REGISTRY_ADDR]",
+ Short: "Implements a credential helper",
+ Example: eg,
+ Args: cobra.MaximumNArgs(1),
+ RunE: func(_ *cobra.Command, args []string) error {
+ registryAddr := ""
+ if len(args) == 1 {
+ registryAddr = args[0]
+ } else {
+ b, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ return err
+ }
+ registryAddr = strings.TrimSpace(string(b))
+ }
+
+ reg, err := name.NewRegistry(registryAddr)
+ if err != nil {
+ return err
+ }
+ authorizer, err := crane.GetOptions(options...).Keychain.Resolve(reg)
+ if err != nil {
+ return err
+ }
+
+ // If we don't find any credentials, there's a magic error to return:
+ //
+ // https://github.com/docker/docker-credential-helpers/blob/f78081d1f7fef6ad74ad6b79368de6348386e591/credentials/error.go#L4-L6
+ // https://github.com/docker/docker-credential-helpers/blob/f78081d1f7fef6ad74ad6b79368de6348386e591/credentials/credentials.go#L61-L63
+ if authorizer == authn.Anonymous {
+ fmt.Fprint(os.Stdout, "credentials not found in native keychain\n")
+ os.Exit(1)
+ }
+
+ auth, err := authorizer.Authorization()
+ if err != nil {
+ return err
+ }
+
+ // Convert back to a form that credential helpers can parse so that this
+ // can act as a meta credential helper.
+ creds := toCreds(auth)
+ return json.NewEncoder(os.Stdout).Encode(creds)
+ },
+ }
+}
+
+// NewCmdAuthLogin creates a new `crane auth login` command.
+func NewCmdAuthLogin(argv ...string) *cobra.Command {
+ var opts loginOptions
+
+ if len(argv) == 0 {
+ argv = []string{os.Args[0]}
+ }
+
+ eg := fmt.Sprintf(` # Log in to reg.example.com
+ %s login reg.example.com -u AzureDiamond -p hunter2`, strings.Join(argv, " "))
+
+ cmd := &cobra.Command{
+ Use: "login [OPTIONS] [SERVER]",
+ Short: "Log in to a registry",
+ Example: eg,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ reg, err := name.NewRegistry(args[0])
+ if err != nil {
+ return err
+ }
+
+ opts.serverAddress = reg.Name()
+
+ return login(opts)
+ },
+ }
+
+ 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
+}
+
+type loginOptions struct {
+ serverAddress string
+ user string
+ password string
+ passwordStdin bool
+}
+
+func login(opts loginOptions) error {
+ if opts.passwordStdin {
+ contents, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ return err
+ }
+
+ opts.password = strings.TrimSuffix(string(contents), "\n")
+ opts.password = strings.TrimSuffix(opts.password, "\r")
+ }
+ if opts.user == "" && opts.password == "" {
+ return errors.New("username and password required")
+ }
+ cf, err := config.Load(os.Getenv("DOCKER_CONFIG"))
+ if err != nil {
+ return 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 err
+ }
+
+ if err := cf.Save(); err != nil {
+ return err
+ }
+ log.Printf("logged in via %s", cf.Filename)
+ return nil
+}