diff --git a/cmd/traefik/anonymize/anonymize_config_test.go b/cmd/traefik/anonymize/anonymize_config_test.go index 0e6e7b6e4..ff3a340d2 100644 --- a/cmd/traefik/anonymize/anonymize_config_test.go +++ b/cmd/traefik/anonymize/anonymize_config_test.go @@ -44,7 +44,6 @@ func TestDo_globalConfiguration(t *testing.T) { config.LogLevel = "LogLevel" config.EntryPoints = configuration.EntryPoints{ "foo": { - Network: "foo Network", Address: "foo Address", TLS: &traefikTls.TLS{ MinVersion: "foo MinVersion", @@ -90,7 +89,6 @@ func TestDo_globalConfiguration(t *testing.T) { }, }, "fii": { - Network: "fii Network", Address: "fii Address", TLS: &traefikTls.TLS{ MinVersion: "fii MinVersion", diff --git a/cmd/traefik/anonymize/anonymize_doOnJSON_test.go b/cmd/traefik/anonymize/anonymize_doOnJSON_test.go index 91ebbf05f..1585bd47c 100644 --- a/cmd/traefik/anonymize/anonymize_doOnJSON_test.go +++ b/cmd/traefik/anonymize/anonymize_doOnJSON_test.go @@ -29,7 +29,6 @@ func Test_doOnJSON(t *testing.T) { "Compress": false }, "https": { - "Network": "", "Address": ":443", "TLS": { "MinVersion": "", @@ -119,7 +118,6 @@ func Test_doOnJSON(t *testing.T) { "Compress": false }, "https": { - "Network": "", "Address": ":443", "TLS": { "MinVersion": "", diff --git a/configuration/configuration.go b/configuration/configuration.go index 960690f4b..705719a23 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -313,157 +313,6 @@ func (dep *DefaultEntryPoints) Type() string { return "defaultentrypoints" } -// 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", *ep) -} - -// 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 configTLS *tls.TLS - if len(result["tls"]) > 0 { - certs := tls.Certificates{} - if err := certs.Set(result["tls"]); err != nil { - return err - } - configTLS = &tls.TLS{ - Certificates: certs, - } - } else if len(result["tls_acme"]) > 0 { - configTLS = &tls.TLS{ - Certificates: tls.Certificates{}, - } - } - if len(result["ca"]) > 0 { - files := strings.Split(result["ca"], ",") - optional := toBool(result, "ca_optional") - configTLS.ClientCA = tls.ClientCA{ - Files: files, - Optional: optional, - } - } - 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"], - } - } - - whiteListSourceRange := []string{} - if len(result["whitelistsourcerange"]) > 0 { - whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",") - } - - compress := toBool(result, "compress") - - 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, ",") - } - } - - // TODO must be changed to false by default in the next breaking version. - forwardedHeaders := &ForwardedHeaders{Insecure: true} - 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, ",") - } - - if proxyProtocol != nil && proxyProtocol.Insecure { - log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'") - } - - (*ep)[result["name"]] = &EntryPoint{ - Address: result["address"], - TLS: configTLS, - Redirect: redirect, - Compress: compress, - WhitelistSourceRange: whiteListSourceRange, - ProxyProtocol: proxyProtocol, - ForwardedHeaders: forwardedHeaders, - } - - return 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 -} - -// 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" -} - -// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) -type EntryPoint struct { - Network string - Address string - TLS *tls.TLS `export:"true"` - Redirect *types.Redirect `export:"true"` - Auth *types.Auth `export:"true"` - WhitelistSourceRange []string - Compress bool `export:"true"` - ProxyProtocol *ProxyProtocol `export:"true"` - ForwardedHeaders *ForwardedHeaders `export:"true"` -} - // Retry contains request retry config type Retry struct { Attempts int `description:"Number of attempts" export:"true"` diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 1acb639c6..fa6a15894 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -7,305 +7,10 @@ import ( "github.com/containous/flaeg" "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/file" - "github.com/containous/traefik/tls" - "github.com/containous/traefik/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const defaultConfigFile = "traefik.toml" -func Test_parseEntryPointsConfiguration(t *testing.T) { - testCases := []struct { - name string - value string - expectedResult map[string]string - }{ - { - name: "all parameters", - value: "Name:foo TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol.TrustedIPs:192.168.0.1 ProxyProtocol.Insecure:false Address::8000", - expectedResult: map[string]string{ - "name": "foo", - "address": ":8000", - "ca": "car", - "tls": "goo", - "tls_acme": "TLS", - "redirect_entrypoint": "RedirectEntryPoint", - "redirect_regex": "RedirectRegex", - "redirect_replacement": "RedirectReplacement", - "whitelistsourcerange": "WhiteListSourceRange", - "proxyprotocol_trustedips": "192.168.0.1", - "proxyprotocol_insecure": "false", - "compress": "true", - }, - }, - { - name: "compress on", - value: "name:foo Compress:on", - expectedResult: map[string]string{ - "name": "foo", - "compress": "on", - }, - }, - { - name: "TLS", - value: "Name:foo TLS:goo TLS", - expectedResult: map[string]string{ - "name": "foo", - "tls": "goo", - "tls_acme": "TLS", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - conf := parseEntryPointsConfiguration(test.value) - - assert.Len(t, conf, len(test.expectedResult)) - assert.Equal(t, test.expectedResult, conf) - }) - } -} - -func Test_toBool(t *testing.T) { - testCases := []struct { - name string - value string - key string - expectedBool bool - }{ - { - name: "on", - value: "on", - key: "foo", - expectedBool: true, - }, - { - name: "true", - value: "true", - key: "foo", - expectedBool: true, - }, - { - name: "enable", - value: "enable", - key: "foo", - expectedBool: true, - }, - { - name: "arbitrary string", - value: "bar", - key: "foo", - expectedBool: false, - }, - { - name: "no existing entry", - value: "bar", - key: "fii", - expectedBool: false, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - conf := map[string]string{ - "foo": test.value, - } - - result := toBool(conf, test.key) - - assert.Equal(t, test.expectedBool, result) - }) - } -} - -func TestEntryPoints_Set(t *testing.T) { - testCases := []struct { - name string - expression string - expectedEntryPointName string - expectedEntryPoint *EntryPoint - }{ - { - name: "all parameters camelcase", - expression: "Name:foo Address::8000 TLS:goo,gii TLS CA:car CA.Optional:false Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - Address: ":8000", - Redirect: &types.Redirect{ - EntryPoint: "RedirectEntryPoint", - Regex: "RedirectRegex", - Replacement: "RedirectReplacement", - }, - Compress: true, - ProxyProtocol: &ProxyProtocol{ - TrustedIPs: []string{"192.168.0.1"}, - }, - ForwardedHeaders: &ForwardedHeaders{ - TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, - }, - WhitelistSourceRange: []string{"Range"}, - TLS: &tls.TLS{ - ClientCA: tls.ClientCA{ - Files: []string{"car"}, - Optional: false, - }, - Certificates: tls.Certificates{ - { - CertFile: tls.FileOrContent("goo"), - KeyFile: tls.FileOrContent("gii"), - }, - }, - }, - }, - }, - { - name: "all parameters lowercase", - expression: "name:foo address::8000 tls:goo,gii tls ca:car ca.optional:true redirect.entryPoint:RedirectEntryPoint redirect.regex:RedirectRegex redirect.replacement:RedirectReplacement compress:true whiteListSourceRange:Range proxyProtocol.trustedIPs:192.168.0.1 forwardedHeaders.trustedIPs:10.0.0.3/24,20.0.0.3/24", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - Address: ":8000", - Redirect: &types.Redirect{ - EntryPoint: "RedirectEntryPoint", - Regex: "RedirectRegex", - Replacement: "RedirectReplacement", - }, - Compress: true, - ProxyProtocol: &ProxyProtocol{ - TrustedIPs: []string{"192.168.0.1"}, - }, - ForwardedHeaders: &ForwardedHeaders{ - TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, - }, - WhitelistSourceRange: []string{"Range"}, - TLS: &tls.TLS{ - ClientCA: tls.ClientCA{ - Files: []string{"car"}, - Optional: true, - }, - Certificates: tls.Certificates{ - { - CertFile: tls.FileOrContent("goo"), - KeyFile: tls.FileOrContent("gii"), - }, - }, - }, - }, - }, - { - name: "default", - expression: "Name:foo", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - }, - }, - { - name: "ForwardedHeaders insecure true", - expression: "Name:foo ForwardedHeaders.Insecure:true", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - }, - }, - { - name: "ForwardedHeaders insecure false", - expression: "Name:foo ForwardedHeaders.Insecure:false", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: false}, - }, - }, - { - name: "ForwardedHeaders TrustedIPs", - expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{ - TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, - }, - }, - }, - { - name: "ProxyProtocol insecure true", - expression: "Name:foo ProxyProtocol.Insecure:true", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - ProxyProtocol: &ProxyProtocol{Insecure: true}, - }, - }, - { - name: "ProxyProtocol insecure false", - expression: "Name:foo ProxyProtocol.Insecure:false", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - ProxyProtocol: &ProxyProtocol{}, - }, - }, - { - name: "ProxyProtocol TrustedIPs", - expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - ProxyProtocol: &ProxyProtocol{ - TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, - }, - }, - }, - { - name: "compress on", - expression: "Name:foo Compress:on", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - Compress: true, - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - }, - }, - { - name: "compress true", - expression: "Name:foo Compress:true", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - Compress: true, - WhitelistSourceRange: []string{}, - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - eps := EntryPoints{} - err := eps.Set(test.expression) - require.NoError(t, err) - - ep := eps[test.expectedEntryPointName] - assert.EqualValues(t, test.expectedEntryPoint, ep) - }) - } -} - func TestSetEffectiveConfigurationGraceTimeout(t *testing.T) { tests := []struct { desc string diff --git a/configuration/entrypoints.go b/configuration/entrypoints.go new file mode 100644 index 000000000..a73ef6100 --- /dev/null +++ b/configuration/entrypoints.go @@ -0,0 +1,241 @@ +package configuration + +import ( + "fmt" + "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"` + WhitelistSourceRange []string + Compress bool `export:"true"` + ProxyProtocol *ProxyProtocol `export:"true"` + ForwardedHeaders *ForwardedHeaders `export:"true"` +} + +// 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 whiteListSourceRange []string + if len(result["whitelistsourcerange"]) > 0 { + whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",") + } + + compress := toBool(result, "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, + WhitelistSourceRange: whiteListSourceRange, + ProxyProtocol: makeEntryPointProxyProtocol(result), + ForwardedHeaders: makeEntryPointForwardedHeaders(result), + } + + return nil +} + +func makeEntryPointAuth(result map[string]string) *types.Auth { + var basic *types.Basic + if v, ok := result["auth_basic_users"]; ok { + basic = &types.Basic{ + Users: strings.Split(v, ","), + } + } + + var digest *types.Digest + if v, ok := result["auth_digest_users"]; ok { + digest = &types.Digest{ + Users: strings.Split(v, ","), + } + } + + 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, + } + } + + forward = &types.Forward{ + Address: address, + TLS: clientTLS, + TrustForwardHeader: toBool(result, "auth_forward_trustforwardheader"), + } + } + + 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 { + // TODO must be changed to false by default in the next breaking version. + forwardedHeaders := &ForwardedHeaders{Insecure: true} + 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 len(result["ca"]) > 0 { + files := strings.Split(result["ca"], ",") + optional := toBool(result, "ca_optional") + configTLS.ClientCA = tls.ClientCA{ + Files: files, + Optional: optional, + } + } + + 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 +} diff --git a/configuration/entrypoints_test.go b/configuration/entrypoints_test.go new file mode 100644 index 000000000..795a6e8c1 --- /dev/null +++ b/configuration/entrypoints_test.go @@ -0,0 +1,445 @@ +package configuration + +import ( + "testing" + + "github.com/containous/traefik/tls" + "github.com/containous/traefik/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_parseEntryPointsConfiguration(t *testing.T) { + testCases := []struct { + name string + value string + expectedResult map[string]string + }{ + { + name: "all parameters", + value: "Name:foo " + + "Address::8000 " + + "TLS:goo,gii " + + "TLS " + + "CA:car " + + "CA.Optional:true " + + "Redirect.EntryPoint:https " + + "Redirect.Regex:http://localhost/(.*) " + + "Redirect.Replacement:http://mydomain/$1 " + + "Redirect.Permanent:true " + + "Compress:true " + + "WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + + "ProxyProtocol.TrustedIPs:192.168.0.1 " + + "ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + + "Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + + "Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + + "Auth.HeaderField:X-WebAuth-User " + + "Auth.Forward.Address:https://authserver.com/auth " + + "Auth.Forward.TrustForwardHeader:true " + + "Auth.Forward.TLS.CA:path/to/local.crt " + + "Auth.Forward.TLS.CAOptional:true " + + "Auth.Forward.TLS.Cert:path/to/foo.cert " + + "Auth.Forward.TLS.Key:path/to/foo.key " + + "Auth.Forward.TLS.InsecureSkipVerify:true ", + expectedResult: map[string]string{ + "address": ":8000", + "auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "auth_digest_users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e", + "auth_forward_address": "https://authserver.com/auth", + "auth_forward_tls_ca": "path/to/local.crt", + "auth_forward_tls_caoptional": "true", + "auth_forward_tls_cert": "path/to/foo.cert", + "auth_forward_tls_insecureskipverify": "true", + "auth_forward_tls_key": "path/to/foo.key", + "auth_forward_trustforwardheader": "true", + "auth_headerfield": "X-WebAuth-User", + "ca": "car", + "ca_optional": "true", + "compress": "true", + "forwardedheaders_trustedips": "10.0.0.3/24,20.0.0.3/24", + "name": "foo", + "proxyprotocol_trustedips": "192.168.0.1", + "redirect_entrypoint": "https", + "redirect_permanent": "true", + "redirect_regex": "http://localhost/(.*)", + "redirect_replacement": "http://mydomain/$1", + "tls": "goo,gii", + "tls_acme": "TLS", + "whitelistsourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16", + }, + }, + { + name: "compress on", + value: "name:foo Compress:on", + expectedResult: map[string]string{ + "name": "foo", + "compress": "on", + }, + }, + { + name: "TLS", + value: "Name:foo TLS:goo TLS", + expectedResult: map[string]string{ + "name": "foo", + "tls": "goo", + "tls_acme": "TLS", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + conf := parseEntryPointsConfiguration(test.value) + + assert.Len(t, conf, len(test.expectedResult)) + assert.Equal(t, test.expectedResult, conf) + }) + } +} + +func Test_toBool(t *testing.T) { + testCases := []struct { + name string + value string + key string + expectedBool bool + }{ + { + name: "on", + value: "on", + key: "foo", + expectedBool: true, + }, + { + name: "true", + value: "true", + key: "foo", + expectedBool: true, + }, + { + name: "enable", + value: "enable", + key: "foo", + expectedBool: true, + }, + { + name: "arbitrary string", + value: "bar", + key: "foo", + expectedBool: false, + }, + { + name: "no existing entry", + value: "bar", + key: "fii", + expectedBool: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + conf := map[string]string{ + "foo": test.value, + } + + result := toBool(conf, test.key) + + assert.Equal(t, test.expectedBool, result) + }) + } +} + +func TestEntryPoints_Set(t *testing.T) { + testCases := []struct { + name string + expression string + expectedEntryPointName string + expectedEntryPoint *EntryPoint + }{ + { + name: "all parameters camelcase", + expression: "Name:foo " + + "Address::8000 " + + "TLS:goo,gii " + + "TLS " + + "CA:car " + + "CA.Optional:true " + + "Redirect.EntryPoint:https " + + "Redirect.Regex:http://localhost/(.*) " + + "Redirect.Replacement:http://mydomain/$1 " + + "Redirect.Permanent:true " + + "Compress:true " + + "WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + + "ProxyProtocol.TrustedIPs:192.168.0.1 " + + "ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + + "Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + + "Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + + "Auth.HeaderField:X-WebAuth-User " + + "Auth.Forward.Address:https://authserver.com/auth " + + "Auth.Forward.TrustForwardHeader:true " + + "Auth.Forward.TLS.CA:path/to/local.crt " + + "Auth.Forward.TLS.CAOptional:true " + + "Auth.Forward.TLS.Cert:path/to/foo.cert " + + "Auth.Forward.TLS.Key:path/to/foo.key " + + "Auth.Forward.TLS.InsecureSkipVerify:true ", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Address: ":8000", + TLS: &tls.TLS{ + Certificates: tls.Certificates{ + { + CertFile: tls.FileOrContent("goo"), + KeyFile: tls.FileOrContent("gii"), + }, + }, + ClientCA: tls.ClientCA{ + Files: []string{"car"}, + Optional: true, + }, + }, + Redirect: &types.Redirect{ + EntryPoint: "https", + Regex: "http://localhost/(.*)", + Replacement: "http://mydomain/$1", + Permanent: true, + }, + Auth: &types.Auth{ + Basic: &types.Basic{ + Users: types.Users{ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }, + }, + Digest: &types.Digest{ + Users: types.Users{ + "test:traefik:a2688e031edb4be6a3797f3882655c05", + "test2:traefik:518845800f9e2bfb1f1f740ec24f074e", + }, + }, + Forward: &types.Forward{ + Address: "https://authserver.com/auth", + TLS: &types.ClientTLS{ + CA: "path/to/local.crt", + CAOptional: true, + Cert: "path/to/foo.cert", + Key: "path/to/foo.key", + InsecureSkipVerify: true, + }, + TrustForwardHeader: true, + }, + HeaderField: "X-WebAuth-User", + }, + WhitelistSourceRange: []string{ + "10.42.0.0/16", + "152.89.1.33/32", + "afed:be44::/16", + }, + Compress: true, + ProxyProtocol: &ProxyProtocol{ + Insecure: false, + TrustedIPs: []string{"192.168.0.1"}, + }, + ForwardedHeaders: &ForwardedHeaders{ + Insecure: false, + TrustedIPs: []string{ + "10.0.0.3/24", + "20.0.0.3/24", + }, + }, + }, + }, + { + name: "all parameters lowercase", + expression: "Name:foo " + + "address::8000 " + + "tls:goo,gii " + + "tls " + + "ca:car " + + "ca.Optional:true " + + "redirect.entryPoint:https " + + "redirect.regex:http://localhost/(.*) " + + "redirect.replacement:http://mydomain/$1 " + + "redirect.permanent:true " + + "compress:true " + + "whiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + + "proxyProtocol.TrustedIPs:192.168.0.1 " + + "forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + + "auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + + "auth.digest.users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + + "auth.headerField:X-WebAuth-User " + + "auth.forward.address:https://authserver.com/auth " + + "auth.forward.trustForwardHeader:true " + + "auth.forward.tls.ca:path/to/local.crt " + + "auth.forward.tls.caOptional:true " + + "auth.forward.tls.cert:path/to/foo.cert " + + "auth.forward.tls.key:path/to/foo.key " + + "auth.forward.tls.insecureSkipVerify:true ", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Address: ":8000", + TLS: &tls.TLS{ + Certificates: tls.Certificates{ + { + CertFile: tls.FileOrContent("goo"), + KeyFile: tls.FileOrContent("gii"), + }, + }, + ClientCA: tls.ClientCA{ + Files: []string{"car"}, + Optional: true, + }, + }, + Redirect: &types.Redirect{ + EntryPoint: "https", + Regex: "http://localhost/(.*)", + Replacement: "http://mydomain/$1", + Permanent: true, + }, + Auth: &types.Auth{ + Basic: &types.Basic{ + Users: types.Users{ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }, + }, + Digest: &types.Digest{ + Users: types.Users{ + "test:traefik:a2688e031edb4be6a3797f3882655c05", + "test2:traefik:518845800f9e2bfb1f1f740ec24f074e", + }, + }, + Forward: &types.Forward{ + Address: "https://authserver.com/auth", + TLS: &types.ClientTLS{ + CA: "path/to/local.crt", + CAOptional: true, + Cert: "path/to/foo.cert", + Key: "path/to/foo.key", + InsecureSkipVerify: true, + }, + TrustForwardHeader: true, + }, + HeaderField: "X-WebAuth-User", + }, + WhitelistSourceRange: []string{ + "10.42.0.0/16", + "152.89.1.33/32", + "afed:be44::/16", + }, + Compress: true, + ProxyProtocol: &ProxyProtocol{ + Insecure: false, + TrustedIPs: []string{"192.168.0.1"}, + }, + ForwardedHeaders: &ForwardedHeaders{ + Insecure: false, + TrustedIPs: []string{ + "10.0.0.3/24", + "20.0.0.3/24", + }, + }, + }, + }, + { + name: "default", + expression: "Name:foo", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + }, + }, + { + name: "ForwardedHeaders insecure true", + expression: "Name:foo ForwardedHeaders.Insecure:true", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + }, + }, + { + name: "ForwardedHeaders insecure false", + expression: "Name:foo ForwardedHeaders.Insecure:false", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{Insecure: false}, + }, + }, + { + name: "ForwardedHeaders TrustedIPs", + expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{ + TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, + }, + }, + }, + { + name: "ProxyProtocol insecure true", + expression: "Name:foo ProxyProtocol.Insecure:true", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + ProxyProtocol: &ProxyProtocol{Insecure: true}, + }, + }, + { + name: "ProxyProtocol insecure false", + expression: "Name:foo ProxyProtocol.Insecure:false", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + ProxyProtocol: &ProxyProtocol{}, + }, + }, + { + name: "ProxyProtocol TrustedIPs", + expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + ProxyProtocol: &ProxyProtocol{ + TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, + }, + }, + }, + { + name: "compress on", + expression: "Name:foo Compress:on", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Compress: true, + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + }, + }, + { + name: "compress true", + expression: "Name:foo Compress:true", + expectedEntryPointName: "foo", + expectedEntryPoint: &EntryPoint{ + Compress: true, + ForwardedHeaders: &ForwardedHeaders{Insecure: true}, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + eps := EntryPoints{} + err := eps.Set(test.expression) + require.NoError(t, err) + + ep := eps[test.expectedEntryPointName] + assert.EqualValues(t, test.expectedEntryPoint, ep) + }) + } +} diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index 1faa05beb..1280a8060 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -91,11 +91,22 @@ CA.Optional:true Redirect.EntryPoint:https Redirect.Regex:http://localhost/(.*) Redirect.Replacement:http://mydomain/$1 +Redirect.Permanent:true Compress:true WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 ProxyProtocol.TrustedIPs:192.168.0.1 ProxyProtocol.Insecure:tue ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 +Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 +Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e +Auth.HeaderField:X-WebAuth-User +Auth.Forward.Address:https://authserver.com/auth +Auth.Forward.TrustForwardHeader:true +Auth.Forward.TLS.CA:path/to/local.crt +Auth.Forward.TLS.CAOptional:true +Auth.Forward.TLS.Cert:path/to/foo.cert +Auth.Forward.TLS.Key:path/to/foo.key +Auth.Forward.TLS.InsecureSkipVerify:true ``` ## Basic diff --git a/middlewares/redirect/redirect_test.go b/middlewares/redirect/redirect_test.go index 2c53f27dd..615b59bde 100644 --- a/middlewares/redirect/redirect_test.go +++ b/middlewares/redirect/redirect_test.go @@ -1,7 +1,6 @@ package redirect import ( - "fmt" "net/http" "net/http/httptest" "testing" @@ -108,7 +107,15 @@ func TestNewRegexHandler(t *testing.T) { { desc: "simple redirection", regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`, - replacement: "https://$1{{\"bar\"}}$2:443$4", + replacement: "https://${1}bar$2:443$4", + url: "http://foo.com:80", + expectedURL: "https://foobar.com:443", + expectedStatus: http.StatusFound, + }, + { + desc: "use request header", + regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`, + replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`, url: "http://foo.com:80", expectedURL: "https://foobar.com:443", expectedStatus: http.StatusFound, @@ -116,7 +123,7 @@ func TestNewRegexHandler(t *testing.T) { { desc: "URL doesn't match regex", regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`, - replacement: "https://$1{{\"bar\"}}$2:443$4", + replacement: "https://${1}bar$2:443$4", url: "http://bar.com:80", expectedStatus: http.StatusOK, }, @@ -145,7 +152,6 @@ func TestNewRegexHandler(t *testing.T) { if test.errorExpected { require.Nil(t, handler) - fmt.Println(err == nil) require.Error(t, err) } else { require.NotNil(t, handler) @@ -153,6 +159,7 @@ func TestNewRegexHandler(t *testing.T) { recorder := httptest.NewRecorder() r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) + r.Header.Set("X-Foo", "bar") next := func(rw http.ResponseWriter, req *http.Request) {} handler.ServeHTTP(recorder, r, next)