Merge pull request #36087 from ericchiang/plugin-auth-oidc-verify-email

Automatic merge from submit-queue

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 username
and the 'email_verified' is not set to `true`, reject that authentication attempt.

cc @erictune @kubernetes/sig-auth @mlbiam

```release-note
When using OIDC authentication and specifying --oidc-username-claim=email, an `"email_verified":true` claim must be returned from the identity provider.
```
pull/6/head
Kubernetes Submit Queue 2017-01-04 00:50:31 -08:00 committed by GitHub
commit 016133cf7d
2 changed files with 36 additions and 6 deletions

View File

@ -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.

View File

@ -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",
"",