Skip to content

Commit

Permalink
add test and remove redundant test
Browse files Browse the repository at this point in the history
  • Loading branch information
fairclothjm committed Jun 13, 2024
1 parent 8cfbea8 commit fa12191
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 62 deletions.
238 changes: 179 additions & 59 deletions path_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ import (
"testing"
"time"

"github.com/go-jose/go-jose/v3"
sqjwt "github.com/go-jose/go-jose/v3/jwt"
"github.com/go-test/deep"
"github.com/hashicorp/cap/jwt"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"

"github.com/go-jose/go-jose/v3"
sqjwt "github.com/go-jose/go-jose/v3/jwt"
)

type H map[string]interface{}
Expand Down Expand Up @@ -152,7 +151,7 @@ func setupBackend(t *testing.T, cfg testConfig) (closeableBackend, logical.Stora
return cb, storage
}

func getTestJWT(t *testing.T, privKey string, cl sqjwt.Claims, privateCl interface{}) (string, *ecdsa.PrivateKey) {
func getTestJWT(t *testing.T, privKey string, cl interface{}, privateCl interface{}) (string, *ecdsa.PrivateKey) {
t.Helper()
var key *ecdsa.PrivateKey
block, _ := pem.Decode([]byte(privKey))
Expand Down Expand Up @@ -209,6 +208,182 @@ func getTestOIDC(t *testing.T) string {
return out.AccessToken
}

// TestLoginBoundAudiences tests that the login JWT's aud claim is ignored if
// it is a single string. This is a case that is fixed in later versions of
// the plugin.
// See https://github.com/hashicorp/vault-plugin-auth-jwt/pull/308
func TestLoginBoundAudiences(t *testing.T) {
testCases := []struct {
name string
roleData map[string]interface{}
jwtData map[string]interface{}
expectErr bool
}{
{
name: "jwt with string aud is ignored",
// bound_audiences is unset
roleData: map[string]interface{}{
"role_type": "jwt",
"bound_subject": "subject",
"user_claim": "https://vault/user",
"policies": "test",
"period": "3s",
"ttl": "1s",
"num_uses": 12,
"max_ttl": "5s",
},
jwtData: map[string]interface{}{
"sub": "subject",
"iss": "https://team-vault.auth0.com/",
"aud": "https://vault.plugin.auth.jwt.test",
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
},
expectErr: false,
},
{
name: "jwt with array aud is checked",
// bound_audiences is unset
roleData: map[string]interface{}{
"role_type": "jwt",
"bound_subject": "subject",
"user_claim": "https://vault/user",
"policies": "test",
"period": "3s",
"ttl": "1s",
"num_uses": 12,
"max_ttl": "5s",
},
jwtData: map[string]interface{}{
"sub": "subject",
"iss": "https://team-vault.auth0.com/",
"aud": []string{"https://vault.plugin.auth.jwt.test"},
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
},
expectErr: true,
},
{
name: "jwt with array aud is checked with role bound_audiences",
roleData: map[string]interface{}{
"role_type": "jwt",
"bound_audiences": []string{"https://vault.plugin.auth.jwt.test", "another_audience"},
"bound_subject": "subject",
"user_claim": "https://vault/user",
"policies": "test",
"period": "3s",
"ttl": "1s",
"num_uses": 12,
"max_ttl": "5s",
},
// aud is an array
jwtData: map[string]interface{}{
"sub": "subject",
"iss": "https://team-vault.auth0.com/",
"aud": []string{"https://vault.plugin.auth.jwt.test"},
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
},
expectErr: false,
},
{
name: "jwt with string aud is ignored with role bound_audiences",
roleData: map[string]interface{}{
"role_type": "jwt",
"bound_audiences": []string{"https://vault.plugin.auth.jwt.test", "another_audience"},
"bound_subject": "subject",
"user_claim": "https://vault/user",
"policies": "test",
"period": "3s",
"ttl": "1s",
"num_uses": 12,
"max_ttl": "5s",
},
// aud is a string
jwtData: map[string]interface{}{
"sub": "subject",
"iss": "https://team-vault.auth0.com/",
"aud": "https://foo.com",
"nbf": sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
},
expectErr: false,
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
b, storage := getBackend(t)

configData := map[string]interface{}{
"bound_issuer": "https://team-vault.auth0.com/",
"jwt_validation_pubkeys": ecdsaPubKey,
}

req := &logical.Request{
Operation: logical.UpdateOperation,
Path: configPath,
Storage: storage,
Data: configData,
}

resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}

req = &logical.Request{
Operation: logical.CreateOperation,
Path: "role/plugin-test",
Storage: storage,
Data: tt.roleData,
}

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}

privateCl := struct {
User string `json:"https://vault/user"`
Groups []string `json:"https://vault/groups"`
}{
"jeff",
[]string{"foo", "bar"},
}

jwtData, _ := getTestJWT(t, ecdsaPrivKey, tt.jwtData, privateCl)

loginData := map[string]interface{}{
"role": "plugin-test",
"jwt": jwtData,
}

req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Storage: storage,
Data: loginData,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}

resp, err = b.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response")
}
if tt.expectErr {
if !resp.IsError() {
t.Fatal("expected error")
}
if !strings.Contains(resp.Error().Error(), "no audiences bound to the role") {
t.Fatalf("unexpected error: %v", resp.Error())
}
}
})
}
}

func TestLogin_JWT(t *testing.T) {
testLogin_JWT(t, false)
testLogin_JWT(t, true)
Expand Down Expand Up @@ -271,61 +446,6 @@ func testLogin_JWT(t *testing.T, jwks bool) {
}
}

// Test missing audience
{

cfg := testConfig{
jwks: jwks,
}
b, storage := setupBackend(t, cfg)

cl := sqjwt.Claims{
Subject: "r3qXcK2bix9eFECzsU3Sbmh0K16fatW6@clients",
Issuer: "https://team-vault.auth0.com/",
NotBefore: sqjwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
Audience: sqjwt.Audience{"https://vault.plugin.auth.jwt.test"},
}

privateCl := struct {
User string `json:"https://vault/user"`
Groups []string `json:"https://vault/groups"`
}{
"jeff",
[]string{"foo", "bar"},
}

jwtData, _ := getTestJWT(t, ecdsaPrivKey, cl, privateCl)

data := map[string]interface{}{
"role": "plugin-test",
"jwt": jwtData,
}

req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Storage: storage,
Data: data,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}

resp, err := b.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response")
}
if !resp.IsError() {
t.Fatal("expected error")
}
if !strings.Contains(resp.Error().Error(), "no audiences bound to the role") {
t.Fatalf("unexpected error: %v", resp.Error())
}
}

// test valid inputs
{
// run test with and without bound_cidrs configured
Expand Down
5 changes: 2 additions & 3 deletions path_oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ import (
"testing"
"time"

"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
)

func TestOIDC_AuthURL(t *testing.T) {
Expand Down

0 comments on commit fa12191

Please sign in to comment.