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

Cache well-known responses to avoid making too much calls to the IdP #251

Merged
merged 1 commit into from
Apr 17, 2024
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
2 changes: 2 additions & 0 deletions internal/authz/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1396,13 +1396,15 @@ func TestLoadWellKnownConfigMissingLogoutRedirectURI(t *testing.T) {
t.Cleanup(idpServer.Stop)

cfg := proto.Clone(dynamicOIDCConfig).(*oidcv1.OIDCConfig)
cfg.ConfigurationUri = "http://missing-logout/.well-known/openid-configuration"
require.ErrorIs(t, loadWellKnownConfig(idpServer.newHTTPClient(), cfg), ErrMissingLogoutRedirectURI)
}

func TestLoadWellKnownConfigError(t *testing.T) {
clock := oidc.Clock{}
tlsPool := internal.NewTLSConfigPool(context.Background())
cfg := proto.Clone(dynamicOIDCConfig).(*oidcv1.OIDCConfig)
cfg.ConfigurationUri = "http://stopped-server/.well-known/openid-configuration"
sessions := &mockSessionStoreFactory{store: oidc.NewMemoryStore(&clock, time.Hour, time.Hour)}
_, err := NewOIDCHandler(cfg, tlsPool, oidc.NewJWKSProvider(newConfigFor(basicOIDCConfig), tlsPool),
sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState))
Expand Down
14 changes: 13 additions & 1 deletion internal/oidc/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,20 @@ type WellKnownConfig struct {
TokenRevocationEndpoint string `json:"token_revocation_endpoint"`
}

var (
// wellKnownConfigs is a map of issuer URL to the OIDC well-known configuration
// It is used to cache well-known configurations as they usually don't change. URLs are usually stable, and the only
// things that are subject to change are the signing keys, but those are already watched periodically by the JWKS fetcher.
wellKnownConfigs = make(map[string]WellKnownConfig)
)

// GetWellKnownConfig retrieves the OIDC well-known configuration from the given issuer URL.
func GetWellKnownConfig(client *http.Client, url string) (WellKnownConfig, error) {
cfg, ok := wellKnownConfigs[url]
if ok {
return cfg, nil
}

// Make a GET request to the well-known configuration endpoint
response, err := client.Get(url)
if err != nil {
Expand All @@ -55,10 +67,10 @@ func GetWellKnownConfig(client *http.Client, url string) (WellKnownConfig, error
}

// Decode the JSON response into the OIDCConfig struct
var cfg WellKnownConfig
if err = json.NewDecoder(response.Body).Decode(&cfg); err != nil {
return WellKnownConfig{}, err
}

wellKnownConfigs[url] = cfg
return cfg, nil
}
21 changes: 20 additions & 1 deletion internal/oidc/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestWellKnownConfig(t *testing.T) {
{"ok", "http://example.com/.well-known/openid-configuration", validWellKnownJSON, false, validWellKnown},
{"not-found", "http://example.com/not-found", validWellKnownJSON, true, WellKnownConfig{}},
{"invalid-url", "invalid", validWellKnownJSON, true, WellKnownConfig{}},
{"invalid-json", "http://example.com/.well-known/openid-configuration", invalidWellKnownJSON, true, WellKnownConfig{}},
{"invalid-json", "http://example2.com/.well-known/openid-configuration", invalidWellKnownJSON, true, WellKnownConfig{}},
}

for _, tt := range tests {
Expand All @@ -74,6 +74,25 @@ func TestWellKnownConfig(t *testing.T) {
}
}

func TestWellKnownConfigCache(t *testing.T) {
s := newServer()
s.wellKnownConfig = validWellKnownJSON
s.Start()
c := s.newHTTPClient()

got, err := GetWellKnownConfig(c, "http://example.com/.well-known/openid-configuration")
require.NoError(t, err)
require.Equal(t, validWellKnown, got)

// Stop the server and run the well-known request again.
// It should succeed and return the cached value.
s.Stop()

got, err = GetWellKnownConfig(c, "http://example.com/.well-known/openid-configuration")
require.NoError(t, err)
require.Equal(t, validWellKnown, got)
}

type idpServer struct {
server *http.Server
listener *bufconn.Listener
Expand Down