Skip to content

Commit

Permalink
Implement introspection endpoint discovery cache
Browse files Browse the repository at this point in the history
  • Loading branch information
vasayxtx committed Nov 13, 2024
1 parent 29ba882 commit 42727c0
Show file tree
Hide file tree
Showing 13 changed files with 409 additions and 188 deletions.
81 changes: 56 additions & 25 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,32 @@ import (
)

const (
cfgKeyHTTPClientRequestTimeout = "auth.httpClient.requestTimeout"
cfgKeyGRPCClientRequestTimeout = "auth.grpcClient.requestTimeout"
cfgKeyJWTTrustedIssuers = "auth.jwt.trustedIssuers"
cfgKeyJWTTrustedIssuerURLs = "auth.jwt.trustedIssuerUrls"
cfgKeyJWTRequireAudience = "auth.jwt.requireAudience"
cfgKeyJWTExceptedAudience = "auth.jwt.expectedAudience"
cfgKeyJWTClaimsCacheEnabled = "auth.jwt.claimsCache.enabled"
cfgKeyJWTClaimsCacheMaxEntries = "auth.jwt.claimsCache.maxEntries"
cfgKeyJWKSCacheUpdateMinInterval = "auth.jwks.cache.updateMinInterval"
cfgKeyIntrospectionEnabled = "auth.introspection.enabled"
cfgKeyIntrospectionEndpoint = "auth.introspection.endpoint"
cfgKeyIntrospectionGRPCEndpoint = "auth.introspection.grpc.endpoint"
cfgKeyIntrospectionGRPCTLSEnabled = "auth.introspection.grpc.tls.enabled"
cfgKeyIntrospectionGRPCTLSCACert = "auth.introspection.grpc.tls.caCert"
cfgKeyIntrospectionGRPCTLSClientCert = "auth.introspection.grpc.tls.clientCert"
cfgKeyIntrospectionGRPCTLSClientKey = "auth.introspection.grpc.tls.clientKey"
cfgKeyIntrospectionAccessTokenScope = "auth.introspection.accessTokenScope" // nolint:gosec // false positive
cfgKeyIntrospectionClaimsCacheEnabled = "auth.introspection.claimsCache.enabled"
cfgKeyIntrospectionClaimsCacheMaxEntries = "auth.introspection.claimsCache.maxEntries"
cfgKeyIntrospectionClaimsCacheTTL = "auth.introspection.claimsCache.ttl"
cfgKeyIntrospectionNegativeCacheEnabled = "auth.introspection.negativeCache.enabled"
cfgKeyIntrospectionNegativeCacheMaxEntries = "auth.introspection.negativeCache.maxEntries"
cfgKeyIntrospectionNegativeCacheTTL = "auth.introspection.negativeCache.ttl"
cfgKeyHTTPClientRequestTimeout = "auth.httpClient.requestTimeout"
cfgKeyGRPCClientRequestTimeout = "auth.grpcClient.requestTimeout"
cfgKeyJWTTrustedIssuers = "auth.jwt.trustedIssuers"
cfgKeyJWTTrustedIssuerURLs = "auth.jwt.trustedIssuerUrls"
cfgKeyJWTRequireAudience = "auth.jwt.requireAudience"
cfgKeyJWTExceptedAudience = "auth.jwt.expectedAudience"
cfgKeyJWTClaimsCacheEnabled = "auth.jwt.claimsCache.enabled"
cfgKeyJWTClaimsCacheMaxEntries = "auth.jwt.claimsCache.maxEntries"
cfgKeyJWKSCacheUpdateMinInterval = "auth.jwks.cache.updateMinInterval"
cfgKeyIntrospectionEnabled = "auth.introspection.enabled"
cfgKeyIntrospectionEndpoint = "auth.introspection.endpoint"
cfgKeyIntrospectionGRPCEndpoint = "auth.introspection.grpc.endpoint"
cfgKeyIntrospectionGRPCTLSEnabled = "auth.introspection.grpc.tls.enabled"
cfgKeyIntrospectionGRPCTLSCACert = "auth.introspection.grpc.tls.caCert"
cfgKeyIntrospectionGRPCTLSClientCert = "auth.introspection.grpc.tls.clientCert"
cfgKeyIntrospectionGRPCTLSClientKey = "auth.introspection.grpc.tls.clientKey"
cfgKeyIntrospectionAccessTokenScope = "auth.introspection.accessTokenScope" // nolint:gosec // false positive
cfgKeyIntrospectionClaimsCacheEnabled = "auth.introspection.claimsCache.enabled"
cfgKeyIntrospectionClaimsCacheMaxEntries = "auth.introspection.claimsCache.maxEntries"
cfgKeyIntrospectionClaimsCacheTTL = "auth.introspection.claimsCache.ttl"
cfgKeyIntrospectionNegativeCacheEnabled = "auth.introspection.negativeCache.enabled"
cfgKeyIntrospectionNegativeCacheMaxEntries = "auth.introspection.negativeCache.maxEntries"
cfgKeyIntrospectionNegativeCacheTTL = "auth.introspection.negativeCache.ttl"
cfgKeyIntrospectionEndpointDiscoveryCacheEnabled = "auth.introspection.endpointDiscoveryCache.enabled"
cfgKeyIntrospectionEndpointDiscoveryCacheMaxEntries = "auth.introspection.endpointDiscoveryCache.maxEntries"
cfgKeyIntrospectionEndpointDiscoveryCacheTTL = "auth.introspection.endpointDiscoveryCache.ttl"
)

