3b3b02b741
Their release notes are here: https://github.com/golang-jwt/jwt/releases Seemed wise to upgrade before we do even more with JWTs. For example this upgrade *would* have mattered if we already implemented common JWT claims such as expiration. Since we didn't rely on any claim verification this upgrade is a noop... ...except for 1 test that called `Claims.Valid()`! Removing that assertion *seems* scary, but it didn't actually do anything because we didn't implement any of the standard claims it validated: https://github.com/golang-jwt/jwt/blob/v4.5.0/map_claims.go#L120-L151 So functionally this major upgrade is a noop.
146 lines
3.8 KiB
Go
146 lines
3.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package jwt
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/hashicorp/cap/oidc"
|
|
"github.com/shoenig/test/must"
|
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
func TestValidate(t *testing.T) {
|
|
iat := time.Now().Unix()
|
|
nbf := time.Now().Unix()
|
|
exp := time.Now().Add(time.Hour).Unix()
|
|
|
|
claims := jwt.MapClaims{
|
|
"foo": "bar",
|
|
"issuer": "test suite",
|
|
"float": 3.14,
|
|
"iat": iat,
|
|
"nbf": nbf,
|
|
"exp": exp,
|
|
}
|
|
|
|
wantedClaims := map[string]any{
|
|
"foo": "bar",
|
|
"issuer": "test suite",
|
|
"float": 3.14,
|
|
"iat": float64(iat),
|
|
"nbf": float64(nbf),
|
|
"exp": float64(exp),
|
|
}
|
|
|
|
// appended to JWKS test server URL
|
|
wellKnownJWKS := "/.well-known/jwks.json"
|
|
|
|
// generate a key pair, so that we can use it for consistent signing and
|
|
// set it as our test server key
|
|
rsaKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
|
must.NoError(t, err)
|
|
|
|
token, _, err := mock.SampleJWTokenWithKeys(claims, rsaKey)
|
|
must.NoError(t, err)
|
|
tokenWithNoClaims, pubKeyPem, err := mock.SampleJWTokenWithKeys(nil, rsaKey)
|
|
must.NoError(t, err)
|
|
|
|
// make an expired token...
|
|
expired := time.Now().Add(-time.Hour).Unix()
|
|
expiredClaims := jwt.MapClaims{"iat": iat, "nbf": nbf, "exp": expired}
|
|
expiredToken, _, err := mock.SampleJWTokenWithKeys(expiredClaims, rsaKey)
|
|
must.NoError(t, err)
|
|
|
|
// ...and one with invalid issuer, too
|
|
invalidIssuer := jwt.MapClaims{"iat": iat, "nbf": nbf, "exp": exp, "iss": "hashicorp vault"}
|
|
invalidIssuerToken, _, err := mock.SampleJWTokenWithKeys(invalidIssuer, rsaKey)
|
|
must.NoError(t, err)
|
|
|
|
testServer := oidc.StartTestProvider(t)
|
|
defer testServer.Stop()
|
|
|
|
keyID := strconv.Itoa(int(time.Now().Unix()))
|
|
testServer.SetSigningKeys(rsaKey, rsaKey.Public(), oidc.RS256, keyID)
|
|
tokenSignedWithRemoteServerKeys, _, err := mock.SampleJWTokenWithKeys(claims, rsaKey)
|
|
must.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
conf *structs.ACLAuthMethodConfig
|
|
want map[string]interface{}
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid signature, local verification",
|
|
token: token,
|
|
conf: &structs.ACLAuthMethodConfig{JWTValidationPubKeys: []string{pubKeyPem}},
|
|
want: wantedClaims,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid signature, local verification, no claims",
|
|
token: tokenWithNoClaims,
|
|
conf: &structs.ACLAuthMethodConfig{JWTValidationPubKeys: []string{pubKeyPem}},
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "valid signature, JWKS verification",
|
|
token: tokenSignedWithRemoteServerKeys,
|
|
conf: &structs.ACLAuthMethodConfig{
|
|
JWKSURL: testServer.Addr() + wellKnownJWKS,
|
|
JWKSCACert: testServer.CACert(),
|
|
},
|
|
want: wantedClaims,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid signature, OIDC verification",
|
|
token: tokenSignedWithRemoteServerKeys,
|
|
conf: &structs.ACLAuthMethodConfig{
|
|
OIDCDiscoveryURL: testServer.Addr(),
|
|
DiscoveryCaPem: []string{testServer.CACert()},
|
|
},
|
|
want: wantedClaims,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expired token, local verification",
|
|
token: expiredToken,
|
|
conf: &structs.ACLAuthMethodConfig{JWTValidationPubKeys: []string{pubKeyPem}},
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid issuer, local verification",
|
|
token: invalidIssuerToken,
|
|
conf: &structs.ACLAuthMethodConfig{
|
|
JWTValidationPubKeys: []string{pubKeyPem},
|
|
BoundIssuer: []string{"test suite"},
|
|
},
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := Validate(context.Background(), tt.token, tt.conf)
|
|
if !tt.wantErr {
|
|
must.Nil(t, err, must.Sprint(err))
|
|
}
|
|
must.Eq(t, got, tt.want)
|
|
})
|
|
}
|
|
}
|