Skip to content

Commit 36839e4

Browse files
authored
refactor: cache authorizer & short local k8s timeout (#2528) (#2533)
1 parent a5048cf commit 36839e4

File tree

7 files changed

+117
-7
lines changed

7 files changed

+117
-7
lines changed

config/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"os"
89
"reflect"
910
"regexp"
1011
"strings"
@@ -333,6 +334,10 @@ func ValidateStoreAndDistroChanges(currentStoreType, previousStoreType StoreType
333334
}
334335

335336
func (c *Config) IsProFeatureEnabled() bool {
337+
if os.Getenv("SKIP_VALIDATE_PRO_FEATURES") == "true" {
338+
return false
339+
}
340+
336341
if len(c.Networking.ResolveDNS) > 0 {
337342
return true
338343
}

pkg/authentication/delegatingauthenticator/delegatingauthenticator.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ import (
1313
"sigs.k8s.io/controller-runtime/pkg/client"
1414
)
1515

16+
var (
17+
cacheTime = 5 * time.Second
18+
)
19+
1620
func New(client client.Client) authenticator.Request {
17-
cache, _ := lru.New[string, cacheEntry](256)
21+
cache, _ := lru.New[string, cacheEntry](512)
1822
return bearertoken.New(&delegatingAuthenticator{
1923
client: client,
2024
cache: cache,
@@ -63,7 +67,7 @@ func (d *delegatingAuthenticator) AuthenticateToken(ctx context.Context, token s
6367
}
6468
d.cache.Add(token, cacheEntry{
6569
response: response,
66-
exp: now.Add(time.Second * 5),
70+
exp: now.Add(cacheTime),
6771
})
6872
return response, true, nil
6973
}

pkg/authorization/delegatingauthorizer/authorizer.go

+11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ func New(delegatingClient client.Client, resources []GroupVersionResourceVerb, n
2828

2929
nonResources: nonResources,
3030
resources: resources,
31+
32+
cache: NewCache(),
3133
}
3234
}
3335

@@ -36,13 +38,21 @@ type delegatingAuthorizer struct {
3638

3739
nonResources []PathVerb
3840
resources []GroupVersionResourceVerb
41+
42+
cache *Cache
3943
}
4044

4145
func (l *delegatingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
4246
if !applies(a, l.resources, l.nonResources) {
4347
return authorizer.DecisionNoOpinion, "", nil
4448
}
4549

50+
// check if in cache
51+
authorized, reason, exists := l.cache.Get(a)
52+
if exists {
53+
return authorized, reason, nil
54+
}
55+
4656
// check if request is allowed in the target cluster
4757
accessReview := &authorizationv1.SubjectAccessReview{
4858
ObjectMeta: metav1.ObjectMeta{},
@@ -73,6 +83,7 @@ func (l *delegatingAuthorizer) Authorize(ctx context.Context, a authorizer.Attri
7383
if err != nil {
7484
return authorizer.DecisionDeny, "", err
7585
} else if accessReview.Status.Allowed && !accessReview.Status.Denied {
86+
l.cache.Set(a, authorizer.DecisionAllow, "")
7687
return authorizer.DecisionAllow, "", nil
7788
}
7889

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package delegatingauthorizer
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"strings"
7+
"time"
8+
9+
lru "github.com/hashicorp/golang-lru/v2"
10+
"k8s.io/apiserver/pkg/authorization/authorizer"
11+
)
12+
13+
var (
14+
cacheTime = 5 * time.Second
15+
)
16+
17+
type Cache struct {
18+
cache *lru.Cache[string, cacheEntry]
19+
}
20+
21+
func NewCache() *Cache {
22+
cache, _ := lru.New[string, cacheEntry](256)
23+
return &Cache{
24+
cache: cache,
25+
}
26+
}
27+
28+
type cacheEntry struct {
29+
authorized authorizer.Decision
30+
reason string
31+
32+
exp time.Time
33+
}
34+
35+
func (c *Cache) Set(a authorizer.Attributes, authorized authorizer.Decision, reason string) {
36+
c.cache.Add(getCacheKey(a), cacheEntry{
37+
authorized: authorized,
38+
reason: reason,
39+
exp: time.Now().Add(cacheTime),
40+
})
41+
}
42+
43+
func (c *Cache) Get(a authorizer.Attributes) (authorized authorizer.Decision, reason string, exists bool) {
44+
// check if in cache
45+
now := time.Now()
46+
entry, ok := c.cache.Get(getCacheKey(a))
47+
if ok && entry.exp.After(now) {
48+
return entry.authorized, entry.reason, true
49+
}
50+
51+
return authorizer.DecisionNoOpinion, "", false
52+
}
53+
54+
func getCacheKey(a authorizer.Attributes) string {
55+
parts := []string{}
56+
if a.GetUser() != nil {
57+
parts = append(parts, a.GetUser().GetName(), a.GetUser().GetUID(), strings.Join(a.GetUser().GetGroups(), ","))
58+
}
59+
if a.IsResourceRequest() {
60+
parts = append(parts, a.GetAPIGroup(), a.GetAPIVersion(), a.GetResource(), a.GetSubresource(), a.GetVerb(), a.GetNamespace(), a.GetName())
61+
} else {
62+
parts = append(parts, a.GetPath(), a.GetVerb())
63+
}
64+
65+
// hash the string
66+
h := sha256.New()
67+
h.Write([]byte(strings.Join(parts, "#")))
68+
return hex.EncodeToString(h.Sum(nil))
69+
}

pkg/authorization/impersonationauthorizer/authorizer.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package impersonationauthorizer
33
import (
44
"context"
55

6+
delegatingauthorizer "github.com/loft-sh/vcluster/pkg/authorization/delegatingauthorizer"
67
"github.com/loft-sh/vcluster/pkg/util/clienthelper"
7-
88
authorizationv1 "k8s.io/api/authorization/v1"
99
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1010
"k8s.io/apiserver/pkg/authorization/authorizer"
@@ -14,18 +14,28 @@ import (
1414
func New(client client.Client) authorizer.Authorizer {
1515
return &impersonationAuthorizer{
1616
client: client,
17+
18+
cache: delegatingauthorizer.NewCache(),
1719
}
1820
}
1921

2022
type impersonationAuthorizer struct {
2123
client client.Client
24+
25+
cache *delegatingauthorizer.Cache
2226
}
2327

2428
func (i *impersonationAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
2529
if a.GetVerb() != "impersonate" || !a.IsResourceRequest() {
2630
return authorizer.DecisionNoOpinion, "", nil
2731
}
2832

33+
// check if in cache
34+
authorized, reason, exists := i.cache.Get(a)
35+
if exists {
36+
return authorized, reason, nil
37+
}
38+
2939
// check if request is allowed in the target cluster
3040
accessReview := &authorizationv1.SubjectAccessReview{
3141
ObjectMeta: metav1.ObjectMeta{},
@@ -48,9 +58,8 @@ func (i *impersonationAuthorizer) Authorize(ctx context.Context, a authorizer.At
4858
err = i.client.Create(ctx, accessReview)
4959
if err != nil {
5060
return authorizer.DecisionDeny, "", err
51-
}
52-
53-
if accessReview.Status.Allowed && !accessReview.Status.Denied {
61+
} else if accessReview.Status.Allowed && !accessReview.Status.Denied {
62+
i.cache.Set(a, authorizer.DecisionAllow, "")
5463
return authorizer.DecisionAllow, "", nil
5564
}
5665

pkg/authorization/kubeletauthorizer/authorizer.go

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kubeletauthorizer
33
import (
44
"context"
55

6+
"github.com/loft-sh/vcluster/pkg/authorization/delegatingauthorizer"
67
"github.com/loft-sh/vcluster/pkg/server/filters"
78
"github.com/loft-sh/vcluster/pkg/util/clienthelper"
89
authorizationv1 "k8s.io/api/authorization/v1"
@@ -20,11 +21,15 @@ type PathVerb struct {
2021
func New(uncachedVirtualClient client.Client) authorizer.Authorizer {
2122
return &kubeletAuthorizer{
2223
uncachedVirtualClient: uncachedVirtualClient,
24+
25+
cache: delegatingauthorizer.NewCache(),
2326
}
2427
}
2528

2629
type kubeletAuthorizer struct {
2730
uncachedVirtualClient client.Client
31+
32+
cache *delegatingauthorizer.Cache
2833
}
2934

3035
func (l *kubeletAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { // get node name
@@ -35,6 +40,12 @@ func (l *kubeletAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
3540
return authorizer.DecisionDeny, "forbidden", nil
3641
}
3742

43+
// check if in cache
44+
authorized, reason, exists := l.cache.Get(a)
45+
if exists {
46+
return authorized, reason, nil
47+
}
48+
3849
// check if request is allowed in the target cluster
3950
accessReview := &authorizationv1.SubjectAccessReview{
4051
ObjectMeta: metav1.ObjectMeta{},
@@ -76,6 +87,7 @@ func (l *kubeletAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
7687
if err != nil {
7788
return authorizer.DecisionDeny, "", err
7889
} else if accessReview.Status.Allowed && !accessReview.Status.Denied {
90+
l.cache.Set(a, authorizer.DecisionAllow, "")
7991
return authorizer.DecisionAllow, "", nil
8092
}
8193

pkg/cli/localkubernetes/configure.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (c ClusterType) LocalKubernetes() bool {
3030

3131
func ExposeLocal(ctx context.Context, rawConfig *clientcmdapi.Config, vRawConfig *clientcmdapi.Config, service *corev1.Service) (string, error) {
3232
// Timeout to wait for connection before falling back to port-forwarding
33-
timeout := time.Second * 30
33+
timeout := time.Second * 5
3434
clusterType := DetectClusterType(rawConfig)
3535
switch clusterType {
3636
case ClusterTypeOrbstack:

0 commit comments

Comments
 (0)