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

Compare commits

...

7 Commits

Author SHA1 Message Date
David Calavera e68b300c17 Release v0.2.0.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-03-14 12:30:40 -04:00
David Calavera 7d02ef740b Merge pull request #8 from calavera/secret_name
Move away from password as a name. We store secrets.
2016-03-14 09:27:50 -07:00
David Calavera bcc242e1ad Merge pull request #9 from calavera/linux_release
Release Linux secret service binary.
2016-03-09 16:26:30 -08:00
David Calavera 8727ffc77b Release Linux secret service binary.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-03-09 19:18:24 -05:00
David Calavera 2275377a31 Move away from password as a name. We store secrets.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-03-09 16:16:01 -05:00
David Calavera 0fb2225199 Merge pull request #7 from runcom/secretservice
secretservice: add D-Bus secret service
2016-03-07 09:52:48 -08:00
Antonio Murdaca a96948acb3 secretservice: add D-Bus secret service
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2016-03-07 15:03:35 +01:00
20 changed files with 354 additions and 54 deletions
+17 -4
View File
@@ -1,15 +1,24 @@
---
# See appveyor.yml for windows build.
sudo: false
sudo: required
language: go
dist: trusty
os:
- linux
- osx
notifications:
email: false
go:
- 1.6
install: make deps
before_script: make validate
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:
@@ -19,13 +28,17 @@
provider: releases
api_key:
secure: "cGs5cao/MeVQVnum+Pr/Tpv+w83NsqGVS3wxvi3LYEf2ON4Kkmtd+Alwi0YFkGPJmSY0jZOct8NVK/M70qSnIU4l+AAq9+3KSMv23u4xrmy2sQog3AF+Ve3Rac+iYwZHOWwGs9I67CSuVv0vjJNVsDsTVefc25lHJImjRvXIS4p9xYzRPeUDCoqAo/QMVE+vFiMyxydsvt8fhd0gZCjPYWEpyHe9tjZ1tr1HsHZKFAjVb6AmF45d8rvadPoVUuLaOtr35wDC3XRKEvCZUefQpwLkrNj7j2L1rVGlY1xTE2APpLtvfd7R1Mx6kSfS1Gm3Pwcv3mugadXIhecL0lsdnU+BANjX3VUiv4ryzTPbsge966mv9ZQYwAzgCQTWRtMNJqsAnPZTeAkiOntd+HMQbPpxljOxv1sjDPY+EIZesyB3yQRJI8vMxqFcAjxeRyLcBqEnRFC2nd/Ln0KZ7ZFu16FcpNqRojdBayyypuXKqAiBNwtp4ti/65x8eHfBJuNjJtNZkRsJEYam4CYMRLxds9plKQfkaZ8045PKpyXO8fMpUhrfqSVID4IrYvD+io6XoXtdR4Lk6isZ2EgrjdrqgdG70S5lwKihL4iAi2F2ZCWhngFhkeNVOZunEWE6qZMk5wKODajR9sixGDApGPZQVojHwCNRGILZaHZ39JCIj3s="
file: docker-credential-osxkeychain-${TRAVIS_TAG}-amd64.tar.gz
# 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
+5
View File
@@ -4,6 +4,11 @@ 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.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.
+25 -9
View File
@@ -1,4 +1,6 @@
.PHONY: all deps osxkeychain test validate wincred
.PHONY: all deps osxkeychain secretservice test validate wincred
TRAVIS_OS_NAME ?= linux
all: test
@@ -9,15 +11,29 @@ osxkeychain:
mkdir -p bin
go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
test:
# tests all packages except vendor
go test -v `go list ./... | grep -v /vendor/`
validate:
go vet ./credentials ./osxkeychain
golint `go list ./... | grep -v /vendor/`
gofmt -s -l `ls **/*.go | grep -v vendor`
secretservice:
mkdir -p bin
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
wincred:
mkdir -p bin
go build -o bin/docker-credential-wincred 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`
+2 -1
View File
@@ -38,13 +38,14 @@ Set the `credsStore` option in your `.docker/config.json` file with the suffix o
### Available programs
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store.
1. secretservice: Provides a helper to use the D-Bus secret service as credentials store.
2. 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 three valid values:
- `store`: Adds credentials to the keychain. The payload in the standard input is a JSON document with `ServerURL`, `Username` and `Password`.
- `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`.
+12 -3
View File
@@ -1,6 +1,15 @@
set -ex
mkdir bin
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
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
+4
View File
@@ -0,0 +1,4 @@
set -ex
sh -e /etc/init.d/xvfb start
sleep 3 # give xvfb some time to start
+3 -3
View File
@@ -12,7 +12,7 @@ import (
type credentialsGetResponse struct {
Username string
Password string
Secret string
}
// Serve initializes the credentials helper and parses the action argument.
@@ -73,14 +73,14 @@ func get(helper Helper, reader io.Reader, writer io.Writer) error {
serverURL := strings.TrimSpace(buffer.String())
username, password, err := helper.Get(serverURL)
username, secret, err := helper.Get(serverURL)
if err != nil {
return err
}
resp := credentialsGetResponse{
Username: username,
Password: password,
Secret: secret,
}
buffer.Reset()
+8 -8
View File
@@ -33,7 +33,7 @@ func (m *memoryStore) Get(serverURL string) (string, string, error) {
if !ok {
return "", "", fmt.Errorf("creds not found for %s", serverURL)
}
return c.Username, c.Password, nil
return c.Username, c.Secret, nil
}
func TestStore(t *testing.T) {
@@ -41,7 +41,7 @@ func TestStore(t *testing.T) {
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
Password: "bar",
Secret: "bar",
}
b, err := json.Marshal(creds)
if err != nil {
@@ -63,8 +63,8 @@ func TestStore(t *testing.T) {
t.Fatalf("expected username foo, got %s\n", c.Username)
}
if c.Password != "bar" {
t.Fatalf("expected username bar, got %s\n", c.Password)
if c.Secret != "bar" {
t.Fatalf("expected username bar, got %s\n", c.Secret)
}
}
@@ -73,7 +73,7 @@ func TestGet(t *testing.T) {
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
Password: "bar",
Secret: "bar",
}
b, err := json.Marshal(creds)
if err != nil {
@@ -105,8 +105,8 @@ func TestGet(t *testing.T) {
t.Fatalf("expected username foo, got %s\n", c.Username)
}
if c.Password != "bar" {
t.Fatalf("expected username bar, got %s\n", c.Password)
if c.Secret != "bar" {
t.Fatalf("expected username bar, got %s\n", c.Secret)
}
}
@@ -115,7 +115,7 @@ func TestErase(t *testing.T) {
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
Password: "bar",
Secret: "bar",
}
b, err := json.Marshal(creds)
if err != nil {
+1 -1
View File
@@ -6,7 +6,7 @@ import "errors"
type Credentials struct {
ServerURL string
Username string
Password string
Secret string
}
// Helper is the interface a credentials store helper must implement.
+5 -5
View File
@@ -10,7 +10,7 @@ char *get_error(OSStatus status) {
return buf;
}
char *keychain_add(struct Server *server, char *username, char *password) {
char *keychain_add(struct Server *server, char *username, char *secret) {
OSStatus status = SecKeychainAddInternetPassword(
NULL,
strlen(server->host), server->host,
@@ -20,7 +20,7 @@ char *keychain_add(struct Server *server, char *username, char *password) {
server->port,
server->proto,
kSecAuthenticationTypeDefault,
strlen(password), password,
strlen(secret), secret,
NULL
);
if (status) {
@@ -29,7 +29,7 @@ char *keychain_add(struct Server *server, char *username, char *password) {
return NULL;
}
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *password_l, char **password) {
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret) {
char *tmp;
SecKeychainItemRef item;
@@ -42,14 +42,14 @@ char *keychain_get(struct Server *server, unsigned int *username_l, char **usern
server->port,
server->proto,
kSecAuthenticationTypeDefault,
password_l, (void **)&tmp,
secret_l, (void **)&tmp,
&item);
if (status) {
return get_error(status);
}
*password = strdup(tmp);
*secret = strdup(tmp);
SecKeychainItemFreeContent(NULL, tmp);
SecKeychainAttributeList list;
+9 -9
View File
@@ -39,10 +39,10 @@ func (h osxkeychain) Add(creds *credentials.Credentials) error {
username := C.CString(creds.Username)
defer C.free(unsafe.Pointer(username))
password := C.CString(creds.Password)
defer C.free(unsafe.Pointer(password))
secret := C.CString(creds.Secret)
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_add(s, username, password)
errMsg := C.keychain_add(s, username, secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
@@ -68,7 +68,7 @@ func (h osxkeychain) Delete(serverURL string) error {
return nil
}
// Get returns the username and password to use for a given registry server URL.
// 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 {
@@ -78,12 +78,12 @@ func (h osxkeychain) Get(serverURL string) (string, string, error) {
var usernameLen C.uint
var username *C.char
var passwordLen C.uint
var password *C.char
var secretLen C.uint
var secret *C.char
defer C.free(unsafe.Pointer(username))
defer C.free(unsafe.Pointer(password))
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_get(s, &usernameLen, &username, &passwordLen, &password)
errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
goMsg := C.GoString(errMsg)
@@ -96,7 +96,7 @@ func (h osxkeychain) Get(serverURL string) (string, string, error) {
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(password, C.int(passwordLen))
pass := C.GoStringN(secret, C.int(secretLen))
return user, pass, nil
}
+2 -2
View File
@@ -7,6 +7,6 @@ struct Server {
unsigned int port;
};
char *keychain_add(struct Server *server, char *username, char *password);
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *password_l, char **password);
char *keychain_add(struct Server *server, 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);
+4 -4
View File
@@ -10,7 +10,7 @@ func TestOSXKeychainHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "foobar",
Password: "foobarbaz",
Secret: "foobarbaz",
}
helper := New()
@@ -18,7 +18,7 @@ func TestOSXKeychainHelper(t *testing.T) {
t.Fatal(err)
}
username, password, err := helper.Get(creds.ServerURL)
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
@@ -27,8 +27,8 @@ func TestOSXKeychainHelper(t *testing.T) {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if password != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", password)
if secret != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
}
if err := helper.Delete(creds.ServerURL); err != nil {
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker-credential-helpers/secretservice"
)
func main() {
credentials.Serve(secretservice.New())
}
+98
View File
@@ -0,0 +1,98 @@
#include <string.h>
#include "secretservice_linux.h"
const SecretSchema *docker_get_schema(void)
{
static const SecretSchema docker_schema = {
"io.docker.Credentials", SECRET_SCHEMA_NONE,
{
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "NULL", 0 },
}
};
return &docker_schema;
}
GError *add(char *server, char *username, char *secret) {
GError *err = NULL;
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
server, secret, NULL, &err,
"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_username(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, "username", 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, NULL, 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_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;
}
+84
View File
@@ -0,0 +1,84 @@
package secretservice
/*
#cgo pkg-config: libsecret-1
#include "secretservice_linux.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"unsafe"
"github.com/docker/docker-credential-helpers/credentials"
)
type secretservice struct{}
// New creates a new secretservice.
func New() credentials.Helper {
return secretservice{}
}
// Add adds new credentials to the keychain.
func (h secretservice) Add(creds *credentials.Credentials) error {
if creds == nil {
return errors.New("missing credentials")
}
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(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 keychain.
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.ErrCredentialsNotFound
}
return user, pass, nil
}
+11
View File
@@ -0,0 +1,11 @@
#define SECRET_WITH_UNSTABLE 1
#define SECRET_API_SUBJECT_TO_CHANGE 1
#include <libsecret/secret.h>
const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
#define DOCKER_SCHEMA docker_get_schema()
GError *add(char *server, char *username, char *secret);
GError *delete(char *server);
GError *get(char *server, char **username, char **secret);
+49
View File
@@ -0,0 +1,49 @@
package secretservice
import (
"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 := New()
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)
}
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
}
func TestMissingCredentials(t *testing.T) {
t.Skip("test requires gnome-keyring but travis CI doesn't have it")
helper := New()
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if err != credentials.ErrCredentialsNotFound {
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err)
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ func New() credentials.Helper {
func (h wincred) Add(creds *credentials.Credentials) error {
g := winc.NewGenericCredential(creds.ServerURL)
g.UserName = creds.Username
g.CredentialBlob = []byte(creds.Password)
g.CredentialBlob = []byte(creds.Secret)
g.Persist = winc.PersistLocalMachine
return g.Write()
}
+4 -4
View File
@@ -10,7 +10,7 @@ func TestWinCredHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "foobar",
Password: "foobarbaz",
Secret: "foobarbaz",
}
helper := New()
@@ -18,7 +18,7 @@ func TestWinCredHelper(t *testing.T) {
t.Fatal(err)
}
username, password, err := helper.Get(creds.ServerURL)
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
@@ -27,8 +27,8 @@ func TestWinCredHelper(t *testing.T) {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if password != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", password)
if secret != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
}
if err := helper.Delete(creds.ServerURL); err != nil {