Compare commits

...

5 Commits

Author SHA1 Message Date
ce7415af20 fix: prevent nil pointer when err session retrieval fails
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
see https://sentry.in.nuonet.fr/share/issue/48b82c13ee3f4721bb6306b533799709/
2024-11-14 10:10:16 +01:00
7cc9de180c feat(authn): add configurable global ttl for session storage 2024-11-14 10:09:33 +01:00
74c2a2c055 fix(authn): correctly handle session-limited cookies
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
See CNOUS/mse#4347
2024-11-08 12:21:23 +01:00
239d4573c3 feat(sentry): ignore 'net/http: abort' errors
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
See:
- https://sentry.in.nuonet.fr/share/issue/972e100ea22d44759c44b6cfad8be7b2/
- https://pkg.go.dev/net/http#:~:text=ErrAbortHandler%20is%20a%20sentinel%20panic%20value%20to%20abort%20a%20handler.%20While%20any%20panic%20from%20ServeHTTP%20aborts%20the%20response%20to%20the%20client%2C%20panicking%20with%20ErrAbortHandler%20also%20suppresses%20logging%20of%20a%20stack%20trace%20to%20the%20server%27s%20error%20log.
2024-11-08 11:19:38 +01:00
cffe3eca1b fix: prevent loss of information when returning errors
Some checks reported warnings
Cadoles/bouncer/pipeline/head This commit was not built
Linked to:
- https://sentry.in.nuonet.fr/share/issue/5fa72de1b01b46bc81601958a2ff5fd2/
- https://sentry.in.nuonet.fr/share/issue/5a225f6400a647c0bbf1f7ea01566e63/
2024-11-08 11:13:39 +01:00
8 changed files with 42 additions and 13 deletions

View File

@ -21,6 +21,9 @@ func NewDefaultLayersConfig() LayersConfig {
Timeout: NewInterpolatedDuration(10 * time.Second), Timeout: NewInterpolatedDuration(10 * time.Second),
}, },
}, },
Sessions: AuthnLayerSessionConfig{
TTL: NewInterpolatedDuration(time.Hour),
},
}, },
} }
} }
@ -34,6 +37,11 @@ type AuthnLayerConfig struct {
Debug InterpolatedBool `yaml:"debug"` Debug InterpolatedBool `yaml:"debug"`
TemplateDir InterpolatedString `yaml:"templateDir"` TemplateDir InterpolatedString `yaml:"templateDir"`
OIDC AuthnOIDCLayerConfig `yaml:"oidc"` OIDC AuthnOIDCLayerConfig `yaml:"oidc"`
Sessions AuthnLayerSessionConfig `yaml:"sessions"`
}
type AuthnLayerSessionConfig struct {
TTL *InterpolatedDuration `yaml:"ttl"`
} }
type AuthnOIDCLayerConfig struct { type AuthnOIDCLayerConfig struct {

View File

@ -32,7 +32,7 @@ func NewDefaultSentryConfig() SentryConfig {
EnableTracing: false, EnableTracing: false,
TracesSampleRate: 0.1, TracesSampleRate: 0.1,
ProfilesSampleRate: 0.1, ProfilesSampleRate: 0.1,
IgnoreErrors: []string{"context canceled"}, IgnoreErrors: []string{"context canceled", "net/http: abort"},
SendDefaultPII: false, SendDefaultPII: false,
ServerName: "", ServerName: "",
Environment: "", Environment: "",

View File

@ -69,7 +69,7 @@ func (c *Client) login(w http.ResponseWriter, r *http.Request, sess *sessions.Se
sess.Values[sessionKeyPostLoginRedirectURL] = postLoginRedirectURL sess.Values[sessionKeyPostLoginRedirectURL] = postLoginRedirectURL
if err := sess.Save(r, w); err != nil { if err := sess.Save(r, w); err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not save session")) director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not save session"))
return return
} }

View File

@ -54,7 +54,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
options, err := fromStoreOptions(layer.Options, q.defaultKeepAlive) options, err := fromStoreOptions(layer.Options, q.defaultKeepAlive)
if err != nil { if err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not parse layer options")) director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not parse layer options"))
return return
} }
@ -89,7 +89,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
rank, err := q.adapter.Touch(ctx, queueName, sessionID) rank, err := q.adapter.Touch(ctx, queueName, sessionID)
if err != nil { if err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not retrieve session rank")) director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not update queue session rank"))
return return
} }
@ -142,7 +142,7 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam
status, err := q.adapter.Status(ctx, queueName) status, err := q.adapter.Status(ctx, queueName)
if err != nil { if err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not retrieve queue status")) director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not retrieve queue status"))
return return
} }
@ -191,7 +191,7 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam
var buf bytes.Buffer var buf bytes.Buffer
if err := q.tmpl.ExecuteTemplate(&buf, "queue", templateData); err != nil { if err := q.tmpl.ExecuteTemplate(&buf, "queue", templateData); err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not render queue page")) director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not render queue page"))
return return
} }

