Skip to content

Commit f449eca

Browse files
authoredMar 21, 2025··
GCP Secret Manager and Storage Bucket Binding implicit authentication support with Workload Identity (#3711)
Signed-off-by: Anton Troshin <anton@diagrid.io>
1 parent 5ede374 commit f449eca

File tree

4 files changed

+137
-23
lines changed

4 files changed

+137
-23
lines changed
 

‎bindings/gcp/bucket/bucket.go

+36-7
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,7 @@ func (g *GCPStorage) Init(ctx context.Context, metadata bindings.Metadata) error
104104
return err
105105
}
106106

107-
b, err := json.Marshal(m)
108-
if err != nil {
109-
return err
110-
}
111-
112-
clientOptions := option.WithCredentialsJSON(b)
113-
client, err := storage.NewClient(ctx, clientOptions)
107+
client, err := g.getClient(ctx, m)
114108
if err != nil {
115109
return err
116110
}
@@ -121,6 +115,41 @@ func (g *GCPStorage) Init(ctx context.Context, metadata bindings.Metadata) error
121115
return nil
122116
}
123117

118+
func (g *GCPStorage) getClient(ctx context.Context, m *gcpMetadata) (*storage.Client, error) {
119+
var client *storage.Client
120+
var err error
121+
122+
if m.Bucket == "" {
123+
return nil, errors.New("missing property `bucket` in metadata")
124+
}
125+
if m.ProjectID == "" {
126+
return nil, errors.New("missing property `project_id` in metadata")
127+
}
128+
129+
// Explicit authentication
130+
if m.PrivateKeyID != "" {
131+
var b []byte
132+
b, err = json.Marshal(m)
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
clientOptions := option.WithCredentialsJSON(b)
138+
client, err = storage.NewClient(ctx, clientOptions)
139+
if err != nil {
140+
return nil, err
141+
}
142+
} else {
143+
// Implicit authentication, using GCP Application Default Credentials (ADC)
144+
// Credentials search order: https://cloud.google.com/docs/authentication/application-default-credentials#order
145+
client, err = storage.NewClient(ctx)
146+
if err != nil {
147+
return nil, err
148+
}
149+
}
150+
return client, nil
151+
}
152+
124153
func (g *GCPStorage) parseMetadata(meta bindings.Metadata) (*gcpMetadata, error) {
125154
m := gcpMetadata{}
126155
err := kitmd.DecodeMetadata(meta.Properties, &m)

‎bindings/gcp/bucket/bucket_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package bucket
1616
import (
1717
"context"
1818
"encoding/json"
19+
"errors"
1920
"testing"
2021

2122
"github.com/stretchr/testify/assert"
@@ -235,6 +236,30 @@ func TestMergeWithRequestMetadata(t *testing.T) {
235236
})
236237
}
237238

239+
func TestInit(t *testing.T) {
240+
t.Run("Init missing bucket from metadata", func(t *testing.T) {
241+
m := bindings.Metadata{}
242+
m.Properties = map[string]string{
243+
"projectID": "my_project_id",
244+
}
245+
gs := GCPStorage{logger: logger.NewLogger("test")}
246+
err := gs.Init(context.Background(), m)
247+
require.Error(t, err)
248+
assert.Equal(t, err, errors.New("missing property `bucket` in metadata"))
249+
})
250+
251+
t.Run("Init missing projectID from metadata", func(t *testing.T) {
252+
m := bindings.Metadata{}
253+
m.Properties = map[string]string{
254+
"bucket": "my_bucket",
255+
}
256+
gs := GCPStorage{logger: logger.NewLogger("test")}
257+
err := gs.Init(context.Background(), m)
258+
require.Error(t, err)
259+
assert.Equal(t, err, errors.New("missing property `project_id` in metadata"))
260+
})
261+
}
262+
238263
func TestGetOption(t *testing.T) {
239264
gs := GCPStorage{logger: logger.NewLogger("test")}
240265
gs.metadata = &gcpMetadata{}

‎secretstores/gcp/secretmanager/secretmanager.go

+31-14
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,38 @@ func (s *Store) Init(ctx context.Context, metadataRaw secretstores.Metadata) err
8888
}
8989

