Merge pull request #123540 from enj/enj/i/jwt_iss
jwt: strictly support compact serialization only
This commit is contained in:
		| @@ -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") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot