Skip to content

Commit 4457688

Browse files
Merge pull request #22697 from stlaz/enj/i/tighten_oauth
Bug 1703595: oauth/user/authz: tighten API validation checks
2 parents 9cf4d51 + d0e260c commit 4457688

18 files changed

Lines changed: 428 additions & 267 deletions

File tree

pkg/apiserver/authentication/oauth/tokenauthenticator.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ func (a *tokenAuthenticator) AuthenticateToken(ctx context.Context, name string)
5050
if err != nil {
5151
return nil, false, err
5252
}
53-
groupNames := make([]string, 0, len(groups)+len(user.Groups))
53+
groupNames := make([]string, 0, len(groups))
5454
for _, group := range groups {
5555
groupNames = append(groupNames, group.Name)
5656
}
57-
groupNames = append(groupNames, user.Groups...)
5857

5958
return &kauthenticator.Response{
6059
User: &kuser.DefaultInfo{

pkg/authorization/authorizer/scope/converter.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -483,12 +483,13 @@ func removeEscalatingResources(in rbacv1.PolicyRule) rbacv1.PolicyRule {
483483
}
484484

485485
func ValidateScopeRestrictions(client *oauthapi.OAuthClient, scopes ...string) error {
486+
if len(scopes) == 0 {
487+
return fmt.Errorf("%s may not request unscoped tokens", client.Name)
488+
}
489+
486490
if len(client.ScopeRestrictions) == 0 {
487491
return nil
488492
}
489-
if len(scopes) == 0 {
490-
return fmt.Errorf("%v may not request unscoped tokens", client.Name)
491-
}
492493

493494
errs := []error{}
494495
for _, scope := range scopes {
@@ -503,10 +504,6 @@ func ValidateScopeRestrictions(client *oauthapi.OAuthClient, scopes ...string) e
503504
func validateScopeRestrictions(client *oauthapi.OAuthClient, scope string) error {
504505
errs := []error{}
505506

506-
if len(client.ScopeRestrictions) == 0 {
507-
return nil
508-
}
509-
510507
for _, restriction := range client.ScopeRestrictions {
511508
if len(restriction.ExactValues) > 0 {
512509
if err := validateLiteralScopeRestrictions(scope, restriction.ExactValues); err != nil {

pkg/authorization/authorizer/scope/validate_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ func TestValidateScopeRestrictions(t *testing.T) {
2626
client: &oauthapi.OAuthClient{},
2727
},
2828
{
29-
name: "unrestricted allows none",
30-
scopes: []string{},
31-
client: &oauthapi.OAuthClient{},
29+
name: "missing scopes check precedes unrestricted",
30+
scopes: []string{},
31+
client: &oauthapi.OAuthClient{},
32+
expectedErrors: []string{"may not request unscoped tokens"},
3233
},
3334
{
3435
name: "simple literal",

pkg/oauth/apis/oauth/validation/validation.go

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"k8s.io/apiserver/pkg/authentication/serviceaccount"
1313
"k8s.io/kubernetes/pkg/apis/core/validation"
1414

15+
routev1 "github.com/openshift/api/route/v1"
1516
authorizerscopes "github.com/openshift/origin/pkg/authorization/authorizer/scope"
1617
oauthapi "github.com/openshift/origin/pkg/oauth/apis/oauth"
1718
"github.com/openshift/origin/pkg/oauthserver/authenticator/password/bootstrap"
@@ -49,7 +50,7 @@ func ValidateTokenName(name string, prefix bool) []string {
4950

5051
func ValidateRedirectURI(redirect string) (bool, string) {
5152
if len(redirect) == 0 {
52-
return true, ""
53+
return false, "may not be empty"
5354
}
5455

5556
u, err := url.Parse(redirect)
@@ -81,8 +82,7 @@ func ValidateAccessToken(accessToken *oauthapi.OAuthAccessToken) field.ErrorList
8182
}
8283
// negative values are not allowed
8384
if accessToken.InactivityTimeoutSeconds < 0 {
84-
allErrs = append(allErrs, field.Invalid(field.NewPath("inactivityTimeoutSeconds"),
85-
accessToken.InactivityTimeoutSeconds, "cannot be a negative value"))
85+
allErrs = append(allErrs, field.Invalid(field.NewPath("inactivityTimeoutSeconds"), accessToken.InactivityTimeoutSeconds, "cannot be a negative value"))
8686
}
8787
if ok, msg := ValidateRedirectURI(accessToken.RedirectURI); !ok {
8888
allErrs = append(allErrs, field.Invalid(field.NewPath("redirectURI"), accessToken.RedirectURI, msg))
@@ -178,22 +178,44 @@ func ValidateClient(client *oauthapi.OAuthClient) field.ErrorList {
178178
}
179179
}
180180

181+
for i, secret := range client.AdditionalSecrets {
182+
if len(secret) == 0 {
183+
allErrs = append(allErrs, field.Invalid(field.NewPath("additionalSecrets").Index(i), "", "may not be empty"))
184+
}
185+
}
186+
181187
for i, restriction := range client.ScopeRestrictions {
182188
allErrs = append(allErrs, ValidateScopeRestriction(restriction, field.NewPath("scopeRestrictions").Index(i))...)
183189
}
184190

191+
if accessTokenMaxAgeSeconds := client.AccessTokenMaxAgeSeconds; accessTokenMaxAgeSeconds != nil {
192+
if *accessTokenMaxAgeSeconds < 0 {
193+
allErrs = append(allErrs, field.Invalid(field.NewPath("accessTokenMaxAgeSeconds"), *accessTokenMaxAgeSeconds, "value cannot be negative"))
194+
}
195+
}
196+
185197
if client.AccessTokenInactivityTimeoutSeconds != nil {
186198
timeout := *client.AccessTokenInactivityTimeoutSeconds
187199
if timeout > 0 && timeout < MinimumInactivityTimeoutSeconds {
188-
allErrs = append(allErrs, field.Invalid(
189-
field.NewPath("accessTokenInactivityTimeoutSeconds"),
190-
client.AccessTokenInactivityTimeoutSeconds,
191-
fmt.Sprintf("The minimum valid timeout value is %d seconds", MinimumInactivityTimeoutSeconds)))
200+
msg := fmt.Sprintf("The minimum valid timeout value is %d seconds", MinimumInactivityTimeoutSeconds)
201+
allErrs = append(allErrs, field.Invalid(field.NewPath("accessTokenInactivityTimeoutSeconds"), timeout, msg))
192202
}
193203
if timeout < 0 {
194-
allErrs = append(allErrs, field.Invalid(
195-
field.NewPath("accessTokenInactivityTimeoutSeconds"),
196-
client.AccessTokenInactivityTimeoutSeconds, "value cannot be negative"))
204+
allErrs = append(allErrs, field.Invalid(field.NewPath("accessTokenInactivityTimeoutSeconds"), timeout, "value cannot be negative"))
205+
}
206+
}
207+
208+
if len(client.GrantMethod) == 0 {
209+
allErrs = append(allErrs, field.Required(field.NewPath("grantMethod"),
210+
fmt.Sprintf("must be %s or %s", oauthapi.GrantHandlerAuto, oauthapi.GrantHandlerPrompt)))
211+
} else {
212+
switch client.GrantMethod {
213+
case oauthapi.GrantHandlerAuto, oauthapi.GrantHandlerPrompt:
214+
// valid grant methods
215+
default:
216+
allErrs = append(allErrs, field.NotSupported(field.NewPath("grantMethod"), string(client.GrantMethod),
217+
[]string{string(oauthapi.GrantHandlerAuto), string(oauthapi.GrantHandlerPrompt)},
218+
))
197219
}
198220
}
199221

@@ -329,6 +351,10 @@ func ValidateUserNameField(value string, fldPath *field.Path) field.ErrorList {
329351
func ValidateScopes(scopes []string, fldPath *field.Path) field.ErrorList {
330352
allErrs := field.ErrorList{}
331353

354+
if len(scopes) == 0 {
355+
allErrs = append(allErrs, field.Required(fldPath, "may not be empty"))
356+
}
357+
332358
for i, scope := range scopes {
333359
illegalCharacter := false
334360
// https://tools.ietf.org/html/rfc6749#section-3.3 (full list of allowed chars is %x21 / %x23-5B / %x5D-7E)
@@ -370,26 +396,31 @@ func ValidateScopes(scopes []string, fldPath *field.Path) field.ErrorList {
370396

371397
func ValidateOAuthRedirectReference(sref *oauthapi.OAuthRedirectReference) field.ErrorList {
372398
allErrs := validation.ValidateObjectMeta(&sref.ObjectMeta, true, path.ValidatePathSegmentName, field.NewPath("metadata"))
373-
return append(allErrs, validateRedirectReference(&sref.Reference)...)
399+
return append(allErrs, validateRedirectReference(&sref.Reference, field.NewPath("reference"))...)
374400
}
375401

376-
func validateRedirectReference(ref *oauthapi.RedirectReference) field.ErrorList {
402+
func validateRedirectReference(ref *oauthapi.RedirectReference, fldPath *field.Path) field.ErrorList {
377403
allErrs := field.ErrorList{}
378404
if len(ref.Name) == 0 {
379-
allErrs = append(allErrs, field.Required(field.NewPath("name"), "may not be empty"))
405+
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "may not be empty"))
380406
} else {
381407
for _, msg := range path.ValidatePathSegmentName(ref.Name, false) {
382-
allErrs = append(allErrs, field.Invalid(field.NewPath("name"), ref.Name, msg))
408+
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg))
383409
}
384410
}
385411
switch ref.Kind {
386412
case "":
387-
allErrs = append(allErrs, field.Required(field.NewPath("kind"), "may not be empty"))
413+
allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "may not be empty"))
388414
case "Route":
389415
// Valid, TODO add ingress once we support it and update error message
390416
default:
391-
allErrs = append(allErrs, field.Invalid(field.NewPath("kind"), ref.Kind, "must be Route"))
417+
allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, "must be Route"))
418+
}
419+
switch ref.Group {
420+
case "", routev1.GroupName:
421+
// valid group names
422+
default:
423+
allErrs = append(allErrs, field.NotSupported(fldPath.Child("group"), ref.Group, []string{"", routev1.GroupName}))
392424
}
393-
// TODO validate group once we start using it
394425
return allErrs
395426
}

0 commit comments

Comments
 (0)