Skip to content

Commit

Permalink
refactor: cache authorizer & short local k8s timeout (#2528) (#2533)
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianKramm authored Feb 24, 2025
1 parent a5048cf commit 36839e4
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 7 deletions.
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"regexp"
"strings"
Expand Down Expand Up @@ -333,6 +334,10 @@ func ValidateStoreAndDistroChanges(currentStoreType, previousStoreType StoreType
}

func (c *Config) IsProFeatureEnabled() bool {
if os.Getenv("SKIP_VALIDATE_PRO_FEATURES") == "true" {
return false
}

if len(c.Networking.ResolveDNS) > 0 {
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
cacheTime = 5 * time.Second
)

func New(client client.Client) authenticator.Request {
cache, _ := lru.New[string, cacheEntry](256)
cache, _ := lru.New[string, cacheEntry](512)
return bearertoken.New(&delegatingAuthenticator{
client: client,
cache: cache,
Expand Down Expand Up @@ -63,7 +67,7 @@ func (d *delegatingAuthenticator) AuthenticateToken(ctx context.Context, token s
}
d.cache.Add(token, cacheEntry{
response: response,
exp: now.Add(time.Second * 5),
exp: now.Add(cacheTime),
})
return response, true, nil
}
11 changes: 11 additions & 0 deletions pkg/authorization/delegatingauthorizer/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ func New(delegatingClient client.Client, resources []GroupVersionResourceVerb, n

nonResources: nonResources,
resources: resources,

cache: NewCache(),
}
}

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

nonResources []PathVerb
resources []GroupVersionResourceVerb

cache *Cache
}

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

// check if in cache
authorized, reason, exists := l.cache.Get(a)
if exists {
return authorized, reason, nil
}

// check if request is allowed in the target cluster
accessReview := &authorizationv1.SubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{},
Expand Down Expand Up @@ -73,6 +83,7 @@ func (l *delegatingAuthorizer) Authorize(ctx context.Context, a authorizer.Attri
if err != nil {
return authorizer.DecisionDeny, "", err
} else if accessReview.Status.Allowed && !accessReview.Status.Denied {
l.cache.Set(a, authorizer.DecisionAllow, "")
return authorizer.DecisionAllow, "", nil
}

Expand Down
69 changes: 69 additions & 0 deletions pkg/authorization/delegatingauthorizer/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package delegatingauthorizer

import (
"crypto/sha256"
"encoding/hex"
"strings"
"time"

lru "github.com/hashicorp/golang-lru/v2"
"k8s.io/apiserver/pkg/authorization/authorizer"
)

var (
cacheTime = 5 * time.Second
)

type Cache struct {
cache *lru.Cache[string, cacheEntry]
}

func NewCache() *Cache {
cache, _ := lru.New[string, cacheEntry](256)
return &Cache{
cache: cache,
}
}

type cacheEntry struct {
authorized authorizer.Decision
reason string

exp time.Time
}

func (c *Cache) Set(a authorizer.Attributes, authorized authorizer.Decision, reason string) {
c.cache.Add(getCacheKey(a), cacheEntry{
authorized: authorized,
reason: reason,
exp: time.Now().Add(cacheTime),
})
}

func (c *Cache) Get(a authorizer.Attributes) (authorized authorizer.Decision, reason string, exists bool) {
// check if in cache
now := time.Now()
entry, ok := c.cache.Get(getCacheKey(a))
if ok && entry.exp.After(now) {
return entry.authorized, entry.reason, true
}

return authorizer.DecisionNoOpinion, "", false
}

func getCacheKey(a authorizer.Attributes) string {
parts := []string{}
if a.GetUser() != nil {
parts = append(parts, a.GetUser().GetName(), a.GetUser().GetUID(), strings.Join(a.GetUser().GetGroups(), ","))
}
if a.IsResourceRequest() {
parts = append(parts, a.GetAPIGroup(), a.GetAPIVersion(), a.GetResource(), a.GetSubresource(), a.GetVerb(), a.GetNamespace(), a.GetName())
} else {
parts = append(parts, a.GetPath(), a.GetVerb())
}

// hash the string
h := sha256.New()
h.Write([]byte(strings.Join(parts, "#")))
return hex.EncodeToString(h.Sum(nil))
}
17 changes: 13 additions & 4 deletions pkg/authorization/impersonationauthorizer/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package impersonationauthorizer
import (
"context"

delegatingauthorizer "github.com/loft-sh/vcluster/pkg/authorization/delegatingauthorizer"
"github.com/loft-sh/vcluster/pkg/util/clienthelper"

authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authorization/authorizer"
Expand All @@ -14,18 +14,28 @@ import (
func New(client client.Client) authorizer.Authorizer {
return &impersonationAuthorizer{
client: client,

cache: delegatingauthorizer.NewCache(),
}
}

type impersonationAuthorizer struct {
client client.Client

cache *delegatingauthorizer.Cache
}

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

// check if in cache
authorized, reason, exists := i.cache.Get(a)
if exists {
return authorized, reason, nil
}

// check if request is allowed in the target cluster
accessReview := &authorizationv1.SubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{},
Expand All @@ -48,9 +58,8 @@ func (i *impersonationAuthorizer) Authorize(ctx context.Context, a authorizer.At
err = i.client.Create(ctx, accessReview)
if err != nil {
return authorizer.DecisionDeny, "", err
}

if accessReview.Status.Allowed && !accessReview.Status.Denied {
} else if accessReview.Status.Allowed && !accessReview.Status.Denied {
i.cache.Set(a, authorizer.DecisionAllow, "")
return authorizer.DecisionAllow, "", nil
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/authorization/kubeletauthorizer/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kubeletauthorizer
import (
"context"

"github.com/loft-sh/vcluster/pkg/authorization/delegatingauthorizer"
"github.com/loft-sh/vcluster/pkg/server/filters"
"github.com/loft-sh/vcluster/pkg/util/clienthelper"
authorizationv1 "k8s.io/api/authorization/v1"
Expand All @@ -20,11 +21,15 @@ type PathVerb struct {
func New(uncachedVirtualClient client.Client) authorizer.Authorizer {
return &kubeletAuthorizer{
uncachedVirtualClient: uncachedVirtualClient,

cache: delegatingauthorizer.NewCache(),
}
}

type kubeletAuthorizer struct {
uncachedVirtualClient client.Client

cache *delegatingauthorizer.Cache
}

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

// check if in cache
authorized, reason, exists := l.cache.Get(a)
if exists {
return authorized, reason, nil
}

// check if request is allowed in the target cluster
accessReview := &authorizationv1.SubjectAccessReview{
ObjectMeta: metav1.ObjectMeta{},
Expand Down Expand Up @@ -76,6 +87,7 @@ func (l *kubeletAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
if err != nil {
return authorizer.DecisionDeny, "", err
} else if accessReview.Status.Allowed && !accessReview.Status.Denied {
l.cache.Set(a, authorizer.DecisionAllow, "")
return authorizer.DecisionAllow, "", nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/localkubernetes/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (c ClusterType) LocalKubernetes() bool {

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

0 comments on commit 36839e4

Please sign in to comment.