Commit 065ddab5 authored by Eric Chiang's avatar Eric Chiang

oidc authenticator: allow string value as groups claim

Allow the group claim to be a single string instead of an array of strings. This means the following claim { "role": "admin" } Will be mapped to the groups ["admin"]
parent 4b700f41
...@@ -401,7 +401,7 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { ...@@ -401,7 +401,7 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.OIDCGroupsClaim, "oidc-groups-claim", "", ""+ fs.StringVar(&s.OIDCGroupsClaim, "oidc-groups-claim", "", ""+
"If provided, the name of a custom OpenID Connect claim for specifying user groups. "+ "If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
"The claim value is expected to be an array of strings. This flag is experimental, "+ "The claim value is expected to be a string or array of strings. This flag is experimental, "+
"please see the authentication documentation for further details.") "please see the authentication documentation for further details.")
fs.Var(&s.RuntimeConfig, "runtime-config", ""+ fs.Var(&s.RuntimeConfig, "runtime-config", ""+
......
...@@ -73,7 +73,7 @@ type OIDCOptions struct { ...@@ -73,7 +73,7 @@ type OIDCOptions struct {
// GroupsClaim, if specified, causes the OIDCAuthenticator to try to populate the user's // GroupsClaim, if specified, causes the OIDCAuthenticator to try to populate the user's
// groups with a ID Token field. If the GrouppClaim field is present in a ID Token the value // groups with a ID Token field. If the GrouppClaim field is present in a ID Token the value
// must be a list of strings. // must be a string or list of strings.
GroupsClaim string GroupsClaim string
} }
...@@ -251,10 +251,14 @@ func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, er ...@@ -251,10 +251,14 @@ func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, er
if a.groupsClaim != "" { if a.groupsClaim != "" {
groups, found, err := claims.StringsClaim(a.groupsClaim) groups, found, err := claims.StringsClaim(a.groupsClaim)
if err != nil { if err != nil {
// Custom claim is present, but isn't an array of strings. // Groups type is present but is not an array of strings, try to decode as a string.
return nil, false, fmt.Errorf("custom group claim contains invalid object: %v", err) group, _, err := claims.StringClaim(a.groupsClaim)
} if err != nil {
if found { // Custom claim is present, but isn't an array of strings or a string.
return nil, false, fmt.Errorf("custom group claim contains invalid type: %T", claims[a.groupsClaim])
}
info.Groups = []string{group}
} else if found {
info.Groups = groups info.Groups = groups
} }
} }
......
...@@ -33,7 +33,7 @@ import ( ...@@ -33,7 +33,7 @@ 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 []string, 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) string {
signer := op.PrivKey.Signer() 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)
...@@ -49,15 +49,15 @@ func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud str ...@@ -49,15 +49,15 @@ func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud str
return jwt.Encode() return jwt.Encode()
} }
func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) 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))
} }
func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) 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)) + "randombits"
} }
func generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) 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))
} }
...@@ -240,6 +240,24 @@ func TestOIDCAuthentication(t *testing.T) { ...@@ -240,6 +240,24 @@ func TestOIDCAuthentication(t *testing.T) {
"", "",
}, },
{ {
// Group claim is a string rather than an array. Map that string to a single group.
"email",
"groups",
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", "group1"),
&user.DefaultInfo{Name: "foo@example.com", Groups: []string{"group1"}},
true,
"",
},
{
// Group claim is not a string or array of strings. Throw out this as invalid.
"email",
"groups",
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", 1),
nil,
false,
"custom group claim contains invalid type: float64",
},
{
"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