mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-10 15:47:36 +02:00
config: allow dynamic configuration of cookie settings (#1267)
This commit is contained in:
parent
0c51ad0e66
commit
fbf5b403b9
17 changed files with 184 additions and 165 deletions
|
@ -110,6 +110,8 @@ type Authenticate struct {
|
||||||
jwk *jose.JSONWebKeySet
|
jwk *jose.JSONWebKeySet
|
||||||
|
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
|
|
||||||
|
options *config.AtomicOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// New validates and creates a new authenticate service from a set of Options.
|
// New validates and creates a new authenticate service from a set of Options.
|
||||||
|
@ -138,11 +140,6 @@ func New(opts *config.Options) (*Authenticate, error) {
|
||||||
Expire: opts.CookieExpire,
|
Expire: opts.CookieExpire,
|
||||||
}
|
}
|
||||||
|
|
||||||
cookieStore, err := cookie.NewStore(cookieOptions, sharedEncoder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBrokerConn, err := grpc.NewGRPCClientConn(
|
dataBrokerConn, err := grpc.NewGRPCClientConn(
|
||||||
&grpc.Options{
|
&grpc.Options{
|
||||||
Addr: opts.DataBrokerURL,
|
Addr: opts.DataBrokerURL,
|
||||||
|
@ -192,9 +189,7 @@ func New(opts *config.Options) (*Authenticate, error) {
|
||||||
cookieSecret: decodedCookieSecret,
|
cookieSecret: decodedCookieSecret,
|
||||||
cookieCipher: cookieCipher,
|
cookieCipher: cookieCipher,
|
||||||
cookieOptions: cookieOptions,
|
cookieOptions: cookieOptions,
|
||||||
sessionStore: cookieStore,
|
|
||||||
encryptedEncoder: encryptedEncoder,
|
encryptedEncoder: encryptedEncoder,
|
||||||
sessionLoaders: []sessions.SessionLoader{qpStore, headerStore, cookieStore},
|
|
||||||
// IdP
|
// IdP
|
||||||
provider: provider,
|
provider: provider,
|
||||||
providerName: opts.Provider,
|
providerName: opts.Provider,
|
||||||
|
@ -202,8 +197,26 @@ func New(opts *config.Options) (*Authenticate, error) {
|
||||||
dataBrokerClient: dataBrokerClient,
|
dataBrokerClient: dataBrokerClient,
|
||||||
jwk: &jose.JSONWebKeySet{},
|
jwk: &jose.JSONWebKeySet{},
|
||||||
templates: template.Must(frontend.NewTemplates()),
|
templates: template.Must(frontend.NewTemplates()),
|
||||||
|
options: config.NewAtomicOptions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookieStore, err := cookie.NewStore(func() cookie.Options {
|
||||||
|
opts := a.options.Load()
|
||||||
|
return cookie.Options{
|
||||||
|
Name: opts.CookieName,
|
||||||
|
Domain: opts.CookieDomain,
|
||||||
|
Secure: opts.CookieSecure,
|
||||||
|
HTTPOnly: opts.CookieHTTPOnly,
|
||||||
|
Expire: opts.CookieExpire,
|
||||||
|
}
|
||||||
|
}, sharedEncoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.sessionStore = cookieStore
|
||||||
|
a.sessionLoaders = []sessions.SessionLoader{qpStore, headerStore, cookieStore}
|
||||||
|
|
||||||
if opts.SigningKey != "" {
|
if opts.SigningKey != "" {
|
||||||
decodedCert, err := base64.StdEncoding.DecodeString(opts.SigningKey)
|
decodedCert, err := base64.StdEncoding.DecodeString(opts.SigningKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -236,5 +249,6 @@ func (a *Authenticate) OnConfigChange(cfg *config.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Str("checksum", fmt.Sprintf("%x", cfg.Options.Checksum())).Msg("authenticate: updating options")
|
log.Info().Str("checksum", fmt.Sprintf("%x", cfg.Options.Checksum())).Msg("authenticate: updating options")
|
||||||
|
a.options.Store(cfg.Options)
|
||||||
a.setAdminUsers(cfg.Options)
|
a.setAdminUsers(cfg.Options)
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,9 +86,6 @@ func TestNew(t *testing.T) {
|
||||||
badRedirectURL.AuthenticateURL = nil
|
badRedirectURL.AuthenticateURL = nil
|
||||||
badRedirectURL.CookieName = "B"
|
badRedirectURL.CookieName = "B"
|
||||||
|
|
||||||
badCookieName := newTestOptions(t)
|
|
||||||
badCookieName.CookieName = ""
|
|
||||||
|
|
||||||
badProvider := newTestOptions(t)
|
badProvider := newTestOptions(t)
|
||||||
badProvider.Provider = ""
|
badProvider.Provider = ""
|
||||||
badProvider.CookieName = "C"
|
badProvider.CookieName = "C"
|
||||||
|
@ -118,7 +115,6 @@ func TestNew(t *testing.T) {
|
||||||
{"good", good, false},
|
{"good", good, false},
|
||||||
{"empty opts", &config.Options{}, true},
|
{"empty opts", &config.Options{}, true},
|
||||||
{"fails to validate", badRedirectURL, true},
|
{"fails to validate", badRedirectURL, true},
|
||||||
{"bad cookie name", badCookieName, true},
|
|
||||||
{"bad provider", badProvider, true},
|
{"bad provider", badProvider, true},
|
||||||
{"bad cache url", badGRPCConn, true},
|
{"bad cache url", badGRPCConn, true},
|
||||||
{"empty provider url", emptyProviderURL, true},
|
{"empty provider url", emptyProviderURL, true},
|
||||||
|
|
|
@ -23,18 +23,6 @@ import (
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type atomicOptions struct {
|
|
||||||
value atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *atomicOptions) Load() *config.Options {
|
|
||||||
return a.value.Load().(*config.Options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *atomicOptions) Store(options *config.Options) {
|
|
||||||
a.value.Store(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
type atomicMarshalUnmarshaler struct {
|
type atomicMarshalUnmarshaler struct {
|
||||||
value atomic.Value
|
value atomic.Value
|
||||||
}
|
}
|
||||||
|
@ -52,7 +40,7 @@ type Authorize struct {
|
||||||
pe *evaluator.Evaluator
|
pe *evaluator.Evaluator
|
||||||
store *evaluator.Store
|
store *evaluator.Store
|
||||||
|
|
||||||
currentOptions atomicOptions
|
currentOptions *config.AtomicOptions
|
||||||
currentEncoder atomicMarshalUnmarshaler
|
currentEncoder atomicMarshalUnmarshaler
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
|
|
||||||
|
@ -84,6 +72,7 @@ func New(opts *config.Options) (*Authorize, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
a := Authorize{
|
a := Authorize{
|
||||||
|
currentOptions: config.NewAtomicOptions(),
|
||||||
store: evaluator.NewStore(),
|
store: evaluator.NewStore(),
|
||||||
templates: template.Must(frontend.NewTemplates()),
|
templates: template.Must(frontend.NewTemplates()),
|
||||||
dataBrokerClient: databroker.NewDataBrokerServiceClient(dataBrokerConn),
|
dataBrokerClient: databroker.NewDataBrokerServiceClient(dataBrokerConn),
|
||||||
|
@ -99,7 +88,6 @@ func New(opts *config.Options) (*Authorize, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.currentEncoder.Store(encoder)
|
a.currentEncoder.Store(encoder)
|
||||||
a.currentOptions.Store(new(config.Options))
|
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ func TestAuthorize_okResponse(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
JWTClaimsHeaders: []string{"email"},
|
JWTClaimsHeaders: []string{"email"},
|
||||||
}
|
}
|
||||||
a := new(Authorize)
|
a := &Authorize{currentOptions: config.NewAtomicOptions()}
|
||||||
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
||||||
a.currentEncoder.Store(encoder)
|
a.currentEncoder.Store(encoder)
|
||||||
a.currentOptions.Store(opt)
|
a.currentOptions.Store(opt)
|
||||||
|
@ -204,7 +204,7 @@ func TestAuthorize_okResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorize_deniedResponse(t *testing.T) {
|
func TestAuthorize_deniedResponse(t *testing.T) {
|
||||||
a := new(Authorize)
|
a := &Authorize{currentOptions: config.NewAtomicOptions()}
|
||||||
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
||||||
a.currentEncoder.Store(encoder)
|
a.currentEncoder.Store(encoder)
|
||||||
a.currentOptions.Store(&config.Options{
|
a.currentOptions.Store(&config.Options{
|
||||||
|
|
|
@ -47,7 +47,7 @@ yE+vPxsiUkvQHdO2fojCkY8jg70jxM+gu59tPDNbw3Uh/2Ij310FgTHsnGQMyA==
|
||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
func Test_getEvaluatorRequest(t *testing.T) {
|
func Test_getEvaluatorRequest(t *testing.T) {
|
||||||
a := new(Authorize)
|
a := &Authorize{currentOptions: config.NewAtomicOptions()}
|
||||||
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
||||||
a.currentEncoder.Store(encoder)
|
a.currentEncoder.Store(encoder)
|
||||||
a.currentOptions.Store(&config.Options{
|
a.currentOptions.Store(&config.Options{
|
||||||
|
@ -273,7 +273,7 @@ func Test_handleForwardAuth(t *testing.T) {
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
a := new(Authorize)
|
a := &Authorize{currentOptions: config.NewAtomicOptions()}
|
||||||
var fau *url.URL
|
var fau *url.URL
|
||||||
if tc.forwardAuthURL != "" {
|
if tc.forwardAuthURL != "" {
|
||||||
fau = mustParseURL(tc.forwardAuthURL)
|
fau = mustParseURL(tc.forwardAuthURL)
|
||||||
|
@ -288,7 +288,7 @@ func Test_handleForwardAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
|
func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
|
||||||
a := new(Authorize)
|
a := &Authorize{currentOptions: config.NewAtomicOptions()}
|
||||||
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
||||||
a.currentEncoder.Store(encoder)
|
a.currentEncoder.Store(encoder)
|
||||||
a.currentOptions.Store(&config.Options{
|
a.currentOptions.Store(&config.Options{
|
||||||
|
|
|
@ -52,14 +52,15 @@ func loadSession(encoder encoding.MarshalUnmarshaler, rawJWT []byte) (*sessions.
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCookieStore(options *config.Options, encoder encoding.MarshalUnmarshaler) (sessions.SessionStore, error) {
|
func getCookieStore(options *config.Options, encoder encoding.MarshalUnmarshaler) (sessions.SessionStore, error) {
|
||||||
cookieOptions := &cookie.Options{
|
cookieStore, err := cookie.NewStore(func() cookie.Options {
|
||||||
Name: options.CookieName,
|
return cookie.Options{
|
||||||
Domain: options.CookieDomain,
|
Name: options.CookieName,
|
||||||
Secure: options.CookieSecure,
|
Domain: options.CookieDomain,
|
||||||
HTTPOnly: options.CookieHTTPOnly,
|
Secure: options.CookieSecure,
|
||||||
Expire: options.CookieExpire,
|
HTTPOnly: options.CookieHTTPOnly,
|
||||||
}
|
Expire: options.CookieExpire,
|
||||||
cookieStore, err := cookie.NewStore(cookieOptions, encoder)
|
}
|
||||||
|
}, encoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ func TestAuthorize_getJWTClaimHeaders(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
a := new(Authorize)
|
a := &Authorize{currentOptions: config.NewAtomicOptions()}
|
||||||
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
|
||||||
a.currentEncoder.Store(encoder)
|
a.currentEncoder.Store(encoder)
|
||||||
a.currentOptions.Store(opt)
|
a.currentOptions.Store(opt)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
@ -986,3 +987,25 @@ func min(x, y int) int {
|
||||||
}
|
}
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AtomicOptions are Options that can be access atomically.
|
||||||
|
type AtomicOptions struct {
|
||||||
|
value atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAtomicOptions creates a new AtomicOptions.
|
||||||
|
func NewAtomicOptions() *AtomicOptions {
|
||||||
|
ao := new(AtomicOptions)
|
||||||
|
ao.Store(new(Options))
|
||||||
|
return ao
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the options.
|
||||||
|
func (a *AtomicOptions) Load() *Options {
|
||||||
|
return a.value.Load().(*Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store stores the options.
|
||||||
|
func (a *AtomicOptions) Store(options *Options) {
|
||||||
|
a.value.Store(options)
|
||||||
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ func Run(ctx context.Context, configFile string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := setupProxy(cfg.Options, controlPlane); err != nil {
|
if err := setupProxy(src, cfg, controlPlane); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,15 +172,20 @@ func setupCache(opt *config.Options, controlPlane *controlplane.Server) (*cache.
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupProxy(opt *config.Options, controlPlane *controlplane.Server) error {
|
func setupProxy(src config.Source, cfg *config.Config, controlPlane *controlplane.Server) error {
|
||||||
if !config.IsProxy(opt.Services) {
|
if !config.IsProxy(cfg.Options.Services) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := proxy.New(*opt)
|
svc, err := proxy.New(cfg.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating proxy service: %w", err)
|
return fmt.Errorf("error creating proxy service: %w", err)
|
||||||
}
|
}
|
||||||
controlPlane.HTTPRouter.PathPrefix("/").Handler(svc)
|
controlPlane.HTTPRouter.PathPrefix("/").Handler(svc)
|
||||||
|
|
||||||
|
log.Info().Msg("enabled proxy service")
|
||||||
|
src.OnConfigChange(svc.OnConfigChange)
|
||||||
|
svc.OnConfigChange(cfg)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,18 +33,6 @@ const (
|
||||||
MaxNumChunks = 5
|
MaxNumChunks = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store implements the session store interface for session cookies.
|
|
||||||
type Store struct {
|
|
||||||
Name string
|
|
||||||
Domain string
|
|
||||||
Expire time.Duration
|
|
||||||
HTTPOnly bool
|
|
||||||
Secure bool
|
|
||||||
|
|
||||||
encoder encoding.Marshaler
|
|
||||||
decoder encoding.Unmarshaler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options holds options for Store
|
// Options holds options for Store
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -54,10 +42,20 @@ type Options struct {
|
||||||
Secure bool
|
Secure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A GetOptionsFunc is a getter for cookie options.
|
||||||
|
type GetOptionsFunc func() Options
|
||||||
|
|
||||||
|
// Store implements the session store interface for session cookies.
|
||||||
|
type Store struct {
|
||||||
|
getOptions GetOptionsFunc
|
||||||
|
encoder encoding.Marshaler
|
||||||
|
decoder encoding.Unmarshaler
|
||||||
|
}
|
||||||
|
|
||||||
// NewStore returns a new store that implements the SessionStore interface
|
// NewStore returns a new store that implements the SessionStore interface
|
||||||
// using http cookies.
|
// using http cookies.
|
||||||
func NewStore(opts *Options, encoder encoding.MarshalUnmarshaler) (sessions.SessionStore, error) {
|
func NewStore(getOptions GetOptionsFunc, encoder encoding.MarshalUnmarshaler) (sessions.SessionStore, error) {
|
||||||
cs, err := NewCookieLoader(opts, encoder)
|
cs, err := NewCookieLoader(getOptions, encoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -67,41 +65,31 @@ func NewStore(opts *Options, encoder encoding.MarshalUnmarshaler) (sessions.Sess
|
||||||
|
|
||||||
// NewCookieLoader returns a new store that implements the SessionLoader
|
// NewCookieLoader returns a new store that implements the SessionLoader
|
||||||
// interface using http cookies.
|
// interface using http cookies.
|
||||||
func NewCookieLoader(opts *Options, dencoder encoding.Unmarshaler) (*Store, error) {
|
func NewCookieLoader(getOptions GetOptionsFunc, dencoder encoding.Unmarshaler) (*Store, error) {
|
||||||
if dencoder == nil {
|
if dencoder == nil {
|
||||||
return nil, fmt.Errorf("internal/sessions: dencoder cannot be nil")
|
return nil, fmt.Errorf("internal/sessions: dencoder cannot be nil")
|
||||||
}
|
}
|
||||||
cs, err := newStore(opts)
|
cs := newStore(getOptions)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cs.decoder = dencoder
|
cs.decoder = dencoder
|
||||||
return cs, nil
|
return cs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStore(opts *Options) (*Store, error) {
|
func newStore(getOptions GetOptionsFunc) *Store {
|
||||||
if opts.Name == "" {
|
|
||||||
return nil, fmt.Errorf("internal/sessions: cookie name cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Store{
|
return &Store{
|
||||||
Name: opts.Name,
|
getOptions: getOptions,
|
||||||
Secure: opts.Secure,
|
}
|
||||||
HTTPOnly: opts.HTTPOnly,
|
|
||||||
Domain: opts.Domain,
|
|
||||||
Expire: opts.Expire,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *Store) makeCookie(value string) *http.Cookie {
|
func (cs *Store) makeCookie(value string) *http.Cookie {
|
||||||
|
opts := cs.getOptions()
|
||||||
return &http.Cookie{
|
return &http.Cookie{
|
||||||
Name: cs.Name,
|
Name: opts.Name,
|
||||||
Value: value,
|
Value: value,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Domain: cs.Domain,
|
Domain: opts.Domain,
|
||||||
HttpOnly: cs.HTTPOnly,
|
HttpOnly: opts.HTTPOnly,
|
||||||
Secure: cs.Secure,
|
Secure: opts.Secure,
|
||||||
Expires: timeNow().Add(cs.Expire),
|
Expires: timeNow().Add(opts.Expire),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +114,8 @@ func getCookies(r *http.Request, name string) []*http.Cookie {
|
||||||
|
|
||||||
// LoadSession returns a State from the cookie in the request.
|
// LoadSession returns a State from the cookie in the request.
|
||||||
func (cs *Store) LoadSession(r *http.Request) (string, error) {
|
func (cs *Store) LoadSession(r *http.Request) (string, error) {
|
||||||
cookies := getCookies(r, cs.Name)
|
opts := cs.getOptions()
|
||||||
|
cookies := getCookies(r, opts.Name)
|
||||||
if len(cookies) == 0 {
|
if len(cookies) == 0 {
|
||||||
return "", sessions.ErrNoSessionFound
|
return "", sessions.ErrNoSessionFound
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,13 +32,16 @@ func TestNewStore(t *testing.T) {
|
||||||
want sessions.SessionStore
|
want sessions.SessionStore
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"good", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, encoder, &Store{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, false},
|
{"good", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, encoder, &Store{getOptions: func() Options {
|
||||||
{"missing name", &Options{Name: "", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, encoder, nil, true},
|
return Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}
|
||||||
|
}}, false},
|
||||||
{"missing encoder", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, nil, nil, true},
|
{"missing encoder", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, nil, nil, true},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := NewStore(tt.opts, tt.encoder)
|
got, err := NewStore(func() Options {
|
||||||
|
return *tt.opts
|
||||||
|
}, tt.encoder)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("NewStore() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("NewStore() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
@ -66,13 +69,16 @@ func TestNewCookieLoader(t *testing.T) {
|
||||||
want *Store
|
want *Store
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"good", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, encoder, &Store{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, false},
|
{"good", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, encoder, &Store{getOptions: func() Options {
|
||||||
{"missing name", &Options{Name: "", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, encoder, nil, true},
|
return Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}
|
||||||
|
}}, false},
|
||||||
{"missing encoder", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, nil, nil, true},
|
{"missing encoder", &Options{Name: "_cookie", Secure: true, HTTPOnly: true, Domain: "pomerium.io", Expire: 10 * time.Second}, nil, nil, true},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := NewCookieLoader(tt.opts, tt.encoder)
|
got, err := NewCookieLoader(func() Options {
|
||||||
|
return *tt.opts
|
||||||
|
}, tt.encoder)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("NewCookieLoader() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("NewCookieLoader() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
@ -117,13 +123,17 @@ func TestStore_SaveSession(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := &Store{
|
s := &Store{
|
||||||
Name: "_pomerium",
|
getOptions: func() Options {
|
||||||
Secure: true,
|
return Options{
|
||||||
HTTPOnly: true,
|
Name: "_pomerium",
|
||||||
Domain: "pomerium.io",
|
Secure: true,
|
||||||
Expire: 10 * time.Second,
|
HTTPOnly: true,
|
||||||
encoder: tt.encoder,
|
Domain: "pomerium.io",
|
||||||
decoder: tt.decoder,
|
Expire: 10 * time.Second,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
encoder: tt.encoder,
|
||||||
|
decoder: tt.decoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
r := httptest.NewRequest("GET", "/", nil)
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
|
|
@ -65,8 +65,10 @@ func TestVerifier(t *testing.T) {
|
||||||
encSession = append(encSession, cryptutil.NewKey()...)
|
encSession = append(encSession, cryptutil.NewKey()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := NewStore(&Options{
|
cs, err := NewStore(func() Options {
|
||||||
Name: "_pomerium",
|
return Options{
|
||||||
|
Name: "_pomerium",
|
||||||
|
}
|
||||||
}, encoder)
|
}, encoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -47,7 +47,7 @@ func TestProxy_ForwardAuth(t *testing.T) {
|
||||||
opts := testOptions(t)
|
opts := testOptions(t)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
options config.Options
|
options *config.Options
|
||||||
ctxError error
|
ctxError error
|
||||||
method string
|
method string
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ func TestProxy_ForwardAuth(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
p.encoder = signer
|
p.encoder = signer
|
||||||
p.UpdateOptions(tt.options)
|
p.OnConfigChange(&config.Config{Options: tt.options})
|
||||||
uri, err := url.Parse(tt.requestURI)
|
uri, err := url.Parse(tt.requestURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -25,12 +25,15 @@ func (p *Proxy) registerDashboardHandlers(r *mux.Router) *mux.Router {
|
||||||
// 2. AuthN - Verify the user is authenticated. Set email, group, & id headers
|
// 2. AuthN - Verify the user is authenticated. Set email, group, & id headers
|
||||||
h.Use(p.AuthenticateSession)
|
h.Use(p.AuthenticateSession)
|
||||||
// 3. Enforce CSRF protections for any non-idempotent http method
|
// 3. Enforce CSRF protections for any non-idempotent http method
|
||||||
h.Use(csrf.Protect(
|
h.Use(func(h http.Handler) http.Handler {
|
||||||
p.cookieSecret,
|
opts := p.currentOptions.Load()
|
||||||
csrf.Secure(p.cookieOptions.Secure),
|
return csrf.Protect(
|
||||||
csrf.CookieName(fmt.Sprintf("%s_csrf", p.cookieOptions.Name)),
|
p.cookieSecret,
|
||||||
csrf.ErrorHandler(httputil.HandlerFunc(httputil.CSRFFailureHandler)),
|
csrf.Secure(opts.CookieSecure),
|
||||||
))
|
csrf.CookieName(fmt.Sprintf("%s_csrf", opts.CookieName)),
|
||||||
|
csrf.ErrorHandler(httputil.HandlerFunc(httputil.CSRFFailureHandler)),
|
||||||
|
)(h)
|
||||||
|
})
|
||||||
// dashboard endpoints can be used by user's to view, or modify their session
|
// dashboard endpoints can be used by user's to view, or modify their session
|
||||||
h.Path("/").HandlerFunc(p.UserDashboard).Methods(http.MethodGet)
|
h.Path("/").HandlerFunc(p.UserDashboard).Methods(http.MethodGet)
|
||||||
h.Path("/sign_out").HandlerFunc(p.SignOut).Methods(http.MethodGet, http.MethodPost)
|
h.Path("/sign_out").HandlerFunc(p.SignOut).Methods(http.MethodGet, http.MethodPost)
|
||||||
|
|
|
@ -118,7 +118,7 @@ func TestProxy_Callback(t *testing.T) {
|
||||||
opts := testOptions(t)
|
opts := testOptions(t)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
options config.Options
|
options *config.Options
|
||||||
|
|
||||||
method string
|
method string
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ func TestProxy_Callback(t *testing.T) {
|
||||||
}
|
}
|
||||||
p.encoder = tt.cipher
|
p.encoder = tt.cipher
|
||||||
p.sessionStore = tt.sessionStore
|
p.sessionStore = tt.sessionStore
|
||||||
p.UpdateOptions(tt.options)
|
p.OnConfigChange(&config.Config{Options: tt.options})
|
||||||
redirectURI := &url.URL{Scheme: tt.scheme, Host: tt.host, Path: tt.path}
|
redirectURI := &url.URL{Scheme: tt.scheme, Host: tt.host, Path: tt.path}
|
||||||
queryString := redirectURI.Query()
|
queryString := redirectURI.Query()
|
||||||
for k, v := range tt.qp {
|
for k, v := range tt.qp {
|
||||||
|
@ -276,7 +276,7 @@ func TestProxy_ProgrammaticLogin(t *testing.T) {
|
||||||
opts := testOptions(t)
|
opts := testOptions(t)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
options config.Options
|
options *config.Options
|
||||||
|
|
||||||
method string
|
method string
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ func TestProxy_ProgrammaticCallback(t *testing.T) {
|
||||||
opts := testOptions(t)
|
opts := testOptions(t)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
options config.Options
|
options *config.Options
|
||||||
|
|
||||||
method string
|
method string
|
||||||
|
|
||||||
|
@ -434,7 +434,7 @@ func TestProxy_ProgrammaticCallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
p.encoder = tt.cipher
|
p.encoder = tt.cipher
|
||||||
p.sessionStore = tt.sessionStore
|
p.sessionStore = tt.sessionStore
|
||||||
p.UpdateOptions(tt.options)
|
p.OnConfigChange(&config.Config{Options: tt.options})
|
||||||
redirectURI, _ := url.Parse(tt.redirectURI)
|
redirectURI, _ := url.Parse(tt.redirectURI)
|
||||||
queryString := redirectURI.Query()
|
queryString := redirectURI.Query()
|
||||||
for k, v := range tt.qp {
|
for k, v := range tt.qp {
|
||||||
|
|
|
@ -43,7 +43,7 @@ const (
|
||||||
|
|
||||||
// ValidateOptions checks that proper configuration settings are set to create
|
// ValidateOptions checks that proper configuration settings are set to create
|
||||||
// a proper Proxy instance
|
// a proper Proxy instance
|
||||||
func ValidateOptions(o config.Options) error {
|
func ValidateOptions(o *config.Options) error {
|
||||||
if _, err := cryptutil.NewAEADCipherFromBase64(o.SharedKey); err != nil {
|
if _, err := cryptutil.NewAEADCipherFromBase64(o.SharedKey); err != nil {
|
||||||
return fmt.Errorf("proxy: invalid 'SHARED_SECRET': %w", err)
|
return fmt.Errorf("proxy: invalid 'SHARED_SECRET': %w", err)
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,6 @@ type Proxy struct {
|
||||||
authenticateRefreshURL *url.URL
|
authenticateRefreshURL *url.URL
|
||||||
|
|
||||||
encoder encoding.Unmarshaler
|
encoder encoding.Unmarshaler
|
||||||
cookieOptions *cookie.Options
|
|
||||||
cookieSecret []byte
|
cookieSecret []byte
|
||||||
refreshCooldown time.Duration
|
refreshCooldown time.Duration
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
|
@ -85,12 +84,13 @@ type Proxy struct {
|
||||||
jwtClaimHeaders []string
|
jwtClaimHeaders []string
|
||||||
authzClient envoy_service_auth_v2.AuthorizationClient
|
authzClient envoy_service_auth_v2.AuthorizationClient
|
||||||
|
|
||||||
currentRouter atomic.Value
|
currentOptions *config.AtomicOptions
|
||||||
|
currentRouter atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// New takes a Proxy service from options and a validation function.
|
// New takes a Proxy service from options and a validation function.
|
||||||
// Function returns an error if options fail to validate.
|
// Function returns an error if options fail to validate.
|
||||||
func New(opts config.Options) (*Proxy, error) {
|
func New(opts *config.Options) (*Proxy, error) {
|
||||||
if err := ValidateOptions(opts); err != nil {
|
if err := ValidateOptions(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,34 +104,16 @@ func New(opts config.Options) (*Proxy, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cookieOptions := &cookie.Options{
|
|
||||||
Name: opts.CookieName,
|
|
||||||
Domain: opts.CookieDomain,
|
|
||||||
Secure: opts.CookieSecure,
|
|
||||||
HTTPOnly: opts.CookieHTTPOnly,
|
|
||||||
Expire: opts.CookieExpire,
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieStore, err := cookie.NewStore(cookieOptions, encoder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &Proxy{
|
p := &Proxy{
|
||||||
SharedKey: opts.SharedKey,
|
SharedKey: opts.SharedKey,
|
||||||
sharedCipher: sharedCipher,
|
sharedCipher: sharedCipher,
|
||||||
encoder: encoder,
|
encoder: encoder,
|
||||||
|
|
||||||
cookieSecret: decodedCookieSecret,
|
cookieSecret: decodedCookieSecret,
|
||||||
cookieOptions: cookieOptions,
|
|
||||||
refreshCooldown: opts.RefreshCooldown,
|
refreshCooldown: opts.RefreshCooldown,
|
||||||
sessionStore: cookieStore,
|
|
||||||
sessionLoaders: []sessions.SessionLoader{
|
|
||||||
cookieStore,
|
|
||||||
header.NewStore(encoder, httputil.AuthorizationTypePomerium),
|
|
||||||
queryparam.NewStore(encoder, "pomerium_session")},
|
|
||||||
templates: template.Must(frontend.NewTemplates()),
|
templates: template.Must(frontend.NewTemplates()),
|
||||||
jwtClaimHeaders: opts.JWTClaimsHeaders,
|
jwtClaimHeaders: opts.JWTClaimsHeaders,
|
||||||
|
currentOptions: config.NewAtomicOptions(),
|
||||||
}
|
}
|
||||||
p.currentRouter.Store(httputil.NewRouter())
|
p.currentRouter.Store(httputil.NewRouter())
|
||||||
// errors checked in ValidateOptions
|
// errors checked in ValidateOptions
|
||||||
|
@ -142,6 +124,25 @@ func New(opts config.Options) (*Proxy, error) {
|
||||||
p.authenticateSignoutURL = p.authenticateURL.ResolveReference(&url.URL{Path: signoutURL})
|
p.authenticateSignoutURL = p.authenticateURL.ResolveReference(&url.URL{Path: signoutURL})
|
||||||
p.authenticateRefreshURL = p.authenticateURL.ResolveReference(&url.URL{Path: refreshURL})
|
p.authenticateRefreshURL = p.authenticateURL.ResolveReference(&url.URL{Path: refreshURL})
|
||||||
|
|
||||||
|
cookieStore, err := cookie.NewStore(func() cookie.Options {
|
||||||
|
opts := p.currentOptions.Load()
|
||||||
|
return cookie.Options{
|
||||||
|
Name: opts.CookieName,
|
||||||
|
Domain: opts.CookieDomain,
|
||||||
|
Secure: opts.CookieSecure,
|
||||||
|
HTTPOnly: opts.CookieHTTPOnly,
|
||||||
|
Expire: opts.CookieExpire,
|
||||||
|
}
|
||||||
|
}, encoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.sessionStore = cookieStore
|
||||||
|
p.sessionLoaders = []sessions.SessionLoader{
|
||||||
|
cookieStore,
|
||||||
|
header.NewStore(encoder, httputil.AuthorizationTypePomerium),
|
||||||
|
queryparam.NewStore(encoder, "pomerium_session")}
|
||||||
|
|
||||||
authzConn, err := grpc.NewGRPCClientConn(&grpc.Options{
|
authzConn, err := grpc.NewGRPCClientConn(&grpc.Options{
|
||||||
Addr: p.authorizeURL,
|
Addr: p.authorizeURL,
|
||||||
OverrideCertificateName: opts.OverrideCertificateName,
|
OverrideCertificateName: opts.OverrideCertificateName,
|
||||||
|
@ -157,11 +158,6 @@ func New(opts config.Options) (*Proxy, error) {
|
||||||
}
|
}
|
||||||
p.authzClient = envoy_service_auth_v2.NewAuthorizationClient(authzConn)
|
p.authzClient = envoy_service_auth_v2.NewAuthorizationClient(authzConn)
|
||||||
|
|
||||||
err = p.UpdateOptions(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.AddPolicyCountCallback("pomerium-proxy", func() int64 {
|
metrics.AddPolicyCountCallback("pomerium-proxy", func() int64 {
|
||||||
return int64(len(opts.Policies))
|
return int64(len(opts.Policies))
|
||||||
})
|
})
|
||||||
|
@ -169,14 +165,15 @@ func New(opts config.Options) (*Proxy, error) {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOptions updates internal structures based on config.Options
|
// OnConfigChange updates internal structures based on config.Options
|
||||||
func (p *Proxy) UpdateOptions(o config.Options) error {
|
func (p *Proxy) OnConfigChange(cfg *config.Config) {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
log.Info().Str("checksum", fmt.Sprintf("%x", o.Checksum())).Msg("proxy: updating options")
|
|
||||||
p.setHandlers(&o)
|
log.Info().Str("checksum", fmt.Sprintf("%x", cfg.Options.Checksum())).Msg("proxy: updating options")
|
||||||
return nil
|
p.currentOptions.Store(cfg.Options)
|
||||||
|
p.setHandlers(cfg.Options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) setHandlers(opts *config.Options) {
|
func (p *Proxy) setHandlers(opts *config.Options) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testOptions(t *testing.T) config.Options {
|
func testOptions(t *testing.T) *config.Options {
|
||||||
opts := config.NewDefaultOptions()
|
opts := config.NewDefaultOptions()
|
||||||
opts.AuthenticateURLString = "https://authenticate.example"
|
opts.AuthenticateURLString = "https://authenticate.example"
|
||||||
opts.AuthorizeURLString = "https://authorize.example"
|
opts.AuthorizeURLString = "https://authorize.example"
|
||||||
|
@ -26,7 +26,7 @@ func testOptions(t *testing.T) config.Options {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return *opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOptions_Validate(t *testing.T) {
|
func TestOptions_Validate(t *testing.T) {
|
||||||
|
@ -57,11 +57,11 @@ func TestOptions_Validate(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
o config.Options
|
o *config.Options
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"good - minimum options", good, false},
|
{"good - minimum options", good, false},
|
||||||
{"nil options", config.Options{}, true},
|
{"nil options", &config.Options{}, true},
|
||||||
{"authenticate service url", badAuthURL, true},
|
{"authenticate service url", badAuthURL, true},
|
||||||
{"authenticate service url no scheme", authenticateBadScheme, true},
|
{"authenticate service url no scheme", authenticateBadScheme, true},
|
||||||
{"authorize service url no scheme", authorizeBadSCheme, true},
|
{"authorize service url no scheme", authorizeBadSCheme, true},
|
||||||
|
@ -93,14 +93,13 @@ func TestNew(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
opts config.Options
|
opts *config.Options
|
||||||
wantProxy bool
|
wantProxy bool
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"good", good, true, false},
|
{"good", good, true, false},
|
||||||
{"empty options", config.Options{}, false, true},
|
{"empty options", &config.Options{}, false, true},
|
||||||
{"short secret/validate sanity check", shortCookieLength, false, true},
|
{"short secret/validate sanity check", shortCookieLength, false, true},
|
||||||
{"invalid cookie name, empty", badCookie, false, true},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -169,8 +168,8 @@ func Test_UpdateOptions(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
originalOptions config.Options
|
originalOptions *config.Options
|
||||||
updatedOptions config.Options
|
updatedOptions *config.Options
|
||||||
host string
|
host string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantRoute bool
|
wantRoute bool
|
||||||
|
@ -198,26 +197,18 @@ func Test_UpdateOptions(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.UpdateOptions(tt.updatedOptions)
|
p.OnConfigChange(&config.Config{Options: tt.updatedOptions})
|
||||||
if (err != nil) != tt.wantErr {
|
r := httptest.NewRequest("GET", tt.host, nil)
|
||||||
t.Errorf("UpdateOptions: err = %v, wantErr = %v", err, tt.wantErr)
|
w := httptest.NewRecorder()
|
||||||
|
p.ServeHTTP(w, r)
|
||||||
|
if tt.wantRoute && w.Code != http.StatusNotFound {
|
||||||
|
t.Errorf("Failed to find route handler")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only safe if we actually can load policies
|
|
||||||
if err == nil {
|
|
||||||
r := httptest.NewRequest("GET", tt.host, nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
p.ServeHTTP(w, r)
|
|
||||||
if tt.wantRoute && w.Code != http.StatusNotFound {
|
|
||||||
t.Errorf("Failed to find route handler")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test nil
|
// Test nil
|
||||||
var p *Proxy
|
var p *Proxy
|
||||||
p.UpdateOptions(config.Options{})
|
p.OnConfigChange(&config.Config{})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue