mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-01 10:22:43 +02:00
authenticate: make service http only
- Rename SessionState to State to avoid stutter. - Simplified option validation to use a wrapper function for base64 secrets. - Removed authenticates grpc code. - Abstracted logic to load and validate a user's authenticate session. - Removed instances of url.Parse in favor of urlutil's version. - proxy: replaces grpc refresh logic with forced deadline advancement. - internal/sessions: remove rest store; parse authorize header as part of session store. - proxy: refactor request signer - sessions: remove extend deadline (fixes #294) - remove AuthenticateInternalAddr - remove AuthenticateInternalAddrString - omit type tag.Key from declaration of vars TagKey* it will be inferred from the right-hand side - remove compatibility package xerrors - use cloned http.DefaultTransport as base transport
This commit is contained in:
parent
bc72d08ad4
commit
380d314404
53 changed files with 718 additions and 2280 deletions
|
@ -1,7 +1,6 @@
|
|||
package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -11,15 +10,17 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
)
|
||||
|
||||
// ErrInvalidSession is an error for invalid sessions.
|
||||
var ErrInvalidSession = errors.New("internal/sessions: invalid session")
|
||||
|
||||
// ChunkedCanaryByte is the byte value used as a canary prefix to distinguish if
|
||||
// the cookie is multi-part or not. This constant *should not* be valid
|
||||
// base64. It's important this byte is ASCII to avoid UTF-8 variable sized runes.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Directives
|
||||
const ChunkedCanaryByte byte = '%'
|
||||
|
||||
// DefaultBearerTokenHeader is default header name for the authorization bearer
|
||||
// token header as defined in rfc2617
|
||||
// https://tools.ietf.org/html/rfc6750#section-2.1
|
||||
const DefaultBearerTokenHeader = "Authorization"
|
||||
|
||||
// MaxChunkSize sets the upper bound on a cookie chunks payload value.
|
||||
// Note, this should be lower than the actual cookie's max size (4096 bytes)
|
||||
// which includes metadata.
|
||||
|
@ -29,39 +30,27 @@ const MaxChunkSize = 3800
|
|||
// set to prevent any abuse.
|
||||
const MaxNumChunks = 5
|
||||
|
||||
// CSRFStore has the functions for setting, getting, and clearing the CSRF cookie
|
||||
type CSRFStore interface {
|
||||
SetCSRF(http.ResponseWriter, *http.Request, string)
|
||||
GetCSRF(*http.Request) (*http.Cookie, error)
|
||||
ClearCSRF(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// SessionStore has the functions for setting, getting, and clearing the Session cookie
|
||||
type SessionStore interface {
|
||||
ClearSession(http.ResponseWriter, *http.Request)
|
||||
LoadSession(*http.Request) (*SessionState, error)
|
||||
SaveSession(http.ResponseWriter, *http.Request, *SessionState) error
|
||||
}
|
||||
|
||||
// CookieStore represents all the cookie related configurations
|
||||
type CookieStore struct {
|
||||
Name string
|
||||
CookieCipher cryptutil.Cipher
|
||||
CookieExpire time.Duration
|
||||
CookieRefresh time.Duration
|
||||
CookieSecure bool
|
||||
CookieHTTPOnly bool
|
||||
CookieDomain string
|
||||
Name string
|
||||
CookieCipher cryptutil.Cipher
|
||||
CookieExpire time.Duration
|
||||
CookieRefresh time.Duration
|
||||
CookieSecure bool
|
||||
CookieHTTPOnly bool
|
||||
CookieDomain string
|
||||
BearerTokenHeader string
|
||||
}
|
||||
|
||||
// CookieStoreOptions holds options for CookieStore
|
||||
type CookieStoreOptions struct {
|
||||
Name string
|
||||
CookieSecure bool
|
||||
CookieHTTPOnly bool
|
||||
CookieDomain string
|
||||
CookieExpire time.Duration
|
||||
CookieCipher cryptutil.Cipher
|
||||
Name string
|
||||
CookieSecure bool
|
||||
CookieHTTPOnly bool
|
||||
CookieDomain string
|
||||
BearerTokenHeader string
|
||||
CookieExpire time.Duration
|
||||
CookieCipher cryptutil.Cipher
|
||||
}
|
||||
|
||||
// NewCookieStore returns a new session with ciphers for each of the cookie secrets
|
||||
|
@ -72,23 +61,28 @@ func NewCookieStore(opts *CookieStoreOptions) (*CookieStore, error) {
|
|||
if opts.CookieCipher == nil {
|
||||
return nil, fmt.Errorf("internal/sessions: cipher cannot be nil")
|
||||
}
|
||||
if opts.BearerTokenHeader == "" {
|
||||
opts.BearerTokenHeader = DefaultBearerTokenHeader
|
||||
}
|
||||
|
||||
return &CookieStore{
|
||||
Name: opts.Name,
|
||||
CookieSecure: opts.CookieSecure,
|
||||
CookieHTTPOnly: opts.CookieHTTPOnly,
|
||||
CookieDomain: opts.CookieDomain,
|
||||
CookieExpire: opts.CookieExpire,
|
||||
CookieCipher: opts.CookieCipher,
|
||||
Name: opts.Name,
|
||||
CookieSecure: opts.CookieSecure,
|
||||
CookieHTTPOnly: opts.CookieHTTPOnly,
|
||||
CookieDomain: opts.CookieDomain,
|
||||
CookieExpire: opts.CookieExpire,
|
||||
CookieCipher: opts.CookieCipher,
|
||||
BearerTokenHeader: opts.BearerTokenHeader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *CookieStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
func (cs *CookieStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
domain := req.Host
|
||||
|
||||
if name == s.csrfName() {
|
||||
if name == cs.csrfName() {
|
||||
domain = req.Host
|
||||
} else if s.CookieDomain != "" {
|
||||
domain = s.CookieDomain
|
||||
} else if cs.CookieDomain != "" {
|
||||
domain = cs.CookieDomain
|
||||
} else {
|
||||
domain = splitDomain(domain)
|
||||
}
|
||||
|
@ -101,8 +95,8 @@ func (s *CookieStore) makeCookie(req *http.Request, name string, value string, e
|
|||
Value: value,
|
||||
Path: "/",
|
||||
Domain: domain,
|
||||
HttpOnly: s.CookieHTTPOnly,
|
||||
Secure: s.CookieSecure,
|
||||
HttpOnly: cs.CookieHTTPOnly,
|
||||
Secure: cs.CookieSecure,
|
||||
}
|
||||
// only set an expiration if we want one, otherwise default to non perm session based
|
||||
if expiration != 0 {
|
||||
|
@ -111,22 +105,20 @@ func (s *CookieStore) makeCookie(req *http.Request, name string, value string, e
|
|||
return c
|
||||
}
|
||||
|
||||
func (s *CookieStore) csrfName() string {
|
||||
return fmt.Sprintf("%s_csrf", s.Name)
|
||||
func (cs *CookieStore) csrfName() string {
|
||||
return fmt.Sprintf("%s_csrf", cs.Name)
|
||||
}
|
||||
|
||||
// makeSessionCookie constructs a session cookie given the request, an expiration time and the current time.
|
||||
func (s *CookieStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
return s.makeCookie(req, s.Name, value, expiration, now)
|
||||
func (cs *CookieStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
return cs.makeCookie(req, cs.Name, value, expiration, now)
|
||||
}
|
||||
|
||||
// makeCSRFCookie creates a CSRF cookie given the request, an expiration time, and the current time.
|
||||
// CSRF cookies should be scoped to the actual domain
|
||||
func (s *CookieStore) makeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
return s.makeCookie(req, s.csrfName(), value, expiration, now)
|
||||
func (cs *CookieStore) makeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
return cs.makeCookie(req, cs.csrfName(), value, expiration, now)
|
||||
}
|
||||
|
||||
func (s *CookieStore) setCookie(w http.ResponseWriter, cookie *http.Cookie) {
|
||||
func (cs *CookieStore) setCookie(w http.ResponseWriter, cookie *http.Cookie) {
|
||||
if len(cookie.String()) <= MaxChunkSize {
|
||||
http.SetCookie(w, cookie)
|
||||
return
|
||||
|
@ -142,9 +134,9 @@ func (s *CookieStore) setCookie(w http.ResponseWriter, cookie *http.Cookie) {
|
|||
nc.Name = fmt.Sprintf("%s_%d", cookie.Name, i)
|
||||
nc.Value = c
|
||||
}
|
||||
fmt.Println(i)
|
||||
http.SetCookie(w, &nc)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func chunk(s string, size int) []string {
|
||||
|
@ -159,43 +151,54 @@ func chunk(s string, size int) []string {
|
|||
}
|
||||
|
||||
// ClearCSRF clears the CSRF cookie from the request
|
||||
func (s *CookieStore) ClearCSRF(w http.ResponseWriter, req *http.Request) {
|
||||
http.SetCookie(w, s.makeCSRFCookie(req, "", time.Hour*-1, time.Now()))
|
||||
func (cs *CookieStore) ClearCSRF(w http.ResponseWriter, req *http.Request) {
|
||||
http.SetCookie(w, cs.makeCSRFCookie(req, "", time.Hour*-1, time.Now()))
|
||||
}
|
||||
|
||||
// SetCSRF sets the CSRFCookie creates a CSRF cookie in a given request
|
||||
func (s *CookieStore) SetCSRF(w http.ResponseWriter, req *http.Request, val string) {
|
||||
http.SetCookie(w, s.makeCSRFCookie(req, val, s.CookieExpire, time.Now()))
|
||||
func (cs *CookieStore) SetCSRF(w http.ResponseWriter, req *http.Request, val string) {
|
||||
http.SetCookie(w, cs.makeCSRFCookie(req, val, cs.CookieExpire, time.Now()))
|
||||
}
|
||||
|
||||
// GetCSRF gets the CSRFCookie creates a CSRF cookie in a given request
|
||||
func (s *CookieStore) GetCSRF(req *http.Request) (*http.Cookie, error) {
|
||||
return req.Cookie(s.csrfName())
|
||||
func (cs *CookieStore) GetCSRF(req *http.Request) (*http.Cookie, error) {
|
||||
c, err := req.Cookie(cs.csrfName())
|
||||
if err != nil {
|
||||
return nil, ErrEmptyCSRF // ErrNoCookie is confusing in this context
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ClearSession clears the session cookie from a request
|
||||
func (s *CookieStore) ClearSession(w http.ResponseWriter, req *http.Request) {
|
||||
http.SetCookie(w, s.makeSessionCookie(req, "", time.Hour*-1, time.Now()))
|
||||
func (cs *CookieStore) ClearSession(w http.ResponseWriter, req *http.Request) {
|
||||
http.SetCookie(w, cs.makeCookie(req, cs.Name, "", time.Hour*-1, time.Now()))
|
||||
}
|
||||
|
||||
func (s *CookieStore) setSessionCookie(w http.ResponseWriter, req *http.Request, val string) {
|
||||
s.setCookie(w, s.makeSessionCookie(req, val, s.CookieExpire, time.Now()))
|
||||
func (cs *CookieStore) setSessionCookie(w http.ResponseWriter, req *http.Request, val string) {
|
||||
cs.setCookie(w, cs.makeSessionCookie(req, val, cs.CookieExpire, time.Now()))
|
||||
}
|
||||
|
||||
// LoadSession returns a SessionState from the cookie in the request.
|
||||
func (s *CookieStore) LoadSession(req *http.Request) (*SessionState, error) {
|
||||
c, err := req.Cookie(s.Name)
|
||||
func loadBearerToken(r *http.Request, headerKey string) string {
|
||||
authHeader := r.Header.Get(headerKey)
|
||||
split := strings.Split(authHeader, "Bearer")
|
||||
if authHeader == "" || len(split) != 2 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(split[1])
|
||||
}
|
||||
|
||||
func loadChunkedCookie(r *http.Request, cookieName string) string {
|
||||
c, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
return nil, err // http.ErrNoCookie
|
||||
return ""
|
||||
}
|
||||
cipherText := c.Value
|
||||
|
||||
// if the first byte is our canary byte, we need to handle the multipart bit
|
||||
if []byte(c.Value)[0] == ChunkedCanaryByte {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "%s", cipherText[1:])
|
||||
for i := 1; i < MaxNumChunks; i++ {
|
||||
next, err := req.Cookie(fmt.Sprintf("%s_%d", s.Name, i))
|
||||
for i := 1; i <= MaxNumChunks; i++ {
|
||||
next, err := r.Cookie(fmt.Sprintf("%s_%d", cookieName, i))
|
||||
if err != nil {
|
||||
break // break if we can't find the next cookie
|
||||
}
|
||||
|
@ -203,20 +206,32 @@ func (s *CookieStore) LoadSession(req *http.Request) (*SessionState, error) {
|
|||
}
|
||||
cipherText = b.String()
|
||||
}
|
||||
session, err := UnmarshalSession(cipherText, s.CookieCipher)
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// LoadSession returns a State from the cookie in the request.
|
||||
func (cs *CookieStore) LoadSession(req *http.Request) (*State, error) {
|
||||
cipherText := loadChunkedCookie(req, cs.Name)
|
||||
if cipherText == "" {
|
||||
cipherText = loadBearerToken(req, cs.BearerTokenHeader)
|
||||
}
|
||||
if cipherText == "" {
|
||||
return nil, ErrEmptySession
|
||||
}
|
||||
session, err := UnmarshalSession(cipherText, cs.CookieCipher)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidSession
|
||||
return nil, err
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// SaveSession saves a session state to a request sessions.
|
||||
func (s *CookieStore) SaveSession(w http.ResponseWriter, req *http.Request, sessionState *SessionState) error {
|
||||
value, err := MarshalSession(sessionState, s.CookieCipher)
|
||||
func (cs *CookieStore) SaveSession(w http.ResponseWriter, req *http.Request, s *State) error {
|
||||
value, err := MarshalSession(s, cs.CookieCipher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.setSessionCookie(w, req, value)
|
||||
cs.setSessionCookie(w, req, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue