Skip to content

Commit 2e49678

Browse files
Merge pull request #17788 from openshift-cherrypick-robot/cherry-pick-17550-to-release-3.8
Automatic merge from submit-queue. Automated cherry-pick of #17550 on release-3.8 This is an automated cherry-pick of #17550 /assign enj
2 parents 4eb2c77 + 9309c19 commit 2e49678

File tree

3 files changed

+184
-12
lines changed

3 files changed

+184
-12
lines changed
+58-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
package registry
22

33
import (
4-
"fmt"
4+
stderrors "errors"
55

66
"k8s.io/apimachinery/pkg/api/errors"
77
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8-
"k8s.io/apiserver/pkg/authentication/user"
8+
kuser "k8s.io/apiserver/pkg/authentication/user"
9+
"k8s.io/client-go/util/retry"
910

1011
"github.com/openshift/origin/pkg/auth/api"
12+
"github.com/openshift/origin/pkg/oauth/apis/oauth"
1113
oauthclient "github.com/openshift/origin/pkg/oauth/generated/internalclientset/typed/oauth/internalversion"
1214
"github.com/openshift/origin/pkg/oauth/registry/oauthclientauthorization"
1315
"github.com/openshift/origin/pkg/oauth/scope"
16+
17+
"github.com/golang/glog"
1418
)
1519

20+
var errEmptyUID = stderrors.New("user from request has empty UID and thus cannot perform a grant flow")
21+
1622
type ClientAuthorizationGrantChecker struct {
1723
client oauthclient.OAuthClientAuthorizationInterface
1824
}
@@ -21,21 +27,61 @@ func NewClientAuthorizationGrantChecker(client oauthclient.OAuthClientAuthorizat
2127
return &ClientAuthorizationGrantChecker{client}
2228
}
2329

