2021-09-08 18:04:56 +00:00
|
|
|
package application
|
|
|
|
|
|
|
|
import (
|
2023-09-26 16:56:37 +00:00
|
|
|
"context"
|
2021-09-08 18:04:56 +00:00
|
|
|
"fmt"
|
2021-12-13 12:33:20 +00:00
|
|
|
"math"
|
2023-08-05 20:09:27 +00:00
|
|
|
"net/http"
|
2022-05-11 08:08:38 +00:00
|
|
|
"net/url"
|
2021-12-12 16:59:31 +00:00
|
|
|
"os"
|
2023-02-02 20:18:59 +00:00
|
|
|
"path"
|
|
|
|
"strings"
|
2021-09-08 18:04:56 +00:00
|
|
|
|
2023-02-02 20:18:59 +00:00
|
|
|
"github.com/gorilla/securecookie"
|
2021-09-08 18:04:56 +00:00
|
|
|
"github.com/gorilla/sessions"
|
2023-09-26 16:56:37 +00:00
|
|
|
"github.com/redis/go-redis/v9"
|
2024-01-02 20:01:53 +00:00
|
|
|
|
2022-03-03 09:40:07 +00:00
|
|
|
"goauthentik.io/api/v3"
|
2021-09-08 18:04:56 +00:00
|
|
|
"goauthentik.io/internal/config"
|
2023-02-12 15:34:57 +00:00
|
|
|
"goauthentik.io/internal/outpost/proxyv2/codecs"
|
2023-02-02 20:18:59 +00:00
|
|
|
"goauthentik.io/internal/outpost/proxyv2/constants"
|
2023-09-26 16:56:37 +00:00
|
|
|
"goauthentik.io/internal/outpost/proxyv2/redisstore"
|
2021-09-08 18:04:56 +00:00
|
|
|
)
|
|
|
|
|
2023-02-02 20:18:59 +00:00
|
|
|
const RedisKeyPrefix = "authentik_proxy_session_"
|
|
|
|
|
2022-05-11 08:08:38 +00:00
|
|
|
func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store {
|
2023-02-12 15:34:57 +00:00
|
|
|
maxAge := 0
|
|
|
|
if p.AccessTokenValidity.IsSet() {
|
|
|
|
t := p.AccessTokenValidity.Get()
|
|
|
|
// Add one to the validity to ensure we don't have a session with indefinite length
|
|
|
|
maxAge = int(*t) + 1
|
|
|
|
}
|
2023-08-26 17:40:48 +00:00
|
|
|
if a.isEmbedded {
|
2023-09-26 16:56:37 +00:00
|
|
|
client := redis.NewClient(&redis.Options{
|
|
|
|
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
|
|
|
|
// Username: config.Get().Redis.Password,
|
|
|
|
Password: config.Get().Redis.Password,
|
|
|
|
DB: config.Get().Redis.DB,
|
|
|
|
})
|
|
|
|
|
|
|
|
// New default RedisStore
|
|
|
|
rs, err := redisstore.NewRedisStore(context.Background(), client)
|
2021-09-08 18:04:56 +00:00
|
|
|
if err != nil {
|
2024-01-02 20:01:53 +00:00
|
|
|
a.log.WithError(err).Panic("failed to connect to redis")
|
2021-09-08 18:04:56 +00:00
|
|
|
}
|
2023-02-12 15:34:57 +00:00
|
|
|
|
2023-09-26 16:56:37 +00:00
|
|
|
rs.KeyPrefix(RedisKeyPrefix)
|
|
|
|
rs.Options(sessions.Options{
|
2023-09-28 19:06:27 +00:00
|
|
|
HttpOnly: true,
|
|
|
|
Secure: strings.ToLower(externalHost.Scheme) == "https",
|
2023-09-26 16:56:37 +00:00
|
|
|
Domain: *p.CookieDomain,
|
|
|
|
SameSite: http.SameSiteLaxMode,
|
2023-09-28 19:06:27 +00:00
|
|
|
MaxAge: maxAge,
|
2023-10-26 22:41:13 +00:00
|
|
|
Path: "/",
|
2023-09-26 16:56:37 +00:00
|
|
|
})
|
|
|
|
|
2022-05-21 11:18:06 +00:00
|
|
|
a.log.Trace("using redis session backend")
|
2023-02-12 15:34:57 +00:00
|
|
|
return rs
|
|
|
|
}
|
|
|
|
dir := os.TempDir()
|
|
|
|
cs := sessions.NewFilesystemStore(dir)
|
|
|
|
cs.Codecs = codecs.CodecsFromPairs(maxAge, []byte(*p.CookieSecret))
|
|
|
|
// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7
|
|
|
|
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
|
|
|
|
// securecookie: the value is too long
|
2023-12-30 15:36:43 +00:00
|
|
|
// when using OpenID Connect, since this can contain a large amount of extra information in the id_token
|
2021-12-13 12:33:20 +00:00
|
|
|
|
2023-02-12 15:34:57 +00:00
|
|
|
// Note, when using the FilesystemStore only the session.ID is written to a browser cookie, so this is explicit for the storage on disk
|
|
|
|
cs.MaxLength(math.MaxInt)
|
2023-08-05 20:09:27 +00:00
|
|
|
cs.Options.HttpOnly = true
|
2023-10-10 10:17:35 +00:00
|
|
|
cs.Options.Secure = strings.ToLower(externalHost.Scheme) == "https"
|
2023-02-12 15:34:57 +00:00
|
|
|
cs.Options.Domain = *p.CookieDomain
|
2023-08-05 20:09:27 +00:00
|
|
|
cs.Options.SameSite = http.SameSiteLaxMode
|
2023-10-10 10:17:35 +00:00
|
|
|
cs.Options.MaxAge = maxAge
|
2023-11-13 14:33:49 +00:00
|
|
|
cs.Options.Path = "/"
|
2023-02-12 15:34:57 +00:00
|
|
|
a.log.WithField("dir", dir).Trace("using filesystem session backend")
|
|
|
|
return cs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Application) SessionName() string {
|
|
|
|
return a.sessionName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Application) getAllCodecs() []securecookie.Codec {
|
|
|
|
apps := a.srv.Apps()
|
|
|
|
cs := []securecookie.Codec{}
|
|
|
|
for _, app := range apps {
|
|
|
|
cs = append(cs, codecs.CodecsFromPairs(0, []byte(*app.proxyConfig.CookieSecret))...)
|
2021-09-08 18:04:56 +00:00
|
|
|
}
|
2023-02-12 15:34:57 +00:00
|
|
|
return cs
|
2021-09-08 18:04:56 +00:00
|
|
|
}
|
2023-02-02 20:18:59 +00:00
|
|
|
|
2023-10-08 23:06:52 +00:00
|
|
|
func (a *Application) Logout(ctx context.Context, filter func(c Claims) bool) error {
|
2023-02-12 15:34:57 +00:00
|
|
|
if _, ok := a.sessions.(*sessions.FilesystemStore); ok {
|
2023-02-02 20:18:59 +00:00
|
|
|
files, err := os.ReadDir(os.TempDir())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, file := range files {
|
|
|
|
s := sessions.Session{}
|
|
|
|
if !strings.HasPrefix(file.Name(), "session_") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fullPath := path.Join(os.TempDir(), file.Name())
|
|
|
|
data, err := os.ReadFile(fullPath)
|
|
|
|
if err != nil {
|
|
|
|
a.log.WithError(err).Warning("failed to read file")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = securecookie.DecodeMulti(
|
2023-02-12 15:34:57 +00:00
|
|
|
a.SessionName(), string(data),
|
|
|
|
&s.Values, a.getAllCodecs()...,
|
2023-02-02 20:18:59 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
a.log.WithError(err).Trace("failed to decode session")
|
|
|
|
continue
|
|
|
|
}
|
2023-05-10 18:58:44 +00:00
|
|
|
rc, ok := s.Values[constants.SessionClaims]
|
|
|
|
if !ok || rc == nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-02-02 20:18:59 +00:00
|
|
|
claims := s.Values[constants.SessionClaims].(Claims)
|
2023-10-08 23:06:52 +00:00
|
|
|
if filter(claims) {
|
2023-02-02 20:18:59 +00:00
|
|
|
a.log.WithField("path", fullPath).Trace("deleting session")
|
|
|
|
err := os.Remove(fullPath)
|
|
|
|
if err != nil {
|
|
|
|
a.log.WithError(err).Warning("failed to delete session")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-09-26 16:56:37 +00:00
|
|
|
if rs, ok := a.sessions.(*redisstore.RedisStore); ok {
|
|
|
|
client := rs.Client()
|
|
|
|
keys, err := client.Keys(ctx, fmt.Sprintf("%s*", RedisKeyPrefix)).Result()
|
2023-02-02 20:18:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-09-26 16:56:37 +00:00
|
|
|
serializer := redisstore.GobSerializer{}
|
2023-02-02 20:18:59 +00:00
|
|
|
for _, key := range keys {
|
2023-09-26 16:56:37 +00:00
|
|
|
v, err := client.Get(ctx, key).Result()
|
2023-02-02 20:18:59 +00:00
|
|
|
if err != nil {
|
|
|
|
a.log.WithError(err).Warning("failed to get value")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
s := sessions.Session{}
|
2023-09-26 16:56:37 +00:00
|
|
|
err = serializer.Deserialize([]byte(v), &s)
|
2023-02-02 20:18:59 +00:00
|
|
|
if err != nil {
|
|
|
|
a.log.WithError(err).Warning("failed to deserialize")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
c := s.Values[constants.SessionClaims]
|
|
|
|
if c == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
claims := c.(Claims)
|
2023-10-08 23:06:52 +00:00
|
|
|
if filter(claims) {
|
2023-02-02 20:18:59 +00:00
|
|
|
a.log.WithField("key", key).Trace("deleting session")
|
2023-09-26 16:56:37 +00:00
|
|
|
_, err := client.Del(ctx, key).Result()
|
2023-02-02 20:18:59 +00:00
|
|
|
if err != nil {
|
|
|
|
a.log.WithError(err).Warning("failed to delete key")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|