Skip to content

Commit 52af34e

Browse files
authored
Prepare for v0.21.0 release (#309)
* Prepare for v0.21.0 release * add test * update test
1 parent cb729dd commit 52af34e

File tree

2 files changed

+222
-1
lines changed

2 files changed

+222
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Unreleased
22

3+
## v0.21.0
4+
5+
NO CHANGES
6+
37
## v0.20.3
48

59
BUG FIXES:

path_login_test.go

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func setupBackend(t *testing.T, cfg testConfig) (closeableBackend, logical.Stora
151151
return cb, storage
152152
}
153153

154-
func getTestJWT(t *testing.T, privKey string, cl sqjwt.Claims, privateCl interface{}) (string, *ecdsa.PrivateKey) {
154+
func getTestJWT(t *testing.T, privKey string, cl interface{}, privateCl interface{}) (string, *ecdsa.PrivateKey) {
155155
t.Helper()
156156
var key *ecdsa.PrivateKey
157157
block, _ := pem.Decode([]byte(privKey))
@@ -208,6 +208,223 @@ func getTestOIDC(t *testing.T) string {
208208
return out.AccessToken
209209
}
210210

211+
// TestLoginBoundAudiences tests that the login JWT's aud claim is ignored if
212+
// it is a single string. This is a case that is fixed in later versions of
213+
// the plugin.
214+
// See https://github.com/hashicorp/vault-plugin-auth-jwt/pull/308
215+
func TestLoginBoundAudiences(t *testing.T) {
216+
testCases := []struct {
217+
name string
218+
roleData map[string]interface{}
219+
jwtData map[string]interface{}
220+
expectErr bool
221+
}{
222+
{
223+
name: "jwt with string aud and no bound_audiences",
224+
roleData: map[string]interface{}{
225+
"role_type": "jwt",
226+
"bound_subject": "subject",
227+
"user_claim": "https://vault/user",
228+
"policies": "test",
229+
"period": "3s",
230+
"ttl": "1s",
231+
"num_uses": 12,
232+
"max_ttl": "5s",
233+
},
234+
jwtData: map[string]interface{}{
235+
"sub": "subject",
236+
"iss": "https://team-vault.auth0.com/",
237+
"aud": "https://vault.plugin.auth.jwt.test",
238+
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
239+
},
240+
expectErr: true,
241+
},
242+
{
243+
name: "jwt with array aud and no bound_audiences",
244+
// bound_audiences is unset
245+
roleData: map[string]interface{}{
246+
"role_type": "jwt",
247+
"bound_subject": "subject",
248+
"user_claim": "https://vault/user",
249+
"policies": "test",
250+
"period": "3s",
251+
"ttl": "1s",
252+
"num_uses": 12,
253+
"max_ttl": "5s",
254+
},
255+
jwtData: map[string]interface{}{
256+
"sub": "subject",
257+
"iss": "https://team-vault.auth0.com/",
258+
"aud": []string{"https://vault.plugin.auth.jwt.test"},
259+
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
260+
},
261+
expectErr: true,
262+
},
263+
{
264+
name: "jwt with array aud and role with bound_audiences",
265+
roleData: map[string]interface{}{
266+
"role_type": "jwt",
267+
"bound_audiences": []string{"https://vault.plugin.auth.jwt.test", "another_audience"},
268+
"bound_subject": "subject",
269+
"user_claim": "https://vault/user",
270+
"policies": "test",
271+
"period": "3s",
272+
"ttl": "1s",
273+
"num_uses": 12,
274+
"max_ttl": "5s",
275+
},
276+
// aud is an array
277+
jwtData: map[string]interface{}{
278+
"sub": "subject",
279+
"iss": "https://team-vault.auth0.com/",
280+
"aud": []string{"https://vault.plugin.auth.jwt.test"},
281+
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
282+
},
283+
expectErr: false,
284+
},
285+
{
286+
name: "error jwt with string aud and role with no match of bound_audiences",
287+
roleData: map[string]interface{}{
288+
"role_type": "jwt",
289+
"bound_audiences": []string{"https://vault.plugin.auth.jwt.test", "another_audience"},
290+
"bound_subject": "subject",
291+
"user_claim": "https://vault/user",
292+
"policies": "test",
293+
"period": "3s",
294+
"ttl": "1s",
295+
"num_uses": 12,
296+
"max_ttl": "5s",
297+
},
298+
// aud is not contained in role's bound_audiences
299+
jwtData: map[string]interface{}{
300+
"sub": "subject",
301+
"iss": "https://team-vault.auth0.com/",
302+
"aud": "https://foo.com",
303+
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
304+
},
305+
expectErr: true,
306+
},
307+
{
308+
name: "jwt with string aud and role with bound_audiences",
309+
roleData: map[string]interface{}{
310+
"role_type": "jwt",
311+
"bound_audiences": []string{"https://vault.plugin.auth.jwt.test", "another_audience"},
312+
"bound_subject": "subject",
313+
"user_claim": "https://vault/user",
314+
"policies": "test",
315+
"period": "3s",
316+
"ttl": "1s",
317+
"num_uses": 12,
318+
"max_ttl": "5s",
319+
},
320+
jwtData: map[string]interface{}{
321+
"sub": "subject",
322+
"iss": "https://team-vault.auth0.com/",
323+
"aud": "https://vault.plugin.auth.jwt.test",
324+
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
325+
},
326+
expectErr: false,
327+
},
328+
{
329+
name: "jwt with no aud and role with no bound_audiences",
330+
roleData: map[string]interface{}{
331+
"role_type": "jwt",
332+
"bound_subject": "subject",
333+
"user_claim": "https://vault/user",
334+
"policies": "test",
335+
"period": "3s",
336+
"ttl": "1s",
337+
"num_uses": 12,
338+
"max_ttl": "5s",
339+
},
340+
// aud is not set
341+
jwtData: map[string]interface{}{
342+
"sub": "subject",
343+
"iss": "https://team-vault.auth0.com/",
344+
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
345+
},
346+
expectErr: false,
347+
},
348+
}
349+
350+
for _, tt := range testCases {
351+
t.Run(tt.name, func(t *testing.T) {
352+
b, storage := getBackend(t)
353+
354+
configData := map[string]interface{}{
355+
"bound_issuer": "https://team-vault.auth0.com/",
356+
"jwt_validation_pubkeys": ecdsaPubKey,
357+
}
358+
359+
req := &logical.Request{
360+
Operation: logical.UpdateOperation,
361+
Path: configPath,
362+
Storage: storage,
363+
Data: configData,
364+
}
365+
366+
resp, err := b.HandleRequest(context.Background(), req)
367+
if err != nil || (resp != nil && resp.IsError()) {
368+
t.Fatalf("err:%s resp:%#v\n", err, resp)
369+
}
370+
371+
req = &logical.Request{
372+
Operation: logical.CreateOperation,
373+
Path: "role/plugin-test",
374+
Storage: storage,
375+
Data: tt.roleData,
376+
}
377+
378+
resp, err = b.HandleRequest(context.Background(), req)
379+
if err != nil || (resp != nil && resp.IsError()) {
380+
t.Fatalf("err:%s resp:%#v\n", err, resp)
381+
}
382+
383+
privateCl := struct {
384+
User string `json:"https://vault/user"`
385+
Groups []string `json:"https://vault/groups"`
386+
}{
387+
"jeff",
388+
[]string{"foo", "bar"},
389+
}
390+
391+
jwtData, _ := getTestJWT(t, ecdsaPrivKey, tt.jwtData, privateCl)
392+
393+
loginData := map[string]interface{}{
394+
"role": "plugin-test",
395+
"jwt": jwtData,
396+
}
397+
398+
req = &logical.Request{
399+
Operation: logical.UpdateOperation,
400+
Path: "login",
401+
Storage: storage,
402+
Data: loginData,
403+
Connection: &logical.Connection{
404+
RemoteAddr: "127.0.0.1",
405+
},
406+
}
407+
408+
resp, err = b.HandleRequest(context.Background(), req)
409+
if err != nil {
410+
t.Fatal(err)
411+
}
412+
if resp == nil {
413+
t.Fatal("got nil response")
414+
}
415+
if tt.expectErr {
416+
if !resp.IsError() {
417+
t.Fatal("expected error")
418+
}
419+
}
420+
421+
if !tt.expectErr && resp.IsError() {
422+
t.Fatalf("unexpected error: %q", resp.Error().Error())
423+
}
424+
})
425+
}
426+
}
427+
211428
func TestLogin_JWT(t *testing.T) {
212429
testLogin_JWT(t, false)
213430
testLogin_JWT(t, true)

0 commit comments

Comments
 (0)