From 778812f63bd9138298649902e031f61494095f52 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Wed, 2 Nov 2016 10:41:29 -0700 Subject: [PATCH] oidc auth-n plugin: enforce email_verified claim This change causes the OpenID Connect authenticator to start enforcing the 'email_verified' claim. https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims If the OIDC authenticator uses the 'email' claim as a user's password and the 'email_verified' holds the value false, reject that authentication attempt. If 'email_verified' is true or not present, continue as before. --- .../pkg/auth/authenticator/token/oidc/oidc.go | 18 +++++++++++++- .../authenticator/token/oidc/oidc_test.go | 24 +++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/plugin/pkg/auth/authenticator/token/oidc/oidc.go b/plugin/pkg/auth/authenticator/token/oidc/oidc.go index c0ea2242161..c2f4abcb2e3 100644 --- a/plugin/pkg/auth/authenticator/token/oidc/oidc.go +++ b/plugin/pkg/auth/authenticator/token/oidc/oidc.go @@ -238,7 +238,23 @@ func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, er var username string switch a.usernameClaim { case "email": - // TODO(yifan): Check 'email_verified' to make sure the email is valid. + verified, ok := claims["email_verified"] + if !ok { + return nil, false, errors.New("'email_verified' claim not present") + } + + emailVerified, ok := verified.(bool) + if !ok { + // OpenID Connect spec defines 'email_verified' as a boolean. For now, be a pain and error if + // it's a different type. If there are enough misbehaving providers we can relax this latter. + // + // See: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + return nil, false, fmt.Errorf("malformed claim 'email_verified', expected boolean got %T", verified) + } + + if !emailVerified { + return nil, false, errors.New("email not verified") + } username = claim default: // For all other cases, use issuerURL + claim as the user name. diff --git a/plugin/pkg/auth/authenticator/token/oidc/oidc_test.go b/plugin/pkg/auth/authenticator/token/oidc/oidc_test.go index 061cd9218cc..01df1f3c9d7 100644 --- a/plugin/pkg/auth/authenticator/token/oidc/oidc_test.go +++ b/plugin/pkg/auth/authenticator/token/oidc/oidc_test.go @@ -33,14 +33,15 @@ import ( oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" ) -func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}, iat, exp time.Time) string { - signer := op.PrivKey.Signer() +func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}, iat, exp time.Time, emailVerified bool) string { claims := oidc.NewClaims(iss, sub, aud, iat, exp) claims.Add(usernameClaim, value) if groups != nil && groupsClaim != "" { claims.Add(groupsClaim, groups) } + claims.Add("email_verified", emailVerified) + signer := op.PrivKey.Signer() jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Cannot generate token: %v", err) @@ -49,16 +50,20 @@ func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud str return jwt.Encode() } +func generateTokenWithUnverifiedEmail(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, email string) string { + return generateToken(t, op, iss, sub, aud, "email", email, "", nil, time.Now(), time.Now().Add(time.Hour), false) +} + func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string { - return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour), true) } func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string { - return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + "randombits" + return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour), true) + "randombits" } func generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string { - return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour)) + return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour), true) } func TestTLSConfig(t *testing.T) { @@ -257,6 +262,15 @@ func TestOIDCAuthentication(t *testing.T) { false, "custom group claim contains invalid type: float64", }, + { + // Email not verified + "email", + "", + generateTokenWithUnverifiedEmail(t, op, srv.URL, "client-foo", "client-foo", "foo@example.com"), + nil, + false, + "email not verified", + }, { "sub", "",