mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
Default embedded IdP to a minimal OAuth2 grant allowlist
This commit is contained in:
@@ -79,7 +79,7 @@ type EmbeddedIdPConfig struct {
|
||||
DashboardPostLogoutRedirectURIs []string
|
||||
// StaticConnectors are additional connectors to seed during initialization
|
||||
StaticConnectors []dex.Connector
|
||||
// GrantTypes restricts allowed OAuth2 grants; empty means all (Dex default). Omit the
|
||||
// GrantTypes restricts allowed OAuth2 grants; empty means dex.DefaultGrantTypes. Omit the
|
||||
// device_code grant to disable the device flow; keep authorization_code and refresh_token.
|
||||
GrantTypes []string
|
||||
}
|
||||
@@ -169,6 +169,13 @@ func (c *EmbeddedIdPConfig) ToYAMLConfig() (*dex.YAMLConfig, error) {
|
||||
redirectURIs = append(redirectURIs, cliRedirectURIs...)
|
||||
redirectURIs = append(redirectURIs, dashboardRedirectURIs...)
|
||||
|
||||
// An empty grant list makes Dex enable every supported grant, so fall back to
|
||||
// the minimal set instead. Operators can still opt in to more by setting it.
|
||||
grantTypes := c.GrantTypes
|
||||
if len(grantTypes) == 0 {
|
||||
grantTypes = dex.DefaultGrantTypes
|
||||
}
|
||||
|
||||
cfg := &dex.YAMLConfig{
|
||||
Issuer: c.Issuer,
|
||||
Storage: dex.Storage{
|
||||
@@ -181,7 +188,7 @@ func (c *EmbeddedIdPConfig) ToYAMLConfig() (*dex.YAMLConfig, error) {
|
||||
},
|
||||
OAuth2: dex.OAuth2{
|
||||
SkipApprovalScreen: true,
|
||||
GrantTypes: c.GrantTypes,
|
||||
GrantTypes: grantTypes,
|
||||
},
|
||||
Frontend: dex.Frontend{
|
||||
Issuer: "NetBird",
|
||||
|
||||
@@ -3,8 +3,13 @@ package idp
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -696,3 +701,95 @@ func TestEmbeddedIdPManager_LocalAuthDisabled(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "local user creation is disabled")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmbeddedIdPConfig_ToYAMLConfig_GrantTypes(t *testing.T) {
|
||||
newConfig := func(grantTypes []string) *EmbeddedIdPConfig {
|
||||
return &EmbeddedIdPConfig{
|
||||
Enabled: true,
|
||||
Issuer: "https://example.com/oauth2",
|
||||
GrantTypes: grantTypes,
|
||||
Storage: EmbeddedStorageConfig{
|
||||
Type: "sqlite3",
|
||||
Config: EmbeddedStorageTypeConfig{
|
||||
File: filepath.Join(t.TempDir(), "dex.db"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("defaults to the minimal allowlist without token exchange", func(t *testing.T) {
|
||||
yamlConfig, err := newConfig(nil).ToYAMLConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []string{
|
||||
"authorization_code",
|
||||
"refresh_token",
|
||||
"urn:ietf:params:oauth:grant-type:device_code",
|
||||
}, yamlConfig.OAuth2.GrantTypes)
|
||||
assert.NotContains(t, yamlConfig.OAuth2.GrantTypes, "urn:ietf:params:oauth:grant-type:token-exchange")
|
||||
})
|
||||
|
||||
t.Run("empty slice also falls back to the default", func(t *testing.T) {
|
||||
yamlConfig, err := newConfig([]string{}).ToYAMLConfig()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dex.DefaultGrantTypes, yamlConfig.OAuth2.GrantTypes)
|
||||
})
|
||||
|
||||
t.Run("explicit operator allowlist is preserved", func(t *testing.T) {
|
||||
grantTypes := []string{"authorization_code", "refresh_token"}
|
||||
yamlConfig, err := newConfig(grantTypes).ToYAMLConfig()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, grantTypes, yamlConfig.OAuth2.GrantTypes)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmbeddedIdPManager_TokenExchangeGrantDisabledByDefault(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
manager, err := NewEmbeddedIdPManager(ctx, &EmbeddedIdPConfig{
|
||||
Enabled: true,
|
||||
Issuer: "http://localhost:5556/oauth2",
|
||||
Storage: EmbeddedStorageConfig{
|
||||
Type: "sqlite3",
|
||||
Config: EmbeddedStorageTypeConfig{
|
||||
File: filepath.Join(t.TempDir(), "dex.db"),
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = manager.Stop(ctx) }()
|
||||
|
||||
t.Run("token endpoint rejects the token-exchange grant", func(t *testing.T) {
|
||||
form := url.Values{
|
||||
"grant_type": {"urn:ietf:params:oauth:grant-type:token-exchange"},
|
||||
"client_id": {StaticClientDashboard},
|
||||
"connector_id": {"external-oidc"},
|
||||
"scope": {"openid profile email"},
|
||||
"subject_token": {"not-a-real-token"},
|
||||
"subject_token_type": {"urn:ietf:params:oauth:token-type:id_token"},
|
||||
"requested_token_type": {"urn:ietf:params:oauth:token-type:id_token"},
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/oauth2/token", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rec := httptest.NewRecorder()
|
||||
manager.Handler().ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "unsupported_grant_type")
|
||||
})
|
||||
|
||||
t.Run("discovery does not advertise the token-exchange grant", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/oauth2/.well-known/openid-configuration", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
manager.Handler().ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
var discovery struct {
|
||||
GrantTypes []string `json:"grant_types_supported"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &discovery))
|
||||
|
||||
assert.NotContains(t, discovery.GrantTypes, "urn:ietf:params:oauth:grant-type:token-exchange")
|
||||
assert.ElementsMatch(t, dex.DefaultGrantTypes, discovery.GrantTypes)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user