// JWTConfig is configuration of how JWT will be verified.
Expand All @@ -68,8 +71,9 @@ type IntrospectionConfig struct {
Endpoint string
AccessTokenScope []string

ClaimsCache IntrospectionCacheConfig
NegativeCache IntrospectionCacheConfig
ClaimsCache IntrospectionCacheConfig
NegativeCache IntrospectionCacheConfig
EndpointDiscoveryCache IntrospectionCacheConfig

GRPC IntrospectionGRPCConfig
}
Expand Down Expand Up @@ -145,12 +149,19 @@ func (c *Config) KeyPrefix() string {
func (c *Config) SetProviderDefaults(dp config.DataProvider) {
dp.SetDefault(cfgKeyHTTPClientRequestTimeout, idputil.DefaultHTTPRequestTimeout.String())
dp.SetDefault(cfgKeyGRPCClientRequestTimeout, idptoken.DefaultGRPCClientRequestTimeout.String())

dp.SetDefault(cfgKeyJWTClaimsCacheMaxEntries, jwt.DefaultClaimsCacheMaxEntries)
dp.SetDefault(cfgKeyJWKSCacheUpdateMinInterval, jwks.DefaultCacheUpdateMinInterval.String())

dp.SetDefault(cfgKeyIntrospectionClaimsCacheMaxEntries, idptoken.DefaultIntrospectionClaimsCacheMaxEntries)
dp.SetDefault(cfgKeyIntrospectionClaimsCacheTTL, idptoken.DefaultIntrospectionClaimsCacheTTL.String())

dp.SetDefault(cfgKeyIntrospectionNegativeCacheMaxEntries, idptoken.DefaultIntrospectionNegativeCacheMaxEntries)
dp.SetDefault(cfgKeyIntrospectionNegativeCacheTTL, idptoken.DefaultIntrospectionNegativeCacheTTL.String())

dp.SetDefault(cfgKeyIntrospectionEndpointDiscoveryCacheEnabled, true)
dp.SetDefault(cfgKeyIntrospectionEndpointDiscoveryCacheMaxEntries, idptoken.DefaultIntrospectionEndpointDiscoveryCacheMaxEntries)
dp.SetDefault(cfgKeyIntrospectionEndpointDiscoveryCacheTTL, idptoken.DefaultIntrospectionEndpointDiscoveryCacheTTL.String())
}

// Set sets auth configuration values from config.DataProvider.
Expand Down Expand Up @@ -279,5 +290,25 @@ func (c *Config) setIntrospectionConfig(dp config.DataProvider) error {
return err
}

// OpenID configuration cache
if c.Introspection.EndpointDiscoveryCache.Enabled, err = dp.GetBool(
cfgKeyIntrospectionEndpointDiscoveryCacheEnabled,
); err != nil {
return err
}
if c.Introspection.EndpointDiscoveryCache.MaxEntries, err = dp.GetInt(
cfgKeyIntrospectionEndpointDiscoveryCacheMaxEntries,
); err != nil {
return err
}
if c.Introspection.EndpointDiscoveryCache.MaxEntries < 0 {
return dp.WrapKeyErr(cfgKeyIntrospectionEndpointDiscoveryCacheMaxEntries, fmt.Errorf("max entries should be non-negative"))
}
if c.Introspection.EndpointDiscoveryCache.TTL, err = dp.GetDuration(
cfgKeyIntrospectionEndpointDiscoveryCacheTTL,
); err != nil {
return err
}

return nil
}
23 changes: 16 additions & 7 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,17 @@ auth:
enabled: true
endpoint: https://my-idp.com/introspect
claimsCache:
enabled: true
maxEntries: 42000
ttl: 42s
enabled: true
maxEntries: 42000
ttl: 42s
negativeCache:
enabled: true
maxEntries: 777
ttl: 77s
enabled: true
maxEntries: 777
ttl: 77m
endpointDiscoveryCache:
enabled: true
maxEntries: 73
ttl: 7h
accessTokenScope:
- token_introspector
grpc:
Expand Down Expand Up @@ -98,7 +102,12 @@ auth:
NegativeCache: IntrospectionCacheConfig{
Enabled: true,
MaxEntries: 777,
TTL: time.Second * 77,
TTL: time.Minute * 77,
},
EndpointDiscoveryCache: IntrospectionCacheConfig{
Enabled: true,
MaxEntries: 73,
TTL: time.Hour * 7,
},
AccessTokenScope: []string{"token_introspector"},
GRPC: IntrospectionGRPCConfig{
Expand Down
36 changes: 32 additions & 4 deletions idptest/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,9 @@ func NewHTTPServer(options ...HTTPServerOption) *HTTPServer {
})

