package configuration import ( "fmt" "strconv" "strings" "github.com/containous/traefik/log" "github.com/containous/traefik/tls" "github.com/containous/traefik/types" ) // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) type EntryPoint struct { Address string TLS *tls.TLS `export:"true"` Redirect *types.Redirect `export:"true"` Auth *types.Auth `export:"true"` WhiteList *types.WhiteList `export:"true"` Compress *Compress `export:"true"` ProxyProtocol *ProxyProtocol `export:"true"` ForwardedHeaders *ForwardedHeaders `export:"true"` ClientIPStrategy *types.IPStrategy `export:"true"` } // Compress contains compress configuration type Compress struct{} // ProxyProtocol contains Proxy-Protocol configuration type ProxyProtocol struct { Insecure bool `export:"true"` TrustedIPs []string } // ForwardedHeaders Trust client forwarding headers type ForwardedHeaders struct { Insecure bool `export:"true"` TrustedIPs []string } // EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...) type EntryPoints map[string]*EntryPoint // String is the method to format the flag's value, part of the flag.Value interface. // The String method's output will be used in diagnostics. func (ep EntryPoints) String() string { return fmt.Sprintf("%+v", map[string]*EntryPoint(ep)) } // Get return the EntryPoints map func (ep *EntryPoints) Get() interface{} { return *ep } // SetValue sets the EntryPoints map with val func (ep *EntryPoints) SetValue(val interface{}) { *ep = val.(EntryPoints) } // Type is type of the struct func (ep *EntryPoints) Type() string { return "entrypoints" } // Set is the method to set the flag value, part of the flag.Value interface. // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (ep *EntryPoints) Set(value string) error { result := parseEntryPointsConfiguration(value) var compress *Compress if len(result["compress"]) > 0 { compress = &Compress{} } configTLS, err := makeEntryPointTLS(result) if err != nil { return err } (*ep)[result["name"]] = &EntryPoint{ Address: result["address"], TLS: configTLS, Auth: makeEntryPointAuth(result), Redirect: makeEntryPointRedirect(result), Compress: compress, WhiteList: makeWhiteList(result), ProxyProtocol: makeEntryPointProxyProtocol(result), ForwardedHeaders: makeEntryPointForwardedHeaders(result), ClientIPStrategy: makeIPStrategy("clientipstrategy", result), } return nil } func makeWhiteList(result map[string]string) *types.WhiteList { if rawRange, ok := result["whitelist_sourcerange"]; ok { return &types.WhiteList{ SourceRange: strings.Split(rawRange, ","), IPStrategy: makeIPStrategy("whitelist_ipstrategy", result), } } return nil } func makeIPStrategy(prefix string, result map[string]string) *types.IPStrategy { depth := toInt(result, prefix+"_depth") excludedIPs := result[prefix+"_excludedips"] if depth == 0 && len(excludedIPs) == 0 { return nil } return &types.IPStrategy{ Depth: depth, ExcludedIPs: strings.Split(excludedIPs, ","), } } func makeEntryPointAuth(result map[string]string) *types.Auth { var basic *types.Basic if v, ok := result["auth_basic_users"]; ok { basic = &types.Basic{ Realm: result["auth_basic_realm"], Users: strings.Split(v, ","), RemoveHeader: toBool(result, "auth_basic_removeheader"), } } var digest *types.Digest if v, ok := result["auth_digest_users"]; ok { digest = &types.Digest{ Users: strings.Split(v, ","), RemoveHeader: toBool(result, "auth_digest_removeheader"), } } var forward *types.Forward if address, ok := result["auth_forward_address"]; ok { var clientTLS *types.ClientTLS cert := result["auth_forward_tls_cert"] key := result["auth_forward_tls_key"] insecureSkipVerify := toBool(result, "auth_forward_tls_insecureskipverify") if len(cert) > 0 && len(key) > 0 || insecureSkipVerify { clientTLS = &types.ClientTLS{ CA: result["auth_forward_tls_ca"], CAOptional: toBool(result, "auth_forward_tls_caoptional"), Cert: cert, Key: key, InsecureSkipVerify: insecureSkipVerify, } } var authResponseHeaders []string if v, ok := result["auth_forward_authresponseheaders"]; ok { authResponseHeaders = strings.Split(v, ",") } forward = &types.Forward{ Address: address, TLS: clientTLS, TrustForwardHeader: toBool(result, "auth_forward_trustforwardheader"), AuthResponseHeaders: authResponseHeaders, } } var auth *types.Auth if basic != nil || digest != nil || forward != nil { auth = &types.Auth{ Basic: basic, Digest: digest, Forward: forward, HeaderField: result["auth_headerfield"], } } return auth } func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol { var proxyProtocol *ProxyProtocol ppTrustedIPs := result["proxyprotocol_trustedips"] if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 { proxyProtocol = &ProxyProtocol{ Insecure: toBool(result, "proxyprotocol_insecure"), } if len(ppTrustedIPs) > 0 { proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",") } } if proxyProtocol != nil && proxyProtocol.Insecure { log.Warn("ProxyProtocol.insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.insecure:true'") } return proxyProtocol } func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders { forwardedHeaders := &ForwardedHeaders{} if _, ok := result["forwardedheaders_insecure"]; ok { forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure") } fhTrustedIPs := result["forwardedheaders_trustedips"] if len(fhTrustedIPs) > 0 { // TODO must be removed in the next breaking version. forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure") forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",") } return forwardedHeaders } func makeEntryPointRedirect(result map[string]string) *types.Redirect { var redirect *types.Redirect if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 { redirect = &types.Redirect{ EntryPoint: result["redirect_entrypoint"], Regex: result["redirect_regex"], Replacement: result["redirect_replacement"], Permanent: toBool(result, "redirect_permanent"), } } return redirect } func makeEntryPointTLS(result map[string]string) (*tls.TLS, error) { var configTLS *tls.TLS if len(result["tls"]) > 0 { certs := tls.Certificates{} if err := certs.Set(result["tls"]); err != nil { return nil, err } configTLS = &tls.TLS{ Certificates: certs, } } else if len(result["tls_acme"]) > 0 { configTLS = &tls.TLS{ Certificates: tls.Certificates{}, } } if configTLS != nil { if len(result["ca"]) > 0 { files := tls.FilesOrContents{} files.Set(result["ca"]) optional := toBool(result, "ca_optional") configTLS.ClientCA = tls.ClientCA{ Files: files, Optional: optional, } } if len(result["tls_minversion"]) > 0 { configTLS.MinVersion = result["tls_minversion"] } if len(result["tls_ciphersuites"]) > 0 { configTLS.CipherSuites = strings.Split(result["tls_ciphersuites"], ",") } if len(result["tls_snistrict"]) > 0 { configTLS.SniStrict = toBool(result, "tls_snistrict") } if len(result["tls_defaultcertificate_cert"]) > 0 && len(result["tls_defaultcertificate_key"]) > 0 { configTLS.DefaultCertificate = &tls.Certificate{ CertFile: tls.FileOrContent(result["tls_defaultcertificate_cert"]), KeyFile: tls.FileOrContent(result["tls_defaultcertificate_key"]), } } } return configTLS, nil } func parseEntryPointsConfiguration(raw string) map[string]string { sections := strings.Fields(raw) config := make(map[string]string) for _, part := range sections { field := strings.SplitN(part, ":", 2) name := strings.ToLower(strings.Replace(field[0], ".", "_", -1)) if len(field) > 1 { config[name] = field[1] } else { if strings.EqualFold(name, "TLS") { config["tls_acme"] = "TLS" } else { config[name] = "" } } } return config } func toBool(conf map[string]string, key string) bool { if val, ok := conf[key]; ok { return strings.EqualFold(val, "true") || strings.EqualFold(val, "enable") || strings.EqualFold(val, "on") } return false } func toInt(conf map[string]string, key string) int { if val, ok := conf[key]; ok { intVal, err := strconv.Atoi(val) if err != nil { return 0 } return intVal } return 0 }