Skip to content
This repository has been archived by the owner on Apr 6, 2022. It is now read-only.

Commit

Permalink
Merge pull request #2 from MarcosCela/store-creds-in-keyring
Browse files Browse the repository at this point in the history
Store creds in keyring
  • Loading branch information
Marcos Cela authored Feb 20, 2019
2 parents 5f5569b + 0fe9464 commit 949d8e9
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 31 deletions.
1 change: 0 additions & 1 deletion command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ func ShowCurrentConfiguration(c *cli.Context) error {
if len(currentConfiguration.Contexts) == 0 {
return cli.NewExitError("Invalid configuration file. You need at least one context!", errcodes.InvalidContext)
}
currentConfiguration.CensorPasswords()
config.GetJSONPrinter(c).Print(currentConfiguration)

return nil
Expand Down
71 changes: 71 additions & 0 deletions command/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package command

import (
"dr/config"
"dr/errcodes"
"fmt"
"github.com/urfave/cli"
"github.com/zalando/go-keyring"
"golang.org/x/crypto/ssh/terminal"
"syscall"
)

// Login ensures that a password is saved for a given context in the OS keyring
func Login(c *cli.Context) error {
if !c.Args().Present() {
return cli.NewExitError("You must provide a context name to login", errcodes.InsufficientArgs)
}
var contextName = c.Args().First()
if !contextExists(contextName, config.GetCurrentConfiguration(c).Contexts) {
return cli.NewExitError("The context you are trying to login-to does not exist", errcodes.InvalidContext)
}
user, err := getUserForContext(contextName, config.GetCurrentConfiguration(c))
if err != nil {
return err
}
fmt.Println("Enter a password:")
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return cli.NewExitError("There was an error getting the password from the terminal: "+err.Error(), errcodes.TerminalReadError)
}

err = keyring.Set(contextName, user, string(bytePassword))
if err != nil {
return cli.NewExitError("There was an error saving the password to the keyring: "+err.Error(), errcodes.KeyringError)
}
fmt.Println("Correctly set the password for context: <" + contextName + "> and user: <" + user + ">")
return nil
}

func getUserForContext(contextName string, configuration config.DrConfig) (string, error) {
for _, context := range configuration.Contexts {
if context.Name == contextName {
if context.User == "" {
return "", cli.NewExitError("The username for the context: "+contextName+" is empty!", errcodes.InvalidContext)
}
return context.User, nil
}
}
return "", cli.NewExitError("Could not recover a valid username for the context: <"+contextName+">", errcodes.InvalidContext)
}

func LoginComplete(c *cli.Context) {
currentConfiguration := config.GetCurrentConfiguration(c)

if len(currentConfiguration.Contexts) == 0 {
return
}

contextNames := getContextNames(currentConfiguration)
for _, contextName := range contextNames {
fmt.Println(contextName)
}
}

func getContextNames(cfg config.DrConfig) []string {
contextNames := make([]string, len(cfg.Contexts))
for i, context := range cfg.Contexts {
contextNames[i] = context.Name
}
return contextNames
}
7 changes: 7 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ var Commands = []cli.Command{
Action: command.ManifestInfo,
BashComplete: command.ManifestInfoAuto,
},
{
Name: "login",
Usage: "Saves the pasword for a given context in the OS keyring",
UsageText: "dr login <contextName>",
Action: command.Login,
BashComplete: command.LoginComplete,
},
}

// CommandNotFound default handler for when a command is not found, with custom exit error
Expand Down
31 changes: 15 additions & 16 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"dr/errcodes"
"dr/printer"
"errors"
"fmt"
"github.com/appscode/docker-registry-client/registry"
"github.com/urfave/cli"
"github.com/zalando/go-keyring"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
Expand All @@ -32,7 +34,6 @@ type DrContext struct {
Name string `yaml:"name" json:"name"`
URL string `yaml:"url" json:"url"`
User string `yaml:"user" json:"user"`
Pass string `yaml:"pass" json:"pass"`
Trusted bool `yaml:"trusted" json:"trusted"`
}

Expand Down Expand Up @@ -68,19 +69,6 @@ var OutputFormatYAML = OutputFormat{
}
var validOutputFormats = []OutputFormat{OutputFormatJSON, OutputFormatPLAIN, OutputFormatYAML}

