Skip to content

Commit 10d7f6a

Browse files
committedDec 5, 2017
Add OAuth token and user validator interface
This change adds the OAuthTokenValidator interface for generically validating an OAuthAccessToken and User. The expiration and UID validation was pulled out from the tokenAuthenticator. tokenAuthenticator simply takes OAuthTokenValidators as input, and delegates validation to them. This allows all future validation to simply append itself to the list of validators without requiring any changes to tokenAuthenticator. Signed-off-by: Monis Khan <[email protected]>
1 parent b638188 commit 10d7f6a

File tree

6 files changed

+147
-32
lines changed

6 files changed

+147
-32
lines changed
 

‎pkg/auth/authenticator/interfaces.go

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"k8s.io/apiserver/pkg/authentication/user"
55

66
"github.com/openshift/origin/pkg/auth/api"
7+
"github.com/openshift/origin/pkg/oauth/apis/oauth"
8+
userapi "github.com/openshift/origin/pkg/user/apis/user"
79
)
810

911
type Assertion interface {
@@ -13,3 +15,28 @@ type Assertion interface {
1315
type Client interface {
1416
AuthenticateClient(client api.Client) (user.Info, bool, error)
1517
}
18+
19+
type OAuthTokenValidator interface {
20+
Validate(token *oauth.OAuthAccessToken, user *userapi.User) error
21+
}
22+
23+
var _ OAuthTokenValidator = OAuthTokenValidatorFunc(nil)
24+
25+
type OAuthTokenValidatorFunc func(token *oauth.OAuthAccessToken, user *userapi.User) error
26+
27+
func (f OAuthTokenValidatorFunc) Validate(token *oauth.OAuthAccessToken, user *userapi.User) error {
28+
return f(token, user)
29+
}
30+
31+
var _ OAuthTokenValidator = OAuthTokenValidators(nil)
32+
33+
type OAuthTokenValidators []OAuthTokenValidator
34+
35+
func (v OAuthTokenValidators) Validate(token *oauth.OAuthAccessToken, user *userapi.User) error {
36+
for _, validator := range v {
37+
if err := validator.Validate(token, user); err != nil {
38+
return err
39+
}
40+
}
41+
return nil
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package registry
2+
3+
import (
4+
"errors"
5+
"time"
6+
7+
"github.com/openshift/origin/pkg/auth/authenticator"
8+
"github.com/openshift/origin/pkg/oauth/apis/oauth"
9+
"github.com/openshift/origin/pkg/user/apis/user"
10+
)
11+
12+
var errExpired = errors.New("token is expired")
13+
14+
func NewExpirationValidator() authenticator.OAuthTokenValidator {
15+
return authenticator.OAuthTokenValidatorFunc(
16+
func(token *oauth.OAuthAccessToken, _ *user.User) error {
17+
if token.ExpiresIn > 0 {
18+
if expire(token).Before(time.Now()) {
19+
return errExpired
20+
}
21+
}
22+
if token.DeletionTimestamp != nil {
23+
return errExpired
24+
}
25+
return nil
26+
},
27+
)
28+
}
29+
30+
func expire(token *oauth.OAuthAccessToken) time.Time {
31+
return token.CreationTimestamp.Add(time.Duration(token.ExpiresIn) * time.Second)
32+
}

‎pkg/auth/oauth/registry/registry_test.go

+45-4
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ func TestAuthenticateTokenNotFound(t *testing.T) {
318318
t.Errorf("Unexpected user: %v", userInfo)
319319
}
320320
}
321+
321322
func TestAuthenticateTokenOtherGetError(t *testing.T) {
322323
fakeOAuthClient := oauthfake.NewSimpleClientset()
323324
fakeOAuthClient.PrependReactor("get", "oauthaccesstokens", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
@@ -340,27 +341,67 @@ func TestAuthenticateTokenOtherGetError(t *testing.T) {
340341
t.Errorf("Unexpected user: %v", userInfo)
341342
}
342343
}
344+
343345
func TestAuthenticateTokenExpired(t *testing.T) {
346+
fakeOAuthClient := oauthfake.NewSimpleClientset(
347+
// expired token that had a lifetime of 10 minutes
348+
&oapi.OAuthAccessToken{
349+
ObjectMeta: metav1.ObjectMeta{Name: "token1", CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)}},
350+
ExpiresIn: 600,
351+
UserName: "foo",
352+
},
353+
// non-expired token that has a lifetime of 10 minutes, but has a non-nil deletion timestamp
354+
&oapi.OAuthAccessToken{
355+
ObjectMeta: metav1.ObjectMeta{Name: "token2", CreationTimestamp: metav1.Time{Time: time.Now()}, DeletionTimestamp: &metav1.Time{}},
356+
ExpiresIn: 600,
357+
UserName: "foo",
358+
},
359+
)
360+
userRegistry := usertest.NewUserRegistry()
361+
userRegistry.GetUsers["foo"] = &userapi.User{ObjectMeta: metav1.ObjectMeta{UID: "bar"}}
362+
363+
tokenAuthenticator := NewTokenAuthenticator(fakeOAuthClient.Oauth().OAuthAccessTokens(), userRegistry, identitymapper.NoopGroupMapper{}, NewExpirationValidator())
364+
365+
for _, tokenName := range []string{"token1", "token2"} {
366+
userInfo, found, err := tokenAuthenticator.AuthenticateToken(tokenName)
367+
if found {
368+
t.Error("Found token, but it should be missing!")
369+
}
370+
if err != errExpired {
371+
t.Errorf("Unexpected error: %v", err)
372+
}
373+
if userInfo != nil {
374+
t.Errorf("Unexpected user: %v", userInfo)
375+
}
376+
}
377+
}
378+
379+
func TestAuthenticateTokenInvalidUID(t *testing.T) {
344380
fakeOAuthClient := oauthfake.NewSimpleClientset(
345381
&oapi.OAuthAccessToken{
346-
ObjectMeta: metav1.ObjectMeta{Name: "token", CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)}},
382+
ObjectMeta: metav1.ObjectMeta{Name: "token", CreationTimestamp: metav1.Time{Time: time.Now()}},
347383
ExpiresIn: 600, // 10 minutes
384+
UserName: "foo",
385+
UserUID: string("bar1"),
348386
},
349387
)
350388
userRegistry := usertest.NewUserRegistry()
351-
tokenAuthenticator := NewTokenAuthenticator(fakeOAuthClient.Oauth().OAuthAccessTokens(), userRegistry, identitymapper.NoopGroupMapper{})
389+
userRegistry.GetUsers["foo"] = &userapi.User{ObjectMeta: metav1.ObjectMeta{UID: "bar2"}}
390+
391+
tokenAuthenticator := NewTokenAuthenticator(fakeOAuthClient.Oauth().OAuthAccessTokens(), userRegistry, identitymapper.NoopGroupMapper{}, NewUIDValidator())
352392

