Skip to content

Commit 8be0587

Browse files
authored
Merge pull request #25 from docker/keychain-interfaces
store: add keychain package
2 parents 696ae65 + d0a5fd4 commit 8be0587

File tree

138 files changed

+22013
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+22013
-1
lines changed

store/errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package store
2+
3+
import "github.com/docker/secrets-engine/pkg/secrets"
4+
5+
var ErrCredentialNotFound = secrets.ErrNotFound

store/go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ replace github.com/docker/secrets-engine => ../
66

77
require (
88
github.com/docker/secrets-engine v0.0.0-00010101000000-000000000000
9+
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a
10+
github.com/keybase/go-keychain v0.0.1
11+
github.com/spf13/cobra v1.9.1
912
github.com/stretchr/testify v1.10.0
1013
)
1114

1215
require (
1316
github.com/davecgh/go-spew v1.1.1 // indirect
17+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1418
github.com/pmezard/go-difflib v1.0.0 // indirect
19+
github.com/spf13/pflag v1.0.6 // indirect
20+
golang.org/x/crypto v0.32.0 // indirect
1521
gopkg.in/yaml.v3 v3.0.1 // indirect
1622
)

store/go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
12
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
23
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
5+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6+
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a h1:K0EAzgzEQHW4Y5lxrmvPMltmlRDzlhLfGmots9EHUTI=
7+
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8=
8+
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
9+
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
310
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
411
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
13+
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
14+
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
15+
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
16+
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
517
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
618
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
19+
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
20+
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
721
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
822
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
923
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