// CensorPasswords censor all sensitive information from the given configuration, such as passwords and other fields
func (cfg *DrConfig) CensorPasswords() {
var censoredContexts []DrContext
for _, ctx := range cfg.Contexts {
censoredContexts = append(censoredContexts, *ctx.censorPassword())
}
cfg.Contexts = censoredContexts
}
func (ctx *DrContext) censorPassword() *DrContext {
ctx.Pass = "---redacted---"
return ctx
}

// GetClient generates a docker registry v2 client, with data from the current context configured by the user
// passed as metadata
func GetClient(c *cli.Context) *registry.Registry {
Expand All @@ -90,13 +78,24 @@ func GetClient(c *cli.Context) *registry.Registry {
}

currentContext := getCurrentContext(context)
hub, err := registry.New(currentContext.URL, currentContext.User, currentContext.Pass)
password := getPasswordForContext(currentContext)
hub, err := registry.New(currentContext.URL, currentContext.User, password)

if err != nil {
log.Fatal(err)
_, _ = fmt.Fprintln(os.Stderr, err.Error())
}
return hub
}

func getPasswordForContext(context DrContext) string {
// get password
secret, err := keyring.Get(context.Name, context.User)
if err != nil {
log.Fatal(err)
}
return secret
}

// getCurrentContext extracts the current context for the user configuration from the CLI context (params etc...)
func getCurrentContext(context DrConfig) DrContext {
var currentContext DrContext
Expand Down
4 changes: 0 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ func TestConfigurationLoadingFromFile(t *testing.T) {
g.Assert(config.Contexts[0].User).Equal("")

})
g.It("Default context has empty password", func() {
g.Assert(config.Contexts[0].Pass).Equal("")

})

})
g.Describe("Check YAML parsing of path that does not exist", func() {
Expand Down
1 change: 0 additions & 1 deletion config/resources/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ contexts:
- name: default
url: http://localhost:5000
user: ""
pass: ""
trusted: true
31 changes: 26 additions & 5 deletions doc/configuration-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ An example configuration file is as follows:
contexts:
- name: default
user: myuser
pass: mypassword
url: http://my.domain.com:1234/repo
trusted: true
currentContext: default
Expand All @@ -47,27 +46,49 @@ An example of a configuration file with multiple contexts should be as follows:
contexts:
- name: default
user: myuser
pass: mypassword
url: http://my.domain.com:1234/repo
trusted: true
- name: production
user: admin
pass: my-super-s3cure-p@ss
url: https://my-secure.domain:1337/prod
currentContext: production
```
# Password-protected repository
DR leverages the power of the OS keyring to store credentials in a safe manner.
To correctly login into any repository, simply setup the configuration file with your private repository:
```yaml
currentContext: passprotected
contexts:
- name: passprotected
url: https://repo.my-company.org:5000
user: ""
```
And then "login":
```bazaar
dr login passprotected

# Takes the form
# dr login <name of context to login>

```

You will be asked a password, that will be then stored in the OS keyring. You can update this password with "login" again.

# Passwordless repository

If your repository does not require authentication of any kind,
simply set the user and password fields to an emtpy string:
simply set the user field to an empty string:

```yaml
currentContext: default
contexts:
- name: default
url: http://localhost:5000
user: ""
pass: ""
trusted: true
```
6 changes: 6 additions & 0 deletions errcodes/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ const WriteConfigError int = 10
// InvalidOutputFormat is used when the user selects an output format that is not valid
const InvalidOutputFormat int = 11

// TerminalReadError is used when the user needs to input some data from the terminal and any kind of I/O related error happens
const TerminalReadError int = 12

// KeyringError signals any kind of error that happened when saving a password to the terminal
const KeyringError int = 13

// pre-built errors

var TemplateDisplayError = cli.NewExitError("There was an unexpected error when showing output to terminal", templateDisplayError)
Expand Down
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module dr

require (
github.com/Sirupsen/logrus v1.0.6 // indirect
github.com/appscode/docker-registry-client v0.0.0-20180426080142-1bb02bb202b0
github.com/danieljoos/wincred v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v0.0.0-20180814190750-9bf62ca7b3fc
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/franela/goblin v0.0.0-20180624194345-6af5ecb776ce
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/godbus/dbus v4.1.0+incompatible // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect
Expand All @@ -20,13 +21,14 @@ require (
github.com/onsi/ginkgo v1.6.0 // indirect
github.com/onsi/gomega v1.4.1 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.0.6 // indirect
github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.2.2 // indirect
github.com/urfave/cli v1.20.0
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect
github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07
golang.org/x/crypto v0.0.0-20180830192347-182538f80094
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339 // indirect
Expand Down

0 comments on commit 949d8e9

Please sign in to comment.