pomerium/config/options.go
Herman Slatman 7812c6985d
Add additional ACME options (#2695)
The `autocert_ca` and `autocert_email` options have been added to be
able to configure CAs that support the ACME protocol as an alternative
to Let's Encrypt.

Fix ProtoBuf definition for additional autocert options

Fix PR comments and add ACME EAB configuration

Add configuration option for trusted CAs when talking ACME

Fix linter issues

copy edits

render updated reference to docs

Add test for autocert manager configuration

Add tests for autocert configuration options

Fix CI build issues

Don't set empty acme.EAB struct if configuration not set

Remove required email when setting custom CA

When using a non-default CA it's no longer required
to specify an email address. I required this before,
because it seemed to cause an issue in which no certificate
was issued. The root cause was something different,
rendering the hard email requirement pointless. It's
still beneficial to specify an email, though. I changed
the text in the docs to explain that.

Update generated docs

Fix failing tests by recreation of a new ACMEManager

The default ACMEManager object was reused in multiple tests,
resulting in unexpected states when tests run in parallel.
By using a new instance for every test, this is no longer
an issue.
2021-11-02 14:44:27 -07:00

1350 lines
49 KiB
Go

package config
import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"reflect"
"strings"
"sync/atomic"
"time"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
"github.com/pomerium/pomerium/internal/directory/azure"
"github.com/pomerium/pomerium/internal/directory/github"
"github.com/pomerium/pomerium/internal/directory/gitlab"
"github.com/pomerium/pomerium/internal/directory/google"
"github.com/pomerium/pomerium/internal/directory/okta"
"github.com/pomerium/pomerium/internal/directory/onelogin"
"github.com/pomerium/pomerium/internal/hashutil"
"github.com/pomerium/pomerium/internal/identity/oauth"
"github.com/pomerium/pomerium/internal/log"
"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"
)
// DisableHeaderKey is the key used to check whether to disable setting header
const DisableHeaderKey = "disable"
const (
idpCustomScopesDocLink = "https://www.pomerium.io/reference/#identity-provider-scopes"
idpCustomScopesWarnMsg = "config: using custom scopes may result in undefined behavior, see: " + idpCustomScopesDocLink
)
// 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"
// EnvoyAdminURL indicates where the envoy control plane is listening
var EnvoyAdminURL = &url.URL{Host: "127.0.0.1:9901", Scheme: "http"}
// 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"`
// 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 AUTO.
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"`
// 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"`
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"`
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"`
ServiceAccount string `mapstructure:"idp_service_account" yaml:"idp_service_account,omitempty"`
// Identity provider refresh directory interval/timeout settings.
RefreshDirectoryTimeout time.Duration `mapstructure:"idp_refresh_directory_timeout" yaml:"idp_refresh_directory_timeout,omitempty"`
RefreshDirectoryInterval time.Duration `mapstructure:"idp_refresh_directory_interval" yaml:"idp_refresh_directory_interval,omitempty"`
QPS float64 `mapstructure:"idp_qps" yaml:"idp_qps"`
// 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"`
// 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"`
// SigningKey is the private key used to add a JWT-signature to upstream requests.
// https://www.pomerium.io/docs/topics/getting-users-identity.html
SigningKey string `mapstructure:"signing_key" yaml:"signing_key,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.
// Zipkin
//
// ZipkinEndpoint configures the zipkin collector URI
// Example: http://zipkin:9411/api/v2/spans
TracingJaegerAgentEndpoint string `mapstructure:"tracing_jaeger_agent_endpoint" yaml:"tracing_jaeger_agent_endpoint,omitempty"`
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"`
// ForwardAuthEndpoint allows for a given route to be used as a forward-auth
// endpoint instead of a reverse proxy. Some third-party proxies that do not
// have rich access control capabilities (nginx, envoy, ambassador, traefik)
// allow you to delegate and authenticate each request to your website
// with an external server or service. Pomerium can be configured to accept
// these requests with this switch
ForwardAuthURLString string `mapstructure:"forward_auth_url" yaml:"forward_auth_url,omitempty"`
// DataBrokerURLString is the routable destination of the databroker service's gRPC endpiont.
DataBrokerURLString string `mapstructure:"databroker_service_url" yaml:"databroker_service_url,omitempty"`
DataBrokerURLStrings []string `mapstructure:"databroker_service_urls" yaml:"databroker_service_urls,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 admin 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"`
// 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"`
}
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,
SetResponseHeaders: map[string]string{
"X-Frame-Options": "SAMEORIGIN",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
},
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,
RefreshDirectoryInterval: 10 * time.Minute,
RefreshDirectoryTimeout: 1 * time.Minute,
QPS: 1.0,
AutocertOptions: AutocertOptions{
Folder: dataDir(),
},
DataBrokerStorageType: "memory",
SkipXffAppend: false,
XffNumTrustedHops: 0,
EnvoyAdminAccessLogPath: os.DevNull,
EnvoyAdminProfilePath: os.DevNull,
EnvoyAdminAddress: "127.0.0.1:9901",
ProgrammaticRedirectDomainWhitelist: []string{"localhost"},
}
// 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)
}
checkUnusedConfigFields(configFile, metadata.Unused)
// 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 checkUnusedConfigFields(configFile string, unused []string) {
keys := make([]string, 0, len(unused))
for _, k := range unused {
if !strings.HasPrefix(k, "policy[") { // policy's embedded protobuf structs are decoded by separate hook and are unknown to mapstructure
keys = append(keys, k)
}
}
if len(keys) == 0 {
return
}
log.Warn(context.Background()).Str("config_file", configFile).Strs("keys", keys).Msg("config contained unknown keys that were ignored")
}
// 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(ctx 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:
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.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.DataBrokerURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.DataBrokerURLString)
if err != nil {
return fmt.Errorf("config: bad databroker service url %s : %w", o.DataBrokerURLString, err)
}
}
if o.ForwardAuthURLString != "" {
_, err := urlutil.ParseAndValidateURL(o.ForwardAuthURLString)
if err != nil {
return fmt.Errorf("config: bad forward-auth-url %s : %w", o.ForwardAuthURLString, 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 := ioutil.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)
}
}
// if no service account was defined, there should not be any policies that
// assert group membership (except for azure which can be derived from the client
// id, secret and provider url)
if o.ServiceAccount == "" && o.Provider != "azure" {
for _, p := range o.GetAllPolicies() {
if len(p.AllowedGroups) != 0 {
return fmt.Errorf("config: `allowed_groups` requires `idp_service_account`")
}
}
}
// 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")
}
switch o.Provider {
case azure.Name, github.Name, gitlab.Name, google.Name, okta.Name, onelogin.Name:
if len(o.Scopes) > 0 {
log.Warn(ctx).Msg(idpCustomScopesWarnMsg)
}
default:
}
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
}
// 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)
}
// 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...)...)
}
// 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...)...)
}
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
}
// GetForwardAuthURL returns the ForwardAuthURL.
func (o *Options) GetForwardAuthURL() (*url.URL, error) {
rawurl := o.ForwardAuthURLString
if rawurl == "" {
return nil, nil
}
return urlutil.ParseAndValidateURL(rawurl)
}
// 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 IsAll(o.Services) {
return true
}
return o.GRPCInsecure
}
// 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,
})
return oauth.Options{
RedirectURL: redirectURL,
ProviderName: o.Provider,
ProviderURL: o.ProviderURL,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
Scopes: o.Scopes,
ServiceAccount: o.ServiceAccount,
}, 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 ioutil.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
// mutual auth between services on the same host can be generated at runtime
if IsAll(o.Services) && o.SharedKey == "" && o.DataBrokerStorageType == StorageInMemoryName {
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)
}
// GetGoogleCloudServerlessAuthenticationServiceAccount gets the GoogleCloudServerlessAuthenticationServiceAccount.
func (o *Options) GetGoogleCloudServerlessAuthenticationServiceAccount() string {
if o.GoogleCloudServerlessAuthenticationServiceAccount == "" && o.Provider == "google" {
return o.ServiceAccount
}
return o.GoogleCloudServerlessAuthenticationServiceAccount
}
// GetSetResponseHeaders gets the SetResponseHeaders.
func (o *Options) GetSetResponseHeaders() map[string]string {
if _, ok := o.SetResponseHeaders[DisableHeaderKey]; ok {
return map[string]string{}
}
return o.SetResponseHeaders
}
// GetQPS gets the QPS.
func (o *Options) GetQPS() float64 {
if o.QPS < 1 {
return 1
}
return o.QPS
}
// GetCodecType gets a codec type.
func (o *Options) GetCodecType() CodecType {
if IsAll(o.Services) && o.CodecType == CodecTypeAuto {
return CodecTypeHTTP1
}
return o.CodecType
}
// 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 data, err := base64.StdEncoding.DecodeString(o.Cert); err != nil {
log.Error(ctx).Err(err).Msg("bad base64 for local cert: skipped")
} else if cert, err := cryptutil.ParsePEMCertificate(data); err != nil {
log.Error(ctx).Err(err).Msg("parsing local cert: skipped")
} else {
idx.addCert(cert)
}
}
for _, c := range o.CertificateFiles {
cert, err := cryptutil.ParsePEMCertificateFromFile(c.CertFile)
if err != nil {
log.Error(ctx).Err(err).Str("file", c.CertFile).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 {
cert, err := cryptutil.ParsePEMCertificate(c.CertBytes)
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
}
cfp := certificateFilePair{
CertFile: c.CertFile,
KeyFile: c.KeyFile,
}
if cfp.CertFile == "" {
cfp.CertFile = base64.StdEncoding.EncodeToString(c.CertBytes)
}
if cfp.KeyFile == "" {
cfp.KeyFile = base64.StdEncoding.EncodeToString(c.KeyBytes)
}
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
}
if settings.InstallationId != nil {
o.InstallationID = settings.GetInstallationId()
}
if settings.Debug != nil {
o.Debug = settings.GetDebug()
}
if settings.LogLevel != nil {
o.LogLevel = settings.GetLogLevel()
}
if settings.ProxyLogLevel != nil {
o.ProxyLogLevel = settings.GetProxyLogLevel()
}
if settings.SharedSecret != nil {
o.SharedKey = settings.GetSharedSecret()
}
if settings.Services != nil {
o.Services = settings.GetServices()
}
if settings.Address != nil {
o.Addr = settings.GetAddress()
}
if settings.InsecureServer != nil {
o.InsecureServer = settings.GetInsecureServer()
}
if settings.DnsLookupFamily != nil {
o.DNSLookupFamily = settings.GetDnsLookupFamily()
}
o.applyExternalCerts(ctx, settings.GetCertificates())
if settings.HttpRedirectAddr != nil {
o.HTTPRedirectAddr = settings.GetHttpRedirectAddr()
}
if settings.TimeoutRead != nil {
o.ReadTimeout = settings.GetTimeoutRead().AsDuration()
}
if settings.TimeoutWrite != nil {
o.WriteTimeout = settings.GetTimeoutWrite().AsDuration()
}
if settings.TimeoutIdle != nil {
o.IdleTimeout = settings.GetTimeoutIdle().AsDuration()
}
if settings.AuthenticateServiceUrl != nil {
o.AuthenticateURLString = settings.GetAuthenticateServiceUrl()
}
if settings.AuthenticateCallbackPath != nil {
o.AuthenticateCallbackPath = settings.GetAuthenticateCallbackPath()
}
if settings.CookieName != nil {
o.CookieName = settings.GetCookieName()
}
if settings.CookieSecret != nil {
o.CookieSecret = settings.GetCookieSecret()
}
if settings.CookieDomain != nil {
o.CookieDomain = settings.GetCookieDomain()
}
if settings.CookieSecure != nil {
o.CookieSecure = settings.GetCookieSecure()
}
if settings.CookieHttpOnly != nil {
o.CookieHTTPOnly = settings.GetCookieHttpOnly()
}
if settings.CookieExpire != nil {
o.CookieExpire = settings.GetCookieExpire().AsDuration()
}
if settings.IdpClientId != nil {
o.ClientID = settings.GetIdpClientId()
}
if settings.IdpClientSecret != nil {
o.ClientSecret = settings.GetIdpClientSecret()
}
if settings.IdpProvider != nil {
o.Provider = settings.GetIdpProvider()
}
if settings.IdpProviderUrl != nil {
o.ProviderURL = settings.GetIdpProviderUrl()
}
if len(settings.Scopes) > 0 {
o.Scopes = settings.Scopes
}
if settings.IdpServiceAccount != nil {
o.ServiceAccount = settings.GetIdpServiceAccount()
}
if settings.IdpRefreshDirectoryTimeout != nil {
o.RefreshDirectoryTimeout = settings.GetIdpRefreshDirectoryTimeout().AsDuration()
}
if settings.IdpRefreshDirectoryInterval != nil {
o.RefreshDirectoryInterval = settings.GetIdpRefreshDirectoryInterval().AsDuration()
}
if settings.RequestParams != nil && len(settings.RequestParams) > 0 {
o.RequestParams = settings.RequestParams
}
if len(settings.AuthorizeServiceUrls) > 0 {
o.AuthorizeURLStrings = settings.GetAuthorizeServiceUrls()
}
if settings.OverrideCertificateName != nil {
o.OverrideCertificateName = settings.GetOverrideCertificateName()
}
if settings.CertificateAuthority != nil {
o.CA = settings.GetCertificateAuthority()
}
if settings.CertificateAuthorityFile != nil {
o.CAFile = settings.GetCertificateAuthorityFile()
}
if settings.SigningKey != nil {
o.SigningKey = settings.GetSigningKey()
}
if settings.SetResponseHeaders != nil && len(settings.SetResponseHeaders) > 0 {
o.SetResponseHeaders = settings.SetResponseHeaders
}
if len(settings.JwtClaimsHeaders) > 0 {
o.JWTClaimsHeaders = settings.GetJwtClaimsHeaders()
}
if settings.DefaultUpstreamTimeout != nil {
o.DefaultUpstreamTimeout = settings.GetDefaultUpstreamTimeout().AsDuration()
}
if settings.MetricsAddress != nil {
o.MetricsAddr = settings.GetMetricsAddress()
}
if settings.MetricsBasicAuth != nil {
o.MetricsBasicAuth = settings.GetMetricsBasicAuth()
}
if len(settings.GetMetricsCertificate().GetCertBytes()) > 0 {
o.MetricsCertificate = base64.StdEncoding.EncodeToString(settings.GetMetricsCertificate().GetCertBytes())
}
if len(settings.GetMetricsCertificate().GetKeyBytes()) > 0 {
o.MetricsCertificateKey = base64.StdEncoding.EncodeToString(settings.GetMetricsCertificate().GetKeyBytes())
}
if settings.GetMetricsCertificate().GetCertFile() != "" {
o.MetricsCertificateFile = settings.GetMetricsCertificate().GetCertFile()
}
if settings.GetMetricsCertificate().GetKeyFile() != "" {
o.MetricsCertificateKeyFile = settings.GetMetricsCertificate().GetKeyFile()
}
if settings.GetMetricsClientCa() != "" {
o.MetricsClientCA = settings.GetMetricsClientCa()
}
if settings.GetMetricsClientCaFile() != "" {
o.MetricsClientCAFile = settings.GetMetricsClientCaFile()
}
if settings.TracingProvider != nil {
o.TracingProvider = settings.GetTracingProvider()
}
if settings.TracingSampleRate != nil {
o.TracingSampleRate = settings.GetTracingSampleRate()
}
if settings.TracingJaegerCollectorEndpoint != nil {
o.TracingJaegerCollectorEndpoint = settings.GetTracingJaegerCollectorEndpoint()
}
if settings.TracingJaegerAgentEndpoint != nil {
o.TracingJaegerAgentEndpoint = settings.GetTracingJaegerAgentEndpoint()
}
if settings.TracingZipkinEndpoint != nil {
o.ZipkinEndpoint = settings.GetTracingZipkinEndpoint()
}
if settings.GrpcAddress != nil {
o.GRPCAddr = settings.GetGrpcAddress()
}
if settings.GrpcInsecure != nil {
o.GRPCInsecure = settings.GetGrpcInsecure()
}
if settings.ForwardAuthUrl != nil {
o.ForwardAuthURLString = settings.GetForwardAuthUrl()
}
if len(settings.DatabrokerServiceUrls) > 0 {
o.DataBrokerURLStrings = settings.GetDatabrokerServiceUrls()
}
if settings.ClientCa != nil {
o.ClientCA = settings.GetClientCa()
}
if settings.ClientCaFile != nil {
o.ClientCAFile = settings.GetClientCaFile()
}
if settings.GoogleCloudServerlessAuthenticationServiceAccount != nil {
o.GoogleCloudServerlessAuthenticationServiceAccount = settings.GetGoogleCloudServerlessAuthenticationServiceAccount()
}
if settings.Autocert != nil {
o.AutocertOptions.Enable = settings.GetAutocert()
}
if settings.AutocertCa != nil {
o.AutocertOptions.CA = settings.GetAutocertCa()
}
if settings.AutocertEmail != nil {
o.AutocertOptions.Email = settings.GetAutocertEmail()
}
if settings.AutocertEabKeyId != nil {
o.AutocertOptions.EABKeyID = settings.GetAutocertEabKeyId()
}
if settings.AutocertEabMacKey != nil {
o.AutocertOptions.EABMACKey = settings.GetAutocertEabMacKey()
}
if settings.AutocertUseStaging != nil {
o.AutocertOptions.UseStaging = settings.GetAutocertUseStaging()
}
if settings.AutocertMustStaple != nil {
o.AutocertOptions.MustStaple = settings.GetAutocertMustStaple()
}
if settings.AutocertDir != nil {
o.AutocertOptions.Folder = settings.GetAutocertDir()
}
if settings.AutocertTrustedCa != nil {
o.AutocertOptions.TrustedCA = settings.GetAutocertTrustedCa()
}
if settings.AutocertTrustedCaFile != nil {
o.AutocertOptions.TrustedCAFile = settings.GetAutocertTrustedCaFile()
}
if settings.SkipXffAppend != nil {
o.SkipXffAppend = settings.GetSkipXffAppend()
}
if settings.XffNumTrustedHops != nil {
o.XffNumTrustedHops = settings.GetXffNumTrustedHops()
}
if len(settings.ProgrammaticRedirectDomainWhitelist) > 0 {
o.ProgrammaticRedirectDomainWhitelist = settings.GetProgrammaticRedirectDomainWhitelist()
}
if settings.AuditKey != nil {
o.AuditKey = &PublicKeyEncryptionKeyOptions{
ID: settings.AuditKey.GetId(),
Data: base64.StdEncoding.EncodeToString(settings.AuditKey.GetData()),
}
}
if settings.CodecType != nil {
o.CodecType = CodecTypeFromEnvoy(settings.GetCodecType())
}
if settings.ClientCrl != nil {
o.ClientCRL = settings.GetClientCrl()
}
if settings.ClientCrlFile != nil {
o.ClientCRLFile = settings.GetClientCrlFile()
}
}
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
}
// AtomicOptions are Options that can be access atomically.
type AtomicOptions struct {
value atomic.Value
}
// NewAtomicOptions creates a new AtomicOptions.
func NewAtomicOptions() *AtomicOptions {
ao := new(AtomicOptions)
ao.Store(new(Options))
return ao
}
// Load loads the options.
func (a *AtomicOptions) Load() *Options {
return a.value.Load().(*Options)
}
// Store stores the options.
func (a *AtomicOptions) Store(options *Options) {
a.value.Store(options)
}