diff --git a/authenticate/authenticate.go b/authenticate/authenticate.go index 1d28f610e..3fe5a03f8 100644 --- a/authenticate/authenticate.go +++ b/authenticate/authenticate.go @@ -11,6 +11,7 @@ import ( "github.com/pomerium/pomerium/internal/config" "github.com/pomerium/pomerium/internal/cryptutil" + "github.com/pomerium/pomerium/internal/encoding" "github.com/pomerium/pomerium/internal/encoding/ecjson" "github.com/pomerium/pomerium/internal/encoding/jws" "github.com/pomerium/pomerium/internal/identity" @@ -59,7 +60,7 @@ type Authenticate struct { sharedCipher cipher.AEAD // sharedEncoder is the encoder to use to serialize data to be consumed // by other services - sharedEncoder sessions.Encoder + sharedEncoder encoding.MarshalUnmarshaler // data related to this service only cookieOptions *sessions.CookieOptions @@ -68,7 +69,7 @@ type Authenticate struct { // is the cipher to use to encrypt data for this service cookieCipher cipher.AEAD sessionStore sessions.SessionStore - encryptedEncoder sessions.Encoder + encryptedEncoder encoding.MarshalUnmarshaler sessionStores []sessions.SessionStore sessionLoaders []sessions.SessionLoader diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 6e0c29cd3..fb0e4d02d 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -72,7 +72,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler { state, err := sessions.FromContext(r.Context()) if errors.Is(err, sessions.ErrExpired) { if err := a.refresh(w, r, state); err != nil { - log.FromRequest(r).Debug().Str("cause", err.Error()).Msg("authenticate: couldn't refresh session") + log.FromRequest(r).Info().Err(err).Msg("authenticate: verify session, refresh") a.redirectToIdentityProvider(w, r) return } @@ -80,7 +80,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler { http.Redirect(w, r, urlutil.GetAbsoluteURL(r).String(), http.StatusFound) return } else if err != nil { - log.FromRequest(r).Err(err).Msg("authenticate: malformed session") + log.FromRequest(r).Info().Err(err).Msg("authenticate: verify session") a.redirectToIdentityProvider(w, r) return } diff --git a/authenticate/handlers_test.go b/authenticate/handlers_test.go index 5dfd2549d..38485569b 100644 --- a/authenticate/handlers_test.go +++ b/authenticate/handlers_test.go @@ -12,6 +12,7 @@ import ( "github.com/pomerium/pomerium/internal/cryptutil" "github.com/pomerium/pomerium/internal/encoding" + "github.com/pomerium/pomerium/internal/encoding/mock" "github.com/pomerium/pomerium/internal/identity" "github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/templates" @@ -85,17 +86,17 @@ func TestAuthenticate_SignIn(t *testing.T) { session sessions.SessionStore provider identity.MockProvider - encoder sessions.Encoder + encoder encoding.MarshalUnmarshaler wantCode int }{ - {"good", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusFound}, - {"session not valid", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(-10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusFound}, - {"bad redirect uri query", "", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusBadRequest}, - {"bad marshal", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusBadRequest}, - {"session error", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{LoadError: errors.New("error")}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusBadRequest}, - {"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusFound}, - {"encrypted encoder error", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusBadRequest}, - {"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &encoding.MockEncoder{}, http.StatusBadRequest}, + {"good", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound}, + {"session not valid", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(-10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound}, + {"bad redirect uri query", "", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest}, + {"bad marshal", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest}, + {"session error", "https", "corp.example.example", map[string]string{"state": "example"}, &sessions.MockSessionStore{LoadError: errors.New("error")}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest}, + {"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound}, + {"encrypted encoder error", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "https://some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest}, + {"good with different programmatic redirect", "https", "corp.example.example", map[string]string{"state": "example", "pomerium_programmatic_destination_url": "some.example"}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@pomerium.io", AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Second)}}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -349,16 +350,16 @@ func TestAuthenticate_RefreshAPI(t *testing.T) { ctxError error provider identity.Authenticator - secretEncoder sessions.Encoder - sharedEncoder sessions.Encoder + secretEncoder encoding.MarshalUnmarshaler + sharedEncoder encoding.MarshalUnmarshaler wantStatus int }{ - {"good", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusOK}, - {"refresh error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshError: errors.New("error")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError}, - {"session is not refreshable error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, errors.New("session error"), identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusBadRequest}, - {"secret encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalError: errors.New("error")}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError}, - {"shared encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, encoding.MockEncoder{MarshalError: errors.New("error")}, http.StatusInternalServerError}, + {"good", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusOK}, + {"refresh error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshError: errors.New("error")}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError}, + {"session is not refreshable error", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, errors.New("session error"), identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusBadRequest}, + {"secret encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalError: errors.New("error")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError}, + {"shared encoder failed", &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: sessions.State{AccessToken: &oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalError: errors.New("error")}, http.StatusInternalServerError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 92169f836..c38b34d6c 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -25,6 +25,10 @@ - Force refresh has been removed from the dashboard. - Previous programmatic authentication endpoints (`/api/v1/token`) has been removed and is no longer supported. +### Fixed + +- Fixed an issue where cookie sessions would not clear on error.[GH-376] + ## v0.4.2 ### Security @@ -338,3 +342,4 @@ [gh-332]: https://github.com/pomerium/pomerium/pull/332/ [gh-338]: https://github.com/pomerium/pomerium/issues/338 [gh-363]: https://github.com/pomerium/pomerium/issues/363 +[gh-376]: https://github.com/pomerium/pomerium/pull/376/ diff --git a/internal/encoding/ecjson/ecjson.go b/internal/encoding/ecjson/ecjson.go index 554bd110a..fa8831454 100644 --- a/internal/encoding/ecjson/ecjson.go +++ b/internal/encoding/ecjson/ecjson.go @@ -11,6 +11,7 @@ import ( "io" "github.com/pomerium/pomerium/internal/cryptutil" + "github.com/pomerium/pomerium/internal/encoding" ) // EncryptedCompressedJSON implements SecureEncoder for JSON using an AEAD cipher. @@ -21,7 +22,7 @@ type EncryptedCompressedJSON struct { } // New takes a base64 encoded secret key and returns a new XChacha20poly1305 cipher. -func New(aead cipher.AEAD) *EncryptedCompressedJSON { +func New(aead cipher.AEAD) encoding.MarshalUnmarshaler { return &EncryptedCompressedJSON{aead: aead} } diff --git a/internal/encoding/econding.go b/internal/encoding/econding.go new file mode 100644 index 000000000..10d8089b9 --- /dev/null +++ b/internal/encoding/econding.go @@ -0,0 +1,17 @@ +package encoding // import "github.com/pomerium/pomerium/internal/encoding" + +// MarshalUnmarshaler can both Marshal and Unmarshal a struct into and from a set of bytes. +type MarshalUnmarshaler interface { + Marshaler + Unmarshaler +} + +// Marshaler encodes a struct into a set of bytes. +type Marshaler interface { + Marshal(interface{}) ([]byte, error) +} + +// Unmarshaler decodes a set of bytes and returns a struct. +type Unmarshaler interface { + Unmarshal([]byte, interface{}) error +} diff --git a/internal/encoding/jws/jws.go b/internal/encoding/jws/jws.go index e7e0df43d..4f1bf702b 100644 --- a/internal/encoding/jws/jws.go +++ b/internal/encoding/jws/jws.go @@ -5,10 +5,11 @@ package jws // import "github.com/pomerium/pomerium/internal/encoding/jws" import ( "encoding/base64" + "github.com/pomerium/pomerium/internal/cryptutil" + "github.com/pomerium/pomerium/internal/encoding" + jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" - - "github.com/pomerium/pomerium/internal/cryptutil" ) // JSONWebSigner is the struct representing a signed JWT. @@ -21,7 +22,7 @@ type JSONWebSigner struct { } // NewHS256Signer creates a SHA256 JWT signer from a 32 byte key. -func NewHS256Signer(key []byte, issuer string) (*JSONWebSigner, error) { +func NewHS256Signer(key []byte, issuer string) (encoding.MarshalUnmarshaler, error) { sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, (&jose.SignerOptions{}).WithType("JWT")) if err != nil { diff --git a/internal/encoding/mock/mock_encoder.go b/internal/encoding/mock/mock_encoder.go new file mode 100644 index 000000000..a28d0f861 --- /dev/null +++ b/internal/encoding/mock/mock_encoder.go @@ -0,0 +1,18 @@ +package mock // import "github.com/pomerium/pomerium/internal/encoding/mock" + +// Encoder MockCSRFStore is a mock implementation of Cipher. +type Encoder struct { + MarshalResponse []byte + MarshalError error + UnmarshalError error +} + +// Marshal is a mock implementation of Encoder. +func (mc Encoder) Marshal(i interface{}) ([]byte, error) { + return mc.MarshalResponse, mc.MarshalError +} + +// Unmarshal is a mock implementation of Encoder. +func (mc Encoder) Unmarshal(s []byte, i interface{}) error { + return mc.UnmarshalError +} diff --git a/internal/encoding/mock_encoder_test.go b/internal/encoding/mock/mock_encoder_test.go similarity index 82% rename from internal/encoding/mock_encoder_test.go rename to internal/encoding/mock/mock_encoder_test.go index 6b3457991..02b791548 100644 --- a/internal/encoding/mock_encoder_test.go +++ b/internal/encoding/mock/mock_encoder_test.go @@ -1,4 +1,4 @@ -package encoding // import "github.com/pomerium/pomerium/internal/encoding" +package mock // import "github.com/pomerium/pomerium/internal/encoding/mock" import ( "errors" @@ -7,7 +7,7 @@ import ( func TestMockEncoder(t *testing.T) { e := errors.New("err") - mc := MockEncoder{ + mc := Encoder{ MarshalResponse: []byte("MarshalResponse"), MarshalError: e, UnmarshalError: e, diff --git a/internal/encoding/mock_encoder.go b/internal/encoding/mock_encoder.go deleted file mode 100644 index d3c8aaa06..000000000 --- a/internal/encoding/mock_encoder.go +++ /dev/null @@ -1,18 +0,0 @@ -package encoding // import "github.com/pomerium/pomerium/internal/encoding" - -// MockEncoder MockCSRFStore is a mock implementation of Cipher. -type MockEncoder struct { - MarshalResponse []byte - MarshalError error - UnmarshalError error -} - -// Marshal is a mock implementation of MockEncoder. -func (mc MockEncoder) Marshal(i interface{}) ([]byte, error) { - return mc.MarshalResponse, mc.MarshalError -} - -// Unmarshal is a mock implementation of MockEncoder. -func (mc MockEncoder) Unmarshal(s []byte, i interface{}) error { - return mc.UnmarshalError -} diff --git a/internal/sessions/cookie_store.go b/internal/sessions/cookie_store.go index 5ac862a76..677c3d62a 100644 --- a/internal/sessions/cookie_store.go +++ b/internal/sessions/cookie_store.go @@ -3,10 +3,11 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions" import ( "errors" "fmt" - "net" "net/http" "strings" "time" + + "github.com/pomerium/pomerium/internal/encoding" ) const ( @@ -31,8 +32,9 @@ type CookieStore struct { Expire time.Duration HTTPOnly bool Secure bool - encoder Marshaler - decoder Unmarshaler + + encoder encoding.Marshaler + decoder encoding.Unmarshaler } // CookieOptions holds options for CookieStore @@ -45,70 +47,60 @@ type CookieOptions struct { } // NewCookieStore returns a new session with ciphers for each of the cookie secrets -func NewCookieStore(opts *CookieOptions, encoder Encoder) (*CookieStore, error) { - if opts.Name == "" { - return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty") +func NewCookieStore(opts *CookieOptions, encoder encoding.MarshalUnmarshaler) (*CookieStore, error) { + cs, err := NewCookieLoader(opts, encoder) + if err != nil { + return nil, err } - if encoder == nil { - return nil, fmt.Errorf("internal/sessions: decoder cannot be nil") - } - return &CookieStore{ - Name: opts.Name, - Secure: opts.Secure, - HTTPOnly: opts.HTTPOnly, - Domain: opts.Domain, - Expire: opts.Expire, - encoder: encoder, - decoder: encoder, - }, nil + cs.encoder = encoder + return cs, nil } // NewCookieLoader returns a new session with ciphers for each of the cookie secrets -func NewCookieLoader(opts *CookieOptions, decoder Unmarshaler) (*CookieStore, error) { +func NewCookieLoader(opts *CookieOptions, dencoder encoding.Unmarshaler) (*CookieStore, error) { + if dencoder == nil { + return nil, fmt.Errorf("internal/sessions: dencoder cannot be nil") + } + cs, err := newCookieStore(opts) + if err != nil { + return nil, err + } + cs.decoder = dencoder + return cs, nil +} + +func newCookieStore(opts *CookieOptions) (*CookieStore, error) { if opts.Name == "" { return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty") } - if decoder == nil { - return nil, fmt.Errorf("internal/sessions: decoder cannot be nil") - } + return &CookieStore{ Name: opts.Name, Secure: opts.Secure, HTTPOnly: opts.HTTPOnly, Domain: opts.Domain, Expire: opts.Expire, - decoder: decoder, }, nil } -func (cs *CookieStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie { - domain := req.Host - - if cs.Domain != "" { - domain = cs.Domain - } - - if h, _, err := net.SplitHostPort(domain); err == nil { - domain = h - } - c := &http.Cookie{ - Name: name, +func (cs *CookieStore) makeCookie(value string) *http.Cookie { + return &http.Cookie{ + Name: cs.Name, Value: value, Path: "/", - Domain: domain, + Domain: cs.Domain, HttpOnly: cs.HTTPOnly, Secure: cs.Secure, + Expires: timeNow().Add(cs.Expire), } - // only set an expiration if we want one, otherwise default to non perm session based - if expiration != 0 { - c.Expires = now.Add(expiration) - } - return c } // ClearSession clears the session cookie from a request -func (cs *CookieStore) ClearSession(w http.ResponseWriter, req *http.Request) { - http.SetCookie(w, cs.makeCookie(req, cs.Name, "", time.Hour*-1, time.Now())) +func (cs *CookieStore) ClearSession(w http.ResponseWriter, _ *http.Request) { + c := cs.makeCookie("") + c.MaxAge = -1 + c.Expires = timeNow().Add(-time.Hour) + http.SetCookie(w, c) } // LoadSession returns a State from the cookie in the request. @@ -127,7 +119,7 @@ func (cs *CookieStore) LoadSession(req *http.Request) (*State, error) { } // SaveSession saves a session state to a request's cookie store. -func (cs *CookieStore) SaveSession(w http.ResponseWriter, req *http.Request, x interface{}) error { +func (cs *CookieStore) SaveSession(w http.ResponseWriter, _ *http.Request, x interface{}) error { var value string if cs.encoder != nil { data, err := cs.encoder.Marshal(x) @@ -145,17 +137,12 @@ func (cs *CookieStore) SaveSession(w http.ResponseWriter, req *http.Request, x i return errors.New("internal/sessions: cannot save non-string type") } } - cs.setSessionCookie(w, req, value) + cs.setSessionCookie(w, value) return nil } -// makeSessionCookie constructs a session cookie given the request, an expiration time and the current time. -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) -} - -func (cs *CookieStore) setSessionCookie(w http.ResponseWriter, req *http.Request, val string) { - cs.setCookie(w, cs.makeSessionCookie(req, val, cs.Expire, time.Now())) +func (cs *CookieStore) setSessionCookie(w http.ResponseWriter, val string) { + cs.setCookie(w, cs.makeCookie(val)) } func (cs *CookieStore) setCookie(w http.ResponseWriter, cookie *http.Cookie) { diff --git a/internal/sessions/cookie_store_test.go b/internal/sessions/cookie_store_test.go index 0618d98db..50ba5209f 100644 --- a/internal/sessions/cookie_store_test.go +++ b/internal/sessions/cookie_store_test.go @@ -4,17 +4,18 @@ import ( "crypto/rand" "errors" "fmt" - "net/http" "net/http/httptest" "strings" "testing" "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/pomerium/pomerium/internal/cryptutil" "github.com/pomerium/pomerium/internal/encoding" "github.com/pomerium/pomerium/internal/encoding/ecjson" + "github.com/pomerium/pomerium/internal/encoding/mock" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) func TestNewCookieStore(t *testing.T) { @@ -26,7 +27,7 @@ func TestNewCookieStore(t *testing.T) { tests := []struct { name string opts *CookieOptions - encoder Encoder + encoder encoding.MarshalUnmarshaler want *CookieStore wantErr bool }{ @@ -60,7 +61,7 @@ func TestNewCookieLoader(t *testing.T) { tests := []struct { name string opts *CookieOptions - encoder Encoder + encoder encoding.MarshalUnmarshaler want *CookieStore wantErr bool }{ @@ -86,58 +87,6 @@ func TestNewCookieLoader(t *testing.T) { } } -func TestCookieStore_makeCookie(t *testing.T) { - cipher, err := cryptutil.NewAEADCipher(cryptutil.NewKey()) - - if err != nil { - t.Fatal(err) - } - - now := time.Now() - tests := []struct { - name string - domain string - - cookieDomain string - cookieName string - value string - expiration time.Duration - want *http.Cookie - wantCSRF *http.Cookie - }{ - {"good", "http://httpbin.corp.pomerium.io", "", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}}, - {"domains with https", "https://httpbin.corp.pomerium.io", "", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}}, - {"domain with port", "http://httpbin.corp.pomerium.io:443", "", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}}, - {"expiration set", "http://httpbin.corp.pomerium.io:443", "", "_pomerium", "value", 10 * time.Second, &http.Cookie{Expires: now.Add(10 * time.Second), Name: "_pomerium", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Expires: now.Add(10 * time.Second), Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}}, - {"good", "http://httpbin.corp.pomerium.io", "pomerium.io", "_pomerium", "value", 0, &http.Cookie{Name: "_pomerium", Value: "value", Path: "/", Domain: "pomerium.io", Secure: true, HttpOnly: true}, &http.Cookie{Name: "_pomerium_csrf", Value: "value", Path: "/", Domain: "httpbin.corp.pomerium.io", Secure: true, HttpOnly: true}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := httptest.NewRequest("GET", tt.domain, nil) - - s, err := NewCookieStore( - &CookieOptions{ - Name: "_pomerium", - Secure: true, - HTTPOnly: true, - Domain: tt.cookieDomain, - Expire: 10 * time.Second, - }, - ecjson.New(cipher)) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(s.makeCookie(r, tt.cookieName, tt.value, tt.expiration, now), tt.want); diff != "" { - t.Errorf("CookieStore.makeCookie() = \n%s", diff) - } - if diff := cmp.Diff(s.makeSessionCookie(r, tt.value, tt.expiration, now), tt.want); diff != "" { - t.Errorf("CookieStore.makeSessionCookie() = \n%s", diff) - } - - }) - } -} - func TestCookieStore_SaveSession(t *testing.T) { c, err := cryptutil.NewAEADCipher(cryptutil.NewKey()) if err != nil { @@ -149,18 +98,21 @@ func TestCookieStore_SaveSession(t *testing.T) { t.Fatal(err) } tests := []struct { - name string - State *State - encoder Encoder - decoder Encoder + name string + // State *State + State interface{} + encoder encoding.Marshaler + decoder encoding.Unmarshaler wantErr bool wantLoadErr bool }{ {"good", &State{Email: "user@domain.com", User: "user"}, ecjson.New(c), ecjson.New(c), false, false}, {"bad cipher", &State{Email: "user@domain.com", User: "user"}, nil, nil, true, true}, {"huge cookie", &State{Subject: fmt.Sprintf("%x", hugeString), Email: "user@domain.com", User: "user"}, ecjson.New(c), ecjson.New(c), false, false}, - {"marshal error", &State{Email: "user@domain.com", User: "user"}, encoding.MockEncoder{MarshalError: errors.New("error")}, ecjson.New(c), true, true}, + {"marshal error", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalError: errors.New("error")}, ecjson.New(c), true, true}, {"nil encoder cannot save non string type", &State{Email: "user@domain.com", User: "user"}, nil, ecjson.New(c), true, true}, + {"good marshal string directly", cryptutil.NewBase64Key(), nil, ecjson.New(c), false, true}, + {"good marshal bytes directly", cryptutil.NewKey(), nil, ecjson.New(c), false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -171,7 +123,7 @@ func TestCookieStore_SaveSession(t *testing.T) { Domain: "pomerium.io", Expire: 10 * time.Second, encoder: tt.encoder, - decoder: tt.encoder, + decoder: tt.decoder, } r := httptest.NewRequest("GET", "/", nil) @@ -182,7 +134,6 @@ func TestCookieStore_SaveSession(t *testing.T) { } r = httptest.NewRequest("GET", "/", nil) for _, cookie := range w.Result().Cookies() { - // t.Log(cookie) r.AddCookie(cookie) } diff --git a/internal/sessions/header_store.go b/internal/sessions/header_store.go index ff5817c16..58ec3c182 100644 --- a/internal/sessions/header_store.go +++ b/internal/sessions/header_store.go @@ -3,6 +3,8 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions" import ( "net/http" "strings" + + "github.com/pomerium/pomerium/internal/encoding" ) const ( @@ -15,7 +17,7 @@ const ( type HeaderStore struct { authHeader string authType string - encoder Unmarshaler + encoder encoding.Unmarshaler } // NewHeaderStore returns a new header store for loading sessions from @@ -23,7 +25,7 @@ type HeaderStore struct { // // NOTA BENE: While most servers do not log Authorization headers by default, // you should ensure no other services are logging or leaking your auth headers. -func NewHeaderStore(enc Unmarshaler, headerType string) *HeaderStore { +func NewHeaderStore(enc encoding.Unmarshaler, headerType string) *HeaderStore { if headerType == "" { headerType = defaultAuthType } diff --git a/internal/sessions/query_store.go b/internal/sessions/query_store.go index b41460b0b..cb8e3a484 100644 --- a/internal/sessions/query_store.go +++ b/internal/sessions/query_store.go @@ -2,6 +2,8 @@ package sessions // import "github.com/pomerium/pomerium/internal/sessions" import ( "net/http" + + "github.com/pomerium/pomerium/internal/encoding" ) const ( @@ -12,8 +14,8 @@ const ( // query strings / query parameters. type QueryParamStore struct { queryParamKey string - encoder Marshaler - decoder Unmarshaler + encoder encoding.Marshaler + decoder encoding.Unmarshaler } // NewQueryParamStore returns a new query param store for loading sessions from @@ -21,7 +23,7 @@ type QueryParamStore struct { // // NOTA BENE: By default, most servers _DO_ log query params, the leaking or // accidental logging of which should be considered a security issue. -func NewQueryParamStore(enc Encoder, qp string) *QueryParamStore { +func NewQueryParamStore(enc encoding.MarshalUnmarshaler, qp string) *QueryParamStore { if qp == "" { qp = defaultQueryParamKey } diff --git a/internal/sessions/query_store_test.go b/internal/sessions/query_store_test.go index 37734dfa4..dc3d5ecf8 100644 --- a/internal/sessions/query_store_test.go +++ b/internal/sessions/query_store_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/pomerium/pomerium/internal/encoding" + "github.com/pomerium/pomerium/internal/encoding/mock" ) func TestNewQueryParamStore(t *testing.T) { @@ -16,13 +17,13 @@ func TestNewQueryParamStore(t *testing.T) { name string State *State - enc Encoder + enc encoding.MarshalUnmarshaler qp string wantErr bool wantURL *url.URL }{ - {"simple good", &State{Email: "user@domain.com", User: "user"}, encoding.MockEncoder{MarshalResponse: []byte("ok")}, "", false, &url.URL{Path: "/", RawQuery: "pomerium_session=ok"}}, - {"marshall error", &State{Email: "user@domain.com", User: "user"}, encoding.MockEncoder{MarshalError: errors.New("error")}, "", true, &url.URL{Path: "/"}}, + {"simple good", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalResponse: []byte("ok")}, "", false, &url.URL{Path: "/", RawQuery: "pomerium_session=ok"}}, + {"marshall error", &State{Email: "user@domain.com", User: "user"}, mock.Encoder{MarshalError: errors.New("error")}, "", true, &url.URL{Path: "/"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/sessions/store.go b/internal/sessions/store.go index bb7dd16a7..9433771e2 100644 --- a/internal/sessions/store.go +++ b/internal/sessions/store.go @@ -37,19 +37,3 @@ type SessionStore interface { type SessionLoader interface { LoadSession(*http.Request) (*State, error) } - -// Encoder can both Marshal and Unmarshal a struct into and from a set of bytes. -type Encoder interface { - Marshaler - Unmarshaler -} - -// Marshaler encodes a struct into a set of bytes. -type Marshaler interface { - Marshal(interface{}) ([]byte, error) -} - -// Unmarshaler decodes a set of bytes and returns a struct. -type Unmarshaler interface { - Unmarshal([]byte, interface{}) error -} diff --git a/proxy/handlers_test.go b/proxy/handlers_test.go index 019e77e7c..5042b07aa 100644 --- a/proxy/handlers_test.go +++ b/proxy/handlers_test.go @@ -13,6 +13,7 @@ import ( "github.com/pomerium/pomerium/internal/config" "github.com/pomerium/pomerium/internal/encoding" + "github.com/pomerium/pomerium/internal/encoding/mock" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/proxy/clients" @@ -65,17 +66,17 @@ func TestProxy_UserDashboard(t *testing.T) { ctxError error options config.Options method string - cipher sessions.Encoder + cipher encoding.MarshalUnmarshaler session sessions.SessionStore authorizer clients.Authorizer wantAdminForm bool wantStatus int }{ - {"good", nil, opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusOK}, - {"session context error", errors.New("error"), opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusInternalServerError}, - {"want admin form good admin authorization", nil, opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK}, - {"is admin but authorization fails", nil, opts, http.MethodGet, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError}, + {"good", nil, opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusOK}, + {"session context error", errors.New("error"), opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusInternalServerError}, + {"want admin form good admin authorization", nil, opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK}, + {"is admin but authorization fails", nil, opts, http.MethodGet, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError}, } for _, tt := range tests { @@ -124,17 +125,17 @@ func TestProxy_Impersonate(t *testing.T) { email string groups string csrf string - cipher sessions.Encoder + cipher encoding.MarshalUnmarshaler sessionStore sessions.SessionStore authorizer clients.Authorizer wantStatus int }{ - {"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound}, - {"good", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError}, - {"session load error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound}, - {"non admin users rejected", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden}, - {"non admin users rejected on error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusForbidden}, - {"groups", false, opts, nil, http.MethodPost, "user@blah.com", "group1,group2", "", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound}, + {"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound}, + {"good", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError}, + {"session load error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound}, + {"non admin users rejected", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden}, + {"non admin users rejected on error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusForbidden}, + {"groups", false, opts, nil, http.MethodPost, "user@blah.com", "group1,group2", "", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -228,24 +229,24 @@ func TestProxy_VerifyWithMiddleware(t *testing.T) { path string verifyURI string - cipher sessions.Encoder + cipher encoding.MarshalUnmarshaler sessionStore sessions.SessionStore authorizer clients.Authorizer wantStatus int wantBody string }{ - {"good", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""}, - {"good verify only", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""}, - {"bad naked domain uri", opts, nil, http.MethodGet, "", "/", "a.naked.domain", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, - {"bad naked domain uri verify only", opts, nil, http.MethodGet, "", "/verify", "a.naked.domain", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, - {"bad empty verification uri", opts, nil, http.MethodGet, "", "/", " ", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, - {"bad empty verification uri verify only", opts, nil, http.MethodGet, "", "/verify", " ", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, - {"not authorized", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"}, - {"not authorized verify endpoint", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"}, - {"not authorized expired, redirect to auth", opts, sessions.ErrExpired, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusFound, ""}, - {"not authorized expired, don't redirect!", opts, sessions.ErrExpired, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"}, - {"not authorized because of error", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeError: errors.New("authz error")}, http.StatusInternalServerError, "{\"error\":\"authz error\"}\n"}, - {"not authorized expired, do not redirect to auth", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"}, + {"good", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""}, + {"good verify only", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""}, + {"bad naked domain uri", opts, nil, http.MethodGet, "", "/", "a.naked.domain", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, + {"bad naked domain uri verify only", opts, nil, http.MethodGet, "", "/verify", "a.naked.domain", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, + {"bad empty verification uri", opts, nil, http.MethodGet, "", "/", " ", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, + {"bad empty verification uri verify only", opts, nil, http.MethodGet, "", "/verify", " ", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"error\":\"bad verification uri\"}\n"}, + {"not authorized", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"}, + {"not authorized verify endpoint", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"user@test.example is not authorized for some.domain.example\"}\n"}, + {"not authorized expired, redirect to auth", opts, sessions.ErrExpired, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusFound, ""}, + {"not authorized expired, don't redirect!", opts, sessions.ErrExpired, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"}, + {"not authorized because of error", opts, nil, http.MethodGet, "", "/", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeError: errors.New("authz error")}, http.StatusInternalServerError, "{\"error\":\"authz error\"}\n"}, + {"not authorized expired, do not redirect to auth", opts, nil, http.MethodGet, "", "/verify", "https://some.domain.example", &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"error\":\"internal/sessions: validation failed, token is expired (exp)\"}\n"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -312,17 +313,17 @@ func TestProxy_Callback(t *testing.T) { qp map[string]string - cipher sessions.Encoder + cipher encoding.MarshalUnmarshaler sessionStore sessions.SessionStore authorizer clients.Authorizer wantStatus int wantBody string }{ - {"good", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_programmatic_destination_url": "ok", "pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""}, - {"bad decrypt", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqexGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""}, - {"bad save session", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError, ""}, - {"bad base64", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "^"}, &encoding.MockEncoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""}, - {"malformed redirect", opts, http.MethodGet, "http", "example.com", "/", nil, &encoding.MockEncoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""}, + {"good", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_programmatic_destination_url": "ok", "pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""}, + {"bad decrypt", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqexGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""}, + {"bad save session", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "KBEjQ9rnCxaAX-GOqetGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError, ""}, + {"bad base64", opts, http.MethodGet, "http", "example.com", "/", map[string]string{"pomerium_jwt": "^"}, &mock.Encoder{MarshalResponse: []byte("x")}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""}, + {"malformed redirect", opts, http.MethodGet, "http", "example.com", "/", nil, &mock.Encoder{}, &sessions.MockSessionStore{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/proxy/middleware.go b/proxy/middleware.go index b5128db85..aeea5b421 100644 --- a/proxy/middleware.go +++ b/proxy/middleware.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/pomerium/pomerium/internal/encoding" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/sessions" @@ -97,7 +98,7 @@ func (p *Proxy) authorize(host string, w http.ResponseWriter, r *http.Request) e // SignRequest is middleware that signs a JWT that contains a user's id, // email, and group. Session state is retrieved from the users's request context -func (p *Proxy) SignRequest(signer sessions.Marshaler) func(next http.Handler) http.Handler { +func (p *Proxy) SignRequest(signer encoding.Marshaler) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx, span := trace.StartSpan(r.Context(), "proxy.SignRequest") diff --git a/proxy/proxy.go b/proxy/proxy.go index 05bce7d97..ccaa91068 100755 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -14,6 +14,7 @@ import ( "github.com/pomerium/pomerium/internal/config" "github.com/pomerium/pomerium/internal/cryptutil" + "github.com/pomerium/pomerium/internal/encoding" "github.com/pomerium/pomerium/internal/encoding/jws" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/log" @@ -75,7 +76,7 @@ type Proxy struct { AuthorizeClient clients.Authorizer - encoder sessions.Encoder + encoder encoding.Unmarshaler cookieOptions *sessions.CookieOptions cookieSecret []byte defaultUpstreamTimeout time.Duration