Skip to content

Commit 0652beb

Browse files
committed
Add keychain module
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
1 parent e652fcf commit 0652beb

File tree

184 files changed

+29065
-0
lines changed

Some content is hidden

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

184 files changed

+29065
-0
lines changed

keychain/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Secrets Engine Keychain
2+
3+
Keychain is a standalone library for use to store secrets in a standardized
4+
format to the OS keychain.

keychain/go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module github.com/docker/secrets-engine/keychain
2+
3+
go 1.24.3
4+
5+
replace github.com/docker/secrets-engine => ../
6+
7+
require (
8+
github.com/docker/secrets-engine v0.0.0-00010101000000-000000000000
9+
github.com/godbus/dbus/v5 v5.1.0
10+
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a
11+
github.com/keybase/go-keychain v0.0.1
12+
github.com/spf13/cobra v1.9.1
13+
)
14+
15+
require (
16+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
17+
github.com/spf13/pflag v1.0.6 // indirect
18+
golang.org/x/crypto v0.32.0 // indirect
19+
)

keychain/go.sum

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

keychain/keychain.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package keychain
2+
3+
import (
4+
"errors"
5+
6+
"github.com/docker/secrets-engine/pkg/secrets"
7+
)
8+
9+
// We depend on the secrets engine to define how secrets should look like
10+
// but the caller of keychain does not necessarily need to import secrets engine
11+
type (
12+
Secret = secrets.Secret
13+
Store = secrets.Store
14+
ID = secrets.ID
15+
)
16+
17+
var ParseID = secrets.ParseID
18+
19+
var (
20+
ErrCredentialNotFound = secrets.ErrNotFound
21+
ErrCollectionPathInvalid = errors.New("keychain collection path is invalid")
22+
)
23+
24+
const (
25+
// the docker label is the default prefix on all keys stored by the keychain
26+
// e.g. io.docker.Secrets:encoded(realm/app/username)
27+
dockerSecretsLabel = "io.docker.Secrets"
28+
)
29+
30+
type keychainStore[T Secret] struct {
31+
keyPrefix string
32+
factory func() T
33+
}
34+
35+
var _ Store = &keychainStore[Secret]{}
36+
37+
type Factory[T secrets.Secret] func() T
38+
39+
type Options[T Secret] func(*keychainStore[T]) error
40+
41+
func WithKeyPrefix[T Secret](prefix string) Options[T] {
42+
return func(ks *keychainStore[T]) error {
43+
if prefix == "" {
44+
return errors.New("the prefix cannot be empty")
45+
}
46+
ks.keyPrefix = prefix
47+
return nil
48+
}
49+
}
50+
51+
// New creates a new keychain store
52+
//
53+
// collectionID is a singular noun indicating the collection name, e.g. "docker"
54+
// factory is a function used to instantiate new secrets of type T.
55+
func New[T Secret](factory Factory[T], opts ...Options[T]) (Store, error) {
56+
k := &keychainStore[T]{
57+
factory: factory,
58+
keyPrefix: dockerSecretsLabel,
59+
}
60+
for _, o := range opts {
61+
if err := o(k); err != nil {
62+
return nil, err
63+
}
64+
}
65+
return k, nil
66+
}

keychain/keychain_dumb.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/pkg/secrets"
7+
)
8+
9+
var _ Store = &keychainStore[Secret]{}
10+
11+
// Erase implements secrets.Store.
12+
func (k *keychainStore[T]) Erase(ctx context.Context, id secrets.ID) error {
13+
panic("unimplemented")
14+
}
15+
16+
// Get implements secrets.Store.
17+
func (k *keychainStore[T]) Get(ctx context.Context, id secrets.ID) (secrets.Secret, error) {
18+
panic("unimplemented")
19+
}
20+
21+
// GetAll implements secrets.Store.
22+
func (k *keychainStore[T]) GetAll(ctx context.Context) (map[secrets.ID]secrets.Secret, error) {
23+
panic("unimplemented")
24+
}
25+
26+
// Store implements secrets.Store.
27+
func (k *keychainStore[T]) Store(ctx context.Context, id secrets.ID, secret secrets.Secret) error {
28+
panic("unimplemented")
29+
}

keychain/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/keychain"
8+
)
9+
10+
type MockCredential struct {
11+
Username string
12+
Password string
13+
}
14+
15+
var _ keychain.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+
}

keychain/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/keychain"
9+
)
10+
11+
type MockStore struct {
12+
lock sync.RWMutex
13+
store map[keychain.ID]keychain.Secret
14+
}
15+
16+
func (m *MockStore) init() {
17+
if m.store == nil {
18+
m.store = make(map[keychain.ID]keychain.Secret)
19+
}
20+
}
21+
22+
// Erase implements Store.
23+
func (m *MockStore) Erase(_ context.Context, id keychain.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 keychain.ID) (keychain.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, keychain.ErrCredentialNotFound
41+
}
42+
return secret, nil
43+
}
44+
45+
// GetAll implements Store.
46+
func (m *MockStore) GetAll(_ context.Context) (map[keychain.ID]keychain.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+
// Store implements Store.
56+
func (m *MockStore) Store(_ context.Context, id keychain.ID, secret keychain.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 _ keychain.Store = &MockStore{}

keychain/vendor/github.com/docker/secrets-engine/LICENSE

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

keychain/vendor/github.com/docker/secrets-engine/pkg/secrets/acl.go

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)