mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-05 05:16:04 +02:00
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
207 lines
8.1 KiB
Go
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
|
|
}
|