Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching in Proxy #877

Merged
merged 7 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog/unreleased/proxy-cache-userinfo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Cache userinfo in proxy

Tags: proxy

We introduced caching for the userinfo response. The token expiration is used for cache invalidation if available. Otherwise we fall back to a preconfigured TTL (default 10 seconds).

https://github.com/owncloud/ocis/pull/877
6 changes: 2 additions & 4 deletions konnectd/assets/identifier-registration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
clients:
- id: phoenix
name: ownCloud web app
application_type: web
insecure: yes
trusted: yes
insecure: yes
redirect_uris:
- https://localhost:9200/
- https://localhost:9200/oidc-callback.html
Expand All @@ -19,9 +18,8 @@ clients:
- http://localhost:9100

- id: ocis-explorer.js
name: OCIS Graph Explorer
name: oCIS Graph Explorer
trusted: yes
application_type: web
insecure: yes

- id: xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69
Expand Down
158 changes: 50 additions & 108 deletions konnectd/pkg/assets/embed.go

Large diffs are not rendered by default.

83 changes: 30 additions & 53 deletions proxy/pkg/cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package cache

import (
"fmt"
"sync"
"time"
)

// Entry represents an entry on the cache. You can type assert on V.
type Entry struct {
V interface{}
Valid bool
V interface{}
expiration time.Time
}

// Cache is a barebones cache implementation.
type Cache struct {
entries map[string]map[string]Entry
entries map[string]*Entry
size int
m sync.Mutex
}
Expand All @@ -24,78 +24,55 @@ func NewCache(o ...Option) Cache {

return Cache{
size: opts.size,
entries: map[string]map[string]Entry{},
entries: map[string]*Entry{},
}
}

