Skip to content

Commit c3eebb2

Browse files
authored
Merge pull request kubernetes#121709 from aramase/aramase/f/authn_user_info_fix
[StructuredAuthn] Ensure empty fields of user object are accessible by CEL
2 parents fb9c94b + b693f09 commit c3eebb2

File tree

2 files changed

+92
-13
lines changed

2 files changed

+92
-13
lines changed

staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -615,23 +615,12 @@ func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*a
615615
}
616616

617617
if a.celMapper.UserValidationRules != nil {
618-
userInfo := &authenticationv1.UserInfo{
619-
Extra: make(map[string]authenticationv1.ExtraValue),
620-
Groups: info.GetGroups(),
621-
UID: info.GetUID(),
622-
Username: info.GetName(),
623-
}
624-
// Convert the extra information in the user object
625-
for key, val := range info.GetExtra() {
626-
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
627-
}
628-
629618
// Convert the user info to unstructured so that we can evaluate the CEL expressions
630619
// against the user info. This is done once here so that we don't have to convert
631620
// the user info to unstructured multiple times in the CEL mapper for each mapping.
632-
userInfoUnstructured, err := convertObjectToUnstructured(userInfo)
621+
userInfoUnstructured, err := convertUserInfoToUnstructured(info)
633622
if err != nil {
634-
return nil, false, fmt.Errorf("oidc: could not convert user info to unstructured: %v", err)
623+
return nil, false, fmt.Errorf("oidc: could not convert user info to unstructured: %w", err)
635624
}
636625

637626
evalResult, err := a.celMapper.UserValidationRules.EvalUser(ctx, userInfoUnstructured)
@@ -944,3 +933,40 @@ func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, e
944933
}
945934
return &unstructured.Unstructured{Object: ret}, nil
946935
}
936+
937+
func convertUserInfoToUnstructured(info user.Info) (*unstructured.Unstructured, error) {
938+
userInfo := &authenticationv1.UserInfo{
939+
Extra: make(map[string]authenticationv1.ExtraValue),
940+
Groups: info.GetGroups(),
941+
UID: info.GetUID(),
942+
Username: info.GetName(),
943+
}
944+
// Convert the extra information in the user object
945+
for key, val := range info.GetExtra() {
946+
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
947+
}
948+
949+
// Convert the user info to unstructured so that we can evaluate the CEL expressions
950+
// against the user info. This is done once here so that we don't have to convert
951+
// the user info to unstructured multiple times in the CEL mapper for each mapping.
952+
userInfoUnstructured, err := convertObjectToUnstructured(userInfo)
953+
if err != nil {
954+
return nil, err
955+
}
956+
957+
// check if the user info contains the required fields. If not, set them to empty values.
958+
// This is done because the CEL expressions expect these fields to be present.
959+
if userInfoUnstructured.Object["username"] == nil {
960+
userInfoUnstructured.Object["username"] = ""
961+
}
962+
if userInfoUnstructured.Object["uid"] == nil {
963+
userInfoUnstructured.Object["uid"] = ""
964+
}
965+
if userInfoUnstructured.Object["groups"] == nil {
966+
userInfoUnstructured.Object["groups"] = []string{}
967+
}
968+
if userInfoUnstructured.Object["extra"] == nil {
969+
userInfoUnstructured.Object["extra"] = map[string]authenticationv1.ExtraValue{}
970+
}
971+
return userInfoUnstructured, nil
972+
}

staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2727,6 +2727,59 @@ func TestToken(t *testing.T) {
27272727
Name: "jane",
27282728
},
27292729
},
2730+
// test to ensure omitempty fields not included in user info
2731+
// are set and accessible for CEL evaluation.
2732+
{
2733+
name: "test user validation rule doesn't fail when user info is empty",
2734+
options: Options{
2735+
JWTAuthenticator: apiserver.JWTAuthenticator{
2736+
Issuer: apiserver.Issuer{
2737+
URL: "https://auth.example.com",
2738+
Audiences: []string{"my-client"},
2739+
},
2740+
ClaimMappings: apiserver.ClaimMappings{
2741+
Username: apiserver.PrefixedClaimOrExpression{
2742+
Expression: "claims.username",
2743+
},
2744+
Groups: apiserver.PrefixedClaimOrExpression{
2745+
Expression: "claims.groups",
2746+
},
2747+
},
2748+
UserValidationRules: []apiserver.UserValidationRule{
2749+
{
2750+
Expression: `user.username == ""`,
2751+
Message: "username must be empty string",
2752+
},
2753+
{
2754+
Expression: `user.uid == ""`,
2755+
Message: "uid must be empty string",
2756+
},
2757+
{
2758+
Expression: `!('bar' in user.groups)`,
2759+
Message: "groups must not contain bar",
2760+
},
2761+
{
2762+
Expression: `!('bar' in user.extra)`,
2763+
Message: "extra must not contain bar",
2764+
},
2765+
},
2766+
},
2767+
now: func() time.Time { return now },
2768+
},
2769+
signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
2770+
pubKeys: []*jose.JSONWebKey{
2771+
loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
2772+
},
2773+
claims: fmt.Sprintf(`{
2774+
"iss": "https://auth.example.com",
2775+
"aud": "my-client",
2776+
"username": "",
2777+
"groups": null,
2778+
"exp": %d,
2779+
"baz": "qux"
2780+
}`, valid.Unix()),
2781+
want: &user.DefaultInfo{},
2782+
},
27302783
}
27312784
for _, test := range tests {
27322785
t.Run(test.name, test.run)

0 commit comments

Comments
 (0)