Skip to content

Commit

Permalink
feat: add cache to hydrator (#418)
Browse files Browse the repository at this point in the history
This patch introduces new configuration parameters that allow the hydrator mutator to cache requests.

Closes #417

Co-authored-by: hackerman <[email protected]>
  • Loading branch information
pike1212 and aeneasr authored May 1, 2020
1 parent fad886c commit 1ae6e7a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 2 deletions.
15 changes: 15 additions & 0 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,21 @@
"$ref": "#/definitions/retry"
}
}
},
"cache": {
"additionalProperties": false,
"required": [
"ttl"
],
"type": "object",
"properties": {
"ttl": {
"type": "string",
"pattern": "^[0-9]+(ns|us|ms|s|m|h)$",
"title": "Cache Time to Live",
"description": "How long to cache hydrate calls"
}
}
}
},
"required": [
Expand Down
5 changes: 5 additions & 0 deletions docs/docs/pipeline/mutator.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ in the `header` field will be added to the final request headers.
- `api.url` (string - required) - The API URL.
- `api.auth.basic.*` (optional) - Enables HTTP Basic Authorization.
- `api.auth.retry.*` (optional) - Configures the retry logic.
- `cache.ttl` (optional) - Configures how long to cache hydrate requests

```yaml
# Global configuration file oathkeeper.yml
Expand All @@ -508,6 +509,8 @@ mutators:
retry:
give_up_after: 2s
max_delay: 100ms
cache:
ttl: 60s
```

```yaml
Expand All @@ -527,6 +530,8 @@ mutators:
retry:
give_up_after: 2s
max_delay: 100ms
cache:
ttl: 60s
```

### Access Rule Example
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/ory/oathkeeper

require (
github.com/Azure/go-autorest/logger v0.1.0 // indirect
github.com/Masterminds/goutils v1.1.0 // indirect
github.com/Masterminds/sprig v2.20.0+incompatible
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v14.0.1+incompatible h1:YhojO9jolWIvvTW7ORhz2ZSNF6Q1TbLqUunKd3jrtyw=
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
Expand Down Expand Up @@ -1200,6 +1203,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI=
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
Expand Down
71 changes: 69 additions & 2 deletions pipeline/mutate/mutator_hydrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ package mutate
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"

"github.com/dgraph-io/ristretto"

"github.com/ory/oathkeeper/pipeline/authn"
"github.com/ory/oathkeeper/x"

Expand Down Expand Up @@ -55,6 +58,9 @@ type MutatorHydrator struct {
c configuration.Provider
client *http.Client
d mutatorHydratorDependencies

hydrateCache *ristretto.Cache
cacheTTL *time.Duration
}

type BasicAuth struct {
Expand All @@ -77,22 +83,67 @@ type externalAPIConfig struct {
Retry *retryConfig `json:"retry"`
}

type cacheConfig struct {
TTL string `json:"ttl"`
}

type MutatorHydratorConfig struct {
Api externalAPIConfig `json:"api"`
Api externalAPIConfig `json:"api"`
Cache cacheConfig `json:"cache"`
}

type mutatorHydratorDependencies interface {
x.RegistryLogger
}

func NewMutatorHydrator(c configuration.Provider, d mutatorHydratorDependencies) *MutatorHydrator {
return &MutatorHydrator{c: c, d: d, client: httpx.NewResilientClientLatencyToleranceSmall(nil)}
cache, _ := ristretto.NewCache(&ristretto.Config{
// This will hold about 1000 unique mutation responses.
NumCounters: 10000,
// Allocate a max of 32MB
MaxCost: 1 << 25,
// This is a best-practice value.
BufferItems: 64,
})
return &MutatorHydrator{c: c, d: d, client: httpx.NewResilientClientLatencyToleranceSmall(nil), hydrateCache: cache}
}

func (a *MutatorHydrator) GetID() string {
return "hydrator"
}

func (a *MutatorHydrator) cacheKey(config *MutatorHydratorConfig, session *authn.AuthenticationSession) string {
return fmt.Sprintf("%s|%s", config.Api.URL, session.Subject)
}

func (a *MutatorHydrator) hydrateFromCache(config *MutatorHydratorConfig, session *authn.AuthenticationSession) (*authn.AuthenticationSession, bool) {
if a.cacheTTL == nil {
return nil, false
}

key := a.cacheKey(config, session)

item, found := a.hydrateCache.Get(key)
if !found {
return nil, false
}

container := item.(*authn.AuthenticationSession)
return container, true
}

func (a *MutatorHydrator) hydrateToCache(config *MutatorHydratorConfig, session *authn.AuthenticationSession) {
if a.cacheTTL == nil {
return
}

key := a.cacheKey(config, session)
cached := a.hydrateCache.SetWithTTL(key, session, 0, *a.cacheTTL)
if !cached {
a.d.Logger().Warn("Item not added to cache")
}
}

func (a *MutatorHydrator) Mutate(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error {
cfg, err := a.Config(config)
if err != nil {
Expand All @@ -104,6 +155,11 @@ func (a *MutatorHydrator) Mutate(r *http.Request, session *authn.AuthenticationS
return errors.WithStack(err)
}

if cacheSession, ok := a.hydrateFromCache(cfg, session); ok {
*session = *cacheSession
return nil
}

if cfg.Api.URL == "" {
return errors.New(ErrMissingAPIURL)
} else if _, err := url.ParseRequestURI(cfg.Api.URL); err != nil {
Expand Down Expand Up @@ -174,6 +230,8 @@ func (a *MutatorHydrator) Mutate(r *http.Request, session *authn.AuthenticationS
}
*session = sessionFromUpstream

a.hydrateToCache(cfg, session)

return nil
}

Expand All @@ -192,5 +250,14 @@ func (a *MutatorHydrator) Config(config json.RawMessage) (*MutatorHydratorConfig
return nil, NewErrMutatorMisconfigured(a, err)
}

if c.Cache.TTL != "" {
cacheTTL, err := time.ParseDuration(c.Cache.TTL)
if err != nil {
a.d.Logger().WithError(err).Error("Unable to parse cache ttl in the Hydrator Mutator.")
return nil, NewErrMutatorMisconfigured(a, err)
}
a.cacheTTL = &cacheTTL
}

return &c, nil
}

0 comments on commit 1ae6e7a

Please sign in to comment.