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
}
GetMetricsCertificate returns the metrics certificate to use for TLS. `nil` will be
returned if there is no certificate.
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"
)
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
)
const DefaultAlternativeAddr = ":5443"
var EnvoyAdminURL = &url.URL{Host: "127.0.0.1:9901", Scheme: "http"}
var randomSharedKey = cryptutil.NewBase64Key()
type Options struct {
	InstallationID string `mapstructure:"installation_id" yaml:"installation_id,omitempty"`
	Debug bool `mapstructure:"pomerium_debug" yaml:"pomerium_debug,omitempty"`
	LogLevel string `mapstructure:"log_level" yaml:"log_level,omitempty"`
	ProxyLogLevel string `mapstructure:"proxy_log_level" yaml:"proxy_log_level,omitempty"`
	SharedKey string `mapstructure:"shared_secret" yaml:"shared_secret,omitempty"`
	Services string `mapstructure:"services" yaml:"services,omitempty"`
	Addr string `mapstructure:"address" yaml:"address,omitempty"`
	InsecureServer bool `mapstructure:"insecure_server" yaml:"insecure_server,omitempty"`
	DNSLookupFamily string `mapstructure:"dns_lookup_family" yaml:"dns_lookup_family,omitempty"`
	CertificateFiles []certificateFilePair `mapstructure:"certificates" yaml:"certificates,omitempty"`
	Cert string `mapstructure:"certificate" yaml:"certificate,omitempty"`
	Key  string `mapstructure:"certificate_key" yaml:"certificate_key,omitempty"`
	CertFile string `mapstructure:"certificate_file" yaml:"certificate_file,omitempty"`
	KeyFile  string `mapstructure:"certificate_key_file" yaml:"certificate_key_file,omitempty"`
	HTTPRedirectAddr string `mapstructure:"http_redirect_addr" yaml:"http_redirect_addr,omitempty"`
	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   []Policy `mapstructure:"policy"`
	PolicyFile string   `mapstructure:"policy_file" yaml:"policy_file,omitempty"`
	Routes     []Policy `mapstructure:"routes"`
	AdditionalPolicies []Policy `yaml:"-"`
	AuthenticateURLString string `mapstructure:"authenticate_service_url" yaml:"authenticate_service_url,omitempty"`
	SignOutRedirectURLString string `mapstructure:"signout_redirect_url" yaml:"signout_redirect_url,omitempty"`
	AuthenticateCallbackPath string `mapstructure:"authenticate_callback_path" yaml:"authenticate_callback_path,omitempty"`
	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"`
	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"`
	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 map[string]string `mapstructure:"idp_request_params" yaml:"idp_request_params,omitempty"`
	AuthorizeURLString  string   `mapstructure:"authorize_service_url" yaml:"authorize_service_url,omitempty"`
	AuthorizeURLStrings []string `mapstructure:"authorize_service_urls" yaml:"authorize_service_urls,omitempty"`
	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 string `mapstructure:"signing_key" yaml:"signing_key,omitempty"`
	HeadersEnv string `yaml:",omitempty"`
	SetResponseHeaders map[string]string `yaml:",omitempty"`
	JWTClaimsHeaders JWTClaimHeaders `mapstructure:"jwt_claims_headers" yaml:"jwt_claims_headers,omitempty"`
	DefaultUpstreamTimeout time.Duration `mapstructure:"default_upstream_timeout" yaml:"default_upstream_timeout,omitempty"`
	MetricsAddr string `mapstructure:"metrics_address" yaml:"metrics_address,omitempty"`
	MetricsBasicAuth string `mapstructure:"metrics_basic_auth" yaml:"metrics_basic_auth,omitempty"`
	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"`
	TracingProvider   string  `mapstructure:"tracing_provider" yaml:"tracing_provider,omitempty"`
	TracingSampleRate float64 `mapstructure:"tracing_sample_rate" yaml:"tracing_sample_rate,omitempty"`
	TracingDatadogAddress string `mapstructure:"tracing_datadog_address" yaml:"tracing_datadog_address,omitempty"`
	TracingJaegerCollectorEndpoint string `mapstructure:"tracing_jaeger_collector_endpoint" yaml:"tracing_jaeger_collector_endpoint,omitempty"`
	TracingJaegerAgentEndpoint string `mapstructure:"tracing_jaeger_agent_endpoint" yaml:"tracing_jaeger_agent_endpoint,omitempty"`
	ZipkinEndpoint             string `mapstructure:"tracing_zipkin_endpoint" yaml:"tracing_zipkin_endpoint"`
	GRPCAddr string `mapstructure:"grpc_address" yaml:"grpc_address,omitempty"`
	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"`
	ForwardAuthURLString string `mapstructure:"forward_auth_url" yaml:"forward_auth_url,omitempty"`
	DataBrokerURLString  string   `mapstructure:"databroker_service_url" yaml:"databroker_service_url,omitempty"`
	DataBrokerURLStrings []string `mapstructure:"databroker_service_urls" yaml:"databroker_service_urls,omitempty"`
	DataBrokerStorageType string `mapstructure:"databroker_storage_type" yaml:"databroker_storage_type,omitempty"`
	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 string `mapstructure:"client_ca" yaml:"client_ca,omitempty"`
	ClientCAFile string `mapstructure:"client_ca_file" yaml:"client_ca_file,omitempty"`
	ClientCRL string `mapstructure:"client_crl" yaml:"client_crl,omitempty"`
	ClientCRLFile string `mapstructure:"client_crl_file" yaml:"client_crl_file,omitempty"`
	GoogleCloudServerlessAuthenticationServiceAccount string `mapstructure:"google_cloud_serverless_authentication_service_account" yaml:"google_cloud_serverless_authentication_service_account,omitempty"`  
	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 bool `mapstructure:"skip_xff_append" yaml:"skip_xff_append,omitempty" json:"skip_xff_append,omitempty"`
	XffNumTrustedHops uint32 `mapstructure:"xff_num_trusted_hops" yaml:"xff_num_trusted_hops,omitempty" json:"xff_num_trusted_hops,omitempty"`
	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 []string `mapstructure:"programmatic_redirect_domain_whitelist" yaml:"programmatic_redirect_domain_whitelist,omitempty" json:"programmatic_redirect_domain_whitelist,omitempty"`  
	CodecType CodecType `mapstructure:"codec_type" yaml:"codec_type"`
	AuditKey *PublicKeyEncryptionKeyOptions `mapstructure:"audit_key"`
}
type certificateFilePair struct {
	CertFile string `mapstructure:"cert" yaml:"cert,omitempty"`
	KeyFile  string `mapstructure:"key" yaml:"key,omitempty"`
}
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,  
	IdleTimeout:              5 * time.Minute,
	GRPCAddr:                 ":443",
	GRPCClientTimeout:        10 * time.Second,  
	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"},
}
func NewDefaultOptions() *Options {
	newOpts := defaultOptions
	newOpts.viper = viper.New()
	return &newOpts
}
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) {
	o := NewDefaultOptions()
	v := o.viper
	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)
	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[") {  
			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")
}
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
	}
	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)
}
func (o *Options) parseHeaders(ctx context.Context) error {
	var headers map[string]string
	if o.HeadersEnv != "" {
		if headers = o.viper.GetStringMapString("HeadersEnv"); len(headers) == 0 {
			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 {
					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
}
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)
		}
	}
	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)
	}
	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
}
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 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`")
			}
		}
	}
	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)
		}
	}
	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: 
	}
	return urlutil.ParseAndValidateURL(rawurl)
}
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...)...)
}
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
}
func (o *Options) GetForwardAuthURL() (*url.URL, error) {
	rawurl := o.ForwardAuthURLString
	if rawurl == "" {
		return nil, nil
	}
	return urlutil.ParseAndValidateURL(rawurl)
}
func (o *Options) GetGRPCAddr() string {
	if IsAll(o.Services) && o.GRPCAddr == defaultOptions.GRPCAddr {
		return DefaultAlternativeAddr
	}
	return o.GRPCAddr
}
func (o *Options) GetGRPCInsecure() bool {
	if IsAll(o.Services) {
		return true
	}
	return o.GRPCInsecure
}
func (o *Options) GetSignOutRedirectURL() (*url.URL, error) {
	rawurl := o.SignOutRedirectURLString
	if rawurl == "" {
		return nil, nil
	}
	return urlutil.ParseAndValidateURL(rawurl)
}