Skip to content

Commit 6c67edf

Browse files
authored
Add sync support for gsm (tellerops#105)
Add sync support for gsm
1 parent 1a0012a commit 6c67edf

File tree

5 files changed

+178
-13
lines changed

5 files changed

+178
-13
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -691,18 +691,22 @@ You should populate `GOOGLE_APPLICATION_CREDENTIALS=account.json` in your enviro
691691

692692
### Features
693693

694-
* Sync - `no`
694+
* Sync - `yes`
695695
* Mapping - `yes`
696-
* Modes - `read`, [write: accepting PR](https://github.com/spectralops/teller)
696+
* Modes - `read+write+delete`
697697
* Key format
698698
* `env` - path based, needs to include a version
699+
* `env_sync` - your project's path (gets the secrets latest version), when using --sync a new secret version will be created
699700
* `decrypt` - available in this provider, will use KMS automatically
700701

701702

702703
### Example Config
703704

704705
```yaml
705706
google_secretmanager:
707+
env_sync:
708+
# secrets version is not relevant here since we are getting the latest version
709+
path: projects/44882
706710
env:
707711
MG_KEY:
708712
# need to supply the relevant version (versions/1)

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ require (
3939
go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0
4040
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
4141
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
42+
google.golang.org/api v0.40.0
4243
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c
4344
gopkg.in/yaml.v2 v2.4.0
4445
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
@@ -151,7 +152,6 @@ require (
151152
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
152153
golang.org/x/text v0.3.7 // indirect
153154
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
154-
google.golang.org/api v0.40.0 // indirect
155155
google.golang.org/appengine v1.6.7 // indirect
156156
google.golang.org/grpc v1.35.0 // indirect
157157
google.golang.org/protobuf v1.27.1 // indirect

pkg/providers/google_secretmanager.go

+83-7
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@ package providers
33
import (
44
"context"
55
"fmt"
6+
"regexp"
7+
"sort"
8+
"strings"
69

710
secretmanager "cloud.google.com/go/secretmanager/apiv1"
811
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
912

1013
"github.com/googleapis/gax-go/v2"
1114
"github.com/spectralops/teller/pkg/core"
1215
"github.com/spectralops/teller/pkg/logging"
16+
"google.golang.org/api/iterator"
1317
)
1418

1519
type GoogleSMClient interface {
1620
AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
21+
DestroySecretVersion(ctx context.Context, req *secretmanagerpb.DestroySecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
22+
ListSecrets(ctx context.Context, in *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator
23+
AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
1724
}
1825
type GoogleSecretManager struct {
1926
client GoogleSMClient
@@ -33,18 +40,46 @@ func (a *GoogleSecretManager) Name() string {
3340
}
3441

3542
func (a *GoogleSecretManager) Put(p core.KeyPath, val string) error {
36-
return fmt.Errorf("provider %q does not implement write yet", a.Name())
43+
reg := regexp.MustCompile(`(?i)/versions/\d+$`)
44+
res := reg.ReplaceAllString(p.Path, "")
45+
return a.addSecret(res, val)
3746
}
3847
func (a *GoogleSecretManager) PutMapping(p core.KeyPath, m map[string]string) error {
39-
return fmt.Errorf("provider %q does not implement write yet", a.Name())
48+
for k, v := range m {
49+
path := fmt.Sprintf("%v/secrets/%v", p.Path, k)
50+
err := a.addSecret(path, v)
51+
if err != nil {
52+
return err
53+
}
54+
}
55+
56+
return nil
4057
}
4158

4259
func (a *GoogleSecretManager) GetMapping(kp core.KeyPath) ([]core.EnvEntry, error) {
43-
return nil, fmt.Errorf("does not support full env sync (path: %s)", kp.Path)
60+
secrets, err := a.getSecrets(kp.Path)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
entries := []core.EnvEntry{}
66+
67+
for _, val := range secrets {
68+
path := fmt.Sprintf("%s/%s", val, "versions/latest")
69+
secretVal, err := a.getSecret(path)
70+
if err != nil {
71+
return nil, err
72+
}
73+
key := strings.TrimPrefix(val, kp.Path)
74+
entries = append(entries, kp.FoundWithKey(key, secretVal))
75+
}
76+
sort.Sort(core.EntriesByKey(entries))
77+
78+
return entries, nil
4479
}
4580

4681
func (a *GoogleSecretManager) Get(p core.KeyPath) (*core.EnvEntry, error) {
47-
secret, err := a.getSecret(p)
82+
secret, err := a.getSecret(p.Path)
4883
if err != nil {
4984
return nil, err
5085
}
@@ -54,16 +89,16 @@ func (a *GoogleSecretManager) Get(p core.KeyPath) (*core.EnvEntry, error) {
5489
}
5590

5691
func (a *GoogleSecretManager) Delete(kp core.KeyPath) error {
57-
return fmt.Errorf("%s does not implement delete yet", a.Name())
92+
return a.deleteSecret(kp.Path)
5893
}
5994

6095
func (a *GoogleSecretManager) DeleteMapping(kp core.KeyPath) error {
6196
return fmt.Errorf("%s does not implement delete yet", a.Name())
6297
}
6398

64-
func (a *GoogleSecretManager) getSecret(kp core.KeyPath) (string, error) {
99+
func (a *GoogleSecretManager) getSecret(path string) (string, error) {
65100
r := secretmanagerpb.AccessSecretVersionRequest{
66-
Name: kp.Path,
101+
Name: path,
67102
}
68103
a.logger.WithField("path", r.Name).Debug("get secret")
69104

@@ -73,3 +108,44 @@ func (a *GoogleSecretManager) getSecret(kp core.KeyPath) (string, error) {
73108
}
74109
return string(secret.Payload.Data), nil
75110
}
111+
112+
func (a *GoogleSecretManager) deleteSecret(path string) error {
113+
req := &secretmanagerpb.DestroySecretVersionRequest{
114+
Name: path,
115+
}
116+
_, err := a.client.DestroySecretVersion(context.TODO(), req)
117+
return err
118+
}
119+
120+
func (a *GoogleSecretManager) addSecret(path, val string) error {
121+
req := &secretmanagerpb.AddSecretVersionRequest{
122+
Parent: path,
123+
Payload: &secretmanagerpb.SecretPayload{
124+
Data: []byte(val),
125+
},
126+
}
127+
128+
_, err := a.client.AddSecretVersion(context.TODO(), req)
129+
return err
130+
}
131+
132+
func (a *GoogleSecretManager) getSecrets(path string) ([]string, error) {
133+
req := &secretmanagerpb.ListSecretsRequest{
134+
Parent: path,
135+
}
136+
entries := []string{}
137+
138+
it := a.client.ListSecrets(context.TODO(), req)
139+
for {
140+
resp, err := it.Next()
141+
if err == iterator.Done {
142+
break
143+
}
144+
145+
if err != nil {
146+
return nil, err
147+
}
148+
entries = append(entries, resp.Name)
149+
}
150+
return entries, nil
151+
}

pkg/providers/google_secretmanager_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"testing"
66

7+
secretmanager "cloud.google.com/go/secretmanager/apiv1"
78
"github.com/alecthomas/assert"
89
"github.com/golang/mock/gomock"
910
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
@@ -24,10 +25,34 @@ func TestGoogleSM(t *testing.T) {
2425
out := &secretmanagerpb.AccessSecretVersionResponse{
2526
Payload: sec,
2627
}
28+
outDelete := &secretmanagerpb.SecretVersion{
29+
Name: string(sec.Data),
30+
}
31+
outAdd := &secretmanagerpb.SecretVersion{
32+
Name: string(sec.Data),
33+
}
34+
outList := &secretmanager.SecretIterator{
35+
Response: string(sec.Data),
36+
}
2737
in := secretmanagerpb.AccessSecretVersionRequest{
2838
Name: path,
2939
}
40+
inDelete := secretmanagerpb.DestroySecretVersionRequest{
41+
Name: path,
42+
}
43+
inList := secretmanagerpb.ListSecretsRequest{
44+
Parent: path,
45+
}
46+
inAdd := secretmanagerpb.AddSecretVersionRequest{
47+
Parent: path,
48+
Payload: &secretmanagerpb.SecretPayload{
49+
Data: []byte("some value"),
50+
},
51+
}
3052
client.EXPECT().AccessSecretVersion(gomock.Any(), gomock.Eq(&in)).Return(out, nil).AnyTimes()
53+
client.EXPECT().DestroySecretVersion(gomock.Any(), gomock.Eq(&inDelete)).Return(outDelete, nil).AnyTimes()
54+
client.EXPECT().ListSecrets(gomock.Any(), gomock.Eq(&inList)).Return(outList).AnyTimes()
55+
client.EXPECT().AddSecretVersion(gomock.Any(), gomock.Eq(&inAdd)).Return(outAdd, nil).AnyTimes()
3156
s := GoogleSecretManager{
3257
client: client,
3358
logger: GetTestLogger(),

pkg/providers/mock_providers/google_secretmanager_mock.go

+63-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)