docker-credential-helpers-0.5.0/000077500000000000000000000000001306162737600165465ustar00rootroot00000000000000docker-credential-helpers-0.5.0/.gitignore000066400000000000000000000000141306162737600205310ustar00rootroot00000000000000bin release docker-credential-helpers-0.5.0/.travis.yml000066400000000000000000000036031306162737600206610ustar00rootroot00000000000000--- # See appveyor.yml for windows build. sudo: required language: go dist: trusty os: - linux - osx notifications: email: false go: - 1.6 install: make deps addons: apt: packages: - libsecret-1-dev before_script: - "export DISPLAY=:99.0" - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh ci/before_script_linux.sh; fi - make validate script: make test before_deploy: - sh ci/before_deploy.sh deploy: provider: releases api_key: secure: "cGs5cao/MeVQVnum+Pr/Tpv+w83NsqGVS3wxvi3LYEf2ON4Kkmtd+Alwi0YFkGPJmSY0jZOct8NVK/M70qSnIU4l+AAq9+3KSMv23u4xrmy2sQog3AF+Ve3Rac+iYwZHOWwGs9I67CSuVv0vjJNVsDsTVefc25lHJImjRvXIS4p9xYzRPeUDCoqAo/QMVE+vFiMyxydsvt8fhd0gZCjPYWEpyHe9tjZ1tr1HsHZKFAjVb6AmF45d8rvadPoVUuLaOtr35wDC3XRKEvCZUefQpwLkrNj7j2L1rVGlY1xTE2APpLtvfd7R1Mx6kSfS1Gm3Pwcv3mugadXIhecL0lsdnU+BANjX3VUiv4ryzTPbsge966mv9ZQYwAzgCQTWRtMNJqsAnPZTeAkiOntd+HMQbPpxljOxv1sjDPY+EIZesyB3yQRJI8vMxqFcAjxeRyLcBqEnRFC2nd/Ln0KZ7ZFu16FcpNqRojdBayyypuXKqAiBNwtp4ti/65x8eHfBJuNjJtNZkRsJEYam4CYMRLxds9plKQfkaZ8045PKpyXO8fMpUhrfqSVID4IrYvD+io6XoXtdR4Lk6isZ2EgrjdrqgdG70S5lwKihL4iAi2F2ZCWhngFhkeNVOZunEWE6qZMk5wKODajR9sixGDApGPZQVojHwCNRGILZaHZ39JCIj3s=" # upload file artifacts using a glob expression. # It requires both options `file_glob` and `file`: # https://github.com/travis-ci/dpl/blob/master/lib/dpl/provider/releases.rb#L47-L53 file_glob: true file: docker-credential-*-${TRAVIS_TAG}-amd64.tar.gz # don't delete the artifacts from previous phases skip_cleanup: true # deploy when a new tag is pushed on: tags: true branches: only: # Pushes and PR to the master branch - master # IMPORTANT Ruby regex to match tags. Required, or travis won't trigger deploys when a new tag # is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22 - /^v\d+\.\d+\.\d+.*$/ docker-credential-helpers-0.5.0/CHANGELOG.md000066400000000000000000000015421306162737600203610ustar00rootroot00000000000000# Changelog This changelog tracks the releases of docker-credential-helpers. This project includes different binaries per platform. The platform released is identified after the tag name. ## v0.4.0 (Go client, Mac OS X, Windows, Linux) - Full implementation for OSX ready - Fix some windows issues - Implement client.List, change list API - mac: delete credentials before adding them to avoid already exist error (fixes #37) ## v0.3.0 (Go client) - Add Go client library to talk with the native programs. ## v0.2.0 (Mac OS X, Windows, Linux) - Initial release of docker-credential-secretservice for Linux. - Use new secrets payload introduced in https://github.com/docker/docker/pull/20970. ## v0.1.0 (Mac OS X, Windows) - Initial release of docker-credential-osxkeychain for Mac OS X. - Initial release of docker-credential-wincred for Microsoft Windows. docker-credential-helpers-0.5.0/LICENSE000066400000000000000000000020421306162737600175510ustar00rootroot00000000000000Copyright (c) 2016 David Calavera Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. docker-credential-helpers-0.5.0/MAINTAINERS000066400000000000000000000063011306162737600202430ustar00rootroot00000000000000# docker-credential-helpers maintainers file # # This file describes who runs the docker/docker-credential-helpers project and how. # This is a living document - if you see something out of date or missing, speak up! # # It is structured to be consumable by both humans and programs. # To extract its contents programmatically, use any TOML-compliant parser. # # This file is compiled into the MAINTAINERS file in docker/opensource. # [Org] [Org."Core maintainers"] people = [ "aaronlehmann", "calavera", "coolljt0725", "cpuguy83", "crosbymichael", "dnephin", "dongluochen", "duglin", "estesp", "icecrime", "jhowardmsft", "lk4d4", "mavenugo", "mhbauer", "runcom", "stevvooe", "thajeztah", "tianon", "tibor", "tonistiigi", "unclejack", "vdemeester", "vieux" ] [people] # A reference list of all people associated with the project. # All other sections should refer to people by their canonical key # in the people section. # 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] Name = "Lei Jitang" Email = "leijitang@huawei.com" GitHub = "coolljt0725" [people.cpuguy83] Name = "Brian Goff" Email = "cpuguy83@gmail.com" Github = "cpuguy83" [people.crosbymichael] Name = "Michael Crosby" Email = "crosbymichael@gmail.com" GitHub = "crosbymichael" [people.dnephin] Name = "Daniel Nephin" Email = "dnephin@gmail.com" GitHub = "dnephin" [people.dongluochen] Name = "Dongluo Chen" Email = "dongluo.chen@docker.com" GitHub = "dongluochen" [people.duglin] Name = "Doug Davis" Email = "dug@us.ibm.com" GitHub = "duglin" [people.estesp] Name = "Phil Estes" Email = "estesp@linux.vnet.ibm.com" GitHub = "estesp" [people.icecrime] Name = "Arnaud Porterie" Email = "arnaud@docker.com" GitHub = "icecrime" [people.jhowardmsft] Name = "John Howard" Email = "jhoward@microsoft.com" GitHub = "jhowardmsft" [people.lk4d4] Name = "Alexander Morozov" Email = "lk4d4@docker.com" GitHub = "lk4d4" [people.mavenugo] Name = "Madhu Venugopal" Email = "madhu@docker.com" GitHub = "mavenugo" [people.mhbauer] Name = "Morgan Bauer" Email = "mbauer@us.ibm.com" GitHub = "mhbauer" [people.runcom] Name = "Antonio Murdaca" Email = "runcom@redhat.com" GitHub = "runcom" [people.stevvooe] Name = "Stephen Day" Email = "stephen.day@docker.com" GitHub = "stevvooe" [people.thajeztah] Name = "Sebastiaan van Stijn" Email = "github@gone.nl" GitHub = "thaJeztah" [people.tianon] Name = "Tianon Gravi" Email = "admwiggin@gmail.com" GitHub = "tianon" [people.tibor] Name = "Tibor Vass" Email = "tibor@docker.com" GitHub = "tiborvass" [people.tonistiigi] Name = "Tõnis Tiigi" Email = "tonis@docker.com" GitHub = "tonistiigi" [people.unclejack] Name = "Cristian Staretu" Email = "cristian.staretu@gmail.com" GitHub = "unclejack" [people.vdemeester] Name = "Vincent Demeester" Email = "vincent@sbr.pm" GitHub = "vdemeester" [people.vieux] Name = "Victor Vieux" Email = "vieux@docker.com" GitHub = "vieux" docker-credential-helpers-0.5.0/Makefile000066400000000000000000000025341306162737600202120ustar00rootroot00000000000000.PHONY: all deps osxkeychain secretservice test validate wincred TRAVIS_OS_NAME ?= linux VERSION = 0.5.0 all: test deps: go get github.com/golang/lint/golint clean: rm -rf bin rm -rf release osxkeychain: mkdir bin go build -ldflags -s -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go codesign: osxkeychain $(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 codesign --verify --deep --strict --verbose=2 --display bin/docker-credential-osxkeychain osxrelease: clean test codesign mkdir -p release cd bin && tar cvfz ../release/docker-credential-osxkeychain-v$(VERSION)-amd64.tar.gz docker-credential-osxkeychain secretservice: mkdir bin go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go wincred: mkdir bin go build -o bin/docker-credential-wincred.exe wincred/cmd/main_windows.go test: # tests all packages except vendor go test -v `go list ./... | grep -v /vendor/` vet: vet_$(TRAVIS_OS_NAME) go vet ./credentials vet_osx: go vet ./osxkeychain vet_linux: go vet ./secretservice validate: vet for p in `go list ./... | grep -v /vendor/`; do \ golint $$p ; \ done gofmt -s -l `ls **/*.go | grep -v vendor` docker-credential-helpers-0.5.0/README.md000066400000000000000000000064561306162737600200400ustar00rootroot00000000000000## Introduction docker-credential-helpers is a suite of programs to use native stores to keep Docker credentials safe. ## Installation Go to the [Releases](https://github.com/docker/docker-credential-helpers/releases) page and download the binary that works better for you. Put that binary in your `$PATH`, so Docker can find it. ### Building from scratch The programs in this repository are written with the Go programming language. These instructions assume that you have previous knowledge about the language and you have it installed in your machine. 1 - Download the source and put it in your `$GOPATH` with `go get`. ``` $ go get github.com/docker/docker-credential-helpers ``` 2 - Use `make` to build the program you want. That will leave any executable in the `bin` directory inside the repository. ``` $ cd $GOPATH/docker/docker-credentials-helpers $ make osxkeychain ``` 3 - Put that binary in your `$PATH`, so Docker can find it. ## Usage ### With the Docker Engine Set the `credsStore` option in your `.docker/config.json` file with the suffix of the program you want to use. For instance, set it to `osxkeychain` if you want to use `docker-credential-osxkeychain`. ```json { "credsStore": "osxkeychain" } ``` ### With other command line applications The sub-package [client](https://godoc.org/github.com/docker/docker-credential-helpers/client) includes functions to call external programs from your own command line applications. There are three things you need to know if you need to interact with a helper: 1. The name of the program to execute, for instance `docker-credential-osxkeychain`. 2. The server address to identify the credentials, for instance `https://example.com`. 3. The username and secret to store, when you want to store credentials. You can see examples of each function in the [client](https://godoc.org/github.com/docker/docker-credential-helpers/client) documentation. ### Available programs 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. 3. wincred: Provides a helper to use Windows credentials manager as store. ## Development A credential helper can be any program that can read values from the standard input. We use the first argument in the command line to differentiate the kind of command to execute. There are four valid values: - `store`: Adds credentials to the keychain. The payload in the standard input is a JSON document with `ServerURL`, `Username` and `Secret`. - `get`: Retrieves credentials from the keychain. The payload in the standard input is the raw value for the `ServerURL`. - `erase`: Removes credentials from the keychain. The payload in the standard input is the raw value for the `ServerURL`. - `list`: Lists stored credentials. There is no standard input payload. This repository also includes libraries to implement new credentials programs in Go. Adding a new helper program is pretty easy. You can see how the OS X keychain helper works in the [osxkeychain](osxkeychain) directory. 1. Implement the interface `credentials.Helper` in `YOUR_PACKAGE/YOUR_PACKAGE_$GOOS.go` 2. Create a main program in `YOUR_PACKAGE/cmd/main_$GOOS.go`. 3. Add make tasks to build your program and run tests. ## License MIT. See [LICENSE](LICENSE) for more information. docker-credential-helpers-0.5.0/appveyor.yml000066400000000000000000000035731306162737600211460ustar00rootroot00000000000000version: "{build}" # Source Config clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers # Build host environment: global: 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: - git config --global core.autocrlf input # Build install: # Install Go 1.6. - 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: - go vet ./wincred - go test -v github.com/docker/docker-credential-helpers/wincred # Equivalent to `before_deploy` phase 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: - path: docker-credential-wincred-$(APPVEYOR_REPO_TAG_NAME)-$(GOARCH).zip name: docker-credential-wincred-$(APPVEYOR_REPO_TAG_NAME)-$(GOARCH).zip deploy: # 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 docker-credential-helpers-0.5.0/ci/000077500000000000000000000000001306162737600171415ustar00rootroot00000000000000docker-credential-helpers-0.5.0/ci/before_deploy.sh000066400000000000000000000007151306162737600223160ustar00rootroot00000000000000set -ex mkdir bin case "$TRAVIS_OS_NAME" in "osx") go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go cd bin tar czf ../docker-credential-osxkeychain-${TRAVIS_TAG}-amd64.tar.gz docker-credential-osxkeychain ;; "linux") go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go cd bin tar czf ../docker-credential-secretservice-${TRAVIS_TAG}-amd64.tar.gz docker-credential-secretservice ;; esac docker-credential-helpers-0.5.0/ci/before_script_linux.sh000066400000000000000000000001151306162737600235370ustar00rootroot00000000000000set -ex sh -e /etc/init.d/xvfb start sleep 3 # give xvfb some time to start docker-credential-helpers-0.5.0/client/000077500000000000000000000000001306162737600200245ustar00rootroot00000000000000docker-credential-helpers-0.5.0/client/client.go000066400000000000000000000042241306162737600216330ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "strings" "github.com/docker/docker-credential-helpers/credentials" ) // Store uses an external program to save credentials. func Store(program ProgramFunc, credentials *credentials.Credentials) error { cmd := program("store") buffer := new(bytes.Buffer) if err := json.NewEncoder(buffer).Encode(credentials); err != nil { return err } cmd.Input(buffer) out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t) } return nil } // Get executes an external program to get the credentials from a native store. func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) { cmd := program("get") cmd.Input(strings.NewReader(serverURL)) out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) if credentials.IsErrCredentialsNotFoundMessage(t) { return nil, credentials.NewErrCredentialsNotFound() } return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t) } resp := &credentials.Credentials{ ServerURL: serverURL, } if err := json.NewDecoder(bytes.NewReader(out)).Decode(resp); err != nil { return nil, err } return resp, nil } // Erase executes a program to remove the server credentials from the native store. func Erase(program ProgramFunc, serverURL string) error { cmd := program("erase") cmd.Input(strings.NewReader(serverURL)) out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t) } return nil } // List executes a program to list server credentials in the native store. func List(program ProgramFunc) (map[string]string, error) { cmd := program("list") cmd.Input(strings.NewReader("unused")) out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t) } var resp map[string]string if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil { return nil, err } return resp, nil } docker-credential-helpers-0.5.0/client/client_test.go000066400000000000000000000117261306162737600226770ustar00rootroot00000000000000package client import ( "encoding/json" "fmt" "io" "io/ioutil" "strings" "testing" "github.com/docker/docker-credential-helpers/credentials" ) const ( validServerAddress = "https://index.docker.io/v1" validUsername = "linus" validServerAddress2 = "https://example.com:5002" invalidServerAddress = "https://foobar.example.com" missingCredsAddress = "https://missing.docker.io/v1" ) var errProgramExited = fmt.Errorf("exited 1") // mockProgram simulates interactions between the docker client and a remote // credentials helper. // Unit tests inject this mocked command into the remote to control execution. type mockProgram struct { arg string input io.Reader } // Output returns responses from the remote credentials helper. // It mocks those responses based in the input in the mock. func (m *mockProgram) Output() ([]byte, error) { in, err := ioutil.ReadAll(m.input) if err != nil { return nil, err } inS := string(in) switch m.arg { case "erase": switch inS { case validServerAddress: return nil, nil default: return []byte("program failed"), errProgramExited } case "get": switch inS { case validServerAddress: return []byte(`{"Username": "foo", "Secret": "bar"}`), nil case validServerAddress2: return []byte(`{"Username": "", "Secret": "abcd1234"}`), nil case missingCredsAddress: return []byte(credentials.NewErrCredentialsNotFound().Error()), errProgramExited case invalidServerAddress: return []byte("program failed"), errProgramExited } case "store": var c credentials.Credentials err := json.NewDecoder(strings.NewReader(inS)).Decode(&c) if err != nil { return []byte("error storing credentials"), errProgramExited } switch c.ServerURL { case validServerAddress: return nil, nil case validServerAddress2: return nil, nil default: return []byte("error storing credentials"), errProgramExited } case "list": return []byte(fmt.Sprintf(`{"%s": "%s"}`, validServerAddress, validUsername)), nil } return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited } // Input sets the input to send to a remote credentials helper. func (m *mockProgram) Input(in io.Reader) { m.input = in } func mockProgramFn(args ...string) Program { return &mockProgram{ arg: args[0], } } func ExampleStore() { p := NewShellProgramFunc("docker-credential-secretservice") c := &credentials.Credentials{ ServerURL: "https://example.com", Username: "calavera", Secret: "my super secret token", } if err := Store(p, c); err != nil { fmt.Println(err) } } func TestStore(t *testing.T) { valid := []credentials.Credentials{ {validServerAddress, "foo", "bar"}, {validServerAddress2, "", "abcd1234"}, } for _, v := range valid { if err := Store(mockProgramFn, &v); err != nil { t.Fatal(err) } } invalid := []credentials.Credentials{ {invalidServerAddress, "foo", "bar"}, } for _, v := range invalid { if err := Store(mockProgramFn, &v); err == nil { t.Fatalf("Expected error for server %s, got nil", v.ServerURL) } } } func ExampleGet() { p := NewShellProgramFunc("docker-credential-secretservice") creds, err := Get(p, "https://example.com") if err != nil { fmt.Println(err) } fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL) } func TestGet(t *testing.T) { valid := []credentials.Credentials{ {validServerAddress, "foo", "bar"}, {validServerAddress2, "", "abcd1234"}, } for _, v := range valid { c, err := Get(mockProgramFn, v.ServerURL) if err != nil { t.Fatal(err) } if c.Username != v.Username { t.Fatalf("expected username `%s`, got %s", v.Username, c.Username) } if c.Secret != v.Secret { t.Fatalf("expected secret `%s`, got %s", v.Secret, c.Secret) } } invalid := []struct { serverURL string err string }{ {missingCredsAddress, credentials.NewErrCredentialsNotFound().Error()}, {invalidServerAddress, "error getting credentials - err: exited 1, out: `program failed`"}, } for _, v := range invalid { _, err := Get(mockProgramFn, v.serverURL) if err == nil { t.Fatalf("Expected error for server %s, got nil", v.serverURL) } if err.Error() != v.err { t.Fatalf("Expected error `%s`, got `%v`", v.err, err) } } } func ExampleErase() { p := NewShellProgramFunc("docker-credential-secretservice") if err := Erase(p, "https://example.com"); err != nil { fmt.Println(err) } } func TestErase(t *testing.T) { if err := Erase(mockProgramFn, validServerAddress); err != nil { t.Fatal(err) } if err := Erase(mockProgramFn, invalidServerAddress); err == nil { t.Fatalf("Expected error for server %s, got nil", invalidServerAddress) } } func TestList(t *testing.T) { auths, err := List(mockProgramFn) if err != nil { t.Fatal(err) } if username, exists := auths[validServerAddress]; !exists || username != validUsername { t.Fatalf("auths[%s] returned %s, %t; expected %s, %t", validServerAddress, username, exists, validUsername, true) } } docker-credential-helpers-0.5.0/client/command.go000066400000000000000000000015731306162737600217770ustar00rootroot00000000000000package client import ( "io" "os/exec" ) // Program is an interface to execute external programs. type Program interface { Output() ([]byte, error) Input(in io.Reader) } // ProgramFunc is a type of function that initializes programs based on arguments. type ProgramFunc func(args ...string) Program // NewShellProgramFunc creates programs that are executed in a Shell. func NewShellProgramFunc(name string) ProgramFunc { return func(args ...string) Program { return &Shell{cmd: exec.Command(name, args...)} } } // Shell invokes shell commands to talk with a remote credentials helper. type Shell struct { cmd *exec.Cmd } // Output returns responses from the remote credentials helper. func (s *Shell) Output() ([]byte, error) { return s.cmd.Output() } // Input sets the input to send to a remote credentials helper. func (s *Shell) Input(in io.Reader) { s.cmd.Stdin = in } docker-credential-helpers-0.5.0/credentials/000077500000000000000000000000001306162737600210435ustar00rootroot00000000000000docker-credential-helpers-0.5.0/credentials/credentials.go000066400000000000000000000071201306162737600236670ustar00rootroot00000000000000package credentials import ( "bufio" "bytes" "encoding/json" "fmt" "io" "os" "strings" ) // Credentials holds the information shared between docker and the credentials store. type Credentials struct { ServerURL string Username string Secret string } // 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, // Windows credentials manager and Linux libsecret. Default value is "Docker Credentials" var CredsLabel = "Docker Credentials" func SetCredsLabel(label string) { CredsLabel = label } // Serve initializes the credentials helper and parses the action argument. // This function is designed to be called from a command line interface. // It uses os.Args[1] as the key for the action. // It uses os.Stdin as input and os.Stdout as output. // This function terminates the program with os.Exit(1) if there is an error. func Serve(helper Helper) { var err error if len(os.Args) != 2 { err = fmt.Errorf("Usage: %s ", os.Args[0]) } if err == nil { err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout) } if err != nil { fmt.Fprintf(os.Stdout, "%v\n", err) os.Exit(1) } } // HandleCommand uses a helper and a key to run a credential action. func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error { switch key { case "store": return Store(helper, in) case "get": return Get(helper, in, out) case "erase": return Erase(helper, in) case "list": return List(helper, out) } return fmt.Errorf("Unknown credential action `%s`", key) } // Store uses a helper and an input reader to save credentials. // The reader must contain the JSON serialization of a Credentials struct. func Store(helper Helper, reader io.Reader) error { scanner := bufio.NewScanner(reader) buffer := new(bytes.Buffer) for scanner.Scan() { buffer.Write(scanner.Bytes()) } if err := scanner.Err(); err != nil && err != io.EOF { return err } var creds Credentials if err := json.NewDecoder(buffer).Decode(&creds); err != nil { return err } return helper.Add(&creds) } // Get retrieves the credentials for a given server url. // The reader must contain the server URL to search. // The writer is used to write the JSON serialization of the credentials. func Get(helper Helper, reader io.Reader, writer io.Writer) error { scanner := bufio.NewScanner(reader) buffer := new(bytes.Buffer) for scanner.Scan() { buffer.Write(scanner.Bytes()) } if err := scanner.Err(); err != nil && err != io.EOF { return err } serverURL := strings.TrimSpace(buffer.String()) username, secret, err := helper.Get(serverURL) if err != nil { return err } resp := Credentials{ Username: username, Secret: secret, } buffer.Reset() if err := json.NewEncoder(buffer).Encode(resp); err != nil { return err } fmt.Fprint(writer, buffer.String()) return nil } // Erase removes credentials from the store. // The reader must contain the server URL to remove. func Erase(helper Helper, reader io.Reader) error { scanner := bufio.NewScanner(reader) buffer := new(bytes.Buffer) for scanner.Scan() { buffer.Write(scanner.Bytes()) } if err := scanner.Err(); err != nil && err != io.EOF { return err } serverURL := strings.TrimSpace(buffer.String()) return helper.Delete(serverURL) } //List returns all the serverURLs of keys in //the OS store as a list of strings func List(helper Helper, writer io.Writer) error { accts, err := helper.List() if err != nil { return err } return json.NewEncoder(writer).Encode(accts) } docker-credential-helpers-0.5.0/credentials/credentials_test.go000066400000000000000000000062561306162737600247370ustar00rootroot00000000000000package credentials import ( "bytes" "encoding/json" "fmt" "strings" "testing" ) type memoryStore struct { creds map[string]*Credentials } func newMemoryStore() *memoryStore { return &memoryStore{ creds: make(map[string]*Credentials), } } func (m *memoryStore) Add(creds *Credentials) error { m.creds[creds.ServerURL] = creds return nil } func (m *memoryStore) Delete(serverURL string) error { delete(m.creds, serverURL) return nil } func (m *memoryStore) Get(serverURL string) (string, string, error) { c, ok := m.creds[serverURL] if !ok { return "", "", fmt.Errorf("creds not found for %s", serverURL) } return c.Username, c.Secret, nil } func (m *memoryStore) List() (map[string]string, error) { //Simply a placeholder to let memoryStore be a valid implementation of Helper interface return nil, nil } func TestStore(t *testing.T) { serverURL := "https://index.docker.io/v1/" creds := &Credentials{ ServerURL: serverURL, Username: "foo", Secret: "bar", } b, err := json.Marshal(creds) if err != nil { t.Fatal(err) } in := bytes.NewReader(b) h := newMemoryStore() if err := Store(h, in); err != nil { t.Fatal(err) } c, ok := h.creds[serverURL] if !ok { t.Fatalf("creds not found for %s\n", serverURL) } if c.Username != "foo" { t.Fatalf("expected username foo, got %s\n", c.Username) } if c.Secret != "bar" { t.Fatalf("expected username bar, got %s\n", c.Secret) } } func TestGet(t *testing.T) { serverURL := "https://index.docker.io/v1/" creds := &Credentials{ ServerURL: serverURL, Username: "foo", Secret: "bar", } b, err := json.Marshal(creds) if err != nil { t.Fatal(err) } in := bytes.NewReader(b) h := newMemoryStore() if err := Store(h, in); err != nil { t.Fatal(err) } buf := strings.NewReader(serverURL) w := new(bytes.Buffer) if err := Get(h, buf, w); err != nil { t.Fatal(err) } if w.Len() == 0 { t.Fatalf("expected output in the writer, got %d", w.Len()) } var c Credentials if err := json.NewDecoder(w).Decode(&c); err != nil { t.Fatal(err) } if c.Username != "foo" { t.Fatalf("expected username foo, got %s\n", c.Username) } if c.Secret != "bar" { t.Fatalf("expected username bar, got %s\n", c.Secret) } } func TestErase(t *testing.T) { serverURL := "https://index.docker.io/v1/" creds := &Credentials{ ServerURL: serverURL, Username: "foo", Secret: "bar", } b, err := json.Marshal(creds) if err != nil { t.Fatal(err) } in := bytes.NewReader(b) h := newMemoryStore() if err := Store(h, in); err != nil { t.Fatal(err) } buf := strings.NewReader(serverURL) if err := Erase(h, buf); err != nil { t.Fatal(err) } w := new(bytes.Buffer) if err := Get(h, buf, w); err == nil { t.Fatal("expected error getting missing creds, got empty") } } func TestList(t *testing.T) { //This tests that there is proper input an output into the byte stream //Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively out := new(bytes.Buffer) h := newMemoryStore() if err := List(h, out); err != nil { t.Fatal(err) } //testing that there is an output if out.Len() == 0 { t.Fatalf("expected output in the writer, got %d", 0) } } docker-credential-helpers-0.5.0/credentials/error.go000066400000000000000000000023751306162737600225320ustar00rootroot00000000000000package credentials // ErrCredentialsNotFound standarizes the not found error, so every helper returns // the same message and docker can handle it properly. const errCredentialsNotFoundMessage = "credentials not found in native keychain" // errCredentialsNotFound represents an error // raised when credentials are not in the store. type errCredentialsNotFound struct{} // Error returns the standard error message // for when the credentials are not in the store. func (errCredentialsNotFound) Error() string { return errCredentialsNotFoundMessage } // NewErrCredentialsNotFound creates a new error // for when the credentials are not in the store. func NewErrCredentialsNotFound() error { return errCredentialsNotFound{} } // IsErrCredentialsNotFound returns true if the error // was caused by not having a set of credentials in a store. func IsErrCredentialsNotFound(err error) bool { _, ok := err.(errCredentialsNotFound) return ok } // IsErrCredentialsNotFoundMessage returns true if the error // was caused by not having a set of credentials in a store. // // This function helps to check messages returned by an // external program via its standard output. func IsErrCredentialsNotFoundMessage(err string) bool { return err == errCredentialsNotFoundMessage } docker-credential-helpers-0.5.0/credentials/helper.go000066400000000000000000000007751306162737600226620ustar00rootroot00000000000000package credentials // Helper is the interface a credentials store helper must implement. type Helper interface { // Add appends credentials to the store. Add(*Credentials) error // Delete removes credentials from the store. Delete(serverURL string) error // Get retrieves credentials from the store. // It returns username and secret as strings. Get(serverURL string) (string, string, error) // List returns the stored serverURLs and their associated usernames. List() (map[string]string, error) } docker-credential-helpers-0.5.0/osxkeychain/000077500000000000000000000000001306162737600210735ustar00rootroot00000000000000docker-credential-helpers-0.5.0/osxkeychain/cmd/000077500000000000000000000000001306162737600216365ustar00rootroot00000000000000docker-credential-helpers-0.5.0/osxkeychain/cmd/main_darwin.go000066400000000000000000000003161306162737600244550ustar00rootroot00000000000000package main import ( "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/osxkeychain" ) func main() { credentials.Serve(osxkeychain.Osxkeychain{}) } docker-credential-helpers-0.5.0/osxkeychain/osxkeychain_darwin.c000066400000000000000000000152731306162737600251400ustar00rootroot00000000000000#include "osxkeychain_darwin.h" #include #include #include #include char *get_error(OSStatus status) { char *buf = malloc(128); CFStringRef str = SecCopyErrorMessageString(status, NULL); int success = CFStringGetCString(str, buf, 128, kCFStringEncodingUTF8); if (!success) { strncpy(buf, "Unknown error", 128); } return buf; } char *keychain_add(struct Server *server, char *label, char *username, char *secret) { SecKeychainItemRef item; OSStatus status = SecKeychainAddInternetPassword( NULL, strlen(server->host), server->host, 0, NULL, strlen(username), username, strlen(server->path), server->path, server->port, server->proto, kSecAuthenticationTypeDefault, strlen(secret), secret, &item ); if (status) { return get_error(status); } SecKeychainAttribute attribute; SecKeychainAttributeList attrs; attribute.tag = kSecLabelItemAttr; attribute.data = label; attribute.length = strlen(label); attrs.count = 1; attrs.attr = &attribute; status = SecKeychainItemModifyContent(item, &attrs, 0, NULL); if (status) { return get_error(status); } return NULL; } char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret) { char *tmp; SecKeychainItemRef item; OSStatus status = SecKeychainFindInternetPassword( NULL, strlen(server->host), server->host, 0, NULL, 0, NULL, strlen(server->path), server->path, server->port, server->proto, kSecAuthenticationTypeDefault, secret_l, (void **)&tmp, &item); if (status) { return get_error(status); } *secret = strdup(tmp); SecKeychainItemFreeContent(NULL, tmp); SecKeychainAttributeList list; SecKeychainAttribute attr; list.count = 1; list.attr = &attr; attr.tag = kSecAccountItemAttr; status = SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL); if (status) { return get_error(status); } *username = strdup(attr.data); *username_l = attr.length; SecKeychainItemFreeContent(&list, NULL); return NULL; } char *keychain_delete(struct Server *server) { SecKeychainItemRef item; OSStatus status = SecKeychainFindInternetPassword( NULL, strlen(server->host), server->host, 0, NULL, 0, NULL, strlen(server->path), server->path, server->port, server->proto, kSecAuthenticationTypeDefault, 0, NULL, &item); if (status) { return get_error(status); } status = SecKeychainItemDelete(item); if (status) { return get_error(status); } return NULL; } char * CFStringToCharArr(CFStringRef aString) { if (aString == NULL) { return NULL; } CFIndex length = CFStringGetLength(aString); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; char *buffer = (char *)malloc(maxSize); if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) { return buffer; } return NULL; } char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) { CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8); CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL); CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword); CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue); CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll); CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF); //Use this query dictionary CFTypeRef result= NULL; OSStatus status = SecItemCopyMatching( query, &result); CFRelease(credsLabelCF); //Ran a search and store the results in result if (status) { return get_error(status); } CFIndex numKeys = CFArrayGetCount(result); *paths = (char **) malloc((int)sizeof(char *)*numKeys); *accts = (char **) malloc((int)sizeof(char *)*numKeys); //result is of type CFArray for(CFIndex i=0; i */ import "C" import ( "errors" "net/url" "strconv" "strings" "unsafe" "github.com/docker/docker-credential-helpers/credentials" ) // errCredentialsNotFound is the specific error message returned by OS X // when the credentials are not in the keychain. const errCredentialsNotFound = "The specified item could not be found in the keychain." // Osxkeychain handles secrets using the OS X Keychain as store. type Osxkeychain struct{} // Add adds new credentials to the keychain. func (h Osxkeychain) Add(creds *credentials.Credentials) error { h.Delete(creds.ServerURL) s, err := splitServer(creds.ServerURL) if err != nil { return err } defer freeServer(s) label := C.CString(credentials.CredsLabel) defer C.free(unsafe.Pointer(label)) username := C.CString(creds.Username) defer C.free(unsafe.Pointer(username)) secret := C.CString(creds.Secret) defer C.free(unsafe.Pointer(secret)) errMsg := C.keychain_add(s, label, username, secret) if errMsg != nil { defer C.free(unsafe.Pointer(errMsg)) return errors.New(C.GoString(errMsg)) } return nil } // Delete removes credentials from the keychain. func (h Osxkeychain) Delete(serverURL string) error { s, err := splitServer(serverURL) if err != nil { return err } defer freeServer(s) errMsg := C.keychain_delete(s) if errMsg != nil { defer C.free(unsafe.Pointer(errMsg)) return errors.New(C.GoString(errMsg)) } return nil } // Get returns the username and secret to use for a given registry server URL. func (h Osxkeychain) Get(serverURL string) (string, string, error) { s, err := splitServer(serverURL) if err != nil { return "", "", err } defer freeServer(s) var usernameLen C.uint var username *C.char var secretLen C.uint var secret *C.char defer C.free(unsafe.Pointer(username)) defer C.free(unsafe.Pointer(secret)) errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret) if errMsg != nil { defer C.free(unsafe.Pointer(errMsg)) goMsg := C.GoString(errMsg) if goMsg == errCredentialsNotFound { return "", "", credentials.NewErrCredentialsNotFound() } return "", "", errors.New(goMsg) } user := C.GoStringN(username, C.int(usernameLen)) pass := C.GoStringN(secret, C.int(secretLen)) return user, pass, nil } // List returns the stored URLs and corresponding usernames. func (h Osxkeychain) List() (map[string]string, error) { credsLabelC := C.CString(credentials.CredsLabel) defer C.free(unsafe.Pointer(credsLabelC)) var pathsC **C.char defer C.free(unsafe.Pointer(pathsC)) var acctsC **C.char defer C.free(unsafe.Pointer(acctsC)) var listLenC C.uint errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC) if errMsg != nil { defer C.free(unsafe.Pointer(errMsg)) goMsg := C.GoString(errMsg) return nil, errors.New(goMsg) } defer C.freeListData(&pathsC, listLenC) defer C.freeListData(&acctsC, listLenC) var listLen int listLen = int(listLenC) pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen] acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen] //taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper resp := make(map[string]string) for i := 0; i < listLen; i++ { if C.GoString(pathTmp[i]) == "0" { continue } resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i]) } return resp, nil } func splitServer(serverURL string) (*C.struct_Server, error) { u, err := url.Parse(serverURL) if err != nil { 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 if u.Scheme != "https" { proto = C.kSecProtocolTypeHTTP } return &C.struct_Server{ proto: C.SecProtocolType(proto), host: C.CString(host), port: C.uint(port), path: C.CString(u.Path), }, nil } func freeServer(s *C.struct_Server) { C.free(unsafe.Pointer(s.host)) C.free(unsafe.Pointer(s.path)) } docker-credential-helpers-0.5.0/osxkeychain/osxkeychain_darwin.h000066400000000000000000000010211306162737600251270ustar00rootroot00000000000000#include struct Server { SecProtocolType proto; char *host; char *path; unsigned int port; }; char *keychain_add(struct Server *server, char *label, char *username, char *secret); char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret); char *keychain_delete(struct Server *server); char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l); void freeListData(char *** data, unsigned int length);docker-credential-helpers-0.5.0/osxkeychain/osxkeychain_darwin_test.go000066400000000000000000000027611306162737600263600ustar00rootroot00000000000000package osxkeychain import ( "github.com/docker/docker-credential-helpers/credentials" "testing" ) func TestOSXKeychainHelper(t *testing.T) { creds := &credentials.Credentials{ ServerURL: "https://foobar.docker.io:2376/v1", Username: "foobar", Secret: "foobarbaz", } creds1 := &credentials.Credentials{ ServerURL: "https://foobar.docker.io:2376/v2", Username: "foobarbaz", Secret: "foobar", } helper := Osxkeychain{} if err := helper.Add(creds); err != nil { t.Fatal(err) } username, secret, err := helper.Get(creds.ServerURL) if err != nil { t.Fatal(err) } if username != "foobar" { t.Fatalf("expected %s, got %s\n", "foobar", username) } if secret != "foobarbaz" { t.Fatalf("expected %s, got %s\n", "foobarbaz", secret) } auths, err := helper.List() if err != nil || len(auths) == 0 { t.Fatal(err) } helper.Add(creds1) defer helper.Delete(creds1.ServerURL) newauths, err := helper.List() if len(newauths)-len(auths) != 1 { if err == nil { t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths)) } t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err) } if err := helper.Delete(creds.ServerURL); err != nil { t.Fatal(err) } } func TestMissingCredentials(t *testing.T) { helper := Osxkeychain{} _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") if !credentials.IsErrCredentialsNotFound(err) { t.Fatalf("expected ErrCredentialsNotFound, got %v", err) } } docker-credential-helpers-0.5.0/secretservice/000077500000000000000000000000001306162737600214145ustar00rootroot00000000000000docker-credential-helpers-0.5.0/secretservice/cmd/000077500000000000000000000000001306162737600221575ustar00rootroot00000000000000docker-credential-helpers-0.5.0/secretservice/cmd/main_linux.go000066400000000000000000000003241306162737600246500ustar00rootroot00000000000000package main import ( "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/secretservice" ) func main() { credentials.Serve(secretservice.Secretservice{}) } docker-credential-helpers-0.5.0/secretservice/secretservice_linux.c000066400000000000000000000112451306162737600256500ustar00rootroot00000000000000#include #include #include "secretservice_linux.h" const SecretSchema *docker_get_schema(void) { static const SecretSchema docker_schema = { "io.docker.Credentials", SECRET_SCHEMA_NONE, { { "label", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "username", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "NULL", 0 }, } }; return &docker_schema; } GError *add(char *label, char *server, char *username, char *secret) { GError *err = NULL; secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT, server, secret, NULL, &err, "label", label, "server", server, "username", username, "docker_cli", "1", NULL); return err; } GError *delete(char *server) { GError *err = NULL; secret_password_clear_sync(DOCKER_SCHEMA, NULL, &err, "server", server, "docker_cli", "1", NULL); if (err != NULL) return err; return NULL; } char *get_attribute(const char *attribute, SecretItem *item) { GHashTable *attributes; GHashTableIter iter; gchar *value, *key; attributes = secret_item_get_attributes(item); g_hash_table_iter_init(&iter, attributes); while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) { if (strncmp(key, attribute, strlen(key)) == 0) return (char *)value; } g_hash_table_unref(attributes); return NULL; } GError *get(char *server, char **username, char **secret) { GError *err = NULL; GHashTable *attributes; SecretService *service; GList *items, *l; SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK; SecretValue *secretValue; gsize length; gchar *value; attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert(attributes, g_strdup("server"), g_strdup(server)); g_hash_table_insert(attributes, g_strdup("docker_cli"), g_strdup("1")); service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); if (err == NULL) { items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err); if (err == NULL) { for (l = items; l != NULL; l = g_list_next(l)) { value = secret_item_get_schema_name(l->data); if (strncmp(value, "io.docker.Credentials", strlen(value)) != 0) { g_free(value); continue; } g_free(value); secretValue = secret_item_get_secret(l->data); if (secret != NULL) { *secret = strdup(secret_value_get(secretValue, &length)); secret_value_unref(secretValue); } *username = get_attribute("username", l->data); } g_list_free_full(items, g_object_unref); } g_object_unref(service); } g_hash_table_unref(attributes); if (err != NULL) { return err; } return NULL; } GError *list(char *ref_label, char *** paths, char *** accts, unsigned int *list_l) { GList *items; GError *err = NULL; SecretService *service; SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK; GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); // List credentials with the right label only g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label)); service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); if (err != NULL) { return err; } items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err); int numKeys = g_list_length(items); if (err != NULL) { return err; } char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys); char **tmp_accts = (char **) calloc(1,(int)sizeof(char *)*numKeys); // items now contains our keys from the gnome keyring // we will now put it in our two lists to return it to go GList *current; int listNumber = 0; for(current = items; current!=NULL; current = current->next) { char *pathTmp = secret_item_get_label(current->data); // you cannot have a key without a label in the gnome keyring char *acctTmp = get_attribute("username",current->data); if (acctTmp==NULL) { acctTmp = "account not defined"; } tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1)); tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1)); memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1)); memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1)); listNumber = listNumber + 1; } *paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber); *accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber); *list_l = listNumber; return NULL; } void freeListData(char *** data, unsigned int length) { int i; for(i=0; i */ import "C" import ( "errors" "unsafe" "github.com/docker/docker-credential-helpers/credentials" ) // Secretservice handles secrets using Linux secret-service as a store. type Secretservice struct{} // Add adds new credentials to the keychain. func (h Secretservice) Add(creds *credentials.Credentials) error { if creds == nil { return errors.New("missing credentials") } credsLabel := C.CString(credentials.CredsLabel) defer C.free(unsafe.Pointer(credsLabel)) server := C.CString(creds.ServerURL) defer C.free(unsafe.Pointer(server)) username := C.CString(creds.Username) defer C.free(unsafe.Pointer(username)) secret := C.CString(creds.Secret) defer C.free(unsafe.Pointer(secret)) if err := C.add(credsLabel, server, username, secret); err != nil { defer C.g_error_free(err) errMsg := (*C.char)(unsafe.Pointer(err.message)) return errors.New(C.GoString(errMsg)) } return nil } // Delete removes credentials from the store. func (h Secretservice) Delete(serverURL string) error { if serverURL == "" { return errors.New("missing server url") } server := C.CString(serverURL) defer C.free(unsafe.Pointer(server)) if err := C.delete(server); err != nil { defer C.g_error_free(err) errMsg := (*C.char)(unsafe.Pointer(err.message)) return errors.New(C.GoString(errMsg)) } return nil } // Get returns the username and secret to use for a given registry server URL. func (h Secretservice) Get(serverURL string) (string, string, error) { if serverURL == "" { return "", "", errors.New("missing server url") } var username *C.char defer C.free(unsafe.Pointer(username)) var secret *C.char defer C.free(unsafe.Pointer(secret)) server := C.CString(serverURL) defer C.free(unsafe.Pointer(server)) err := C.get(server, &username, &secret) if err != nil { defer C.g_error_free(err) errMsg := (*C.char)(unsafe.Pointer(err.message)) return "", "", errors.New(C.GoString(errMsg)) } user := C.GoString(username) pass := C.GoString(secret) if pass == "" { return "", "", credentials.NewErrCredentialsNotFound() } return user, pass, nil } // List returns the stored URLs and corresponding usernames for a given credentials label func (h Secretservice) List() (map[string]string, error) { credsLabelC := C.CString(credentials.CredsLabel) defer C.free(unsafe.Pointer(credsLabelC)) var pathsC **C.char defer C.free(unsafe.Pointer(pathsC)) var acctsC **C.char defer C.free(unsafe.Pointer(acctsC)) var listLenC C.uint err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC) if err != nil { defer C.free(unsafe.Pointer(err)) return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library") } defer C.freeListData(&pathsC, listLenC) defer C.freeListData(&acctsC, listLenC) resp := make(map[string]string) listLen := int(listLenC) if listLen == 0 { return resp, nil } pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen] acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen] for i := 0; i < listLen; i++ { resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i]) } return resp, nil } docker-credential-helpers-0.5.0/secretservice/secretservice_linux.h000066400000000000000000000007621306162737600256570ustar00rootroot00000000000000#define SECRET_WITH_UNSTABLE 1 #define SECRET_API_SUBJECT_TO_CHANGE 1 #include const SecretSchema *docker_get_schema(void) G_GNUC_CONST; #define DOCKER_SCHEMA docker_get_schema() GError *add(char *label, char *server, char *username, char *secret); GError *delete(char *server); GError *get(char *server, char **username, char **secret); GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l); void freeListData(char *** data, unsigned int length); docker-credential-helpers-0.5.0/secretservice/secretservice_linux_test.go000066400000000000000000000043361306162737600270750ustar00rootroot00000000000000package secretservice import ( "strings" "testing" "github.com/docker/docker-credential-helpers/credentials" ) func TestSecretServiceHelper(t *testing.T) { t.Skip("test requires gnome-keyring but travis CI doesn't have it") creds := &credentials.Credentials{ ServerURL: "https://foobar.docker.io:2376/v1", Username: "foobar", Secret: "foobarbaz", } helper := Secretservice{} // Check how many docker credentials we have when starting the test old_auths, err := helper.List() if err != nil { t.Fatal(err) } // If any docker credentials with the tests values we are providing, we // remove them as they probably come from a previous failed test for k, v := range old_auths { if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 { if err := helper.Delete(creds.ServerURL); err != nil { t.Fatal(err) } } } // Check again how many docker credentials we have when starting the test old_auths, err = helper.List() if err != nil { t.Fatal(err) } // Add new credentials if err := helper.Add(creds); err != nil { t.Fatal(err) } // Verify that it is inside the secret service store username, secret, err := helper.Get(creds.ServerURL) if err != nil { t.Fatal(err) } if username != "foobar" { t.Fatalf("expected %s, got %s\n", "foobar", username) } if secret != "foobarbaz" { t.Fatalf("expected %s, got %s\n", "foobarbaz", secret) } // We should have one more credential than before adding new_auths, err := helper.List() if err != nil || (len(new_auths)-len(old_auths) != 1) { t.Fatal(err) } old_auths = new_auths // Deleting the credentials associated to current server url should succeed if err := helper.Delete(creds.ServerURL); err != nil { t.Fatal(err) } // We should have one less credential than before deleting new_auths, err = helper.List() if err != nil || (len(old_auths)-len(new_auths) != 1) { t.Fatal(err) } } func TestMissingCredentials(t *testing.T) { t.Skip("test requires gnome-keyring but travis CI doesn't have it") helper := Secretservice{} _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") if !credentials.IsErrCredentialsNotFound(err) { t.Fatalf("expected ErrCredentialsNotFound, got %v", err) } } docker-credential-helpers-0.5.0/wincred/000077500000000000000000000000001306162737600202015ustar00rootroot00000000000000docker-credential-helpers-0.5.0/wincred/cmd/000077500000000000000000000000001306162737600207445ustar00rootroot00000000000000docker-credential-helpers-0.5.0/wincred/cmd/main_windows.go000066400000000000000000000003021306162737600237640ustar00rootroot00000000000000package main import ( "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/wincred" ) func main() { credentials.Serve(wincred.Wincred{}) } docker-credential-helpers-0.5.0/wincred/wincred_windows.go000066400000000000000000000032661306162737600237440ustar00rootroot00000000000000package wincred import ( "bytes" winc "github.com/danieljoos/wincred" "github.com/docker/docker-credential-helpers/credentials" "strings" ) // Wincred handles secrets using the Windows credential service. type Wincred struct{} // Add adds new credentials to the windows credentials manager. func (h Wincred) Add(creds *credentials.Credentials) error { g := winc.NewGenericCredential(creds.ServerURL) g.UserName = creds.Username g.CredentialBlob = []byte(creds.Secret) g.Persist = winc.PersistLocalMachine g.Attributes = []winc.CredentialAttribute{{"label", []byte(credentials.CredsLabel)}} return g.Write() } // Delete removes credentials from the windows credentials manager. func (h Wincred) Delete(serverURL string) error { g, err := winc.GetGenericCredential(serverURL) if g == nil { return nil } if err != nil { return err } return g.Delete() } // Get retrieves credentials from the windows credentials manager. func (h Wincred) Get(serverURL string) (string, string, error) { g, _ := winc.GetGenericCredential(serverURL) if g == nil { return "", "", credentials.NewErrCredentialsNotFound() } return g.UserName, string(g.CredentialBlob), nil } // List returns the stored URLs and corresponding usernames for a given credentials label. func (h Wincred) List() (map[string]string, error) { creds, err := winc.List() if err != nil { return nil, err } resp := make(map[string]string) for i := range creds { attrs := creds[i].Attributes for _, attr := range attrs { if strings.Compare(attr.Keyword, "label") == 0 && bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 { resp[creds[i].TargetName] = creds[i].UserName } } } return resp, nil } docker-credential-helpers-0.5.0/wincred/wincred_windows_test.go000066400000000000000000000042241306162737600247760ustar00rootroot00000000000000package wincred import ( "testing" "strings" "github.com/docker/docker-credential-helpers/credentials" ) func TestWinCredHelper(t *testing.T) { creds := &credentials.Credentials{ ServerURL: "https://foobar.docker.io:2376/v1", Username: "foobar", Secret: "foobarbaz", } creds1 := &credentials.Credentials{ ServerURL: "https://foobar.docker.io:2376/v2", Username: "foobarbaz", Secret: "foobar", } helper := Wincred{} // check for and remove remaining credentials from previous fail tests oldauths, err := helper.List() if err != nil { t.Fatal(err) } for k, v := range oldauths { if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 { if err := helper.Delete(creds.ServerURL); err != nil { t.Fatal(err) } } else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 { if err := helper.Delete(creds1.ServerURL); err != nil { t.Fatal(err) } } } // recount for credentials oldauths, err = helper.List() if err != nil { t.Fatal(err) } if err := helper.Add(creds); err != nil { t.Fatal(err) } username, secret, err := helper.Get(creds.ServerURL) if err != nil { t.Fatal(err) } if username != "foobar" { t.Fatalf("expected %s, got %s\n", "foobar", username) } if secret != "foobarbaz" { t.Fatalf("expected %s, got %s\n", "foobarbaz", secret) } auths, err := helper.List() if err != nil || len(auths) - len(oldauths) != 1 { t.Fatal(err) } helper.Add(creds1) defer helper.Delete(creds1.ServerURL) newauths, err := helper.List() if err != nil { t.Fatal(err) } if len(newauths)-len(auths) != 1 { if err == nil { t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths)) } t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err) } if err := helper.Delete(creds.ServerURL); err != nil { t.Fatal(err) } } func TestMissingCredentials(t *testing.T) { helper := Wincred{} _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") if !credentials.IsErrCredentialsNotFound(err) { t.Fatalf("expected ErrCredentialsNotFound, got %v", err) } }