1
2
3
4 package secretmanager
5
6 import (
7 "bytes"
8 "context"
9 "fmt"
10 "reflect"
11 "strconv"
12 "strings"
13 "time"
14
15 secretmanager "cloud.google.com/go/secretmanager/apiv1"
16 secretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
17 "google.golang.org/api/iterator"
18 "google.golang.org/api/option"
19 "google.golang.org/grpc/codes"
20 "google.golang.org/grpc/status"
21 fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb"
22 "google.golang.org/protobuf/types/known/timestamppb"
23 )
24
25
26 type SecretManager struct {
27 Client *secretmanager.Client
28 projectID string
29 }
30
31
32
33
34 func New(ctx context.Context, googleAppCredsPath string, projectID string) (SecretManager, error) {
35 var opts option.ClientOption
36 if googleAppCredsPath != "" {
37 opts = option.WithCredentialsFile(googleAppCredsPath)
38 }
39
40 client, err := secretmanager.NewClient(ctx, opts)
41 if err != nil {
42 return SecretManager{}, err
43 }
44
45 return SecretManager{
46 projectID: projectID,
47 Client: client,
48 }, nil
49 }
50
51
52
53 func NewWithOptions(ctx context.Context, projectID string, opts ...option.ClientOption) (SecretManager, error) {
54 client, err := secretmanager.NewClient(ctx, opts...)
55 if err != nil {
56 return SecretManager{}, err
57 }
58
59 return SecretManager{
60 projectID: projectID,
61 Client: client,
62 }, nil
63 }
64
65
66
67 func DecodeSecretManagerSecretVersion(name string) (int, error) {
68 path := strings.Split(name, "/")
69 versionParsed := path[len(path)-1]
70 return strconv.Atoi(versionParsed)
71 }
72
73
74 func (s SecretManager) GetSecret(ctx context.Context, secretID string) (*secretmanagerpb.Secret, error) {
75 req := &secretmanagerpb.GetSecretRequest{Name: s.secretRef(secretID)}
76
77 return s.Client.GetSecret(ctx, req)
78 }
79
80
81 func (s SecretManager) ListSecrets(ctx context.Context, pageToken string) ([]*secretmanagerpb.Secret, error) {
82 var results []*secretmanagerpb.Secret
83 req := &secretmanagerpb.ListSecretsRequest{
84 Parent: s.projectRef(),
85 PageToken: pageToken,
86 }
87 t1 := s.Client.ListSecrets(ctx, req)
88 for {
89 secret, err := t1.Next()
90 if err == iterator.Done {
91 return results, nil
92 }
93 if err != nil {
94 return nil, err
95 }
96 results = append(results, secret)
97 }
98 }
99
100
101 func (s SecretManager) GetLatestSecretValue(ctx context.Context, secretID string) ([]byte, error) {
102 return s.GetSecretVersionValue(ctx, secretID, "latest")
103 }
104
105
106 func (s SecretManager) GetSecretVersionValue(ctx context.Context, secretID string, version string) ([]byte, error) {
107 getVersionReq := &secretmanagerpb.AccessSecretVersionRequest{
108 Name: fmt.Sprintf("%s/versions/%s", s.secretRef(secretID), version),
109 }
110 latestVersion, err := s.Client.AccessSecretVersion(ctx, getVersionReq)
111 if err != nil {
112 return nil, err
113 }
114 return latestVersion.Payload.Data, nil
115 }
116
117
118 func (s SecretManager) GetSecretVersionValueInfo(ctx context.Context, secretID, version string) (*secretmanagerpb.SecretVersion, error) {
119 getVersionReq := &secretmanagerpb.GetSecretVersionRequest{
120 Name: fmt.Sprintf("%s/versions/%s", s.secretRef(secretID), version),
121 }
122 latestVersion, err := s.Client.GetSecretVersion(ctx, getVersionReq)
123 if err != nil {
124 return nil, err
125 }
126 return latestVersion, nil
127 }
128
129
130 func (s SecretManager) GetLatestSecretValueInfo(ctx context.Context, secretID string) (*secretmanagerpb.SecretVersion, error) {
131 return s.GetSecretVersionValueInfo(ctx, secretID, "latest")
132 }
133
134
135
136
137
138
139 func (s SecretManager) AddSecret(ctx context.Context, secretID string, secretValue []byte, labels map[string]string, forceLabelsUpdate bool, expireAt *time.Time, versionAlias string) error {
140
141 secret, err := s.GetSecret(ctx, secretID)
142 code := status.Code(err)
143
144 if code == codes.NotFound {
145 secret, err = s.createNewSecret(ctx, secretID, labels, expireAt)
146 if err != nil {
147 return err
148 }
149
150 if err := s.addSecretVersion(ctx, secret, secretValue); err != nil {
151 return err
152 }
153
154 if versionAlias == "" {
155 return nil
156 }
157 return s.updateSecretVersionAlias(ctx, secret, secretID, versionAlias)
158 } else if code != codes.OK {
159
160
161 return err
162 }
163
164 if !reflect.DeepEqual(secret.Labels, labels) {
165 if !forceLabelsUpdate {
166 return fmt.Errorf("this secret is out of date, please delete and recreate it")
167 }
168 secret.Labels = labels
169 updateSecretReq := &secretmanagerpb.UpdateSecretRequest{
170 Secret: secret,
171 UpdateMask: &fieldmaskpb.FieldMask{
172 Paths: []string{"labels"},
173 },
174 }
175 secret, err = s.Client.UpdateSecret(ctx, updateSecretReq)
176 if err != nil {
177 return err
178 }
179 }
180
181
182 getVersionReq := &secretmanagerpb.AccessSecretVersionRequest{
183 Name: fmt.Sprintf("%s/versions/latest", s.secretRef(secretID)),
184 }
185 latestVersion, err := s.Client.AccessSecretVersion(ctx, getVersionReq)
186 if err != nil {
187 return err
188 }
189
190 if !bytes.Equal(secretValue, latestVersion.Payload.Data) {
191 if err = s.addSecretVersion(ctx, secret, secretValue); err != nil {
192 return err
193 }
194 }
195
196
197 if versionAlias == "" {
198 return nil
199 }
200 return s.updateSecretVersionAlias(ctx, secret, secretID, versionAlias)
201 }
202
203
204
205
206 func (s SecretManager) AddSecrets(ctx context.Context, secrets map[string][]byte) error {
207 var errs []string
208 for k, v := range secrets {
209 if err := s.AddSecret(ctx, k, v, nil, false, nil, ""); err != nil {
210 errs = append(errs, fmt.Sprintf("error adding secret '%s': %s", k, err.Error()))
211 }
212 }
213 if len(errs) > 0 {
214 return fmt.Errorf("adding secrets failed with error(s): %s", strings.Join(errs, ", "))
215 }
216 return nil
217 }
218
219
220 func (s SecretManager) DeleteSecret(ctx context.Context, secretID string) error {
221
222 deleteSecretReq := &secretmanagerpb.DeleteSecretRequest{
223 Name: s.secretRef(secretID),
224 }
225
226 return s.Client.DeleteSecret(ctx, deleteSecretReq)
227 }
228
229 func (s SecretManager) GetProjectID() string { return s.projectID }
230
231 func (s *SecretManager) SetProjectID(newProjectID string) { s.projectID = newProjectID }
232
233 func (s SecretManager) addSecretVersion(ctx context.Context, secret *secretmanagerpb.Secret, secretValue []byte) error {
234
235 addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
236 Parent: secret.Name,
237 Payload: &secretmanagerpb.SecretPayload{
238 Data: secretValue,
239 },
240 }
241
242
243 _, err := s.Client.AddSecretVersion(ctx, addSecretVersionReq)
244 if err != nil {
245 return err
246 }
247
248 return nil
249 }
250
251 func (s SecretManager) secretRef(secretID string) string {
252 return fmt.Sprintf("projects/%s/secrets/%s", s.projectID, secretID)
253 }
254
255 func (s SecretManager) projectRef() string {
256 return fmt.Sprintf("projects/%s", s.projectID)
257 }
258
259 func (s SecretManager) createNewSecret(ctx context.Context, secretID string, labels map[string]string, expireAt *time.Time) (*secretmanagerpb.Secret, error) {
260 createSecretReq := &secretmanagerpb.CreateSecretRequest{
261 Parent: fmt.Sprintf("projects/%s", s.projectID),
262 SecretId: secretID,
263 Secret: &secretmanagerpb.Secret{
264 Replication: &secretmanagerpb.Replication{
265 Replication: &secretmanagerpb.Replication_Automatic_{
266 Automatic: &secretmanagerpb.Replication_Automatic{},
267 },
268 },
269 Labels: labels,
270 },
271 }
272
273 if expireAt != nil {
274 createSecretReq.Secret.Expiration = &secretmanagerpb.Secret_ExpireTime{
275 ExpireTime: timestamppb.New(*expireAt),
276 }
277 }
278 return s.Client.CreateSecret(ctx, createSecretReq)
279 }
280
281 func (s SecretManager) updateSecretVersionAlias(ctx context.Context, secret *secretmanagerpb.Secret, secretID, versionAlias string) error {
282 trueVersion, err := s.trueSecretVersion(ctx, secretID)
283 if err != nil {
284 return err
285 }
286
287 if secret.VersionAliases == nil {
288 secret.VersionAliases = map[string]int64{}
289 }
290 secret.VersionAliases[versionAlias] = int64(trueVersion)
291 updateSecretReq := &secretmanagerpb.UpdateSecretRequest{
292 Secret: secret,
293 UpdateMask: &fieldmaskpb.FieldMask{
294 Paths: []string{"version_aliases"},
295 },
296 }
297 _, err = s.Client.UpdateSecret(ctx, updateSecretReq)
298 return err
299 }
300
301 func (s SecretManager) trueSecretVersion(ctx context.Context, secretID string) (int, error) {
302 latestSecretInfo, err := s.GetLatestSecretValueInfo(ctx, secretID)
303 if err != nil && status.Code(err) != codes.NotFound {
304 return 1, err
305 } else if err != nil && status.Code(err) == codes.NotFound {
306 return 1, nil
307 }
308
309 return DecodeSecretManagerSecretVersion(latestSecretInfo.Name)
310 }
311
View as plain text