s.Router = http.NewServeMux()
s.Router.Handle(s.paths.OpenIDConfiguration, s.OpenIDConfigurationHandler)
s.Router.Handle(s.paths.JWKS, s.KeysHandler)
s.Router.Handle(s.paths.Token, s.TokenHandler)
s.Router.Handle(s.paths.TokenIntrospection, s.TokenIntrospectionHandler)
for path, handler := range s.allHandlers() {
s.Router.Handle(path, handler)
}

// nolint:gosec // This server is used for testing purposes only.
s.Server = &http.Server{Handler: s.Router}
Expand Down Expand Up @@ -256,8 +255,37 @@ func (s *HTTPServer) StartAndWaitForReady(timeout time.Duration) error {
return testutil.WaitListeningServer(s.addr.Load().(string), timeout)
}

// ServedCounts returns the number of requests served by each handler.
func (s *HTTPServer) ServedCounts() map[string]uint64 {
counts := make(map[string]uint64)
for path, handler := range s.allHandlers() {
if counter, ok := handler.(interface{ ServedCount() uint64 }); ok {
counts[path] = counter.ServedCount()
}
}
return counts
}

// ResetServedCounts resets the number of requests served by each handler.
func (s *HTTPServer) ResetServedCounts() {
for _, h := range s.allHandlers() {
if r, ok := h.(interface{ ResetServedCount() }); ok {
r.ResetServedCount()
}
}
}

func (s *HTTPServer) makeJWTParser() *jwt.Parser {
p := jwt.NewParser(jwks.NewClient())
_ = p.AddTrustedIssuerURL(s.URL())
return p
}

func (s *HTTPServer) allHandlers() map[string]http.Handler {
return map[string]http.Handler{
s.paths.JWKS: s.KeysHandler,
s.paths.OpenIDConfiguration: s.OpenIDConfigurationHandler,
s.paths.Token: s.TokenHandler,
s.paths.TokenIntrospection: s.TokenIntrospectionHandler,
}
}
5 changes: 5 additions & 0 deletions idptest/jwks_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ func (h *JWKSHandler) ServedCount() uint64 {
return h.servedCount.Load()
}

// ResetServedCount resets the number of times JWKS handler has been served.
func (h *JWKSHandler) ResetServedCount() {
h.servedCount.Store(0)
}

type PublicJWKSResponse struct {
Keys []PublicJWK `json:"keys"`
}
5 changes: 5 additions & 0 deletions idptest/openid_configuration_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ func (h *OpenIDConfigurationHandler) ServedCount() uint64 {
return h.servedCount.Load()
}

// ResetServedCount resets the number of times the handler has been served.
func (h *OpenIDConfigurationHandler) ResetServedCount() {
h.servedCount.Store(0)
}

// OpenIDConfigurationResponse is a response for .well-known/openid-configuration endpoint.
type OpenIDConfigurationResponse struct {
TokenEndpoint string `json:"token_endpoint"`
Expand Down
10 changes: 10 additions & 0 deletions idptest/token_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ func (h *TokenHandler) ServedCount() uint64 {
return h.servedCount.Load()
}

// ResetServedCount resets the number of times the handler has been served.
func (h *TokenHandler) ResetServedCount() {
h.servedCount.Store(0)
}

// TokenResponse is a response for POST /idp/token endpoint.
type TokenResponse struct {
AccessToken string `json:"access_token"`
Expand Down Expand Up @@ -149,3 +154,8 @@ func (h *TokenIntrospectionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Re
func (h *TokenIntrospectionHandler) ServedCount() uint64 {
return h.servedCount.Load()
}

// ResetServedCount resets the number of times the handler has been served.
func (h *TokenIntrospectionHandler) ResetServedCount() {
h.servedCount.Store(0)
}
Loading

0 comments on commit 42727c0

Please sign in to comment.