1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-13 16:01:28 +05:30

Merge pull request #110 from euank/lazy-init

pass: only init on run, and do so lazily
This commit is contained in:
Nassim Eddequiouaq
2018-06-27 14:33:29 +02:00
committed by GitHub
+48 -36
View File
@@ -14,42 +14,73 @@ 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(stdin 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 var stdout, stderr bytes.Buffer
cmd := exec.Command("pass", args...) cmd := exec.Command("pass", args...)
cmd.Stdin = strings.NewReader(stdin) cmd.Stdin = strings.NewReader(stdinContent)
cmd.Stdout = &stdout cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
@@ -62,37 +93,26 @@ func runPass(stdin string, args ...string) (string, error) {
return strings.TrimRight(stdout.String(), "\n\r"), nil return strings.TrimRight(stdout.String(), "\n\r"), 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
} }
@@ -124,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")
} }
@@ -152,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