1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-13 16:01:28 +05:30

🌅 complete OS X keychain implementation.

Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
David Calavera
2016-02-07 17:16:54 -08:00
commit b7ffbdb74f
13 changed files with 478 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
bin
+17
View File
@@ -0,0 +1,17 @@
---
sudo: false
language: go
os:
- osx
notifications:
email: false
go:
- 1.5
install:
- go get github.com/golang/lint/golint
before_script:
- go vet ./...
- test -z "$(golint ./... | tee /dev/stderr)"
- test -z "$(gofmt -s -l . | tee /dev/stderr)"
script:
- go test -v ./...
+20
View File
@@ -0,0 +1,20 @@
Copyright (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.
+10
View File
@@ -0,0 +1,10 @@
.PHONY: all osxkeychain test
all: test
osxkeychain:
mkdir -p bin
go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
test:
go test ./...
+52
View File
@@ -0,0 +1,52 @@
## Introduction
docker-credential-helpers is a suite of programs to use native stores to keep Docker credentials safe.
## Installation
Go to the [Releases](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/calavera/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/calavera/docker-credentials-helpers
$ make osxkeychain
```
3 - Put that binary in your `$PATH`, so Docker can find it.
## Usage
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"
}
```
### Available programs
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store.
## Development
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.
+15
View File
@@ -0,0 +1,15 @@
package credentials
// Credentials holds the information shared between docker and the credentials store.
type Credentials struct {
ServerURL string
Username string
Password string
}
// Helper is the interface a credentials store helper must implement.
type Helper interface {
Add(*Credentials) error
Delete(serverURL string) error
Get(serverURL string) (string, string, error)
}
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/calavera/docker-credential-helpers/osxkeychain"
"github.com/calavera/docker-credential-helpers/plugin"
)
func main() {
plugin.Serve(osxkeychain.New())
}
+98
View File
@@ -0,0 +1,98 @@
#include "osxkeychain_darwin.h"
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 *username, char *password) {
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(password), password,
NULL
);
if (status) {
return get_error(status);
}
return NULL;
}
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *password_l, char **password) {
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,
password_l, (void **)&tmp,
&item);
if (status) {
return get_error(status);
}
*password = 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;
}
+125
View File
@@ -0,0 +1,125 @@
package osxkeychain
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Security -framework Foundation
#include "osxkeychain_darwin.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"net/url"
"strconv"
"strings"
"unsafe"
"github.com/calavera/docker-credential-helpers/credentials"
)
type osxkeychain struct{}
func New() credentials.Helper {
return osxkeychain{}
}
// Add adds new credentials to the keychain.
func (h osxkeychain) Add(creds *credentials.Credentials) error {
s, err := splitServer(creds.ServerURL)
if err != nil {
return err
}
defer freeServer(s)
username := C.CString(creds.Username)
defer C.free(unsafe.Pointer(username))
password := C.CString(creds.Password)
defer C.free(unsafe.Pointer(password))
errMsg := C.keychain_add(s, username, password)
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 password 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 passwordLen C.uint
var password *C.char
defer C.free(unsafe.Pointer(username))
defer C.free(unsafe.Pointer(password))
errMsg := C.keychain_get(s, &usernameLen, &username, &passwordLen, &password)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return "", "", errors.New(C.GoString(errMsg))
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(password, C.int(passwordLen))
return user, pass, 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))
}
+12
View File
@@ -0,0 +1,12 @@
#include <Security/Security.h>
struct Server {
SecProtocolType proto;
char *host;
char *path;
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_delete(struct Server *server);
+37
View File
@@ -0,0 +1,37 @@
package osxkeychain
import (
"testing"
"github.com/calavera/docker-credential-helpers/credentials"
)
func TestOSXKeychainHelper(t *testing.T) {
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)
}
}
+38
View File
@@ -0,0 +1,38 @@
package plugin
import (
"net/rpc"
"github.com/calavera/docker-credential-helpers/credentials"
"github.com/hashicorp/go-plugin"
)
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "DOCKER_CREDENTIAL_PLUGIN",
MagicCookieValue: "nyzGgJQpfOYO$oUVHo4RsLaYaNmCqeWLEqZnZG}peMVq4nXdFp",
}
type credentialsPlugin struct {
helper credentials.Helper
}
func (p *credentialsPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return p, nil
}
func (*credentialsPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return nil, nil
}
// Serve initializes the socket connection to a store helper.
func Serve(helper credentials.Helper) {
pluginMap := map[string]plugin.Plugin{
"credentials": &credentialsPlugin{helper},
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshakeConfig,
Plugins: pluginMap,
})
}
+43
View File
@@ -0,0 +1,43 @@
package plugin
import "github.com/calavera/docker-credential-helpers/credentials"
// CredentialsGetResponse holds the information sent to docker after
// a request for credentials.
type CredentialsGetResponse struct {
Error string
Username string
Password string
}
func (p *credentialsPlugin) Get(c *credentials.Credentials, resp *CredentialsGetResponse) error {
username, password, err := p.helper.Get(c.ServerURL)
if err != nil {
*resp = CredentialsGetResponse{
Error: err.Error(),
}
return nil
}
*resp = CredentialsGetResponse{
Username: username,
Password: password,
}
return nil
}
func (p *credentialsPlugin) Add(c *credentials.Credentials, resp *string) error {
err := p.helper.Add(c)
if err != nil {
*resp = err.Error()
}
return nil
}
func (p *credentialsPlugin) Delete(c *credentials.Credentials, resp *string) error {
err := p.helper.Delete(c.ServerURL)
if err != nil {
*resp = err.Error()
}
return nil
}