proxy: add per-route request headers setting (#346)

Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
Bobby DeSimone 2019-10-04 14:51:52 -07:00 committed by GitHub
parent c95a72e12a
commit a96aec57d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 13 deletions

View file

@ -4,6 +4,7 @@
### New ### New
- Allow setting request headers for back-end requests on per route basis in policy. [GH-308]
- Add endpoint to support "forward-auth" integration with third-party ingresses and proxies. Supports [nginx]https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/, [nginx-ingress](https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/), and [Traefik](https://docs.traefik.io/middlewares/forwardauth/). [GH-324] - Add endpoint to support "forward-auth" integration with third-party ingresses and proxies. Supports [nginx]https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/, [nginx-ingress](https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/), and [Traefik](https://docs.traefik.io/middlewares/forwardauth/). [GH-324]
- Add insecure transport support. [GH-328] - Add insecure transport support. [GH-328]
- Add setting to override HTTPS backend's TLS Server Name. [GH-297] - Add setting to override HTTPS backend's TLS Server Name. [GH-297]
@ -13,7 +14,7 @@
### Security ### Security
- The user's original intended location before completing the authentication process is now encrypted and kept confidential from the identity provider. [GH-316] - The user's original intended location before completing the authentication process is now encrypted and kept confidential from the identity provider. [GH-316]
- Under certain circumstances, where debug logging was enabled, pomerium's shared secret could be leaked to http access logs as a query param. - Under certain circumstances, where debug logging was enabled, pomerium's shared secret could be leaked to http access logs as a query param. [GH-338]
### Fixed ### Fixed
@ -288,3 +289,4 @@
[gh-319]: https://github.com/pomerium/pomerium/issues/319 [gh-319]: https://github.com/pomerium/pomerium/issues/319
[gh-328]: https://github.com/pomerium/pomerium/issues/328 [gh-328]: https://github.com/pomerium/pomerium/issues/328
[gh-332]: https://github.com/pomerium/pomerium/pull/332/ [gh-332]: https://github.com/pomerium/pomerium/pull/332/
[gh-338]: https://github.com/pomerium/pomerium/issues/338

View file

@ -491,6 +491,28 @@ Note: This setting will replace (not append) the system's trust store for a give
Pomerium supports client certificates which can be used to enforce [mutually authenticated and encrypted TLS connections](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS). For more details, see our [mTLS example repository](https://github.com/pomerium/examples/tree/master/mutual-tls) and the [certificate docs](./certificates.md). Pomerium supports client certificates which can be used to enforce [mutually authenticated and encrypted TLS connections](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS). For more details, see our [mTLS example repository](https://github.com/pomerium/examples/tree/master/mutual-tls) and the [certificate docs](./certificates.md).
### Set Request Headers
- Config File Key: `set_request_headers`
- Type: map of `strings` key value pairs
- Optional
Set Request Headers allows you to set static values for given request headers. This can be useful if you want to pass along additional information to downstream applications as headers, or set authentication header to the request. For example:
```yaml
- from: https://httpbin.corp.example.com
to: https://httpbin.org
allowed_users:
- bdd@pomerium.io
- bobbydesimone@gmail.com
- bobby@tdia.com
set_request_headers:
# works auto-magically!
# https://httpbin.corp.example.com/basic-auth/root/hunter42
Authorization: Basic cm9vdDpodW50ZXI0Mg==
X-Your-favorite-authenticating-Proxy: "Pomerium"
```
# Authenticate Service # Authenticate Service
## Authenticate Service URL ## Authenticate Service URL

View file

@ -66,14 +66,10 @@ type Policy struct {
TLSClientKeyFile string `mapstructure:"tls_client_key_file" yaml:"tls_client_key_file"` TLSClientKeyFile string `mapstructure:"tls_client_key_file" yaml:"tls_client_key_file"`
ClientCertificate *tls.Certificate ClientCertificate *tls.Certificate
// IsForwardAuthEndpoint allows for a given route to be used as a forward-auth // SetRequestHeaders adds a collection of headers to the downstream request
// endpoint instead of a reverse proxy. Some third-party proxies that do not // in the form of key value pairs. Note bene, this will overwrite the
// have rich access control capabilities (nginx, envoy, ambassador, traefik) // value of any existing value of a given header key.
// allow you to delegate and authenticate each request to your website SetRequestHeaders map[string]string `mapstructure:"set_request_headers" yaml:"set_request_headers"`
// with an external server or service. Pomerium can be configured to accept
// these requests with this switch
// todo(bdd): link to docs
IsForwardAuthEndpoint bool
} }
// Validate checks the validity of a policy. // Validate checks the validity of a policy.

View file

@ -16,13 +16,13 @@ import (
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
// SetHeaders ensures that every response includes some basic security headers // SetHeaders sets a map of response headers.
func SetHeaders(securityHeaders map[string]string) func(next http.Handler) http.Handler { func SetHeaders(headers map[string]string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "middleware.SetHeaders") ctx, span := trace.StartSpan(r.Context(), "middleware.SetHeaders")
defer span.End() defer span.End()
for key, val := range securityHeaders { for key, val := range headers {
w.Header().Set(key, val) w.Header().Set(key, val)
} }
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))

View file

@ -111,3 +111,17 @@ func (p *Proxy) reqNeedsAuthentication(w http.ResponseWriter, r *http.Request) {
uri := urlutil.SignedRedirectURL(p.SharedKey, p.authenticateSigninURL, urlutil.GetAbsoluteURL(r)) uri := urlutil.SignedRedirectURL(p.SharedKey, p.authenticateSigninURL, urlutil.GetAbsoluteURL(r))
http.Redirect(w, r, uri.String(), http.StatusFound) http.Redirect(w, r, uri.String(), http.StatusFound)
} }
// SetResponseHeaders sets a map of response headers.
func SetResponseHeaders(headers map[string]string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "middleware.SetResponseHeaders")
defer span.End()
for key, val := range headers {
r.Header.Set(key, val)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

View file

@ -6,9 +6,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"time" "time"
"github.com/google/go-cmp/cmp"
"github.com/pomerium/pomerium/internal/identity" "github.com/pomerium/pomerium/internal/identity"
"github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/proxy/clients" "github.com/pomerium/pomerium/proxy/clients"
@ -173,3 +175,40 @@ func TestProxy_SignRequest(t *testing.T) {
}) })
} }
} }
func TestProxy_SetResponseHeaders(t *testing.T) {
t.Parallel()
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
var sb strings.Builder
for k, v := range r.Header {
k = strings.ToLower(k)
for _, h := range v {
sb.WriteString(fmt.Sprintf("%v: %v\n", k, h))
}
}
fmt.Fprint(w, sb.String())
w.WriteHeader(http.StatusOK)
})
tests := []struct {
name string
setHeaders map[string]string
wantHeaders string
}{
{"good", map[string]string{"x-gonna": "give-it-to-ya"}, "x-gonna: give-it-to-ya\n"},
{"nil", nil, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
got := SetResponseHeaders(tt.setHeaders)(fn)
got.ServeHTTP(w, r)
if diff := cmp.Diff(w.Body.String(), tt.wantHeaders); diff != "" {
t.Errorf("SignRequest() :\n %s", diff)
}
})
}
}

View file

@ -242,7 +242,11 @@ func (p *Proxy) reverseProxyHandler(r *mux.Router, policy *config.Policy) (*mux.
} }
rp.Use(p.SignRequest(signer)) rp.Use(p.SignRequest(signer))
} }
// Optional: if additional headers are to be set for this url
if len(policy.SetRequestHeaders) != 0 {
log.Warn().Interface("headers", policy.SetRequestHeaders).Msg("proxy: set request headers")
rp.Use(SetResponseHeaders(policy.SetRequestHeaders))
}
return r, nil return r, nil
} }