mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 10:56:28 +02:00
Add automatic configuration reloading and
policy handling
This commit is contained in:
parent
77f3933560
commit
8c2beac6f1
12 changed files with 287 additions and 34 deletions
|
@ -5,6 +5,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/config"
|
"github.com/pomerium/pomerium/internal/config"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/policy"
|
"github.com/pomerium/pomerium/internal/policy"
|
||||||
|
@ -63,3 +65,10 @@ func NewIdentityWhitelist(policies []policy.Policy, admins []string) IdentityVal
|
||||||
func (a *Authorize) ValidIdentity(route string, identity *Identity) bool {
|
func (a *Authorize) ValidIdentity(route string, identity *Identity) bool {
|
||||||
return a.identityAccess.Valid(route, identity)
|
return a.identityAccess.Valid(route, identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateOptions updates internal structres based on config.Options
|
||||||
|
func (a *Authorize) UpdateOptions(o *config.Options) error {
|
||||||
|
log.Info().Msg("authorize: updating options")
|
||||||
|
a.identityAccess = NewIdentityWhitelist(o.Policies, o.Administrators)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,11 +10,7 @@ import (
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
goodPolicy := policy.Policy{From: "pomerium.io", To: "httpbin.org"}
|
policies := testPolicies()
|
||||||
goodPolicy.Validate()
|
|
||||||
policies := []policy.Policy{
|
|
||||||
goodPolicy,
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -46,3 +42,50 @@ func TestNew(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testPolicies() []policy.Policy {
|
||||||
|
testPolicy := policy.Policy{From: "pomerium.io", To: "httpbin.org", AllowedEmails: []string{"test@gmail.com"}}
|
||||||
|
testPolicy.Validate()
|
||||||
|
policies := []policy.Policy{
|
||||||
|
testPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
return policies
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UpdateOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
policies := testPolicies()
|
||||||
|
newPolicy := policy.Policy{From: "foo.notatld", To: "bar.notatld", AllowedEmails: []string{"test@gmail.com"}}
|
||||||
|
newPolicy.Validate()
|
||||||
|
newPolicies := []policy.Policy{
|
||||||
|
newPolicy,
|
||||||
|
}
|
||||||
|
identity := &Identity{Email: "test@gmail.com"}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
SharedKey string
|
||||||
|
Policies []policy.Policy
|
||||||
|
newPolices []policy.Policy
|
||||||
|
route string
|
||||||
|
wantAllowed bool
|
||||||
|
}{
|
||||||
|
{"good", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, policies, "pomerium.io", true},
|
||||||
|
{"changed", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, newPolicies, "foo.notatld", true},
|
||||||
|
{"changed and missing", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, newPolicies, "pomerium.io", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
o := &config.Options{SharedKey: tt.SharedKey, Policies: tt.Policies}
|
||||||
|
authorize, _ := New(o)
|
||||||
|
o.Policies = tt.newPolices
|
||||||
|
authorize.UpdateOptions(o)
|
||||||
|
|
||||||
|
allowed := authorize.ValidIdentity(tt.route, identity)
|
||||||
|
if allowed != tt.wantAllowed {
|
||||||
|
t.Errorf("New() allowed = %v, wantAllowed %v", allowed, tt.wantAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authenticate"
|
"github.com/pomerium/pomerium/authenticate"
|
||||||
"github.com/pomerium/pomerium/authorize"
|
"github.com/pomerium/pomerium/authorize"
|
||||||
"github.com/pomerium/pomerium/internal/config"
|
"github.com/pomerium/pomerium/internal/config"
|
||||||
|
@ -21,6 +20,8 @@ import (
|
||||||
pbAuthenticate "github.com/pomerium/pomerium/proto/authenticate"
|
pbAuthenticate "github.com/pomerium/pomerium/proto/authenticate"
|
||||||
pbAuthorize "github.com/pomerium/pomerium/proto/authorize"
|
pbAuthorize "github.com/pomerium/pomerium/proto/authorize"
|
||||||
"github.com/pomerium/pomerium/proxy"
|
"github.com/pomerium/pomerium/proxy"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionFlag = flag.Bool("version", false, "prints the version")
|
var versionFlag = flag.Bool("version", false, "prints the version")
|
||||||
|
@ -48,15 +49,24 @@ func main() {
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: authenticate")
|
log.Fatal().Err(err).Msg("cmd/pomerium: authenticate")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = newAuthorizeService(opt, grpcServer)
|
authz, err := newAuthorizeService(opt, grpcServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: authorize")
|
log.Fatal().Err(err).Msg("cmd/pomerium: authorize")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = newProxyService(opt, mux)
|
proxy, err := newProxyService(opt, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: proxy")
|
log.Fatal().Err(err).Msg("cmd/pomerium: proxy")
|
||||||
}
|
}
|
||||||
|
go viper.WatchConfig()
|
||||||
|
|
||||||
|
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||||
|
log.Info().
|
||||||
|
Str("file", e.Name).
|
||||||
|
Msg("cmd/pomerium: configuration file changed")
|
||||||
|
|
||||||
|
opt = handleConfigUpdate(opt, []config.OptionsUpdater{authz, proxy})
|
||||||
|
})
|
||||||
// defer statements ignored anyway : https://stackoverflow.com/a/17888654
|
// defer statements ignored anyway : https://stackoverflow.com/a/17888654
|
||||||
// defer proxyService.AuthenticateClient.Close()
|
// defer proxyService.AuthenticateClient.Close()
|
||||||
// defer proxyService.AuthorizeClient.Close()
|
// defer proxyService.AuthorizeClient.Close()
|
||||||
|
@ -181,3 +191,40 @@ func parseOptions(configFile string) (*config.Options, error) {
|
||||||
}
|
}
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleConfigUpdate(opt *config.Options, services []config.OptionsUpdater) *config.Options {
|
||||||
|
newOpt, err := parseOptions(*configFile)
|
||||||
|
optChecksum := opt.Checksum()
|
||||||
|
newOptChecksum := newOpt.Checksum()
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("old-checksum", optChecksum).
|
||||||
|
Str("new-checksum", newOptChecksum).
|
||||||
|
Msg("cmd/pomerium: configuration file changed")
|
||||||
|
|
||||||
|
if newOptChecksum == optChecksum {
|
||||||
|
log.Debug().Msg("cmd/pomerium: loaded configuration has not changed")
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("cmd/pomerium: could not reload configuration")
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("config-checksum", newOptChecksum).
|
||||||
|
Msg("cmd/pomerium: running configuration has changed")
|
||||||
|
for _, service := range services {
|
||||||
|
err := service.UpdateOptions(newOpt)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("cmd/pomerium: could not update options")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpt
|
||||||
|
}
|
||||||
|
|
|
@ -241,3 +241,45 @@ func Test_parseOptions(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockService struct {
|
||||||
|
fail bool
|
||||||
|
Updated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockService) UpdateOptions(o *config.Options) error {
|
||||||
|
|
||||||
|
m.Updated = true
|
||||||
|
if m.fail {
|
||||||
|
return fmt.Errorf("Failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_handleConfigUpdate(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("SHARED_SECRET", "foo")
|
||||||
|
defer os.Unsetenv("SHARED_SECRET")
|
||||||
|
|
||||||
|
blankOpts := config.NewOptions()
|
||||||
|
goodOpts, _ := config.OptionsFromViper("")
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
service *mockService
|
||||||
|
oldOpts *config.Options
|
||||||
|
wantUpdate bool
|
||||||
|
}{
|
||||||
|
{"good", &mockService{fail: false}, blankOpts, true},
|
||||||
|
{"bad", &mockService{fail: true}, blankOpts, true},
|
||||||
|
{"no change", &mockService{fail: false}, goodOpts, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
handleConfigUpdate(tt.oldOpts, []config.OptionsUpdater{tt.service})
|
||||||
|
if tt.service.Updated != tt.wantUpdate {
|
||||||
|
t.Errorf("Failed to update config on service")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ If you are coming from a kubernetes or docker background this should feel famili
|
||||||
|
|
||||||
In general, any setting specified by environment variable can also be present in the optional config file as the same name but lower cased. Environment variables take precedence.
|
In general, any setting specified by environment variable can also be present in the optional config file as the same name but lower cased. Environment variables take precedence.
|
||||||
|
|
||||||
|
Pomerium will automatically reload the configuration file if it is changed. At this time, only policy is re-configured when this reload occurs, but additional options may be added in the future. It is suggested that your policy is stored in a configuration file so that you can take advantage of this feature.
|
||||||
|
|
||||||
## Global settings
|
## Global settings
|
||||||
|
|
||||||
These are configuration variables shared by all services, in all service modes.
|
These are configuration variables shared by all services, in all service modes.
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -3,10 +3,12 @@ module github.com/pomerium/pomerium
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/golang/mock v1.2.0
|
github.com/golang/mock v1.2.0
|
||||||
github.com/golang/protobuf v1.3.1
|
github.com/golang/protobuf v1.3.1
|
||||||
github.com/google/go-cmp v0.3.0
|
github.com/google/go-cmp v0.3.0
|
||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
|
github.com/mitchellh/hashstructure v1.0.0
|
||||||
github.com/pomerium/go-oidc v2.0.0+incompatible
|
github.com/pomerium/go-oidc v2.0.0+incompatible
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
github.com/rs/zerolog v1.14.3
|
github.com/rs/zerolog v1.14.3
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -39,6 +39,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||||
|
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
|
|
@ -10,11 +10,11 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"github.com/mitchellh/hashstructure"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/policy"
|
"github.com/pomerium/pomerium/internal/policy"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DisableHeaderKey is the key used to check whether to disable setting header
|
// DisableHeaderKey is the key used to check whether to disable setting header
|
||||||
|
@ -70,8 +70,8 @@ type Options struct {
|
||||||
|
|
||||||
// AuthenticateURL represents the externally accessible http endpoints
|
// AuthenticateURL represents the externally accessible http endpoints
|
||||||
// used for authentication requests and callbacks
|
// used for authentication requests and callbacks
|
||||||
AuthenticateURLString string `mapstructure:"authenticate_service_url"`
|
AuthenticateURLString string `mapstructure:"authenticate_service_url"`
|
||||||
AuthenticateURL *url.URL
|
AuthenticateURL *url.URL `hash:"ignore"`
|
||||||
|
|
||||||
// Session/Cookie management
|
// Session/Cookie management
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||||
|
@ -172,29 +172,32 @@ func OptionsFromViper(configFile string) (*Options, error) {
|
||||||
// Load up config
|
// Load up config
|
||||||
o.bindEnvs()
|
o.bindEnvs()
|
||||||
if configFile != "" {
|
if configFile != "" {
|
||||||
log.Info().Msgf("Loading config from '%s'", configFile)
|
log.Info().
|
||||||
|
Str("file", configFile).
|
||||||
|
Msg("loading config from file")
|
||||||
|
|
||||||
viper.SetConfigFile(configFile)
|
viper.SetConfigFile(configFile)
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to read config: %s", err)
|
return nil, fmt.Errorf("failed to read config: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := viper.Unmarshal(o)
|
err := viper.Unmarshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to load options from config: %s", err)
|
return nil, fmt.Errorf("failed to load options from config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn URL strings into url structs
|
// Turn URL strings into url structs
|
||||||
err = o.parseURLs()
|
err = o.parseURLs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to parse URLs: %s", err)
|
return nil, fmt.Errorf("failed to parse URLs: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load and initialize policy
|
// Load and initialize policy
|
||||||
err = o.parsePolicy()
|
err = o.parsePolicy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to parse Policy: %s", err)
|
return nil, fmt.Errorf("failed to parse Policy: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Debug {
|
if o.Debug {
|
||||||
|
@ -212,6 +215,9 @@ func OptionsFromViper(configFile string) (*Options, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("config-checksum", o.Checksum()).
|
||||||
|
Msg("read configuration with checksum")
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,3 +388,19 @@ func IsProxy(s string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptionsUpdater updates local state based on an Options struct
|
||||||
|
type OptionsUpdater interface {
|
||||||
|
UpdateOptions(*Options) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checksum returns the checksum of the current options struct
|
||||||
|
func (o *Options) Checksum() string {
|
||||||
|
hash, err := hashstructure.Hash(o, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Msg("could not calculate Option checksum")
|
||||||
|
return "no checksum availablle"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", hash)
|
||||||
|
}
|
||||||
|
|
|
@ -355,3 +355,23 @@ func Test_parsePolicyFile(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Checksum(t *testing.T) {
|
||||||
|
o := NewOptions()
|
||||||
|
|
||||||
|
oldChecksum := o.Checksum()
|
||||||
|
o.SharedKey = "changemeplease"
|
||||||
|
newChecksum := o.Checksum()
|
||||||
|
|
||||||
|
if newChecksum == oldChecksum {
|
||||||
|
t.Errorf("Checksum() failed to update old = %s, new = %s", oldChecksum, newChecksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newChecksum == "" || oldChecksum == "" {
|
||||||
|
t.Error("Checksum() not returning data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Checksum() != o.Checksum() {
|
||||||
|
t.Error("Checksum() inconsistent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -423,14 +423,6 @@ func (p *Proxy) authenticate(w http.ResponseWriter, r *http.Request, session *se
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle constructs a route from the given host string and matches it to the provided http.Handler and UpstreamConfig
|
|
||||||
func (p *Proxy) Handle(host string, handler http.Handler, pol *policy.Policy) {
|
|
||||||
p.routeConfigs[host] = &routeConfig{
|
|
||||||
mux: handler,
|
|
||||||
policy: *pol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// router attempts to find a route for a request. If a route is successfully matched,
|
// 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,
|
// 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.
|
// a nil value for the route and false bool value is returned.
|
||||||
|
|
|
@ -148,14 +148,9 @@ func New(opts *config.Options) (*Proxy, error) {
|
||||||
refreshCooldown: opts.RefreshCooldown,
|
refreshCooldown: opts.RefreshCooldown,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range opts.Policies {
|
err = p.UpdatePolicies(opts)
|
||||||
proxy := NewReverseProxy(route.Destination)
|
if err != nil {
|
||||||
handler, err := NewReverseProxyHandler(opts, proxy, &route)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.Handle(route.Source.Host, handler, &route)
|
|
||||||
log.Info().Str("src", route.Source.Host).Str("dst", route.Destination.Host).Msg("proxy: new route")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.AuthenticateClient, err = clients.NewAuthenticateClient("grpc",
|
p.AuthenticateClient, err = clients.NewAuthenticateClient("grpc",
|
||||||
|
@ -181,6 +176,25 @@ func New(opts *config.Options) (*Proxy, error) {
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePolicies updates the handlers based on the configured policies
|
||||||
|
func (p *Proxy) UpdatePolicies(opts *config.Options) error {
|
||||||
|
routeConfigs := make(map[string]*routeConfig)
|
||||||
|
for _, route := range opts.Policies {
|
||||||
|
proxy := NewReverseProxy(route.Destination)
|
||||||
|
handler, err := NewReverseProxyHandler(opts, proxy, &route)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
routeConfigs[route.Source.Host] = &routeConfig{
|
||||||
|
mux: handler,
|
||||||
|
policy: route,
|
||||||
|
}
|
||||||
|
log.Info().Str("src", route.Source.Host).Str("dst", route.Destination.Host).Msg("proxy: new route")
|
||||||
|
}
|
||||||
|
p.routeConfigs = routeConfigs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpstreamProxy stores information for proxying the request to the upstream.
|
// UpstreamProxy stores information for proxying the request to the upstream.
|
||||||
type UpstreamProxy struct {
|
type UpstreamProxy struct {
|
||||||
name string
|
name string
|
||||||
|
@ -270,3 +284,13 @@ func urlParse(uri string) (*url.URL, error) {
|
||||||
}
|
}
|
||||||
return url.ParseRequestURI(uri)
|
return url.ParseRequestURI(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateOptions updates internal structres 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
|
||||||
|
}
|
||||||
|
|
|
@ -283,3 +283,51 @@ func TestProxy_OAuthCallback(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{
|
||||||
|
newPolicy,
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
opts *config.Options
|
||||||
|
newPolicy []policy.Policy
|
||||||
|
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},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
o := tt.opts
|
||||||
|
p, _ := New(o)
|
||||||
|
|
||||||
|
o.Policies = tt.newPolicy
|
||||||
|
err := p.UpdateOptions(o)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("UpdateOptions: err = %v, wantErr = %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is only safe if we actually can load policies
|
||||||
|
if err == nil {
|
||||||
|
req := httptest.NewRequest("GET", tt.host, nil)
|
||||||
|
_, ok := p.router(req)
|
||||||
|
if ok != tt.wantRoute {
|
||||||
|
t.Errorf("Failed to find route handler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue