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

Compare commits

..

39 Commits

Author SHA1 Message Date
Jean-Laurent de Morlhon d68f9aeca3 Merge pull request #88 from n4ss/add-pass-config-notice
Add instructions for pass helper configuration
2017-08-30 14:17:07 +02:00
Vincent Demeester 05a9d4c50d Merge pull request #89 from tych0/better-initialization-check
pass: better initialization check
2017-08-29 21:46:31 +02:00
Tycho Andersen c2eec534ee pass: better initialization check
See comment for details, but basically, let's actually use pass to check
that it works.

Signed-off-by: Tycho Andersen <tycho@docker.com>
2017-08-29 08:25:08 -07:00
Nassim 'Nass' Eddequiouaq 5be80ca212 Add instructions for pass helper configuration
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2017-08-28 18:11:27 +02:00
Vincent Demeester f00de1b72f Merge pull request #87 from n4ss/bump-0.6.0
Bump 0.6.0
2017-08-28 14:18:12 +02:00
Nassim 'Nass' Eddequiouaq 72f0375e37 Bump 0.6.0
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2017-08-25 19:37:31 +02:00
Nassim Eddequiouaq 3cce61446f Merge pull request #85 from promiseofcake/ljk-fix-makefile
Fix Makefile issues
2017-08-25 18:50:05 +02:00
Lucas Kacher ec0d036273 Fix Makefile issues
* mkdir fails when `bin` exists
* obtaining golint fails if previously installed (w/out the -u flag)

Signed-off-by: Lucas Kacher <lucas@vsco.co>
2017-08-23 11:08:54 -04:00
Vincent Demeester d3d9934897 Merge pull request #84 from tych0/fix-helpers-protocol
return "" instead of an error when pass isn't present
2017-08-22 20:10:46 +02:00
Tycho Andersen f212ea17df fix test
Signed-off-by: Tycho Andersen <tycho@docker.com>
2017-08-17 10:36:48 -06:00
Tycho Andersen 09e536a128 return "" instead of an error when pass isn't present
It turns out the cred helpers protocol is to return "" and not an error
when a credential isn't present, so let's do that.

Signed-off-by: Tycho Andersen <tycho@docker.com>
2017-08-17 09:19:19 -06:00
Vincent Demeester 3c90bd29a4 Merge pull request #83 from tych0/expose-pass-initialized
pass backend: expose pass initialized flag
2017-08-16 11:06:21 +02:00
Tycho Andersen b8fb9690c8 pass backend: expose pass initialized flag
This way clients don't have to run their own checks, they can just use the
library's detection.

Signed-off-by: Tycho Andersen <tycho@docker.com>
2017-08-14 08:56:59 -06:00
Vincent Demeester 7efaffb4c4 Merge pull request #81 from tych0/pass-backend-and-package
Add `pass` backend and debian packaging for both -secretservice and -pass backends
2017-08-14 16:00:25 +02:00
Nassim Eddequiouaq 6338c06ba4 Merge pull request #69 from shhsu/command_env
A new entry point for calling the cred helpers that allow passing environment variables
2017-08-14 11:15:13 +02:00
Tycho Andersen 86c94d3e30 add a deb package for pass/secret service backends
Note that this single source package produces two binary packages: one for
-pass, and one for -secretservice, so that users can install whichever
password backend (and thus deps) that they want.

