traefik/provider/kv/kv_config.go
Jean-Baptiste Doumenjou 79bf19c897 Auth support in frontends
2018-07-06 16:52:04 +02:00

696 lines
20 KiB
Go

package kv
import (
"fmt"
"math"
"net/http"
"sort"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/abronan/valkeyrie/store"
"github.com/containous/flaeg"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
)
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,
"GetList": p.getList,
"SplitGet": p.splitGet,
"Last": p.last,
"Has": p.has,
"getTLSSection": p.getTLSSection,
// Frontend functions
"getBackendName": p.getFuncString(pathFrontendBackend, ""),
"getPriority": p.getFuncInt(pathFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": p.getPassHostHeader(),
"getPassTLSCert": p.getFuncBool(pathFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": p.getFuncList(pathFrontendEntryPoints),
"getBasicAuth": p.getFuncList(pathFrontendBasicAuth), // Deprecated
"getAuth": p.getAuth,
"getRoutes": p.getRoutes,
"getRedirect": p.getRedirect,
"getErrorPages": p.getErrorPages,
"getRateLimit": p.getRateLimit,
"getHeaders": p.getHeaders,
"getWhiteList": p.getWhiteList,
// Backend functions
"getServers": p.getServers,
"getCircuitBreaker": p.getCircuitBreaker,
"getLoadBalancer": p.getLoadBalancer,
"getMaxConn": p.getMaxConn,
"getHealthCheck": p.getHealthCheck,
"getBuffering": p.getBuffering,
"getSticky": p.getSticky, // Deprecated [breaking]
"hasStickinessLabel": p.hasStickinessLabel, // Deprecated [breaking]
"getStickinessCookieName": p.getStickinessCookieName, // Deprecated [breaking]
}
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
}
// Deprecated
func (p *Provider) getPassHostHeader() func(rootPath string) bool {
return func(rootPath string) bool {
rawValue := p.get("", rootPath, pathFrontendPassHostHeader)
if len(rawValue) > 0 {
value, err := strconv.ParseBool(rawValue)
if err != nil {
log.Errorf("Invalid value for %s %s: %s", rootPath, pathFrontendPassHostHeader, rawValue)
return label.DefaultPassHostHeader
}
return value
}
return p.getBool(label.DefaultPassHostHeader, rootPath, pathFrontendPassHostHeaderDeprecated)
}
}
// Deprecated
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
}
// Deprecated
func (p *Provider) hasStickinessLabel(rootPath string) bool {
return p.getBool(false, rootPath, pathBackendLoadBalancerStickiness)
}
// Deprecated
func (p *Provider) getStickinessCookieName(rootPath string) string {
return p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName)
}
func (p *Provider) getWhiteList(rootPath string) *types.WhiteList {
ranges := p.getList(rootPath, pathFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: p.getBool(false, rootPath, pathFrontendWhiteListUseXForwardedFor),
}
}
return nil
}
func (p *Provider) getRedirect(rootPath string) *types.Redirect {
permanent := p.getBool(false, rootPath, pathFrontendRedirectPermanent)
if p.has(rootPath, pathFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: p.get("", rootPath, pathFrontendRedirectEntryPoint),
Permanent: permanent,
}
}
if p.has(rootPath, pathFrontendRedirectRegex) && p.has(rootPath, pathFrontendRedirectReplacement) {
return &types.Redirect{
Regex: p.get("", rootPath, pathFrontendRedirectRegex),
Replacement: p.get("", rootPath, pathFrontendRedirectReplacement),
Permanent: permanent,
}
}
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.getList(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.getList("", rootPath, pathFrontendAllowedHosts),
HostsProxyHeaders: p.getList(rootPath, pathFrontendHostsProxyHeaders),
SSLForceHost: p.getBool(false, rootPath, pathFrontendSSLForceHost),
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),
CustomBrowserXSSValue: p.get("", rootPath, pathFrontendCustomBrowserXSSValue),
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) getLoadBalancer(rootPath string) *types.LoadBalancer {
lb := &types.LoadBalancer{
Method: p.get(label.DefaultBackendLoadBalancerMethod, rootPath, pathBackendLoadBalancerMethod),
Sticky: p.getSticky(rootPath),
}
if p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) {
lb.Stickiness = &types.Stickiness{
CookieName: p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName),
}
}
return lb
}
func (p *Provider) getCircuitBreaker(rootPath string) *types.CircuitBreaker {
if !p.has(rootPath, pathBackendCircuitBreakerExpression) {
return nil
}
circuitBreaker := p.get(label.DefaultCircuitBreakerExpression, rootPath, pathBackendCircuitBreakerExpression)
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
func (p *Provider) getMaxConn(rootPath string) *types.MaxConn {
amount := p.getInt64(math.MinInt64, rootPath, pathBackendMaxConnAmount)
extractorFunc := p.get(label.DefaultBackendMaxconnExtractorFunc, rootPath, pathBackendMaxConnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func (p *Provider) getHealthCheck(rootPath string) *types.HealthCheck {
path := p.get("", rootPath, pathBackendHealthCheckPath)
if len(path) == 0 {
return nil
}
scheme := p.get("", rootPath, pathBackendHealthCheckScheme)
port := p.getInt(label.DefaultBackendHealthCheckPort, rootPath, pathBackendHealthCheckPort)
interval := p.get("30s", rootPath, pathBackendHealthCheckInterval)
hostname := p.get("", rootPath, pathBackendHealthCheckHostname)
headers := p.getMap(rootPath, pathBackendHealthCheckHeaders)
return &types.HealthCheck{
Scheme: scheme,
Path: path,
Port: port,
Interval: interval,
Hostname: hostname,
Headers: headers,
}
}
func (p *Provider) getBuffering(rootPath string) *types.Buffering {
pathsBuffering := p.list(rootPath, pathBackendBuffering)
var buffering *types.Buffering
if len(pathsBuffering) > 0 {
if buffering == nil {
buffering = &types.Buffering{}
}
buffering.MaxRequestBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMaxRequestBodyBytes)
buffering.MaxResponseBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMaxResponseBodyBytes)
buffering.MemRequestBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMemRequestBodyBytes)
buffering.MemResponseBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMemResponseBodyBytes)
buffering.RetryExpression = p.get("", rootPath, pathBackendBufferingRetryExpression)
}
return buffering
}
func (p *Provider) getTLSSection(prefix string) []*tls.Configuration {
var tlsSection []*tls.Configuration
for _, tlsConfPath := range p.list(prefix, pathTLS) {
certFile := p.get("", tlsConfPath, pathTLSCertFile)
keyFile := p.get("", tlsConfPath, pathTLSKeyFile)
if len(certFile) == 0 && len(keyFile) == 0 {
log.Warnf("Invalid TLS configuration (no cert and no key): %s", tlsConfPath)
continue
}
entryPoints := p.getList(tlsConfPath, pathTLSEntryPoints)
if len(entryPoints) == 0 {
log.Warnf("Invalid TLS configuration (no entry points): %s", tlsConfPath)
continue
}
tlsConf := &tls.Configuration{
EntryPoints: entryPoints,
Certificate: &tls.Certificate{
CertFile: tls.FileOrContent(certFile),
KeyFile: tls.FileOrContent(keyFile),
},
}
tlsSection = append(tlsSection, tlsConf)
}
return tlsSection
}
// hasDeprecatedBasicAuth check if the frontend basic auth use the deprecated configuration
func (p *Provider) hasDeprecatedBasicAuth(rootPath string) bool {
return len(p.getList(rootPath, pathFrontendBasicAuth)) > 0
}
// GetAuth Create auth from path
func (p *Provider) getAuth(rootPath string) *types.Auth {
hasDeprecatedBasicAuth := p.hasDeprecatedBasicAuth(rootPath)
if len(p.getList(rootPath, pathFrontendAuth)) > 0 || hasDeprecatedBasicAuth {
auth := &types.Auth{
HeaderField: p.get("", rootPath, pathFrontendAuthHeaderField),
}
if len(p.getList(rootPath, pathFrontendAuthBasic)) > 0 || hasDeprecatedBasicAuth {
auth.Basic = p.getAuthBasic(rootPath)
} else if len(p.getList(rootPath, pathFrontendAuthDigest)) > 0 {
auth.Digest = p.getAuthDigest(rootPath)
} else if len(p.getList(rootPath, pathFrontendAuthForward)) > 0 {
auth.Forward = p.getAuthForward(rootPath)
}
return auth
}
return nil
}
// getAuthBasic Create Basic Auth from path
func (p *Provider) getAuthBasic(rootPath string) *types.Basic {
basicAuth := &types.Basic{
UsersFile: p.get("", rootPath, pathFrontendAuthBasicUsersFile),
}
// backward compatibility
if p.hasDeprecatedBasicAuth(rootPath) {
basicAuth.Users = p.getList(rootPath, pathFrontendBasicAuth)
log.Warnf("Deprecated configuration found: %s. Please use %s.", pathFrontendBasicAuth, pathFrontendAuthBasic)
} else {
basicAuth.Users = p.getList(rootPath, pathFrontendAuthBasicUsers)
}
return basicAuth
}
// getAuthDigest Create Digest Auth from path
func (p *Provider) getAuthDigest(rootPath string) *types.Digest {
return &types.Digest{
Users: p.getList(rootPath, pathFrontendAuthDigestUsers),
UsersFile: p.get("", rootPath, pathFrontendAuthDigestUsersFile),
}
}
// getAuthForward Create Forward Auth from path
func (p *Provider) getAuthForward(rootPath string) *types.Forward {
forwardAuth := &types.Forward{
Address: p.get("", rootPath, pathFrontendAuthForwardAddress),
TrustForwardHeader: p.getBool(false, rootPath, pathFrontendAuthForwardTrustForwardHeader),
}
// TLS configuration
if len(p.getList(rootPath, pathFrontendAuthForwardTLS)) > 0 {
forwardAuth.TLS = &types.ClientTLS{
CA: p.get("", rootPath, pathFrontendAuthForwardTLSCa),
CAOptional: p.getBool(false, rootPath, pathFrontendAuthForwardTLSCaOptional),
Cert: p.get("", rootPath, pathFrontendAuthForwardTLSCert),
InsecureSkipVerify: p.getBool(false, rootPath, pathFrontendAuthForwardTLSInsecureSkipVerify),
Key: p.get("", rootPath, pathFrontendAuthForwardTLSKey),
}
}
return forwardAuth
}
func (p *Provider) getRoutes(rootPath string) map[string]types.Route {
var routes map[string]types.Route
rts := p.list(rootPath, pathFrontendRoutes)
for _, rt := range rts {
rule := p.get("", rt, pathFrontendRule)
if len(rule) == 0 {
continue
}
if routes == nil {
routes = make(map[string]types.Route)
}
routeName := p.last(rt)
routes[routeName] = types.Route{
Rule: rule,
}
}
return routes
}
func (p *Provider) getServers(rootPath string) map[string]types.Server {
var servers map[string]types.Server
serverKeys := p.listServers(rootPath)
for _, serverKey := range serverKeys {
serverURL := p.get("", serverKey, pathBackendServerURL)
if len(serverURL) == 0 {
continue
}
if servers == nil {
servers = make(map[string]types.Server)
}
serverName := p.last(serverKey)
servers[serverName] = types.Server{
URL: serverURL,
Weight: p.getInt(label.DefaultWeight, serverKey, pathBackendServerWeight),
}
}
return servers
}
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) getFuncString(key string, defaultValue string) func(rootPath string) string {
return func(rootPath string) string {
return p.get(defaultValue, rootPath, key)
}
}
func (p *Provider) getFuncBool(key string, defaultValue bool) func(rootPath string) bool {
return func(rootPath string) bool {
return p.getBool(defaultValue, rootPath, key)
}
}
func (p *Provider) getFuncInt(key string, defaultValue int) func(rootPath string) int {
return func(rootPath string) int {
return p.getInt(defaultValue, rootPath, key)
}
}
func (p *Provider) getFuncList(key string) func(rootPath string) []string {
return func(rootPath string) []string {
return p.getList(rootPath, key)
}
}
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) getList(keyParts ...string) []string {
values := p.splitGet(keyParts...)
if len(values) > 0 {
return values
}
return p.getSlice(keyParts...)
}
// get sub keys. ex: foo/0, foo/1, foo/2
func (p *Provider) getSlice(keyParts ...string) []string {
baseKey := strings.Join(keyParts, "")
if !strings.HasSuffix(baseKey, "/") {
baseKey += "/"
}
listKeys := p.list(baseKey)
var values []string
for _, entryKey := range listKeys {
val := p.get("", entryKey)
if len(val) > 0 {
values = append(values, val)
}
}
return values
}
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
}