24-
func (c *ClientAuthorizationGrantChecker) HasAuthorizedClient(user user.Info, grant *api.Grant) (approved bool, err error) {
25-
id := oauthclientauthorization.ClientAuthorizationName(user.GetName(), grant.Client.GetId())
26-
authorization, err := c.client.Get(id, metav1.GetOptions{})
27-
if errors.IsNotFound(err) {
28-
return false, nil
30+
func (c *ClientAuthorizationGrantChecker) HasAuthorizedClient(user kuser.Info, grant *api.Grant) (approved bool, err error) {
31+
// Validation prevents OAuthClientAuthorization.UserUID from being empty (and always has).
32+
// However, user.GetUID() is empty during impersonation, meaning this flow does not work for impersonation.
33+
// This is fine because no OAuth / grant flow works with impersonation in general.
34+
if len(user.GetUID()) == 0 {
35+
return false, errEmptyUID
2936
}
30-
if err != nil {
37+
38+
id := oauthclientauthorization.ClientAuthorizationName(user.GetName(), grant.Client.GetId())
39+
var authorization *oauth.OAuthClientAuthorization
40+
41+
// getClientAuthorization ignores not found errors, thus it is possible for authorization to be nil
42+
// getClientAuthorization does not ignore conflict errors, so we retry those in case we having multiple clients racing on this grant flow
43+
// all other errors are considered fatal
44+
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
45+
authorization, err = c.getClientAuthorization(id, user)
46+
return err
47+
}); err != nil {
3148
return false, err
3249
}
33-
if len(authorization.UserUID) != 0 && authorization.UserUID != user.GetUID() {
34-
return false, fmt.Errorf("user %s UID %s does not match stored client authorization value for UID %s", user.GetName(), user.GetUID(), authorization.UserUID)
35-
}
50+
3651
// TODO: improve this to allow the scope implementation to determine overlap
37-
if !scope.Covers(authorization.Scopes, scope.Split(grant.Scope)) {
52+
if authorization == nil || !scope.Covers(authorization.Scopes, scope.Split(grant.Scope)) {
3853
return false, nil
3954
}
55+
4056
return true, nil
4157
}
58+
59+
// getClientAuthorization gets the OAuthClientAuthorization with the given name and validates that it matches the given user
60+
// it attempts to delete stale client authorizations, and thus must be retried in case of conflicts
61+
func (c *ClientAuthorizationGrantChecker) getClientAuthorization(name string, user kuser.Info) (*oauth.OAuthClientAuthorization, error) {
62+
authorization, err := c.client.Get(name, metav1.GetOptions{})
63+
if errors.IsNotFound(err) {
64+
// if no such authorization exists, it simply means the user needs to go through the grant flow
65+
return nil, nil
66+
}
67+
if err != nil {
68+
// any other error is fatal (this will never be retried since it cannot return a conflict error)
69+
return nil, err
70+
}
71+
72+
// check to see if we have a stale authorization
73+
// user.GetUID() and authorization.UserUID are both guaranteed to be non-empty
74+
if user.GetUID() != authorization.UserUID {
75+
glog.Infof("%#v does not match stored client authorization %#v, attempting to delete stale authorization", user, authorization)
76+
if err := c.client.Delete(name, metav1.NewPreconditionDeleteOptions(string(authorization.UID))); err != nil && !errors.IsNotFound(err) {
77+
// ignore not found since that could be caused by multiple grant flows occurring at once (the other flow deleted the authorization before we did)
78+
// this could be a conflict error, which will cause this whole function to be retried
79+
return nil, err
80+
}
81+
// we successfully deleted the authorization so the user needs to go through the grant flow
82+
return nil, nil
83+
}
84+
85+
// everything looks good so we can return the authorization
86+
return authorization, nil
87+
}

pkg/auth/oauth/registry/registry_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ func TestRegistryAndServer(t *testing.T) {
130130
AuthSuccess: true,
131131
AuthUser: &user.DefaultInfo{
132132
Name: "user",
133+
UID: "1",
133134
},
134135
Check: func(h *testHandlers, _ *http.Request) {
135136
if h.AuthNeed || !h.GrantNeed || h.AuthErr != nil || h.GrantErr != nil || h.HandleAuthorizeReq.Authorized {
@@ -168,10 +169,12 @@ func TestRegistryAndServer(t *testing.T) {
168169
AuthSuccess: true,
169170
AuthUser: &user.DefaultInfo{
170171
Name: "user",
172+
UID: "1",
171173
},
172174
ClientAuth: &oapi.OAuthClientAuthorization{
173175
ObjectMeta: metav1.ObjectMeta{Name: "user:test"},
174176
UserName: "user",
177+
UserUID: "1",
175178
ClientName: "test",
176179
Scopes: []string{"user:info"},
177180
},
@@ -187,10 +190,12 @@ func TestRegistryAndServer(t *testing.T) {
187190
AuthSuccess: true,
188191
AuthUser: &user.DefaultInfo{
189192
Name: "user",
193+
UID: "1",
190194
},
191195
ClientAuth: &oapi.OAuthClientAuthorization{
192196
ObjectMeta: metav1.ObjectMeta{Name: "user:test"},
193197
UserName: "user",
198+
UserUID: "1",
194199
ClientName: "test",
195200
Scopes: []string{"user:info", "user:check-access"},
196201
},
@@ -206,10 +211,12 @@ func TestRegistryAndServer(t *testing.T) {
206211
AuthSuccess: true,
207212
AuthUser: &user.DefaultInfo{
208213
Name: "user",
214+
UID: "1",
209215
},
210216
ClientAuth: &oapi.OAuthClientAuthorization{
211217
ObjectMeta: metav1.ObjectMeta{Name: "user:test"},
212218
UserName: "user",
219+
UserUID: "1",
213220
ClientName: "test",
214221
Scopes: []string{"user:full"},
215222
},
@@ -227,6 +234,45 @@ func TestRegistryAndServer(t *testing.T) {
227234
}
228235
},
229236
},
237+
"has auth with no UID, mimics impersonation": {
238+
Client: validClient,
239+
AuthSuccess: true,
240+
AuthUser: &user.DefaultInfo{
241+
Name: "user",
242+
},
243+
ClientAuth: &oapi.OAuthClientAuthorization{
244+
ObjectMeta: metav1.ObjectMeta{Name: "user:test"},
245+
UserName: "user",
246+
UserUID: "2",
247+
ClientName: "test",
248+
Scopes: []string{"user:full"},
249+
},
250+
Check: func(h *testHandlers, r *http.Request) {
251+
if h.AuthNeed || h.GrantNeed || h.AuthErr != nil || h.GrantErr != nil || h.HandleAuthorizeReq.Authorized || h.HandleAuthorizeResp.ErrorId != "server_error" {
252+
t.Errorf("expected server_error error: %#v, %#v, %#v", h, h.HandleAuthorizeReq, h.HandleAuthorizeResp)
253+
}
254+
},
255+
},
256+
"has auth and grant with different UIDs": {
257+
Client: validClient,
258+
AuthSuccess: true,
259+
AuthUser: &user.DefaultInfo{
260+
Name: "user",
261+
UID: "1",
262+
},
263+
ClientAuth: &oapi.OAuthClientAuthorization{
264+
ObjectMeta: metav1.ObjectMeta{Name: "user:test"},
265+
UserName: "user",
266+
UserUID: "2",
267+
ClientName: "test",
268+
Scopes: []string{"user:full"},
269+
},
270+
Check: func(h *testHandlers, _ *http.Request) {
271+
if h.AuthNeed || !h.GrantNeed || h.AuthErr != nil || h.GrantErr != nil || h.HandleAuthorizeReq.Authorized {
272+
t.Errorf("expected request to need to grant access due to UID mismatch: %#v", h)
273+
}
274+
},
275+
},
230276
}
231277

232278
for _, testCase := range testCases {

test/integration/oauth_serviceaccount_client_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func TestOAuthServiceAccountClient(t *testing.T) {
6969
t.Fatalf("unexpected error: %v", err)
7070
}
7171
clusterAdminOAuthClient := oauthclient.NewForConfigOrDie(clusterAdminClientConfig).Oauth()
72+
clusterAdminUserClient := userclient.NewForConfigOrDie(clusterAdminClientConfig)
7273

7374
projectName := "hammer-project"
7475
if _, _, err := testserver.CreateNewProject(clusterAdminClientConfig, projectName, "harold"); err != nil {
@@ -375,6 +376,85 @@ func TestOAuthServiceAccountClient(t *testing.T) {
375376
})
376377
clusterAdminOAuthClient.OAuthClientAuthorizations().Delete("harold:"+oauthClientConfig.ClientId, nil)
377378
}
379+
380+
{
381+
oauthClientConfig := &osincli.ClientConfig{
382+
ClientId: apiserverserviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name),
383+
ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]),
384+
AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize",
385+
TokenUrl: clusterAdminClientConfig.Host + "/oauth/token",
386+
RedirectUrl: redirectURL,
387+
Scope: scope.Join([]string{"user:info", "role:edit:" + projectName}),
388+
SendClientSecretInParams: true,
389+
}
390+
t.Log("Testing grant flow is reentrant")
391+
// First time, the approval steps are needed
392+
// Second time, the approval steps are skipped
393+
// Then we delete and recreate the user to make the client authorization UID no longer match
394+
// Third time, the approval steps are needed
395+
// Fourth time, the approval steps are skipped
396+
runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{
397+
"GET /oauth/authorize",
398+
"received challenge",
399+
"GET /oauth/authorize",
400+
"redirect to /oauth/authorize/approve",
401+
"form",
402+
"POST /oauth/authorize/approve",
403+
"redirect to /oauth/authorize",
404+
"redirect to /oauthcallback",
405+
"code",
406+
"scope:" + oauthClientConfig.Scope,
407+
})
408+
runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{
409+
"GET /oauth/authorize",
410+
"received challenge",
411+
"GET /oauth/authorize",
412+
"redirect to /oauthcallback",
413+
"code",
414+
"scope:" + oauthClientConfig.Scope,
415+
})
416+
417+
// Delete the user to make the client authorization UID no longer match
418+
// runOAuthFlow will cause the creation of the same user with a different UID during its challenge phase
419+
if err := deleteUser(clusterAdminUserClient, "harold"); err != nil {
420+
t.Fatalf("Failed to delete and recreate harold user: %v", err)
421+
}
422+
423+
runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{
424+
"GET /oauth/authorize",
425+
"received challenge",
426+
"GET /oauth/authorize",
427+
"redirect to /oauth/authorize/approve",
428+
"form",
429+
"POST /oauth/authorize/approve",
430+
"redirect to /oauth/authorize",
431+
"redirect to /oauthcallback",
432+
"code",
433+
"scope:" + oauthClientConfig.Scope,
434+
})
435+
runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{
436+
"GET /oauth/authorize",
437+
"received challenge",
438+
"GET /oauth/authorize",
439+
"redirect to /oauthcallback",
440+
"code",
441+
"scope:" + oauthClientConfig.Scope,
442+
})
443+
clusterAdminOAuthClient.OAuthClientAuthorizations().Delete("harold:"+oauthClientConfig.ClientId, nil)
444+
}
445+
}
446+
447+
func deleteUser(clusterAdminUserClient userclient.UserInterface, name string) error {
448+
oldUser, err := clusterAdminUserClient.Users().Get(name, metav1.GetOptions{})
449+
if err != nil {
450+
return err
451+
}
452+
for _, identity := range oldUser.Identities {
453+
if err := clusterAdminUserClient.Identities().Delete(identity, nil); err != nil {
454+
return err
455+
}
456+
}
457+
return clusterAdminUserClient.Users().Delete(name, nil)
378458
}
379459

380460
func drain(ch chan string) {

0 commit comments

Comments
 (0)