Skip to content
Closed
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
519 changes: 268 additions & 251 deletions router-tests/authentication_test.go

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions router-tests/header_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,16 @@ func TestHeaderSetWithExpression(t *testing.T) {

t.Cleanup(authServer.Close)

tokenDecoder, err := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})
opts := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), opts)
require.NoError(t, err)

authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down
18 changes: 13 additions & 5 deletions router-tests/modules/set_scopes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,20 @@ func configureAuth(t *testing.T) ([]authentication.Authenticator, *jwks.Server)
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(integration.NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{
{
URL: authServer.JWKSURL(),
RefreshInterval: time.Second * 5,

jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{
{
URL: authServer.JWKSURL(),
RefreshInterval: time.Second * 5,
AllowedAlgorithms: integration.BaseJWKSAlgorithms,
},
},
})
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(integration.NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: jwksName,
TokenDecoder: tokenDecoder,
Expand Down
18 changes: 13 additions & 5 deletions router-tests/ratelimit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,20 @@ func TestRateLimit(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{
{
URL: authServer.JWKSURL(),
RefreshInterval: time.Second * 5,

opts := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{
{
URL: authServer.JWKSURL(),
RefreshInterval: time.Second * 5,
AllowedAlgorithms: BaseJWKSAlgorithms,
},
},
})
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), opts)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: "my-jwks-server",
TokenDecoder: tokenDecoder,
Expand Down
34 changes: 27 additions & 7 deletions router-tests/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,22 @@ import (
)

const (
JwksName = "my-jwks-server"
JWKSName = "my-jwks-server"
)

var BaseJWKSAlgorithms = []string{
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"PS256",
"PS384",
"PS512",
"EdDSA",
}

// NewContextWithCancel creates a new context with a cancel function that is called when the test is done.
func NewContextWithCancel(t *testing.T) context.Context {
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -46,22 +59,29 @@ func ConfigureAuth(t *testing.T) ([]authentication.Authenticator, *jwks.Server)
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
authenticators := ConfigureAuthWithJwksConfig(t, []authentication.JWKSConfig{

authenticators := ConfigureAuthWithJWKSConfig(t, []authentication.JWKSConfig{
{
URL: authServer.JWKSURL(),
RefreshInterval: time.Second * 5,
URL: authServer.JWKSURL(),
RefreshInterval: time.Second * 5,
AllowedAlgorithms: BaseJWKSAlgorithms,
},
})

return authenticators, authServer
}

func ConfigureAuthWithJwksConfig(t *testing.T, jwksConfig []authentication.JWKSConfig) []authentication.Authenticator {
tokenDecoder, err := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), jwksConfig)
func ConfigureAuthWithJWKSConfig(t *testing.T, jwksConfig []authentication.JWKSConfig) []authentication.Authenticator {
opts := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: jwksConfig,
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), opts)
require.NoError(t, err)

authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down
79 changes: 66 additions & 13 deletions router-tests/websocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,16 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})

jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down Expand Up @@ -175,9 +182,16 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})

jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down Expand Up @@ -224,9 +238,16 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})

jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down Expand Up @@ -283,9 +304,16 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})

jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down Expand Up @@ -341,7 +369,14 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})

jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.WebsocketInitialPayloadAuthenticatorOptions{
TokenDecoder: tokenDecoder,
Key: "Authorization",
Expand Down Expand Up @@ -403,7 +438,13 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})
jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.WebsocketInitialPayloadAuthenticatorOptions{
TokenDecoder: tokenDecoder,
Key: "Authorization",
Expand Down Expand Up @@ -452,7 +493,13 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})
jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.WebsocketInitialPayloadAuthenticatorOptions{
TokenDecoder: tokenDecoder,
Key: "Authorization",
Expand Down Expand Up @@ -853,9 +900,15 @@ func TestWebSockets(t *testing.T) {
authServer, err := jwks.NewServer(t)
require.NoError(t, err)
t.Cleanup(authServer.Close)
tokenDecoder, _ := authentication.NewJwksTokenDecoder(NewContextWithCancel(t), zap.NewNop(), []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)})
jwksConfig := authentication.JWKSTokenDecoderConfig{
Logger: zap.NewNop(),
JWKSConfigs: []authentication.JWKSConfig{toJWKSConfig(authServer.JWKSURL(), time.Second*5)},
AllowInsecureJWKSURLs: true,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(NewContextWithCancel(t), jwksConfig)
require.NoError(t, err)
authOptions := authentication.HttpHeaderAuthenticatorOptions{
Name: JwksName,
Name: JWKSName,
TokenDecoder: tokenDecoder,
}
authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions)
Expand Down
12 changes: 8 additions & 4 deletions router/core/supervisor_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package core
import (
"context"
"fmt"
"net/http"
"os"

"github.com/KimMachineGun/automemlimit/memlimit"
"github.com/dustin/go-humanize"
"github.com/wundergraph/cosmo/router/pkg/authentication"
Expand All @@ -15,6 +12,8 @@ import (
"github.com/wundergraph/cosmo/router/pkg/logging"
"go.uber.org/automaxprocs/maxprocs"
"go.uber.org/zap"
"net/http"
"os"
)

// newRouter creates a new router instance.
Expand Down Expand Up @@ -264,7 +263,12 @@ func setupAuthenticators(ctx context.Context, logger *zap.Logger, cfg *config.Co
})
}

