Change:
<!-- Thanks for filing an issue! Before hitting the button, please answer these questions.--Is this a request for help? (If yes, you should use our troubleshooting guide and community support channels, see ): No, already discussed on sig-auth What keywords did you search in Kubernetes issues before filing this one? (If you have found any duplicates, you should instead reply there.): confirmed issue on sig-auth channel Is this a BUG REPORT or FEATURE REQUEST? (choose one): Depends on interpretation <!-- If this is a BUG REPORT, please: Fill in as much of the template below as you can. If you leave out information, we can't help you as well. If this is a FEATURE REQUEST, please: Describe in detail the feature/behavior/change you'd like to see. In both cases, be ready for followup questions, and please respond in a timely manner. If we can't reproduce a bug or think a feature already exists, we might close your issue. If we're wrong, PLEASE feel free to reopen it and explain why. --Kubernetes version (use ): Client Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.0", GitCommit:"", GitTreeState:"clean", BuildDate:"2016-07-01T19:26:38Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.4+coreos.0", GitCommit:"", GitTreeState:"clean", BuildDate:"2016-08-02T00:54:53Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"} Environment: Cloud provider or hardware configuration: CoreOS Vagrant Image OS (e.g. from /etc/os-release): NAME=CoreOS ID=coreos VERSION=1122.0.0 VERSIONID=1122.0.0 BUILDID=2016-07-27-0739 PRETTYNAME="CoreOS 1122.0.0 (MoreOS)" ANSICOLOR="1;32" HOMEURL="" BUGREPORTURL="" Kernel (e.g. ): Linux localhost 4.7.0-coreos SMP Wed Jul 27 07:30:04 UTC 2016 x8664 Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz GenuineIntel GNU/Linux Install tools: Vagrant Others: What happened: The claim named in --oidc-groups-claim MUST be list of strings, even if there is only one value. Talking to this is by design since its assumed that users will have more then one group. What you expected to happen: The OIDC authenticator should accept either a list of strings or a single string since: There is no concept of schema that marks an attribute as single value or multi value in JSON or a JWT There is no standard for representing groups in a JWT There is no standard for representing groups in OIDC An OIDC should be flexible in how it accepts data. How to reproduce it (as minimally and precisely as possible): Deploy with RBAC enabled Create a role binding for the group "admin" Generate a JWT such as the one that does NOT work below User won't be able to login Generate a JWT where the role is multi-value User will be authorized Does NOT Work: DOES Work: Anything else do we need to know:
These should be called groups, not roles, right? Those are different. And, is it normal for a JWT to contain keys which are arrays? That seems weird.
where do you see role defined? you mean the user_role claim?
Claims can be structured, yes. The name of the groups claim is arbitrary. Could easily have been "group". In my mind, this is all about enabling mapping a "groupish" claim from whatever IDP issued the JWT, even if it is a single value instead of an array.
That's the way it works currently. Yeah I'm fine with this.
Okay.

Discussion:
fs.StringVar(&s.OIDCGroupsClaim, "oidc-groups-claim", "", ""+ "If provided, the name of a custom OpenID Connect claim for specifying user groups. "+ <del> "The claim value is expected to be an array of strings. This flag is experimental, "+ </del> <ins> "The claim value is expected to be a string or array of strings. This flag is experimental, "+ </ins> "please see the authentication documentation for further details.") fs.Var(&s.RuntimeConfig, "runtime-config", ""+

// 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 <del> // must be a list of strings. </del> <ins> // must be a string or list of strings. </ins> GroupsClaim string }

if a.groupsClaim != "" { groups, found, err := claims.StringsClaim(a.groupsClaim) if err != nil { <del> // Custom claim is present, but isn't an array of strings. return nil, false, fmt.Errorf("custom group claim contains invalid object: %v", err) } if found { </del> <ins> // Groups type is present but is not an array of strings, try to decode as a string. group, _, err := claims.StringClaim(a.groupsClaim) if err != nil { // 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 { </ins> info.Groups = groups } }

oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" ) <del> func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string, iat, exp time.Time) string { </del> <ins> func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}, iat, exp time.Time) string { </ins> signer := op.PrivKey.Signer() claims := oidc.NewClaims(iss, sub, aud, iat, exp) claims.Add(usernameClaim, value)

return jwt.Encode() } <del> func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string { </del> <ins> func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string { </ins> return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) } <del> func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string { </del> <ins> func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string { </ins> return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + "randombits" } <del> func generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string { </del> <ins> func generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string { </ins> return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour)) }

"", }, { <ins> // 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", }, { </ins> "sub", "", generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),