store/keychain/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Store Keychain
2+
3+
Keychain integrates with the OS keystore. It supports Linux, macOS and Windows
4+
and can be used directly with `keychain.New`.
5+
6+
- Linux uses the [`org.freedesktop.secrets` API](https://www.freedesktop.org/wiki/Specifications/secret-storage-spec/secrets-api-0.1.html).
7+
- macOS uses the [macOS Keychain services API](https://developer.apple.com/documentation/security/keychain-services).
8+
9+
## Quickstart
10+
11+
```go
12+
import "github.com/docker/secrets-engine/store/keychain"
13+
14+
func main() {
15+
kc, err := keychain.New[*]()
16+
}
17+
```

store/keychain/keychain.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package keychain
2+
3+
import (
4+
"errors"
5+
6+
"github.com/docker/secrets-engine/store"
7+
)
8+
9+
var ErrCollectionPathInvalid = errors.New("keychain collection path is invalid")
10+
11+
const (
12+
// the docker label is the default prefix on all keys stored by the keychain
13+
// e.g. io.docker.Secrets:id(realm/app/username)
14+
dockerSecretsLabel = "io.docker.Secrets"
15+
)
16+
17+
type keychainStore[T store.Secret] struct {
18+
keyPrefix string
19+
factory func() T
20+
}
21+
22+
var _ store.Store = &keychainStore[store.Secret]{}
23+
24+
type Factory[T store.Secret] func() T
25+
26+
type Options[T store.Secret] func(*keychainStore[T]) error
27+
28+
func WithKeyPrefix[T store.Secret](prefix string) Options[T] {
29+
return func(ks *keychainStore[T]) error {
30+
if prefix == "" {
31+
return errors.New("the prefix cannot be empty")
32+
}
33+
ks.keyPrefix = prefix
34+
return nil
35+
}
36+
}
37+
38+
// New creates a new keychain store
39+
//
40+
// factory is a function used to instantiate new secrets of type T.
41+
func New[T store.Secret](factory Factory[T], opts ...Options[T]) (store.Store, error) {
42+
k := &keychainStore[T]{
43+
factory: factory,
44+
keyPrefix: dockerSecretsLabel,
45+
}
46+
for _, o := range opts {
47+
if err := o(k); err != nil {
48+
return nil, err
49+
}
50+
}
51+
return k, nil
52+
}

store/keychain/keychain_dummy.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package keychain
2+
3+
import (
4+
"context"
5+
6+
"github.com/docker/secrets-engine/store"
7+
)
8+
9+
var _ store.Store = &keychainStore[store.Secret]{}
10+
11+
// Erase implements secrets.Store.
12+
func (k *keychainStore[T]) Delete(ctx context.Context, id store.ID) error {
13+
panic("unimplemented")
14+
}
15+
16+
// Get implements secrets.Store.
17+
func (k *keychainStore[T]) Get(ctx context.Context, id store.ID) (store.Secret, error) {
18+
panic("unimplemented")
19+
}
20+
21+
// GetAll implements secrets.Store.
22+
func (k *keychainStore[T]) GetAll(ctx context.Context) (map[store.ID]store.Secret, error) {
23+
panic("unimplemented")
24+
}
25+
26+
// Store implements secrets.Store.
27+
func (k *keychainStore[T]) Save(ctx context.Context, id store.ID, secret store.Secret) error {
28+
panic("unimplemented")
29+
}

store/mocks/mock_credential.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package mocks
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
7+
"github.com/docker/secrets-engine/store"
8+
)
9+
10+
type MockCredential struct {
11+
Username string
12+
Password string
13+
}
14+
15+
var _ store.Secret = &MockCredential{}
16+
17+
// Marshal implements secrets.Secret.
18+
func (m *MockCredential) Marshal() ([]byte, error) {
19+
return []byte(m.Username + ":" + m.Password), nil
20+
}
21+
22+
// Unmarshal implements secrets.Secret.
23+
func (m *MockCredential) Unmarshal(data []byte) error {
24+
items := bytes.Split(data, []byte(":"))
25+
if len(items) != 2 {
26+
return errors.New("failed to unmarshal data into mock credential type")
27+
}
28+
m.Username = string(items[0])
29+
m.Password = string(items[1])
30+
return nil
31+
}

store/mocks/mock_store.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package mocks
2+
3+
import (
4+
"context"
5+
"maps"
6+
"sync"
7+
8+
"github.com/docker/secrets-engine/store"
9+
)
10+
11+
type MockStore struct {
12+
lock sync.RWMutex
13+
store map[store.ID]store.Secret
14+
}
15+
16+
func (m *MockStore) init() {
17+
if m.store == nil {
18+
m.store = make(map[store.ID]store.Secret)
19+
}
20+
}
21+
22+
// Delete implements Store.
23+
func (m *MockStore) Delete(_ context.Context, id store.ID) error {
24+
m.lock.Lock()
25+
defer m.lock.Unlock()
26+
m.init()
27+
28+
delete(m.store, id)
29+
return nil
30+
}
31+
32+
// Get implements Store.
33+
func (m *MockStore) Get(_ context.Context, id store.ID) (store.Secret, error) {
34+
m.lock.RLock()
35+
defer m.lock.RUnlock()
36+
m.init()
37+
38+
secret, exists := m.store[id]
39+
if !exists {
40+
return nil, store.ErrCredentialNotFound
41+
}
42+
return secret, nil
43+
}
44+
45+
// GetAll implements Store.
46+
func (m *MockStore) GetAll(_ context.Context) (map[store.ID]store.Secret, error) {
47+
m.lock.RLock()
48+
defer m.lock.RUnlock()
49+
m.init()
50+
51+
// Return a copy of the store to avoid concurrent map read/write issues.
52+
return maps.Clone(m.store), nil
53+
}
54+
55+
// Save implements Store.
56+
func (m *MockStore) Save(_ context.Context, id store.ID, secret store.Secret) error {
57+
m.lock.Lock()
58+
defer m.lock.Unlock()
59+
m.init()
60+
61+
m.store[id] = secret
62+
return nil
63+
}
64+
65+
var _ store.Store = &MockStore{}

store/store.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package secrets
1+
package store
22

33
import (
44
"context"

0 commit comments

Comments
 (0)