// Get gets an entry on a service `svcKey` by a give `key`.
func (c *Cache) Get(svcKey, key string) (*Entry, error) {
var value Entry
ok := true

// Get gets a role-bundle by a given `roleID`.
func (c *Cache) Get(k string) *Entry {
c.m.Lock()
defer c.m.Unlock()

if value, ok = c.entries[svcKey][key]; !ok {
return nil, fmt.Errorf("invalid service key: `%v`", key)
if _, ok := c.entries[k]; ok {
if c.expired(c.entries[k]) {
delete(c.entries, k)
return nil
}
return c.entries[k]
}

return &value, nil
return nil
}

// Set sets a key / value. It lets a service add entries on a request basis.
func (c *Cache) Set(svcKey, key string, val interface{}) error {
// Set sets a roleID / role-bundle.
func (c *Cache) Set(k string, val interface{}, expiration time.Time) {
c.m.Lock()
defer c.m.Unlock()

if !c.fits() {
return fmt.Errorf("cache is full")
}

if _, ok := c.entries[svcKey]; !ok {
c.entries[svcKey] = map[string]Entry{}
}

if _, ok := c.entries[svcKey][key]; ok {
return fmt.Errorf("key `%v` already exists", key)
c.evict()
}

c.entries[svcKey][key] = Entry{
V: val,
Valid: true,
c.entries[k] = &Entry{
val,
expiration,
}

return nil
}

// Invalidate invalidates a cache Entry by key.
func (c *Cache) Invalidate(svcKey, key string) error {
r, err := c.Get(svcKey, key)
if err != nil {
return err
}

r.Valid = false
c.entries[svcKey][key] = *r
return nil
}

// Evict frees memory from the cache by removing invalid keys. It is a noop.
func (c *Cache) Evict() {
for _, v := range c.entries {
for k, svcEntry := range v {
if !svcEntry.Valid {
delete(v, k)
}
// evict frees memory from the cache by removing entries that exceeded the cache TTL.
func (c *Cache) evict() {
for i := range c.entries {
if c.expired(c.entries[i]) {
delete(c.entries, i)
}
}
}

// Length returns the amount of entries per service key.
func (c *Cache) Length(k string) int {
return len(c.entries[k])
// expired checks if an entry is expired
func (c *Cache) expired(e *Entry) bool {
return e.expiration.Before(time.Now())
}

// fits returns whether the cache fits more entries.
func (c *Cache) fits() bool {
return c.size >= len(c.entries)
return c.size > len(c.entries)
}
99 changes: 0 additions & 99 deletions proxy/pkg/cache/cache_test.go

This file was deleted.

4 changes: 3 additions & 1 deletion proxy/pkg/command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import (
"github.com/coreos/go-oidc"
"github.com/justinas/alice"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/ocis-pkg/service/grpc"
"github.com/oklog/run"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
acc "github.com/owncloud/ocis/accounts/pkg/proto/v0"
"github.com/owncloud/ocis/ocis-pkg/log"
"github.com/owncloud/ocis/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/proxy/pkg/config"
"github.com/owncloud/ocis/proxy/pkg/cs3"
"github.com/owncloud/ocis/proxy/pkg/flagset"
Expand Down Expand Up @@ -281,6 +281,8 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic
}),
middleware.HTTPClient(oidcHTTPClient),
middleware.OIDCIss(cfg.OIDC.Issuer),
middleware.TokenCacheSize(cfg.OIDC.UserinfoCache.Size),
middleware.TokenCacheTTL(time.Second*time.Duration(cfg.OIDC.UserinfoCache.TTL)),
),
middleware.BasicAuth(
middleware.Logger(l),
Expand Down
11 changes: 9 additions & 2 deletions proxy/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ type Reva struct {
Address string
}

// Cache is a TTL cache configuration.
type Cache struct {
Size int
TTL int
}

// Config combines all available configuration parts.
type Config struct {
File string
Expand All @@ -105,8 +111,9 @@ type Config struct {
// OIDC is the config for the OpenID-Connect middleware. If set the proxy will try to authenticate every request
// with the configured oidc-provider
type OIDC struct {
Issuer string
Insecure bool
Issuer string
Insecure bool
UserinfoCache Cache
}

// PolicySelector is the toplevel-configuration for different selectors
Expand Down
14 changes: 14 additions & 0 deletions proxy/pkg/flagset/flagset.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
EnvVars: []string{"PROXY_OIDC_INSECURE"},
Destination: &cfg.OIDC.Insecure,
},
&cli.IntFlag{
Name: "oidc-userinfo-cache-tll",
Value: 10,
Usage: "Fallback TTL in seconds for caching userinfo, when no token lifetime can be identified",
EnvVars: []string{"PROXY_OIDC_USERINFO_CACHE_TTL"},
Destination: &cfg.OIDC.UserinfoCache.TTL,
},
&cli.IntFlag{
Name: "oidc-userinfo-cache-size",
Value: 1024,
Usage: "Max entries for caching userinfo",
EnvVars: []string{"PROXY_OIDC_USERINFO_CACHE_SIZE"},
Destination: &cfg.OIDC.UserinfoCache.Size,
},

&cli.BoolFlag{
Name: "autoprovision-accounts",
Expand Down
11 changes: 4 additions & 7 deletions proxy/pkg/middleware/account_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,31 @@ package middleware
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/micro/go-micro/v2/client"
"github.com/owncloud/ocis/accounts/pkg/proto/v0"
"github.com/owncloud/ocis/ocis-pkg/log"
"github.com/owncloud/ocis/ocis-pkg/oidc"
"github.com/owncloud/ocis/proxy/pkg/config"
settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
"net/http"
"net/http/httptest"
"testing"
)

func TestGetAccountSuccess(t *testing.T) {
svcCache.Invalidate(AccountsKey, "success")
if _, status := getAccount(log.NewLogger(), mockAccountResolverMiddlewareAccSvc(false, true), "mail eq 'success'"); status != 0 {
t.Errorf("expected an account")
}
}

func TestGetAccountInternalError(t *testing.T) {
svcCache.Invalidate(AccountsKey, "failure")
if _, status := getAccount(log.NewLogger(), mockAccountResolverMiddlewareAccSvc(true, false), "mail eq 'failure'"); status != http.StatusInternalServerError {
t.Errorf("expected an internal server error")
}
}

func TestAccountResolverMiddleware(t *testing.T) {
svcCache.Invalidate(AccountsKey, "success")
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
m := AccountResolver(
Logger(log.NewLogger()),
Expand All @@ -50,7 +48,6 @@ func TestAccountResolverMiddleware(t *testing.T) {
}

func TestAccountResolverMiddlewareWithDisabledAccount(t *testing.T) {
svcCache.Invalidate(AccountsKey, "failure")
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
m := AccountResolver(
Logger(log.NewLogger()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ var (
ErrUnauthorized = errors.New("unauthorized")

// ErrInternal is returned if something went wrong
ErrInternal = errors.New("internal error")
ErrInternal = errors.New("internal error")
)
17 changes: 0 additions & 17 deletions proxy/pkg/middleware/middleware_test.go

This file was deleted.

Loading