1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-28 07:11:36 +05:30

Compare commits

...

16 Commits

Author SHA1 Message Date
Nassim Eddequiouaq 5241b46610 Merge pull request #110 from euank/lazy-init
pass: only init on run, and do so lazily
2018-06-27 14:33:29 +02:00
Vincent Demeester 3cba3913ea pass: add IsInitialized helper
This will be useful for the cli where they check initialization:
https://github.com/docker/cli/blob/be8dab26a3ab589b96788fdb95f3d07378e57b9b/cli/config/credentials/default_store_linux.go#L8-L10

Signed-off-by: Euan Kemp <euank@euank.com>
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-06-27 14:28:47 +02:00
Vincent Demeester 8502b53592 Merge pull request #108 from euank/pass-trim‮‮‮‮‮‮‮‮trim-pass
pass: trim pass show output
2018-06-27 14:24:08 +02:00
Euan Kemp 5da09fd251 pass: only init on run, and do so lazily
This also fixes the following issues:

1. Safe for concurrent initialization still (it was before in 'init',
   but the alternative to this PR is not)
2. Uses the same password directory during init as it does during
   runtime (the change to getPassDir in initialization logic.
3. Prints significantly better errors if initialization fails
4. Has slightly cleaner abstractions by hiding the initialization check
   in 'runPass'

The 4th item there does mean there are a few cases where more work is
done before erroring, but that amount of work is trivial and my manual
audit didn't reveal anything that seemed worrying.

Fixes #96, alternative to #106

Signed-off-by: Euan Kemp <euank@euank.com>
2018-06-27 14:23:22 +02:00
Euan Kemp dd27c246bd pass: trim pass show output
As of 8446a40, pass show will include a newline when showing a password.
This causes the pass helper here to reliably fail to initialize since a
password doesn't round-trip.

Before making this change, the pass test would fail if the installed
password-store version was v1.7.1+, and after this change it passes
again.

Fixes #107

Signed-off-by: Euan Kemp <euank@euank.com>
2018-06-27 14:21:52 +02:00
Vincent Demeester 26deb2937d Merge pull request #109 from euank/better-exec
pass: simplify some code
2018-06-27 14:19:27 +02:00
Euan Kemp a13ff50017 pass: simplify some code
The exec.Command code and os.Getenv implementation were both needlessly
verbose. This replaces them with simpler variations.

Signed-off-by: Euan Kemp <euank@euank.com>
2018-06-27 14:15:36 +02:00
Vincent Demeester 1c295f7de8 Merge pull request #115 from n4ss/fix-appveyor
Fix Windows CI
2018-06-27 14:14:13 +02:00
Nassim 'Nass' Eddequiouaq 093af814ee fix go vet complaining on composite literales
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2018-06-27 14:02:28 +02:00
Vincent Demeester d499cf5cb9 Merge pull request #114 from n4ss/fix-travisci-osxkeychain
fix osxkeychain ci
2018-06-27 13:52:26 +02:00
Nassim 'Nass' Eddequiouaq b049338a6b Fix appveyor windows ci
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2018-06-27 13:45:32 +02:00
Nassim 'Nass' Eddequiouaq 91fc39d57a install yarn on osx for travisci
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2018-06-27 13:22:53 +02:00
Nassim 'Nass' Eddequiouaq 317219f3a6 Fix yarn complaint in travisci
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2018-06-27 12:13:18 +02:00
Nassim 'Nass' Eddequiouaq 21f4937ebc fix osxkeychain ci
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2018-06-27 11:58:22 +02:00
Vincent Demeester 19b711cc92 Merge pull request #100 from vdemeester/update-maintainers
Update MAINTAINERS file with current maintainers
2018-02-14 08:13:21 +01:00
Vincent Demeester 1f635a73ad Update MAINTAINERS file with current maintainers
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-02-12 14:39:43 +01:00
5 changed files with 73 additions and 128 deletions
+8 -2
View File
@@ -3,20 +3,26 @@
sudo: required sudo: required
language: go language: go
dist: trusty dist: trusty
osx_image: xcode9
os: os:
- linux - linux
- osx - osx
notifications: notifications:
email: false email: false
go: go:
- 1.8 - 1.8.1
install: make deps before_install:
# work-around for issue https://github.com/travis-ci/travis-ci/issues/6307
# might not be necessary in the future
- command curl -sSL https://rvm.io/mpapis.asc | gpg --import -
- rvm get stable
addons: addons:
apt: apt:
packages: packages:
- libsecret-1-dev - libsecret-1-dev
- pass - pass
before_script: before_script:
- make deps
- "export DISPLAY=:99.0" - "export DISPLAY=:99.0"
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh ci/before_script_linux.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh ci/before_script_linux.sh; fi
- make validate - make validate
-30
View File
@@ -11,18 +11,13 @@
[Org] [Org]
[Org."Core maintainers"] [Org."Core maintainers"]
people = [ people = [
"aaronlehmann",
"calavera",
"coolljt0725", "coolljt0725",
"cpuguy83", "cpuguy83",
"crosbymichael", "crosbymichael",
"dnephin", "dnephin",
"dongluochen",
"duglin", "duglin",
"estesp", "estesp",
"icecrime",
"jhowardmsft", "jhowardmsft",
"lk4d4",
"mavenugo", "mavenugo",
"mhbauer", "mhbauer",
"n4ss", "n4ss",
@@ -45,16 +40,6 @@
# ADD YOURSELF HERE IN ALPHABETICAL ORDER # ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.aaronlehmann]
Name = "Aaron Lehmann"
Email = "aaron.lehmann@docker.com"
GitHub = "aaronlehmann"
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"
[people.coolljt0725] [people.coolljt0725]
Name = "Lei Jitang" Name = "Lei Jitang"
Email = "leijitang@huawei.com" Email = "leijitang@huawei.com"
@@ -75,11 +60,6 @@
Email = "dnephin@gmail.com" Email = "dnephin@gmail.com"
GitHub = "dnephin" GitHub = "dnephin"
[people.dongluochen]
Name = "Dongluo Chen"
Email = "dongluo.chen@docker.com"
GitHub = "dongluochen"
[people.duglin] [people.duglin]
Name = "Doug Davis" Name = "Doug Davis"
Email = "dug@us.ibm.com" Email = "dug@us.ibm.com"
@@ -90,21 +70,11 @@
Email = "estesp@linux.vnet.ibm.com" Email = "estesp@linux.vnet.ibm.com"
GitHub = "estesp" GitHub = "estesp"
[people.icecrime]
Name = "Arnaud Porterie"
Email = "arnaud@docker.com"
GitHub = "icecrime"
[people.jhowardmsft] [people.jhowardmsft]
Name = "John Howard" Name = "John Howard"
Email = "jhoward@microsoft.com" Email = "jhoward@microsoft.com"
GitHub = "jhowardmsft" GitHub = "jhowardmsft"
[people.lk4d4]
Name = "Alexander Morozov"
Email = "lk4d4@docker.com"
GitHub = "lk4d4"
[people.mavenugo] [people.mavenugo]
Name = "Madhu Venugopal" Name = "Madhu Venugopal"
Email = "madhu@docker.com" Email = "madhu@docker.com"
+2 -2
View File
@@ -2,13 +2,13 @@ image: Visual Studio 2015
environment: environment:
GOPATH: c:\gopath GOPATH: c:\gopath
stack: go 1.8.7
clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers
clone_depth: 10 clone_depth: 10
before_build: before_build:
- set PATH=%PATH%;C:\MinGW\bin; - set PATH=%PATH%;C:\MinGW\bin;
- set PATH=%PATH%;C:\go18\bin;
- set GOROOT=C:\go18
build_script: build_script:
- mingw32-make vet_win wincred - mingw32-make vet_win wincred
+61 -93
View File
@@ -5,6 +5,7 @@
package pass package pass
import ( import (
"bytes"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
@@ -13,139 +14,114 @@ import (
"os/exec" "os/exec"
"path" "path"
"strings" "strings"
"sync"
"github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/credentials"
) )
const PASS_FOLDER = "docker-credential-helpers" const PASS_FOLDER = "docker-credential-helpers"
var ( // Pass handles secrets using Linux secret-service as a store.
PassInitialized bool type Pass struct{}
)
func init() { // Ideally these would be stored as members of Pass, but since all of Pass's
// methods have value receivers, not pointer receivers, and changing that is
// backwards incompatible, we assume that all Pass instances share the same configuration
// initializationMutex is held while initializing so that only one 'pass'
// round-tripping is done to check pass is functioning.
var initializationMutex sync.Mutex
var passInitialized bool
// CheckInitialized checks whether the password helper can be used. It
// internally caches and so may be safely called multiple times with no impact
// on performance, though the first call may take longer.
func (p Pass) CheckInitialized() bool {
return p.checkInitialized() == nil
}
func (p Pass) checkInitialized() error {
initializationMutex.Lock()
defer initializationMutex.Unlock()
if passInitialized {
return nil
}
// In principle, we could just run `pass init`. However, pass has a bug // In principle, we could just run `pass init`. However, pass has a bug
// where if gpg fails, it doesn't always exit 1. Additionally, pass // where if gpg fails, it doesn't always exit 1. Additionally, pass
// uses gpg2, but gpg is the default, which may be confusing. So let's // uses gpg2, but gpg is the default, which may be confusing. So let's
// just explictily check that pass actually can store and retreive a // just explictily check that pass actually can store and retreive a
// password. // password.
password := "pass is initialized" password := "pass is initialized"
name := path.Join(PASS_FOLDER, "docker-pass-initialized-check") name := path.Join(getPassDir(), "docker-pass-initialized-check")
_, err := runPass(password, "insert", "-f", "-m", name) _, err := p.runPassHelper(password, "insert", "-f", "-m", name)
if err != nil { if err != nil {
return return fmt.Errorf("error initializing pass: %v", err)
} }
stored, err := runPass("", "show", name) stored, err := p.runPassHelper("", "show", name)
PassInitialized = err == nil && stored == password if err != nil {
return fmt.Errorf("error fetching password during initialization: %v", err)
if PassInitialized {
runPass("", "rm", "-rf", name)
} }
if stored != password {
return fmt.Errorf("error round-tripping password during initialization: %q != %q", password, stored)
}
passInitialized = true
return nil
} }
func runPass(stdinContent string, args ...string) (string, error) { func (p Pass) runPass(stdinContent string, args ...string) (string, error) {
if err := p.checkInitialized(); err != nil {
return "", err
}
return p.runPassHelper(stdinContent, args...)
}
func (p Pass) runPassHelper(stdinContent string, args ...string) (string, error) {
var stdout, stderr bytes.Buffer
cmd := exec.Command("pass", args...) cmd := exec.Command("pass", args...)
cmd.Stdin = strings.NewReader(stdinContent)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
stdin, err := cmd.StdinPipe() err := cmd.Run()
if err != nil { if err != nil {
return "", err return "", fmt.Errorf("%s: %s", err, stderr.String())
}
defer stdin.Close()
stderr, err := cmd.StderrPipe()
if err != nil {
return "", err
}
defer stderr.Close()
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
defer stdout.Close()
err = cmd.Start()
if err != nil {
return "", err
} }
_, err = stdin.Write([]byte(stdinContent)) // trim newlines; pass v1.7.1+ includes a newline at the end of `show` output
if err != nil { return strings.TrimRight(stdout.String(), "\n\r"), nil
return "", err
}
stdin.Close()
errContent, err := ioutil.ReadAll(stderr)
if err != nil {
return "", fmt.Errorf("error reading stderr: %s", err)
}
result, err := ioutil.ReadAll(stdout)
if err != nil {
return "", fmt.Errorf("Error reading stdout: %s", err)
}
cmdErr := cmd.Wait()
if cmdErr != nil {
return "", fmt.Errorf("%s: %s", cmdErr, errContent)
}
return string(result), nil
} }
// Pass handles secrets using Linux secret-service as a store.
type Pass struct{}
// Add adds new credentials to the keychain. // Add adds new credentials to the keychain.
func (h Pass) Add(creds *credentials.Credentials) error { func (h Pass) Add(creds *credentials.Credentials) error {
if !PassInitialized {
return errors.New("pass store is uninitialized")
}
if creds == nil { if creds == nil {
return errors.New("missing credentials") return errors.New("missing credentials")
} }
encoded := base64.URLEncoding.EncodeToString([]byte(creds.ServerURL)) encoded := base64.URLEncoding.EncodeToString([]byte(creds.ServerURL))
_, err := runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username)) _, err := h.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username))
return err return err
} }
// Delete removes credentials from the store. // Delete removes credentials from the store.
func (h Pass) Delete(serverURL string) error { func (h Pass) Delete(serverURL string) error {
if !PassInitialized {
return errors.New("pass store is uninitialized")
}
if serverURL == "" { if serverURL == "" {
return errors.New("missing server url") return errors.New("missing server url")
} }
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL)) encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
_, err := runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded)) _, err := h.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded))
return err return err
} }
func getPassDir() string { func getPassDir() string {
passDir := os.ExpandEnv("$HOME/.password-store") passDir := "$HOME/.password-store"
for _, e := range os.Environ() { if envDir := os.Getenv("PASSWORD_STORE_DIR"); envDir != "" {
parts := strings.SplitN(e, "=", 2) passDir = envDir
if len(parts) < 2 {
continue
}
if parts[0] != "PASSWORD_STORE_DIR" {
continue
}
passDir = parts[1]
break
} }
return os.ExpandEnv(passDir)
return passDir
} }
// listPassDir lists all the contents of a directory in the password store. // listPassDir lists all the contents of a directory in the password store.
@@ -168,10 +144,6 @@ func listPassDir(args ...string) ([]os.FileInfo, error) {
// Get returns the username and secret to use for a given registry server URL. // Get returns the username and secret to use for a given registry server URL.
func (h Pass) Get(serverURL string) (string, string, error) { func (h Pass) Get(serverURL string) (string, string, error) {
if !PassInitialized {
return "", "", errors.New("pass store is uninitialized")
}
if serverURL == "" { if serverURL == "" {
return "", "", errors.New("missing server url") return "", "", errors.New("missing server url")
} }
@@ -180,7 +152,7 @@ func (h Pass) Get(serverURL string) (string, string, error) {
if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil { if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return "", "", nil; return "", "", nil
} }
return "", "", err return "", "", err
@@ -196,16 +168,12 @@ func (h Pass) Get(serverURL string) (string, string, error) {
} }
actual := strings.TrimSuffix(usernames[0].Name(), ".gpg") actual := strings.TrimSuffix(usernames[0].Name(), ".gpg")
secret, err := runPass("", "show", path.Join(PASS_FOLDER, encoded, actual)) secret, err := h.runPass("", "show", path.Join(PASS_FOLDER, encoded, actual))
return actual, secret, err return actual, secret, err
} }
// List returns the stored URLs and corresponding usernames for a given credentials label // List returns the stored URLs and corresponding usernames for a given credentials label
func (h Pass) List() (map[string]string, error) { func (h Pass) List() (map[string]string, error) {
if !PassInitialized {
return nil, errors.New("pass store is uninitialized")
}
servers, err := listPassDir() servers, err := listPassDir()
if err != nil { if err != nil {
return nil, err return nil, err
+2 -1
View File
@@ -13,11 +13,12 @@ type Wincred struct{}
// Add adds new credentials to the windows credentials manager. // Add adds new credentials to the windows credentials manager.
func (h Wincred) Add(creds *credentials.Credentials) error { func (h Wincred) Add(creds *credentials.Credentials) error {
credsLabels := []byte(credentials.CredsLabel)
g := winc.NewGenericCredential(creds.ServerURL) g := winc.NewGenericCredential(creds.ServerURL)
g.UserName = creds.Username g.UserName = creds.Username
g.CredentialBlob = []byte(creds.Secret) g.CredentialBlob = []byte(creds.Secret)
g.Persist = winc.PersistLocalMachine g.Persist = winc.PersistLocalMachine
g.Attributes = []winc.CredentialAttribute{{"label", []byte(credentials.CredsLabel)}} g.Attributes = []winc.CredentialAttribute{{Keyword: "label", Value: credsLabels}}
return g.Write() return g.Write()
} }