353393
userInfo, found, err := tokenAuthenticator.AuthenticateToken("token")
354394
if found {
355395
t.Error("Found token, but it should be missing!")
356396
}
357-
if err != ErrExpired {
397+
if err.Error() != "user.UID (bar2) does not match token.userUID (bar1)" {
358398
t.Errorf("Unexpected error: %v", err)
359399
}
360400
if userInfo != nil {
361401
t.Errorf("Unexpected user: %v", userInfo)
362402
}
363403
}
404+
364405
func TestAuthenticateTokenValidated(t *testing.T) {
365406
fakeOAuthClient := oauthfake.NewSimpleClientset(
366407
&oapi.OAuthAccessToken{
@@ -373,7 +414,7 @@ func TestAuthenticateTokenValidated(t *testing.T) {
373414
userRegistry := usertest.NewUserRegistry()
374415
userRegistry.GetUsers["foo"] = &userapi.User{ObjectMeta: metav1.ObjectMeta{UID: "bar"}}
375416

376-
tokenAuthenticator := NewTokenAuthenticator(fakeOAuthClient.Oauth().OAuthAccessTokens(), userRegistry, identitymapper.NoopGroupMapper{})
417+
tokenAuthenticator := NewTokenAuthenticator(fakeOAuthClient.Oauth().OAuthAccessTokens(), userRegistry, identitymapper.NoopGroupMapper{}, NewExpirationValidator(), NewUIDValidator())
377418

378419
userInfo, found, err := tokenAuthenticator.AuthenticateToken("token")
379420
if !found {

‎pkg/auth/oauth/registry/tokenauthenticator.go

+18-27
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,61 @@
11
package registry
22

33
import (
4-
"errors"
5-
"fmt"
6-
"time"
7-
84
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
kauthenticator "k8s.io/apiserver/pkg/authentication/authenticator"
96
kuser "k8s.io/apiserver/pkg/authentication/user"
107

8+
"github.com/openshift/origin/pkg/auth/authenticator"
119
"github.com/openshift/origin/pkg/auth/userregistry/identitymapper"
1210
authorizationapi "github.com/openshift/origin/pkg/authorization/apis/authorization"
1311
oauthclient "github.com/openshift/origin/pkg/oauth/generated/internalclientset/typed/oauth/internalversion"
1412
userclient "github.com/openshift/origin/pkg/user/generated/internalclientset/typed/user/internalversion"
1513
)
1614

17-
type TokenAuthenticator struct {
15+
type tokenAuthenticator struct {
1816
tokens oauthclient.OAuthAccessTokenInterface
1917
users userclient.UserResourceInterface
2018
groupMapper identitymapper.UserToGroupMapper
19+
validators authenticator.OAuthTokenValidator
2120
}
2221

23-
var ErrExpired = errors.New("Token is expired")
24-
25-
func NewTokenAuthenticator(tokens oauthclient.OAuthAccessTokenInterface, users userclient.UserResourceInterface, groupMapper identitymapper.UserToGroupMapper) *TokenAuthenticator {
26-
return &TokenAuthenticator{
22+
func NewTokenAuthenticator(tokens oauthclient.OAuthAccessTokenInterface, users userclient.UserResourceInterface, groupMapper identitymapper.UserToGroupMapper, validators ...authenticator.OAuthTokenValidator) kauthenticator.Token {
23+
return &tokenAuthenticator{
2724
tokens: tokens,
2825
users: users,
2926
groupMapper: groupMapper,
27+
validators: authenticator.OAuthTokenValidators(validators),
3028
}
3129
}
3230

33-
func (a *TokenAuthenticator) AuthenticateToken(value string) (kuser.Info, bool, error) {
34-
token, err := a.tokens.Get(value, metav1.GetOptions{})
31+
func (a *tokenAuthenticator) AuthenticateToken(name string) (kuser.Info, bool, error) {
32+
token, err := a.tokens.Get(name, metav1.GetOptions{})
3533
if err != nil {
3634
return nil, false, err
3735
}
38-
if token.ExpiresIn > 0 {
39-
if token.CreationTimestamp.Time.Add(time.Duration(token.ExpiresIn) * time.Second).Before(time.Now()) {
40-
return nil, false, ErrExpired
41-
}
42-
}
43-
if token.DeletionTimestamp != nil {
44-
return nil, false, ErrExpired
45-
}
4636

47-
u, err := a.users.Get(token.UserName, metav1.GetOptions{})
37+
user, err := a.users.Get(token.UserName, metav1.GetOptions{})
4838
if err != nil {
4939
return nil, false, err
5040
}
51-
if string(u.UID) != token.UserUID {
52-
return nil, false, fmt.Errorf("user.UID (%s) does not match token.userUID (%s)", u.UID, token.UserUID)
41+
42+
if err := a.validators.Validate(token, user); err != nil {
43+
return nil, false, err
5344
}
5445

55-
groups, err := a.groupMapper.GroupsFor(u.Name)
46+
groups, err := a.groupMapper.GroupsFor(user.Name)
5647
if err != nil {
5748
return nil, false, err
5849
}
59-
groupNames := []string{}
50+
groupNames := make([]string, 0, len(groups)+len(user.Groups))
6051
for _, group := range groups {
6152
groupNames = append(groupNames, group.Name)
6253
}
63-
groupNames = append(groupNames, u.Groups...)
54+
groupNames = append(groupNames, user.Groups...)
6455

6556
return &kuser.DefaultInfo{
66-
Name: u.Name,
67-
UID: string(u.UID),
57+
Name: user.Name,
58+
UID: string(user.UID),
6859
Groups: groupNames,
6960
Extra: map[string][]string{
7061
authorizationapi.ScopesKey: token.Scopes,
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package registry
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/openshift/origin/pkg/auth/authenticator"
7+
"github.com/openshift/origin/pkg/oauth/apis/oauth"
8+
userapi "github.com/openshift/origin/pkg/user/apis/user"
9+
)
10+
11+
const errInvalidUIDStr = "user.UID (%s) does not match token.userUID (%s)"
12+
13+
func NewUIDValidator() authenticator.OAuthTokenValidator {
14+
return authenticator.OAuthTokenValidatorFunc(
15+
func(token *oauth.OAuthAccessToken, user *userapi.User) error {
16+
if string(user.UID) != token.UserUID {
17+
return fmt.Errorf(errInvalidUIDStr, user.UID, token.UserUID)
18+
}
19+
return nil
20+
},
21+
)
22+
}

‎pkg/cmd/server/origin/authenticator.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
sacontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
2121
"k8s.io/kubernetes/pkg/serviceaccount"
2222

23+
openshiftauthenticator "github.com/openshift/origin/pkg/auth/authenticator"
2324
"github.com/openshift/origin/pkg/auth/authenticator/request/paramtoken"
2425
authnregistry "github.com/openshift/origin/pkg/auth/oauth/registry"
2526
"github.com/openshift/origin/pkg/auth/userregistry/identitymapper"
@@ -88,7 +89,8 @@ func newAuthenticator(config configapi.MasterConfig, accessTokenGetter oauthclie
8889

8990
// OAuth token
9091
if config.OAuthConfig != nil {
91-
oauthTokenAuthenticator := authnregistry.NewTokenAuthenticator(accessTokenGetter, userGetter, groupMapper)
92+
validators := []openshiftauthenticator.OAuthTokenValidator{authnregistry.NewExpirationValidator(), authnregistry.NewUIDValidator()}
93+
oauthTokenAuthenticator := authnregistry.NewTokenAuthenticator(accessTokenGetter, userGetter, groupMapper, validators...)
9294
tokenAuthenticators = append(tokenAuthenticators,
9395
// if you have a bearer token, you're a human (usually)
9496
// if you change this, have a look at the impersonationFilter where we attach groups to the impersonated user

0 commit comments

Comments
 (0)
Please sign in to comment.