From dc9e3f1b3e9e557e2676393b460c1eb43a097282 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Thu, 17 May 2018 12:47:27 -0700 Subject: [PATCH] svcacct: validate min and max expiration seconds on TokenRequest --- pkg/apis/authentication/BUILD | 1 + pkg/apis/authentication/validation/BUILD | 26 +++++++++++ .../authentication/validation/validation.go | 41 +++++++++++++++++ .../core/serviceaccount/storage/BUILD | 1 + .../core/serviceaccount/storage/token.go | 19 +++++--- test/integration/auth/BUILD | 1 + test/integration/auth/svcaccttoken_test.go | 46 ++++++++++++------- 7 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 pkg/apis/authentication/validation/BUILD create mode 100644 pkg/apis/authentication/validation/validation.go diff --git a/pkg/apis/authentication/BUILD b/pkg/apis/authentication/BUILD index 1d09c7766db..1781f634232 100644 --- a/pkg/apis/authentication/BUILD +++ b/pkg/apis/authentication/BUILD @@ -37,6 +37,7 @@ filegroup( "//pkg/apis/authentication/install:all-srcs", "//pkg/apis/authentication/v1:all-srcs", "//pkg/apis/authentication/v1beta1:all-srcs", + "//pkg/apis/authentication/validation:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/apis/authentication/validation/BUILD b/pkg/apis/authentication/validation/BUILD new file mode 100644 index 00000000000..3192543f9c7 --- /dev/null +++ b/pkg/apis/authentication/validation/BUILD @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importpath = "k8s.io/kubernetes/pkg/apis/authentication/validation", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/authentication:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/authentication/validation/validation.go b/pkg/apis/authentication/validation/validation.go new file mode 100644 index 00000000000..b174ddfc1e9 --- /dev/null +++ b/pkg/apis/authentication/validation/validation.go @@ -0,0 +1,41 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package validation contains methods to validate kinds in the +// authentication.k8s.io API group. +package validation + +import ( + "time" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/authentication" +) + +// ValidateTokenRequest validates a TokenRequest. +func ValidateTokenRequest(tr *authentication.TokenRequest) field.ErrorList { + allErrs := field.ErrorList{} + specPath := field.NewPath("spec") + + const min = 10 * time.Minute + if tr.Spec.ExpirationSeconds < int64(min.Seconds()) { + allErrs = append(allErrs, field.Invalid(specPath.Child("expirationSeconds"), tr.Spec.ExpirationSeconds, "may not specify a duration less than 10 minutes")) + } + if tr.Spec.ExpirationSeconds > 1<<32 { + allErrs = append(allErrs, field.Invalid(specPath.Child("expirationSeconds"), tr.Spec.ExpirationSeconds, "may not specify a duration larger than 2^32 seconds")) + } + return allErrs +} diff --git a/pkg/registry/core/serviceaccount/storage/BUILD b/pkg/registry/core/serviceaccount/storage/BUILD index 1469f663888..d74dcd4db52 100644 --- a/pkg/registry/core/serviceaccount/storage/BUILD +++ b/pkg/registry/core/serviceaccount/storage/BUILD @@ -32,6 +32,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/registry/core/serviceaccount/storage", deps = [ "//pkg/apis/authentication:go_default_library", + "//pkg/apis/authentication/validation:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", diff --git a/pkg/registry/core/serviceaccount/storage/token.go b/pkg/registry/core/serviceaccount/storage/token.go index 32df9503f10..e1a94f6e50f 100644 --- a/pkg/registry/core/serviceaccount/storage/token.go +++ b/pkg/registry/core/serviceaccount/storage/token.go @@ -29,6 +29,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" authenticationapi "k8s.io/kubernetes/pkg/apis/authentication" + authenticationvalidation "k8s.io/kubernetes/pkg/apis/authentication/validation" api "k8s.io/kubernetes/pkg/apis/core" token "k8s.io/kubernetes/pkg/serviceaccount" ) @@ -48,6 +49,12 @@ type TokenREST struct { var _ = rest.NamedCreater(&TokenREST{}) var _ = rest.GroupVersionKindProvider(&TokenREST{}) +var gvk = schema.GroupVersionKind{ + Group: authenticationapiv1.SchemeGroupVersion.Group, + Version: authenticationapiv1.SchemeGroupVersion.Version, + Kind: "TokenRequest", +} + func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if err := createValidation(obj); err != nil { return nil, err @@ -55,6 +62,10 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object, out := obj.(*authenticationapi.TokenRequest) + if errs := authenticationvalidation.ValidateTokenRequest(out); len(errs) != 0 { + return nil, errors.NewInvalid(gvk.GroupKind(), "", errs) + } + svcacctObj, err := r.svcaccts.Get(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, err @@ -113,12 +124,8 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object, return out, nil } -func (r *TokenREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { - return schema.GroupVersionKind{ - Group: authenticationapiv1.SchemeGroupVersion.Group, - Version: authenticationapiv1.SchemeGroupVersion.Version, - Kind: "TokenRequest", - } +func (r *TokenREST) GroupVersionKind(schema.GroupVersion) schema.GroupVersionKind { + return gvk } type getter interface { diff --git a/test/integration/auth/BUILD b/test/integration/auth/BUILD index 619c3df4a09..e5ed620905f 100644 --- a/test/integration/auth/BUILD +++ b/test/integration/auth/BUILD @@ -51,6 +51,7 @@ go_test( "//test/integration:go_default_library", "//test/integration/framework:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library", "//vendor/k8s.io/api/authentication/v1:go_default_library", "//vendor/k8s.io/api/authentication/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/test/integration/auth/svcaccttoken_test.go b/test/integration/auth/svcaccttoken_test.go index 65edcc44dc5..ac7ba22edae 100644 --- a/test/integration/auth/svcaccttoken_test.go +++ b/test/integration/auth/svcaccttoken_test.go @@ -24,17 +24,20 @@ import ( "testing" "time" + "gopkg.in/square/go-jose.v2/jwt" authenticationv1 "k8s.io/api/authentication/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/authentication/request/bearertoken" + apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authorization/authorizerfactory" utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" clientset "k8s.io/client-go/kubernetes" externalclientset "k8s.io/client-go/kubernetes" certutil "k8s.io/client-go/util/cert" + "k8s.io/kubernetes/pkg/apis/core" serviceaccountgetter "k8s.io/kubernetes/pkg/controller/serviceaccount" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/serviceaccount" @@ -118,7 +121,6 @@ func TestServiceAccountTokenCreate(t *testing.T) { }, } - one = int64(1) wrongUID = types.UID("wrong") noUID = types.UID("") ) @@ -126,8 +128,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("bound to service account", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, }, } @@ -157,8 +158,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("bound to service account and pod", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, BoundObjectRef: &authenticationv1.BoundObjectReference{ Kind: "Pod", APIVersion: "v1", @@ -211,8 +211,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("bound to service account and secret", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, BoundObjectRef: &authenticationv1.BoundObjectReference{ Kind: "Secret", APIVersion: "v1", @@ -266,8 +265,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("bound to service account and pod running as different service account", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, BoundObjectRef: &authenticationv1.BoundObjectReference{ Kind: "Pod", APIVersion: "v1", @@ -289,8 +287,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("expired token", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, }, } @@ -303,7 +300,26 @@ func TestServiceAccountTokenCreate(t *testing.T) { } doTokenReview(t, cs, treq, false) - time.Sleep(63 * time.Second) + + // backdate the token + then := time.Now().Add(-2 * time.Hour) + sc := &jwt.Claims{ + Subject: apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name), + Audience: jwt.Audience([]string{"api"}), + IssuedAt: jwt.NewNumericDate(then), + NotBefore: jwt.NewNumericDate(then), + Expiry: jwt.NewNumericDate(then.Add(time.Duration(60*60) * time.Second)), + } + coresa := core.ServiceAccount{ + ObjectMeta: sa.ObjectMeta, + } + _, pc := serviceaccount.Claims(coresa, nil, nil, 0, nil) + tok, err := masterConfig.ExtraConfig.ServiceAccountIssuer.GenerateToken(sc, pc) + if err != nil { + t.Fatalf("err signing expired token: %v", err) + } + + treq.Status.Token = tok doTokenReview(t, cs, treq, true) }) @@ -346,8 +362,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("a token should be invalid after recreating same name pod", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, BoundObjectRef: &authenticationv1.BoundObjectReference{ Kind: "Pod", APIVersion: "v1", @@ -386,8 +401,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { t.Run("a token should be invalid after recreating same name secret", func(t *testing.T) { treq := &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{"api"}, - ExpirationSeconds: &one, + Audiences: []string{"api"}, BoundObjectRef: &authenticationv1.BoundObjectReference{ Kind: "Secret", APIVersion: "v1",