mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
jwt: strictly support compact serialization only
Signed-off-by: Monis Khan <mok@microsoft.com>
This commit is contained in:
parent
12217672a3
commit
e89dddd4af
@ -291,6 +291,11 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData
|
|||||||
return nil, false, utilerrors.NewAggregate(errlist)
|
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)
|
tokenAudiences := authenticator.Audiences(public.Audience)
|
||||||
if len(tokenAudiences) == 0 {
|
if len(tokenAudiences) == 0 {
|
||||||
// only apiserver audiences are allowed for legacy tokens
|
// 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.
|
// Note: go-jose currently does not allow access to unverified JWS payloads.
|
||||||
// See https://github.com/square/go-jose/issues/169
|
// See https://github.com/square/go-jose/issues/169
|
||||||
func (j *jwtTokenAuthenticator) hasCorrectIssuer(tokenData string) bool {
|
func (j *jwtTokenAuthenticator) hasCorrectIssuer(tokenData string) bool {
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(tokenData), "{") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
parts := strings.Split(tokenData, ".")
|
parts := strings.Split(tokenData, ".")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return false
|
return false
|
||||||
|
@ -18,6 +18,8 @@ package serviceaccount_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -200,23 +202,16 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
|||||||
checkJSONWebSignatureHasKeyID(t, invalidAutoSecretToken, rsaKeyID)
|
checkJSONWebSignatureHasKeyID(t, invalidAutoSecretToken, rsaKeyID)
|
||||||
|
|
||||||
// Generate the ECDSA token
|
// Generate the ECDSA token
|
||||||
ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey))
|
ecdsaToken := generateECDSAToken(t, serviceaccount.LegacyIssuer, serviceAccount, ecdsaSecret)
|
||||||
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")
|
|
||||||
}
|
|
||||||
ecdsaSecret.Data = map[string][]byte{
|
ecdsaSecret.Data = map[string][]byte{
|
||||||
"token": []byte(ecdsaToken),
|
"token": []byte(ecdsaToken),
|
||||||
}
|
}
|
||||||
|
|
||||||
checkJSONWebSignatureHasKeyID(t, ecdsaToken, ecdsaKeyID)
|
checkJSONWebSignatureHasKeyID(t, ecdsaToken, ecdsaKeyID)
|
||||||
|
|
||||||
|
ecdsaTokenMalformedIss := generateECDSATokenWithMalformedIss(t, serviceAccount, ecdsaSecret)
|
||||||
|
|
||||||
// Generate signer with same keys as RSA signer but different unrecognized issuer
|
// Generate signer with same keys as RSA signer but different unrecognized issuer
|
||||||
badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey))
|
badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -356,6 +351,13 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
|||||||
Keys: []interface{}{getPublicKey(rsaPublicKey)},
|
Keys: []interface{}{getPublicKey(rsaPublicKey)},
|
||||||
ExpectedErr: true,
|
ExpectedErr: true,
|
||||||
},
|
},
|
||||||
|
"malformed iss": {
|
||||||
|
Token: ecdsaTokenMalformedIss,
|
||||||
|
Client: nil,
|
||||||
|
Keys: []interface{}{getPublicKey(ecdsaPublicKey)},
|
||||||
|
ExpectedErr: false,
|
||||||
|
ExpectedOK: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, tc := range testCases {
|
for k, tc := range testCases {
|
||||||
@ -457,3 +459,46 @@ func (f *fakeIndexer) GetByKey(key string) (interface{}, bool, error) {
|
|||||||
obj, err := f.get(namespace, name)
|
obj, err := f.get(namespace, name)
|
||||||
return obj, err == nil, err
|
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)
|
||||||
|
}
|
||||||
|
@ -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
|
// or returns an error if the token can not be parsed. Since the JWT is not
|
||||||
// verified, the returned issuer should not be trusted.
|
// verified, the returned issuer should not be trusted.
|
||||||
func untrustedIssuer(token string) (string, error) {
|
func untrustedIssuer(token string) (string, error) {
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(token), "{") {
|
||||||
|
return "", fmt.Errorf("token is not compact JWT")
|
||||||
|
}
|
||||||
parts := strings.Split(token, ".")
|
parts := strings.Split(token, ".")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return "", fmt.Errorf("malformed token")
|
return "", fmt.Errorf("malformed token")
|
||||||
|
Loading…
Reference in New Issue
Block a user