Commit 016133cf authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

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. ```
parents 2bad7e6b 778812f6
...@@ -238,7 +238,23 @@ func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, er ...@@ -238,7 +238,23 @@ func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, er
var username string var username string
switch a.usernameClaim { switch a.usernameClaim {
case "email": 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 username = claim
default: default:
// For all other cases, use issuerURL + claim as the user name. // For all other cases, use issuerURL + claim as the user name.
......
...@@ -33,14 +33,15 @@ import ( ...@@ -33,14 +33,15 @@ import (
oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" 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 { 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 {
signer := op.PrivKey.Signer()
claims := oidc.NewClaims(iss, sub, aud, iat, exp) claims := oidc.NewClaims(iss, sub, aud, iat, exp)
claims.Add(usernameClaim, value) claims.Add(usernameClaim, value)
if groups != nil && groupsClaim != "" { if groups != nil && groupsClaim != "" {
claims.Add(groupsClaim, groups) claims.Add(groupsClaim, groups)
} }
claims.Add("email_verified", emailVerified)
signer := op.PrivKey.Signer()
jwt, err := jose.NewSignedJWT(claims, signer) jwt, err := jose.NewSignedJWT(claims, signer)
if err != nil { if err != nil {
t.Fatalf("Cannot generate token: %v", err) t.Fatalf("Cannot generate token: %v", err)
...@@ -49,16 +50,20 @@ func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud str ...@@ -49,16 +50,20 @@ func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud str
return jwt.Encode() 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 { 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 { 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 { 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) { func TestTLSConfig(t *testing.T) {
...@@ -258,6 +263,15 @@ func TestOIDCAuthentication(t *testing.T) { ...@@ -258,6 +263,15 @@ func TestOIDCAuthentication(t *testing.T) {
"custom group claim contains invalid type: float64", "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", "sub",
"", "",
generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil), generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment