mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-04 01:09:36 +02:00
internal/config: refactor option parsing
- authorize: build whitelist from policy's URLs instead of strings. - internal/httputil: merged httputil and https package. - internal/config: merged config and policy packages. - internal/metrics: removed unused measure struct. - proxy/clients: refactor Addr fields to be urls. - proxy: remove unused extend deadline function. - proxy: use handler middleware for reverse proxy leg. - proxy: change the way websocket requests are made (route based). General improvements - omitted value from range in several cases where for loop could be simplified. - added error checking to many tests. - standardize url parsing. - remove unnecessary return statements. - proxy: add self-signed certificate support. #179 - proxy: add skip tls certificate verification. #179 - proxy: Refactor websocket support to be route based. #204
This commit is contained in:
parent
28efa3359b
commit
7558d5b0de
38 changed files with 1354 additions and 1079 deletions
|
@ -3,6 +3,7 @@ package clients // import "github.com/pomerium/pomerium/proxy/clients"
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -23,8 +24,8 @@ func TestNew(t *testing.T) {
|
|||
opts *Options
|
||||
wantErr bool
|
||||
}{
|
||||
{"grpc good", "grpc", &Options{Addr: "test", InternalAddr: "intranet.local", SharedSecret: "secret"}, false},
|
||||
{"grpc missing shared secret", "grpc", &Options{Addr: "test", InternalAddr: "intranet.local", SharedSecret: ""}, true},
|
||||
{"grpc good", "grpc", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example"}, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, SharedSecret: "secret"}, false},
|
||||
{"grpc missing shared secret", "grpc", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example"}, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, SharedSecret: ""}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -211,15 +212,17 @@ func TestNewGRPC(t *testing.T) {
|
|||
wantTarget string
|
||||
}{
|
||||
{"no shared secret", &Options{}, true, "proxy/authenticator: grpc client requires shared secret", ""},
|
||||
{"empty connection", &Options{Addr: "", SharedSecret: "shh"}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"both internal and addr empty", &Options{Addr: "", InternalAddr: "", SharedSecret: "shh"}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"internal addr with port", &Options{Addr: "", InternalAddr: "intranet.local:8443", SharedSecret: "shh"}, false, "", "intranet.local:8443"},
|
||||
{"internal addr without port", &Options{Addr: "", InternalAddr: "intranet.local", SharedSecret: "shh"}, false, "", "intranet.local:443"},
|
||||
{"cert override", &Options{Addr: "", InternalAddr: "intranet.local", OverrideCertificateName: "*.local", SharedSecret: "shh"}, false, "", "intranet.local:443"},
|
||||
{"custom ca", &Options{Addr: "", InternalAddr: "intranet.local", OverrideCertificateName: "*.local", SharedSecret: "shh", CA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFVENDQWZrQ0ZBWHhneFg5K0hjWlBVVVBEK0laV0NGNUEvVTdNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1FVXgKQ3pBSkJnTlZCQVlUQWtGVk1STXdFUVlEVlFRSURBcFRiMjFsTFZOMFlYUmxNU0V3SHdZRFZRUUtEQmhKYm5SbApjbTVsZENCWGFXUm5hWFJ6SUZCMGVTQk1kR1F3SGhjTk1Ua3dNakk0TVRnMU1EQTNXaGNOTWprd01qSTFNVGcxCk1EQTNXakJGTVFzd0NRWURWUVFHRXdKQlZURVRNQkVHQTFVRUNBd0tVMjl0WlMxVGRHRjBaVEVoTUI4R0ExVUUKQ2d3WVNXNTBaWEp1WlhRZ1YybGtaMmwwY3lCUWRIa2dUSFJrTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQwpBUThBTUlJQkNnS0NBUUVBOVRFMEFiaTdnMHhYeURkVUtEbDViNTBCT05ZVVVSc3F2THQrSWkwdlpjMzRRTHhOClJrT0hrOFZEVUgzcUt1N2UrNGVubUdLVVNUdzRPNFlkQktiSWRJTFpnb3o0YitNL3FVOG5adVpiN2pBVTdOYWkKajMzVDVrbXB3L2d4WHNNUzNzdUpXUE1EUDB3Z1BUZUVRK2J1bUxVWmpLdUVIaWNTL0l5dmtaVlBzRlE4NWlaUwpkNXE2a0ZGUUdjWnFXeFg0dlhDV25Sd3E3cHY3TThJd1RYc1pYSVRuNXB5Z3VTczNKb29GQkg5U3ZNTjRKU25GCmJMK0t6ekduMy9ScXFrTXpMN3FUdkMrNWxVT3UxUmNES21mZXBuVGVaN1IyVnJUQm42NndWMjVHRnBkSDIzN00KOXhJVkJrWEd1U2NvWHVPN1lDcWFrZkt6aXdoRTV4UmRaa3gweXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQQpBNElCQVFCaHRWUEI0OCs4eFZyVmRxM1BIY3k5QkxtVEtrRFl6N2Q0ODJzTG1HczBuVUdGSTFZUDdmaFJPV3ZxCktCTlpkNEI5MUpwU1NoRGUrMHpoNno4WG5Ha01mYnRSYWx0NHEwZ3lKdk9hUWhqQ3ZCcSswTFk5d2NLbXpFdnMKcTRiNUZ5NXNpRUZSekJLTmZtTGwxTTF2cW1hNmFCVnNYUUhPREdzYS83dE5MalZ2ay9PYm52cFg3UFhLa0E3cQpLMTQvV0tBRFBJWm9mb00xMzB4Q1RTYXVpeXROajlnWkx1WU9leEZhblVwNCt2MHBYWS81OFFSNTk2U0ROVTlKClJaeDhwTzBTaUYvZXkxVUZXbmpzdHBjbTQzTFVQKzFwU1hFeVhZOFJrRTI2QzNvdjNaTFNKc2pMbC90aXVqUlgKZUJPOWorWDdzS0R4amdtajBPbWdpVkpIM0YrUAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}, false, "", "intranet.local:443"},
|
||||
{"bad ca encoding", &Options{Addr: "", InternalAddr: "intranet.local", OverrideCertificateName: "*.local", SharedSecret: "shh", CA: "^"}, true, "", "intranet.local:443"},
|
||||
{"custom ca file", &Options{Addr: "", InternalAddr: "intranet.local", OverrideCertificateName: "*.local", SharedSecret: "shh", CAFile: "testdata/example.crt"}, false, "", "intranet.local:443"},
|
||||
{"bad custom ca file", &Options{Addr: "", InternalAddr: "intranet.local", OverrideCertificateName: "*.local", SharedSecret: "shh", CAFile: "testdata/example.crt2"}, true, "", "intranet.local:443"},
|
||||
{"empty connection", &Options{Addr: nil, SharedSecret: "shh"}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"both internal and addr empty", &Options{Addr: nil, InternalAddr: nil, SharedSecret: "shh"}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"addr with port", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, SharedSecret: "shh"}, false, "", "localhost.example:8443"},
|
||||
{"addr without port", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example"}, SharedSecret: "shh"}, false, "", "localhost.example:443"},
|
||||
{"internal addr with port", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, SharedSecret: "shh"}, false, "", "localhost.example:8443"},
|
||||
{"internal addr without port", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, SharedSecret: "shh"}, false, "", "localhost.example:443"},
|
||||
{"cert override", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh"}, false, "", "localhost.example:443"},
|
||||
{"custom ca", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFVENDQWZrQ0ZBWHhneFg5K0hjWlBVVVBEK0laV0NGNUEvVTdNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1FVXgKQ3pBSkJnTlZCQVlUQWtGVk1STXdFUVlEVlFRSURBcFRiMjFsTFZOMFlYUmxNU0V3SHdZRFZRUUtEQmhKYm5SbApjbTVsZENCWGFXUm5hWFJ6SUZCMGVTQk1kR1F3SGhjTk1Ua3dNakk0TVRnMU1EQTNXaGNOTWprd01qSTFNVGcxCk1EQTNXakJGTVFzd0NRWURWUVFHRXdKQlZURVRNQkVHQTFVRUNBd0tVMjl0WlMxVGRHRjBaVEVoTUI4R0ExVUUKQ2d3WVNXNTBaWEp1WlhRZ1YybGtaMmwwY3lCUWRIa2dUSFJrTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQwpBUThBTUlJQkNnS0NBUUVBOVRFMEFiaTdnMHhYeURkVUtEbDViNTBCT05ZVVVSc3F2THQrSWkwdlpjMzRRTHhOClJrT0hrOFZEVUgzcUt1N2UrNGVubUdLVVNUdzRPNFlkQktiSWRJTFpnb3o0YitNL3FVOG5adVpiN2pBVTdOYWkKajMzVDVrbXB3L2d4WHNNUzNzdUpXUE1EUDB3Z1BUZUVRK2J1bUxVWmpLdUVIaWNTL0l5dmtaVlBzRlE4NWlaUwpkNXE2a0ZGUUdjWnFXeFg0dlhDV25Sd3E3cHY3TThJd1RYc1pYSVRuNXB5Z3VTczNKb29GQkg5U3ZNTjRKU25GCmJMK0t6ekduMy9ScXFrTXpMN3FUdkMrNWxVT3UxUmNES21mZXBuVGVaN1IyVnJUQm42NndWMjVHRnBkSDIzN00KOXhJVkJrWEd1U2NvWHVPN1lDcWFrZkt6aXdoRTV4UmRaa3gweXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQQpBNElCQVFCaHRWUEI0OCs4eFZyVmRxM1BIY3k5QkxtVEtrRFl6N2Q0ODJzTG1HczBuVUdGSTFZUDdmaFJPV3ZxCktCTlpkNEI5MUpwU1NoRGUrMHpoNno4WG5Ha01mYnRSYWx0NHEwZ3lKdk9hUWhqQ3ZCcSswTFk5d2NLbXpFdnMKcTRiNUZ5NXNpRUZSekJLTmZtTGwxTTF2cW1hNmFCVnNYUUhPREdzYS83dE5MalZ2ay9PYm52cFg3UFhLa0E3cQpLMTQvV0tBRFBJWm9mb00xMzB4Q1RTYXVpeXROajlnWkx1WU9leEZhblVwNCt2MHBYWS81OFFSNTk2U0ROVTlKClJaeDhwTzBTaUYvZXkxVUZXbmpzdHBjbTQzTFVQKzFwU1hFeVhZOFJrRTI2QzNvdjNaTFNKc2pMbC90aXVqUlgKZUJPOWorWDdzS0R4amdtajBPbWdpVkpIM0YrUAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}, false, "", "localhost.example:443"},
|
||||
{"bad ca encoding", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CA: "^"}, true, "", "localhost.example:443"},
|
||||
{"custom ca file", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CAFile: "testdata/example.crt"}, false, "", "localhost.example:443"},
|
||||
{"bad custom ca file", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CAFile: "testdata/example.crt2"}, true, "", "localhost.example:443"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/proto/authorize"
|
||||
mock "github.com/pomerium/pomerium/proto/authorize/mock_authorize"
|
||||
|
|
|
@ -7,15 +7,15 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/metrics"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
)
|
||||
|
||||
const defaultGRPCPort = 443
|
||||
|
@ -23,10 +23,10 @@ const defaultGRPCPort = 443
|
|||
// Options contains options for connecting to a pomerium rpc service.
|
||||
type Options struct {
|
||||
// Addr is the location of the authenticate service. e.g. "service.corp.example:8443"
|
||||
Addr string
|
||||
Addr *url.URL
|
||||
// InternalAddr is the internal (behind the ingress) address to use when
|
||||
// making a connection. If empty, Addr is used.
|
||||
InternalAddr string
|
||||
InternalAddr *url.URL
|
||||
// OverrideCertificateName overrides the server name used to verify the hostname on the
|
||||
// returned certificates from the server. gRPC internals also use it to override the virtual
|
||||
// hosting name if it is set.
|
||||
|
@ -45,16 +45,17 @@ func NewGRPCClientConn(opts *Options) (*grpc.ClientConn, error) {
|
|||
if opts.SharedSecret == "" {
|
||||
return nil, errors.New("proxy/clients: grpc client requires shared secret")
|
||||
}
|
||||
if opts.InternalAddr == nil && opts.Addr == nil {
|
||||
return nil, errors.New("proxy/clients: connection address required")
|
||||
|
||||
}
|
||||
grpcAuth := middleware.NewSharedSecretCred(opts.SharedSecret)
|
||||
|
||||
var connAddr string
|
||||
if opts.InternalAddr != "" {
|
||||
connAddr = opts.InternalAddr
|
||||
if opts.InternalAddr != nil {
|
||||
connAddr = opts.InternalAddr.Host
|
||||
} else {
|
||||
connAddr = opts.Addr
|
||||
}
|
||||
if connAddr == "" {
|
||||
return nil, errors.New("proxy/clients: connection address required")
|
||||
connAddr = opts.Addr.Host
|
||||
}
|
||||
// no colon exists in the connection string, assume one must be added manually
|
||||
if !strings.Contains(connAddr, ":") {
|
||||
|
|
|
@ -9,12 +9,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
)
|
||||
|
@ -345,7 +343,6 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
CSRF: csrf.SessionID,
|
||||
}
|
||||
templates.New().ExecuteTemplate(w, "dashboard.html", t)
|
||||
return
|
||||
}
|
||||
|
||||
// Refresh redeems and extends an existing authenticated oidc session with
|
||||
|
@ -366,8 +363,7 @@ func (p *Proxy) Refresh(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// reject a refresh if it's been less than 5 minutes to prevent a bad actor
|
||||
// trying to DOS the identity provider.
|
||||
// reject a refresh if it's been less than the refresh cooldown to prevent abuse
|
||||
if time.Since(iss) < p.refreshCooldown {
|
||||
log.FromRequest(r).Error().Dur("cooldown", p.refreshCooldown).Err(err).Msg("proxy: refresh cooldown")
|
||||
httpErr := &httputil.Error{
|
||||
|
@ -467,22 +463,21 @@ func (p *Proxy) authenticate(w http.ResponseWriter, r *http.Request, s *sessions
|
|||
if err != nil {
|
||||
return fmt.Errorf("proxy: session refresh failed : %v", err)
|
||||
}
|
||||
err = p.sessionStore.SaveSession(w, r, s)
|
||||
if err != nil {
|
||||
if err := p.sessionStore.SaveSession(w, r, s); err != nil {
|
||||
return fmt.Errorf("proxy: refresh failed : %v", err)
|
||||
}
|
||||
} else {
|
||||
valid, err := p.AuthenticateClient.Validate(r.Context(), s.IDToken)
|
||||
if err != nil || !valid {
|
||||
return fmt.Errorf("proxy: session valid: %v : %v", valid, err)
|
||||
return fmt.Errorf("proxy: session validate failed: %v : %v", valid, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// router attempts to find a route for a request. If a route is successfully matched,
|
||||
// it returns the route information and a bool value of `true`. If a route can not be matched,
|
||||
// a nil value for the route and false bool value is returned.
|
||||
// it returns the route information and a bool value of `true`. If a route can
|
||||
// not be matched, a nil value for the route and false bool value is returned.
|
||||
func (p *Proxy) router(r *http.Request) (http.Handler, bool) {
|
||||
config, ok := p.routeConfigs[r.Host]
|
||||
if ok {
|
||||
|
@ -494,7 +489,7 @@ func (p *Proxy) router(r *http.Request) (http.Handler, bool) {
|
|||
// policy attempts to find a policy for a request. If a policy is successfully matched,
|
||||
// it returns the policy information and a bool value of `true`. If a policy can not be matched,
|
||||
// a nil value for the policy and false bool value is returned.
|
||||
func (p *Proxy) policy(r *http.Request) (*policy.Policy, bool) {
|
||||
func (p *Proxy) policy(r *http.Request) (*config.Policy, bool) {
|
||||
config, ok := p.routeConfigs[r.Host]
|
||||
if ok {
|
||||
return &config.policy, true
|
||||
|
@ -546,32 +541,3 @@ func (p *Proxy) GetSignOutURL(authenticateURL, redirectURL *url.URL) *url.URL {
|
|||
a.RawQuery = params.Encode()
|
||||
return a
|
||||
}
|
||||
|
||||
func extendDeadline(ttl time.Duration) time.Time {
|
||||
return time.Now().Add(ttl).Truncate(time.Second)
|
||||
}
|
||||
|
||||
// websocketHandlerFunc splits request serving with timeouts depending on the protocol
|
||||
func websocketHandlerFunc(baseHandler http.Handler, timeoutHandler http.Handler, o config.Options) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Do not use timeouts for websockets because they are long-lived connections.
|
||||
if r.ProtoMajor == 1 &&
|
||||
strings.EqualFold(r.Header.Get("Connection"), "upgrade") &&
|
||||
strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||
|
||||
if o.AllowWebsockets {
|
||||
baseHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
log.FromRequest(r).Warn().Msg("proxy: attempt to proxy a websocket connection, but websocket support is disabled in the configuration")
|
||||
httpErr := &httputil.Error{Message: "websockets not supported by proxy", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
return
|
||||
}
|
||||
|
||||
// All other non-websocket requests are served with timeouts to prevent abuse
|
||||
timeoutHandler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
)
|
||||
|
@ -108,13 +107,12 @@ func TestProxy_GetSignOutURL(t *testing.T) {
|
|||
redirect string
|
||||
wantPrefix string
|
||||
}{
|
||||
{"without scheme", "auth.corp.pomerium.io", "hello.corp.pomerium.io", "https://auth.corp.pomerium.io/sign_out?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io"},
|
||||
{"with scheme", "https://auth.corp.pomerium.io", "https://hello.corp.pomerium.io", "https://auth.corp.pomerium.io/sign_out?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io"},
|
||||
{"good", "https://auth.corp.pomerium.io", "https://hello.corp.pomerium.io", "https://auth.corp.pomerium.io/sign_out?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
authenticateURL, _ := urlParse(tt.authenticate)
|
||||
redirectURL, _ := urlParse(tt.redirect)
|
||||
authenticateURL, _ := url.Parse(tt.authenticate)
|
||||
redirectURL, _ := url.Parse(tt.redirect)
|
||||
|
||||
p := &Proxy{}
|
||||
// signature is ignored as it is tested above. Avoids testing time.Now
|
||||
|
@ -135,14 +133,13 @@ func TestProxy_GetSignInURL(t *testing.T) {
|
|||
|
||||
wantPrefix string
|
||||
}{
|
||||
{"without scheme", "auth.corp.pomerium.io", "hello.corp.pomerium.io", "example_state", "https://auth.corp.pomerium.io/sign_in?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io&response_type=code&shared_secret=shared-secret"},
|
||||
{"with scheme", "https://auth.corp.pomerium.io", "https://hello.corp.pomerium.io", "example_state", "https://auth.corp.pomerium.io/sign_in?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io&response_type=code&shared_secret=shared-secret"},
|
||||
{"good", "https://auth.corp.pomerium.io", "https://hello.corp.pomerium.io", "example_state", "https://auth.corp.pomerium.io/sign_in?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io&response_type=code&shared_secret=shared-secret"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := &Proxy{SharedKey: "shared-secret"}
|
||||
authenticateURL, _ := urlParse(tt.authenticate)
|
||||
redirectURL, _ := urlParse(tt.redirect)
|
||||
authenticateURL, _ := url.Parse(tt.authenticate)
|
||||
redirectURL, _ := url.Parse(tt.redirect)
|
||||
|
||||
if got := p.GetSignInURL(authenticateURL, redirectURL, tt.state); !strings.HasPrefix(got.String(), tt.wantPrefix) {
|
||||
t.Errorf("Proxy.GetSignOutURL() = %v, wantPrefix %v", got.String(), tt.wantPrefix)
|
||||
|
@ -153,7 +150,12 @@ func TestProxy_GetSignInURL(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxy_Signout(t *testing.T) {
|
||||
proxy, err := New(testOptions())
|
||||
opts := testOptions(t)
|
||||
err := ValidateOptions(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
proxy, err := New(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -171,7 +173,7 @@ func TestProxy_Signout(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxy_OAuthStart(t *testing.T) {
|
||||
proxy, err := New(testOptions())
|
||||
proxy, err := New(testOptions(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -184,14 +186,14 @@ func TestProxy_OAuthStart(t *testing.T) {
|
|||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusFound)
|
||||
}
|
||||
// expected url
|
||||
expected := `<a href="https://authenticate.corp.beyondperimeter.com/sign_in`
|
||||
expected := `<a href="https://authenticate.example/sign_in`
|
||||
body := rr.Body.String()
|
||||
if !strings.HasPrefix(body, expected) {
|
||||
t.Errorf("handler returned unexpected body: got %v want %v", body, expected)
|
||||
}
|
||||
}
|
||||
func TestProxy_Handler(t *testing.T) {
|
||||
proxy, err := New(testOptions())
|
||||
proxy, err := New(testOptions(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -209,32 +211,16 @@ func TestProxy_Handler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_extendDeadline(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ttl time.Duration
|
||||
want time.Time
|
||||
}{
|
||||
{"good", time.Second, time.Now().Add(time.Second).Truncate(time.Second)},
|
||||
{"test nanoseconds truncated", 500 * time.Nanosecond, time.Now().Truncate(time.Second)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := extendDeadline(tt.ttl); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("extendDeadline() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy_router(t *testing.T) {
|
||||
testPolicy := policy.Policy{From: "corp.example.com", To: "example.com"}
|
||||
testPolicy.Validate()
|
||||
policies := []policy.Policy{testPolicy}
|
||||
testPolicy := config.Policy{From: "https://corp.example.com", To: "https://example.com"}
|
||||
if err := testPolicy.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
policies := []config.Policy{testPolicy}
|
||||
tests := []struct {
|
||||
name string
|
||||
host string
|
||||
mux []policy.Policy
|
||||
mux []config.Policy
|
||||
route http.Handler
|
||||
wantOk bool
|
||||
}{
|
||||
|
@ -242,13 +228,13 @@ func TestProxy_router(t *testing.T) {
|
|||
{"good with slash", "https://corp.example.com/", policies, nil, true},
|
||||
{"good with path", "https://corp.example.com/123", policies, nil, true},
|
||||
// {"multiple", "https://corp.example.com/", map[string]string{"corp.unrelated.com": "unrelated.com", "corp.example.com": "example.com"}, nil, true},
|
||||
{"no policies", "https://notcorp.example.com/123", []policy.Policy{}, nil, false},
|
||||
{"no policies", "https://notcorp.example.com/123", []config.Policy{}, nil, false},
|
||||
{"bad corp", "https://notcorp.example.com/123", policies, nil, false},
|
||||
{"bad sub-sub", "https://notcorp.corp.example.com/123", policies, nil, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
opts := testOptions()
|
||||
opts := testOptions(t)
|
||||
opts.Policies = tt.mux
|
||||
p, err := New(opts)
|
||||
if err != nil {
|
||||
|
@ -278,11 +264,10 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
opts, optsWs := testOptionsTestServer(ts.URL), testOptionsTestServer(ts.URL)
|
||||
optsCORS := testOptionsWithCORS(ts.URL)
|
||||
optsPublic := testOptionsWithPublicAccess(ts.URL)
|
||||
optsNoPolicies := testOptionsWithEmptyPolicies(ts.URL)
|
||||
optsWs.AllowWebsockets = true
|
||||
opts := testOptionsTestServer(t, ts.URL)
|
||||
optsCORS := testOptionsWithCORS(t, ts.URL)
|
||||
optsPublic := testOptionsWithPublicAccess(t, ts.URL)
|
||||
optsNoPolicies := testOptionsWithEmptyPolicies(t, ts.URL)
|
||||
|
||||
defaultHeaders, goodCORSHeaders, badCORSHeaders, headersWs := http.Header{}, http.Header{}, http.Header{}, http.Header{}
|
||||
goodCORSHeaders.Set("origin", "anything")
|
||||
|
@ -325,15 +310,15 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
{"public access, but unknown host", optsPublic, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized},
|
||||
// no session, redirect to login
|
||||
{"no http found (no session)", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: http.ErrNoCookie}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest},
|
||||
// Should be expecting a 101 Switching Protocols, but expect a 200 OK because we don't have a websocket backend to respond
|
||||
{"ws supported, ws connection", optsWs, http.MethodGet, headersWs, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK},
|
||||
{"ws supported, http connection", optsWs, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK},
|
||||
{"ws unsupported, ws connection", opts, http.MethodGet, headersWs, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest},
|
||||
{"No policies", optsNoPolicies, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusNotFound},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateOptions(tt.options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p, err := New(tt.options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -361,7 +346,7 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxy_UserDashboard(t *testing.T) {
|
||||
opts := testOptions()
|
||||
opts := testOptions(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
options config.Options
|
||||
|
@ -409,9 +394,9 @@ func TestProxy_UserDashboard(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxy_Refresh(t *testing.T) {
|
||||
opts := testOptions()
|
||||
opts := testOptions(t)
|
||||
opts.RefreshCooldown = 0
|
||||
timeSinceError := testOptions()
|
||||
timeSinceError := testOptions(t)
|
||||
timeSinceError.RefreshCooldown = time.Duration(int(^uint(0) >> 1))
|
||||
|
||||
tests := []struct {
|
||||
|
@ -455,7 +440,7 @@ func TestProxy_Refresh(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxy_Impersonate(t *testing.T) {
|
||||
opts := testOptions()
|
||||
opts := testOptions(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -535,7 +520,7 @@ func TestProxy_OAuthCallback(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
proxy, err := New(testOptions())
|
||||
proxy, err := New(testOptions(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -576,7 +561,7 @@ func TestProxy_SignOut(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
opts := testOptions()
|
||||
opts := testOptions(t)
|
||||
p, err := New(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
237
proxy/proxy.go
237
proxy/proxy.go
|
@ -1,6 +1,8 @@
|
|||
package proxy // import "github.com/pomerium/pomerium/proxy"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -9,14 +11,13 @@ import (
|
|||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/metrics"
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
"github.com/pomerium/pomerium/internal/tripper"
|
||||
|
@ -44,13 +45,13 @@ func ValidateOptions(o config.Options) error {
|
|||
if len(decoded) != 32 {
|
||||
return fmt.Errorf("`SHARED_SECRET` want 32 but got %d bytes", len(decoded))
|
||||
}
|
||||
if o.AuthenticateURL.String() == "" {
|
||||
if o.AuthenticateURL == nil || o.AuthenticateURL.String() == "" {
|
||||
return errors.New("missing setting: authenticate-service-url")
|
||||
}
|
||||
if o.AuthenticateURL.Scheme != "https" {
|
||||
return errors.New("authenticate-service-url must be a valid https url")
|
||||
}
|
||||
if o.AuthorizeURL.String() == "" {
|
||||
if o.AuthorizeURL == nil || o.AuthorizeURL.String() == "" {
|
||||
return errors.New("missing setting: authorize-service-url")
|
||||
}
|
||||
if o.AuthorizeURL.Scheme != "https" {
|
||||
|
@ -67,40 +68,42 @@ func ValidateOptions(o config.Options) error {
|
|||
return fmt.Errorf("cookie secret expects 32 bytes but got %d", len(decodedCookieSecret))
|
||||
}
|
||||
if len(o.SigningKey) != 0 {
|
||||
_, err := base64.StdEncoding.DecodeString(o.SigningKey)
|
||||
decodedSigningKey, err := base64.StdEncoding.DecodeString(o.SigningKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("signing key is invalid base64: %v", err)
|
||||
}
|
||||
_, err = cryptutil.NewES256Signer(decodedSigningKey, "localhost")
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid signing key is : %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Proxy stores all the information associated with proxying a request.
|
||||
type Proxy struct {
|
||||
SharedKey string
|
||||
|
||||
// authenticate service
|
||||
// SharedKey used to mutually authenticate service communication
|
||||
SharedKey string
|
||||
AuthenticateURL *url.URL
|
||||
AuthenticateClient clients.Authenticator
|
||||
AuthorizeClient clients.Authorizer
|
||||
|
||||
// authorize service
|
||||
AuthorizeClient clients.Authorizer
|
||||
|
||||
// session
|
||||
cipher cryptutil.Cipher
|
||||
csrfStore sessions.CSRFStore
|
||||
sessionStore sessions.SessionStore
|
||||
restStore sessions.SessionStore
|
||||
|
||||
redirectURL *url.URL
|
||||
templates *template.Template
|
||||
routeConfigs map[string]*routeConfig
|
||||
refreshCooldown time.Duration
|
||||
cipher cryptutil.Cipher
|
||||
cookieName string
|
||||
csrfStore sessions.CSRFStore
|
||||
defaultUpstreamTimeout time.Duration
|
||||
redirectURL *url.URL
|
||||
refreshCooldown time.Duration
|
||||
restStore sessions.SessionStore
|
||||
routeConfigs map[string]*routeConfig
|
||||
sessionStore sessions.SessionStore
|
||||
signingKey string
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
type routeConfig struct {
|
||||
mux http.Handler
|
||||
policy policy.Policy
|
||||
policy config.Policy
|
||||
}
|
||||
|
||||
// New takes a Proxy service from options and a validation function.
|
||||
|
@ -134,29 +137,32 @@ func New(opts config.Options) (*Proxy, error) {
|
|||
return nil, err
|
||||
}
|
||||
p := &Proxy{
|
||||
SharedKey: opts.SharedKey,
|
||||
|
||||
routeConfigs: make(map[string]*routeConfig),
|
||||
// services
|
||||
AuthenticateURL: &opts.AuthenticateURL,
|
||||
// session state
|
||||
cipher: cipher,
|
||||
csrfStore: cookieStore,
|
||||
sessionStore: cookieStore,
|
||||
restStore: restStore,
|
||||
SharedKey: opts.SharedKey,
|
||||
redirectURL: &url.URL{Path: "/.pomerium/callback"},
|
||||
templates: templates.New(),
|
||||
refreshCooldown: opts.RefreshCooldown,
|
||||
AuthenticateURL: opts.AuthenticateURL,
|
||||
|
||||
cipher: cipher,
|
||||
cookieName: opts.CookieName,
|
||||
csrfStore: cookieStore,
|
||||
defaultUpstreamTimeout: opts.DefaultUpstreamTimeout,
|
||||
redirectURL: &url.URL{Path: "/.pomerium/callback"},
|
||||
refreshCooldown: opts.RefreshCooldown,
|
||||
restStore: restStore,
|
||||
sessionStore: cookieStore,
|
||||
signingKey: opts.SigningKey,
|
||||
templates: templates.New(),
|
||||
}
|
||||
|
||||
err = p.UpdatePolicies(opts)
|
||||
if err != nil {
|
||||
if err := p.UpdatePolicies(&opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.AuthenticateClient, err = clients.NewAuthenticateClient("grpc",
|
||||
&clients.Options{
|
||||
Addr: opts.AuthenticateURL.Host,
|
||||
InternalAddr: opts.AuthenticateInternalAddr.Host,
|
||||
Addr: opts.AuthenticateURL,
|
||||
InternalAddr: opts.AuthenticateInternalAddr,
|
||||
OverrideCertificateName: opts.OverrideCertificateName,
|
||||
SharedSecret: opts.SharedKey,
|
||||
CA: opts.CA,
|
||||
|
@ -167,7 +173,7 @@ func New(opts config.Options) (*Proxy, error) {
|
|||
}
|
||||
p.AuthorizeClient, err = clients.NewAuthorizeClient("grpc",
|
||||
&clients.Options{
|
||||
Addr: opts.AuthorizeURL.Host,
|
||||
Addr: opts.AuthorizeURL,
|
||||
OverrideCertificateName: opts.OverrideCertificateName,
|
||||
SharedSecret: opts.SharedKey,
|
||||
CA: opts.CA,
|
||||
|
@ -177,26 +183,44 @@ func New(opts config.Options) (*Proxy, error) {
|
|||
}
|
||||
|
||||
// UpdatePolicies updates the handlers based on the configured policies
|
||||
func (p *Proxy) UpdatePolicies(opts config.Options) error {
|
||||
routeConfigs := make(map[string]*routeConfig)
|
||||
|
||||
policyCount := len(opts.Policies)
|
||||
if policyCount == 0 {
|
||||
log.Warn().Msg("proxy: loaded configuration with no policies specified")
|
||||
func (p *Proxy) UpdatePolicies(opts *config.Options) error {
|
||||
routeConfigs := make(map[string]*routeConfig, len(opts.Policies))
|
||||
if len(opts.Policies) == 0 {
|
||||
log.Warn().Msg("proxy: configuration has no policies")
|
||||
}
|
||||
log.Info().Int("policy-count", policyCount).Msg("proxy: updated policies")
|
||||
for _, policy := range opts.Policies {
|
||||
if err := policy.Validate(); err != nil {
|
||||
return fmt.Errorf("proxy: couldn't update policies %s", err)
|
||||
}
|
||||
proxy := NewReverseProxy(policy.Destination)
|
||||
// build http transport (roundtripper) middleware chain
|
||||
// todo(bdd): this will make vet complain, it is safe
|
||||
// and can be replaced with transport.Clone() in go 1.13
|
||||
// https://go-review.googlesource.com/c/go/+/174597/
|
||||
// https://github.com/golang/go/issues/26013#issuecomment-399481302
|
||||
transport := *(http.DefaultTransport.(*http.Transport))
|
||||
c := tripper.NewChain()
|
||||
c = c.Append(metrics.HTTPMetricsRoundTripper("proxy"))
|
||||
if policy.TLSSkipVerify {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
if policy.TLSCustomCA != "" {
|
||||
rootCA, err := p.customCAPool(policy.TLSCustomCA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proxy: couldn't add custom ca to policy %s", policy.From)
|
||||
}
|
||||
transport.TLSClientConfig = &tls.Config{RootCAs: rootCA}
|
||||
}
|
||||
proxy.Transport = c.Then(&transport)
|
||||
|
||||
for _, route := range opts.Policies {
|
||||
proxy := NewReverseProxy(route.Destination)
|
||||
handler, err := NewReverseProxyHandler(opts, proxy, &route)
|
||||
handler, err := p.newReverseProxyHandler(proxy, &policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routeConfigs[route.Source.Host] = &routeConfig{
|
||||
routeConfigs[policy.Source.Host] = &routeConfig{
|
||||
mux: handler,
|
||||
policy: route,
|
||||
policy: policy,
|
||||
}
|
||||
log.Info().Str("src", route.Source.Host).Str("dst", route.Destination.Host).Msg("proxy: new route")
|
||||
}
|
||||
p.routeConfigs = routeConfigs
|
||||
return nil
|
||||
|
@ -204,40 +228,12 @@ func (p *Proxy) UpdatePolicies(opts config.Options) error {
|
|||
|
||||
// UpstreamProxy stores information for proxying the request to the upstream.
|
||||
type UpstreamProxy struct {
|
||||
name string
|
||||
cookieName string
|
||||
handler http.Handler
|
||||
signer cryptutil.JWTSigner
|
||||
name string
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
// deleteUpstreamCookies deletes the session cookie from the request header string.
|
||||
func deleteUpstreamCookies(req *http.Request, cookieName string) {
|
||||
headers := []string{}
|
||||
for _, cookie := range req.Cookies() {
|
||||
if cookie.Name != cookieName {
|
||||
headers = append(headers, cookie.String())
|
||||
}
|
||||
}
|
||||
req.Header.Set("Cookie", strings.Join(headers, ";"))
|
||||
}
|
||||
|
||||
func (u *UpstreamProxy) signRequest(r *http.Request) {
|
||||
if u.signer != nil {
|
||||
jwt, err := u.signer.SignJWT(
|
||||
r.Header.Get(HeaderUserID),
|
||||
r.Header.Get(HeaderEmail),
|
||||
r.Header.Get(HeaderGroups))
|
||||
if err == nil {
|
||||
r.Header.Set(HeaderJWT, jwt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP signs the http request and deletes cookie headers
|
||||
// before calling the upstream's ServeHTTP function.
|
||||
// ServeHTTP handles the second (reverse-proxying) leg of pomerium's request flow
|
||||
func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
deleteUpstreamCookies(r, u.cookieName)
|
||||
u.signRequest(r)
|
||||
u.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
|
@ -247,8 +243,6 @@ func NewReverseProxy(to *url.URL) *httputil.ReverseProxy {
|
|||
proxy := httputil.NewSingleHostReverseProxy(to)
|
||||
sublogger := log.With().Str("proxy", to.Host).Logger()
|
||||
proxy.ErrorLog = stdlog.New(&log.StdLogWrapper{Logger: &sublogger}, "", 0)
|
||||
// todo(bdd): default is already http.DefaultTransport)
|
||||
// proxy.Transport = defaultUpstreamTransport
|
||||
director := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
// Identifies the originating IP addresses of a client connecting to
|
||||
|
@ -257,51 +251,62 @@ func NewReverseProxy(to *url.URL) *httputil.ReverseProxy {
|
|||
director(req)
|
||||
req.Host = to.Host
|
||||
}
|
||||
|
||||
chain := tripper.NewChain().Append(metrics.HTTPMetricsRoundTripper("proxy"))
|
||||
proxy.Transport = chain.Then(nil)
|
||||
return proxy
|
||||
}
|
||||
|
||||
// NewReverseProxyHandler applies handler specific options to a given route.
|
||||
func NewReverseProxyHandler(o config.Options, proxy *httputil.ReverseProxy, route *policy.Policy) (http.Handler, error) {
|
||||
up := &UpstreamProxy{
|
||||
name: route.Destination.Host,
|
||||
handler: proxy,
|
||||
cookieName: o.CookieName,
|
||||
// newRouteSigner creates a route specific signer.
|
||||
func (p *Proxy) newRouteSigner(audience string) (cryptutil.JWTSigner, error) {
|
||||
decodedSigningKey, err := base64.StdEncoding.DecodeString(p.signingKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(o.SigningKey) != 0 {
|
||||
decodedSigningKey, _ := base64.StdEncoding.DecodeString(o.SigningKey)
|
||||
signer, err := cryptutil.NewES256Signer(decodedSigningKey, route.Source.Host)
|
||||
return cryptutil.NewES256Signer(decodedSigningKey, audience)
|
||||
}
|
||||
|
||||
func (p *Proxy) customCAPool(cert string) (*x509.CertPool, error) {
|
||||
certPool := x509.NewCertPool()
|
||||
decodedCert, err := base64.StdEncoding.DecodeString(cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode cert: %s", err)
|
||||
}
|
||||
if ok := certPool.AppendCertsFromPEM(decodedCert); !ok {
|
||||
return nil, fmt.Errorf("could not append cert: %s", decodedCert)
|
||||
}
|
||||
return certPool, nil
|
||||
}
|
||||
|
||||
// newReverseProxyHandler applies handler specific options to a given route.
|
||||
func (p *Proxy) newReverseProxyHandler(rp *httputil.ReverseProxy, route *config.Policy) (http.Handler, error) {
|
||||
var handler http.Handler
|
||||
handler = &UpstreamProxy{
|
||||
name: route.Destination.Host,
|
||||
handler: rp,
|
||||
}
|
||||
c := middleware.NewChain()
|
||||
c = c.Append(middleware.StripPomeriumCookie(p.cookieName))
|
||||
|
||||
// if signing key is set, add signer to middleware
|
||||
if len(p.signingKey) != 0 {
|
||||
signer, err := p.newRouteSigner(route.Source.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
up.signer = signer
|
||||
c = c.Append(middleware.SignRequest(signer, HeaderUserID, HeaderEmail, HeaderGroups, HeaderJWT))
|
||||
}
|
||||
timeout := o.DefaultUpstreamTimeout
|
||||
if route.UpstreamTimeout != 0 {
|
||||
timeout = route.UpstreamTimeout
|
||||
// websockets cannot use the non-hijackable timeout-handler
|
||||
if !route.AllowWebsockets {
|
||||
timeout := p.defaultUpstreamTimeout
|
||||
if route.UpstreamTimeout != 0 {
|
||||
timeout = route.UpstreamTimeout
|
||||
}
|
||||
timeoutMsg := fmt.Sprintf("%s failed to respond within the %s timeout period", route.Destination.Host, timeout)
|
||||
handler = http.TimeoutHandler(handler, timeout, timeoutMsg)
|
||||
}
|
||||
timeoutMsg := fmt.Sprintf("%s failed to respond within the %s timeout period", route.Destination.Host, timeout)
|
||||
timeoutHandler := http.TimeoutHandler(up, timeout, timeoutMsg)
|
||||
return websocketHandlerFunc(up, timeoutHandler, o), nil
|
||||
}
|
||||
|
||||
// urlParse wraps url.Parse to add a scheme if none-exists.
|
||||
// https://github.com/golang/go/issues/12585
|
||||
func urlParse(uri string) (*url.URL, error) {
|
||||
if !strings.Contains(uri, "://") {
|
||||
uri = fmt.Sprintf("https://%s", uri)
|
||||
}
|
||||
return url.ParseRequestURI(uri)
|
||||
return c.Then(handler), nil
|
||||
}
|
||||
|
||||
// UpdateOptions updates internal structures based on config.Options
|
||||
func (p *Proxy) UpdateOptions(o config.Options) error {
|
||||
log.Info().Msg("proxy: updating options")
|
||||
err := p.UpdatePolicies(o)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not update policies: %s", err)
|
||||
}
|
||||
return nil
|
||||
return p.UpdatePolicies(&o)
|
||||
}
|
||||
|
|
|
@ -10,12 +10,19 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
)
|
||||
|
||||
var fixedDate = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
|
||||
func newTestOptions(t *testing.T) *config.Options {
|
||||
opts, err := config.NewOptions("https://authenticate.example", "https://authorize.example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw="
|
||||
return opts
|
||||
}
|
||||
|
||||
func TestNewReverseProxy(t *testing.T) {
|
||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -54,14 +61,19 @@ func TestNewReverseProxyHandler(t *testing.T) {
|
|||
backendHost := net.JoinHostPort(backendHostname, backendPort)
|
||||
proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
|
||||
proxyHandler := NewReverseProxy(proxyURL)
|
||||
opts := config.NewOptions()
|
||||
opts := newTestOptions(t)
|
||||
opts.SigningKey = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU0zbXBaSVdYQ1g5eUVneFU2czU3Q2J0YlVOREJTQ0VBdFFGNWZVV0hwY1FvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFaFBRditMQUNQVk5tQlRLMHhTVHpicEVQa1JyazFlVXQxQk9hMzJTRWZVUHpOaTRJV2VaLwpLS0lUdDJxMUlxcFYyS01TYlZEeXI5aWp2L1hoOThpeUV3PT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo="
|
||||
testPolicy := policy.Policy{From: "corp.example.com", To: "example.com", UpstreamTimeout: 1 * time.Second}
|
||||
testPolicy.Validate()
|
||||
|
||||
handle, err := NewReverseProxyHandler(opts, proxyHandler, &testPolicy)
|
||||
testPolicy := config.Policy{From: "https://corp.example.com", To: "https://example.com", UpstreamTimeout: 1 * time.Second}
|
||||
if err := testPolicy.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p, err := New(*opts)
|
||||
if err != nil {
|
||||
t.Errorf("got %q", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
handle, err := p.newReverseProxyHandler(proxyHandler, &testPolicy)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
frontend := httptest.NewServer(handle)
|
||||
|
@ -77,109 +89,104 @@ func TestNewReverseProxyHandler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testOptions() config.Options {
|
||||
func testOptions(t *testing.T) config.Options {
|
||||
authenticateService, _ := url.Parse("https://authenticate.corp.beyondperimeter.com")
|
||||
authorizeService, _ := url.Parse("https://authorize.corp.beyondperimeter.com")
|
||||
|
||||
opts := config.NewOptions()
|
||||
testPolicy := policy.Policy{From: "corp.example.notatld", To: "example.notatld"}
|
||||
testPolicy.Validate()
|
||||
opts.Policies = []policy.Policy{testPolicy}
|
||||
opts.AuthenticateURL = *authenticateService
|
||||
opts.AuthorizeURL = *authorizeService
|
||||
opts := newTestOptions(t)
|
||||
testPolicy := config.Policy{From: "https://corp.example.example", To: "https://example.example"}
|
||||
opts.Policies = []config.Policy{testPolicy}
|
||||
opts.AuthenticateURL = authenticateService
|
||||
opts.AuthorizeURL = authorizeService
|
||||
opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="
|
||||
opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw="
|
||||
opts.CookieName = "pomerium"
|
||||
return opts
|
||||
err := opts.Validate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return *opts
|
||||
}
|
||||
|
||||
func testOptionsTestServer(uri string) config.Options {
|
||||
func testOptionsTestServer(t *testing.T, uri string) config.Options {
|
||||
authenticateService, _ := url.Parse("https://authenticate.corp.beyondperimeter.com")
|
||||
authorizeService, _ := url.Parse("https://authorize.corp.beyondperimeter.com")
|
||||
// RFC 2606
|
||||
testPolicy := policy.Policy{
|
||||
From: "httpbin.corp.example",
|
||||
testPolicy := config.Policy{
|
||||
From: "https://httpbin.corp.example",
|
||||
To: uri,
|
||||
}
|
||||
testPolicy.Validate()
|
||||
opts := config.NewOptions()
|
||||
opts.Policies = []policy.Policy{testPolicy}
|
||||
opts.AuthenticateURL = *authenticateService
|
||||
opts.AuthorizeURL = *authorizeService
|
||||
if err := testPolicy.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts := newTestOptions(t)
|
||||
opts.Policies = []config.Policy{testPolicy}
|
||||
opts.AuthenticateURL = authenticateService
|
||||
opts.AuthorizeURL = authorizeService
|
||||
opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="
|
||||
opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw="
|
||||
opts.CookieName = "pomerium"
|
||||
return opts
|
||||
return *opts
|
||||
}
|
||||
|
||||
func testOptionsWithCORS(uri string) config.Options {
|
||||
testPolicy := policy.Policy{
|
||||
From: "httpbin.corp.example",
|
||||
func testOptionsWithCORS(t *testing.T, uri string) config.Options {
|
||||
testPolicy := config.Policy{
|
||||
From: "https://httpbin.corp.example",
|
||||
To: uri,
|
||||
CORSAllowPreflight: true,
|
||||
}
|
||||
testPolicy.Validate()
|
||||
opts := testOptionsTestServer(uri)
|
||||
opts.Policies = []policy.Policy{testPolicy}
|
||||
if err := testPolicy.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts := testOptionsTestServer(t, uri)
|
||||
opts.Policies = []config.Policy{testPolicy}
|
||||
return opts
|
||||
}
|
||||
|
||||
func testOptionsWithPublicAccess(uri string) config.Options {
|
||||
testPolicy := policy.Policy{
|
||||
From: "httpbin.corp.example",
|
||||
func testOptionsWithPublicAccess(t *testing.T, uri string) config.Options {
|
||||
testPolicy := config.Policy{
|
||||
From: "https://httpbin.corp.example",
|
||||
To: uri,
|
||||
AllowPublicUnauthenticatedAccess: true,
|
||||
}
|
||||
testPolicy.Validate()
|
||||
opts := testOptions()
|
||||
opts.Policies = []policy.Policy{testPolicy}
|
||||
return opts
|
||||
}
|
||||
|
||||
func testOptionsWithPublicAccessAndWhitelist(uri string) config.Options {
|
||||
testPolicy := policy.Policy{
|
||||
From: "httpbin.corp.example",
|
||||
To: uri,
|
||||
AllowPublicUnauthenticatedAccess: true,
|
||||
AllowedEmails: []string{"test@gmail.com"},
|
||||
if err := testPolicy.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testPolicy.Validate()
|
||||
opts := testOptions()
|
||||
opts.Policies = []policy.Policy{testPolicy}
|
||||
opts := testOptions(t)
|
||||
opts.Policies = []config.Policy{testPolicy}
|
||||
return opts
|
||||
}
|
||||
|
||||
func testOptionsWithEmptyPolicies(uri string) config.Options {
|
||||
opts := testOptionsTestServer(uri)
|
||||
opts.Policies = []policy.Policy{}
|
||||
func testOptionsWithEmptyPolicies(t *testing.T, uri string) config.Options {
|
||||
opts := testOptionsTestServer(t, uri)
|
||||
opts.Policies = []config.Policy{}
|
||||
return opts
|
||||
}
|
||||
|
||||
func TestOptions_Validate(t *testing.T) {
|
||||
good := testOptions()
|
||||
badAuthURL := testOptions()
|
||||
badAuthURL.AuthenticateURL = url.URL{}
|
||||
good := testOptions(t)
|
||||
badAuthURL := testOptions(t)
|
||||
badAuthURL.AuthenticateURL = nil
|
||||
authurl, _ := url.Parse("http://authenticate.corp.beyondperimeter.com")
|
||||
authenticateBadScheme := testOptions()
|
||||
authenticateBadScheme.AuthenticateURL = *authurl
|
||||
authorizeBadSCheme := testOptions()
|
||||
authorizeBadSCheme.AuthorizeURL = *authurl
|
||||
authorizeNil := testOptions()
|
||||
authorizeNil.AuthorizeURL = url.URL{}
|
||||
emptyCookieSecret := testOptions()
|
||||
authenticateBadScheme := testOptions(t)
|
||||
authenticateBadScheme.AuthenticateURL = authurl
|
||||
authorizeBadSCheme := testOptions(t)
|
||||
authorizeBadSCheme.AuthorizeURL = authurl
|
||||
authorizeNil := testOptions(t)
|
||||
authorizeNil.AuthorizeURL = nil
|
||||
emptyCookieSecret := testOptions(t)
|
||||
emptyCookieSecret.CookieSecret = ""
|
||||
invalidCookieSecret := testOptions()
|
||||
invalidCookieSecret := testOptions(t)
|
||||
invalidCookieSecret.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw^"
|
||||
shortCookieLength := testOptions()
|
||||
shortCookieLength := testOptions(t)
|
||||
shortCookieLength.CookieSecret = "gN3xnvfsAwfCXxnJorGLKUG4l2wC8sS8nfLMhcStPg=="
|
||||
invalidSignKey := testOptions()
|
||||
invalidSignKey := testOptions(t)
|
||||
invalidSignKey.SigningKey = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw^"
|
||||
badSharedKey := testOptions()
|
||||
badSharedKey := testOptions(t)
|
||||
badSharedKey.SharedKey = ""
|
||||
sharedKeyBadBas64 := testOptions()
|
||||
sharedKeyBadBas64 := testOptions(t)
|
||||
sharedKeyBadBas64.SharedKey = "%(*@389"
|
||||
missingPolicy := testOptions()
|
||||
missingPolicy.Policies = []policy.Policy{}
|
||||
missingPolicy := testOptions(t)
|
||||
missingPolicy.Policies = []config.Policy{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -197,7 +204,6 @@ func TestOptions_Validate(t *testing.T) {
|
|||
{"short cookie secret", shortCookieLength, true},
|
||||
{"no shared secret", badSharedKey, true},
|
||||
{"invalid signing key", invalidSignKey, true},
|
||||
{"missing policy", missingPolicy, false},
|
||||
{"shared secret bad base64", sharedKeyBadBas64, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -212,10 +218,10 @@ func TestOptions_Validate(t *testing.T) {
|
|||
|
||||
func TestNew(t *testing.T) {
|
||||
|
||||
good := testOptions()
|
||||
shortCookieLength := testOptions()
|
||||
good := testOptions(t)
|
||||
shortCookieLength := testOptions(t)
|
||||
shortCookieLength.CookieSecret = "gN3xnvfsAwfCXxnJorGLKUG4l2wC8sS8nfLMhcStPg=="
|
||||
badRoutedProxy := testOptions()
|
||||
badRoutedProxy := testOptions(t)
|
||||
badRoutedProxy.SigningKey = "YmFkIGtleQo="
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -240,7 +246,7 @@ func TestNew(t *testing.T) {
|
|||
t.Errorf("New() expected valid proxy struct")
|
||||
}
|
||||
if got != nil && len(got.routeConfigs) != tt.numRoutes {
|
||||
t.Errorf("New() = num routeConfigs \n%+v, want \n%+v", got, tt.numRoutes)
|
||||
t.Errorf("New() = num routeConfigs \n%+v, want \n%+v \nfrom %+v", got, tt.numRoutes, tt.opts)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -248,34 +254,65 @@ func TestNew(t *testing.T) {
|
|||
|
||||
func Test_UpdateOptions(t *testing.T) {
|
||||
|
||||
good := testOptions()
|
||||
bad := testOptions()
|
||||
bad.SigningKey = "f"
|
||||
newPolicy := policy.Policy{To: "foo.notatld", From: "bar.notatld"}
|
||||
newPolicy.Validate()
|
||||
newPolicies := []policy.Policy{
|
||||
good := testOptions(t)
|
||||
newPolicy := config.Policy{To: "http://foo.example", From: "http://bar.example"}
|
||||
newPolicies := testOptions(t)
|
||||
newPolicies.Policies = []config.Policy{
|
||||
newPolicy,
|
||||
}
|
||||
err := newPolicy.Validate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
badPolicyURL := config.Policy{To: "http://", From: "http://bar.example"}
|
||||
badNewPolicy := testOptions(t)
|
||||
badNewPolicy.Policies = []config.Policy{
|
||||
badPolicyURL,
|
||||
}
|
||||
disableTLSPolicy := config.Policy{To: "http://foo.example", From: "http://bar.example", TLSSkipVerify: true}
|
||||
disableTLSPolicies := testOptions(t)
|
||||
disableTLSPolicies.Policies = []config.Policy{
|
||||
disableTLSPolicy,
|
||||
}
|
||||
customCAPolicy := config.Policy{To: "http://foo.example", From: "http://bar.example", TLSCustomCA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lKQUszMmhoR0JIcmFtTUEwR0NTcUdTSWIzRFFFQkN3VUFNR0l4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIREExVFlXNGdSbkpoYm1OcApjMk52TVE4d0RRWURWUVFLREFaQ1lXUlRVMHd4RlRBVEJnTlZCQU1NRENvdVltRmtjM05zTG1OdmJUQWVGdzB4Ck9UQTJNVEl4TlRNeE5UbGFGdzB5TVRBMk1URXhOVE14TlRsYU1HSXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWUQKVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1ROHdEUVlEVlFRSwpEQVpDWVdSVFUwd3hGVEFUQmdOVkJBTU1EQ291WW1Ga2MzTnNMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1JRTdQaU03Z1RDczloUTFYQll6Sk1ZNjF5b2FFbXdJclg1bFo2eEt5eDIKUG16QVMyQk1UT3F5dE1BUGdMYXcrWExKaGdMNVhFRmRFeXQvY2NSTHZPbVVMbEEzcG1jY1lZejJRVUxGUnRNVwpoeWVmZE9zS25SRlNKaUZ6YklSTWVWWGswV3ZvQmoxSUZWS3RzeWpicXY5dS8yQ1ZTbmRyT2ZFazBURzIzVTNBCnhQeFR1VzFDcmJWOC9xNzFGZEl6U09jaWNjZkNGSHBzS09vM1N0L3FiTFZ5dEg1YW9oYmNhYkZYUk5zS0VxdmUKd3c5SGRGeEJJdUdhK1J1VDVxMGlCaWt1c2JwSkhBd25ucVA3aS9kQWNnQ3NrZ2paakZlRVU0RUZ5K2IrYTFTWQpRQ2VGeHhDN2MzRHZhUmhCQjBWVmZQbGtQejBzdzZsODY1TWFUSWJSeW9VQ0F3RUFBYU15TURBd0NRWURWUjBUCkJBSXdBREFqQmdOVkhSRUVIREFhZ2d3cUxtSmhaSE56YkM1amIyMkNDbUpoWkhOemJDNWpiMjB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFJaTV1OXc4bWdUNnBwQ2M3eHNHK0E5ZkkzVzR6K3FTS2FwaHI1bHM3MEdCS2JpWQpZTEpVWVpoUGZXcGgxcXRra1UwTEhGUG04M1ZhNTJlSUhyalhUMFZlNEt0TzFuMElBZkl0RmFXNjJDSmdoR1luCmp6dzByeXpnQzRQeUZwTk1uTnRCcm9QdS9iUGdXaU1nTE9OcEVaaGlneDRROHdmMVkvVTlzK3pDQ3hvSmxhS1IKTVhidVE4N1g3bS85VlJueHhvNk56NVpmN09USFRwTk9JNlZqYTBCeGJtSUFVNnlyaXc5VXJnaWJYZk9qM2o2bgpNVExCdWdVVklCMGJCYWFzSnNBTUsrdzRMQU52YXBlWjBET1NuT1I0S0syNEowT3lvRjVmSG1wNTllTTE3SW9GClFxQmh6cG1RVWd1bmVjRVc4QlRxck5wRzc5UjF1K1YrNHd3Y2tQYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}
|
||||
customCAPolicies := testOptions(t)
|
||||
customCAPolicies.Policies = []config.Policy{
|
||||
customCAPolicy,
|
||||
}
|
||||
badCustomCAPolicy := config.Policy{To: "http://foo.example", From: "http://bar.example", TLSCustomCA: "=@@"}
|
||||
badCustomCAPolicies := testOptions(t)
|
||||
badCustomCAPolicies.Policies = []config.Policy{
|
||||
badCustomCAPolicy,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
opts config.Options
|
||||
newPolicy []policy.Policy
|
||||
host string
|
||||
wantErr bool
|
||||
wantRoute bool
|
||||
name string
|
||||
originalOptions config.Options
|
||||
updatedOptions config.Options
|
||||
signingKey string
|
||||
host string
|
||||
wantErr bool
|
||||
wantRoute bool
|
||||
}{
|
||||
{"good", good, good.Policies, "https://corp.example.notatld", false, true},
|
||||
{"changed", good, newPolicies, "https://bar.notatld", false, true},
|
||||
{"changed and missing", good, newPolicies, "https://corp.example.notatld", false, false},
|
||||
{"bad options", bad, good.Policies, "https://corp.example.notatld", true, false},
|
||||
{"good no change", good, good, "", "https://corp.example.example", false, true},
|
||||
{"changed", good, newPolicies, "", "https://bar.example", false, true},
|
||||
{"changed and missing", good, newPolicies, "", "https://corp.example.example", false, false},
|
||||
// todo(bdd): not sure what intent of this test is?
|
||||
{"bad signing key", good, newPolicies, "^bad base 64", "https://corp.example.example", true, false},
|
||||
{"bad change bad policy url", good, badNewPolicy, "", "https://bar.example", true, false},
|
||||
// todo: stand up a test server using self signed certificates
|
||||
{"disable tls verification", good, disableTLSPolicies, "", "https://bar.example", false, true},
|
||||
{"custom root ca", good, customCAPolicies, "", "https://bar.example", false, true},
|
||||
{"bad custom root ca base64", good, badCustomCAPolicies, "", "https://bar.example", true, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := tt.opts
|
||||
p, _ := New(o)
|
||||
p, err := New(tt.originalOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
o.Policies = tt.newPolicy
|
||||
err := p.UpdateOptions(o)
|
||||
p.signingKey = tt.signingKey
|
||||
err = p.UpdateOptions(tt.updatedOptions)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("UpdateOptions: err = %v, wantErr = %v", err, tt.wantErr)
|
||||
return
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue