fix(openid): bound and validate the provider metadata fetch

The metadata document was fetched with http.Get, which has no deadline,
so a hung connection blocked startup indefinitely. The response status
was never checked either, leaving an error page to surface as a JSON
decoding error.

The fetch now runs with a 10s deadline derived from the context passed
in from main, goes through the shared transport so it is traced and
pooled, and rejects any non-200 response.
This commit is contained in:
Trong Huu Nguyen
2026-07-28 11:43:48 +02:00
parent ea20e96f87
commit 4d1d591448
3 changed files with 27 additions and 5 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ func run() error {
}
func standalone(ctx context.Context, cfg *config.Config, crypt crypto.Crypter) (*handler.Standalone, error) {
openidConfig, err := openidconfig.NewConfig(cfg)
openidConfig, err := openidconfig.NewConfig(ctx, cfg)
if err != nil {
return nil, err
}
+4 -2
View File
@@ -1,6 +1,8 @@
package config
import (
"context"
wonderwallconfig "github.com/nais/wonderwall/pkg/config"
)
@@ -22,13 +24,13 @@ func (c *openidconfig) Provider() Provider {
return c.providerConfig
}
func NewConfig(cfg *wonderwallconfig.Config) (Config, error) {
func NewConfig(ctx context.Context, cfg *wonderwallconfig.Config) (Config, error) {
clientCfg, err := NewClientConfig(cfg)
if err != nil {
return nil, err
}
providerCfg, err := NewProviderConfig(cfg)
providerCfg, err := NewProviderConfig(ctx, cfg)
if err != nil {
return nil, err
}
+22 -2
View File
@@ -1,15 +1,18 @@
package config
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"slices"
"time"
"github.com/lestrrat-go/jwx/v3/jwa"
log "github.com/sirupsen/logrus"
httpinternal "github.com/nais/wonderwall/internal/http"
"github.com/nais/wonderwall/pkg/config"
"github.com/nais/wonderwall/pkg/openid/acr"
)
@@ -83,13 +86,30 @@ func (p *provider) SidClaimRequired() bool {
return p.metadata.FrontchannelLogoutSupported && p.metadata.FrontchannelLogoutSessionSupported
}
func NewProviderConfig(cfg *config.Config) (Provider, error) {
response, err := http.Get(cfg.OpenID.WellKnownURL)
// wellKnownTimeout bounds the fetch of the provider's metadata document at startup.
const wellKnownTimeout = 10 * time.Second
func NewProviderConfig(ctx context.Context, cfg *config.Config) (Provider, error) {
ctx, cancel := context.WithTimeout(ctx, wellKnownTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.OpenID.WellKnownURL, nil)
if err != nil {
return nil, fmt.Errorf("creating request for well known configuration: %w", err)
}
client := &http.Client{Transport: httpinternal.Transport()}
response, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("fetching well known configuration: %w", err)
}
defer func() { _ = response.Body.Close() }()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("fetching well known configuration: %s responded with HTTP %d", cfg.OpenID.WellKnownURL, response.StatusCode)
}
providerCfg := new(ProviderMetadata)
if err := json.NewDecoder(response.Body).Decode(providerCfg); err != nil {
return nil, fmt.Errorf("decoding well known configuration: %w", err)