package kv import ( "fmt" "net/http" "sort" "strconv" "strings" "text/template" "github.com/BurntSushi/ty/fun" "github.com/containous/flaeg" "github.com/containous/traefik/log" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" "github.com/docker/libkv/store" ) func (p *Provider) buildConfiguration() *types.Configuration { templateObjects := struct { Prefix string }{ // Allow `/traefik/alias` to supersede `p.Prefix` Prefix: strings.TrimSuffix(p.get(p.Prefix, p.Prefix+pathAlias), pathSeparator), } var KvFuncMap = template.FuncMap{ "List": p.list, "ListServers": p.listServers, "Get": p.get, "GetBool": p.getBool, "GetInt": p.getInt, "GetInt64": p.getInt64, "SplitGet": p.splitGet, "Last": p.last, "Has": p.has, // Frontend functions "getRedirect": p.getRedirect, "getErrorPages": p.getErrorPages, "getRateLimit": p.getRateLimit, "getHeaders": p.getHeaders, // Backend functions "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, } configuration, err := p.GetConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects) if err != nil { log.Error(err) } for key, frontend := range configuration.Frontends { if _, ok := configuration.Backends[frontend.Backend]; !ok { delete(configuration.Frontends, key) } } return configuration } func (p *Provider) getSticky(rootPath string) bool { stickyValue := p.get("", rootPath, pathBackendLoadBalancerSticky) if len(stickyValue) > 0 { log.Warnf("Deprecated configuration found: %s. Please use %s.", pathBackendLoadBalancerSticky, pathBackendLoadBalancerStickiness) } else { return false } sticky, err := strconv.ParseBool(stickyValue) if err != nil { log.Warnf("Invalid %s value: %s.", pathBackendLoadBalancerSticky, stickyValue) } return sticky } func (p *Provider) hasStickinessLabel(rootPath string) bool { return p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) } func (p *Provider) getStickinessCookieName(rootPath string) string { return p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName) } func (p *Provider) getRedirect(rootPath string) *types.Redirect { if p.has(rootPath, pathFrontendRedirectEntryPoint) { return &types.Redirect{ EntryPoint: p.get("", rootPath, pathFrontendRedirectEntryPoint), } } if p.has(rootPath, pathFrontendRedirectRegex) && p.has(rootPath, pathFrontendRedirectReplacement) { return &types.Redirect{ Regex: p.get("", rootPath, pathFrontendRedirectRegex), Replacement: p.get("", rootPath, pathFrontendRedirectReplacement), } } return nil } func (p *Provider) getErrorPages(rootPath string) map[string]*types.ErrorPage { var errorPages map[string]*types.ErrorPage pathErrors := p.list(rootPath, pathFrontendErrorPages) for _, pathPage := range pathErrors { if errorPages == nil { errorPages = make(map[string]*types.ErrorPage) } pageName := p.last(pathPage) errorPages[pageName] = &types.ErrorPage{ Backend: p.get("", pathPage, pathFrontendErrorPagesBackend), Query: p.get("", pathPage, pathFrontendErrorPagesQuery), Status: p.splitGet(pathPage, pathFrontendErrorPagesStatus), } } return errorPages } func (p *Provider) getRateLimit(rootPath string) *types.RateLimit { extractorFunc := p.get("", rootPath, pathFrontendRateLimitExtractorFunc) if len(extractorFunc) == 0 { return nil } var limits map[string]*types.Rate pathRateSet := p.list(rootPath, pathFrontendRateLimitRateSet) for _, pathLimits := range pathRateSet { if limits == nil { limits = make(map[string]*types.Rate) } rawPeriod := p.get("", pathLimits+pathFrontendRateLimitPeriod) var period flaeg.Duration err := period.Set(rawPeriod) if err != nil { log.Errorf("Invalid %q value: %q", pathLimits+pathFrontendRateLimitPeriod, rawPeriod) continue } limitName := p.last(pathLimits) limits[limitName] = &types.Rate{ Average: p.getInt64(0, pathLimits+pathFrontendRateLimitAverage), Burst: p.getInt64(0, pathLimits+pathFrontendRateLimitBurst), Period: period, } } return &types.RateLimit{ ExtractorFunc: extractorFunc, RateSet: limits, } } func (p *Provider) getHeaders(rootPath string) *types.Headers { headers := &types.Headers{ CustomRequestHeaders: p.getMap(rootPath, pathFrontendCustomRequestHeaders), CustomResponseHeaders: p.getMap(rootPath, pathFrontendCustomResponseHeaders), SSLProxyHeaders: p.getMap(rootPath, pathFrontendSSLProxyHeaders), AllowedHosts: p.splitGet("", rootPath, pathFrontendAllowedHosts), HostsProxyHeaders: p.splitGet(rootPath, pathFrontendHostsProxyHeaders), SSLRedirect: p.getBool(false, rootPath, pathFrontendSSLRedirect), SSLTemporaryRedirect: p.getBool(false, rootPath, pathFrontendSSLTemporaryRedirect), SSLHost: p.get("", rootPath, pathFrontendSSLHost), STSSeconds: p.getInt64(0, rootPath, pathFrontendSTSSeconds), STSIncludeSubdomains: p.getBool(false, rootPath, pathFrontendSTSIncludeSubdomains), STSPreload: p.getBool(false, rootPath, pathFrontendSTSPreload), ForceSTSHeader: p.getBool(false, rootPath, pathFrontendForceSTSHeader), FrameDeny: p.getBool(false, rootPath, pathFrontendFrameDeny), CustomFrameOptionsValue: p.get("", rootPath, pathFrontendCustomFrameOptionsValue), ContentTypeNosniff: p.getBool(false, rootPath, pathFrontendContentTypeNosniff), BrowserXSSFilter: p.getBool(false, rootPath, pathFrontendBrowserXSSFilter), ContentSecurityPolicy: p.get("", rootPath, pathFrontendContentSecurityPolicy), PublicKey: p.get("", rootPath, pathFrontendPublicKey), ReferrerPolicy: p.get("", rootPath, pathFrontendReferrerPolicy), IsDevelopment: p.getBool(false, rootPath, pathFrontendIsDevelopment), } if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() { return nil } return headers } func (p *Provider) listServers(backend string) []string { serverNames := p.list(backend, pathBackendServers) return fun.Filter(p.serverFilter, serverNames).([]string) } func (p *Provider) serverFilter(serverName string) bool { key := fmt.Sprint(serverName, pathBackendServerURL) if _, err := p.kvClient.Get(key, nil); err != nil { if err != store.ErrKeyNotFound { log.Errorf("Failed to retrieve value for key %s: %s", key, err) } return false } return p.checkConstraints(serverName, pathTags) } func (p *Provider) checkConstraints(keys ...string) bool { joinedKeys := strings.Join(keys, "") keyPair, err := p.kvClient.Get(joinedKeys, nil) value := "" if err == nil && keyPair != nil && keyPair.Value != nil { value = string(keyPair.Value) } constraintTags := label.SplitAndTrimString(value, ",") ok, failingConstraint := p.MatchConstraints(constraintTags) if !ok { if failingConstraint != nil { log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value) } return false } return true } func (p *Provider) get(defaultValue string, keyParts ...string) string { key := strings.Join(keyParts, "") if p.storeType == store.ETCD { key = strings.TrimPrefix(key, pathSeparator) } keyPair, err := p.kvClient.Get(key, nil) if err != nil { log.Debugf("Cannot get key %s %s, setting default %s", key, err, defaultValue) return defaultValue } else if keyPair == nil { log.Debugf("Cannot get key %s, setting default %s", key, defaultValue) return defaultValue } return string(keyPair.Value) } func (p *Provider) getBool(defaultValue bool, keyParts ...string) bool { rawValue := p.get(strconv.FormatBool(defaultValue), keyParts...) if len(rawValue) == 0 { return defaultValue } value, err := strconv.ParseBool(rawValue) if err != nil { log.Errorf("Invalid value for %v: %s", keyParts, rawValue) return defaultValue } return value } func (p *Provider) has(keyParts ...string) bool { value := p.get("", keyParts...) return len(value) > 0 } func (p *Provider) getInt(defaultValue int, keyParts ...string) int { rawValue := p.get("", keyParts...) if len(rawValue) == 0 { return defaultValue } value, err := strconv.Atoi(rawValue) if err != nil { log.Errorf("Invalid value for %v: %s", keyParts, rawValue) return defaultValue } return value } func (p *Provider) getInt64(defaultValue int64, keyParts ...string) int64 { rawValue := p.get("", keyParts...) if len(rawValue) == 0 { return defaultValue } value, err := strconv.ParseInt(rawValue, 10, 64) if err != nil { log.Errorf("Invalid value for %v: %s", keyParts, rawValue) return defaultValue } return value } func (p *Provider) list(keyParts ...string) []string { rootKey := strings.Join(keyParts, "") keysPairs, err := p.kvClient.List(rootKey, nil) if err != nil { log.Debugf("Cannot list keys under %q: %v", rootKey, err) return nil } directoryKeys := make(map[string]string) for _, key := range keysPairs { directory := strings.Split(strings.TrimPrefix(key.Key, rootKey), pathSeparator)[0] directoryKeys[directory] = rootKey + directory } keys := fun.Values(directoryKeys).([]string) sort.Strings(keys) return keys } func (p *Provider) splitGet(keyParts ...string) []string { value := p.get("", keyParts...) if len(value) == 0 { return nil } return label.SplitAndTrimString(value, ",") } func (p *Provider) last(key string) string { index := strings.LastIndex(key, pathSeparator) return key[index+1:] } func (p *Provider) getMap(keyParts ...string) map[string]string { var mapData map[string]string list := p.list(keyParts...) for _, name := range list { if mapData == nil { mapData = make(map[string]string) } mapData[http.CanonicalHeaderKey(p.last(name))] = p.get("", name) } return mapData }