-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathconfig.go
More file actions
308 lines (253 loc) · 9.41 KB
/
Copy pathconfig.go
File metadata and controls
308 lines (253 loc) · 9.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
package server
import (
"errors"
"fmt"
"io/fs"
"log/slog"
"net/http"
"net/netip"
"net/url"
"os"
"sort"
"time"
gosundheit "github.com/AppsFlyer/go-sundheit"
"github.com/prometheus/client_golang/prometheus"
"github.com/dexidp/dex/server/authflow"
"github.com/dexidp/dex/server/mfa"
"github.com/dexidp/dex/server/oauth2"
"github.com/dexidp/dex/server/session"
"github.com/dexidp/dex/server/signer"
"github.com/dexidp/dex/server/templates"
"github.com/dexidp/dex/server/tokens"
"github.com/dexidp/dex/storage"
"github.com/dexidp/dex/web"
)
// Config holds the server's configuration options.
//
// Multiple servers using the same storage are expected to be configured identically.
type Config struct {
Issuer string
// The backing persistence layer.
Storage storage.Storage
AllowedGrantTypes []string
// Valid values are "code" to enable the code flow and "token" to enable the implicit
// flow. If no response types are supplied this value defaults to "code".
SupportedResponseTypes []string
// Headers is a map of headers to be added to the all responses.
Headers http.Header
// Header to extract real ip from.
RealIPHeader string
TrustedRealIPCIDRs []netip.Prefix
// List of allowed origins for CORS requests on discovery, token and keys endpoint.
// If none are indicated, CORS requests are disabled. Passing in "*" will allow any
// domain.
AllowedOrigins []string
// List of allowed headers for CORS requests on discovery, token, and keys endpoint.
AllowedHeaders []string
// If enabled, the server won't prompt the user to approve authorization requests.
// Logging in implies approval.
SkipApprovalScreen bool
// If enabled, the connectors selection page will always be shown even if there's only one
AlwaysShowLoginScreen bool
IDTokensValidFor time.Duration // Defaults to 24 hours
AuthRequestsValidFor time.Duration // Defaults to 24 hours
DeviceRequestsValidFor time.Duration // Defaults to 5 minutes
// Refresh token expiration settings
RefreshTokenPolicy *tokens.RefreshStrategy
// If set, the server will use this connector to handle password grants
PasswordConnector string
// PKCE configuration
PKCE authflow.PKCEConfig
// DynamicClientRegistration enables the RFC 7591 client registration
// endpoint. Nil keeps the endpoint disabled and out of discovery.
DynamicClientRegistration *DynamicClientRegistrationConfig
GCFrequency time.Duration // Defaults to 5 minutes
// If specified, the server will use this function for determining time.
Now func() time.Time
Web WebConfig
Logger *slog.Logger
// Signer is used to sign tokens.
Signer signer.Signer
PrometheusRegistry *prometheus.Registry
HealthChecker gosundheit.Health
// If enabled, the server will continue starting even if some connectors fail to initialize.
// This allows the server to operate with a subset of connectors if some are misconfigured.
ContinueOnConnectorFailure bool
// SessionConfig holds session settings. Nil when sessions are disabled.
SessionConfig *session.Config
// MFAProviders maps authenticator IDs to their provider implementations.
MFAProviders map[string]mfa.Provider
// DefaultMFAChain is applied to clients that don't specify their own mfaChain.
DefaultMFAChain []string
}
// DynamicClientRegistrationConfig configures protected RFC 7591 client
// registration. Dex deliberately requires an initial access token when the
// endpoint is enabled; an accidentally open endpoint would allow arbitrary
// callers to create persistent clients.
type DynamicClientRegistrationConfig struct {
InitialAccessToken string
}
// WebConfig holds the server's frontend templates and asset configuration.
type WebConfig struct {
// A file path to static web assets.
//
// It is expected to contain the following directories:
//
// * static - Static static served at "( issuer URL )/static".
// * templates - HTML templates controlled by dex.
// * themes/(theme) - Static static served at "( issuer URL )/theme".
Dir string
// Alternative way to programmatically configure static web assets.
// If Dir is specified, WebFS is ignored.
// It's expected to contain the same files and directories as mentioned above.
//
// Note: this is experimental. Might get removed without notice!
WebFS fs.FS
// Defaults to "( issuer URL )/theme/logo.png"
LogoURL string
// Defaults to "dex"
Issuer string
// Defaults to "light"
Theme string
// Map of extra values passed into the templates
Extra map[string]string
}
func value(val, defaultValue time.Duration) time.Duration {
if val == 0 {
return defaultValue
}
return val
}
// resolvedConfig is Config after defaults are filled in and values validated:
// everything the handlers are wired from, derived once so newServer only has to
// hand the pieces out.
type resolvedConfig struct {
issuerURL oauth2.IssuerURL
now func() time.Time
// responseTypes and grantTypes are what the server advertises and accepts,
// narrowed to the configured subset.
responseTypes map[string]bool
grantTypes []string
authRequestsValidFor time.Duration
deviceRequestsValidFor time.Duration
idTokensValidFor time.Duration
templates *templates.Templates
static http.Handler
theme http.Handler
robots http.HandlerFunc
}
// normalizeConfig validates c and derives everything the server is built from.
// It fills c's own defaults in place (response types, allowed headers, PKCE
// methods), because the handlers read those fields directly.
func normalizeConfig(c *Config) (resolvedConfig, error) {
if c.Storage == nil {
return resolvedConfig{}, errors.New("server: storage cannot be nil")
}
if c.DynamicClientRegistration != nil && c.DynamicClientRegistration.InitialAccessToken == "" {
return resolvedConfig{}, errors.New("server: dynamic client registration requires an initial access token")
}
issuerURL, err := url.Parse(c.Issuer)
if err != nil {
return resolvedConfig{}, fmt.Errorf("server: can't parse issuer URL")
}
if len(c.SupportedResponseTypes) == 0 {
c.SupportedResponseTypes = []string{oauth2.ResponseTypeCode}
}
if len(c.AllowedHeaders) == 0 {
c.AllowedHeaders = []string{"Authorization"}
}
if len(c.PKCE.CodeChallengeMethodsSupported) == 0 {
c.PKCE.CodeChallengeMethodsSupported = []string{oauth2.PKCEMethodS256, oauth2.PKCEMethodPlain}
}
for _, m := range c.PKCE.CodeChallengeMethodsSupported {
if m != oauth2.PKCEMethodS256 && m != oauth2.PKCEMethodPlain {
return resolvedConfig{}, fmt.Errorf("unsupported PKCE challenge method %q", m)
}
}
responseTypes, grantTypes, err := supportedTypes(c)
if err != nil {
return resolvedConfig{}, err
}
static, theme, robots, tmpls, err := templates.LoadWebConfig(webConfig(c))
if err != nil {
return resolvedConfig{}, fmt.Errorf("server: failed to load web static: %v", err)
}
now := c.Now
if now == nil {
now = time.Now
}
return resolvedConfig{
issuerURL: oauth2.IssuerURL{URL: *issuerURL},
now: now,
responseTypes: responseTypes,
grantTypes: grantTypes,
authRequestsValidFor: value(c.AuthRequestsValidFor, 24*time.Hour),
deviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute),
idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour),
templates: tmpls,
static: static,
theme: theme,
robots: robots,
}, nil
}
// supportedTypes resolves the response types the server accepts and the grant
// types it advertises. A response type enabling the implicit flow adds the
// implicit grant; AllowedGrantTypes, when set, narrows the result to it.
func supportedTypes(c *Config) (map[string]bool, []string, error) {
allGrants := map[string]bool{
oauth2.GrantTypeAuthorizationCode: true,
oauth2.GrantTypeRefreshToken: true,
oauth2.GrantTypeDeviceCode: true,
oauth2.GrantTypeTokenExchange: true,
oauth2.GrantTypeClientCredentials: true,
}
responseTypes := make(map[string]bool)
for _, respType := range c.SupportedResponseTypes {
switch respType {
case oauth2.ResponseTypeCode, oauth2.ResponseTypeIDToken, oauth2.ResponseTypeCodeIDToken:
// continue
case oauth2.ResponseTypeToken, oauth2.ResponseTypeCodeToken, oauth2.ResponseTypeIDTokenToken, oauth2.ResponseTypeCodeIDTokenToken:
// response_type=token is an implicit flow, let's add it to the discovery info
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
allGrants[oauth2.GrantTypeImplicit] = true
default:
return nil, nil, fmt.Errorf("unsupported response_type %q", respType)
}
responseTypes[respType] = true
}
if c.PasswordConnector != "" {
allGrants[oauth2.GrantTypePassword] = true
}
var grantTypes []string
if len(c.AllowedGrantTypes) > 0 {
for _, grant := range c.AllowedGrantTypes {
if allGrants[grant] {
grantTypes = append(grantTypes, grant)
}
}
} else {
for grant := range allGrants {
grantTypes = append(grantTypes, grant)
}
}
sort.Strings(grantTypes)
return responseTypes, grantTypes, nil
}
// webConfig resolves where the frontend assets are loaded from: an explicit
// directory, a caller-supplied filesystem, or the assets embedded in dex.
func webConfig(c *Config) templates.Config {
webFS := web.FS()
if c.Web.Dir != "" {
webFS = os.DirFS(c.Web.Dir)
} else if c.Web.WebFS != nil {
webFS = c.Web.WebFS
}
return templates.Config{
WebFS: webFS,
LogoURL: c.Web.LogoURL,
IssuerURL: c.Issuer,
Issuer: c.Web.Issuer,
Theme: c.Web.Theme,
Extra: c.Web.Extra,
}
}