1
0
Fork 0

Send anonymized dynamic configuration to Pilot

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Harold Ozouf 2020-12-03 15:52:05 +01:00 committed by GitHub
parent a488430f23
commit 64a65cadf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1394 additions and 374 deletions

View file

@ -7,6 +7,8 @@ import (
"regexp"
"github.com/mitchellh/copystructure"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/tls"
"mvdan.cc/xurls/v2"
)
@ -43,6 +45,11 @@ func doOnJSON(input string) string {
}
func doOnStruct(field reflect.Value) error {
if field.Type().AssignableTo(reflect.TypeOf(dynamic.PluginConf{})) {
resetPlugin(field)
return nil
}
switch field.Kind() {
case reflect.Ptr:
if !field.IsNil() {
@ -57,19 +64,48 @@ func doOnStruct(field reflect.Value) error {
if !isExported(stField) {
continue
}
if stField.Tag.Get("export") == "true" {
// A struct field cannot be set it must be filled as pointer.
if fld.Kind() == reflect.Struct {
fldPtr := reflect.New(fld.Type())
fldPtr.Elem().Set(fld)
if err := doOnStruct(fldPtr); err != nil {
return err
}
fld.Set(fldPtr.Elem())
continue
}
if err := doOnStruct(fld); err != nil {
return err
}
} else {
if err := reset(fld, stField.Name); err != nil {
return err
}
} else if err := reset(fld, stField.Name); err != nil {
return err
}
}
case reflect.Map:
for _, key := range field.MapKeys() {
if err := doOnStruct(field.MapIndex(key)); err != nil {
val := field.MapIndex(key)
// A struct value cannot be set it must be filled as pointer.
if val.Kind() == reflect.Struct {
valPtr := reflect.New(val.Type())
valPtr.Elem().Set(val)
if err := doOnStruct(valPtr); err != nil {
return err
}
field.SetMapIndex(key, valPtr.Elem())
continue
}
if err := doOnStruct(val); err != nil {
return err
}
}
@ -100,7 +136,11 @@ func reset(field reflect.Value, name string) error {
}
case reflect.String:
if field.String() != "" {
field.Set(reflect.ValueOf(maskShort))
if field.Type().AssignableTo(reflect.TypeOf(tls.FileOrContent(""))) {
field.Set(reflect.ValueOf(tls.FileOrContent(maskShort)))
} else {
field.Set(reflect.ValueOf(maskShort))
}
}
case reflect.Map:
if field.Len() > 0 {
@ -130,6 +170,13 @@ func reset(field reflect.Value, name string) error {
return nil
}
// resetPlugin resets the plugin configuration so it keep the plugin name but not its configuration.
func resetPlugin(field reflect.Value) {
for _, key := range field.MapKeys() {
field.SetMapIndex(key, reflect.ValueOf(struct{}{}))
}
}
// isExported return true is a struct field is exported, else false.
func isExported(f reflect.StructField) bool {
if f.PkgPath != "" && !f.Anonymous {

View file

@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/static"
"github.com/traefik/traefik/v2/pkg/ping"
"github.com/traefik/traefik/v2/pkg/plugins"
@ -43,7 +44,438 @@ import (
var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures")
func TestDo_globalConfiguration(t *testing.T) {
func TestDo_dynamicConfiguration(t *testing.T) {
config := &dynamic.Configuration{}
config.HTTP = &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"foo": {
EntryPoints: []string{"foo"},
Middlewares: []string{"foo"},
Service: "foo",
Rule: "foo",
Priority: 42,
TLS: &dynamic.RouterTLSConfig{
Options: "foo",
CertResolver: "foo",
Domains: []types.Domain{
{
Main: "foo",
SANs: []string{"foo"},
},
},
},
},
},
Services: map[string]*dynamic.Service{
"foo": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Name: "foo",
Secure: true,
HTTPOnly: true,
SameSite: "foo",
},
},
HealthCheck: &dynamic.HealthCheck{
Scheme: "foo",
Path: "foo",
Port: 42,
Interval: "foo",
Timeout: "foo",
Hostname: "foo",
FollowRedirects: boolPtr(true),
Headers: map[string]string{
"foo": "bar",
},
},
PassHostHeader: boolPtr(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foo",
},
ServersTransport: "foo",
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1:8080",
},
},
},
},
"bar": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "foo",
Weight: intPtr(42),
},
},
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Name: "foo",
Secure: true,
HTTPOnly: true,
SameSite: "foo",
},
},
},
},
"baz": {
Mirroring: &dynamic.Mirroring{
Service: "foo",
MaxBodySize: int64Ptr(42),
Mirrors: []dynamic.MirrorService{
{
Name: "foo",
Percent: 42,
},
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{
"foo": {
ServerName: "foo",
InsecureSkipVerify: true,
RootCAs: []traefiktls.FileOrContent{"rootca.pem"},
Certificates: []traefiktls.Certificate{
{
CertFile: "cert.pem",
KeyFile: "key.pem",
},
},
MaxIdleConnsPerHost: 42,
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
DialTimeout: 42,
ResponseHeaderTimeout: 42,
IdleConnTimeout: 42,
},
},
},
Models: map[string]*dynamic.Model{
"foo": {
Middlewares: []string{"foo"},
TLS: &dynamic.RouterTLSConfig{
Options: "foo",
CertResolver: "foo",
Domains: []types.Domain{
{
Main: "foo",
SANs: []string{"foo"},
},
},
},
},
},
Middlewares: map[string]*dynamic.Middleware{
"foo": {
AddPrefix: &dynamic.AddPrefix{
Prefix: "foo",
},
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{"foo"},
ForceSlash: true,
},
StripPrefixRegex: &dynamic.StripPrefixRegex{
Regex: []string{"foo"},
},
ReplacePath: &dynamic.ReplacePath{
Path: "foo",
},
ReplacePathRegex: &dynamic.ReplacePathRegex{
Regex: "foo",
Replacement: "foo",
},
Chain: &dynamic.Chain{
Middlewares: []string{"foo"},
},
IPWhiteList: &dynamic.IPWhiteList{
SourceRange: []string{"foo"},
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{"127.0.0.1"},
},
},
Headers: &dynamic.Headers{
CustomRequestHeaders: map[string]string{"foo": "bar"},
CustomResponseHeaders: map[string]string{"foo": "bar"},
AccessControlAllowCredentials: true,
AccessControlAllowHeaders: []string{"foo"},
AccessControlAllowMethods: []string{"foo"},
AccessControlAllowOrigin: "foo",
AccessControlAllowOriginList: []string{"foo"},
AccessControlAllowOriginListRegex: []string{"foo"},
AccessControlExposeHeaders: []string{"foo"},
AccessControlMaxAge: 42,
AddVaryHeader: true,
AllowedHosts: []string{"foo"},
HostsProxyHeaders: []string{"foo"},
SSLRedirect: true,
SSLTemporaryRedirect: true,
SSLHost: "foo",
SSLProxyHeaders: map[string]string{"foo": "bar"},
SSLForceHost: true,
STSSeconds: 42,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
CustomFrameOptionsValue: "foo",
ContentTypeNosniff: true,
BrowserXSSFilter: true,
CustomBrowserXSSValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
FeaturePolicy: "foo",
IsDevelopment: true,
},
Errors: &dynamic.ErrorPage{
Status: []string{"foo"},
Service: "foo",
Query: "foo",
},
RateLimit: &dynamic.RateLimit{
Average: 42,
Period: 42,
Burst: 42,
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{"foo"},
},
RequestHeaderName: "foo",
RequestHost: true,
},
},
RedirectRegex: &dynamic.RedirectRegex{
Regex: "foo",
Replacement: "foo",
Permanent: true,
},
RedirectScheme: &dynamic.RedirectScheme{
Scheme: "foo",
Port: "foo",
Permanent: true,
},
BasicAuth: &dynamic.BasicAuth{
Users: []string{"foo"},
UsersFile: "foo",
Realm: "foo",
RemoveHeader: true,
HeaderField: "foo",
},
DigestAuth: &dynamic.DigestAuth{
Users: []string{"foo"},
UsersFile: "foo",
RemoveHeader: true,
Realm: "foo",
HeaderField: "foo",
},
ForwardAuth: &dynamic.ForwardAuth{
Address: "127.0.0.1",
TLS: &dynamic.ClientTLS{
CA: "ca.pem",
CAOptional: true,
Cert: "cert.pem",
Key: "cert.pem",
InsecureSkipVerify: true,
},
TrustForwardHeader: true,
AuthResponseHeaders: []string{"foo"},
AuthResponseHeadersRegex: "foo",
AuthRequestHeaders: []string{"foo"},
},
InFlightReq: &dynamic.InFlightReq{
Amount: 42,
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{"foo"},
},
RequestHeaderName: "foo",
RequestHost: true,
},
},
Buffering: &dynamic.Buffering{
MaxRequestBodyBytes: 42,
MemRequestBodyBytes: 42,
MaxResponseBodyBytes: 42,
MemResponseBodyBytes: 42,
RetryExpression: "foo",
},
CircuitBreaker: &dynamic.CircuitBreaker{
Expression: "foo",
},
Compress: &dynamic.Compress{
ExcludedContentTypes: []string{"foo"},
},
PassTLSClientCert: &dynamic.PassTLSClientCert{
PEM: true,
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
NotBefore: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
SerialNumber: true,
},
},
Retry: &dynamic.Retry{
Attempts: 42,
InitialInterval: 42,
},
ContentType: &dynamic.ContentType{
AutoDetect: true,
},
Plugin: map[string]dynamic.PluginConf{
"foo": {
"answer": struct{ Answer int }{
Answer: 42,
},
},
},
},
},
}
config.TCP = &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"foo": {
EntryPoints: []string{"foo"},
Service: "foo",
Rule: "foo",
TLS: &dynamic.RouterTCPTLSConfig{
Passthrough: true,
Options: "foo",
CertResolver: "foo",
Domains: []types.Domain{
{
Main: "foo",
SANs: []string{"foo"},
},
},
},
},
},
Services: map[string]*dynamic.TCPService{
"foo": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
TerminationDelay: intPtr(42),
ProxyProtocol: &dynamic.ProxyProtocol{
Version: 42,
},
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:8080",
},
},
},
},
"bar": {
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{
{
Name: "foo",
Weight: intPtr(42),
},
},
},
},
},
}
config.UDP = &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
EntryPoints: []string{"foo"},
Service: "foo",
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:8080",
},
},
},
},
"bar": {
Weighted: &dynamic.UDPWeightedRoundRobin{
Services: []dynamic.UDPWRRService{
{
Name: "foo",
Weight: intPtr(42),
},
},
},
},
},
}
config.TLS = &dynamic.TLSConfiguration{
Options: map[string]traefiktls.Options{
"foo": {
MinVersion: "foo",
MaxVersion: "foo",
CipherSuites: []string{"foo"},
CurvePreferences: []string{"foo"},
ClientAuth: traefiktls.ClientAuth{
CAFiles: []traefiktls.FileOrContent{"ca.pem"},
ClientAuthType: "RequireAndVerifyClientCert",
},
SniStrict: true,
PreferServerCipherSuites: true,
},
},
Certificates: []*traefiktls.CertAndStores{
{
Certificate: traefiktls.Certificate{
CertFile: "cert.pem",
KeyFile: "key.pem",
},
Stores: []string{"foo"},
},
},
Stores: map[string]traefiktls.Store{
"foo": {
DefaultCertificate: &traefiktls.Certificate{
CertFile: "cert.pem",
KeyFile: "key.pem",
},
},
},
}
expectedConfiguration, err := ioutil.ReadFile("./testdata/anonymized-dynamic-config.json")
require.NoError(t, err)
cleanJSON, err := Do(config, true)
require.NoError(t, err)
if *updateExpected {
require.NoError(t, ioutil.WriteFile("testdata/anonymized-dynamic-config.json", []byte(cleanJSON), 0666))
}
expected := strings.TrimSuffix(string(expectedConfiguration), "\n")
assert.Equal(t, expected, cleanJSON)
}
func TestDo_staticConfiguration(t *testing.T) {
config := &static.Configuration{}
config.Global = &static.Global{
@ -538,3 +970,15 @@ func TestDo_globalConfiguration(t *testing.T) {
expected := strings.TrimSuffix(string(expectedConfiguration), "\n")
assert.Equal(t, expected, cleanJSON)
}
func boolPtr(value bool) *bool {
return &value
}
func intPtr(value int) *int {
return &value
}
func int64Ptr(value int64) *int64 {
return &value
}

View file

@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type Courgette struct {
@ -39,7 +40,6 @@ func Test_doOnStruct(t *testing.T) {
name string
base *Carotte
expected *Carotte
hasError bool
}{
{
name: "primitive",
@ -145,7 +145,7 @@ func Test_doOnStruct(t *testing.T) {
},
},
{
name: "export map string/struct (UNSAFE)",
name: "export map string/struct",
base: &Carotte{
Name: "koko",
ESAubergine: map[string]Tomate{
@ -158,11 +158,10 @@ func Test_doOnStruct(t *testing.T) {
Name: "xxxx",
ESAubergine: map[string]Tomate{
"foo": {
Ji: "JiJiJi",
Ji: "xxxx",
},
},
},
hasError: true,
},
}
@ -170,12 +169,7 @@ func Test_doOnStruct(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
val := reflect.ValueOf(test.base).Elem()
err := doOnStruct(val)
if !test.hasError && err != nil {
t.Fatal(err)
}
if test.hasError && err == nil {
t.Fatal("Got no error but want an error.")
}
require.NoError(t, err)
assert.EqualValues(t, test.expected, test.base)
})

View file

@ -0,0 +1,476 @@
{
"http": {
"routers": {
"foo": {
"entryPoints": [
"foo"
],
"middlewares": [
"foo"
],
"service": "foo",
"rule": "xxxx",
"priority": 42,
"tls": {
"options": "foo",
"certResolver": "foo",
"domains": [
{
"main": "xxxx",
"sans": [
"xxxx"
]
}
]
}
}
},
"services": {
"bar": {
"weighted": {
"services": [
{
"name": "foo",
"weight": 42
}
],
"sticky": {
"cookie": {
"name": "foo",
"secure": true,
"httpOnly": true,
"sameSite": "foo"
}
}
}
},
"baz": {
"mirroring": {
"service": "foo",
"maxBodySize": 42,
"mirrors": [
{
"name": "foo",
"percent": 42
}
]
}
},
"foo": {
"loadBalancer": {
"sticky": {
"cookie": {
"name": "foo",
"secure": true,
"httpOnly": true,
"sameSite": "foo"
}
},
"servers": [
{
"url": "xxxx"
}
],
"healthCheck": {
"scheme": "foo",
"path": "foo",
"port": 42,
"interval": "foo",
"timeout": "foo",
"hostname": "xxxx",
"followRedirects": true,
"headers": {
"foo": "bar"
}
},
"passHostHeader": true,
"responseForwarding": {
"flushInterval": "foo"
},
"serversTransport": "foo"
}
}
},
"middlewares": {
"foo": {
"addPrefix": {
"prefix": "foo"
},
"stripPrefix": {
"prefixes": [
"foo"
],
"forceSlash": true
},
"stripPrefixRegex": {
"regex": [
"foo"
]
},
"replacePath": {
"path": "foo"
},
"replacePathRegex": {
"regex": "foo",
"replacement": "foo"
},
"chain": {
"middlewares": [
"foo"
]
},
"ipWhiteList": {
"sourceRange": [
"xxxx"
],
"ipStrategy": {
"depth": 42,
"excludedIPs": [
"xxxx"
]
}
},
"headers": {
"customRequestHeaders": {
"foo": "bar"
},
"customResponseHeaders": {
"foo": "bar"
},
"accessControlAllowCredentials": true,
"accessControlAllowHeaders": [
"foo"
],
"accessControlAllowMethods": [
"foo"
],
"accessControlAllowOrigin": "xxxx",
"accessControlAllowOriginList": [
"xxxx"
],
"accessControlAllowOriginListRegex": [
"xxxx"
],
"accessControlExposeHeaders": [
"foo"
],
"accessControlMaxAge": 42,
"addVaryHeader": true,
"allowedHosts": [
"xxxx"
],
"hostsProxyHeaders": [
"foo"
],
"sslRedirect": true,
"sslTemporaryRedirect": true,
"sslHost": "xxxx",
"sslForceHost": true,
"stsSeconds": 42,
"stsIncludeSubdomains": true,
"stsPreload": true,
"forceSTSHeader": true,
"frameDeny": true,
"customFrameOptionsValue": "xxxx",
"contentTypeNosniff": true,
"browserXssFilter": true,
"customBrowserXSSValue": "xxxx",
"contentSecurityPolicy": "xxxx",
"publicKey": "xxxx",
"referrerPolicy": "foo",
"featurePolicy": "foo",
"isDevelopment": true
},
"errors": {
"status": [
"foo"
],
"service": "foo",
"query": "foo"
},
"rateLimit": {
"average": 42,
"period": 42,
"burst": 42,
"sourceCriterion": {
"ipStrategy": {
"depth": 42,
"excludedIPs": [
"xxxx"
]
},
"requestHeaderName": "foo",
"requestHost": true
}
},
"redirectRegex": {
"regex": "xxxx",
"replacement": "xxxx",
"permanent": true
},
"redirectScheme": {
"scheme": "foo",
"port": "foo",
"permanent": true
},
"basicAuth": {
"users": [
"xxxx"
],
"usersFile": "xxxx",
"realm": "xxxx",
"removeHeader": true,
"headerField": "foo"
},
"digestAuth": {
"users": [
"xxxx"
],
"usersFile": "xxxx",
"removeHeader": true,
"realm": "xxxx",
"headerField": "foo"
},
"forwardAuth": {
"address": "xxxx",
"tls": {
"ca": "xxxx",
"caOptional": true,
"cert": "xxxx",
"key": "xxxx",
"insecureSkipVerify": true
},
"trustForwardHeader": true,
"authResponseHeaders": [
"foo"
],
"authResponseHeadersRegex": "foo",
"authRequestHeaders": [
"foo"
]
},
"inFlightReq": {
"amount": 42,
"sourceCriterion": {
"ipStrategy": {
"depth": 42,
"excludedIPs": [
"xxxx"
]
},
"requestHeaderName": "foo",
"requestHost": true
}
},
"buffering": {
"maxRequestBodyBytes": 42,
"memRequestBodyBytes": 42,
"maxResponseBodyBytes": 42,
"memResponseBodyBytes": 42,
"retryExpression": "foo"
},
"circuitBreaker": {
"expression": "foo"
},
"compress": {
"excludedContentTypes": [
"foo"
]
},
"passTLSClientCert": {
"pem": true,
"info": {
"notAfter": true,
"notBefore": true,
"sans": true,
"subject": {
"country": true,
"province": true,
"locality": true,
"organization": true,
"commonName": true,
"serialNumber": true,
"domainComponent": true
},
"issuer": {
"country": true,
"province": true,
"locality": true,
"organization": true,
"commonName": true,
"serialNumber": true,
"domainComponent": true
},
"serialNumber": true
}
},
"retry": {
"attempts": 42,
"initialInterval": 42
},
"contentType": {
"autoDetect": true
},
"plugin": {
"foo": {
"answer": {}
}
}
}
},
"models": {
"foo": {
"middlewares": [
"foo"
],
"tls": {
"options": "foo",
"certResolver": "foo",
"domains": [
{
"main": "xxxx",
"sans": [
"xxxx"
]
}
]
}
}
},
"serversTransports": {
"foo": {
"serverName": "xxxx",
"insecureSkipVerify": true,
"rootCAs": [
"xxxx"
],
"certificates": [
{
"certFile": "xxxx",
"keyFile": "xxxx"
}
],
"maxIdleConnsPerHost": 42,
"forwardingTimeouts": {
"dialTimeout": 42,
"responseHeaderTimeout": 42,
"idleConnTimeout": 42
}
}
}
},
"tcp": {
"routers": {
"foo": {
"entryPoints": [
"foo"
],
"service": "foo",
"rule": "xxxx",
"tls": {
"passthrough": true,
"options": "foo",
"certResolver": "foo",
"domains": [
{
"main": "xxxx",
"sans": [
"xxxx"
]
}
]
}
}
},
"services": {
"bar": {
"weighted": {
"services": [
{
"name": "foo",
"weight": 42
}
]
}
},
"foo": {
"loadBalancer": {
"terminationDelay": 42,
"proxyProtocol": {
"version": 42
},
"servers": [
{
"address": "xxxx"
}
]
}
}
}
},
"udp": {
"routers": {
"foo": {
"entryPoints": [
"foo"
],
"service": "foo"
}
},
"services": {
"bar": {
"weighted": {
"services": [
{
"name": "foo",
"weight": 42
}
]
}
},
"foo": {
"loadBalancer": {
"servers": [
{
"address": "xxxx"
}
]
}
}
}
},
"tls": {
"certificates": [
{
"certFile": "xxxx",
"keyFile": "xxxx",
"stores": [
"foo"
]
}
],
"options": {
"foo": {
"minVersion": "foo",
"maxVersion": "foo",
"cipherSuites": [
"foo"
],
"curvePreferences": [
"foo"
],
"clientAuth": {},
"sniStrict": true,
"preferServerCipherSuites": true
}
},
"stores": {
"foo": {
"defaultCertificate": {
"certFile": "xxxx",
"keyFile": "xxxx"
}
}
}
}
}