Merge pull request #1102 from openziti/striped_cookies

Striped Cookie Support (#1101)
This commit is contained in:
Michael Quigley
2025-10-16 18:34:48 +00:00
committed by GitHub
12 changed files with 314 additions and 77 deletions
+2
View File
@@ -1,11 +1,13 @@
.$*
.idea
.vscode
.contexts
CLAUDE.md
*.db
/automated-release-build/
etc/dev.yml
etc/dev-*
etc/*.pem
# Dependencies
node_modules/
+2
View File
@@ -2,6 +2,8 @@
## v1.1.9
CHANGE: The `publicProxy` now supports "striped session cookies" to support larger authentication payloads when working with OIDC providers that use larger tokens/payloads. (https://github.com/openziti/zrok/issues/1101)
FIX: Fix for icon/favicon in HTML for the api console. (https://github.com/openziti/zrok/pull/1094)
## v1.1.8
+13 -13
View File
@@ -20,7 +20,7 @@ type Frontend struct {
}
func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved, permission_mode, description, bind_address) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning id")
stmt, err := tx.Unsafe().Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved, permission_mode, description, bind_address) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing frontends insert statement")
}
@@ -32,7 +32,7 @@ func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, erro
}
func (str *Store) CreateGlobalFrontend(f *Frontend, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved, permission_mode, description) values ($1, $2, $3, $4, $5, $6, $7) returning id")
stmt, err := tx.Unsafe().Prepare("insert into frontends (token, z_id, public_name, url_template, reserved, permission_mode, description) values ($1, $2, $3, $4, $5, $6, $7) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing global frontends insert statement")
}
@@ -45,7 +45,7 @@ func (str *Store) CreateGlobalFrontend(f *Frontend, tx *sqlx.Tx) (int, error) {
func (str *Store) GetFrontend(id int, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select * from frontends where id = $1", id).StructScan(i); err != nil {
if err := tx.Unsafe().QueryRowx("select * from frontends where id = $1", id).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by id")
}
return i, nil
@@ -53,7 +53,7 @@ func (str *Store) GetFrontend(id int, tx *sqlx.Tx) (*Frontend, error) {
func (str *Store) FindFrontendWithToken(token string, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select frontends.* from frontends where token = $1 and not deleted", token).StructScan(i); err != nil {
if err := tx.Unsafe().QueryRowx("select frontends.* from frontends where token = $1 and not deleted", token).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by name")
}
return i, nil
@@ -61,7 +61,7 @@ func (str *Store) FindFrontendWithToken(token string, tx *sqlx.Tx) (*Frontend, e
func (str *Store) FindFrontendWithZId(zId string, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select frontends.* from frontends where z_id = $1 and not deleted", zId).StructScan(i); err != nil {
if err := tx.Unsafe().QueryRowx("select frontends.* from frontends where z_id = $1 and not deleted", zId).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by ziti id")
}
return i, nil
@@ -69,14 +69,14 @@ func (str *Store) FindFrontendWithZId(zId string, tx *sqlx.Tx) (*Frontend, error
func (str *Store) FindFrontendPubliclyNamed(publicName string, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select frontends.* from frontends where public_name = $1 and not deleted", publicName).StructScan(i); err != nil {
if err := tx.Unsafe().QueryRowx("select frontends.* from frontends where public_name = $1 and not deleted", publicName).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by public_name")
}
return i, nil
}
func (str *Store) FindFrontendsForEnvironment(envId int, tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx("select frontends.* from frontends where environment_id = $1 and not deleted", envId)
rows, err := tx.Unsafe().Queryx("select frontends.* from frontends where environment_id = $1 and not deleted", envId)
if err != nil {
return nil, errors.Wrap(err, "error selecting frontends by environment_id")
}
@@ -92,7 +92,7 @@ func (str *Store) FindFrontendsForEnvironment(envId int, tx *sqlx.Tx) ([]*Fronte
}
func (str *Store) FindPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx("select frontends.* from frontends where environment_id is null and reserved = true and not deleted")
rows, err := tx.Unsafe().Queryx("select frontends.* from frontends where environment_id is null and reserved = true and not deleted")
if err != nil {
return nil, errors.Wrap(err, "error selecting public frontends")
}
@@ -108,7 +108,7 @@ func (str *Store) FindPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
}
func (str *Store) FindOpenPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx("select frontends.* from frontends where environment_id is null and permission_mode = 'open' and reserved = true and not deleted")
rows, err := tx.Unsafe().Queryx("select frontends.* from frontends where environment_id is null and permission_mode = 'open' and reserved = true and not deleted")
if err != nil {
return nil, errors.Wrap(err, "error selecting open public frontends")
}
@@ -124,7 +124,7 @@ func (str *Store) FindOpenPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
}
func (str *Store) FindClosedPublicFrontendsGrantedToAccount(accountId int, tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx(`
rows, err := tx.Unsafe().Queryx(`
select frontends.* from frontends
inner join frontend_grants on frontends.id = frontend_grants.frontend_id
where frontend_grants.account_id = $1
@@ -148,7 +148,7 @@ func (str *Store) FindClosedPublicFrontendsGrantedToAccount(accountId int, tx *s
}
func (str *Store) FindFrontendsForPrivateShare(shrId int, tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx("select frontends.* from frontends where private_share_id = $1 and not deleted", shrId)
rows, err := tx.Unsafe().Queryx("select frontends.* from frontends where private_share_id = $1 and not deleted", shrId)
if err != nil {
return nil, errors.Wrap(err, "error selecting frontends by private_share_id")
}
@@ -165,7 +165,7 @@ func (str *Store) FindFrontendsForPrivateShare(shrId int, tx *sqlx.Tx) ([]*Front
func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, permission_mode = $8, description = $9, bind_address = $10, updated_at = current_timestamp where id = $11"
stmt, err := tx.Prepare(sql)
stmt, err := tx.Unsafe().Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing frontends update statement")
}
@@ -177,7 +177,7 @@ func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
}
func (str *Store) DeleteFrontend(id int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("update frontends set updated_at = current_timestamp, deleted = true where id = $1")
stmt, err := tx.Unsafe().Prepare("update frontends set updated_at = current_timestamp, deleted = true where id = $1")
if err != nil {
return errors.Wrap(err, "error preparing frontends delete statement")
}
+227
View File
@@ -0,0 +1,227 @@
package endpoints
import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
)
// OAuthCookieConfig defines the interface for OAuth cookie configuration
// This allows different proxy types to implement their own config structs while sharing cookie utilities
type OAuthCookieConfig interface {
GetCookieName() string
GetCookieDomain() string
GetMaxCookieSize() int
GetSessionLifetime() time.Duration
}
// CompressToken compresses a token string using gzip and returns base64-encoded result
func CompressToken(token string) (string, error) {
var buf bytes.Buffer
gzipWriter := gzip.NewWriter(&buf)
if _, err := gzipWriter.Write([]byte(token)); err != nil {
return "", fmt.Errorf("error writing to gzip writer: %w", err)
}
if err := gzipWriter.Close(); err != nil {
return "", fmt.Errorf("error closing gzip writer: %w", err)
}
return base64.URLEncoding.EncodeToString(buf.Bytes()), nil
}
// DecompressToken decompresses a base64-encoded, gzip-compressed token string
func DecompressToken(compressed string) (string, error) {
data, err := base64.URLEncoding.DecodeString(compressed)
if err != nil {
return "", fmt.Errorf("error decoding base64: %w", err)
}
gzipReader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return "", fmt.Errorf("error creating gzip reader: %w", err)
}
defer gzipReader.Close()
decompressed, err := io.ReadAll(gzipReader)
if err != nil {
return "", fmt.Errorf("error reading from gzip reader: %w", err)
}
return string(decompressed), nil
}
// GetSessionCookie retrieves and reassembles a session cookie that may be striped across multiple cookies
func GetSessionCookie(r *http.Request, cookieName string) (*http.Cookie, error) {
baseCookie, err := r.Cookie(cookieName)
if err != nil {
return nil, err
}
// check if this is a striped cookie by looking for the count prefix
parts := strings.SplitN(baseCookie.Value, "|", 2)
if len(parts) != 2 {
// not striped, decompress and return
decompressed, err := DecompressToken(baseCookie.Value)
if err != nil {
return nil, fmt.Errorf("error decompressing cookie: %w", err)
}
return &http.Cookie{
Name: cookieName,
Value: decompressed,
}, nil
}
// striped cookie - reassemble all chunks
count, err := strconv.Atoi(parts[0])
if err != nil {
return nil, fmt.Errorf("invalid cookie count prefix: %w", err)
}
// start with the data from the base cookie
var reassembled strings.Builder
reassembled.WriteString(parts[1])
// retrieve and append the numbered chunks
for i := 1; i < count; i++ {
chunkName := fmt.Sprintf("%s_%d", cookieName, i)
chunk, err := r.Cookie(chunkName)
if err != nil {
return nil, fmt.Errorf("missing cookie chunk %s: %w", chunkName, err)
}
reassembled.WriteString(chunk.Value)
}
// decompress the reassembled data
decompressed, err := DecompressToken(reassembled.String())
if err != nil {
return nil, fmt.Errorf("error decompressing reassembled cookie: %w", err)
}
return &http.Cookie{
Name: cookieName,
Value: decompressed,
}, nil
}
// SetSessionCookie compresses and stripes a session cookie across multiple cookies if needed
func SetSessionCookie(w http.ResponseWriter, cookieName string, tokenValue string, cfg OAuthCookieConfig) error {
// compress the token
compressed, err := CompressToken(tokenValue)
if err != nil {
return fmt.Errorf("error compressing token: %w", err)
}
maxSize := cfg.GetMaxCookieSize()
if maxSize == 0 {
maxSize = 3072
}
// if compressed data fits in a single cookie, set it directly
if len(compressed) <= maxSize {
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: compressed,
MaxAge: int(cfg.GetSessionLifetime().Seconds()),
Domain: cfg.GetCookieDomain(),
Path: "/",
Expires: time.Now().Add(cfg.GetSessionLifetime()),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
return nil
}
// need to stripe across multiple cookies
logrus.Debugf("cookie size %d exceeds max %d, striping across multiple cookies", len(compressed), maxSize)
// calculate how many cookies we need
// account for the count prefix in the first cookie (e.g., "3|")
countPrefixSize := len(fmt.Sprintf("%d|", (len(compressed)/maxSize)+2)) // estimate
firstChunkSize := maxSize - countPrefixSize
remainingSize := len(compressed) - firstChunkSize
additionalChunks := (remainingSize + maxSize - 1) / maxSize // ceiling division
totalCookies := additionalChunks + 1
// set the base cookie with count prefix
firstChunkData := compressed[:firstChunkSize]
baseValue := fmt.Sprintf("%d|%s", totalCookies, firstChunkData)
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: baseValue,
MaxAge: int(cfg.GetSessionLifetime().Seconds()),
Domain: cfg.GetCookieDomain(),
Path: "/",
Expires: time.Now().Add(cfg.GetSessionLifetime()),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
// set the numbered chunks
offset := firstChunkSize
for i := 1; i < totalCookies; i++ {
chunkName := fmt.Sprintf("%s_%d", cookieName, i)
end := offset + maxSize
if end > len(compressed) {
end = len(compressed)
}
chunkData := compressed[offset:end]
http.SetCookie(w, &http.Cookie{
Name: chunkName,
Value: chunkData,
MaxAge: int(cfg.GetSessionLifetime().Seconds()),
Domain: cfg.GetCookieDomain(),
Path: "/",
Expires: time.Now().Add(cfg.GetSessionLifetime()),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
offset = end
}
return nil
}
// ClearSessionCookies clears all session cookies including any striped chunks
func ClearSessionCookies(w http.ResponseWriter, r *http.Request, cookieName string, cfg OAuthCookieConfig) {
// iterate through all cookies and clear any that match the session cookie pattern
for _, cookie := range r.Cookies() {
// clear base cookie or any numbered chunks (cookieName_1, cookieName_2, etc.)
if cookie.Name == cookieName || strings.HasPrefix(cookie.Name, cookieName+"_") {
http.SetCookie(w, &http.Cookie{
Name: cookie.Name,
Value: "",
MaxAge: -1,
Domain: cfg.GetCookieDomain(),
Path: "/",
HttpOnly: true,
})
}
}
}
// FilterSessionCookies filters out session cookies (including striped chunks) from a cookie list
func FilterSessionCookies(cookies []*http.Cookie, cookieName string) []*http.Cookie {
var filtered []*http.Cookie
for _, cookie := range cookies {
// skip the base cookie
if cookie.Name == cookieName {
continue
}
// skip numbered chunks (e.g., "cookieName_1", "cookieName_2", etc.)
if strings.HasPrefix(cookie.Name, cookieName+"_") {
continue
}
filtered = append(filtered, cookie)
}
return filtered
}
@@ -1,4 +1,4 @@
package publicProxy
package endpoints
import (
"crypto/sha256"
@@ -8,8 +8,8 @@ import (
"golang.org/x/crypto/hkdf"
)
// deriveKey uses HKDF to expand a "password" into a []byte to be used as a key; better than just a raw hash
func deriveKey(keyString string, sz int) ([]byte, error) {
// DeriveKey uses HKDF to expand a "password" into a []byte to be used as a key; better than just a raw hash
func DeriveKey(keyString string, sz int) ([]byte, error) {
out := hkdf.New(sha256.New, []byte(keyString), nil, []byte("derived-key"))
key := make([]byte, sz)
_, err := out.Read(key)
@@ -19,9 +19,9 @@ func deriveKey(keyString string, sz int) ([]byte, error) {
return key, nil
}
// encryptToken uses AES-GCM (256) to encrypt tokens for inclusion in session tokens so that they're opaque outside of
// EncryptToken uses AES-GCM (256) to encrypt tokens for inclusion in session tokens so that they're opaque outside of
// the auth subsystem
func encryptToken(token string, key []byte) (string, error) {
func EncryptToken(token string, key []byte) (string, error) {
enc, err := jose.NewEncrypter(
jose.A256GCM,
jose.Recipient{
@@ -42,7 +42,8 @@ func encryptToken(token string, key []byte) (string, error) {
return obj.CompactSerialize()
}
func decryptToken(encrypted string, key []byte) (string, error) {
// DecryptToken decrypts an encrypted token using AES-GCM (256)
func DecryptToken(encrypted string, key []byte) (string, error) {
obj, err := jose.ParseEncrypted(encrypted, []jose.KeyAlgorithm{jose.DIRECT}, []jose.ContentEncryption{jose.A256GCM})
if err != nil {
return "", fmt.Errorf("failed to parse encrypted token: %v", err)
+1 -1
View File
@@ -47,7 +47,7 @@ func (h *authHandler) handleOAuth(w http.ResponseWriter, r *http.Request, cfg ma
refreshInterval := getRefreshInterval(oauthMap)
target := fmt.Sprintf("%s%s", r.Host, r.URL.Path)
cookie, err := r.Cookie(h.cfg.Oauth.CookieName)
cookie, err := getSessionCookie(r, h.cfg.Oauth.CookieName)
if err != nil {
logrus.Errorf("unable to get '%v' cookie: %v", h.cfg.Oauth.CookieName, err)
oauthLoginRequired(w, r, h.cfg.Oauth, provider, target, refreshInterval)
+6
View File
@@ -40,6 +40,7 @@ type OauthConfig struct {
CookieDomain string
SessionLifetime time.Duration
IntermediateLifetime time.Duration
MaxCookieSize int
SigningKey string `cf:"+secret"`
EncryptionKey string `cf:"+secret"`
Providers []interface{} `cf:"+secret"`
@@ -147,3 +148,8 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error {
return nil
}
func (c *OauthConfig) GetCookieName() string { return c.CookieName }
func (c *OauthConfig) GetCookieDomain() string { return c.CookieDomain }
func (c *OauthConfig) GetMaxCookieSize() int { return c.MaxCookieSize }
func (c *OauthConfig) GetSessionLifetime() time.Duration { return c.SessionLifetime }
+35 -18
View File
@@ -6,6 +6,7 @@ import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/openziti/zrok/endpoints"
"github.com/openziti/zrok/endpoints/proxyUi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -23,6 +24,11 @@ type sessionCookieRequest struct {
targetHost string
}
// getSessionCookie retrieves and reassembles a session cookie using the shared endpoints package
func getSessionCookie(r *http.Request, cookieName string) (*http.Cookie, error) {
return endpoints.GetSessionCookie(r, cookieName)
}
func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) {
targetHost := strings.TrimSpace(req.targetHost)
if targetHost == "" {
@@ -33,7 +39,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) {
}
targetHost = strings.Split(targetHost, "/")[0]
encryptedAccessToken, err := encryptToken(req.accessToken, req.encryptionKey)
encryptedAccessToken, err := endpoints.EncryptToken(req.accessToken, req.encryptionKey)
if err != nil {
logrus.Errorf("failed to encrypt access token: %v", err)
proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("failed to encrypt access token")))
@@ -59,30 +65,41 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) {
return
}
http.SetCookie(w, &http.Cookie{
Name: req.oauthCfg.CookieName,
Value: sTkn,
MaxAge: int(req.oauthCfg.SessionLifetime.Seconds()),
Domain: req.oauthCfg.CookieDomain,
Path: "/",
Expires: time.Now().Add(req.oauthCfg.SessionLifetime),
// Secure: true, // pending server tls feature https://github.com/openziti/zrok/issues/24
HttpOnly: true, // enabled because zrok frontend is the only intended consumer of this cookie, not client-side scripts
SameSite: http.SameSiteLaxMode, // explicitly set to the default Lax mode which allows the zrok share to be navigated to from another site and receive the cookie
})
// use the shared endpoints package to set the cookie with compression and striping
if err := endpoints.SetSessionCookie(w, req.oauthCfg.CookieName, sTkn, req.oauthCfg); err != nil {
logrus.Errorf("failed to set session cookie: %v", err)
proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(req.email).WithError(errors.New("failed to set session cookie")))
return
}
}
// clearSessionCookies clears all session cookies using the shared endpoints package
func clearSessionCookies(w http.ResponseWriter, r *http.Request, cookieName string, cfg *OauthConfig) {
endpoints.ClearSessionCookies(w, r, cookieName, cfg)
}
// filterSessionCookies strips out the configured session cookie and also any `pkce` cookie
func filterSessionCookies(w http.ResponseWriter, r *http.Request, cfg *Config) {
cookies := r.Cookies()
r.Header.Del("Cookie")
for _, cookie := range cookies {
if cfg.Oauth != nil && cfg.Oauth.CookieName == cookie.Name {
continue
if cfg.Oauth != nil {
// use the shared endpoints package to filter session cookies
filtered := endpoints.FilterSessionCookies(cookies, cfg.Oauth.CookieName)
for _, cookie := range filtered {
// also filter out pkce cookie
if cookie.Name == "pkce" {
continue
}
r.AddCookie(cookie)
}
if cookie.Name == "pkce" {
continue
} else {
// no oauth config, just filter pkce
for _, cookie := range cookies {
if cookie.Name == "pkce" {
continue
}
r.AddCookie(cookie)
}
r.AddCookie(cookie)
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ func NewHTTP(cfg *Config) (*HttpFrontend, error) {
var signingKey []byte
var err error
if cfg.Oauth != nil {
signingKey, err = deriveKey(cfg.Oauth.SigningKey, 32)
signingKey, err = endpoints.DeriveKey(cfg.Oauth.SigningKey, 32)
if err != nil {
return nil, err
}
+6 -12
View File
@@ -12,6 +12,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/openziti/zrok/endpoints"
"github.com/openziti/zrok/endpoints/proxyUi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -59,11 +60,11 @@ func (c *githubConfigurer) configure() error {
scheme = "https"
}
signingKey, err := deriveKey(c.cfg.SigningKey, 32)
signingKey, err := endpoints.DeriveKey(c.cfg.SigningKey, 32)
if err != nil {
return err
}
encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32)
encryptionKey, err := endpoints.DeriveKey(c.cfg.EncryptionKey, 32)
if err != nil {
return err
}
@@ -211,7 +212,7 @@ func (c *githubConfigurer) configure() error {
http.Handle(fmt.Sprintf("/%v/auth/callback", c.githubCfg.Name), rp.CodeExchangeHandler(login, provider))
logout := func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(c.cfg.CookieName)
cookie, err := getSessionCookie(r, c.cfg.CookieName)
if err == nil {
tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) {
return signingKey, nil
@@ -219,7 +220,7 @@ func (c *githubConfigurer) configure() error {
if err == nil {
claims := tkn.Claims.(*zrokClaims)
if claims.Provider == c.githubCfg.Name {
accessToken, err := decryptToken(claims.AccessToken, encryptionKey)
accessToken, err := endpoints.DecryptToken(claims.AccessToken, encryptionKey)
if err == nil {
req, err := http.NewRequest("DELETE",
fmt.Sprintf("https://api.github.com/applications/%s/token", c.githubCfg.ClientId),
@@ -269,14 +270,7 @@ func (c *githubConfigurer) configure() error {
return
}
http.SetCookie(w, &http.Cookie{
Name: c.cfg.CookieName,
Value: "",
MaxAge: -1,
Domain: c.cfg.CookieDomain,
Path: "/",
HttpOnly: true,
})
clearSessionCookies(w, r, c.cfg.CookieName, c.cfg)
redirectURL := r.URL.Query().Get("redirect_url")
if redirectURL == "" {
+6 -12
View File
@@ -12,6 +12,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/openziti/zrok/endpoints"
"github.com/openziti/zrok/endpoints/proxyUi"
"github.com/sirupsen/logrus"
"github.com/zitadel/oidc/v2/pkg/client/rp"
@@ -58,11 +59,11 @@ func (c *googleConfigurer) configure() error {
scheme = "https"
}
signingKey, err := deriveKey(c.cfg.SigningKey, 32)
signingKey, err := endpoints.DeriveKey(c.cfg.SigningKey, 32)
if err != nil {
return err
}
encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32)
encryptionKey, err := endpoints.DeriveKey(c.cfg.EncryptionKey, 32)
if err != nil {
return err
}
@@ -182,7 +183,7 @@ func (c *googleConfigurer) configure() error {
http.Handle(fmt.Sprintf("/%v/auth/callback", c.googleCfg.Name), rp.CodeExchangeHandler(login, provider))
logout := func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(c.cfg.CookieName)
cookie, err := getSessionCookie(r, c.cfg.CookieName)
if err == nil {
tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) {
return signingKey, nil
@@ -190,7 +191,7 @@ func (c *googleConfigurer) configure() error {
if err == nil {
claims := tkn.Claims.(*zrokClaims)
if claims.Provider == c.googleCfg.Name {
accessToken, err := decryptToken(claims.AccessToken, encryptionKey)
accessToken, err := endpoints.DecryptToken(claims.AccessToken, encryptionKey)
if err == nil {
revokeURL := "https://oauth2.googleapis.com/revoke"
resp, err := http.PostForm(revokeURL, url.Values{
@@ -231,14 +232,7 @@ func (c *googleConfigurer) configure() error {
return
}
http.SetCookie(w, &http.Cookie{
Name: c.cfg.CookieName,
Value: "",
MaxAge: -1,
Domain: c.cfg.CookieDomain,
Path: "/",
HttpOnly: true,
})
clearSessionCookies(w, r, c.cfg.CookieName, c.cfg)
redirectURL := r.URL.Query().Get("redirect_url")
if redirectURL == "" {
+8 -14
View File
@@ -11,6 +11,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/openziti/zrok/endpoints"
"github.com/openziti/zrok/endpoints/proxyUi"
"github.com/sirupsen/logrus"
"github.com/zitadel/oidc/v3/pkg/client/rp"
@@ -59,11 +60,11 @@ func (c *oidcConfigurer) configure() error {
scheme = "https"
}
signingKey, err := deriveKey(c.cfg.SigningKey, 32)
signingKey, err := endpoints.DeriveKey(c.cfg.SigningKey, 32)
if err != nil {
return err
}
encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32)
encryptionKey, err := endpoints.DeriveKey(c.cfg.EncryptionKey, 32)
if err != nil {
return err
}
@@ -139,7 +140,7 @@ func (c *oidcConfigurer) configure() error {
return
}
cookie, err := r.Cookie(c.cfg.CookieName)
cookie, err := getSessionCookie(r, c.cfg.CookieName)
if err != nil {
logrus.Errorf("unable to get auth session cookie: %v", err)
proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to get auth session cookie")))
@@ -162,7 +163,7 @@ func (c *oidcConfigurer) configure() error {
return
}
accessToken, err := decryptToken(claims.AccessToken, encryptionKey)
accessToken, err := endpoints.DecryptToken(claims.AccessToken, encryptionKey)
if err != nil {
logrus.Errorf("unable to decrypt access token: %v", err)
proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")))
@@ -228,7 +229,7 @@ func (c *oidcConfigurer) configure() error {
http.Handle(fmt.Sprintf("/%v/auth/callback", c.oidcCfg.Name), rp.CodeExchangeHandler(rp.UserinfoCallback(login), provider))
logout := func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(c.cfg.CookieName)
cookie, err := getSessionCookie(r, c.cfg.CookieName)
if err == nil {
tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) {
return signingKey, nil
@@ -236,7 +237,7 @@ func (c *oidcConfigurer) configure() error {
if err == nil {
claims := tkn.Claims.(*zrokClaims)
if claims.Provider == c.oidcCfg.Name {
accessToken, err := decryptToken(claims.AccessToken, encryptionKey)
accessToken, err := endpoints.DecryptToken(claims.AccessToken, encryptionKey)
if err == nil {
if err := rp.RevokeToken(context.Background(), provider, accessToken, "access_token"); err == nil {
logrus.Infof("revoked access token for '%v'", claims.Email)
@@ -266,14 +267,7 @@ func (c *oidcConfigurer) configure() error {
return
}
http.SetCookie(w, &http.Cookie{
Name: c.cfg.CookieName,
Value: "",
MaxAge: -1,
Domain: c.cfg.CookieDomain,
Path: "/",
HttpOnly: true,
})
clearSessionCookies(w, r, c.cfg.CookieName, c.cfg)
redirectURL := r.URL.Query().Get("redirect_url")
if redirectURL == "" {