Skip to content

Commit ff49794

Browse files
authored
Merge pull request #35 from n-e/short-secrets
Allow omitting the project id in google secrets
2 parents 6b56dca + 3241b3f commit ff49794

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ MY_DB_PASSWORD=gcp:secretmanager:projects/$PROJECT_ID/secrets/mydbpassword/versi
7070
MY_DB_PASSWORD=very-secret-password
7171
```
7272

73+
#### Project auto-detection
74+
75+
If secret-manager is running in an environment where the Google metadata server is available, or the `-google-project` flag is set, the secret path may be omitted, and the current project is used.
76+
77+
```sh
78+
MY_DB_PASSWORD=mydbpassword
79+
MY_DB_PASSWORD=mydbpassword/versions/2
80+
```
81+
7382
### Requirement
7483

7584
#### Container

main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func main() {
4949
Usage: "supported secrets manager provider ['aws', 'google']",
5050
Value: "aws",
5151
},
52+
&cli.StringFlag{
53+
Name: "google-project",
54+
Usage: "the google cloud project for secrets without a project prefix",
55+
},
5256
},
5357
Commands: []*cli.Command{
5458
{
@@ -129,7 +133,7 @@ func mainCmd(c *cli.Context) error {
129133
if c.String("provider") == "aws" {
130134
provider, err = aws.NewAwsSecretsProvider()
131135
} else if c.String("provider") == "google" {
132-
provider, err = google.NewGoogleSecretsProvider(ctx)
136+
provider, err = google.NewGoogleSecretsProvider(ctx, c.String("google-project"))
133137
}
134138
if err != nil {
135139
log.WithField("provider", c.String("provider")).WithError(err).Error("failed to initialize secrets provider")

pkg/secrets/google/secrets.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,39 @@ package google
22

33
import (
44
"context"
5+
"fmt"
6+
"regexp"
57
"strings"
68

79
"secrets-init/pkg/secrets" //nolint:gci
810

11+
"cloud.google.com/go/compute/metadata"
912
secretmanager "cloud.google.com/go/secretmanager/apiv1"
1013
"github.com/pkg/errors"
14+
log "github.com/sirupsen/logrus"
1115
secretspb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" //nolint:gci
1216
)
1317

1418
// SecretsProvider Google Cloud secrets provider
1519
type SecretsProvider struct {
16-
sm SecretsManagerAPI
20+
sm SecretsManagerAPI
21+
projectID string
1722
}
1823

1924
// NewGoogleSecretsProvider init Google Secrets Provider
20-
func NewGoogleSecretsProvider(ctx context.Context) (secrets.Provider, error) {
25+
func NewGoogleSecretsProvider(ctx context.Context, projectID string) (secrets.Provider, error) {
2126
sp := SecretsProvider{}
2227
var err error
28+
29+
if projectID != "" {
30+
sp.projectID = projectID
31+
} else {
32+
sp.projectID, err = metadata.ProjectID()
33+
if err != nil {
34+
log.WithError(err).Infoln("The Google project cannot be detected, you won't be able to use the short secret version")
35+
}
36+
}
37+
2338
sp.sm, err = secretmanager.NewClient(ctx)
2439
if err != nil {
2540
return nil, errors.Wrap(err, "failed to initialize Google Cloud SDK")
@@ -33,14 +48,29 @@ func NewGoogleSecretsProvider(ctx context.Context) (secrets.Provider, error) {
3348
//
3449
// `gcp:secretmanager:projects/{PROJECT_ID}/secrets/{SECRET_NAME}`
3550
// `gcp:secretmanager:projects/{PROJECT_ID}/secrets/{SECRET_NAME}/versions/{VERSION|latest}`
51+
// `gcp:secretmanager:{SECRET_NAME}
52+
// `gcp:secretmanager:{SECRET_NAME}/versions/{VERSION|latest}`
3653
func (sp SecretsProvider) ResolveSecrets(ctx context.Context, vars []string) ([]string, error) {
3754
envs := make([]string, 0, len(vars))
55+
56+
fullSecretRe := regexp.MustCompile("projects/[^/]+/secrets/[^/+](/version/[^/+])?")
57+
3858
for _, env := range vars {
3959
kv := strings.Split(env, "=")
4060
key, value := kv[0], kv[1]
4161
if strings.HasPrefix(value, "gcp:secretmanager:") {
4262
// construct valid secret name
4363
name := strings.TrimPrefix(value, "gcp:secretmanager:")
64+
65+
isLong := fullSecretRe.MatchString(name)
66+
67+
if !isLong {
68+
if sp.projectID == "" {
69+
return vars, errors.Errorf("failed to get secret \"%s\" from Google Secret Manager (unknown project)", name)
70+
}
71+
name = fmt.Sprintf("projects/%s/secrets/%s", sp.projectID, name)
72+
}
73+
4474
// if no version specified add latest
4575
if !strings.Contains(name, "/versions/") {
4676
name += "/versions/latest"

pkg/secrets/google/secrets_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,29 @@ func TestSecretsProvider_ResolveSecrets(t *testing.T) {
5252
return &sp
5353
},
5454
},
55+
{
56+
name: "get implicit (latest) version single secret from Secrets Manager with the shorthand syntax",
57+
args: args{
58+
ctx: context.TODO(),
59+
vars: []string{
60+
"test-secret=gcp:secretmanager:test-secret",
61+
},
62+
},
63+
want: []string{
64+
"test-secret=test-secret-value",
65+
},
66+
mockServiceProvider: func(ctx context.Context, mockSM *mocks.GoogleSecretsManagerAPI) secrets.Provider {
67+
sp := SecretsProvider{sm: mockSM, projectID: "test-project-id"}
68+
req := secretspb.AccessSecretVersionRequest{
69+
Name: "projects/test-project-id/secrets/test-secret/versions/latest",
70+
}
71+
res := secretspb.AccessSecretVersionResponse{Payload: &secretspb.SecretPayload{
72+
Data: []byte("test-secret-value"),
73+
}}
74+
mockSM.On("AccessSecretVersion", ctx, &req).Return(&res, nil)
75+
return &sp
76+
},
77+
},
5578
{
5679
name: "get explicit single secret version from Secrets Manager",
5780
args: args{
@@ -75,6 +98,29 @@ func TestSecretsProvider_ResolveSecrets(t *testing.T) {
7598
return &sp
7699
},
77100
},
101+
{
102+
name: "get explicit single secret version from Secrets Manager with the shorthand syntax",
103+
args: args{
104+
ctx: context.TODO(),
105+
vars: []string{
106+
"test-secret=gcp:secretmanager:test-secret/versions/5",
107+
},
108+
},
109+
want: []string{
110+
"test-secret=test-secret-value",
111+
},
112+
mockServiceProvider: func(ctx context.Context, mockSM *mocks.GoogleSecretsManagerAPI) secrets.Provider {
113+
sp := SecretsProvider{sm: mockSM, projectID: "test-project-id"}
114+
req := secretspb.AccessSecretVersionRequest{
115+
Name: "projects/test-project-id/secrets/test-secret/versions/5",
116+
}
117+
res := secretspb.AccessSecretVersionResponse{Payload: &secretspb.SecretPayload{
118+
Data: []byte("test-secret-value"),
119+
}}
120+
mockSM.On("AccessSecretVersion", ctx, &req).Return(&res, nil)
121+
return &sp
122+
},
123+
},
78124
{
79125
name: "get 2 secrets from Secrets Manager",
80126
args: args{

0 commit comments

Comments
 (0)