9090
func (s *Store) getClient(ctx context.Context, metadata *GcpSecretManagerMetadata) (*secretmanager.Client, error) {
91-
b, _ := json.Marshal(metadata)
92-
clientOptions := option.WithCredentialsJSON(b)
91+
var client *secretmanager.Client
92+
var err error
9393

94-
client, err := secretmanager.NewClient(ctx, clientOptions)
95-
if err != nil {
96-
return nil, err
94+
if metadata.ProjectID == "" {
95+
return nil, errors.New("missing property `project_id` in metadata")
96+
}
97+
98+
// Explicit authentication
99+
if metadata.PrivateKeyID != "" {
100+
if metadata.Type == "" {
101+
return nil, errors.New("missing property `type` in metadata")
102+
}
103+
if metadata.PrivateKey == "" {
104+
return nil, errors.New("missing property `private_key` in metadata")
105+
}
106+
if metadata.ClientEmail == "" {
107+
return nil, errors.New("missing property `client_email` in metadata")
108+
}
109+
110+
b, _ := json.Marshal(metadata)
111+
clientOptions := option.WithCredentialsJSON(b)
112+
client, err = secretmanager.NewClient(ctx, clientOptions)
113+
if err != nil {
114+
return nil, err
115+
}
116+
} else {
117+
// Implicit authentication, using GCP Application Default Credentials (ADC)
118+
// Credentials search order: https://cloud.google.com/docs/authentication/application-default-credentials#order
119+
client, err = secretmanager.NewClient(ctx)
120+
if err != nil {
121+
return nil, err
122+
}
97123
}
98124

99125
return client, nil
@@ -183,18 +209,9 @@ func (s *Store) parseSecretManagerMetadata(metadataRaw secretstores.Metadata) (*
183209
return nil, fmt.Errorf("failed to decode metadata: %w", err)
184210
}
185211

186-
if meta.Type == "" {
187-
return nil, errors.New("missing property `type` in metadata")
188-
}
189212
if meta.ProjectID == "" {
190213
return nil, errors.New("missing property `project_id` in metadata")
191214
}
192-
if meta.PrivateKey == "" {
193-
return nil, errors.New("missing property `private_key` in metadata")
194-
}
195-
if meta.ClientEmail == "" {
196-
return nil, errors.New("missing property `client_email` in metadata")
197-
}
198215

199216
return &meta, nil
200217
}

‎secretstores/gcp/secretmanager/secretmanager_test.go

+45-2
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,47 @@ func TestInit(t *testing.T) {
7676

7777
t.Run("Init with missing `type` metadata", func(t *testing.T) {
7878
m.Properties = map[string]string{
79-
"dummy": "a",
79+
"dummy": "a",
80+
"private_key_id": "a",
81+
"project_id": "a",
8082
}
8183
err := sm.Init(ctx, m)
8284
require.Error(t, err)
83-
assert.Equal(t, err, errors.New("missing property `type` in metadata"))
85+
assert.Equal(t, errors.New("failed to setup secretmanager client: missing property `type` in metadata"), err)
86+
})
87+
88+
t.Run("Init with missing `private_key` metadata", func(t *testing.T) {
89+
m.Properties = map[string]string{
90+
"dummy": "a",
91+
"private_key_id": "a",
92+
"type": "a",
93+
"project_id": "a",
94+
}
95+
err := sm.Init(ctx, m)
96+
require.Error(t, err)
97+
assert.Equal(t, errors.New("failed to setup secretmanager client: missing property `private_key` in metadata"), err)
98+
})
99+
100+
t.Run("Init with missing `client_email` metadata", func(t *testing.T) {
101+
m.Properties = map[string]string{
102+
"dummy": "a",
103+
"private_key_id": "a",
104+
"private_key": "a",
105+
"type": "a",
106+
"project_id": "a",
107+
}
108+
err := sm.Init(ctx, m)
109+
require.Error(t, err)
110+
assert.Equal(t, errors.New("failed to setup secretmanager client: missing property `client_email` in metadata"), err)
111+
})
112+
113+
t.Run("Init with missing `project_id` metadata", func(t *testing.T) {
114+
m.Properties = map[string]string{
115+
"type": "service_account",
116+
}
117+
err := sm.Init(ctx, m)
118+
require.Error(t, err)
119+
assert.Equal(t, err, errors.New("missing property `project_id` in metadata"))
84120
})
85121

86122
t.Run("Init with missing `project_id` metadata", func(t *testing.T) {
@@ -91,6 +127,13 @@ func TestInit(t *testing.T) {
91127
require.Error(t, err)
92128
assert.Equal(t, err, errors.New("missing property `project_id` in metadata"))
93129
})
130+
131+
t.Run("Init with empty metadata", func(t *testing.T) {
132+
m.Properties = map[string]string{}
133+
err := sm.Init(ctx, m)
134+
require.Error(t, err)
135+
assert.Equal(t, err, errors.New("missing property `project_id` in metadata"))
136+
})
94137
}
95138

96139
func TestGetSecret(t *testing.T) {

0 commit comments

Comments
 (0)
Please sign in to comment.