Skip to content

Commit

Permalink
Keystone authorization cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Maciej Zimnoch committed Nov 9, 2017
1 parent 8201043 commit f7d74c1
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 0 deletions.
14 changes: 14 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ Gohan supports OpenStack Keystone authentication backend.

v2.0 or v3 is supported

- use_auth_cache

enable memory cache which stores keystone authorization responses for configurable time duration.
Please note that any token may be revoked before TTL expiration.
Revoke operation on cache is not supported. Such tokens will be authorized until TTL expires.

- cache_ttl

TTL of each cache entry.
Please note that this TTL must not exceed Keystone token expiration time.

```yaml
keystone:
use_keystone: false
Expand All @@ -248,6 +259,9 @@ Gohan supports OpenStack Keystone authentication backend.
user_name: "admin"
tenant_name: "admin"
password: "gohan"
use_auth_cache: false
cache_ttl: 15m
```

## CORS
Expand Down
4 changes: 4 additions & 0 deletions etc/gohan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ keystone:
user_name: "admin"
tenant_name: "admin"
password: "gohan"
# Whether should use cache for storing and fetching authorizations
use_auth_cache: false
# TTL of cache entry
cache_ttl: 15m

# CORS (Cross-origin resource sharing (CORS)) configuraion for javascript based client
# cors: "*"
Expand Down
60 changes: 60 additions & 0 deletions server/middleware/cached.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package middleware

import (
"time"

"github.com/cloudwan/gohan/schema"
"github.com/patrickmn/go-cache"
"github.com/rackspace/gophercloud"
)

type CachedIdentityService struct {
inner IdentityService
cache *cache.Cache
}

func (c *CachedIdentityService) GetTenantID(tenantName string) (string, error) {
return c.inner.GetTenantID(tenantName)
}

func (c *CachedIdentityService) GetTenantName(tenantID string) (string, error) {
i, ok := c.cache.Get(tenantID)
if ok {
return i.(string), nil
}
name, err := c.inner.GetTenantName(tenantID)
if err != nil {
return "", err
}
c.cache.Set(tenantID, name, cache.DefaultExpiration)
return name, nil
}

func (c *CachedIdentityService) VerifyToken(token string) (schema.Authorization, error) {
i, ok := c.cache.Get(token)
if ok {
return i.(schema.Authorization), nil
}
a, err := c.inner.VerifyToken(token)
if err != nil {
return nil, err
}
c.cache.Set(token, a, cache.DefaultExpiration)
return a, nil
}

func (c *CachedIdentityService) GetServiceAuthorization() (schema.Authorization, error) {
return c.VerifyToken(c.GetClient().TokenID)
}

func (c *CachedIdentityService) GetClient() *gophercloud.ServiceClient {
return c.inner.GetClient()
}

func NewCachedIdentityService(inner IdentityService, ttl time.Duration) IdentityService {
cleanupInterval := 4 * ttl
return &CachedIdentityService{
inner: inner,
cache: cache.New(ttl, cleanupInterval),
}
}
76 changes: 76 additions & 0 deletions server/middleware/cached_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package middleware

import (
"time"

"github.com/cloudwan/gohan/schema"
"github.com/golang/mock/gomock"
"github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/rackspace/gophercloud"
)

var _ = ginkgo.Describe("Cached identity service", func() {

var (
cachedIdentityService IdentityService
mockedIdentityService *MockIdentityService
auth schema.Authorization
serviceClient *gophercloud.ServiceClient
tenantID string
tenantName string
token string
ctrl *gomock.Controller
)

ginkgo.BeforeEach(func() {
ctrl = gomock.NewController(ginkgo.GinkgoT())
mockedIdentityService = NewMockIdentityService(ctrl)
cachedIdentityService = NewCachedIdentityService(mockedIdentityService, time.Second)
serviceClient = &gophercloud.ServiceClient{ProviderClient: &gophercloud.ProviderClient{TokenID: token}}
tenantID = "tenant-id"
tenantName = "tenant-name"
token = "token"
auth = schema.NewAuthorization(tenantID, tenantName, token, []string{}, []*schema.Catalog{})
})

ginkgo.AfterEach(func() {
ctrl.Finish()
})

ginkgo.It("Use inner service if authorization is not cached and save returned one in cache", func() {
mockedIdentityService.EXPECT().VerifyToken(token).Return(auth, nil).Times(1)
rv, err := cachedIdentityService.VerifyToken(token)
Expect(rv).To(Equal(auth))
Expect(err).To(BeNil())
rv, err = cachedIdentityService.VerifyToken(token)
Expect(rv).To(Equal(auth))
Expect(err).To(BeNil())
})

ginkgo.It("Pass GetTenantID to inner service", func() {
mockedIdentityService.EXPECT().GetTenantID(tenantName).Return(tenantID, nil)
rv, err := cachedIdentityService.GetTenantID(tenantName)
Expect(rv).To(Equal(tenantID))
Expect(err).To(BeNil())

})

ginkgo.It("Cache tenant names", func() {
mockedIdentityService.EXPECT().GetTenantName(tenantID).Return(tenantName, nil).Times(1)
rv, err := cachedIdentityService.GetTenantName(tenantID)
Expect(rv).To(Equal(tenantName))
Expect(err).To(BeNil())
rv, err = cachedIdentityService.GetTenantName(tenantID)
Expect(rv).To(Equal(tenantName))
Expect(err).To(BeNil())
})

ginkgo.It("Uses client Token during GetServiceAuthorization", func() {
mockedIdentityService.EXPECT().GetClient().Return(serviceClient)
mockedIdentityService.EXPECT().VerifyToken(token).Return(auth, nil)
rv, err := cachedIdentityService.GetServiceAuthorization()
Expect(rv).To(Equal(auth))
Expect(err).To(BeNil())
})
})
135 changes: 135 additions & 0 deletions server/middleware/identityservicemock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions server/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ func CreateIdentityServiceFromConfig(config *util.Config) (IdentityService, erro

}
log.Info("Keystone backend server configured")
var keystoneIdentity IdentityService
keystoneIdentity, err := cloud.NewKeystoneIdentity(
config.GetString("keystone/auth_url", "http://localhost:35357/v3"),
config.GetString("keystone/user_name", "admin"),
Expand All @@ -136,6 +137,13 @@ func CreateIdentityServiceFromConfig(config *util.Config) (IdentityService, erro
if err != nil {
log.Fatal(fmt.Sprintf("Failed to create keystone identity service, err: %s", err))
}
if config.GetBool("keystone/use_auth_cache", false) {
ttl, err := time.ParseDuration(config.GetString("keystone/cache_ttl", "15m"))
if err != nil {
log.Fatal("Failed to parse keystone cache TTL")
}
keystoneIdentity = NewCachedIdentityService(keystoneIdentity, ttl)
}
return keystoneIdentity, nil
}
return nil, fmt.Errorf("No identity service defined in config")
Expand Down
13 changes: 13 additions & 0 deletions server/middleware/middleware_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package middleware

import (
"testing"

"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)

func TestServer(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Middleware Suite")
}

0 comments on commit f7d74c1

Please sign in to comment.