Skip to content

Commit e43d208

Browse files
bug: service account key should use config ttl (#85)
* bug: service account key should use config ttl * Use configured TTL unless overridden by request * update makefile * code cleanup Co-authored-by: catsby <[email protected]>
1 parent f90dbea commit e43d208

File tree

4 files changed

+192
-21
lines changed

4 files changed

+192
-21
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ testcompile: fmtcheck generate
2929
done
3030

3131
test:
32-
@go test -short -parallel=40 ./...
32+
@go test -short -parallel=40 ./... $(TESTARGS)
3333

3434
test-acc:
35-
@go test -parallel=40 $(TESTARGS)
35+
@go test -parallel=40 ./... $(TESTARGS)
3636
# generate runs `go generate` to build the dynamically generated
3737
# source files.
3838
generate:

plugin/path_role_set_test.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestPathRoleSet_Basic(t *testing.T) {
2424
"roles/viewer": struct{}{},
2525
}
2626

27-
td := setupTest(t)
27+
td := setupTest(t, "0s", "2h")
2828
defer cleanup(t, td, rsName, roles)
2929

3030
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -53,6 +53,7 @@ func TestPathRoleSet_Basic(t *testing.T) {
5353
if respData == nil {
5454
t.Fatalf("expected role set to have been created")
5555
}
56+
5657
verifyReadData(t, respData, map[string]interface{}{
5758
"secret_type": SecretTypeAccessToken, // default
5859
"project": td.Project,
@@ -79,7 +80,7 @@ func TestPathRoleSet_UpdateKeyRoleSet(t *testing.T) {
7980
}
8081

8182
// Initial test set up - backend, initial config, test resources in project
82-
td := setupTest(t)
83+
td := setupTest(t, "0s", "2h")
8384
defer cleanup(t, td, rsName, initRoles.Union(updatedRoles))
8485

8586
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -179,7 +180,7 @@ func TestPathRoleSet_RotateKeyRoleSet(t *testing.T) {
179180
}
180181

181182
// Initial test set up - backend, initial config, test resources in project
182-
td := setupTest(t)
183+
td := setupTest(t, "0s", "2h")
183184
defer cleanup(t, td, rsName, roles)
184185

185186
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -239,7 +240,7 @@ func TestPathRoleSet_UpdateTokenRoleSet(t *testing.T) {
239240
}
240241

241242
// Initial test set up - backend, initial config, test resources in project
242-
td := setupTest(t)
243+
td := setupTest(t, "0s", "2h")
243244
defer cleanup(t, td, rsName, initRoles.Union(updatedRoles))
244245

245246
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -333,7 +334,7 @@ func TestPathRoleSet_RotateTokenRoleSet(t *testing.T) {
333334
}
334335

335336
// Initial test set up - backend, initial config, test resources in project
336-
td := setupTest(t)
337+
td := setupTest(t, "0s", "2h")
337338
defer cleanup(t, td, rsName, roles)
338339

339340
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -657,7 +658,7 @@ type testData struct {
657658
IamAdmin *iam.Service
658659
}
659660

660-
func setupTest(t *testing.T) *testData {
661+
func setupTest(t *testing.T, ttl, maxTTL string) *testData {
661662
proj := util.GetTestProject(t)
662663
credsJson, creds := util.GetTestCredentials(t)
663664
httpC, err := gcputil.GetHttpClient(creds, iam.CloudPlatformScope)
@@ -671,8 +672,11 @@ func setupTest(t *testing.T) *testData {
671672
}
672673

673674
b, reqStorage := getTestBackend(t)
675+
674676
testConfigUpdate(t, b, reqStorage, map[string]interface{}{
675677
"credentials": credsJson,
678+
"ttl": ttl,
679+
"max_ttl": maxTTL,
676680
})
677681

678682
return &testData{

plugin/secrets_service_account_key.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func pathSecretServiceAccountKey(b *backend) *framework.Path {
5858
Description: fmt.Sprintf(`Private key type for service account key - defaults to %s"`, privateKeyTypeJson),
5959
Default: privateKeyTypeJson,
6060
},
61-
"ttl": &framework.FieldSchema{
61+
"ttl": {
6262
Type: framework.TypeDurationSecond,
6363
Description: "Lifetime of the service account key",
6464
},
@@ -215,6 +215,10 @@ func (b *backend) getSecretKey(ctx context.Context, s logical.Storage, rs *RoleS
215215
resp := b.Secret(SecretTypeKey).Response(secretD, internalD)
216216
resp.Secret.Renewable = true
217217

218+
resp.Secret.MaxTTL = cfg.MaxTTL
219+
resp.Secret.TTL = cfg.TTL
220+
221+
// If the request came with a TTL value, overwrite the config default
218222
if ttl > 0 {
219223
resp.Secret.TTL = time.Duration(ttl) * time.Second
220224
}

plugin/secrets_test.go

Lines changed: 175 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestSecrets_GenerateAccessToken(t *testing.T) {
3333
secretType := SecretTypeAccessToken
3434
rsName := "test-gentoken"
3535

36-
td := setupTest(t)
36+
td := setupTest(t, "0s", "2h")
3737
defer cleanup(t, td, rsName, testRoles)
3838

3939
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -69,11 +69,11 @@ func TestSecrets_GenerateAccessToken(t *testing.T) {
6969
verifyProjectBindingsRemoved(t, td, sa.Email, testRoles)
7070
}
7171

72-
func TestSecrets_GenerateKey(t *testing.T) {
72+
func TestSecrets_GenerateKeyConfigTTL(t *testing.T) {
7373
secretType := SecretTypeKey
7474
rsName := "test-genkey"
7575

76-
td := setupTest(t)
76+
td := setupTest(t, "1h", "2h")
7777
defer cleanup(t, td, rsName, testRoles)
7878

7979
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
@@ -95,13 +95,148 @@ func TestSecrets_GenerateKey(t *testing.T) {
9595
// expect error for trying to read token from key roleset
9696
testGetTokenFail(t, td, rsName)
9797

98-
creds, secret := testGetKey(t, td, rsName)
98+
creds, resp := testGetKey(t, td, rsName)
99+
if int(resp.Secret.LeaseTotal().Hours()) != 1 {
100+
t.Fatalf("expected lease duration %d, got %d", 1, int(resp.Secret.LeaseTotal().Hours()))
101+
}
102+
103+
// Confirm calls with key work
104+
keyHttpC := oauth2.NewClient(context.Background(), creds.TokenSource)
105+
checkSecretPermissions(t, td, keyHttpC)
106+
107+
keyName := resp.Secret.InternalData["key_name"].(string)
108+
if keyName == "" {
109+
t.Fatalf("expected internal data to include key name")
110+
}
111+
112+
_, err = td.IamAdmin.Projects.ServiceAccounts.Keys.Get(keyName).Do()
113+
if err != nil {
114+
t.Fatalf("could not get key from given internal 'key_name': %v", err)
115+
}
116+
117+
testRenewSecretKey(t, td, resp.Secret)
118+
testRevokeSecretKey(t, td, resp.Secret)
119+
120+
k, err := td.IamAdmin.Projects.ServiceAccounts.Keys.Get(keyName).Do()
121+
122+
if k != nil {
123+
t.Fatalf("expected error as revoked key was deleted, instead got key: %v", k)
124+
}
125+
if err == nil || !isGoogleAccountKeyNotFoundErr(err) {
126+
t.Fatalf("expected 404 error from getting deleted key, instead got error: %v", err)
127+
}
128+
129+
// Cleanup: Delete role set
130+
testRoleSetDelete(t, td, rsName, sa.Name)
131+
verifyProjectBindingsRemoved(t, td, sa.Email, testRoles)
132+
}
133+
134+
func TestSecrets_GenerateKeyTTLOverride(t *testing.T) {
135+
secretType := SecretTypeKey
136+
rsName := "test-genkey"
137+
138+
td := setupTest(t, "1h", "2h")
139+
defer cleanup(t, td, rsName, testRoles)
140+
141+
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
142+
143+
// Create new role set
144+
expectedBinds := ResourceBindings{projRes: testRoles}
145+
bindsRaw, err := util.BindingsHCL(expectedBinds)
146+
if err != nil {
147+
t.Fatalf("unable to convert resource bindings to HCL string: %v", err)
148+
}
149+
testRoleSetCreate(t, td, rsName,
150+
map[string]interface{}{
151+
"secret_type": secretType,
152+
"project": td.Project,
153+
"bindings": bindsRaw,
154+
})
155+
sa := getRoleSetAccount(t, td, rsName)
156+
157+
// expect error for trying to read token from key roleset
158+
testGetTokenFail(t, td, rsName)
159+
160+
// call the POST endpoint of /gcp/key/:roleset with updated TTL
161+
creds, resp := testPostKey(t, td, rsName, "60s")
162+
if int(resp.Secret.LeaseTotal().Seconds()) != 60 {
163+
t.Fatalf("expected lease duration %d, got %d", 60, int(resp.Secret.LeaseTotal().Seconds()))
164+
}
165+
166+
// Confirm calls with key work
167+
keyHttpC := oauth2.NewClient(context.Background(), creds.TokenSource)
168+
checkSecretPermissions(t, td, keyHttpC)
169+
170+
keyName := resp.Secret.InternalData["key_name"].(string)
171+
if keyName == "" {
172+
t.Fatalf("expected internal data to include key name")
173+
}
174+
175+
_, err = td.IamAdmin.Projects.ServiceAccounts.Keys.Get(keyName).Do()
176+
if err != nil {
177+
t.Fatalf("could not get key from given internal 'key_name': %v", err)
178+
}
179+
180+
testRenewSecretKey(t, td, resp.Secret)
181+
testRevokeSecretKey(t, td, resp.Secret)
182+
183+
k, err := td.IamAdmin.Projects.ServiceAccounts.Keys.Get(keyName).Do()
184+
185+
if k != nil {
186+
t.Fatalf("expected error as revoked key was deleted, instead got key: %v", k)
187+
}
188+
if err == nil || !isGoogleAccountKeyNotFoundErr(err) {
189+
t.Fatalf("expected 404 error from getting deleted key, instead got error: %v", err)
190+
}
191+
192+
// Cleanup: Delete role set
193+
testRoleSetDelete(t, td, rsName, sa.Name)
194+
verifyProjectBindingsRemoved(t, td, sa.Email, testRoles)
195+
}
196+
197+
// TestSecrets_GenerateKeyMaxTTLCheck verifies the MaxTTL is set for the
198+
// configured backend
199+
func TestSecrets_GenerateKeyMaxTTLCheck(t *testing.T) {
200+
secretType := SecretTypeKey
201+
rsName := "test-genkey"
202+
203+
td := setupTest(t, "1h", "2h")
204+
defer cleanup(t, td, rsName, testRoles)
205+
206+
projRes := fmt.Sprintf(testProjectResourceTemplate, td.Project)
207+
208+
// Create new role set
209+
expectedBinds := ResourceBindings{projRes: testRoles}
210+
bindsRaw, err := util.BindingsHCL(expectedBinds)
211+
if err != nil {
212+
t.Fatalf("unable to convert resource bindings to HCL string: %v", err)
213+
}
214+
testRoleSetCreate(t, td, rsName,
215+
map[string]interface{}{
216+
"secret_type": secretType,
217+
"project": td.Project,
218+
"bindings": bindsRaw,
219+
})
220+
sa := getRoleSetAccount(t, td, rsName)
221+
222+
// expect error for trying to read token from key roleset
223+
testGetTokenFail(t, td, rsName)
224+
225+
// call the POST endpoint of /gcp/key/:roleset with updated TTL
226+
creds, resp := testPostKey(t, td, rsName, "60s")
227+
if int(resp.Secret.LeaseTotal().Seconds()) != 60 {
228+
t.Fatalf("expected lease duration %d, got %d", 60, int(resp.Secret.LeaseTotal().Seconds()))
229+
}
230+
231+
if int(resp.Secret.LeaseOptions.MaxTTL.Hours()) != 2 {
232+
t.Fatalf("expected max lease %d, got %d", 2, int(resp.Secret.LeaseOptions.MaxTTL.Hours()))
233+
}
99234

100235
// Confirm calls with key work
101236
keyHttpC := oauth2.NewClient(context.Background(), creds.TokenSource)
102237
checkSecretPermissions(t, td, keyHttpC)
103238

104-
keyName := secret.InternalData["key_name"].(string)
239+
keyName := resp.Secret.InternalData["key_name"].(string)
105240
if keyName == "" {
106241
t.Fatalf("expected internal data to include key name")
107242
}
@@ -111,8 +246,8 @@ func TestSecrets_GenerateKey(t *testing.T) {
111246
t.Fatalf("could not get key from given internal 'key_name': %v", err)
112247
}
113248

114-
testRenewSecretKey(t, td, secret)
115-
testRevokeSecretKey(t, td, secret)
249+
testRenewSecretKey(t, td, resp.Secret)
250+
testRevokeSecretKey(t, td, resp.Secret)
116251

117252
k, err := td.IamAdmin.Projects.ServiceAccounts.Keys.Get(keyName).Do()
118253

@@ -211,11 +346,18 @@ func testGetToken(t *testing.T, td *testData, rsName string) (token string) {
211346
return tokenRaw.(string)
212347
}
213348

214-
func testGetKey(t *testing.T, td *testData, rsName string) (*google.Credentials, *logical.Secret) {
349+
// testPostKey enables the POST call to /gcp/key/:roleset
350+
func testPostKey(t *testing.T, td *testData, rsName, ttl string) (*google.Credentials, *logical.Response) {
351+
data := map[string]interface{}{}
352+
if ttl != "" {
353+
data["ttl"] = ttl
354+
}
355+
215356
resp, err := td.B.HandleRequest(context.Background(), &logical.Request{
216-
Operation: logical.ReadOperation,
357+
Operation: logical.UpdateOperation,
217358
Path: fmt.Sprintf("key/%s", rsName),
218359
Storage: td.S,
360+
Data: data,
219361
})
220362

221363
if err != nil {
@@ -227,12 +369,33 @@ func testGetKey(t *testing.T, td *testData, rsName string) (*google.Credentials,
227369
if resp == nil || resp.Secret == nil {
228370
t.Fatalf("expected response with secret, got response: %v", resp)
229371
}
230-
if resp.Secret.ExpirationTime().Sub(resp.Secret.IssueTime) > defaultLeaseTTLHr*time.Hour {
231-
t.Fatalf("unexpected lease duration is longer than backend default")
372+
373+
creds := getGoogleCredentials(t, resp.Data)
374+
return creds, resp
375+
}
376+
377+
func testGetKey(t *testing.T, td *testData, rsName string) (*google.Credentials, *logical.Response) {
378+
data := map[string]interface{}{}
379+
380+
resp, err := td.B.HandleRequest(context.Background(), &logical.Request{
381+
Operation: logical.ReadOperation,
382+
Path: fmt.Sprintf("key/%s", rsName),
383+
Storage: td.S,
384+
Data: data,
385+
})
386+
387+
if err != nil {
388+
t.Fatal(err)
389+
}
390+
if resp != nil && resp.IsError() {
391+
t.Fatal(resp.Error())
392+
}
393+
if resp == nil || resp.Secret == nil {
394+
t.Fatalf("expected response with secret, got response: %v", resp)
232395
}
233396

234397
creds := getGoogleCredentials(t, resp.Data)
235-
return creds, resp.Secret
398+
return creds, resp
236399
}
237400

238401
func testRenewSecretKey(t *testing.T, td *testData, sec *logical.Secret) {

0 commit comments

Comments
 (0)