mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-06 04:42:56 +02:00
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:
parent
0086fa05f8
commit
5e37c29dfe
8 changed files with 79 additions and 43 deletions
|
@ -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.
|
||||
|
||||

|
||||
|
||||
[base64 encoded]: https://en.wikipedia.org/wiki/Base64
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
[identity provider]: ./identity-providers.md
|
||||
|
|
BIN
docs/reference/security-headers.png
Normal file
BIN
docs/reference/security-headers.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
9
go.mod
9
go.mod
|
@ -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
19
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue