Skip to content

Commit a96948a

Browse files
committed
secretservice: add D-Bus secret service
Signed-off-by: Antonio Murdaca <[email protected]>
1 parent 900f815 commit a96948a

10 files changed

+294
-12
lines changed

.travis.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
---
22
# See appveyor.yml for windows build.
3-
sudo: false
3+
sudo: required
44
language: go
5+
dist: trusty
56
os:
7+
- linux
68
- osx
79
notifications:
810
email: false
911
go:
1012
- 1.6
1113
install: make deps
12-
before_script: make validate
14+
addons:
15+
apt:
16+
packages:
17+
- libsecret-1-dev
18+
before_script:
19+
- "export DISPLAY=:99.0"
20+
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh ci/before_script_linux.sh; fi
21+
- make validate
1322
script: make test
1423

1524
before_deploy:
16-
- sh ci/before_deploy.sh
25+
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sh ci/before_deploy_osx.sh; fi
1726

1827
deploy:
1928
provider: releases
@@ -25,7 +34,7 @@
2534
# deploy when a new tag is pushed
2635
on:
2736
tags: true
28-
37+
2938
branches:
3039
only:
3140
# Pushes and PR to the master branch

Makefile

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
.PHONY: all deps osxkeychain test validate wincred
1+
.PHONY: all deps osxkeychain secretservice test validate wincred
2+
3+
TRAVIS_OS_NAME ?= linux
24

35
all: test
46

@@ -9,15 +11,29 @@ osxkeychain:
911
mkdir -p bin
1012
go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
1113

14+
secretservice:
15+
mkdir -p bin
16+
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
17+
18+
wincred:
19+
mkdir -p bin
20+
go build -o bin/docker-credential-wincred wincred/cmd/main_windows.go
21+
1222
test:
1323
# tests all packages except vendor
1424
go test -v `go list ./... | grep -v /vendor/`
1525

16-
validate:
17-
go vet ./credentials ./osxkeychain
18-
golint `go list ./... | grep -v /vendor/`
19-
gofmt -s -l `ls **/*.go | grep -v vendor`
26+
vet: vet_$(TRAVIS_OS_NAME)
27+
go vet ./credentials
2028

21-
wincred:
22-
mkdir -p bin
23-
go build -o bin/docker-credential-wincred wincred/cmd/main_windows.go
29+
vet_osx:
30+
go vet ./osxkeychain
31+
32+
vet_linux:
33+
go vet ./secretservice
34+
35+
validate: vet
36+
for p in `go list ./... | grep -v /vendor/`; do \
37+
golint $$p ; \
38+
done
39+
gofmt -s -l `ls **/*.go | grep -v vendor`

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Set the `credsStore` option in your `.docker/config.json` file with the suffix o
3838
### Available programs
3939

4040
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store.
41+
1. secretservice: Provides a helper to use the D-Bus secret service as credentials store.
4142
2. wincred: Provides a helper to use Windows credentials manager as store.
4243

4344
## Development
File renamed without changes.

ci/before_script_linux.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
set -ex
2+
3+
sh -e /etc/init.d/xvfb start
4+
sleep 3 # give xvfb some time to start

secretservice/cmd/main_linux.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import (
4+
"github.com/docker/docker-credential-helpers/credentials"
5+
"github.com/docker/docker-credential-helpers/secretservice"
6+
)
7+
8+
func main() {
9+
credentials.Serve(secretservice.New())
10+
}

secretservice/secretservice_linux.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include <string.h>
2+
#include "secretservice_linux.h"
3+
4+
const SecretSchema *docker_get_schema(void)
5+
{
6+
static const SecretSchema docker_schema = {
7+
"io.docker.Credentials", SECRET_SCHEMA_NONE,
8+
{
9+
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
10+
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
11+
{ "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING },
12+
{ "NULL", 0 },
13+
}
14+
};
15+
return &docker_schema;
16+
}
17+
18+
GError *add(char *server, char *username, char *password) {
19+
GError *err = NULL;
20+
21+
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
22+
server, password, NULL, &err,
23+
"server", server,
24+
"username", username,
25+
"docker_cli", "1",
26+
NULL);
27+
return err;
28+
}
29+
30+
GError *delete(char *server) {
31+
GError *err = NULL;
32+
33+
secret_password_clear_sync(DOCKER_SCHEMA, NULL, &err,
34+
"server", server,
35+
"docker_cli", "1",
36+
NULL);
37+
if (err != NULL)
38+
return err;
39+
return NULL;
40+
}
41+
42+
char *get_username(SecretItem *item) {
43+
GHashTable *attributes;
44+
GHashTableIter iter;
45+
gchar *value, *key;
46+
47+
attributes = secret_item_get_attributes(item);
48+
g_hash_table_iter_init(&iter, attributes);
49+
while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) {
50+
if (strncmp(key, "username", strlen(key)) == 0)
51+
return (char *)value;
52+
}
53+
g_hash_table_unref(attributes);
54+
return NULL;
55+
}
56+
57+
GError *get(char *server, char **username, char **password) {
58+
GError *err = NULL;
59+
GHashTable *attributes;
60+
SecretService *service;
61+
GList *items, *l;
62+
SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
63+
SecretValue *secret;
64+
gsize length;
65+
gchar *value;
66+
67+
attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
68+
g_hash_table_insert(attributes, g_strdup("server"), g_strdup(server));
69+
g_hash_table_insert(attributes, g_strdup("docker_cli"), g_strdup("1"));
70+
71+
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
72+
if (err == NULL) {
73+
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
74+
if (err == NULL) {
75+
for (l = items; l != NULL; l = g_list_next(l)) {
76+
value = secret_item_get_schema_name(l->data);
77+
if (strncmp(value, "io.docker.Credentials", strlen(value)) != 0) {
78+
g_free(value);
79+
continue;
80+
}
81+
g_free(value);
82+
secret = secret_item_get_secret(l->data);
83+
if (secret != NULL) {
84+
*password = strdup(secret_value_get(secret, &length));
85+
secret_value_unref(secret);
86+
}
87+
*username = get_username(l->data);
88+
}
89+
g_list_free_full(items, g_object_unref);
90+
}
91+
g_object_unref(service);
92+
}
93+
g_hash_table_unref(attributes);
94+
if (err != NULL) {
95+
return err;
96+
}
97+
return NULL;
98+
}

