mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
Add token cache component
This commit is contained in:
parent
15d8509a71
commit
1670ba58d5
@ -117,6 +117,7 @@ filegroup(
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/request/websocket:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/request/x509:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/token/cache:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/user:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:all-srcs",
|
||||
|
54
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD
vendored
Normal file
54
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"cache_test.go",
|
||||
"cached_token_authenticator_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/github.com/pborman/uuid:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cache_simple.go",
|
||||
"cache_striped.go",
|
||||
"cached_token_authenticator.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
49
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go
vendored
Normal file
49
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
lrucache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
)
|
||||
|
||||
type simpleCache struct {
|
||||
lru *lrucache.LRUExpireCache
|
||||
}
|
||||
|
||||
func newSimpleCache(size int, clock clock.Clock) cache {
|
||||
return &simpleCache{lru: lrucache.NewLRUExpireCacheWithClock(size, clock)}
|
||||
}
|
||||
|
||||
func (c *simpleCache) get(key string) (*cacheRecord, bool) {
|
||||
record, ok := c.lru.Get(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
value, ok := record.(*cacheRecord)
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (c *simpleCache) set(key string, value *cacheRecord, ttl time.Duration) {
|
||||
c.lru.Add(key, value, ttl)
|
||||
}
|
||||
|
||||
func (c *simpleCache) remove(key string) {
|
||||
c.lru.Remove(key)
|
||||
}
|
60
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go
vendored
Normal file
60
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// split cache lookups across N striped caches
|
||||
type stripedCache struct {
|
||||
stripeCount uint32
|
||||
keyFunc func(string) uint32
|
||||
caches []cache
|
||||
}
|
||||
|
||||
type keyFunc func(string) uint32
|
||||
type newCacheFunc func() cache
|
||||
|
||||
func newStripedCache(stripeCount int, keyFunc keyFunc, newCacheFunc newCacheFunc) cache {
|
||||
caches := []cache{}
|
||||
for i := 0; i < stripeCount; i++ {
|
||||
caches = append(caches, newCacheFunc())
|
||||
}
|
||||
return &stripedCache{
|
||||
stripeCount: uint32(stripeCount),
|
||||
keyFunc: keyFunc,
|
||||
caches: caches,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stripedCache) get(key string) (*cacheRecord, bool) {
|
||||
return c.caches[c.keyFunc(key)%c.stripeCount].get(key)
|
||||
}
|
||||
func (c *stripedCache) set(key string, value *cacheRecord, ttl time.Duration) {
|
||||
c.caches[c.keyFunc(key)%c.stripeCount].set(key, value, ttl)
|
||||
}
|
||||
func (c *stripedCache) remove(key string) {
|
||||
c.caches[c.keyFunc(key)%c.stripeCount].remove(key)
|
||||
}
|
||||
|
||||
func fnvKeyFunc(key string) uint32 {
|
||||
f := fnv.New32()
|
||||
f.Write([]byte(key))
|
||||
return f.Sum32()
|
||||
}
|
94
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_test.go
vendored
Normal file
94
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
func TestSimpleCache(t *testing.T) {
|
||||
testCache(newSimpleCache(4096, clock.RealClock{}), t)
|
||||
}
|
||||
|
||||
func BenchmarkSimpleCache(b *testing.B) {
|
||||
benchmarkCache(newSimpleCache(4096, clock.RealClock{}), b)
|
||||
}
|
||||
|
||||
func TestStripedCache(t *testing.T) {
|
||||
testCache(newStripedCache(32, fnvKeyFunc, func() cache { return newSimpleCache(128, clock.RealClock{}) }), t)
|
||||
}
|
||||
|
||||
func BenchmarkStripedCache(b *testing.B) {
|
||||
benchmarkCache(newStripedCache(32, fnvKeyFunc, func() cache { return newSimpleCache(128, clock.RealClock{}) }), b)
|
||||
}
|
||||
|
||||
func benchmarkCache(cache cache, b *testing.B) {
|
||||
keys := []string{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := uuid.NewRandom().String()
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.SetParallelism(500)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
key := keys[rand.Intn(b.N)]
|
||||
_, ok := cache.get(key)
|
||||
if ok {
|
||||
cache.remove(key)
|
||||
} else {
|
||||
cache.set(key, &cacheRecord{}, time.Second)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testCache(cache cache, t *testing.T) {
|
||||
if result, ok := cache.get("foo"); ok || result != nil {
|
||||
t.Errorf("Expected null, false, got %#v, %v", result, ok)
|
||||
}
|
||||
|
||||
record1 := &cacheRecord{user: &user.DefaultInfo{Name: "bob"}}
|
||||
record2 := &cacheRecord{user: &user.DefaultInfo{Name: "alice"}}
|
||||
|
||||
// when empty, record is stored
|
||||
cache.set("foo", record1, time.Hour)
|
||||
if result, ok := cache.get("foo"); !ok || result != record1 {
|
||||
t.Errorf("Expected %#v, true, got %#v, %v", record1, ok)
|
||||
}
|
||||
|
||||
// newer record overrides
|
||||
cache.set("foo", record2, time.Hour)
|
||||
if result, ok := cache.get("foo"); !ok || result != record2 {
|
||||
t.Errorf("Expected %#v, true, got %#v, %v", record2, ok)
|
||||
}
|
||||
|
||||
// removing the current value removes
|
||||
cache.remove("foo")
|
||||
if result, ok := cache.get("foo"); ok || result != nil {
|
||||
t.Errorf("Expected null, false, got %#v, %v", result, ok)
|
||||
}
|
||||
}
|
82
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go
vendored
Normal file
82
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
utilclock "k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// cacheRecord holds the three return values of the authenticator.Token AuthenticateToken method
|
||||
type cacheRecord struct {
|
||||
user user.Info
|
||||
ok bool
|
||||
err error
|
||||
}
|
||||
|
||||
type cachedTokenAuthenticator struct {
|
||||
authenticator authenticator.Token
|
||||
|
||||
successTTL time.Duration
|
||||
failureTTL time.Duration
|
||||
|
||||
cache cache
|
||||
}
|
||||
|
||||
type cache interface {
|
||||
// given a key, return the record, and whether or not it existed
|
||||
get(key string) (value *cacheRecord, exists bool)
|
||||
// caches the record for the key
|
||||
set(key string, value *cacheRecord, ttl time.Duration)
|
||||
// removes the record for the key
|
||||
remove(key string)
|
||||
}
|
||||
|
||||
// New returns a token authenticator that caches the results of the specified authenticator. A ttl of 0 bypasses the cache.
|
||||
func New(authenticator authenticator.Token, successTTL, failureTTL time.Duration) authenticator.Token {
|
||||
return newWithClock(authenticator, successTTL, failureTTL, utilclock.RealClock{})
|
||||
}
|
||||
|
||||
func newWithClock(authenticator authenticator.Token, successTTL, failureTTL time.Duration, clock utilclock.Clock) authenticator.Token {
|
||||
return &cachedTokenAuthenticator{
|
||||
authenticator: authenticator,
|
||||
successTTL: successTTL,
|
||||
failureTTL: failureTTL,
|
||||
cache: newStripedCache(32, fnvKeyFunc, func() cache { return newSimpleCache(128, clock) }),
|
||||
}
|
||||
}
|
||||
|
||||
// AuthenticateToken implements authenticator.Token
|
||||
func (a *cachedTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
|
||||
if record, ok := a.cache.get(token); ok {
|
||||
return record.user, record.ok, record.err
|
||||
}
|
||||
|
||||
user, ok, err := a.authenticator.AuthenticateToken(token)
|
||||
|
||||
switch {
|
||||
case ok && a.successTTL > 0:
|
||||
a.cache.set(token, &cacheRecord{user: user, ok: ok, err: err}, a.successTTL)
|
||||
case !ok && a.failureTTL > 0:
|
||||
a.cache.set(token, &cacheRecord{user: user, ok: ok, err: err}, a.failureTTL)
|
||||
}
|
||||
|
||||
return user, ok, err
|
||||
}
|
105
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator_test.go
vendored
Normal file
105
staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator_test.go
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
utilclock "k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
func TestCachedTokenAuthenticator(t *testing.T) {
|
||||
var (
|
||||
calledWithToken []string
|
||||
|
||||
resultUsers map[string]user.Info
|
||||
resultOk bool
|
||||
resultErr error
|
||||
)
|
||||
fakeAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
|
||||
calledWithToken = append(calledWithToken, token)
|
||||
return resultUsers[token], resultOk, resultErr
|
||||
})
|
||||
fakeClock := utilclock.NewFakeClock(time.Now())
|
||||
|
||||
a := newWithClock(fakeAuth, time.Minute, 0, fakeClock)
|
||||
|
||||
calledWithToken, resultUsers, resultOk, resultErr = []string{}, nil, false, nil
|
||||
a.AuthenticateToken("bad1")
|
||||
a.AuthenticateToken("bad2")
|
||||
a.AuthenticateToken("bad3")
|
||||
a.AuthenticateToken("bad1")
|
||||
a.AuthenticateToken("bad2")
|
||||
a.AuthenticateToken("bad3")
|
||||
if !reflect.DeepEqual(calledWithToken, []string{"bad1", "bad2", "bad3", "bad1", "bad2", "bad3"}) {
|
||||
t.Errorf("Expected failing calls to bypass cache, got %v", calledWithToken)
|
||||
}
|
||||
|
||||
// reset calls, make the backend return success for three user tokens
|
||||
calledWithToken = []string{}
|
||||
resultUsers, resultOk, resultErr = map[string]user.Info{}, true, nil
|
||||
resultUsers["usertoken1"] = &user.DefaultInfo{Name: "user1"}
|
||||
resultUsers["usertoken2"] = &user.DefaultInfo{Name: "user2"}
|
||||
resultUsers["usertoken3"] = &user.DefaultInfo{Name: "user3"}
|
||||
|
||||
// populate cache
|
||||
if user, ok, err := a.AuthenticateToken("usertoken1"); err != nil || !ok || user.GetName() != "user1" {
|
||||
t.Errorf("Expected user1")
|
||||
}
|
||||
if user, ok, err := a.AuthenticateToken("usertoken2"); err != nil || !ok || user.GetName() != "user2" {
|
||||
t.Errorf("Expected user2")
|
||||
}
|
||||
if user, ok, err := a.AuthenticateToken("usertoken3"); err != nil || !ok || user.GetName() != "user3" {
|
||||
t.Errorf("Expected user3")
|
||||
}
|
||||
if !reflect.DeepEqual(calledWithToken, []string{"usertoken1", "usertoken2", "usertoken3"}) {
|
||||
t.Errorf("Expected token calls, got %v", calledWithToken)
|
||||
}
|
||||
|
||||
// reset calls, make the backend return failures
|
||||
calledWithToken = []string{}
|
||||
resultUsers, resultOk, resultErr = nil, false, nil
|
||||
|
||||
// authenticate calls still succeed and backend is not hit
|
||||
if user, ok, err := a.AuthenticateToken("usertoken1"); err != nil || !ok || user.GetName() != "user1" {
|
||||
t.Errorf("Expected user1")
|
||||
}
|
||||
if user, ok, err := a.AuthenticateToken("usertoken2"); err != nil || !ok || user.GetName() != "user2" {
|
||||
t.Errorf("Expected user2")
|
||||
}
|
||||
if user, ok, err := a.AuthenticateToken("usertoken3"); err != nil || !ok || user.GetName() != "user3" {
|
||||
t.Errorf("Expected user3")
|
||||
}
|
||||
if !reflect.DeepEqual(calledWithToken, []string{}) {
|
||||
t.Errorf("Expected no token calls, got %v", calledWithToken)
|
||||
}
|
||||
|
||||
// skip forward in time
|
||||
fakeClock.Step(2 * time.Minute)
|
||||
|
||||
// backend is consulted again and fails
|
||||
a.AuthenticateToken("usertoken1")
|
||||
a.AuthenticateToken("usertoken2")
|
||||
a.AuthenticateToken("usertoken3")
|
||||
if !reflect.DeepEqual(calledWithToken, []string{"usertoken1", "usertoken2", "usertoken3"}) {
|
||||
t.Errorf("Expected token calls, got %v", calledWithToken)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user