package dynamic import ( "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "os" "github.com/containous/traefik/pkg/ip" "github.com/containous/traefik/pkg/types" ) // +k8s:deepcopy-gen=true // Middleware holds the Middleware configuration. type Middleware struct { AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"` StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"` StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"` ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"` ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"` Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"` IPWhiteList *IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"` Headers *Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` Errors *ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"` // TODO: disable temporarily // RateLimit *RateLimit `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"` RedirectRegex *RedirectRegex `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"` RedirectScheme *RedirectScheme `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"` BasicAuth *BasicAuth `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"` DigestAuth *DigestAuth `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"` ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"` MaxConn *MaxConn `json:"maxConn,omitempty" toml:"maxConn,omitempty" yaml:"maxConn,omitempty"` Buffering *Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"` CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"` Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"` PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"` Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"` } // +k8s:deepcopy-gen=true // AddPrefix holds the AddPrefix configuration. type AddPrefix struct { Prefix string `json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty"` } // +k8s:deepcopy-gen=true // Auth holds the authentication configuration (BASIC, DIGEST, users). type Auth struct { Basic *BasicAuth `json:"basic,omitempty" toml:"basic,omitempty" yaml:"basic,omitempty" export:"true"` Digest *DigestAuth `json:"digest,omitempty" toml:"digest,omitempty" yaml:"digest,omitempty" export:"true"` Forward *ForwardAuth `json:"forward,omitempty" toml:"forward,omitempty" yaml:"forward,omitempty" export:"true"` } // +k8s:deepcopy-gen=true // BasicAuth holds the HTTP basic authentication configuration. type BasicAuth struct { Users Users `json:"users,omitempty" toml:"users,omitempty" yaml:"users,omitempty"` UsersFile string `json:"usersFile,omitempty" toml:"usersFile,omitempty" yaml:"usersFile,omitempty"` Realm string `json:"realm,omitempty" toml:"realm,omitempty" yaml:"realm,omitempty"` RemoveHeader bool `json:"removeHeader,omitempty" toml:"removeHeader,omitempty" yaml:"removeHeader,omitempty"` HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } // +k8s:deepcopy-gen=true // Buffering holds the request/response buffering configuration. type Buffering struct { MaxRequestBodyBytes int64 `json:"maxRequestBodyBytes,omitempty" toml:"maxRequestBodyBytes,omitempty" yaml:"maxRequestBodyBytes,omitempty"` MemRequestBodyBytes int64 `json:"memRequestBodyBytes,omitempty" toml:"memRequestBodyBytes,omitempty" yaml:"memRequestBodyBytes,omitempty"` MaxResponseBodyBytes int64 `json:"maxResponseBodyBytes,omitempty" toml:"maxResponseBodyBytes,omitempty" yaml:"maxResponseBodyBytes,omitempty"` MemResponseBodyBytes int64 `json:"memResponseBodyBytes,omitempty" toml:"memResponseBodyBytes,omitempty" yaml:"memResponseBodyBytes,omitempty"` RetryExpression string `json:"retryExpression,omitempty" toml:"retryExpression,omitempty" yaml:"retryExpression,omitempty"` } // +k8s:deepcopy-gen=true // Chain holds a chain of middlewares type Chain struct { Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"` } // +k8s:deepcopy-gen=true // CircuitBreaker holds the circuit breaker configuration. type CircuitBreaker struct { Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty"` } // +k8s:deepcopy-gen=true // Compress holds the compress configuration. type Compress struct{} // +k8s:deepcopy-gen=true // DigestAuth holds the Digest HTTP authentication configuration. type DigestAuth struct { Users Users `json:"users,omitempty" toml:"users,omitempty" yaml:"users,omitempty"` UsersFile string `json:"usersFile,omitempty" toml:"usersFile,omitempty" yaml:"usersFile,omitempty"` RemoveHeader bool `json:"removeHeader,omitempty" toml:"removeHeader,omitempty" yaml:"removeHeader,omitempty"` Realm string `json:"realm,omitempty" toml:"realm,omitempty" yaml:"realm,omitempty"` HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } // +k8s:deepcopy-gen=true // ErrorPage holds the custom error page configuration. type ErrorPage struct { Status []string `json:"status,omitempty" toml:"status,omitempty" yaml:"status,omitempty"` Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty"` Query string `json:"query,omitempty" toml:"query,omitempty" yaml:"query,omitempty"` } // +k8s:deepcopy-gen=true // ForwardAuth holds the http forward authentication configuration. type ForwardAuth struct { Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"` TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"` AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty"` } // +k8s:deepcopy-gen=true // Headers holds the custom header configuration. type Headers struct { CustomRequestHeaders map[string]string `json:"customRequestHeaders,omitempty" toml:"customRequestHeaders,omitempty" yaml:"customRequestHeaders,omitempty"` CustomResponseHeaders map[string]string `json:"customResponseHeaders,omitempty" toml:"customResponseHeaders,omitempty" yaml:"customResponseHeaders,omitempty"` // AccessControlAllowCredentials is only valid if true. false is ignored. AccessControlAllowCredentials bool `json:"accessControlAllowCredentials,omitempty" toml:"accessControlAllowCredentials,omitempty" yaml:"accessControlAllowCredentials,omitempty"` // AccessControlAllowHeaders must be used in response to a preflight request with Access-Control-Request-Headers set. AccessControlAllowHeaders []string `json:"accessControlAllowHeaders,omitempty" toml:"accessControlAllowHeaders,omitempty" yaml:"accessControlAllowHeaders,omitempty"` // AccessControlAllowMethods must be used in response to a preflight request with Access-Control-Request-Method set. AccessControlAllowMethods []string `json:"accessControlAllowMethods,omitempty" toml:"accessControlAllowMethods,omitempty" yaml:"accessControlAllowMethods,omitempty"` // AccessControlAllowOrigin Can be "origin-list-or-null" or "*". From (https://www.w3.org/TR/cors/#access-control-allow-origin-response-header) AccessControlAllowOrigin string `json:"accessControlAllowOrigin,omitempty" toml:"accessControlAllowOrigin,omitempty" yaml:"accessControlAllowOrigin,omitempty"` // AccessControlExposeHeaders sets valid headers for the response. AccessControlExposeHeaders []string `json:"accessControlExposeHeaders,omitempty" toml:"accessControlExposeHeaders,omitempty" yaml:"accessControlExposeHeaders,omitempty"` // AccessControlMaxAge sets the time that a preflight request may be cached. AccessControlMaxAge int64 `json:"accessControlMaxAge,omitempty" toml:"accessControlMaxAge,omitempty" yaml:"accessControlMaxAge,omitempty"` // AddVaryHeader controls if the Vary header is automatically added/updated when the AccessControlAllowOrigin is set. AddVaryHeader bool `json:"addVaryHeader,omitempty" toml:"addVaryHeader,omitempty" yaml:"addVaryHeader,omitempty"` AllowedHosts []string `json:"allowedHosts,omitempty" toml:"allowedHosts,omitempty" yaml:"allowedHosts,omitempty"` HostsProxyHeaders []string `json:"hostsProxyHeaders,omitempty" toml:"hostsProxyHeaders,omitempty" yaml:"hostsProxyHeaders,omitempty"` SSLRedirect bool `json:"sslRedirect,omitempty" toml:"sslRedirect,omitempty" yaml:"sslRedirect,omitempty"` SSLTemporaryRedirect bool `json:"sslTemporaryRedirect,omitempty" toml:"sslTemporaryRedirect,omitempty" yaml:"sslTemporaryRedirect,omitempty"` SSLHost string `json:"sslHost,omitempty" toml:"sslHost,omitempty" yaml:"sslHost,omitempty"` SSLProxyHeaders map[string]string `json:"sslProxyHeaders,omitempty" toml:"sslProxyHeaders,omitempty" yaml:"sslProxyHeaders,omitempty"` SSLForceHost bool `json:"sslForceHost,omitempty" toml:"sslForceHost,omitempty" yaml:"sslForceHost,omitempty"` STSSeconds int64 `json:"stsSeconds,omitempty" toml:"stsSeconds,omitempty" yaml:"stsSeconds,omitempty"` STSIncludeSubdomains bool `json:"stsIncludeSubdomains,omitempty" toml:"stsIncludeSubdomains,omitempty" yaml:"stsIncludeSubdomains,omitempty"` STSPreload bool `json:"stsPreload,omitempty" toml:"stsPreload,omitempty" yaml:"stsPreload,omitempty"` ForceSTSHeader bool `json:"forceSTSHeader,omitempty" toml:"forceSTSHeader,omitempty" yaml:"forceSTSHeader,omitempty"` FrameDeny bool `json:"frameDeny,omitempty" toml:"frameDeny,omitempty" yaml:"frameDeny,omitempty"` CustomFrameOptionsValue string `json:"customFrameOptionsValue,omitempty" toml:"customFrameOptionsValue,omitempty" yaml:"customFrameOptionsValue,omitempty"` ContentTypeNosniff bool `json:"contentTypeNosniff,omitempty" toml:"contentTypeNosniff,omitempty" yaml:"contentTypeNosniff,omitempty"` BrowserXSSFilter bool `json:"browserXssFilter,omitempty" toml:"browserXssFilter,omitempty" yaml:"browserXssFilter,omitempty"` CustomBrowserXSSValue string `json:"customBrowserXSSValue,omitempty" toml:"customBrowserXSSValue,omitempty" yaml:"customBrowserXSSValue,omitempty"` ContentSecurityPolicy string `json:"contentSecurityPolicy,omitempty" toml:"contentSecurityPolicy,omitempty" yaml:"contentSecurityPolicy,omitempty"` PublicKey string `json:"publicKey,omitempty" toml:"publicKey,omitempty" yaml:"publicKey,omitempty"` ReferrerPolicy string `json:"referrerPolicy,omitempty" toml:"referrerPolicy,omitempty" yaml:"referrerPolicy,omitempty"` FeaturePolicy string `json:"featurePolicy,omitempty" toml:"featurePolicy,omitempty" yaml:"featurePolicy,omitempty"` IsDevelopment bool `json:"isDevelopment,omitempty" toml:"isDevelopment,omitempty" yaml:"isDevelopment,omitempty"` } // HasCustomHeadersDefined checks to see if any of the custom header elements have been set func (h *Headers) HasCustomHeadersDefined() bool { return h != nil && (len(h.CustomResponseHeaders) != 0 || len(h.CustomRequestHeaders) != 0) } // HasCorsHeadersDefined checks to see if any of the cors header elements have been set func (h *Headers) HasCorsHeadersDefined() bool { return h != nil && (h.AccessControlAllowCredentials || len(h.AccessControlAllowHeaders) != 0 || len(h.AccessControlAllowMethods) != 0 || h.AccessControlAllowOrigin != "" || len(h.AccessControlExposeHeaders) != 0 || h.AccessControlMaxAge != 0 || h.AddVaryHeader) } // HasSecureHeadersDefined checks to see if any of the secure header elements have been set func (h *Headers) HasSecureHeadersDefined() bool { return h != nil && (len(h.AllowedHosts) != 0 || len(h.HostsProxyHeaders) != 0 || h.SSLRedirect || h.SSLTemporaryRedirect || h.SSLForceHost || h.SSLHost != "" || len(h.SSLProxyHeaders) != 0 || h.STSSeconds != 0 || h.STSIncludeSubdomains || h.STSPreload || h.ForceSTSHeader || h.FrameDeny || h.CustomFrameOptionsValue != "" || h.ContentTypeNosniff || h.BrowserXSSFilter || h.CustomBrowserXSSValue != "" || h.ContentSecurityPolicy != "" || h.PublicKey != "" || h.ReferrerPolicy != "" || h.FeaturePolicy != "" || h.IsDevelopment) } // +k8s:deepcopy-gen=true // IPStrategy holds the ip strategy configuration. type IPStrategy struct { Depth int `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"` ExcludedIPs []string `json:"excludedIPs,omitempty" toml:"excludedIPs,omitempty" yaml:"excludedIPs,omitempty"` } // Get an IP selection strategy // if nil return the RemoteAddr strategy // else return a strategy base on the configuration using the X-Forwarded-For Header. // Depth override the ExcludedIPs func (s *IPStrategy) Get() (ip.Strategy, error) { if s == nil { return &ip.RemoteAddrStrategy{}, nil } if s.Depth > 0 { return &ip.DepthStrategy{ Depth: s.Depth, }, nil } if len(s.ExcludedIPs) > 0 { checker, err := ip.NewChecker(s.ExcludedIPs) if err != nil { return nil, err } return &ip.CheckerStrategy{ Checker: checker, }, nil } return &ip.RemoteAddrStrategy{}, nil } // +k8s:deepcopy-gen=true // IPWhiteList holds the ip white list configuration. type IPWhiteList struct { SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty"` } // +k8s:deepcopy-gen=true // MaxConn holds maximum connection configuration. type MaxConn struct { Amount int64 `json:"amount,omitempty" toml:"amount,omitempty" yaml:"amount,omitempty"` ExtractorFunc string `json:"extractorFunc,omitempty" toml:"extractorFunc,omitempty" yaml:"extractorFunc,omitempty"` } // SetDefaults Default values for a MaxConn. func (m *MaxConn) SetDefaults() { m.ExtractorFunc = "request.host" } // +k8s:deepcopy-gen=true // PassTLSClientCert holds the TLS client cert headers configuration. type PassTLSClientCert struct { PEM bool `json:"pem,omitempty" toml:"pem,omitempty" yaml:"pem,omitempty"` Info *TLSClientCertificateInfo `json:"info,omitempty" toml:"info,omitempty" yaml:"info,omitempty"` } // +k8s:deepcopy-gen=true // Rate holds the rate limiting configuration for a specific time period. type Rate struct { Period types.Duration `json:"period,omitempty" toml:"period,omitempty" yaml:"period,omitempty"` Average int64 `json:"average,omitempty" toml:"average,omitempty" yaml:"average,omitempty"` Burst int64 `json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty"` } // +k8s:deepcopy-gen=true // RateLimit holds the rate limiting configuration for a given frontend. type RateLimit struct { RateSet map[string]*Rate `json:"rateSet,omitempty" toml:"rateSet,omitempty" yaml:"rateSet,omitempty"` // FIXME replace by ipStrategy see oxy and replace ExtractorFunc string `json:"extractorFunc,omitempty" toml:"extractorFunc,omitempty" yaml:"extractorFunc,omitempty"` } // SetDefaults Default values for a MaxConn. func (r *RateLimit) SetDefaults() { r.ExtractorFunc = "request.host" } // +k8s:deepcopy-gen=true // RedirectRegex holds the redirection configuration. type RedirectRegex struct { Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty"` Replacement string `json:"replacement,omitempty" toml:"replacement,omitempty" yaml:"replacement,omitempty"` Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty"` } // +k8s:deepcopy-gen=true // RedirectScheme holds the scheme redirection configuration. type RedirectScheme struct { Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty"` Port string `json:"port,omitempty" toml:"port,omitempty" yaml:"port,omitempty"` Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty"` } // +k8s:deepcopy-gen=true // ReplacePath holds the ReplacePath configuration. type ReplacePath struct { Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty"` } // +k8s:deepcopy-gen=true // ReplacePathRegex holds the ReplacePathRegex configuration. type ReplacePathRegex struct { Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty"` Replacement string `json:"replacement,omitempty" toml:"replacement,omitempty" yaml:"replacement,omitempty"` } // +k8s:deepcopy-gen=true // Retry holds the retry configuration. type Retry struct { Attempts int `json:"attempts,omitempty" toml:"attempts,omitempty" yaml:"attempts,omitempty" export:"true"` } // +k8s:deepcopy-gen=true // StripPrefix holds the StripPrefix configuration. type StripPrefix struct { Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty"` } // +k8s:deepcopy-gen=true // StripPrefixRegex holds the StripPrefixRegex configuration. type StripPrefixRegex struct { Regex []string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty"` } // +k8s:deepcopy-gen=true // TLSClientCertificateInfo holds the client TLS certificate info configuration. type TLSClientCertificateInfo struct { NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty"` NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty"` Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"` Subject *TLSCLientCertificateDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty"` Issuer *TLSCLientCertificateDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty"` } // +k8s:deepcopy-gen=true // TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration // cf https://tools.ietf.org/html/rfc3739 type TLSCLientCertificateDNInfo struct { Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty"` Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty"` Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty"` Organization bool `json:"organization,omitempty" toml:"organization,omitempty" yaml:"organization,omitempty"` CommonName bool `json:"commonName,omitempty" toml:"commonName,omitempty" yaml:"commonName,omitempty"` SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty"` DomainComponent bool `json:"domainComponent,omitempty" toml:"domainComponent,omitempty" yaml:"domainComponent,omitempty"` } // +k8s:deepcopy-gen=true // Users holds a list of users type Users []string // +k8s:deepcopy-gen=true // ClientTLS holds the TLS specific configurations as client // CA, Cert and Key can be either path or file contents. type ClientTLS struct { CA string `json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"` CAOptional bool `json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty"` Cert string `json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"` Key string `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"` InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty"` } // CreateTLSConfig creates a TLS config from ClientTLS structures. func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) { if clientTLS == nil { return nil, nil } var err error caPool := x509.NewCertPool() clientAuth := tls.NoClientCert if clientTLS.CA != "" { var ca []byte if _, errCA := os.Stat(clientTLS.CA); errCA == nil { ca, err = ioutil.ReadFile(clientTLS.CA) if err != nil { return nil, fmt.Errorf("failed to read CA. %s", err) } } else { ca = []byte(clientTLS.CA) } if !caPool.AppendCertsFromPEM(ca) { return nil, fmt.Errorf("failed to parse CA") } if clientTLS.CAOptional { clientAuth = tls.VerifyClientCertIfGiven } else { clientAuth = tls.RequireAndVerifyClientCert } } cert := tls.Certificate{} _, errKeyIsFile := os.Stat(clientTLS.Key) if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) { return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created") } if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 { if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil { if errKeyIsFile == nil { cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key) if err != nil { return nil, fmt.Errorf("failed to load TLS keypair: %v", err) } } else { return nil, fmt.Errorf("tls cert is a file, but tls key is not") } } else { if errKeyIsFile != nil { cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key)) if err != nil { return nil, fmt.Errorf("failed to load TLS keypair: %v", err) } } else { return nil, fmt.Errorf("TLS key is a file, but tls cert is not") } } } return &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caPool, InsecureSkipVerify: clientTLS.InsecureSkipVerify, ClientAuth: clientAuth, }, nil }