jwt: strictly support compact serialization only

Signed-off-by: Monis Khan <mok@microsoft.com>
This commit is contained in:
Monis Khan 2024-02-27 12:40:59 -05:00
parent 12217672a3
commit e89dddd4af
No known key found for this signature in database
3 changed files with 67 additions and 11 deletions

View File

@ -291,6 +291,11 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData
return nil, false, utilerrors.NewAggregate(errlist)
}
// sanity check issuer since we parsed it out before signature validation
if !j.issuers[public.Issuer] {
return nil, false, fmt.Errorf("token issuer %q is invalid", public.Issuer)
}
tokenAudiences := authenticator.Audiences(public.Audience)
if len(tokenAudiences) == 0 {
// only apiserver audiences are allowed for legacy tokens
@ -330,6 +335,9 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData
// Note: go-jose currently does not allow access to unverified JWS payloads.
// See https://github.com/square/go-jose/issues/169
func (j *jwtTokenAuthenticator) hasCorrectIssuer(tokenData string) bool {
if strings.HasPrefix(strings.TrimSpace(tokenData), "{") {
return false
}
parts := strings.Split(tokenData, ".")
if len(parts) != 3 {
return false

View File

@ -18,6 +18,8 @@ package serviceaccount_test
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"strings"
@ -200,23 +202,16 @@ func TestTokenGenerateAndValidate(t *testing.T) {
checkJSONWebSignatureHasKeyID(t, invalidAutoSecretToken, rsaKeyID)
// Generate the ECDSA token
ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey))
if err != nil {
t.Fatalf("error making generator: %v", err)
}
ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret))
if err != nil {
t.Fatalf("error generating token: %v", err)
}
if len(ecdsaToken) == 0 {
t.Fatalf("no token generated")
}
ecdsaToken := generateECDSAToken(t, serviceaccount.LegacyIssuer, serviceAccount, ecdsaSecret)
ecdsaSecret.Data = map[string][]byte{
"token": []byte(ecdsaToken),
}
checkJSONWebSignatureHasKeyID(t, ecdsaToken, ecdsaKeyID)
ecdsaTokenMalformedIss := generateECDSATokenWithMalformedIss(t, serviceAccount, ecdsaSecret)
// Generate signer with same keys as RSA signer but different unrecognized issuer
badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey))
if err != nil {
@ -356,6 +351,13 @@ func TestTokenGenerateAndValidate(t *testing.T) {
Keys: []interface{}{getPublicKey(rsaPublicKey)},
ExpectedErr: true,
},
"malformed iss": {
Token: ecdsaTokenMalformedIss,
Client: nil,
Keys: []interface{}{getPublicKey(ecdsaPublicKey)},
ExpectedErr: false,
ExpectedOK: false,
},
}
for k, tc := range testCases {
@ -457,3 +459,46 @@ func (f *fakeIndexer) GetByKey(key string) (interface{}, bool, error) {
obj, err := f.get(namespace, name)
return obj, err == nil, err
}
func generateECDSAToken(t *testing.T, iss string, serviceAccount *v1.ServiceAccount, ecdsaSecret *v1.Secret) string {
t.Helper()
ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(iss, getPrivateKey(ecdsaPrivateKey))
if err != nil {
t.Fatalf("error making generator: %v", err)
}
ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret))
if err != nil {
t.Fatalf("error generating token: %v", err)
}
if len(ecdsaToken) == 0 {
t.Fatalf("no token generated")
}
return ecdsaToken
}
func generateECDSATokenWithMalformedIss(t *testing.T, serviceAccount *v1.ServiceAccount, ecdsaSecret *v1.Secret) string {
t.Helper()
ecdsaToken := generateECDSAToken(t, "panda", serviceAccount, ecdsaSecret)
ecdsaTokenJWS, err := jose.ParseSigned(ecdsaToken)
if err != nil {
t.Fatal(err)
}
dataFullSerialize := map[string]any{}
if err := json.Unmarshal([]byte(ecdsaTokenJWS.FullSerialize()), &dataFullSerialize); err != nil {
t.Fatal(err)
}
dataFullSerialize["malformed_iss"] = "." + base64.RawURLEncoding.EncodeToString([]byte(`{"iss":"bar"}`)) + "."
out, err := json.Marshal(dataFullSerialize)
if err != nil {
t.Fatal(err)
}
return string(out)
}

View File

@ -342,6 +342,9 @@ func New(opts Options) (*Authenticator, error) {
// or returns an error if the token can not be parsed. Since the JWT is not
// verified, the returned issuer should not be trusted.
func untrustedIssuer(token string) (string, error) {
if strings.HasPrefix(strings.TrimSpace(token), "{") {
return "", fmt.Errorf("token is not compact JWT")
}
parts := strings.Split(token, ".")
if len(parts) != 3 {
return "", fmt.Errorf("malformed token")