Skip to content

Commit 70bae35

Browse files
committed
store/keychain: store and retrieve secret metadata
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
1 parent 47ec869 commit 70bae35

17 files changed

Lines changed: 286 additions & 100 deletions

File tree

go.work.sum

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,55 @@
1+
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
12
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
3+
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
24
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
5+
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
36
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
7+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
48
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
9+
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
510
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
11+
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
612
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
7-
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
13+
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
14+
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
15+
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA=
816
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
17+
github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
918
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
19+
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
1020
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
21+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
1122
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
12-
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8=
13-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
14-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
23+
github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
24+
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
25+
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
26+
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
1527
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
28+
github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU=
1629
github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
30+
github.com/planetscale/vtprotobuf v0.4.0 h1:NEI+g4woRaAZgeZ3sAvbtyvMBRjIv5kE7EWYQ8m4JwY=
1731
github.com/planetscale/vtprotobuf v0.4.0/go.mod h1:wm1N3qk9G/4+VM1WhpkLbvY/d8+0PbwYYpP5P5VhTks=
18-
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
32+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
33+
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
1934
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
35+
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
2036
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
21-
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
37+
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
2238
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
39+
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
2340
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
41+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
2442
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
25-
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2643
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
2744
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
45+
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
2846
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
29-
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
30-
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
47+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
48+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
3149
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
3250
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
3351
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
52+
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
3453
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
54+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
3555
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

store/examples/secret.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
type secret struct {
1111
AccessToken string
1212
RefreshToken string
13-
Attributes map[string]string
13+
Attributes map[string]any
1414
}
1515

1616
var _ secrets.Secret = &secret{}
@@ -29,6 +29,6 @@ func (s *secret) Unmarshal(data []byte) error {
2929
return nil
3030
}
3131

