-
Notifications
You must be signed in to change notification settings - Fork 190
/
authentication.go
352 lines (301 loc) · 12.8 KB
/
authentication.go
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package config
import (
"fmt"
"net/url"
"strings"
"testing"
"time"
"github.com/spf13/viper"
"go.flipt.io/flipt/rpc/flipt/auth"
"google.golang.org/protobuf/types/known/structpb"
)
var (
_ defaulter = (*AuthenticationConfig)(nil)
stringToAuthMethod = map[string]auth.Method{}
)
func init() {
for _, v := range auth.Method_value {
method := auth.Method(v)
if method == auth.Method_METHOD_NONE {
continue
}
stringToAuthMethod[methodName(method)] = method
}
}
func methodName(method auth.Method) string {
return strings.ToLower(strings.TrimPrefix(auth.Method_name[int32(method)], "METHOD_"))
}
// AuthenticationConfig configures Flipts authentication mechanisms
type AuthenticationConfig struct {
// Required designates whether authentication credentials are validated.
// If required == true, then authentication is required for all API endpoints.
// Else, authentication is not required and Flipt's APIs are not secured.
Required bool `json:"required,omitempty" mapstructure:"required"`
Session AuthenticationSession `json:"session,omitempty" mapstructure:"session"`
Methods AuthenticationMethods `json:"methods,omitempty" mapstructure:"methods"`
}
// ShouldRunCleanup returns true if the cleanup background process should be started.
// It returns true given at-least 1 method is enabled and it's associated schedule
// has been configured (non-nil).
func (c AuthenticationConfig) ShouldRunCleanup() (shouldCleanup bool) {
for _, info := range c.Methods.AllMethods() {
shouldCleanup = shouldCleanup || (info.Enabled && info.Cleanup != nil)
}
return
}
func (c *AuthenticationConfig) setDefaults(v *viper.Viper) {
methods := map[string]any{}
// set default for each methods
for _, info := range c.Methods.AllMethods() {
method := map[string]any{"enabled": false}
// if the method has been enabled then set the defaults
// for its cleanup strategy
prefix := fmt.Sprintf("authentication.methods.%s", info.Name())
if v.GetBool(prefix + ".enabled") {
// apply any method specific defaults
info.setDefaults(method)
// set default cleanup
method["cleanup"] = map[string]any{
"interval": time.Hour,
"grace_period": 30 * time.Minute,
}
}
methods[info.Name()] = method
}
v.SetDefault("authentication", map[string]any{
"required": false,
"session": map[string]any{
"token_lifetime": "24h",
"state_lifetime": "10m",
},
"methods": methods,
})
}
func (c *AuthenticationConfig) validate() error {
var sessionEnabled bool
for _, info := range c.Methods.AllMethods() {
sessionEnabled = sessionEnabled || (info.Enabled && info.SessionCompatible)
if info.Cleanup == nil {
continue
}
field := "authentication.method" + info.Name()
if info.Cleanup.Interval <= 0 {
return errFieldWrap(field+".cleanup.interval", errPositiveNonZeroDuration)
}
if info.Cleanup.GracePeriod <= 0 {
return errFieldWrap(field+".cleanup.grace_period", errPositiveNonZeroDuration)
}
}
// ensure that when a session compatible authentication method has been
// enabled that the session cookie domain has been configured with a non
// empty value.
if sessionEnabled {
if c.Session.Domain == "" {
err := errFieldWrap("authentication.session.domain", errValidationRequired)
return fmt.Errorf("when session compatible auth method enabled: %w", err)
}
host, err := getHostname(c.Session.Domain)
if err != nil {
return fmt.Errorf("invalid domain: %w", err)
}
// strip scheme and port from domain
// domain cookies are not allowed to have a scheme or port
// https://github.com/golang/go/issues/28297
c.Session.Domain = host
}
return nil
}
func getHostname(rawurl string) (string, error) {
if !strings.Contains(rawurl, "://") {
rawurl = "http://" + rawurl
}
u, err := url.Parse(rawurl)
if err != nil {
return "", err
}
return strings.Split(u.Host, ":")[0], nil
}
// AuthenticationSession configures the session produced for browsers when
// establishing authentication via HTTP.
type AuthenticationSession struct {
// Domain is the domain on which to register session cookies.
Domain string `json:"domain,omitempty" mapstructure:"domain"`
// Secure sets the secure property (i.e. HTTPS only) on both the state and token cookies.
Secure bool `json:"secure" mapstructure:"secure"`
// TokenLifetime is the duration of the flipt client token generated once
// authentication has been established via a session compatible method.
TokenLifetime time.Duration `json:"tokenLifetime,omitempty" mapstructure:"token_lifetime"`
// StateLifetime is the lifetime duration of the state cookie.
StateLifetime time.Duration `json:"stateLifetime,omitempty" mapstructure:"state_lifetime"`
// CSRF configures CSRF provention mechanisms.
CSRF AuthenticationSessionCSRF `json:"csrf,omitempty" mapstructure:"csrf"`
}
// AuthenticationSessionCSRF configures cross-site request forgery prevention.
type AuthenticationSessionCSRF struct {
// Key is the private key string used to authenticate csrf tokens.
Key string `json:"-" mapstructure:"key"`
}
// AuthenticationMethods is a set of configuration for each authentication
// method available for use within Flipt.
type AuthenticationMethods struct {
Token AuthenticationMethod[AuthenticationMethodTokenConfig] `json:"token,omitempty" mapstructure:"token"`
OIDC AuthenticationMethod[AuthenticationMethodOIDCConfig] `json:"oidc,omitempty" mapstructure:"oidc"`
Kubernetes AuthenticationMethod[AuthenticationMethodKubernetesConfig] `json:"kubernetes,omitempty" mapstructure:"kubernetes"`
}
// AllMethods returns all the AuthenticationMethod instances available.
func (a *AuthenticationMethods) AllMethods() []StaticAuthenticationMethodInfo {
return []StaticAuthenticationMethodInfo{
a.Token.info(),
a.OIDC.info(),
a.Kubernetes.info(),
}
}
// StaticAuthenticationMethodInfo embeds an AuthenticationMethodInfo alongside
// the other properties of an AuthenticationMethod.
type StaticAuthenticationMethodInfo struct {
AuthenticationMethodInfo
Enabled bool
Cleanup *AuthenticationCleanupSchedule
// used for bootstrapping defaults
setDefaults func(map[string]any)
// used for testing purposes to ensure all methods
// are appropriately cleaned up via the background process.
setEnabled func()
setCleanup func(AuthenticationCleanupSchedule)
}
// Enable can only be called in a testing scenario.
// It is used to enable a target method without having a concrete reference.
func (s StaticAuthenticationMethodInfo) Enable(t *testing.T) {
s.setEnabled()
}
// SetCleanup can only be called in a testing scenario.
// It is used to configure cleanup for a target method without having a concrete reference.
func (s StaticAuthenticationMethodInfo) SetCleanup(t *testing.T, c AuthenticationCleanupSchedule) {
s.setCleanup(c)
}
// AuthenticationMethodInfo is a structure which describes properties
// of a particular authentication method.
// i.e. the name and whether or not the method is session compatible.
type AuthenticationMethodInfo struct {
Method auth.Method
SessionCompatible bool
Metadata *structpb.Struct
}
// Name returns the friendly lower-case name for the authentication method.
func (a AuthenticationMethodInfo) Name() string {
return methodName(a.Method)
}
// AuthenticationMethodInfoProvider is a type with a single method Info
// which returns an AuthenticationMethodInfo describing the underlying
// methods properties.
type AuthenticationMethodInfoProvider interface {
setDefaults(map[string]any)
info() AuthenticationMethodInfo
}
// AuthenticationMethod is a container for authentication methods.
// It describes the common properties of all authentication methods.
// Along with leaving a generic slot for the particular method to declare
// its own structural fields. This generic field (Method) must implement
// the AuthenticationMethodInfoProvider to be valid at compile time.
type AuthenticationMethod[C AuthenticationMethodInfoProvider] struct {
Method C `mapstructure:",squash"`
Enabled bool `json:"enabled,omitempty" mapstructure:"enabled"`
Cleanup *AuthenticationCleanupSchedule `json:"cleanup,omitempty" mapstructure:"cleanup"`
}
func (a *AuthenticationMethod[C]) setDefaults(defaults map[string]any) {
a.Method.setDefaults(defaults)
}
func (a *AuthenticationMethod[C]) info() StaticAuthenticationMethodInfo {
return StaticAuthenticationMethodInfo{
AuthenticationMethodInfo: a.Method.info(),
Enabled: a.Enabled,
Cleanup: a.Cleanup,
setDefaults: a.setDefaults,
setEnabled: func() {
a.Enabled = true
},
setCleanup: func(c AuthenticationCleanupSchedule) {
a.Cleanup = &c
},
}
}
// AuthenticationMethodTokenConfig contains fields used to configure the authentication
// method "token".
// This authentication method supports the ability to create static tokens via the
// /auth/v1/method/token prefix of endpoints.
type AuthenticationMethodTokenConfig struct{}
func (a AuthenticationMethodTokenConfig) setDefaults(map[string]any) {}
// info describes properties of the authentication method "token".
func (a AuthenticationMethodTokenConfig) info() AuthenticationMethodInfo {
return AuthenticationMethodInfo{
Method: auth.Method_METHOD_TOKEN,
SessionCompatible: false,
}
}
// AuthenticationMethodOIDCConfig configures the OIDC authentication method.
// This method can be used to establish browser based sessions.
type AuthenticationMethodOIDCConfig struct {
Providers map[string]AuthenticationMethodOIDCProvider `json:"providers,omitempty" mapstructure:"providers"`
}
func (a AuthenticationMethodOIDCConfig) setDefaults(map[string]any) {}
// info describes properties of the authentication method "oidc".
func (a AuthenticationMethodOIDCConfig) info() AuthenticationMethodInfo {
info := AuthenticationMethodInfo{
Method: auth.Method_METHOD_OIDC,
SessionCompatible: true,
}
var (
metadata = make(map[string]any)
providers = make(map[string]any, len(a.Providers))
)
// this ensures we expose the authorize and callback URL endpoint
// to the UI via the /auth/v1/method endpoint
for provider := range a.Providers {
providers[provider] = map[string]any{
"authorize_url": fmt.Sprintf("/auth/v1/method/oidc/%s/authorize", provider),
"callback_url": fmt.Sprintf("/auth/v1/method/oidc/%s/callback", provider),
}
}
metadata["providers"] = providers
info.Metadata, _ = structpb.NewStruct(metadata)
return info
}
// AuthenticationOIDCProvider configures provider credentials
type AuthenticationMethodOIDCProvider struct {
IssuerURL string `json:"issuerURL,omitempty" mapstructure:"issuer_url"`
ClientID string `json:"clientID,omitempty" mapstructure:"client_id"`
ClientSecret string `json:"clientSecret,omitempty" mapstructure:"client_secret"`
RedirectAddress string `json:"redirectAddress,omitempty" mapstructure:"redirect_address"`
Scopes []string `json:"scopes,omitempty" mapstructure:"scopes"`
}
// AuthenticationCleanupSchedule is used to configure a cleanup goroutine.
type AuthenticationCleanupSchedule struct {
Interval time.Duration `json:"interval,omitempty" mapstructure:"interval"`
GracePeriod time.Duration `json:"gracePeriod,omitempty" mapstructure:"grace_period"`
}
// AuthenticationMethodKubernetesConfig contains the fields necessary for the Kubernetes authentication
// method to be performed. This method supports Flipt being deployed in a Kubernetes environment
// and allowing it to exchange client tokens for valid service account tokens presented via this method.
type AuthenticationMethodKubernetesConfig struct {
// IssuerURL is the URL to the local Kubernetes cluster.
// The URL is used to fetch the OIDC configuration and subsequently the public key chain.
IssuerURL string `json:"issuerURL,omitempty" mapstructure:"issuer_url"`
// CAPath is the path on disk to the trusted certificate authority certificate for validating
// HTTPS requests to the issuer.
CAPath string `json:"caPath,omitempty" mapstructure:"ca_path"`
// ServiceAccountTokenPath is the location on disk to the Flipt instances service account token.
// This should be the token issued for the service account associated with Flipt in the environment.
ServiceAccountTokenPath string `json:"serviceAccountTokenPath,omitempty" mapstructure:"service_account_token_path"`
}
func (a AuthenticationMethodKubernetesConfig) setDefaults(defaults map[string]any) {
defaults["issuer_url"] = "https://kubernetes.default.svc"
defaults["ca_path"] = "/var/run/secrets/kubernetes.io/serviceaccount/ca.cert"
defaults["service_account_token_path"] = "/var/run/secrets/kubernetes.io/serviceaccount/token"
}
// info describes properties of the authentication method "kubernetes".
func (a AuthenticationMethodKubernetesConfig) info() AuthenticationMethodInfo {
return AuthenticationMethodInfo{
Method: auth.Method_METHOD_KUBERNETES,
SessionCompatible: false,
}
}