mirror of https://github.com/docker/cli.git
commit
be8dab26a3
|
@ -7,13 +7,15 @@ import (
|
||||||
// DetectDefaultStore return the default credentials store for the platform if
|
// DetectDefaultStore return the default credentials store for the platform if
|
||||||
// the store executable is available.
|
// the store executable is available.
|
||||||
func DetectDefaultStore(store string) string {
|
func DetectDefaultStore(store string) string {
|
||||||
|
platformDefault := defaultCredentialsStore()
|
||||||
|
|
||||||
// user defined or no default for platform
|
// user defined or no default for platform
|
||||||
if store != "" || defaultCredentialsStore == "" {
|
if store != "" || platformDefault == "" {
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
|
if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err == nil {
|
||||||
return defaultCredentialsStore
|
return platformDefault
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
const defaultCredentialsStore = "osxkeychain"
|
func defaultCredentialsStore() string {
|
||||||
|
return "osxkeychain"
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
const defaultCredentialsStore = "secretservice"
|
import (
|
||||||
|
"github.com/docker/docker-credential-helpers/pass"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultCredentialsStore() string {
|
||||||
|
if pass.PassInitialized {
|
||||||
|
return "pass"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "secretservice"
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
const defaultCredentialsStore = "wincred"
|
func defaultCredentialsStore() string {
|
||||||
|
return "wincred"
|
||||||
|
}
|
||||||
|
|
|
@ -63,8 +63,9 @@ $ cat ~/my_password.txt | docker login --username foo --password-stdin
|
||||||
2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/security/security/#docker-daemon-attack-surface) for details.
|
2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/security/security/#docker-daemon-attack-surface) for details.
|
||||||
|
|
||||||
You can log into any public or private repository for which you have
|
You can log into any public or private repository for which you have
|
||||||
credentials. When you log in, the command stores encoded credentials in
|
credentials. When you log in, the command stores credentials in
|
||||||
`$HOME/.docker/config.json` on Linux or `%USERPROFILE%/.docker/config.json` on Windows.
|
`$HOME/.docker/config.json` on Linux or `%USERPROFILE%/.docker/config.json` on
|
||||||
|
Windows, via the procedure described below.
|
||||||
|
|
||||||
### Credentials store
|
### Credentials store
|
||||||
|
|
||||||
|
@ -82,6 +83,7 @@ you can download them from:
|
||||||
- D-Bus Secret Service: https://github.com/docker/docker-credential-helpers/releases
|
- D-Bus Secret Service: https://github.com/docker/docker-credential-helpers/releases
|
||||||
- Apple macOS keychain: https://github.com/docker/docker-credential-helpers/releases
|
- Apple macOS keychain: https://github.com/docker/docker-credential-helpers/releases
|
||||||
- Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases
|
- Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases
|
||||||
|
- [pass](https://www.passwordstore.org/): https://github.com/docker/docker-credential-helpers/releases
|
||||||
|
|
||||||
You need to specify the credentials store in `$HOME/.docker/config.json`
|
You need to specify the credentials store in `$HOME/.docker/config.json`
|
||||||
to tell the docker engine to use it. The value of the config property should be
|
to tell the docker engine to use it. The value of the config property should be
|
||||||
|
@ -97,6 +99,15 @@ For example, to use `docker-credential-osxkeychain`:
|
||||||
If you are currently logged in, run `docker logout` to remove
|
If you are currently logged in, run `docker logout` to remove
|
||||||
the credentials from the file and run `docker login` again.
|
the credentials from the file and run `docker login` again.
|
||||||
|
|
||||||
|
### Default behavior
|
||||||
|
|
||||||
|
By default, Docker looks for the native binary on each of the platforms, i.e.
|
||||||
|
"osxkeychain" on macOS, "wincred" on windows, and "pass" on Linux. A special
|
||||||
|
case is that on Linux, Docker will fall back to the "secretservice" binary if
|
||||||
|
it cannot find the "pass" binary. If none of these binaries are present, it
|
||||||
|
stores the credentials (i.e. password) in base64 encoding in the config files
|
||||||
|
described above.
|
||||||
|
|
||||||
### Credential helper protocol
|
### Credential helper protocol
|
||||||
|
|
||||||
Credential helpers can be any program or script that follows a very simple protocol.
|
Credential helpers can be any program or script that follows a very simple protocol.
|
||||||
|
|
|
@ -5,7 +5,7 @@ github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
||||||
github.com/docker/docker 84144a8c66c1bb2af8fa997288f51ef2719971b4
|
github.com/docker/docker 84144a8c66c1bb2af8fa997288f51ef2719971b4
|
||||||
github.com/docker/docker-credential-helpers v0.5.1
|
github.com/docker/docker-credential-helpers 3c90bd29a46b943b2a9842987b58fb91a7c1819b
|
||||||
|
|
||||||
# the docker/go package contains a customized version of canonical/json
|
# the docker/go package contains a customized version of canonical/json
|
||||||
# and is used by Notary. The package is periodically rebased on current Go versions.
|
# and is used by Notary. The package is periodically rebased on current Go versions.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -17,15 +18,26 @@ type ProgramFunc func(args ...string) Program
|
||||||
|
|
||||||
// NewShellProgramFunc creates programs that are executed in a Shell.
|
// NewShellProgramFunc creates programs that are executed in a Shell.
|
||||||
func NewShellProgramFunc(name string) ProgramFunc {
|
func NewShellProgramFunc(name string) ProgramFunc {
|
||||||
|
return NewShellProgramFuncWithEnv(name, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShellProgramFuncWithEnv creates programs that are executed in a Shell with environment variables
|
||||||
|
func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc {
|
||||||
return func(args ...string) Program {
|
return func(args ...string) Program {
|
||||||
return &Shell{cmd: newCmdRedirectErr(name, args)}
|
return &Shell{cmd: createProgramCmdRedirectErr(name, args, env)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCmdRedirectErr(name string, args []string) *exec.Cmd {
|
func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd {
|
||||||
newCmd := exec.Command(name, args...)
|
programCmd := exec.Command(commandName, args...)
|
||||||
newCmd.Stderr = os.Stderr
|
programCmd.Env = os.Environ()
|
||||||
return newCmd
|
if env != nil {
|
||||||
|
for k, v := range *env {
|
||||||
|
programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
programCmd.Stderr = os.Stderr
|
||||||
|
return programCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shell invokes shell commands to talk with a remote credentials helper.
|
// Shell invokes shell commands to talk with a remote credentials helper.
|
||||||
|
|
|
@ -33,11 +33,12 @@ func (c *Credentials) isValid() (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Docker credentials should be labeled as such in credentials stores that allow labelling.
|
// CredsLabel holds the way Docker credentials should be labeled as such in credentials stores that allow labelling.
|
||||||
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
|
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
|
||||||
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
|
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
|
||||||
var CredsLabel = "Docker Credentials"
|
var CredsLabel = "Docker Credentials"
|
||||||
|
|
||||||
|
// SetCredsLabel is a simple setter for CredsLabel
|
||||||
func SetCredsLabel(label string) {
|
func SetCredsLabel(label string) {
|
||||||
CredsLabel = label
|
CredsLabel = label
|
||||||
}
|
}
|
||||||
|
@ -50,7 +51,7 @@ func SetCredsLabel(label string) {
|
||||||
func Serve(helper Helper) {
|
func Serve(helper Helper) {
|
||||||
var err error
|
var err error
|
||||||
if len(os.Args) != 2 {
|
if len(os.Args) != 2 {
|
||||||
err = fmt.Errorf("Usage: %s <store|get|erase|list>", os.Args[0])
|
err = fmt.Errorf("Usage: %s <store|get|erase|list|version>", os.Args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -74,6 +75,8 @@ func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error
|
||||||
return Erase(helper, in)
|
return Erase(helper, in)
|
||||||
case "list":
|
case "list":
|
||||||
return List(helper, out)
|
return List(helper, out)
|
||||||
|
case "version":
|
||||||
|
return PrintVersion(out)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||||
}
|
}
|
||||||
|
@ -131,8 +134,8 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||||
|
|
||||||
resp := Credentials{
|
resp := Credentials{
|
||||||
ServerURL: serverURL,
|
ServerURL: serverURL,
|
||||||
Username: username,
|
Username: username,
|
||||||
Secret: secret,
|
Secret: secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
|
@ -175,3 +178,9 @@ func List(helper Helper, writer io.Writer) error {
|
||||||
}
|
}
|
||||||
return json.NewEncoder(writer).Encode(accts)
|
return json.NewEncoder(writer).Encode(accts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//PrintVersion outputs the current version.
|
||||||
|
func PrintVersion(writer io.Writer) error {
|
||||||
|
fmt.Fprintln(writer, Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ const (
|
||||||
// ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize
|
// ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize
|
||||||
// invalid credentials or credentials management operations
|
// invalid credentials or credentials management operations
|
||||||
errCredentialsMissingServerURLMessage = "no credentials server URL"
|
errCredentialsMissingServerURLMessage = "no credentials server URL"
|
||||||
errCredentialsMissingUsernameMessage = "no credentials username"
|
errCredentialsMissingUsernameMessage = "no credentials username"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errCredentialsNotFound represents an error
|
// errCredentialsNotFound represents an error
|
||||||
|
@ -43,7 +43,6 @@ func IsErrCredentialsNotFoundMessage(err string) bool {
|
||||||
return err == errCredentialsNotFoundMessage
|
return err == errCredentialsNotFoundMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// errCredentialsMissingServerURL represents an error raised
|
// errCredentialsMissingServerURL represents an error raised
|
||||||
// when the credentials object has no server URL or when no
|
// when the credentials object has no server URL or when no
|
||||||
// server URL is provided to a credentials operation requiring
|
// server URL is provided to a credentials operation requiring
|
||||||
|
@ -64,7 +63,6 @@ func (errCredentialsMissingUsername) Error() string {
|
||||||
return errCredentialsMissingUsernameMessage
|
return errCredentialsMissingUsernameMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// NewErrCredentialsMissingServerURL creates a new error for
|
// NewErrCredentialsMissingServerURL creates a new error for
|
||||||
// errCredentialsMissingServerURL.
|
// errCredentialsMissingServerURL.
|
||||||
func NewErrCredentialsMissingServerURL() error {
|
func NewErrCredentialsMissingServerURL() error {
|
||||||
|
@ -77,7 +75,6 @@ func NewErrCredentialsMissingUsername() error {
|
||||||
return errCredentialsMissingUsername{}
|
return errCredentialsMissingUsername{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// IsCredentialsMissingServerURL returns true if the error
|
// IsCredentialsMissingServerURL returns true if the error
|
||||||
// was an errCredentialsMissingServerURL.
|
// was an errCredentialsMissingServerURL.
|
||||||
func IsCredentialsMissingServerURL(err error) bool {
|
func IsCredentialsMissingServerURL(err error) bool {
|
||||||
|
|
4
vendor/github.com/docker/docker-credential-helpers/credentials/version.go
generated
vendored
Normal file
4
vendor/github.com/docker/docker-credential-helpers/credentials/version.go
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
// Version holds a string describing the current version
|
||||||
|
const Version = "0.5.2"
|
50
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.go
generated
vendored
50
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.go
generated
vendored
|
@ -135,30 +135,27 @@ func (h Osxkeychain) List() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitServer(serverURL string) (*C.struct_Server, error) {
|
func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||||
u, err := url.Parse(serverURL)
|
u, err := parseURL(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hostAndPort := strings.Split(u.Host, ":")
|
proto := C.kSecProtocolTypeHTTPS
|
||||||
host := hostAndPort[0]
|
if u.Scheme == "http" {
|
||||||
|
proto = C.kSecProtocolTypeHTTP
|
||||||
|
}
|
||||||
var port int
|
var port int
|
||||||
if len(hostAndPort) == 2 {
|
p := getPort(u)
|
||||||
p, err := strconv.Atoi(hostAndPort[1])
|
if p != "" {
|
||||||
|
port, err = strconv.Atoi(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
port = p
|
|
||||||
}
|
|
||||||
|
|
||||||
proto := C.kSecProtocolTypeHTTPS
|
|
||||||
if u.Scheme != "https" {
|
|
||||||
proto = C.kSecProtocolTypeHTTP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &C.struct_Server{
|
return &C.struct_Server{
|
||||||
proto: C.SecProtocolType(proto),
|
proto: C.SecProtocolType(proto),
|
||||||
host: C.CString(host),
|
host: C.CString(getHostname(u)),
|
||||||
port: C.uint(port),
|
port: C.uint(port),
|
||||||
path: C.CString(u.Path),
|
path: C.CString(u.Path),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -168,3 +165,32 @@ func freeServer(s *C.struct_Server) {
|
||||||
C.free(unsafe.Pointer(s.host))
|
C.free(unsafe.Pointer(s.host))
|
||||||
C.free(unsafe.Pointer(s.path))
|
C.free(unsafe.Pointer(s.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseURL parses and validates a given serverURL to an url.URL, and
|
||||||
|
// returns an error if validation failed. Querystring parameters are
|
||||||
|
// omitted in the resulting URL, because they are not used in the helper.
|
||||||
|
//
|
||||||
|
// If serverURL does not have a valid scheme, `//` is used as scheme
|
||||||
|
// before parsing. This prevents the hostname being used as path,
|
||||||
|
// and the credentials being stored without host.
|
||||||
|
func parseURL(serverURL string) (*url.URL, error) {
|
||||||
|
// Check if serverURL has a scheme, otherwise add `//` as scheme.
|
||||||
|
if !strings.Contains(serverURL, "://") && !strings.HasPrefix(serverURL, "//") {
|
||||||
|
serverURL = "//" + serverURL
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "" && u.Scheme != "https" && u.Scheme != "http" {
|
||||||
|
return nil, errors.New("unsupported scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
if getHostname(u) == "" {
|
||||||
|
return nil, errors.New("no hostname in URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = ""
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
13
vendor/github.com/docker/docker-credential-helpers/osxkeychain/url_go18.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker-credential-helpers/osxkeychain/url_go18.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//+build go1.8
|
||||||
|
|
||||||
|
package osxkeychain
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
func getHostname(u *url.URL) string {
|
||||||
|
return u.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPort(u *url.URL) string {
|
||||||
|
return u.Port()
|
||||||
|
}
|
41
vendor/github.com/docker/docker-credential-helpers/osxkeychain/url_non_go18.go
generated
vendored
Normal file
41
vendor/github.com/docker/docker-credential-helpers/osxkeychain/url_non_go18.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
//+build !go1.8
|
||||||
|
|
||||||
|
package osxkeychain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHostname(u *url.URL) string {
|
||||||
|
return stripPort(u.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPort(u *url.URL) string {
|
||||||
|
return portOnly(u.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripPort(hostport string) string {
|
||||||
|
colon := strings.IndexByte(hostport, ':')
|
||||||
|
if colon == -1 {
|
||||||
|
return hostport
|
||||||
|
}
|
||||||
|
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||||||
|
return strings.TrimPrefix(hostport[:i], "[")
|
||||||
|
}
|
||||||
|
return hostport[:colon]
|
||||||
|
}
|
||||||
|
|
||||||
|
func portOnly(hostport string) string {
|
||||||
|
colon := strings.IndexByte(hostport, ':')
|
||||||
|
if colon == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if i := strings.Index(hostport, "]:"); i != -1 {
|
||||||
|
return hostport[i+len("]:"):]
|
||||||
|
}
|
||||||
|
if strings.Contains(hostport, "]") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return hostport[colon+len(":"):]
|
||||||
|
}
|
208
vendor/github.com/docker/docker-credential-helpers/pass/pass_linux.go
generated
vendored
Normal file
208
vendor/github.com/docker/docker-credential-helpers/pass/pass_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
// A `pass` based credential helper. Passwords are stored as arguments to pass
|
||||||
|
// of the form: "$PASS_FOLDER/base64-url(serverURL)/username". We base64-url
|
||||||
|
// encode the serverURL, because under the hood pass uses files and folders, so
|
||||||
|
// /s will get translated into additional folders.
|
||||||
|
package pass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PASS_FOLDER = "docker-credential-helpers"
|
||||||
|
|
||||||
|
var (
|
||||||
|
PassInitialized bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
PassInitialized = exec.Command("pass").Run() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPass(stdinContent string, args ...string) (string, error) {
|
||||||
|
cmd := exec.Command("pass", args...)
|
||||||
|
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
if err != 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.
|
||||||
|
func (h Pass) Add(creds *credentials.Credentials) error {
|
||||||
|
if !PassInitialized {
|
||||||
|
return errors.New("pass store is uninitialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds == nil {
|
||||||
|
return errors.New("missing credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := base64.URLEncoding.EncodeToString([]byte(creds.ServerURL))
|
||||||
|
|
||||||
|
_, err := runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes credentials from the store.
|
||||||
|
func (h Pass) Delete(serverURL string) error {
|
||||||
|
if !PassInitialized {
|
||||||
|
return errors.New("pass store is uninitialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverURL == "" {
|
||||||
|
return errors.New("missing server url")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
|
||||||
|
_, err := runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// listPassDir lists all the contents of a directory in the password store.
|
||||||
|
// Pass uses fancy unicode to emit stuff to stdout, so rather than try
|
||||||
|
// and parse this, let's just look at the directory structure instead.
|
||||||
|
func listPassDir(args ...string) ([]os.FileInfo, error) {
|
||||||
|
passDir := os.ExpandEnv("$HOME/.password-store")
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
parts := strings.SplitN(e, "=", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[0] != "PASSWORD_STORE_DIR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
passDir = parts[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p := path.Join(append([]string{passDir, PASS_FOLDER}, args...)...)
|
||||||
|
contents, err := ioutil.ReadDir(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return []os.FileInfo{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the username and secret to use for a given registry server URL.
|
||||||
|
func (h Pass) Get(serverURL string) (string, string, error) {
|
||||||
|
if !PassInitialized {
|
||||||
|
return "", "", errors.New("pass store is uninitialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverURL == "" {
|
||||||
|
return "", "", errors.New("missing server url")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
|
||||||
|
|
||||||
|
usernames, err := listPassDir(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(usernames) < 1 {
|
||||||
|
return "", "", fmt.Errorf("no usernames for %s", serverURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSuffix(usernames[0].Name(), ".gpg")
|
||||||
|
secret, err := runPass("", "show", path.Join(PASS_FOLDER, encoded, actual))
|
||||||
|
return actual, secret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the stored URLs and corresponding usernames for a given credentials label
|
||||||
|
func (h Pass) List() (map[string]string, error) {
|
||||||
|
if !PassInitialized {
|
||||||
|
return nil, errors.New("pass store is uninitialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := listPassDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := map[string]string{}
|
||||||
|
|
||||||
|
for _, server := range servers {
|
||||||
|
if !server.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
serverURL, err := base64.URLEncoding.DecodeString(server.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usernames, err := listPassDir(server.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(usernames) < 1 {
|
||||||
|
return nil, fmt.Errorf("no usernames for %s", serverURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp[string(serverURL)] = strings.TrimSuffix(usernames[0].Name(), ".gpg")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
|
@ -105,8 +105,11 @@ func (h Secretservice) List() (map[string]string, error) {
|
||||||
if listLen == 0 {
|
if listLen == 0 {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
// The maximum capacity of the following two slices is limited to (2^29)-1 to remain compatible
|
||||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
// with 32-bit platforms. The size of a `*C.char` (a pointer) is 4 Byte on a 32-bit system
|
||||||
|
// and (2^29)*4 == math.MaxInt32 + 1. -- See issue golang/go#13656
|
||||||
|
pathTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||||
|
acctTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||||
for i := 0; i < listLen; i++ {
|
for i := 0; i < listLen; i++ {
|
||||||
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
|
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue