pomerium/config/options.go
Caleb Doxsey bbed421cd8
config: remove source, remove deadcode, fix linting issues (#4118)
* remove source, remove deadcode, fix linting issues

* use github action for lint

* fix missing envoy
2023-04-21 17:25:11 -06:00

1449 lines
51 KiB
Go

package config
import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"reflect"
"strings"
"time"
envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/mitchellh/mapstructure"
"github.com/rs/zerolog"
"github.com/spf13/viper"
"github.com/volatiletech/null/v9"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/hashutil"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/identity/oauth"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sets"
"github.com/pomerium/pomerium/internal/telemetry"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/config"
"github.com/pomerium/pomerium/pkg/grpc/crypt"
"github.com/pomerium/pomerium/pkg/hpke"
)
// DisableHeaderKey is the key used to check whether to disable setting header
const DisableHeaderKey = "disable"
// DefaultAlternativeAddr is the address used is two services are competing over
// the same listener. Typically this is invisible to the end user (e.g. localhost)
// gRPC server, or is used for healthchecks (authorize only service)
const DefaultAlternativeAddr = ":5443"
// The randomSharedKey is used if no shared key is supplied in all-in-one mode.
var randomSharedKey = cryptutil.NewBase64Key()
// Options are the global environmental flags used to set up pomerium's services.
// Use NewXXXOptions() methods for a safely initialized data structure.
type Options struct {
// InstallationID is used to indicate a unique installation of pomerium. Useful for telemetry.
InstallationID string `mapstructure:"installation_id" yaml:"installation_id,omitempty"`
// Debug outputs human-readable logs to Stdout.
Debug bool `mapstructure:"pomerium_debug" yaml:"pomerium_debug,omitempty"`
// LogLevel sets the global override for log level. All Loggers will use at least this value.
// Possible options are "info","warn","debug" and "error". Defaults to "info".
LogLevel string `mapstructure:"log_level" yaml:"log_level,omitempty"`
// ProxyLogLevel sets the log level for the proxy service.
// Possible options are "info","warn", and "error". Defaults to the value of `LogLevel`.
ProxyLogLevel string `mapstructure:"proxy_log_level" yaml:"proxy_log_level,omitempty"`
// SharedKey is the shared secret authorization key used to mutually authenticate
// requests between services.
SharedKey string `mapstructure:"shared_secret" yaml:"shared_secret,omitempty"`
SharedSecretFile string `mapstructure:"shared_secret_file" yaml:"shared_secret_file,omitempty"`
// Services is a list enabled service mode. If none are selected, "all" is used.
// Available options are : "all", "authenticate", "proxy".
Services string `mapstructure:"services" yaml:"services,omitempty"`
// Addr specifies the host and port on which the server should serve
// HTTPS requests. If empty, ":443" (localhost:443) is used.
Addr string `mapstructure:"address" yaml:"address,omitempty"`
// InsecureServer when enabled disables all transport security.
// In this mode, Pomerium is susceptible to man-in-the-middle attacks.
// This should be used only for testing.
InsecureServer bool `mapstructure:"insecure_server" yaml:"insecure_server,omitempty"`
// DNSLookupFamily is the DNS IP address resolution policy.
// If this setting is not specified, the value defaults to V4_PREFERRED.
DNSLookupFamily string `mapstructure:"dns_lookup_family" yaml:"dns_lookup_family,omitempty"`
CertificateFiles []certificateFilePair `mapstructure:"certificates" yaml:"certificates,omitempty"`
// Cert and Key is the x509 certificate used to create the HTTPS server.
Cert string `mapstructure:"certificate" yaml:"certificate,omitempty"`
Key string `mapstructure:"certificate_key" yaml:"certificate_key,omitempty"`
// CertFile and KeyFile is the x509 certificate used to hydrate TLSCertificate
CertFile string `mapstructure:"certificate_file" yaml:"certificate_file,omitempty"`
KeyFile string `mapstructure:"certificate_key_file" yaml:"certificate_key_file,omitempty"`
// HttpRedirectAddr, if set, specifies the host and port to run the HTTP
// to HTTPS redirect server on. If empty, no redirect server is started.
HTTPRedirectAddr string `mapstructure:"http_redirect_addr" yaml:"http_redirect_addr,omitempty"`
// Timeout settings : https://github.com/pomerium/pomerium/issues/40
ReadTimeout time.Duration `mapstructure:"timeout_read" yaml:"timeout_read,omitempty"`
WriteTimeout time.Duration `mapstructure:"timeout_write" yaml:"timeout_write,omitempty"`
IdleTimeout time.Duration `mapstructure:"timeout_idle" yaml:"timeout_idle,omitempty"`
// Policies define per-route configuration and access control policies.
Policies []Policy `mapstructure:"policy"`
PolicyFile string `mapstructure:"policy_file" yaml:"policy_file,omitempty"`
Routes []Policy `mapstructure:"routes"`
// AdditionalPolicies are any additional policies added to the options.
AdditionalPolicies []Policy `yaml:"-"`
// AuthenticateURL represents the externally accessible http endpoints
// used for authentication requests and callbacks
AuthenticateURLString string `mapstructure:"authenticate_service_url" yaml:"authenticate_service_url,omitempty"`
AuthenticateInternalURLString string `mapstructure:"authenticate_internal_service_url" yaml:"authenticate_internal_service_url,omitempty"`
// SignOutRedirectURL represents the url that user will be redirected to after signing out.
SignOutRedirectURLString string `mapstructure:"signout_redirect_url" yaml:"signout_redirect_url,omitempty"`
// AuthenticateCallbackPath is the path to the HTTP endpoint that will
// receive the response from your identity provider. The value must exactly
// match one of the authorized redirect URIs for the OAuth 2.0 client.
// Defaults to: `/oauth2/callback`
AuthenticateCallbackPath string `mapstructure:"authenticate_callback_path" yaml:"authenticate_callback_path,omitempty"`
// Session/Cookie management
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
CookieName string `mapstructure:"cookie_name" yaml:"cookie_name,omitempty"`
CookieSecret string `mapstructure:"cookie_secret" yaml:"cookie_secret,omitempty"`
CookieSecretFile string `mapstructure:"cookie_secret_file" yaml:"cookie_secret_file,omitempty"`
CookieDomain string `mapstructure:"cookie_domain" yaml:"cookie_domain,omitempty"`
CookieSecure bool `mapstructure:"cookie_secure" yaml:"cookie_secure,omitempty"`
CookieHTTPOnly bool `mapstructure:"cookie_http_only" yaml:"cookie_http_only,omitempty"`
CookieExpire time.Duration `mapstructure:"cookie_expire" yaml:"cookie_expire,omitempty"`
// Identity provider configuration variables as specified by RFC6749
// https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
ClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
ClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
ClientSecretFile string `mapstructure:"idp_client_secret_file" yaml:"idp_client_secret_file,omitempty"`
Provider string `mapstructure:"idp_provider" yaml:"idp_provider,omitempty"`
ProviderURL string `mapstructure:"idp_provider_url" yaml:"idp_provider_url,omitempty"`
Scopes []string `mapstructure:"idp_scopes" yaml:"idp_scopes,omitempty"`
// RequestParams are custom request params added to the signin request as
// part of an Oauth2 code flow.
//
// https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml
// https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters
RequestParams map[string]string `mapstructure:"idp_request_params" yaml:"idp_request_params,omitempty"`
// AuthorizeURLString is the routable destination of the authorize service's
// gRPC endpoint. NOTE: As many load balancers do not support
// externally routed gRPC so this may be an internal location.
AuthorizeURLString string `mapstructure:"authorize_service_url" yaml:"authorize_service_url,omitempty"`
AuthorizeURLStrings []string `mapstructure:"authorize_service_urls" yaml:"authorize_service_urls,omitempty"`
AuthorizeInternalURLString string `mapstructure:"authorize_internal_service_url" yaml:"authorize_internal_service_url,omitempty"`
// Settings to enable custom behind-the-ingress service communication
OverrideCertificateName string `mapstructure:"override_certificate_name" yaml:"override_certificate_name,omitempty"`
CA string `mapstructure:"certificate_authority" yaml:"certificate_authority,omitempty"`
CAFile string `mapstructure:"certificate_authority_file" yaml:"certificate_authority_file,omitempty"`
// DeriveInternalDomainCert is an option that would derive certificate authority
// and domain certificates from the shared key and use them for internal communication
DeriveInternalDomainCert *string `mapstructure:"tls_derive" yaml:"tls_derive,omitempty"`
// SigningKey is the private key used to add a JWT-signature to upstream requests.
// https://www.pomerium.com/docs/topics/getting-users-identity.html
SigningKey string `mapstructure:"signing_key" yaml:"signing_key,omitempty"`
SigningKeyFile string `mapstructure:"signing_key_file" yaml:"signing_key_file,omitempty"`
HeadersEnv string `yaml:",omitempty"`
// SetResponseHeaders to set on all proxied requests. Add a 'disable' key map to turn off.
SetResponseHeaders map[string]string `yaml:",omitempty"`
// List of JWT claims to insert as x-pomerium-claim-* headers on proxied requests
JWTClaimsHeaders JWTClaimHeaders `mapstructure:"jwt_claims_headers" yaml:"jwt_claims_headers,omitempty"`
DefaultUpstreamTimeout time.Duration `mapstructure:"default_upstream_timeout" yaml:"default_upstream_timeout,omitempty"`
// Address/Port to bind to for prometheus metrics
MetricsAddr string `mapstructure:"metrics_address" yaml:"metrics_address,omitempty"`
// - require basic auth for prometheus metrics, base64 encoded user:pass string
MetricsBasicAuth string `mapstructure:"metrics_basic_auth" yaml:"metrics_basic_auth,omitempty"`
// - TLS options
MetricsCertificate string `mapstructure:"metrics_certificate" yaml:"metrics_certificate,omitempty"`
MetricsCertificateKey string `mapstructure:"metrics_certificate_key" yaml:"metrics_certificate_key,omitempty"`
MetricsCertificateFile string `mapstructure:"metrics_certificate_file" yaml:"metrics_certificate_file,omitempty"`
MetricsCertificateKeyFile string `mapstructure:"metrics_certificate_key_file" yaml:"metrics_certificate_key_file,omitempty"`
MetricsClientCA string `mapstructure:"metrics_client_ca" yaml:"metrics_client_ca,omitempty"`
MetricsClientCAFile string `mapstructure:"metrics_client_ca_file" yaml:"metrics_client_ca_file,omitempty"`
// Tracing shared settings
TracingProvider string `mapstructure:"tracing_provider" yaml:"tracing_provider,omitempty"`
TracingSampleRate float64 `mapstructure:"tracing_sample_rate" yaml:"tracing_sample_rate,omitempty"`
// Datadog tracing address
TracingDatadogAddress string `mapstructure:"tracing_datadog_address" yaml:"tracing_datadog_address,omitempty"`
// Jaeger
//
// CollectorEndpoint is the full url to the Jaeger HTTP Thrift collector.
// For example, http://localhost:14268/api/traces
TracingJaegerCollectorEndpoint string `mapstructure:"tracing_jaeger_collector_endpoint" yaml:"tracing_jaeger_collector_endpoint,omitempty"`
// AgentEndpoint instructs exporter to send spans to jaeger-agent at this address.
// For example, localhost:6831.
TracingJaegerAgentEndpoint string `mapstructure:"tracing_jaeger_agent_endpoint" yaml:"tracing_jaeger_agent_endpoint,omitempty"`
// Zipkin
//
// ZipkinEndpoint configures the zipkin collector URI
// Example: http://zipkin:9411/api/v2/spans
ZipkinEndpoint string `mapstructure:"tracing_zipkin_endpoint" yaml:"tracing_zipkin_endpoint"`
// GRPC Service Settings
// GRPCAddr specifies the host and port on which the server should serve
// gRPC requests. If running in all-in-one mode, ":5443" (localhost:5443) is used.
GRPCAddr string `mapstructure:"grpc_address" yaml:"grpc_address,omitempty"`
// GRPCInsecure disables transport security.
// If running in all-in-one mode, defaults to true.
GRPCInsecure *bool `mapstructure:"grpc_insecure" yaml:"grpc_insecure,omitempty"`
GRPCClientTimeout time.Duration `mapstructure:"grpc_client_timeout" yaml:"grpc_client_timeout,omitempty"`
GRPCClientDNSRoundRobin bool `mapstructure:"grpc_client_dns_roundrobin" yaml:"grpc_client_dns_roundrobin,omitempty"`
// DataBrokerURLString is the routable destination of the databroker service's gRPC endpoint.
DataBrokerURLString string `mapstructure:"databroker_service_url" yaml:"databroker_service_url,omitempty"`
DataBrokerURLStrings []string `mapstructure:"databroker_service_urls" yaml:"databroker_service_urls,omitempty"`
DataBrokerInternalURLString string `mapstructure:"databroker_internal_service_url" yaml:"databroker_internal_service_url,omitempty"`
// DataBrokerStorageType is the storage backend type that databroker will use.
// Supported type: memory, redis
DataBrokerStorageType string `mapstructure:"databroker_storage_type" yaml:"databroker_storage_type,omitempty"`
// DataBrokerStorageConnectionString is the data source name for storage backend.
DataBrokerStorageConnectionString string `mapstructure:"databroker_storage_connection_string" yaml:"databroker_storage_connection_string,omitempty"`
DataBrokerStorageCertFile string `mapstructure:"databroker_storage_cert_file" yaml:"databroker_storage_cert_file,omitempty"`
DataBrokerStorageCertKeyFile string `mapstructure:"databroker_storage_key_file" yaml:"databroker_storage_key_file,omitempty"`
DataBrokerStorageCAFile string `mapstructure:"databroker_storage_ca_file" yaml:"databroker_storage_ca_file,omitempty"`
DataBrokerStorageCertSkipVerify bool `mapstructure:"databroker_storage_tls_skip_verify" yaml:"databroker_storage_tls_skip_verify,omitempty"`
// ClientCA is the base64-encoded certificate authority to validate client mTLS certificates against.
ClientCA string `mapstructure:"client_ca" yaml:"client_ca,omitempty"`
// ClientCAFile points to a file that contains the certificate authority to validate client mTLS certificates against.
ClientCAFile string `mapstructure:"client_ca_file" yaml:"client_ca_file,omitempty"`
// ClientCRL is the base64-encoded certificate revocation list for client mTLS certificates.
ClientCRL string `mapstructure:"client_crl" yaml:"client_crl,omitempty"`
// ClientCRLFile points to a file that contains the certificate revocation list for client mTLS certificates.
ClientCRLFile string `mapstructure:"client_crl_file" yaml:"client_crl_file,omitempty"`
// GoogleCloudServerlessAuthenticationServiceAccount is the service account to use for GCP serverless authentication.
// If unset, the GCP metadata server will be used to query for identity tokens.
GoogleCloudServerlessAuthenticationServiceAccount string `mapstructure:"google_cloud_serverless_authentication_service_account" yaml:"google_cloud_serverless_authentication_service_account,omitempty"` //nolint
// UseProxyProtocol configures the HTTP listener to require the HAProxy proxy protocol (either v1 or v2) on incoming requests.
UseProxyProtocol bool `mapstructure:"use_proxy_protocol" yaml:"use_proxy_protocol,omitempty" json:"use_proxy_protocol,omitempty"`
viper *viper.Viper
AutocertOptions `mapstructure:",squash" yaml:",inline"`
// SkipXffAppend instructs proxy not to append its IP address to x-forwarded-for header.
// see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html?highlight=skip_xff_append#x-forwarded-for
SkipXffAppend bool `mapstructure:"skip_xff_append" yaml:"skip_xff_append,omitempty" json:"skip_xff_append,omitempty"`
// XffNumTrustedHops determines the trusted client address from x-forwarded-for addresses.
// see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html?highlight=xff_num_trusted_hops#x-forwarded-for
XffNumTrustedHops uint32 `mapstructure:"xff_num_trusted_hops" yaml:"xff_num_trusted_hops,omitempty" json:"xff_num_trusted_hops,omitempty"`
// Envoy bootstrap options. These do not support dynamic updates.
EnvoyAdminAccessLogPath string `mapstructure:"envoy_admin_access_log_path" yaml:"envoy_admin_access_log_path"`
EnvoyAdminProfilePath string `mapstructure:"envoy_admin_profile_path" yaml:"envoy_admin_profile_path"`
EnvoyAdminAddress string `mapstructure:"envoy_admin_address" yaml:"envoy_admin_address"`
EnvoyBindConfigSourceAddress string `mapstructure:"envoy_bind_config_source_address" yaml:"envoy_bind_config_source_address,omitempty"`
EnvoyBindConfigFreebind null.Bool `mapstructure:"envoy_bind_config_freebind" yaml:"envoy_bind_config_freebind,omitempty"`
// ProgrammaticRedirectDomainWhitelist restricts the allowed redirect URLs when using programmatic login.
ProgrammaticRedirectDomainWhitelist []string `mapstructure:"programmatic_redirect_domain_whitelist" yaml:"programmatic_redirect_domain_whitelist,omitempty" json:"programmatic_redirect_domain_whitelist,omitempty"` //nolint
// CodecType is the codec to use for downstream connections.
CodecType CodecType `mapstructure:"codec_type" yaml:"codec_type"`
AuditKey *PublicKeyEncryptionKeyOptions `mapstructure:"audit_key"`
BrandingOptions httputil.BrandingOptions
}
type certificateFilePair struct {
// CertFile and KeyFile is the x509 certificate used to hydrate TLSCertificate
CertFile string `mapstructure:"cert" yaml:"cert,omitempty"`
KeyFile string `mapstructure:"key" yaml:"key,omitempty"`
}
// DefaultOptions are the default configuration options for pomerium
var defaultOptions = Options{
Debug: false,
LogLevel: "info",
Services: "all",
CookieHTTPOnly: true,
CookieSecure: true,
CookieExpire: 14 * time.Hour,
CookieName: "_pomerium",
DefaultUpstreamTimeout: 30 * time.Second,
Addr: ":443",
ReadTimeout: 30 * time.Second,
WriteTimeout: 0, // support streaming by default
IdleTimeout: 5 * time.Minute,
GRPCAddr: ":443",
GRPCClientTimeout: 10 * time.Second, // Try to withstand transient service failures for a single request
GRPCClientDNSRoundRobin: true,
AuthenticateCallbackPath: "/oauth2/callback",
TracingSampleRate: 0.0001,
AutocertOptions: AutocertOptions{
Folder: dataDir(),
},
DataBrokerStorageType: "memory",
SkipXffAppend: false,
XffNumTrustedHops: 0,
EnvoyAdminAccessLogPath: os.DevNull,
EnvoyAdminProfilePath: os.DevNull,
ProgrammaticRedirectDomainWhitelist: []string{"localhost"},
}
var defaultSetResponseHeaders = map[string]string{
"X-Frame-Options": "SAMEORIGIN",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
}
// NewDefaultOptions returns a copy the default options. It's the caller's
// responsibility to do a follow up Validate call.
func NewDefaultOptions() *Options {
newOpts := defaultOptions
newOpts.viper = viper.New()
return &newOpts
}
// newOptionsFromConfig builds the main binary's configuration options by parsing
// environmental variables and config file
func newOptionsFromConfig(configFile string) (*Options, error) {
o, err := optionsFromViper(configFile)
if err != nil {
return nil, fmt.Errorf("config: options from config file %q: %w", configFile, err)
}
serviceName := telemetry.ServiceName(o.Services)
metrics.AddPolicyCountCallback(serviceName, func() int64 {
return int64(len(o.GetAllPolicies()))
})
return o, nil
}
func optionsFromViper(configFile string) (*Options, error) {
// start a copy of the default options
o := NewDefaultOptions()
v := o.viper
// Load up config
err := bindEnvs(o, v)
if err != nil {
return nil, fmt.Errorf("failed to bind options to env vars: %w", err)
}
if configFile != "" {
v.SetConfigFile(configFile)
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config: %w", err)
}
}
var metadata mapstructure.Metadata
if err := v.Unmarshal(o, ViperPolicyHooks, func(c *mapstructure.DecoderConfig) { c.Metadata = &metadata }); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
if err := checkConfigKeysErrors(configFile, metadata.Unused); err != nil {
return nil, err
}
// This is necessary because v.Unmarshal will overwrite .viper field.
o.viper = v
if err := o.Validate(); err != nil {
return nil, fmt.Errorf("validation error %w", err)
}
return o, nil
}
func checkConfigKeysErrors(configFile string, unused []string) error {
checks := CheckUnknownConfigFields(unused)
ctx := context.Background()
errInvalidConfigKeys := errors.New("some configuration options are no longer supported, please check logs for details")
var err error
for _, check := range checks {
var evt *zerolog.Event
switch check.KeyAction {
case KeyActionError:
evt = log.Error(ctx)
err = errInvalidConfigKeys
default:
evt = log.Warn(ctx)
}
evt.Str("config_file", configFile).Str("key", check.Key)
if check.DocsURL != "" {
evt = evt.Str("help", check.DocsURL)
}
evt.Msg(string(check.FieldCheckMsg))
}
return err
}
// parsePolicy initializes policy to the options from either base64 environmental
// variables or from a file
func (o *Options) parsePolicy() error {
var policies []Policy
if err := o.viper.UnmarshalKey("policy", &policies, ViperPolicyHooks); err != nil {
return err
}
if len(policies) != 0 {
o.Policies = policies
}
var routes []Policy
if err := o.viper.UnmarshalKey("routes", &routes, ViperPolicyHooks); err != nil {
return err
}
if len(routes) != 0 {
o.Routes = routes
}
// Finish initializing policies
for i := range o.Policies {
p := &o.Policies[i]
if err := p.Validate(); err != nil {
return err
}
}
for i := range o.Routes {
p := &o.Routes[i]
if err := p.Validate(); err != nil {
return err
}
}
for i := range o.AdditionalPolicies {
p := &o.AdditionalPolicies[i]
if err := p.Validate(); err != nil {
return err
}
}
return nil
}
func (o *Options) viperSet(key string, value interface{}) {
o.viper.Set(key, value)
}
func (o *Options) viperIsSet(key string) bool {
return o.viper.IsSet(key)
}
// parseHeaders handles unmarshalling any custom headers correctly from the
// environment or viper's parsed keys
func (o *Options) parseHeaders(_ context.Context) error {
var headers map[string]string
if o.HeadersEnv != "" {
// Handle JSON by default via viper
if headers = o.viper.GetStringMapString("HeadersEnv"); len(headers) == 0 {
// Try to parse "Key1:Value1,Key2:Value2" syntax
headerSlice := strings.Split(o.HeadersEnv, ",")
for n := range headerSlice {
headerFields := strings.SplitN(headerSlice[n], ":", 2)
if len(headerFields) == 2 {
headers[headerFields[0]] = headerFields[1]
} else {
// Something went wrong
return fmt.Errorf("failed to decode headers from '%s'", o.HeadersEnv)
}
}
}
o.SetResponseHeaders = headers
return nil
}
if o.viperIsSet("set_response_headers") {
if err := o.viper.UnmarshalKey("set_response_headers", &headers); err != nil {
return fmt.Errorf("header %s failed to parse: %w", o.viper.Get("set_response_headers"), err)
}
o.SetResponseHeaders = headers
}
return nil
}
// bindEnvs binds a viper instance to each env var of an Options struct based
// on the mapstructure tag
func bindEnvs(o *Options, v *viper.Viper) error {
tagName := `mapstructure`
t := reflect.TypeOf(*o)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
envName := field.Tag.Get(tagName)
err := v.BindEnv(envName)
if err != nil {
return fmt.Errorf("failed to bind field '%s' to env var '%s': %w", field.Name, envName, err)
}
}
// Statically bind fields
err := v.BindEnv("Policy", "POLICY")
if err != nil {
return fmt.Errorf("failed to bind field 'Policy' to env var 'POLICY': %w", err)
}
err = v.BindEnv("HeadersEnv", "HEADERS")
if err != nil {
return fmt.Errorf("failed to bind field 'HeadersEnv' to env var 'HEADERS': %w", err)
}
// autocert options
ao := reflect.TypeOf(o.AutocertOptions)
for i := 0; i < ao.NumField(); i++ {
field := ao.Field(i)
envName := field.Tag.Get(tagName)
err := v.BindEnv(envName)
if err != nil {
return fmt.Errorf("failed to bind field '%s' to env var '%s': %w", field.Name, envName, err)
}
}
return nil
}
// Validate ensures the Options fields are valid, and hydrated.
func (o *Options) Validate() error {
ctx := context.TODO()
if !IsValidService(o.Services) {
return fmt.Errorf("config: %s is an invalid service type", o.Services)
}
switch o.DataBrokerStorageType {
case StorageInMemoryName:
case StorageRedisName, StoragePostgresName:
if o.DataBrokerStorageConnectionString == "" {
return errors.New("config: missing databroker storage backend dsn")
}
default:
return errors.New("config: unknown databroker storage backend type")
}
_, err := o.GetSharedKey()
if err != nil {
return fmt.Errorf("config: invalid shared secret: %w", err)
}
if o.AuthenticateURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.AuthenticateURLString)
if err != nil {
return fmt.Errorf("config: bad authenticate-url %s : %w", o.AuthenticateURLString, err)
}
}
if o.AuthenticateInternalURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.AuthenticateInternalURLString)
if err != nil {
return fmt.Errorf("config: bad authenticate-internal-url %s : %w", o.AuthenticateInternalURLString, err)
}
}
if o.SignOutRedirectURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.SignOutRedirectURLString)
if err != nil {
return fmt.Errorf("config: bad signout-redirect-url %s : %w", o.SignOutRedirectURLString, err)
}
}
if o.AuthorizeURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.AuthorizeURLString)
if err != nil {
return fmt.Errorf("config: bad authorize-url %s : %w", o.AuthorizeURLString, err)
}
}
if o.AuthorizeInternalURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.AuthorizeInternalURLString)
if err != nil {
return fmt.Errorf("config: bad authorize-internal-url %s : %w", o.AuthorizeInternalURLString, err)
}
}
if o.DataBrokerURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.DataBrokerURLString)
if err != nil {
return fmt.Errorf("config: bad databroker service url %s : %w", o.DataBrokerURLString, err)
}
}
if o.DataBrokerInternalURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.DataBrokerInternalURLString)
if err != nil {
return fmt.Errorf("config: bad databroker internal service url %s : %w", o.DataBrokerInternalURLString, err)
}
}
if o.PolicyFile != "" {
return errors.New("config: policy file setting is deprecated")
}
if err := o.parsePolicy(); err != nil {
return fmt.Errorf("config: failed to parse policy: %w", err)
}
if err := o.parseHeaders(ctx); err != nil {
return fmt.Errorf("config: failed to parse headers: %w", err)
}
hasCert := false
if o.Cert != "" || o.Key != "" {
_, err := cryptutil.CertificateFromBase64(o.Cert, o.Key)
if err != nil {
return fmt.Errorf("config: bad cert base64 %w", err)
}
hasCert = true
}
for _, c := range o.CertificateFiles {
_, err := cryptutil.CertificateFromBase64(c.CertFile, c.KeyFile)
if err != nil {
_, err = cryptutil.CertificateFromFile(c.CertFile, c.KeyFile)
}
if err != nil {
return fmt.Errorf("config: bad cert entry, base64 or file reference invalid. %w", err)
}
hasCert = true
}
if o.CertFile != "" || o.KeyFile != "" {
_, err := cryptutil.CertificateFromFile(o.CertFile, o.KeyFile)
if err != nil {
return fmt.Errorf("config: bad cert file %w", err)
}
hasCert = true
}
if o.DataBrokerStorageCertFile != "" || o.DataBrokerStorageCertKeyFile != "" {
_, err := cryptutil.CertificateFromFile(o.DataBrokerStorageCertFile, o.DataBrokerStorageCertKeyFile)
if err != nil {
return fmt.Errorf("config: bad databroker cert file %w", err)
}
}
if o.DataBrokerStorageCAFile != "" {
if _, err := os.Stat(o.DataBrokerStorageCAFile); err != nil {
return fmt.Errorf("config: bad databroker ca file: %w", err)
}
}
if o.ClientCA != "" {
if _, err := base64.StdEncoding.DecodeString(o.ClientCA); err != nil {
return fmt.Errorf("config: bad client ca base64: %w", err)
}
}
if o.ClientCAFile != "" {
_, err := os.ReadFile(o.ClientCAFile)
if err != nil {
return fmt.Errorf("config: bad client ca file: %w", err)
}
}
if o.ClientCRL != "" {
_, err = cryptutil.CRLFromBase64(o.ClientCRL)
if err != nil {
return fmt.Errorf("config: bad client crl base64: %w", err)
}
}
if o.ClientCRLFile != "" {
_, err = cryptutil.CRLFromFile(o.ClientCRLFile)
if err != nil {
return fmt.Errorf("config: bad client crl file: %w", err)
}
}
// strip quotes from redirect address (#811)
o.HTTPRedirectAddr = strings.Trim(o.HTTPRedirectAddr, `"'`)
if !o.InsecureServer && !hasCert && !o.AutocertOptions.Enable {
log.Warn(ctx).Msg("neither `autocert`, " +
"`insecure_server` or manually provided certificates were provided, server will be using a self-signed certificate")
}
if err := ValidateDNSLookupFamily(o.DNSLookupFamily); err != nil {
return fmt.Errorf("config: %w", err)
}
if o.MetricsAddr != "" {
if err := ValidateMetricsAddress(o.MetricsAddr); err != nil {
return fmt.Errorf("config: invalid metrics_addr: %w", err)
}
}
// validate metrics basic auth
if o.MetricsBasicAuth != "" {
str, err := base64.StdEncoding.DecodeString(o.MetricsBasicAuth)
if err != nil {
return fmt.Errorf("config: metrics_basic_auth must be a base64 encoded string")
}
if !strings.Contains(string(str), ":") {
return fmt.Errorf("config: metrics_basic_auth should contain a user name and password separated by a colon")
}
}
if o.MetricsCertificate != "" && o.MetricsCertificateKey != "" {
_, err := cryptutil.CertificateFromBase64(o.MetricsCertificate, o.MetricsCertificateKey)
if err != nil {
return fmt.Errorf("config: invalid metrics_certificate or metrics_certificate_key: %w", err)
}
}
if o.MetricsCertificateFile != "" && o.MetricsCertificateKeyFile != "" {
_, err := cryptutil.CertificateFromFile(o.MetricsCertificateFile, o.MetricsCertificateKeyFile)
if err != nil {
return fmt.Errorf("config: invalid metrics_certificate_file or metrics_certificate_key_file: %w", err)
}
}
// validate the Autocert options
err = o.AutocertOptions.Validate()
if err != nil {
return err
}
return nil
}
// GetDeriveInternalDomain returns an optional internal domain name to use for gRPC endpoint
func (o *Options) GetDeriveInternalDomain() string {
if o.DeriveInternalDomainCert == nil {
return ""
}
return *o.DeriveInternalDomainCert
}
// GetAuthenticateURL returns the AuthenticateURL in the options or 127.0.0.1.
func (o *Options) GetAuthenticateURL() (*url.URL, error) {
rawurl := o.AuthenticateURLString
if rawurl == "" {
rawurl = "https://127.0.0.1"
}
return urlutil.ParseAndValidateURL(rawurl)
}
// GetInternalAuthenticateURL returns the internal AuthenticateURL in the options or the AuthenticateURL.
func (o *Options) GetInternalAuthenticateURL() (*url.URL, error) {
rawurl := o.AuthenticateInternalURLString
if rawurl == "" {
return o.GetAuthenticateURL()
}
return urlutil.ParseAndValidateURL(o.AuthenticateInternalURLString)
}
// GetAuthorizeURLs returns the AuthorizeURLs in the options or 127.0.0.1:5443.
func (o *Options) GetAuthorizeURLs() ([]*url.URL, error) {
if IsAll(o.Services) && o.AuthorizeURLString == "" && len(o.AuthorizeURLStrings) == 0 {
u, err := urlutil.ParseAndValidateURL("http://127.0.0.1" + DefaultAlternativeAddr)
if err != nil {
return nil, err
}
return []*url.URL{u}, nil
}
return o.getURLs(append([]string{o.AuthorizeURLString}, o.AuthorizeURLStrings...)...)
}
// GetInternalAuthorizeURLs returns the internal AuthorizeURLs in the options or the AuthorizeURLs.
func (o *Options) GetInternalAuthorizeURLs() ([]*url.URL, error) {
rawurl := o.AuthorizeInternalURLString
if rawurl == "" {
return o.GetAuthorizeURLs()
}
return o.getURLs(rawurl)
}
// GetDataBrokerURLs returns the DataBrokerURLs in the options or 127.0.0.1:5443.
func (o *Options) GetDataBrokerURLs() ([]*url.URL, error) {
if IsAll(o.Services) && o.DataBrokerURLString == "" && len(o.DataBrokerURLStrings) == 0 {
u, err := urlutil.ParseAndValidateURL("http://127.0.0.1" + DefaultAlternativeAddr)
if err != nil {
return nil, err
}
return []*url.URL{u}, nil
}
return o.getURLs(append([]string{o.DataBrokerURLString}, o.DataBrokerURLStrings...)...)
}
// GetInternalDataBrokerURLs returns the internal DataBrokerURLs in the options or the DataBrokerURLs.
func (o *Options) GetInternalDataBrokerURLs() ([]*url.URL, error) {
rawurl := o.DataBrokerInternalURLString
if rawurl == "" {
return o.GetDataBrokerURLs()
}
return o.getURLs(rawurl)
}
func (o *Options) getURLs(strs ...string) ([]*url.URL, error) {
var urls []*url.URL
if o != nil {
for _, str := range strs {
if str == "" {
continue
}
u, err := urlutil.ParseAndValidateURL(str)
if err != nil {
return nil, err
}
urls = append(urls, u)
}
}
if len(urls) == 0 {
u, _ := url.Parse("http://127.0.0.1" + DefaultAlternativeAddr)
urls = append(urls, u)
}
return urls, nil
}
// GetGRPCAddr gets the gRPC address.
func (o *Options) GetGRPCAddr() string {
// to avoid port collision when running on localhost
if IsAll(o.Services) && o.GRPCAddr == defaultOptions.GRPCAddr {
return DefaultAlternativeAddr
}
return o.GRPCAddr
}
// GetGRPCInsecure gets whether or not gRPC is insecure.
func (o *Options) GetGRPCInsecure() bool {
if o.GRPCInsecure != nil {
return *o.GRPCInsecure
}
if IsAll(o.Services) {
return true
}
return false
}
// GetSignOutRedirectURL gets the SignOutRedirectURL.
func (o *Options) GetSignOutRedirectURL() (*url.URL, error) {
rawurl := o.SignOutRedirectURLString
if rawurl == "" {
return nil, nil
}
return urlutil.ParseAndValidateURL(rawurl)
}
// GetMetricsCertificate returns the metrics certificate to use for TLS. `nil` will be
// returned if there is no certificate.
func (o *Options) GetMetricsCertificate() (*tls.Certificate, error) {
if o.MetricsCertificate != "" && o.MetricsCertificateKey != "" {
return cryptutil.CertificateFromBase64(o.MetricsCertificate, o.MetricsCertificateKey)
}
if o.MetricsCertificateFile != "" && o.MetricsCertificateKeyFile != "" {
return cryptutil.CertificateFromFile(o.MetricsCertificateFile, o.MetricsCertificateKeyFile)
}
return nil, nil
}
// GetOauthOptions gets the oauth.Options for the given config options.
func (o *Options) GetOauthOptions() (oauth.Options, error) {
redirectURL, err := o.GetAuthenticateURL()
if err != nil {
return oauth.Options{}, err
}
redirectURL = redirectURL.ResolveReference(&url.URL{
Path: o.AuthenticateCallbackPath,
})
clientSecret, err := o.GetClientSecret()
if err != nil {
return oauth.Options{}, err
}
return oauth.Options{
RedirectURL: redirectURL,
ProviderName: o.Provider,
ProviderURL: o.ProviderURL,
ClientID: o.ClientID,
ClientSecret: clientSecret,
Scopes: o.Scopes,
}, nil
}
// GetAllPolicies gets all the policies in the options.
func (o *Options) GetAllPolicies() []Policy {
if o == nil {
return nil
}
policies := make([]Policy, 0, len(o.Policies)+len(o.Routes)+len(o.AdditionalPolicies))
policies = append(policies, o.Policies...)
policies = append(policies, o.Routes...)
policies = append(policies, o.AdditionalPolicies...)
return policies
}
// GetMetricsBasicAuth gets the metrics basic auth username and password.
func (o *Options) GetMetricsBasicAuth() (username, password string, ok bool) {
if o.MetricsBasicAuth == "" {
return "", "", false
}
bs, err := base64.StdEncoding.DecodeString(o.MetricsBasicAuth)
if err != nil {
return "", "", false
}
idx := bytes.Index(bs, []byte{':'})
if idx == -1 {
return "", "", false
}
return string(bs[:idx]), string(bs[idx+1:]), true
}
// GetClientCA returns the client certificate authority. If neither client_ca nor client_ca_file is specified nil will
// be returned.
func (o *Options) GetClientCA() ([]byte, error) {
if o.ClientCA != "" {
return base64.StdEncoding.DecodeString(o.ClientCA)
}
if o.ClientCAFile != "" {
return os.ReadFile(o.ClientCAFile)
}
return nil, nil
}
// GetDataBrokerCertificate gets the optional databroker certificate. This method will return nil if no certificate is
// specified.
func (o *Options) GetDataBrokerCertificate() (*tls.Certificate, error) {
if o.DataBrokerStorageCertFile == "" || o.DataBrokerStorageCertKeyFile == "" {
return nil, nil
}
return cryptutil.CertificateFromFile(o.DataBrokerStorageCertFile, o.DataBrokerStorageCertKeyFile)
}
// GetCertificates gets all the certificates from the options.
func (o *Options) GetCertificates() ([]tls.Certificate, error) {
var certs []tls.Certificate
if o.Cert != "" && o.Key != "" {
cert, err := cryptutil.CertificateFromBase64(o.Cert, o.Key)
if err != nil {
return nil, fmt.Errorf("config: invalid base64 certificate: %w", err)
}
certs = append(certs, *cert)
}
for _, c := range o.CertificateFiles {
cert, err := cryptutil.CertificateFromBase64(c.CertFile, c.KeyFile)
if err != nil {
cert, err = cryptutil.CertificateFromFile(c.CertFile, c.KeyFile)
}
if err != nil {
return nil, fmt.Errorf("config: invalid certificate entry: %w", err)
}
certs = append(certs, *cert)
}
if o.CertFile != "" && o.KeyFile != "" {
cert, err := cryptutil.CertificateFromFile(o.CertFile, o.KeyFile)
if err != nil {
return nil, fmt.Errorf("config: bad cert file %w", err)
}
certs = append(certs, *cert)
}
return certs, nil
}
// GetSharedKey gets the decoded shared key.
func (o *Options) GetSharedKey() ([]byte, error) {
sharedKey := o.SharedKey
if o.SharedSecretFile != "" {
bs, err := os.ReadFile(o.SharedSecretFile)
if err != nil {
return nil, err
}
sharedKey = string(bs)
}
// mutual auth between services on the same host can be generated at runtime
if IsAll(o.Services) && sharedKey == "" {
sharedKey = randomSharedKey
}
if sharedKey == "" {
return nil, errors.New("empty shared secret")
}
if strings.TrimSpace(sharedKey) != sharedKey {
return nil, errors.New("shared secret contains whitespace")
}
return base64.StdEncoding.DecodeString(sharedKey)
}
// GetHPKEPrivateKey gets the hpke.PrivateKey dervived from the shared key.
func (o *Options) GetHPKEPrivateKey() (*hpke.PrivateKey, error) {
sharedKey, err := o.GetSharedKey()
if err != nil {
return nil, err
}
return hpke.DerivePrivateKey(sharedKey), nil
}
// GetGoogleCloudServerlessAuthenticationServiceAccount gets the GoogleCloudServerlessAuthenticationServiceAccount.
func (o *Options) GetGoogleCloudServerlessAuthenticationServiceAccount() string {
return o.GoogleCloudServerlessAuthenticationServiceAccount
}
// GetSetResponseHeaders gets the SetResponseHeaders.
func (o *Options) GetSetResponseHeaders(requireStrictTransportSecurity bool) map[string]string {
return o.GetSetResponseHeadersForPolicy(nil, requireStrictTransportSecurity)
}
// GetSetResponseHeadersForPolicy gets the SetResponseHeaders for a policy.
func (o *Options) GetSetResponseHeadersForPolicy(policy *Policy, requireStrictTransportSecurity bool) map[string]string {
hdrs := o.SetResponseHeaders
if hdrs == nil {
hdrs = make(map[string]string)
for k, v := range defaultSetResponseHeaders {
hdrs[k] = v
}
}
if _, ok := hdrs[DisableHeaderKey]; ok {
hdrs = make(map[string]string)
}
if policy != nil && policy.SetResponseHeaders != nil {
for k, v := range policy.SetResponseHeaders {
hdrs[k] = v
}
}
if _, ok := hdrs[DisableHeaderKey]; ok {
hdrs = make(map[string]string)
}
if !requireStrictTransportSecurity {
delete(hdrs, "Strict-Transport-Security")
}
return hdrs
}
// GetCodecType gets a codec type.
func (o *Options) GetCodecType() CodecType {
if o.CodecType == CodecTypeUnset {
return CodecTypeAuto
}
return o.CodecType
}
// GetAllRouteableGRPCHosts returns all the possible gRPC hosts handled by the Pomerium options.
func (o *Options) GetAllRouteableGRPCHosts() ([]string, error) {
hosts := sets.NewSorted[string]()
// authorize urls
if IsAll(o.Services) {
authorizeURLs, err := o.GetAuthorizeURLs()
if err != nil {
return nil, err
}
for _, u := range authorizeURLs {
hosts.Add(urlutil.GetDomainsForURL(u)...)
}
} else if IsAuthorize(o.Services) {
authorizeURLs, err := o.GetInternalAuthorizeURLs()
if err != nil {
return nil, err
}
for _, u := range authorizeURLs {
hosts.Add(urlutil.GetDomainsForURL(u)...)
}
}
// databroker urls
if IsAll(o.Services) {
dataBrokerURLs, err := o.GetDataBrokerURLs()
if err != nil {
return nil, err
}
for _, u := range dataBrokerURLs {
hosts.Add(urlutil.GetDomainsForURL(u)...)
}
} else if IsDataBroker(o.Services) {
dataBrokerURLs, err := o.GetInternalDataBrokerURLs()
if err != nil {
return nil, err
}
for _, u := range dataBrokerURLs {
hosts.Add(urlutil.GetDomainsForURL(u)...)
}
}
return hosts.ToSlice(), nil
}
// GetAllRouteableHTTPHosts returns all the possible HTTP hosts handled by the Pomerium options.
func (o *Options) GetAllRouteableHTTPHosts() ([]string, error) {
hosts := sets.NewSorted[string]()
if IsAuthenticate(o.Services) {
authenticateURL, err := o.GetInternalAuthenticateURL()
if err != nil {
return nil, err
}
hosts.Add(urlutil.GetDomainsForURL(authenticateURL)...)
authenticateURL, err = o.GetAuthenticateURL()
if err != nil {
return nil, err
}
hosts.Add(urlutil.GetDomainsForURL(authenticateURL)...)
}
// policy urls
if IsProxy(o.Services) {
for _, policy := range o.GetAllPolicies() {
fromURL, err := urlutil.ParseAndValidateURL(policy.From)
if err != nil {
return nil, err
}
hosts.Add(urlutil.GetDomainsForURL(fromURL)...)
if policy.TLSDownstreamServerName != "" {
tlsURL := fromURL.ResolveReference(&url.URL{Host: policy.TLSDownstreamServerName})
hosts.Add(urlutil.GetDomainsForURL(tlsURL)...)
}
}
}
return hosts.ToSlice(), nil
}
// GetClientSecret gets the client secret.
func (o *Options) GetClientSecret() (string, error) {
if o == nil {
return "", nil
}
if o.ClientSecretFile != "" {
bs, err := os.ReadFile(o.ClientSecretFile)
if err != nil {
return "", err
}
return string(bs), nil
}
return o.ClientSecret, nil
}
// GetCookieSecret gets the decoded cookie secret.
func (o *Options) GetCookieSecret() ([]byte, error) {
cookieSecret := o.CookieSecret
if o.CookieSecretFile != "" {
bs, err := os.ReadFile(o.CookieSecretFile)
if err != nil {
return nil, err
}
cookieSecret = string(bs)
}
if IsAll(o.Services) && cookieSecret == "" {
log.WarnCookieSecret()
cookieSecret = randomSharedKey
}
if cookieSecret == "" {
return nil, errors.New("empty cookie secret")
}
return base64.StdEncoding.DecodeString(cookieSecret)
}
// GetSigningKey gets the signing key.
func (o *Options) GetSigningKey() ([]byte, error) {
if o == nil {
return nil, nil
}
rawSigningKey := o.SigningKey
if o.SigningKeyFile != "" {
bs, err := os.ReadFile(o.SigningKeyFile)
if err != nil {
return nil, err
}
rawSigningKey = string(bs)
}
rawSigningKey = strings.TrimSpace(rawSigningKey)
if bs, err := base64.StdEncoding.DecodeString(rawSigningKey); err == nil {
return bs, nil
}
return []byte(rawSigningKey), nil
}
// Checksum returns the checksum of the current options struct
func (o *Options) Checksum() uint64 {
return hashutil.MustHash(o)
}
func (o *Options) indexCerts(ctx context.Context) certsIndex {
idx := make(certsIndex)
if o.CertFile != "" {
cert, err := cryptutil.ParsePEMCertificateFromFile(o.CertFile)
if err != nil {
log.Error(ctx).Err(err).Str("file", o.CertFile).Msg("parsing local cert: skipped")
} else {
idx.addCert(cert)
}
} else if o.Cert != "" {
if cert, err := cryptutil.ParsePEMCertificateFromBase64(o.Cert); err != nil {
log.Error(ctx).Err(err).Msg("parsing local cert: skipped")
} else {
idx.addCert(cert)
}
}
for _, c := range o.CertificateFiles {
cert, err := cryptutil.ParsePEMCertificateFromBase64(c.CertFile)
if err != nil {
cert, err = cryptutil.ParsePEMCertificateFromFile(c.CertFile)
}
if err != nil {
log.Error(ctx).Err(err).Msg("parsing local cert: skipped")
} else {
idx.addCert(cert)
}
}
return idx
}
func (o *Options) applyExternalCerts(ctx context.Context, certs []*config.Settings_Certificate) {
idx := o.indexCerts(ctx)
for _, c := range certs {
cfp := certificateFilePair{}
cfp.CertFile = base64.StdEncoding.EncodeToString(c.CertBytes)
cfp.KeyFile = base64.StdEncoding.EncodeToString(c.KeyBytes)
cert, err := cryptutil.ParsePEMCertificateFromBase64(cfp.CertFile)
if err != nil {
log.Error(ctx).Err(err).Msg("parsing cert from databroker: skipped")
continue
}
if overlaps, name := idx.matchCert(cert); overlaps {
log.Error(ctx).Err(err).Str("domain", name).Msg("overlaps with local certs: skipped")
continue
}
o.CertificateFiles = append(o.CertificateFiles, cfp)
}
}
// ApplySettings modifies the config options using the given protobuf settings.
func (o *Options) ApplySettings(ctx context.Context, settings *config.Settings) {
if settings == nil {
return
}
set(&o.InstallationID, settings.InstallationId)
set(&o.Debug, settings.Debug)
set(&o.LogLevel, settings.LogLevel)
set(&o.ProxyLogLevel, settings.ProxyLogLevel)
set(&o.SharedKey, settings.SharedSecret)
set(&o.Services, settings.Services)
set(&o.Addr, settings.Address)
set(&o.InsecureServer, settings.InsecureServer)
set(&o.DNSLookupFamily, settings.DnsLookupFamily)
o.applyExternalCerts(ctx, settings.GetCertificates())
set(&o.HTTPRedirectAddr, settings.HttpRedirectAddr)
setDuration(&o.ReadTimeout, settings.TimeoutRead)
setDuration(&o.WriteTimeout, settings.TimeoutWrite)
setDuration(&o.IdleTimeout, settings.TimeoutIdle)
set(&o.AuthenticateURLString, settings.AuthenticateServiceUrl)
set(&o.AuthenticateInternalURLString, settings.AuthenticateInternalServiceUrl)
set(&o.SignOutRedirectURLString, settings.SignoutRedirectUrl)
set(&o.AuthenticateCallbackPath, settings.AuthenticateCallbackPath)
set(&o.CookieName, settings.CookieName)
set(&o.CookieSecret, settings.CookieSecret)
set(&o.CookieDomain, settings.CookieDomain)
set(&o.CookieSecure, settings.CookieSecure)
set(&o.CookieHTTPOnly, settings.CookieHttpOnly)
setDuration(&o.CookieExpire, settings.CookieExpire)
set(&o.ClientID, settings.IdpClientId)
set(&o.ClientSecret, settings.IdpClientSecret)
set(&o.Provider, settings.IdpProvider)
set(&o.ProviderURL, settings.IdpProviderUrl)
setSlice(&o.Scopes, settings.Scopes)
setMap(&o.RequestParams, settings.RequestParams)
setSlice(&o.AuthorizeURLStrings, settings.AuthorizeServiceUrls)
set(&o.AuthorizeInternalURLString, settings.AuthorizeInternalServiceUrl)
set(&o.OverrideCertificateName, settings.OverrideCertificateName)
set(&o.CA, settings.CertificateAuthority)
setOptional(&o.DeriveInternalDomainCert, settings.DeriveTls)
set(&o.SigningKey, settings.SigningKey)
setMap(&o.SetResponseHeaders, settings.SetResponseHeaders)
setMap(&o.JWTClaimsHeaders, settings.JwtClaimsHeaders)
setDuration(&o.DefaultUpstreamTimeout, settings.DefaultUpstreamTimeout)
set(&o.MetricsAddr, settings.MetricsAddress)
set(&o.MetricsBasicAuth, settings.MetricsBasicAuth)
setCertificate(&o.MetricsCertificate, &o.MetricsCertificateKey, settings.MetricsCertificate)
set(&o.MetricsClientCA, settings.MetricsClientCa)
set(&o.TracingProvider, settings.TracingProvider)
set(&o.TracingSampleRate, settings.TracingSampleRate)
set(&o.TracingDatadogAddress, settings.TracingDatadogAddress)
set(&o.TracingJaegerCollectorEndpoint, settings.TracingJaegerCollectorEndpoint)
set(&o.TracingJaegerAgentEndpoint, settings.TracingJaegerAgentEndpoint)
set(&o.ZipkinEndpoint, settings.TracingZipkinEndpoint)
set(&o.GRPCAddr, settings.GrpcAddress)
setOptional(&o.GRPCInsecure, settings.GrpcInsecure)
setDuration(&o.GRPCClientTimeout, settings.GrpcClientTimeout)
set(&o.GRPCClientDNSRoundRobin, settings.GrpcClientDnsRoundrobin)
setSlice(&o.DataBrokerURLStrings, settings.DatabrokerServiceUrls)
set(&o.DataBrokerInternalURLString, settings.DatabrokerInternalServiceUrl)
set(&o.DataBrokerStorageType, settings.DatabrokerStorageType)
set(&o.DataBrokerStorageConnectionString, settings.DatabrokerStorageConnectionString)
set(&o.DataBrokerStorageCertSkipVerify, settings.DatabrokerStorageTlsSkipVerify)
set(&o.ClientCA, settings.ClientCa)
set(&o.GoogleCloudServerlessAuthenticationServiceAccount, settings.GoogleCloudServerlessAuthenticationServiceAccount)
set(&o.UseProxyProtocol, settings.UseProxyProtocol)
set(&o.AutocertOptions.Enable, settings.Autocert)
set(&o.AutocertOptions.CA, settings.AutocertCa)
set(&o.AutocertOptions.Email, settings.AutocertEmail)
set(&o.AutocertOptions.EABKeyID, settings.AutocertEabKeyId)
set(&o.AutocertOptions.EABMACKey, settings.AutocertEabMacKey)
set(&o.AutocertOptions.UseStaging, settings.AutocertUseStaging)
set(&o.AutocertOptions.MustStaple, settings.AutocertMustStaple)
set(&o.AutocertOptions.Folder, settings.AutocertDir)
set(&o.AutocertOptions.TrustedCA, settings.AutocertTrustedCa)
set(&o.SkipXffAppend, settings.SkipXffAppend)
set(&o.XffNumTrustedHops, settings.XffNumTrustedHops)
setSlice(&o.ProgrammaticRedirectDomainWhitelist, settings.ProgrammaticRedirectDomainWhitelist)
setAuditKey(&o.AuditKey, settings.AuditKey)
setCodecType(&o.CodecType, settings.CodecType)
set(&o.ClientCRL, settings.ClientCrl)
o.BrandingOptions = settings
}
func dataDir() string {
homeDir, _ := os.UserHomeDir()
if homeDir == "" {
homeDir = "."
}
baseDir := filepath.Join(homeDir, ".local", "share")
if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
baseDir = xdgData
}
return filepath.Join(baseDir, "pomerium")
}
func compareByteSliceSlice(a, b [][]byte) int {
sz := min(len(a), len(b))
for i := 0; i < sz; i++ {
switch bytes.Compare(a[i], b[i]) {
case -1:
return -1
case 1:
return 1
}
}
switch {
case len(a) < len(b):
return -1
case len(b) < len(a):
return 1
default:
return 0
}
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
// NewAtomicOptions creates a new AtomicOptions.
func NewAtomicOptions() *atomicutil.Value[*Options] {
return atomicutil.NewValue(new(Options))
}
func set[T any](dst, src *T) {
if src == nil {
return
}
*dst = *src
}
func setAuditKey(dst **PublicKeyEncryptionKeyOptions, src *crypt.PublicKeyEncryptionKey) {
if src == nil {
return
}
*dst = &PublicKeyEncryptionKeyOptions{
ID: src.GetId(),
Data: base64.StdEncoding.EncodeToString(src.GetData()),
}
}
func setCodecType(dst *CodecType, src *envoy_http_connection_manager.HttpConnectionManager_CodecType) {
if src == nil {
return
}
*dst = CodecTypeFromEnvoy(*src)
}
func setDuration(dst *time.Duration, src *durationpb.Duration) {
if src == nil {
return
}
*dst = src.AsDuration()
}
func setOptional[T any](dst **T, src *T) {
if src == nil {
return
}
v := *src
*dst = &v
}
func setSlice[T any](dst *[]T, src []T) {
if len(src) == 0 {
return
}
*dst = src
}
func setMap[TKey comparable, TValue any, TMap ~map[TKey]TValue](dst *TMap, src map[TKey]TValue) {
if len(src) == 0 {
return
}
*dst = src
}
func setCertificate(
dstCertificate *string,
dstCertificateKey *string,
src *config.Settings_Certificate,
) {
if src == nil {
return
}
if len(src.GetCertBytes()) > 0 {
*dstCertificate = base64.StdEncoding.EncodeToString(src.GetCertBytes())
}
if len(src.GetKeyBytes()) > 0 {
*dstCertificateKey = base64.StdEncoding.EncodeToString(src.GetKeyBytes())
}
}