32-
func (s *secret) Metadata() map[string]string {
32+
func (s *secret) Metadata() map[string]any {
3333
return s.Attributes
3434
}

store/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.24.3
44

55
replace github.com/docker/secrets-engine => ../
66

7+
replace github.com/keybase/go-keychain => ../../go-keychain/
8+
79
require (
810
github.com/danieljoos/wincred v1.2.2
911
github.com/docker/secrets-engine v0.0.0-00010101000000-000000000000

store/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
88
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
99
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a h1:K0EAzgzEQHW4Y5lxrmvPMltmlRDzlhLfGmots9EHUTI=
1010
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8=
11-
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
12-
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
1311
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
1412
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1513
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=

store/keychain/cmd/main.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"log"
88
"path"
9+
"strings"
910

1011
"github.com/spf13/cobra"
1112

@@ -20,8 +21,10 @@ func newCommand() (*cobra.Command, error) {
2021
kc, err := keychain.New(
2122
"io.docker.Secrets",
2223
"docker-example-cli",
23-
func() *mocks.MockCredential {
24-
return &mocks.MockCredential{}
24+
func(metadata map[string]any) *mocks.MockCredential {
25+
return &mocks.MockCredential{
26+
Attributes: metadata,
27+
}
2528
},
2629
)
2730
if err != nil {
@@ -42,7 +45,8 @@ func newCommand() (*cobra.Command, error) {
4245

4346
var e error
4447
for _, v := range secrets {
45-
fmt.Printf("\nID: %s\n", v)
48+
fmt.Printf("\nID: %s\n", v.ID)
49+
fmt.Printf("\nMetadata: %+v", v.Metadata)
4650
}
4751
return e
4852
},
@@ -51,6 +55,7 @@ func newCommand() (*cobra.Command, error) {
5155
var (
5256
username string
5357
password string
58+
metadata []string
5459
)
5560
save := &cobra.Command{
5661
Use: "store",
@@ -60,15 +65,25 @@ func newCommand() (*cobra.Command, error) {
6065
if err != nil {
6166
return err
6267
}
68+
69+
secretMetadata := make(map[string]any)
70+
for _, vals := range metadata {
71+
vv := strings.Split(vals, ":")
72+
if len(vv) == 2 && vv[0] != "" && vv[1] != "" {
73+
secretMetadata[vv[0]] = vv[1]
74+
}
75+
}
6376
creds := &mocks.MockCredential{
64-
Username: username,
65-
Password: password,
77+
Username: username,
78+
Password: password,
79+
Attributes: secretMetadata,
6680
}
6781
return kc.Save(cmd.Context(), id, creds)
6882
},
6983
}
7084
save.PersistentFlags().StringVar(&username, "username", "", "The secret key")
7185
save.PersistentFlags().StringVar(&password, "password", "", "The secret value")
86+
save.PersistentFlags().StringSliceVar(&metadata, "metadata", nil, "The secret metadata")
7287
save.MarkFlagsRequiredTogether("username", "password")
7388

7489
get := &cobra.Command{
@@ -87,7 +102,9 @@ func newCommand() (*cobra.Command, error) {
87102
if err != nil {
88103
return err
89104
}
90-
fmt.Printf("Secret:\nID:%s\nValue:%s\n", id.String(), val)
105+
fmt.Printf("Secret ID: %s\n", id.String())
106+
fmt.Printf("Secret Val: %s\n", val)
107+
fmt.Printf("Secret Metadata: %v\n", secret.Metadata())
91108
return nil
92109
},
93110
}

store/keychain/keychain.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import (
99
type keychainStore[T store.Secret] struct {
1010
serviceGroup string
1111
serviceName string
12-
factory func() T
12+
factory Factory[T]
1313
}
1414

1515
var _ store.Store = &keychainStore[store.Secret]{}
1616

17-
type Factory[T store.Secret] func() T
17+
type Factory[T store.Secret] func(metadata map[string]any) T
1818

1919
// New creates a new keychain store.
2020
//

store/keychain/keychain_darwin.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package keychain
33
import (
44
"context"
55
"errors"
6+
"fmt"
7+
"maps"
8+
"os"
9+
"reflect"
10+
"slices"
611
"strings"
712

813
kc "github.com/keybase/go-keychain"
@@ -88,14 +93,14 @@ func (k *keychainStore[T]) Get(ctx context.Context, id store.ID) (store.Secret,
8893
return nil, err
8994
}
9095

91-
secret := k.factory()
96+
secret := k.factory(result.Attributes)
9297
if err := secret.Unmarshal(result.Data); err != nil {
9398
return nil, err
9499
}
95100
return secret, nil
96101
}
97102

98-
func (k *keychainStore[T]) GetAll(ctx context.Context) ([]store.ID, error) {
103+
func (k *keychainStore[T]) GetAll(ctx context.Context) ([]store.SecretMetadata, error) {
99104
item := newKeychainItem("", k)
100105

101106
// We use the MatchLimitAll attribute to query for multiple items from the
@@ -108,13 +113,16 @@ func (k *keychainStore[T]) GetAll(ctx context.Context) ([]store.ID, error) {
108113
return nil, mapKeychainError(err)
109114
}
110115

111-
creds := make([]store.ID, len(results))
112-
for _, result := range results {
116+
creds := make([]store.SecretMetadata, len(results))
117+
for i, result := range results {
113118
id, err := store.ParseID(result.Account)
114119
if err != nil {
115120
continue
116121
}
117-
creds = append(creds, id)
122+
creds[i] = store.SecretMetadata{
123+
ID: id,
124+
Metadata: result.Attributes,
125+
}
118126
}
119127
return creds, nil
120128
}
@@ -135,40 +143,56 @@ func (k *keychainStore[T]) Save(ctx context.Context, id store.ID, secret store.S
135143
// https://developer.apple.com/documentation/security/ksecattrlabel
136144
item.SetLabel(k.itemLabel(id))
137145

146+
metadata := make(map[string]any)
147+
maps.Copy(metadata, secret.Metadata())
138148
parts := strings.SplitSeq(id.String(), "/")
139149
for p := range parts {
140150
if p == "" {
141151
continue
142152
}
143-
item.SetString(p, p)
153+
metadata[p] = p
144154
}
145155

146-
for k, v := range secret.Metadata() {
147-
item.SetString(k, v)
148-
}
156+
item.SetGenericMetadata(metadata)
149157

150158
return mapKeychainError(kc.AddItem(item))
151159
}
152160

153-
func (k *keychainStore[T]) Filter(ctx context.Context, id store.ID, attributes map[string]string) (map[store.ID]store.Secret, error) {
161+
func hasAttribute(attributes map[string]any, result kc.QueryResult) bool {
162+
if attributes == nil {
163+
return true
164+
}
165+
fmt.Fprintf(os.Stdout, "Attributes: %v\n", result.Attributes)
166+
allMatches := make([]bool, len(attributes))
167+
var i int
168+
for k, v := range result.Attributes {
169+
if needle, ok := attributes[k]; ok && reflect.DeepEqual(needle, v) {
170+
allMatches[i] = true
171+
i++
172+
}
173+
}
174+
return !slices.Contains(allMatches, false)
175+
}
176+
177+
func (k *keychainStore[T]) Filter(ctx context.Context, id store.ID, attributes map[string]any) (map[store.ID]store.Secret, error) {
154178
item := newKeychainItem("", k)
155179

156180
// We use the MatchLimitAll attribute to query for multiple items from the
157181
// store. It cannot be used with item.SetReturnData.
158182
// https://developer.apple.com/documentation/security/secitemcopymatching(_:_:)#Discussion
159183
item.SetMatchLimit(kc.MatchLimitAll)
160184

185+
metadata := make(map[string]any)
186+
maps.Copy(metadata, attributes)
161187
parts := strings.SplitSeq(id.String(), "/")
162188
for p := range parts {
163189
if p == "" {
164190
continue
165191
}
166-
item.SetString(p, p)
192+
metadata[p] = p
167193
}
168194

169-
for k, v := range attributes {
170-
item.SetString(k, v)
171-
}
195+
item.SetGenericMetadata(metadata)
172196

173197
results, err := kc.QueryItem(item)
174198
if err != nil {
@@ -182,12 +206,16 @@ func (k *keychainStore[T]) Filter(ctx context.Context, id store.ID, attributes m
182206
continue
183207
}
184208

209+
if !hasAttribute(attributes, result) {
210+
continue
211+
}
212+
185213
i, err := getItemWithData(id.String(), k)
186214
if err != nil {
187215
return nil, err
188216
}
189217

190-
secret := k.factory()
218+
secret := k.factory(i.Attributes)
191219
if err := secret.Unmarshal(i.Data); err != nil {
192220
return nil, err
193221
}

0 commit comments

Comments
 (0)