proxy: make http headers configurable (#108)

- http headers can be disabled via an env config
- http headers can be configured by k/v map env config
- pomerium/envconfig updated to use original syntax v1.5.0
- go.mod / go.sum patches updated
This commit is contained in:
Bobby DeSimone 2019-05-07 12:05:25 -07:00 committed by GitHub
parent 0086fa05f8
commit 5e37c29dfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 43 deletions

View file

@ -33,7 +33,6 @@ Service mode sets the pomerium service(s) to run. If testing, you may want to se
Address specifies the host and port to serve HTTPS and gRPC requests from. If empty, `:https`/`:443` is used.
### HTTP Redirect Address
- Environmental Variable: `HTTP_REDIRECT_ADDR`
@ -41,7 +40,7 @@ Address specifies the host and port to serve HTTPS and gRPC requests from. If em
- Example: `:80`, `:http`, `:8080`
- Optional
If set, the HTTP Redirect Address specifies the host and port to redirect http to https traffic on. If unset, no redirect server is started.
If set, the HTTP Redirect Address specifies the host and port to redirect http to https traffic on. If unset, no redirect server is started.
### Shared Secret
@ -206,7 +205,7 @@ Authenticate Service URL is the externally accessible URL for the authenticate s
- Optional
- Example: `pomerium-authenticate-service.pomerium.svc.cluster.local`
Authenticate Internal Service URL is the internally routed dns name of the authenticate service. This setting is typically used with load balancers that do not gRPC, thus allowing you to specify an internally accessible name.
Authenticate Internal Service URL is the internally routed dns name of the authenticate service. This setting is typically used with load balancers that do not gRPC, thus allowing you to specify an internally accessible name.
### Authorize Service URL
@ -215,9 +214,9 @@ Authenticate Internal Service URL is the internally routed dns name of the authe
- Required
- Example: `https://access.corp.example.com` or `pomerium-authorize-service.pomerium.svc.cluster.local`
Authorize Service URL is the location of the internally accessible authorize service. NOTE: Unlike authenticate, authorize has no publicly accessible http handlers so this setting is purely for gRPC communication.
Authorize Service URL is the location of the internally accessible authorize service. NOTE: Unlike authenticate, authorize has no publicly accessible http handlers so this setting is purely for gRPC communication.
If your load balancer does not support gRPC pass-through you'll need to set this value to an internally routable location (`pomerium-authorize-service.pomerium.svc.cluster.local`) instead of an externally routable one (`https://access.corp.example.com`).
If your load balancer does not support gRPC pass-through you'll need to set this value to an internally routable location (`pomerium-authorize-service.pomerium.svc.cluster.local`) instead of an externally routable one (`https://access.corp.example.com`).
### Override Certificate Name
@ -236,6 +235,19 @@ When Authenticate Internal Service Address is set, secure service communication
Certificate Authority is set when behind-the-ingress service communication uses self-signed certificates. Be sure to include the intermediary certificate.
### Headers
- Environmental Variable: `HEADERS`
- Type: map of `strings` key value pairs
- Example: `X-Content-Type-Options:nosniff,X-Frame-Options:SAMEORIGIN`
- To disable: `disable:true`
Headers specifies a mapping of [HTTP Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) to be added to proxied requests. *Nota bene* Downstream application headers will be overwritten by Pomerium's headers on conflict.
By default, conservative [secure HTTP headers](https://www.owasp.org/index.php/OWASP_Secure_Headers_Project) are set.
![pomerium security headers](./security-headers.png)
[base64 encoded]: https://en.wikipedia.org/wiki/Base64
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
[identity provider]: ./identity-providers.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

9
go.mod
View file

@ -5,8 +5,8 @@ go 1.12
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/mock v1.2.0
github.com/golang/protobuf v1.3.0
github.com/pomerium/envconfig v1.4.0
github.com/golang/protobuf v1.3.1
github.com/pomerium/envconfig v1.5.0
github.com/pomerium/go-oidc v2.0.0+incompatible
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/rs/zerolog v1.12.0
@ -14,8 +14,9 @@ require (
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
golang.org/x/text v0.3.2 // indirect
google.golang.org/api v0.1.0
google.golang.org/grpc v1.19.0
gopkg.in/square/go-jose.v2 v2.3.0
google.golang.org/grpc v1.19.1
gopkg.in/square/go-jose.v2 v2.3.1
gopkg.in/yaml.v2 v2.2.2
)

19
go.sum
View file

@ -17,8 +17,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -26,8 +26,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pomerium/envconfig v1.4.0 h1:o+WY/E/9M4fh0nDX7oJodU7N9p1hcHPsTnNLYjlbQA8=
github.com/pomerium/envconfig v1.4.0/go.mod h1:1Kz8Ca8PhJDtLYqgvbDZGn6GsJCvrT52SxQ3sPNJkDc=
github.com/pomerium/envconfig v1.5.0 h1:OeYS/p6AUxKFqCZHM5BG7pUb0m3MkaC1ZhRLPTHbk8g=
github.com/pomerium/envconfig v1.5.0/go.mod h1:1Kz8Ca8PhJDtLYqgvbDZGn6GsJCvrT52SxQ3sPNJkDc=
github.com/pomerium/go-oidc v2.0.0+incompatible h1:gVvG/ExWsHQqatV+uceROnGmbVYF44mDNx5nayBhC0o=
github.com/pomerium/go-oidc v2.0.0+incompatible/go.mod h1:DRsGVw6MOgxbfq4Y57jKOE8lbEfayxeiY0A8/4vxjBM=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
@ -67,7 +67,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
@ -83,12 +86,12 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.3.0 h1:nLzhkFyl5bkblqYBoiWJUt5JkWOzmiaBtCxdJAqJd3U=
gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -1,23 +1,23 @@
- from: httpbin.corp.beyondperimeter.com
to: http://httpbin
allowed_domains:
- pomerium.io
- pomerium.io
- from: external-httpbin.corp.beyondperimeter.com
to: httpbin.org
allowed_domains:
- gmail.com
- gmail.com
- from: weirdlyssl.corp.beyondperimeter.com
to: http://neverssl.com
allowed_users:
- bdd@pomerium.io
- bdd@pomerium.io
allowed_groups:
- admins
- developers
- admins
- developers
- from: hello.corp.beyondperimeter.com
to: http://hello:8080
allowed_groups:
- admins
- from: cross-origin.corp.beyondperimeter.com
- admins
- from: cross-origin.corp.beyondperimeter.com
to: httpbin.org
allowed_domains:
- gmail.com

View file

@ -24,13 +24,6 @@ var (
ErrUserNotAuthorized = errors.New("user not authorized")
)
var securityHeaders = map[string]string{
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "SAMEORIGIN",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", // 1 year
}
// StateParameter holds the redirect id along with the session id.
type StateParameter struct {
SessionID string `json:"session_id"`
@ -63,7 +56,7 @@ func (p *Proxy) Handler() http.Handler {
Str("pomerium-email", r.Header.Get(HeaderEmail)).
Msg("proxy: request")
}))
c = c.Append(middleware.SetHeaders(securityHeaders))
c = c.Append(middleware.SetHeaders(p.headers))
c = c.Append(middleware.ForwardedAddrHandler("fwd_ip"))
c = c.Append(middleware.RemoteAddrHandler("ip"))
c = c.Append(middleware.UserAgentHandler("user_agent"))

View file

@ -31,6 +31,8 @@ const (
HeaderEmail = "x-pomerium-authenticated-user-email"
// HeaderGroups is the header key containing the user's groups.
HeaderGroups = "x-pomerium-authenticated-user-groups"
// DisableHeaderKey is the key used to check whether to disable setting header
DisableHeaderKey = "disable"
)
// Options represents the configurations available for the proxy service.
@ -70,6 +72,9 @@ type Options struct {
CookieExpire time.Duration `envconfig:"COOKIE_EXPIRE"`
CookieRefresh time.Duration `envconfig:"COOKIE_REFRESH"`
// Headers to set on all proxied requests. Add a 'disable' key map to turn off.
Headers map[string]string `envconfig:"HEADERS"`
// Sub-routes
Routes map[string]string `envconfig:"ROUTES"`
DefaultUpstreamTimeout time.Duration `envconfig:"DEFAULT_UPSTREAM_TIMEOUT"`
@ -83,6 +88,12 @@ var defaultOptions = &Options{
CookieExpire: time.Duration(14) * time.Hour,
CookieRefresh: time.Duration(30) * time.Minute,
DefaultUpstreamTimeout: time.Duration(30) * time.Second,
Headers: map[string]string{
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "SAMEORIGIN",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
},
}
// OptionsFromEnvConfig builds the identity provider service's configuration
@ -175,6 +186,7 @@ type Proxy struct {
redirectURL *url.URL
templates *template.Template
routeConfigs map[string]*routeConfig
headers map[string]string
}
type routeConfig struct {
@ -212,6 +224,12 @@ func New(opts *Options) (*Proxy, error) {
return nil, err
}
// if the disable key is found in the security header map, clear the map
if _, disable := opts.Headers[DisableHeaderKey]; disable {
opts.Headers = make(map[string]string)
}
log.Debug().Interface("headers", opts.Headers).Msg("proxy: security headers")
p := &Proxy{
routeConfigs: make(map[string]*routeConfig),
// services
@ -223,6 +241,7 @@ func New(opts *Options) (*Proxy, error) {
SharedKey: opts.SharedKey,
redirectURL: &url.URL{Path: "/.pomerium/callback"},
templates: templates.New(),
headers: opts.Headers,
}
var policies []policy.Policy
if opts.Policy != "" {

View file

@ -124,6 +124,7 @@ func testOptions() *Options {
SharedKey: "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=",
CookieSecret: "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=",
CookieName: "pomerium",
Headers: defaultOptions.Headers,
}
}
@ -205,20 +206,23 @@ func TestNew(t *testing.T) {
shortCookieLength.CookieSecret = "gN3xnvfsAwfCXxnJorGLKUG4l2wC8sS8nfLMhcStPg=="
badRoutedProxy := testOptions()
badRoutedProxy.SigningKey = "YmFkIGtleQo="
disableHeaders := testOptions()
disableHeaders.Headers = map[string]string{"disable": "true"}
tests := []struct {
name string
opts *Options
optFuncs []func(*Proxy) error
wantProxy bool
numRoutes int
wantErr bool
name string
opts *Options
wantProxy bool
numRoutes int
wantErr bool
numHeaders int
}{
{"good", good, nil, true, 1, false},
{"empty options", &Options{}, nil, false, 0, true},
{"nil options", nil, nil, false, 0, true},
{"short secret/validate sanity check", shortCookieLength, nil, false, 0, true},
{"invalid ec key, valid base64 though", badRoutedProxy, nil, false, 0, true},
{"good", good, true, 1, false, len(defaultOptions.Headers)},
{"empty options", &Options{}, false, 0, true, 0},
{"nil options", nil, false, 0, true, 0},
{"short secret/validate sanity check", shortCookieLength, false, 0, true, 0},
{"invalid ec key, valid base64 though", badRoutedProxy, false, 0, true, 0},
{"test disabled headers", disableHeaders, false, 1, false, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -233,6 +237,10 @@ func TestNew(t *testing.T) {
if got != nil && len(got.routeConfigs) != tt.numRoutes {
t.Errorf("New() = num routeConfigs \n%+v, want \n%+v", got, tt.numRoutes)
}
if got != nil && len(got.headers) != tt.numHeaders {
t.Errorf("New() = num Headers \n%+v, want \n%+v", got.headers, tt.numHeaders)
}
})
}
}