secretservice/secretservice_linux.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package secretservice
2+
3+
/*
4+
#cgo pkg-config: libsecret-1
5+
6+
#include "secretservice_linux.h"
7+
#include <stdlib.h>
8+
*/
9+
import "C"
10+
import (
11+
"errors"
12+
"unsafe"
13+
14+
"github.com/docker/docker-credential-helpers/credentials"
15+
)
16+
17+
type secretservice struct{}
18+
19+
// New creates a new secretservice.
20+
func New() credentials.Helper {
21+
return secretservice{}
22+
}
23+
24+
// Add adds new credentials to the keychain.
25+
func (h secretservice) Add(creds *credentials.Credentials) error {
26+
if creds == nil {
27+
return errors.New("missing credentials")
28+
}
29+
server := C.CString(creds.ServerURL)
30+
defer C.free(unsafe.Pointer(server))
31+
username := C.CString(creds.Username)
32+
defer C.free(unsafe.Pointer(username))
33+
password := C.CString(creds.Password)
34+
defer C.free(unsafe.Pointer(password))
35+
36+
if err := C.add(server, username, password); err != nil {
37+
defer C.g_error_free(err)
38+
errMsg := (*C.char)(unsafe.Pointer(err.message))
39+
return errors.New(C.GoString(errMsg))
40+
}
41+
return nil
42+
}
43+
44+
// Delete removes credentials from the keychain.
45+
func (h secretservice) Delete(serverURL string) error {
46+
if serverURL == "" {
47+
return errors.New("missing server url")
48+
}
49+
server := C.CString(serverURL)
50+
defer C.free(unsafe.Pointer(server))
51+
52+
if err := C.delete(server); err != nil {
53+
defer C.g_error_free(err)
54+
errMsg := (*C.char)(unsafe.Pointer(err.message))
55+
return errors.New(C.GoString(errMsg))
56+
}
57+
return nil
58+
}
59+
60+
// Get returns the username and password to use for a given registry server URL.
61+
func (h secretservice) Get(serverURL string) (string, string, error) {
62+
if serverURL == "" {
63+
return "", "", errors.New("missing server url")
64+
}
65+
var username *C.char
66+
defer C.free(unsafe.Pointer(username))
67+
var password *C.char
68+
defer C.free(unsafe.Pointer(password))
69+
server := C.CString(serverURL)
70+
defer C.free(unsafe.Pointer(server))
71+
72+
err := C.get(server, &username, &password)
73+
if err != nil {
74+
defer C.g_error_free(err)
75+
errMsg := (*C.char)(unsafe.Pointer(err.message))
76+
return "", "", errors.New(C.GoString(errMsg))
77+
}
78+
user := C.GoString(username)
79+
pass := C.GoString(password)
80+
if pass == "" {
81+
return "", "", credentials.ErrCredentialsNotFound
82+
}
83+
return user, pass, nil
84+
}

secretservice/secretservice_linux.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#define SECRET_WITH_UNSTABLE 1
2+
#define SECRET_API_SUBJECT_TO_CHANGE 1
3+
#include <libsecret/secret.h>
4+
5+
const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
6+
7+
#define DOCKER_SCHEMA docker_get_schema()
8+
9+
GError *add(char *server, char *username, char *password);
10+
GError *delete(char *server);
11+
GError *get(char *server, char **username, char **password);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package secretservice
2+
3+
import (
4+
"testing"
5+
6+
"github.com/docker/docker-credential-helpers/credentials"
7+
)
8+
9+
func TestSecretServiceHelper(t *testing.T) {
10+
t.Skip("test requires gnome-keyring but travis CI doesn't have it")
11+
12+
creds := &credentials.Credentials{
13+
ServerURL: "https://foobar.docker.io:2376/v1",
14+
Username: "foobar",
15+
Password: "foobarbaz",
16+
}
17+
18+
helper := New()
19+
if err := helper.Add(creds); err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
username, password, err := helper.Get(creds.ServerURL)
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
if username != "foobar" {
29+
t.Fatalf("expected %s, got %s\n", "foobar", username)
30+
}
31+
32+
if password != "foobarbaz" {
33+
t.Fatalf("expected %s, got %s\n", "foobarbaz", password)
34+
}
35+
36+
if err := helper.Delete(creds.ServerURL); err != nil {
37+
t.Fatal(err)
38+
}
39+
}
40+
41+
func TestMissingCredentials(t *testing.T) {
42+
t.Skip("test requires gnome-keyring but travis CI doesn't have it")
43+
44+
helper := New()
45+
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
46+
if err != credentials.ErrCredentialsNotFound {
47+
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err)
48+
}
49+
}

0 commit comments

Comments
 (0)