View File

@ -31,7 +31,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
options, err := fromStoreOptions(layer.Options) options, err := fromStoreOptions(layer.Options)
if err != nil { if err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not parse layer options")) director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not parse layer options"))
return return
} }

View File

@ -10,6 +10,7 @@ import (
type Options struct { type Options struct {
Session sessions.Options Session sessions.Options
KeyPrefix string KeyPrefix string
TTL time.Duration
} }
type OptionFunc func(opts *Options) type OptionFunc func(opts *Options)
@ -25,6 +26,7 @@ func NewOptions(funcs ...OptionFunc) *Options {
SameSite: http.SameSiteDefaultMode, SameSite: http.SameSiteDefaultMode,
}, },
KeyPrefix: "session:", KeyPrefix: "session:",
TTL: time.Hour,
} }
for _, fn := range funcs { for _, fn := range funcs {
@ -45,3 +47,9 @@ func WithKeyPrefix(prefix string) OptionFunc {
opts.KeyPrefix = prefix opts.KeyPrefix = prefix
} }
} }
func WithTTL(ttl time.Duration) OptionFunc {
return func(opts *Options) {
opts.TTL = ttl
}
}

View File

@ -31,6 +31,7 @@ type Store struct {
keyPrefix string keyPrefix string
keyGen KeyGenFunc keyGen KeyGenFunc
serializer SessionSerializer serializer SessionSerializer
ttl time.Duration
} }
type KeyGenFunc func() (string, error) type KeyGenFunc func() (string, error)
@ -43,6 +44,7 @@ func NewStore(adapter StoreAdapter, funcs ...OptionFunc) *Store {
keyPrefix: opts.KeyPrefix, keyPrefix: opts.KeyPrefix,
keyGen: generateRandomKey, keyGen: generateRandomKey,
serializer: GobSerializer{}, serializer: GobSerializer{},
ttl: opts.TTL,
} }
return rs return rs
@ -62,20 +64,21 @@ func (s *Store) New(r *http.Request, name string) (*sessions.Session, error) {
if err != nil { if err != nil {
return session, nil return session, nil
} }
session.ID = c.Value session.ID = c.Value
err = s.load(r.Context(), session) err = s.load(r.Context(), session)
if err == nil { if err == nil {
session.IsNew = false session.IsNew = false
} else if !errors.Is(err, ErrNotFound) { } else if !errors.Is(err, ErrNotFound) {
return nil, errors.WithStack(err) return session, errors.WithStack(err)
} }
return session, nil return session, nil
} }
func (s *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { func (s *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
if session.Options.MaxAge <= 0 { if session.Options.MaxAge < 0 {
if err := s.delete(r.Context(), session); err != nil { if err := s.delete(r.Context(), session); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
@ -120,7 +123,12 @@ func (s *Store) save(ctx context.Context, session *sessions.Session) error {
return errors.WithStack(err) return errors.WithStack(err)
} }
if err := s.adapter.Set(ctx, s.keyPrefix+session.ID, b, time.Duration(session.Options.MaxAge)*time.Second); err != nil { ttl := time.Duration(session.Options.MaxAge) * time.Second
if s.ttl < ttl || ttl == 0 {
ttl = s.ttl
}
if err := s.adapter.Set(ctx, s.keyPrefix+session.ID, b, ttl); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }

View File

@ -218,6 +218,11 @@ layers:
authn: authn:
# Répertoire contenant les templates # Répertoire contenant les templates
templateDir: "/etc/bouncer/layers/authn/templates" templateDir: "/etc/bouncer/layers/authn/templates"
# Configuration des sessions
sessions:
# Temps de persistence sans actualisation des sessions dans le store
# (prévalent sur le MaxAge de la session)
ttl: "1h"
# Configuration d'une série de proxy/layers # Configuration d'une série de proxy/layers
# à créer par défaut par le serveur d'administration # à créer par défaut par le serveur d'administration