diff --git a/internal/devproxy/dev_config.go b/internal/devproxy/dev_config.go index 1e09638b..7400c322 100644 --- a/internal/devproxy/dev_config.go +++ b/internal/devproxy/dev_config.go @@ -55,16 +55,27 @@ type UpstreamConfig struct { HeaderOverrides map[string]string TLSSkipVerify bool SkipRequestSigning bool + User string + Groups string + Email string } // RouteConfig maps to the yaml config fields, // * "from" - the domain that will be used to access the service // * "to" - the cname of the proxied service (this tells sso proxy where to proxy requests that come in on the from field) type RouteConfig struct { - From string `yaml:"from"` - To string `yaml:"to"` - Type string `yaml:"type"` - Options *OptionsConfig `yaml:"options"` + From string `yaml:"from"` + To string `yaml:"to"` + Type string `yaml:"type"` + Options *OptionsConfig `yaml:"options"` + UserInfo *UserInfo `yaml:"user_info"` +} + +//UserInfo is going to be injected into the header +type UserInfo struct { + User string `yaml:"user"` + Groups string `yaml:"groups"` + Email string `yaml:"email"` } // OptionsConfig maps to the yaml config fields: @@ -177,6 +188,10 @@ func loadServiceConfigs(raw []byte, cluster, scheme string, configVars map[strin if err != nil { return nil, err } + err = parseUserInfoConfig(proxy) + if err != nil { + return nil, err + } } for _, proxy := range configs { @@ -353,6 +368,18 @@ func parseOptionsConfig(proxy *UpstreamConfig) error { return nil } +func parseUserInfoConfig(proxy *UpstreamConfig) error { + if proxy.RouteConfig.UserInfo == nil { + return nil + } + + proxy.User = proxy.RouteConfig.UserInfo.User + proxy.Groups = proxy.RouteConfig.UserInfo.Groups + proxy.Email = proxy.RouteConfig.UserInfo.Email + + return nil +} + func cleanWhiteSpace(s string) string { // This trims all white space from a service name and collapses all remaining space to `_` return space.ReplaceAllString(strings.TrimSpace(s), "_") // diff --git a/internal/devproxy/devproxy.go b/internal/devproxy/devproxy.go index da9f1b9f..43ac0d92 100644 --- a/internal/devproxy/devproxy.go +++ b/internal/devproxy/devproxy.go @@ -23,23 +23,30 @@ const HMACSignatureHeader = "Gap-Signature" // SignatureHeaders are the headers that are valid in the request. var SignatureHeaders = []string{ + "Content-Length", + "Content-Md5", + "Content-Type", + "Date", + "Authorization", "X-Forwarded-User", "X-Forwarded-Email", "X-Forwarded-Groups", - "X-Forwarded-Access-Token", + "Cookie", } const statusInvalidHost = 421 // DevProxy stores all the information associated with proxying the request. type DevProxy struct { - // redirectURL *url.URL // the url to receive requests at skipAuthPreflight bool templates *template.Template mux map[string]*route regexRoutes []*route requestSigner *RequestSigner publicCertsJSON []byte + user string + groups string + email string } type route struct { @@ -61,6 +68,7 @@ type StateParameter struct { type UpstreamProxy struct { name string handler http.Handler + auth hmacauth.HmacAuth requestSigner *RequestSigner } @@ -104,9 +112,10 @@ func newUpstreamTransport(insecureSkipVerify bool) *upstreamTransport { // ServeHTTP calls the upstream's ServeHTTP function. func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { - + if u.auth != nil { + u.auth.SignRequest(r) + } if u.requestSigner != nil { - u.requestSigner.Sign(r) } @@ -157,6 +166,7 @@ func NewReverseProxy(to *url.URL, config *UpstreamConfig) *httputil.ReverseProxy dir(req) req.Host = to.Host } + return proxy } @@ -194,6 +204,7 @@ func NewReverseProxyHandler(reverseProxy *httputil.ReverseProxy, opts *Options, upstreamProxy := &UpstreamProxy{ name: config.Service, handler: reverseProxy, + auth: config.HMACAuth, requestSigner: signer, } @@ -274,10 +285,8 @@ func NewDevProxy(opts *Options, optFuncs ...func(*DevProxy) error) (*DevProxy, e p := &DevProxy{ // these fields make up the routing mechanism - mux: make(map[string]*route), - regexRoutes: make([]*route, 0), - - // redirectURL: &url.URL{Path: "/oauth2/callback"}, + mux: make(map[string]*route), + regexRoutes: make([]*route, 0), templates: getTemplates(), requestSigner: requestSigner, publicCertsJSON: certsAsStr, @@ -289,8 +298,10 @@ func NewDevProxy(opts *Options, optFuncs ...func(*DevProxy) error) (*DevProxy, e return nil, err } } - for _, upstreamConfig := range opts.upstreamConfigs { + p.user = upstreamConfig.User + p.email = upstreamConfig.Email + p.groups = upstreamConfig.Groups switch route := upstreamConfig.Route.(type) { case *SimpleRoute: reverseProxy := NewReverseProxy(route.ToURL, upstreamConfig) @@ -419,6 +430,7 @@ func (p *DevProxy) UnknownHost(rw http.ResponseWriter, req *http.Request) { // Handle constructs a route from the given host string and matches it to the provided http.Handler and UpstreamConfig func (p *DevProxy) Handle(host string, handler http.Handler, tags []string, upstreamConfig *UpstreamConfig) { + tags = append(tags, "route:simple") p.mux[host] = &route{handler: handler, upstreamConfig: upstreamConfig, tags: tags} } @@ -430,9 +442,9 @@ func (p *DevProxy) HandleRegex(regex *regexp.Regexp, handler http.Handler, tags } func (p *DevProxy) setProxyHeaders(rw http.ResponseWriter, req *http.Request) (err error) { - req.Header.Set("X-Forwarded-User", req.Header.Get("User")) - req.Header.Set("X-Forwarded-Email", req.Header.Get("Email")) - req.Header.Set("X-Forwarded-Groups", req.Header.Get("groups")) + req.Header.Set("X-Forwarded-User", p.user) + req.Header.Set("X-Forwarded-Email", p.email) + req.Header.Set("X-Forwarded-Groups", p.groups) // req.Header.set("X-Forwarded-Access-Token", "") return nil } diff --git a/internal/devproxy/options.go b/internal/devproxy/options.go index eaf279fa..00c5c367 100644 --- a/internal/devproxy/options.go +++ b/internal/devproxy/options.go @@ -18,26 +18,17 @@ import ( // TCPWriteTimeout - http server tcp write timeout // TCPReadTimeout - http server tcp read timeout type Options struct { - Port int `envconfig:"PORT" default:"4180"` - - UpstreamConfigsFile string `envconfig:"UPSTREAM_CONFIGS"` - Cluster string `envconfig:"CLUSTER"` - Scheme string `envconfig:"SCHEME" default:"https"` - - Host string `envconfig:"HOST"` - + Port int `envconfig:"PORT" default:"4180"` + UpstreamConfigsFile string `envconfig:"UPSTREAM_CONFIGS"` + Scheme string `envconfig:"SCHEME" default:"https"` + Host string `envconfig:"HOST"` DefaultUpstreamTimeout time.Duration `envconfig:"DEFAULT_UPSTREAM_TIMEOUT" default:"10s"` - - TCPWriteTimeout time.Duration `envconfig:"TCP_WRITE_TIMEOUT" default:"30s"` - TCPReadTimeout time.Duration `envconfig:"TCP_READ_TIMEOUT" default:"30s"` - - RequestLogging bool `envconfig:"REQUEST_LOGGING" default:"true"` - - RequestSigningKey string `envconfig:"REQUEST_SIGNATURE_KEY"` - + TCPWriteTimeout time.Duration `envconfig:"TCP_WRITE_TIMEOUT" default:"30s"` + TCPReadTimeout time.Duration `envconfig:"TCP_READ_TIMEOUT" default:"30s"` + RequestLogging bool `envconfig:"REQUEST_LOGGING" default:"true"` + RequestSigningKey string `envconfig:"REQUEST_SIGNATURE_KEY"` // This is an override for supplying template vars at test time testTemplateVars map[string]string - // internal values that are set after config validation upstreamConfigs []*UpstreamConfig } @@ -62,11 +53,10 @@ func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string // Validate validates options func (o *Options) Validate() error { msgs := make([]string, 0) - if o.Cluster == "" { - msgs = append(msgs, "missing setting: cluster") - } + if o.UpstreamConfigsFile == "" { msgs = append(msgs, "missing setting: upstream-configs") + o.UpstreamConfigsFile = "internal/devproxy/testdata/upstream_configs.yml" } if o.UpstreamConfigsFile != "" { @@ -80,7 +70,7 @@ func (o *Options) Validate() error { templateVars = o.testTemplateVars } - o.upstreamConfigs, err = loadServiceConfigs(rawBytes, o.Cluster, o.Scheme, templateVars) + o.upstreamConfigs, err = loadServiceConfigs(rawBytes, "default", o.Scheme, templateVars) if err != nil { msgs = append(msgs, fmt.Sprintf("error parsing upstream configs file %s", err)) } diff --git a/internal/devproxy/request_signer.go b/internal/devproxy/request_signer.go index 323830cc..7184a35e 100644 --- a/internal/devproxy/request_signer.go +++ b/internal/devproxy/request_signer.go @@ -27,6 +27,7 @@ var signedHeaders = []string{ "X-Forwarded-User", "X-Forwarded-Email", "X-Forwarded-Groups", + "Cookie", } // Name of the header used to transmit the signature computed for the request. diff --git a/internal/devproxy/testdata/.env b/internal/devproxy/testdata/.env index 91c839bb..9e9b4a15 100644 --- a/internal/devproxy/testdata/.env +++ b/internal/devproxy/testdata/.env @@ -1,10 +1,11 @@ export PORT=4888 +export UPSTREAM_CONFIGS=/path/to/upstream_configs.yml export SCHEME=http export HOST=http://localhost/ -export UPSTREAM_CONFIGS=/path/to/upstream_configs.yml export CLUSTER=sso-dev export DEFAULT_UPSTREAM_TIMEOUT=10s export TCP_WRITE_TIMEOUT=30s export TCP_READ_TIMEOUT=30s export REQUEST_LOGGING=true -export REQUEST_SIGNATURE_KEY=$(cat /path/to/devproxy/testdata/private_key.pem) +export REQUEST_SIGNATURE_KEY=$(cat /path/to/private_key.pem) +export DEV_CONFIG_DEVSHIM_SIGNING_KEY="sha256:shared-secret-value" diff --git a/internal/devproxy/testdata/upstream_configs.yml b/internal/devproxy/testdata/upstream_configs.yml index a2ad2e32..8c8b4767 100644 --- a/internal/devproxy/testdata/upstream_configs.yml +++ b/internal/devproxy/testdata/upstream_configs.yml @@ -1,8 +1,11 @@ -- service: dev-shim +- service: devshim default: from: http://localhost:4888 to: http://localhost:4810 options: skip_request_signing: false - + user_info: + user: testUser + groups: team + email: testtest@remitly.com