tokenDecoder, err := authentication.NewJwksTokenDecoder(ctx, logger, configs)
decoderConfig := authentication.JWKSTokenDecoderConfig{
Logger: logger,
JWKSConfigs: configs,
AllowInsecureJWKSURLs: jwtConf.AllowInsecureJWKSUrls,
}
tokenDecoder, err := authentication.NewJWKSTokenDecoder(ctx, decoderConfig)
if err != nil {
return nil, err
}
Expand Down
2 changes: 2 additions & 0 deletions router/pkg/authentication/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ func (a *authentication) Scopes() []string {

var errUnacceptableAud = errors.New("audience match not found")

var errNoAlgorithmsSpecified = errors.New("algorithms not specified")

// Authenticate tries to authenticate the given Provider using the given authenticators. If any of
// the authenticators succeeds, the Authentication result is returned with no error. If the Provider
// has no authentication information, the Authentication result is nil with no error. If the authentication
Expand Down
36 changes: 29 additions & 7 deletions router/pkg/authentication/jwks_token_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"time"

"github.com/MicahParks/jwkset"
Expand Down Expand Up @@ -58,18 +59,39 @@ type audKey struct {

type audienceSet map[string]struct{}

func NewJwksTokenDecoder(ctx context.Context, logger *zap.Logger, configs []JWKSConfig) (TokenDecoder, error) {
audiencesMap := make(map[audKey]audienceSet, len(configs))
keyFuncMap := make(map[audKey]keyfunc.Keyfunc, len(configs))
type JWKSTokenDecoderConfig struct {
Logger *zap.Logger
JWKSConfigs []JWKSConfig
AllowInsecureJWKSURLs bool
}

func NewJWKSTokenDecoder(ctx context.Context, config JWKSTokenDecoderConfig) (TokenDecoder, error) {
audiencesMap := make(map[audKey]audienceSet, len(config.JWKSConfigs))
keyFuncMap := make(map[audKey]keyfunc.Keyfunc, len(config.JWKSConfigs))

for _, c := range configs {
for _, c := range config.JWKSConfigs {
if c.URL != "" {
key := audKey{url: c.URL}
if _, ok := audiencesMap[key]; ok {
return nil, fmt.Errorf("duplicate JWK URL found: %s", c.URL)
}

l := logger.With(zap.String("url", c.URL))
if !config.AllowInsecureJWKSURLs {
uri, err := url.ParseRequestURI(c.URL)
if err != nil {
return nil, fmt.Errorf("failed to parse given URL %q: %w", c.URL, err)
}
if uri.Scheme != "https" {
return nil, fmt.Errorf("insecure JWK URL %q is not allowed", c.URL)
}
}

l := config.Logger.With(zap.String("url", c.URL))

newValidationStore, err := NewValidationStore(config.Logger, nil, c.AllowedAlgorithms)
if err != nil {
return nil, fmt.Errorf("failed to create validation store: %w", err)
}

jwksetHTTPStorageOptions := jwkset.HTTPClientStorageOptions{
Client: newOIDCDiscoveryClient(httpclient.NewRetryableHTTPClient(l)),
Expand All @@ -81,7 +103,7 @@ func NewJwksTokenDecoder(ctx context.Context, logger *zap.Logger, configs []JWKS
l.Error("Failed to refresh HTTP JWK Set from remote HTTP resource.", zap.Error(err))
},
RefreshInterval: c.RefreshInterval,
Storage: NewValidationStore(logger, nil, c.AllowedAlgorithms),
Storage: newValidationStore,
}

store, err := jwkset.NewStorageFromHTTP(c.URL, jwksetHTTPStorageOptions)
Expand Down Expand Up @@ -117,7 +139,7 @@ func NewJwksTokenDecoder(ctx context.Context, logger *zap.Logger, configs []JWKS
Private: true,
}
if len(c.Secret) < 32 {
logger.Warn("Using a short secret for JWKs may lead to weak security. Consider using a longer secret.")
config.Logger.Warn("Using a short secret for JWKs may lead to weak security. Consider using a longer secret.")
}

alg := jwkset.ALG(c.Algorithm)
Expand Down
Loading
Loading