Signed-off-by: Tycho Andersen <tycho@docker.com>
2017-08-11 08:15:29 -06:00
Tycho Andersen 1ab1037707 add a pass credential helper backend
Signed-off-by: Tycho Andersen <tycho@docker.com>
2017-08-11 08:15:29 -06:00
Peter Hsu c69c0725bb A new entry point for calling the cred helpers that allow passing environment variables
Signed-off-by: Peter Hsu <shhsu@microsoft.com>
2017-06-30 10:45:22 -07:00
Nassim Eddequiouaq a8de4f6e8a Merge pull request #77 from jeanlaurent/win-release
Adding winrelease target
2017-06-15 12:39:03 +02:00
Jean-Laurent de Morlhon 4fbc86d7d0 Adding winrelease target
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-06-15 12:03:00 +02:00
Vincent Demeester 2d19ebb7f4 Merge pull request #76 from thaJeztah/bump-0.5.2
Bump version 0.5.2
2017-06-15 11:31:05 +02:00
Sebastiaan van Stijn 054c53824f Bump version 0.5.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-15 11:21:41 +02:00
Sebastiaan van Stijn be1808e3ed Merge pull request #75 from thaJeztah/update-changelog
Update changelog
2017-06-15 11:07:09 +02:00
Sebastiaan van Stijn e1d4c012bc Fix storing URLs without scheme (#72)
* Fix storing URLs without scheme

If secrets are stored without specifying a scheme
(https://), the keychain-helper would interpret the
hostname as _path_, causing lookup of secrets to fail.

This patch makes sure that a scheme is added (if missing).

If no scheme is specified, https:// is used as a default.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

* Have pre go1.8 compiler able to compile

Signed-off-by: Tibor Vass <teabee89@gmail.com>

* Fix URL parsing with port and no scheme

Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>

* Improve parseURL comment

Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2017-06-15 10:58:17 +02:00
Sebastiaan van Stijn ad6ee5d58d Update changelog
The changelog wasn't updated for each release.

This patch updates the changelog, with changes
taken from the releases page

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-15 00:46:21 +02:00
Nassim Eddequiouaq 94be56b6f4 Merge pull request #74 from thaJeztah/bump-travis-to-go-18
Update TravisCI to use Go 1.8
2017-06-14 22:08:13 +02:00
Sebastiaan van Stijn 37bf8afe8b Update TravisCI to use Go 1.8
To match the version used in docker

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-14 21:29:13 +02:00
Nassim Eddequiouaq 3d7e1817f2 Merge pull request #73 from n4ss/travisci-golang1.7
Update TravisCI to use Go 1.7
2017-06-14 17:21:44 +02:00
Nassim 'Nass' Eddequiouaq 5651367281 Update TravisCI to use Go 1.7
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
2017-06-14 16:18:01 +02:00
Nassim Eddequiouaq d8f57a18c6 Merge pull request #61 from danieljoos/linux32
Workaround for Linux 32-bit build
2017-05-30 20:56:52 +02:00
Sebastiaan van Stijn 4bc0bc2a85 Merge pull request #67 from jeanlaurent/add-version-command
Add version command
2017-05-30 19:13:34 +02:00
Jean-Laurent de Morlhon 167b137eb4 vet/lint/fmt before release
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-05-30 18:26:18 +02:00
Jean-Laurent de Morlhon 2a67ef1524 make linter happy
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-05-30 18:26:18 +02:00
Jean-Laurent de Morlhon 6ba4edf6e9 Read version from sources
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-05-30 18:26:18 +02:00
Jean-Laurent de Morlhon fa4a4d4f71 Bump Version 0.5.1-> 0.5.2-devel
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-05-30 18:26:18 +02:00
Jean-Laurent de Morlhon 4d60b372aa Add a version command
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-05-30 18:26:18 +02:00
Vincent Demeester 607bf3c174 Merge pull request #68 from jeanlaurent/appveyor-build
AppVeyor build
2017-05-30 08:05:06 -07:00
Jean-Laurent de Morlhon 74f4f75bc1 AppVeyor build
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
2017-05-30 11:59:52 +02:00
Daniel Joos cdde659563 Workaround for Linux 32-bit build
This adds a workaround for the `secretservice` sub-package to build under 32bit Linux.
It limits the total number of list entries to (2^29)-1.
A short explanation of how this number was calculated has been added in a comment.

Signed-off-by: Daniel Joos <daniel@joosweb.de>
2017-05-10 00:38:33 +02:00
27 changed files with 854 additions and 100 deletions
+2 -1
View File
@@ -9,12 +9,13 @@
notifications: notifications:
email: false email: false
go: go:
- 1.6 - 1.8
install: make deps install: make deps
addons: addons:
apt: apt:
packages: packages:
- libsecret-1-dev - libsecret-1-dev
- pass
before_script: before_script:
- "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
+35 -1
View File
@@ -4,6 +4,41 @@ This changelog tracks the releases of docker-credential-helpers.
This project includes different binaries per platform. This project includes different binaries per platform.
The platform released is identified after the tag name. The platform released is identified after the tag name.
## v0.6.0 (Go client, Linux)
- New credential helper on Linux using `pass`
- New entry point for passing environment variables when calling a credential helper
- Add a Makefile rule generating a Windows release binary
### Note
`pass` needs to be configured for `docker-credential-pass` to work properly.
It must be initialized with a `gpg2` key ID. Make sure your GPG key exists is in `gpg2` keyring as `pass` uses `gpg2` instead of the regular `gpg`.
## v0.5.2 (Mac OS X, Windows, Linux)
- Add a `version` command to output the version
- Fix storing URLs without scheme, and use `https://` by default
## v0.5.1 (Go client, Mac OS X, Windows, Linux)
- Redirect credential helpers' standard error to the caller's
- Prevent invalid credentials and credentials queries
## v0.5.0 (Mac OS X)
- Add a label for Docker credentials and filter credentials lookup to filter keychain lookups
## v0.4.2 (Mac OS X, Windows)
- Fix osxkeychain list
- macOS binary is now signed on release
- Generate a `.exe` instead
## v0.4.1 (Mac OS X)
- Fixes to support older version of OSX (10.10, 10.11)
## v0.4.0 (Go client, Mac OS X, Windows, Linux) ## v0.4.0 (Go client, Mac OS X, Windows, Linux)
- Full implementation for OSX ready - Full implementation for OSX ready
@@ -11,7 +46,6 @@ The platform released is identified after the tag name.
- Implement client.List, change list API - Implement client.List, change list API
- mac: delete credentials before adding them to avoid already exist error (fixes #37) - mac: delete credentials before adding them to avoid already exist error (fixes #37)
## v0.3.0 (Go client) ## v0.3.0 (Go client)
- Add Go client library to talk with the native programs. - Add Go client library to talk with the native programs.
+38 -9
View File
@@ -1,38 +1,48 @@
.PHONY: all deps osxkeychain secretservice test validate wincred .PHONY: all deps osxkeychain secretservice test validate wincred pass deb
TRAVIS_OS_NAME ?= linux TRAVIS_OS_NAME ?= linux
VERSION = 0.5.1 VERSION := $(shell grep 'const Version' credentials/version.go | awk -F'"' '{ print $$2 }')
all: test all: test
deps: deps:
go get github.com/golang/lint/golint go get -u github.com/golang/lint/golint
clean: clean:
rm -rf bin rm -rf bin
rm -rf release rm -rf release
osxkeychain: osxkeychain:
mkdir bin mkdir -p bin
go build -ldflags -s -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go go build -ldflags -s -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
codesign: osxkeychain osxcodesign: osxkeychain
$(eval SIGNINGHASH = $(shell security find-identity -v -p codesigning | grep "Developer ID Application: Docker Inc" | cut -d ' ' -f 4)) $(eval SIGNINGHASH = $(shell security find-identity -v -p codesigning | grep "Developer ID Application: Docker Inc" | cut -d ' ' -f 4))
xcrun -log codesign -s $(SIGNINGHASH) --force --verbose bin/docker-credential-osxkeychain xcrun -log codesign -s $(SIGNINGHASH) --force --verbose bin/docker-credential-osxkeychain
xcrun codesign --verify --deep --strict --verbose=2 --display bin/docker-credential-osxkeychain xcrun codesign --verify --deep --strict --verbose=2 --display bin/docker-credential-osxkeychain
osxrelease: clean test codesign osxrelease: clean vet_osx lint fmt test osxcodesign
mkdir -p release mkdir -p release
@echo "\nPackaging version ${VERSION}\n"
cd bin && tar cvfz ../release/docker-credential-osxkeychain-v$(VERSION)-amd64.tar.gz docker-credential-osxkeychain cd bin && tar cvfz ../release/docker-credential-osxkeychain-v$(VERSION)-amd64.tar.gz docker-credential-osxkeychain
secretservice: secretservice:
mkdir bin mkdir -p bin
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
pass:
mkdir -p bin
go build -o bin/docker-credential-pass pass/cmd/main_linux.go
wincred: wincred:
mkdir bin mkdir -p bin
go build -o bin/docker-credential-wincred.exe wincred/cmd/main_windows.go go build -o bin/docker-credential-wincred.exe wincred/cmd/main_windows.go
winrelease: clean vet_win lint fmt test wincred
mkdir -p release
@echo "\nPackaging version ${VERSION}\n"
cd bin && zip ../release/docker-credential-wincred-v$(VERSION)-amd64.zip docker-credential-wincred.exe
test: test:
# tests all packages except vendor # tests all packages except vendor
go test -v `go list ./... | grep -v /vendor/` go test -v `go list ./... | grep -v /vendor/`
@@ -40,14 +50,33 @@ test:
vet: vet_$(TRAVIS_OS_NAME) vet: vet_$(TRAVIS_OS_NAME)
go vet ./credentials go vet ./credentials
vet_win:
go vet ./wincred
vet_osx: vet_osx:
go vet ./osxkeychain go vet ./osxkeychain
vet_linux: vet_linux:
go vet ./secretservice go vet ./secretservice
validate: vet lint:
for p in `go list ./... | grep -v /vendor/`; do \ for p in `go list ./... | grep -v /vendor/`; do \
golint $$p ; \ golint $$p ; \
done done
fmt:
gofmt -s -l `ls **/*.go | grep -v vendor` gofmt -s -l `ls **/*.go | grep -v vendor`
validate: vet lint fmt
BUILDIMG:=docker-credential-secretservice-$(VERSION)
deb:
mkdir -p release
docker build -f deb/Dockerfile \
--build-arg VERSION=$(VERSION) \
--build-arg DISTRO=xenial \
--tag $(BUILDIMG) \
.
docker run --rm --net=none $(BUILDIMG) tar cf - /release | tar xf -
docker rmi $(BUILDIMG)
+6
View File
@@ -55,6 +55,12 @@ You can see examples of each function in the [client](https://godoc.org/github.c
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store. 1. osxkeychain: Provides a helper to use the OS X keychain as credentials store.
2. secretservice: Provides a helper to use the D-Bus secret service as credentials store. 2. secretservice: Provides a helper to use the D-Bus secret service as credentials store.
3. wincred: Provides a helper to use Windows credentials manager as store. 3. wincred: Provides a helper to use Windows credentials manager as store.
4. pass: Provides a helper to use `pass` as credentials store.
#### Note
`pass` needs to be configured for `docker-credential-pass` to work properly.
It must be initialized with a `gpg2` key ID. Make sure your GPG key exists is in `gpg2` keyring as `pass` uses `gpg2` instead of the regular `gpg`.
## Development ## Development
+13 -56
View File
@@ -1,67 +1,24 @@
version: "{build}" image: Visual Studio 2015
# Source Config
clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers
# Build host
environment: environment:
global:
GOPATH: c:\gopath GOPATH: c:\gopath
CGO_ENABLED: 1
GOVERSION: 1.6
matrix:
- platform: x86
GOARCH: 386
MSYS2_BITS: 32
- platform: x64
GOARCH: amd64
MSYS2_BITS: 64
init: clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers
- git config --global core.autocrlf input clone_depth: 10
# Build before_build:
- set PATH=%PATH%;C:\MinGW\bin;
- set PATH=%PATH%;C:\go18\bin;
- set GOROOT=C:\go18
install: build_script:
# Install Go 1.6. - mingw32-make vet_win wincred
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-%GOARCH%.msi
- msiexec /i go%GOVERSION%.windows-%GOARCH%.msi /q
- set Path=c:\msys64\mingw%MSYS2_BITS%\bin;c:\go\bin;%Path%
- go version
- go env
build: false
test_script: test_script:
- go vet ./wincred - mingw32-make test
- go test -v github.com/docker/docker-credential-helpers/wincred
# Equivalent to `before_deploy` phase deploy: off
after_test:
# build binary
- mkdir bin
- go build -o bin/docker-credential-wincred wincred/cmd/main_windows.go
# build zipfile, will look like docker-credential-wincred-v0.1.0-amd64.zip in the root directory
- cd bin && 7z a ../docker-credential-wincred-%APPVEYOR_REPO_TAG_NAME%-%GOARCH%.zip docker-credential-wincred
# IMPORTANT All the artifacts need to be listed here, or they won't be uploaded to GitHub
artifacts: artifacts:
- path: docker-credential-wincred-$(APPVEYOR_REPO_TAG_NAME)-$(GOARCH).zip - path: bin/docker-credential-wincred.exe
name: docker-credential-wincred-$(APPVEYOR_REPO_TAG_NAME)-$(GOARCH).zip
deploy: configuration: Release
# All the zipped artifacts will be deployed
description: "Visit the [Changelog](https://github.com/docker/docker-credential-helpers/blob/master/CHANGELOG.md) for a detailed description of what's new in this release."
artifact: /.*\.zip/
auth_token:
secure: ixWmTXZs8aV5+9s6vPXziIcdMMLd+lBVINJ0K/Sy++2wllpRxUec4/TPVKUGLqvL
provider: GitHub
# deploy when a new tag is pushed
on:
appveyor_repo_tag: true
branches:
only:
- master
+18
View File
@@ -2,3 +2,21 @@ set -ex
sh -e /etc/init.d/xvfb start sh -e /etc/init.d/xvfb start
sleep 3 # give xvfb some time to start sleep 3 # give xvfb some time to start
# init key for pass
gpg --batch --gen-key <<-EOF
%echo Generating a standard key
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Meshuggah Rocks
Name-Email: meshuggah@example.com
Expire-Date: 0
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
EOF
key=$(gpg --no-auto-check-trustdb --list-secret-keys | grep ^sec | cut -d/ -f2 | cut -d" " -f1)
pass init $key
+17 -5
View File
@@ -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.
+11 -2
View File
@@ -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)
} }
@@ -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
}
-3
View File
@@ -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
View File
@@ -0,0 +1,4 @@
package credentials
// Version holds a string describing the current version
const Version = "0.6.0"
+19
View File
@@ -0,0 +1,19 @@
FROM ubuntu:xenial
ARG VERSION
ARG DISTRO
RUN apt-get update && apt-get install -yy debhelper dh-make golang-go libsecret-1-dev
RUN mkdir -p /build
WORKDIR /build
ENV GOPATH /build
COPY Makefile .
COPY credentials credentials
COPY secretservice secretservice
COPY pass pass
COPY deb/debian ./debian
COPY deb/build-deb .
RUN /build/build-deb ${VERSION} ${DISTRO}
Executable
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -x
set -e
version=$1
distro=$2
maintainer=$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' debian/control)
cat > "debian/changelog" <<-EOF
docker-credential-helpers ($version) $DISTRO; urgency=low
* New upstream version
-- $maintainer $(date --rfc-2822)
EOF
mkdir -p src/github.com/docker/docker-credential-helpers
ln -s /build/credentials /build/src/github.com/docker/docker-credential-helpers/credentials
ln -s /build/secretservice /build/src/github.com/docker/docker-credential-helpers/secretservice
ln -s /build/pass /build/src/github.com/docker/docker-credential-helpers/pass
dpkg-buildpackage -us -uc
mkdir /release
mv /docker-credential-* /release
+1
View File
@@ -0,0 +1 @@
9
+25
View File
@@ -0,0 +1,25 @@
Source: docker-credential-helpers
Section: admin
Priority: optional
Maintainer: Docker <support@docker.com>
Homepage: https://dockerproject.org
Standards-Version: 3.9.6
Vcs-Browser: https://github.com/docker/docker-credential-helpers
Vcs-Git: git://github.com/docker/docker-credential-helpers.git
Build-Depends: debhelper
, dh-make
, libsecret-1-dev
Package: docker-credential-secretservice
Architecture: any
Depends: libsecret-1-0
, ${misc:Depends}
Description: docker-credential-secretservice is a credential helper backend
which uses libsecret to keep Docker credentials safe.
Package: docker-credential-pass
Architecture: any
Depends: pass
, ${misc:Depends}
Description: docker-credential-secretservice is a credential helper backend
which uses the pass utility to keep Docker credentials safe.
@@ -0,0 +1 @@
debian/tmp/usr/bin/docker-credential-pass
@@ -0,0 +1 @@
debian/tmp/usr/bin/docker-credential-secretservice
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/make -f
DESTDIR := $(CURDIR)/debian/tmp
override_dh_auto_build:
make secretservice pass
override_dh_auto_install:
install -D bin/docker-credential-secretservice $(DESTDIR)/usr/bin/docker-credential-secretservice
install -D bin/docker-credential-pass $(DESTDIR)/usr/bin/docker-credential-pass
%:
dh $@
override_dh_auto_test:
# no tests
+40 -14
View File
@@ -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, ":")
host := hostAndPort[0]
var port int
if len(hostAndPort) == 2 {
p, err := strconv.Atoi(hostAndPort[1])
if err != nil {
return nil, err
}
port = p
}
proto := C.kSecProtocolTypeHTTPS proto := C.kSecProtocolTypeHTTPS
if u.Scheme != "https" { if u.Scheme == "http" {
proto = C.kSecProtocolTypeHTTP proto = C.kSecProtocolTypeHTTP
} }
var port int
p := getPort(u)
if p != "" {
port, err = strconv.Atoi(p)
if err != nil {
return nil, err
}
}
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
}
+190
View File
@@ -1,6 +1,8 @@
package osxkeychain package osxkeychain
import ( import (
"errors"
"fmt"
"github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/credentials"
"testing" "testing"
) )
@@ -54,6 +56,194 @@ func TestOSXKeychainHelper(t *testing.T) {
} }
} }
// TestOSXKeychainHelperParseURL verifies that a // "scheme" is added to URLs,
// and that invalid URLs produce an error.
func TestOSXKeychainHelperParseURL(t *testing.T) {
tests := []struct {
url string
expectedURL string
err error
}{
{url: "foobar.docker.io", expectedURL: "//foobar.docker.io"},
{url: "foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
{url: "//foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
{url: "http://foobar.docker.io:2376", expectedURL: "http://foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376", expectedURL: "https://foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376/some/path", expectedURL: "https://foobar.docker.io:2376/some/path"},
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar", expectedURL: "https://foobar.docker.io:2376/some/other/path"},
{url: "/foobar.docker.io", err: errors.New("no hostname in URL")},
{url: "ftp://foobar.docker.io:2376", err: errors.New("unsupported scheme: ftp")},
}
for _, te := range tests {
u, err := parseURL(te.url)
if te.err == nil && err != nil {
t.Errorf("Error: failed to parse URL %q: %s", te.url, err)
continue
}
if te.err != nil && err == nil {
t.Errorf("Error: expected error %q, got none when parsing URL %q", te.err, te.url)
continue
}
if te.err != nil && err.Error() != te.err.Error() {
t.Errorf("Error: expected error %q, got %q when parsing URL %q", te.err, err, te.url)
continue
}
if u != nil && u.String() != te.expectedURL {
t.Errorf("Error: expected URL: %q, but got %q for URL: %q", te.expectedURL, u.String(), te.url)
}
}
}
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
// through variations on the URL
func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
tests := []struct {
storeURL string
readURL string
}{
// stored with port, retrieved without
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
// stored as https, retrieved without scheme
{"https://foobar.docker.io:2376", "foobar.docker.io"},
// stored with path, retrieved without
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234"},
}
helper := Osxkeychain{}
defer func() {
for _, te := range tests {
helper.Delete(te.storeURL)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.storeURL)
}
for _, te := range tests {
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
continue
}
if _, _, err := helper.Get(te.readURL); err != nil {
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
}
helper.Delete(te.storeURL)
}
}
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
// returned.
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
tests := []struct {
storeURL string
readURL string
}{
// stored as https, retrieved using http
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
// stored as http, retrieved using https
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
{"http://foobar.docker.io", "foobar.docker.io:5678"},
// non-matching ports
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
// non-matching ports TODO is this desired behavior? The other way round does work
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
// non-matching paths
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
}
helper := Osxkeychain{}
defer func() {
for _, te := range tests {
helper.Delete(te.storeURL)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.storeURL)
}
for _, te := range tests {
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
continue
}
if _, _, err := helper.Get(te.readURL); err == nil {
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
}
helper.Delete(te.storeURL)
}
}
// TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the
// the keychain can be read back using the URL that was used to store them.
func TestOSXKeychainHelperStoreRetrieve(t *testing.T) {
tests := []struct {
url string
}{
{url: "foobar.docker.io"},
{url: "foobar.docker.io:2376"},
{url: "//foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376"},
{url: "http://foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376/some/path"},
{url: "https://foobar.docker.io:2376/some/other/path"},
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
}
helper := Osxkeychain{}
defer func() {
for _, te := range tests {
helper.Delete(te.url)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.url)
}
// Note that we don't delete between individual tests here, to verify that
// subsequent stores/overwrites don't affect storing / retrieving secrets.
for i, te := range tests {
c := &credentials.Credentials{
ServerURL: te.url,
Username: fmt.Sprintf("user-%d", i),
Secret: fmt.Sprintf("secret-%d", i),
}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
continue
}
user, secret, err := helper.Get(te.url)
if err != nil {
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
continue
}
if user != c.Username {
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
}
if secret != c.Secret {
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
}
}
}
func TestMissingCredentials(t *testing.T) { func TestMissingCredentials(t *testing.T) {
helper := Osxkeychain{} helper := Osxkeychain{}
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
+13
View 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
View 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(":"):]
}
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker-credential-helpers/pass"
)
func main() {
credentials.Serve(pass.Pass{})
}
+239
View File
@@ -0,0 +1,239 @@
// 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() {
// 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
// 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
// password.
password := "pass is initialized"
name := path.Join(PASS_FOLDER, "docker-pass-initialized-check")
_, err := runPass(password, "insert", "-f", "-m", name)
if err != nil {
return
}
stored, err := runPass("", "show", name)
PassInitialized = err == nil && stored == password
if PassInitialized {
runPass("", "rm", "-rf", name)
}
}
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
}
func getPassDir() string {
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
}
return passDir
}
// 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 := getPassDir()
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))
if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil {
if os.IsNotExist(err) {
return "", "", nil;
}
return "", "", err
}
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
}
+75
View File
@@ -0,0 +1,75 @@
package pass
import (
"strings"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestPassHelper(t *testing.T) {
helper := Pass{}
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "nothing",
Secret: "isthebestmeshuggahalbum",
}
helper.Add(creds)
creds.ServerURL = "https://foobar.docker.io:9999/v2"
helper.Add(creds)
credsList, err := helper.List()
if err != nil {
t.Fatal(err)
}
for server, username := range credsList {
if !(strings.Contains(server, "2376") ||
strings.Contains(server, "9999")) {
t.Fatalf("invalid url: %s", creds.ServerURL)
}
if username != "nothing" {
t.Fatalf("invalid username: %v", username)
}
u, s, err := helper.Get(server)
if err != nil {
t.Fatal(err)
}
if u != username {
t.Fatalf("invalid username %s", u)
}
if s != "isthebestmeshuggahalbum" {
t.Fatalf("invalid secret: %s", s)
}
err = helper.Delete(server)
if err != nil {
t.Fatal(err)
}
username, _, err = helper.Get(server)
if err != nil {
t.Fatal(err)
}
if username != "" {
t.Fatalf("%s shouldn't exist any more", username)
}
}
credsList, err = helper.List()
if err != nil {
t.Fatal(err)
}
if len(credsList) != 0 {
t.Fatal("didn't delete all creds?")
}
}
+5 -2
View File
@@ -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])
} }
+2 -2
View File
@@ -1,8 +1,8 @@
package wincred package wincred
import ( import (
"testing"
"strings" "strings"
"testing"
"github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/credentials"
) )
@@ -63,7 +63,7 @@ func TestWinCredHelper(t *testing.T) {
} }
auths, err := helper.List() auths, err := helper.List()
if err != nil || len(auths) - len(oldauths) != 1 { if err != nil || len(auths)-len(oldauths) != 1 {
t.Fatal(err) t.Fatal(err)
} }