pomerium/config/policy.go
Cuong Manh Le 8d0deb0732
config: add PassIdentityHeaders option (#903)
Currently, user's identity headers are always inserted to downstream
request. For privacy reason, it would be better to not insert these
headers by default, and let user chose whether to include these headers
per=policy basis.

Fixes #702
2020-06-22 10:29:44 +07:00

207 lines
8.1 KiB
Go

package config
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"os"
"time"
"github.com/cespare/xxhash/v2"
"github.com/mitchellh/hashstructure"
"github.com/pomerium/pomerium/internal/cryptutil"
"github.com/pomerium/pomerium/internal/urlutil"
)
// Policy contains route specific configuration and access settings.
type Policy struct {
From string `mapstructure:"from" yaml:"from"`
To string `mapstructure:"to" yaml:"to"`
// Identity related policy
AllowedUsers []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty" json:"allowed_users,omitempty"`
AllowedGroups []string `mapstructure:"allowed_groups" yaml:"allowed_groups,omitempty" json:"allowed_groups,omitempty"`
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
Source *StringURL `yaml:",omitempty" json:"source,omitempty" hash:"ignore"`
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty" hash:"ignore"`
// Additional route matching options
Prefix string `mapstructure:"prefix" yaml:"prefix,omitempty" json:"prefix,omitempty"`
Path string `mapstructure:"path" yaml:"path,omitempty" json:"path,omitempty"`
Regex string `mapstructure:"regex" yaml:"regex,omitempty" json:"regex,omitempty"`
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
CORSAllowPreflight bool `mapstructure:"cors_allow_preflight" yaml:"cors_allow_preflight,omitempty"`
// Allow any public request to access this route. **Bypasses authentication**
AllowPublicUnauthenticatedAccess bool `mapstructure:"allow_public_unauthenticated_access" yaml:"allow_public_unauthenticated_access,omitempty"`
// UpstreamTimeout is the route specific timeout. Must be less than the global
// timeout. If unset, route will fallback to the proxy's DefaultUpstreamTimeout.
UpstreamTimeout time.Duration `mapstructure:"timeout" yaml:"timeout,omitempty"`
// Enable proxying of websocket connections by removing the default timeout handler.
// Caution: Enabling this feature could result in abuse via DOS attacks.
AllowWebsockets bool `mapstructure:"allow_websockets" yaml:"allow_websockets,omitempty"`
// TLSSkipVerify controls whether a client verifies the server's certificate
// chain and host name.
// If TLSSkipVerify is true, TLS accepts any certificate presented by the
// server and any host name in that certificate.
// In this mode, TLS is susceptible to man-in-the-middle attacks.
// This should be used only for testing.
TLSSkipVerify bool `mapstructure:"tls_skip_verify" yaml:"tls_skip_verify,omitempty"`
// TLSServerName overrides the hostname in the `to` field. This is useful
// if your backend is an HTTPS server with a valid certificate, but you
// want to communicate to the backend with an internal hostname (e.g.
// Docker container name).
TLSServerName string `mapstructure:"tls_server_name" yaml:"tls_server_name,omitempty"`
// TLSCustomCA defines the root certificate to use with a given
// route when verifying server certificates.
TLSCustomCA string `mapstructure:"tls_custom_ca" yaml:"tls_custom_ca,omitempty"`
TLSCustomCAFile string `mapstructure:"tls_custom_ca_file" yaml:"tls_custom_ca_file,omitempty"`
// Contains the x.509 client certificate to present to the downstream
// host.
TLSClientCert string `mapstructure:"tls_client_cert" yaml:"tls_client_cert,omitempty"`
TLSClientKey string `mapstructure:"tls_client_key" yaml:"tls_client_key,omitempty"`
TLSClientCertFile string `mapstructure:"tls_client_cert_file" yaml:"tls_client_cert_file,omitempty"`
TLSClientKeyFile string `mapstructure:"tls_client_key_file" yaml:"tls_client_key_file,omitempty"`
ClientCertificate *tls.Certificate `yaml:",omitempty" hash:"ignore"`
// SetRequestHeaders adds a collection of headers to the downstream request
// in the form of key value pairs. Note bene, this will overwrite the
// value of any existing value of a given header key.
SetRequestHeaders map[string]string `mapstructure:"set_request_headers" yaml:"set_request_headers,omitempty"`
// RemoveRequestHeaders removes a collection of headers from a downstream request.
// Note that this has lower priority than `SetRequestHeaders`, if you specify `X-Custom-Header` in both
// `SetRequestHeaders` and `RemoveRequestHeaders`, then the header won't be removed.
RemoveRequestHeaders []string `mapstructure:"remove_request_headers" yaml:"remove_request_headers,omitempty"`
// PreserveHostHeader disables host header rewriting.
//
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header
PreserveHostHeader bool `mapstructure:"preserve_host_header" yaml:"preserve_host_header,omitempty"`
// PassIdentityHeaders controls whether to add a user's identity headers to the downstream request.
// These includes:
//
// - X-Pomerium-Jwt-Assertion
// - X-Pomerium-Claim-*
//
PassIdentityHeaders bool `mapstructure:"pass_identity_headers" yaml:"pass_identity_headers,omitempty"`
}
// Validate checks the validity of a policy.
func (p *Policy) Validate() error {
var err error
source, err := urlutil.ParseAndValidateURL(p.From)
if err != nil {
return fmt.Errorf("config: policy bad source url %w", err)
}
// Make sure there's no path set on the from url
if !(source.Path == "" || source.Path == "/") {
return fmt.Errorf("config: policy source url (%s) contains a path, but it should be set using the path field instead",
source.String())
}
p.Source = &StringURL{source}
p.Destination, err = urlutil.ParseAndValidateURL(p.To)
if err != nil {
return fmt.Errorf("config: policy bad destination url %w", err)
}
// Only allow public access if no other whitelists are in place
if p.AllowPublicUnauthenticatedAccess && (p.AllowedDomains != nil || p.AllowedGroups != nil || p.AllowedUsers != nil) {
return fmt.Errorf("config: policy route marked as public but contains whitelists")
}
if (p.TLSClientCert == "" && p.TLSClientKey != "") || (p.TLSClientCert != "" && p.TLSClientKey == "") ||
(p.TLSClientCertFile == "" && p.TLSClientKeyFile != "") || (p.TLSClientCertFile != "" && p.TLSClientKeyFile == "") {
return fmt.Errorf("config: client certificate key and cert both must be non-empty")
}
if p.TLSClientCert != "" && p.TLSClientKey != "" {
p.ClientCertificate, err = cryptutil.CertificateFromBase64(p.TLSClientCert, p.TLSClientKey)
if err != nil {
return fmt.Errorf("config: couldn't decode client cert %w", err)
}
} else if p.TLSClientCertFile != "" && p.TLSClientKeyFile != "" {
p.ClientCertificate, err = cryptutil.CertificateFromFile(p.TLSClientCertFile, p.TLSClientKeyFile)
if err != nil {
return fmt.Errorf("config: couldn't load client cert file %w", err)
}
}
if p.TLSCustomCA != "" {
_, err := base64.StdEncoding.DecodeString(p.TLSCustomCA)
if err != nil {
return fmt.Errorf("config: couldn't decode custom ca: %w", err)
}
} else if p.TLSCustomCAFile != "" {
_, err := os.Stat(p.TLSCustomCAFile)
if err != nil {
return fmt.Errorf("config: couldn't load client ca file: %w", err)
}
}
return nil
}
// Checksum returns the xxhash hash for the policy.
func (p *Policy) Checksum() uint64 {
cs, _ := hashstructure.Hash(p, &hashstructure.HashOptions{
Hasher: xxhash.New(),
})
return cs
}
// RouteID returns a unique identifier for a route
func (p *Policy) RouteID() uint64 {
id := routeID{
Source: p.Source,
Destination: p.Destination,
Prefix: p.Prefix,
Path: p.Path,
Regex: p.Regex,
}
cs, _ := hashstructure.Hash(id, &hashstructure.HashOptions{
Hasher: xxhash.New(),
})
return cs
}
func (p *Policy) String() string {
if p.Source == nil || p.Destination == nil {
return fmt.Sprintf("%s → %s", p.From, p.To)
}
return fmt.Sprintf("%s → %s", p.Source.String(), p.Destination.String())
}
// StringURL stores a URL as a string in json.
type StringURL struct {
*url.URL
}
// MarshalJSON returns the URLs host as json.
func (u *StringURL) MarshalJSON() ([]byte, error) {
return json.Marshal(u.String())
}
type routeID struct {
Source *StringURL
Destination *url.URL
Prefix string
Path string
Regex string
}