From a96948acb336930a60230c18bcfeaee11231da81 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 2 Mar 2016 16:38:17 +0100 Subject: [PATCH] secretservice: add D-Bus secret service Signed-off-by: Antonio Murdaca --- .travis.yml | 17 +++- Makefile | 34 +++++-- README.md | 1 + ci/{before_deploy.sh => before_deploy_osx.sh} | 0 ci/before_script_linux.sh | 4 + secretservice/cmd/main_linux.go | 10 ++ secretservice/secretservice_linux.c | 98 +++++++++++++++++++ secretservice/secretservice_linux.go | 84 ++++++++++++++++ secretservice/secretservice_linux.h | 11 +++ secretservice/secretservice_linux_test.go | 49 ++++++++++ 10 files changed, 295 insertions(+), 13 deletions(-) rename ci/{before_deploy.sh => before_deploy_osx.sh} (100%) create mode 100644 ci/before_script_linux.sh create mode 100644 secretservice/cmd/main_linux.go create mode 100644 secretservice/secretservice_linux.c create mode 100644 secretservice/secretservice_linux.go create mode 100644 secretservice/secretservice_linux.h create mode 100644 secretservice/secretservice_linux_test.go diff --git a/.travis.yml b/.travis.yml index 4373e39..0695b8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,28 @@ --- # 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: - - sh ci/before_deploy.sh + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sh ci/before_deploy_osx.sh; fi deploy: provider: releases @@ -25,7 +34,7 @@ # deploy when a new tag is pushed on: tags: true - + branches: only: # Pushes and PR to the master branch diff --git a/Makefile b/Makefile index fb30b73..58128f3 100644 --- a/Makefile +++ b/Makefile @@ -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` diff --git a/README.md b/README.md index 9251f47..661a705 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ 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 diff --git a/ci/before_deploy.sh b/ci/before_deploy_osx.sh similarity index 100% rename from ci/before_deploy.sh rename to ci/before_deploy_osx.sh diff --git a/ci/before_script_linux.sh b/ci/before_script_linux.sh new file mode 100644 index 0000000..eb3ac19 --- /dev/null +++ b/ci/before_script_linux.sh @@ -0,0 +1,4 @@ +set -ex + +sh -e /etc/init.d/xvfb start +sleep 3 # give xvfb some time to start diff --git a/secretservice/cmd/main_linux.go b/secretservice/cmd/main_linux.go new file mode 100644 index 0000000..bb8be9f --- /dev/null +++ b/secretservice/cmd/main_linux.go @@ -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()) +} diff --git a/secretservice/secretservice_linux.c b/secretservice/secretservice_linux.c new file mode 100644 index 0000000..6fd99d1 --- /dev/null +++ b/secretservice/secretservice_linux.c @@ -0,0 +1,98 @@ +#include +#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 *password) { + GError *err = NULL; + + secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT, + server, password, 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 **password) { + GError *err = NULL; + GHashTable *attributes; + SecretService *service; + GList *items, *l; + SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK; + SecretValue *secret; + 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); + secret = secret_item_get_secret(l->data); + if (secret != NULL) { + *password = strdup(secret_value_get(secret, &length)); + secret_value_unref(secret); + } + *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; +} diff --git a/secretservice/secretservice_linux.go b/secretservice/secretservice_linux.go new file mode 100644 index 0000000..9fb8c05 --- /dev/null +++ b/secretservice/secretservice_linux.go @@ -0,0 +1,84 @@ +package secretservice + +/* +#cgo pkg-config: libsecret-1 + +#include "secretservice_linux.h" +#include +*/ +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)) + password := C.CString(creds.Password) + defer C.free(unsafe.Pointer(password)) + + if err := C.add(server, username, password); 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 password 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 password *C.char + defer C.free(unsafe.Pointer(password)) + server := C.CString(serverURL) + defer C.free(unsafe.Pointer(server)) + + err := C.get(server, &username, &password) + 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(password) + if pass == "" { + return "", "", credentials.ErrCredentialsNotFound + } + return user, pass, nil +} diff --git a/secretservice/secretservice_linux.h b/secretservice/secretservice_linux.h new file mode 100644 index 0000000..1d3d386 --- /dev/null +++ b/secretservice/secretservice_linux.h @@ -0,0 +1,11 @@ +#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 *server, char *username, char *password); +GError *delete(char *server); +GError *get(char *server, char **username, char **password); diff --git a/secretservice/secretservice_linux_test.go b/secretservice/secretservice_linux_test.go new file mode 100644 index 0000000..c7d028b --- /dev/null +++ b/secretservice/secretservice_linux_test.go @@ -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", + Password: "foobarbaz", + } + + helper := New() + if err := helper.Add(creds); err != nil { + t.Fatal(err) + } + + username, password, err := helper.Get(creds.ServerURL) + if err != nil { + t.Fatal(err) + } + + if username != "foobar" { + t.Fatalf("expected %s, got %s\n", "foobar", username) + } + + if password != "foobarbaz" { + t.Fatalf("expected %s, got %s\n", "foobarbaz", password) + } + + 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) + } +}