2017-04-17 10:50:02 +00:00
package kubernetes
2016-02-08 20:57:32 +00:00
import (
2017-04-23 14:17:20 +00:00
"bufio"
"bytes"
"errors"
2017-07-03 08:06:32 +00:00
"flag"
2017-03-07 12:09:11 +00:00
"fmt"
2018-06-05 08:28:03 +00:00
"net"
2017-03-07 12:09:11 +00:00
"os"
2016-06-22 16:31:14 +00:00
"reflect"
2018-12-20 19:08:03 +00:00
"sort"
2016-05-18 16:30:42 +00:00
"strconv"
2016-04-25 14:56:06 +00:00
"strings"
2016-02-08 20:57:32 +00:00
"text/template"
"time"
2016-09-12 19:06:21 +00:00
2019-02-04 15:38:08 +00:00
"github.com/cenkalti/backoff"
2016-12-30 08:21:13 +00:00
"github.com/containous/traefik/job"
2018-11-14 09:18:03 +00:00
"github.com/containous/traefik/old/log"
"github.com/containous/traefik/old/provider"
"github.com/containous/traefik/old/provider/label"
"github.com/containous/traefik/old/types"
2016-11-11 22:50:20 +00:00
"github.com/containous/traefik/safe"
2018-01-07 23:36:03 +00:00
"github.com/containous/traefik/tls"
2018-01-26 12:29:42 +00:00
"gopkg.in/yaml.v2"
2018-02-14 08:56:04 +00:00
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
2018-04-12 09:14:05 +00:00
"k8s.io/apimachinery/pkg/labels"
2018-02-14 08:56:04 +00:00
"k8s.io/apimachinery/pkg/util/intstr"
2016-02-08 20:57:32 +00:00
)
2017-04-17 10:50:02 +00:00
var _ provider . Provider = ( * Provider ) ( nil )
2016-08-16 17:13:18 +00:00
2017-02-06 23:04:30 +00:00
const (
2018-07-06 08:06:04 +00:00
ruleTypePath = "Path"
2018-02-01 18:04:04 +00:00
ruleTypePathPrefix = "PathPrefix"
2018-07-06 08:06:04 +00:00
ruleTypePathStrip = "PathStrip"
ruleTypePathPrefixStrip = "PathPrefixStrip"
ruleTypeAddPrefix = "AddPrefix"
2018-02-01 18:04:04 +00:00
ruleTypeReplacePath = "ReplacePath"
2018-07-06 08:06:04 +00:00
ruleTypeReplacePathRegex = "ReplacePathRegex"
2018-02-01 18:04:04 +00:00
traefikDefaultRealm = "traefik"
traefikDefaultIngressClass = "traefik"
2018-07-03 16:58:03 +00:00
defaultBackendName = "global-default-backend"
defaultFrontendName = "global-default-frontend"
2018-10-25 07:50:03 +00:00
defaultFrontendRule = "PathPrefix:/"
2018-07-31 15:12:03 +00:00
allowedProtocolHTTPS = "https"
allowedProtocolH2C = "h2c"
2017-02-06 23:04:30 +00:00
)
2018-05-18 12:12:03 +00:00
// IngressEndpoint holds the endpoint information for the Kubernetes provider
type IngressEndpoint struct {
IP string ` description:"IP used for Kubernetes Ingress endpoints" `
Hostname string ` description:"Hostname used for Kubernetes Ingress endpoints" `
PublishedService string ` description:"Published Kubernetes Service to copy status from" `
}
2017-04-17 10:50:02 +00:00
// Provider holds configurations of the provider.
type Provider struct {
2017-10-02 08:32:02 +00:00
provider . BaseProvider ` mapstructure:",squash" export:"true" `
2018-05-18 12:12:03 +00:00
Endpoint string ` description:"Kubernetes server endpoint (required for external cluster client)" `
Token string ` description:"Kubernetes bearer token (not needed for in-cluster client)" `
CertAuthFilePath string ` description:"Kubernetes certificate authority file path (not needed for in-cluster client)" `
DisablePassHostHeaders bool ` description:"Kubernetes disable PassHost Headers" export:"true" `
2018-10-29 15:02:06 +00:00
EnablePassTLSCert bool ` description:"Kubernetes enable Pass TLS Client Certs" export:"true" ` // Deprecated
2018-05-18 12:12:03 +00:00
Namespaces Namespaces ` description:"Kubernetes namespaces" export:"true" `
LabelSelector string ` description:"Kubernetes Ingress label selector to use" export:"true" `
IngressClass string ` description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true" `
IngressEndpoint * IngressEndpoint ` description:"Kubernetes Ingress Endpoint" `
2016-06-22 16:31:14 +00:00
lastConfiguration safe . Safe
2016-02-08 20:57:32 +00:00
}
2018-04-12 09:14:05 +00:00
func ( p * Provider ) newK8sClient ( ingressLabelSelector string ) ( Client , error ) {
ingLabelSel , err := labels . Parse ( ingressLabelSelector )
if err != nil {
return nil , fmt . Errorf ( "invalid ingress label selector: %q" , ingressLabelSelector )
}
log . Infof ( "ingress label selector is: %q" , ingLabelSel )
2017-03-07 12:09:11 +00:00
withEndpoint := ""
2017-04-17 10:50:02 +00:00
if p . Endpoint != "" {
withEndpoint = fmt . Sprintf ( " with endpoint %v" , p . Endpoint )
2016-02-08 20:57:32 +00:00
}
2017-03-07 12:09:11 +00:00
2018-04-12 09:14:05 +00:00
var cl * clientImpl
2017-03-07 12:09:11 +00:00
if os . Getenv ( "KUBERNETES_SERVICE_HOST" ) != "" && os . Getenv ( "KUBERNETES_SERVICE_PORT" ) != "" {
2018-01-15 15:04:05 +00:00
log . Infof ( "Creating in-cluster Provider client%s" , withEndpoint )
2018-04-12 09:14:05 +00:00
cl , err = newInClusterClient ( p . Endpoint )
} else {
log . Infof ( "Creating cluster-external Provider client%s" , withEndpoint )
cl , err = newExternalClusterClient ( p . Endpoint , p . Token , p . CertAuthFilePath )
}
if err == nil {
cl . ingressLabelSelector = ingLabelSel
2017-03-07 12:09:11 +00:00
}
2018-04-12 09:14:05 +00:00
return cl , err
2016-04-19 17:23:08 +00:00
}
2018-07-11 07:08:03 +00:00
// Init the provider
func ( p * Provider ) Init ( constraints types . Constraints ) error {
return p . BaseProvider . Init ( constraints )
}
2017-04-17 10:50:02 +00:00
// Provide allows the k8s provider to provide configurations to traefik
2016-04-19 17:23:08 +00:00
// using the given configuration channel.
2018-07-11 07:08:03 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool ) error {
2017-07-03 08:06:32 +00:00
// Tell glog (used by client-go) to log into STDERR. Otherwise, we risk
// certain kinds of API errors getting logged into a directory not
// available in a `FROM scratch` Docker container, causing glog to abort
// hard with an exit code > 0.
2017-12-02 18:28:11 +00:00
err := flag . Set ( "logtostderr" , "true" )
if err != nil {
return err
}
2017-07-03 08:06:32 +00:00
2018-04-12 09:14:05 +00:00
log . Debugf ( "Using Ingress label selector: %q" , p . LabelSelector )
k8sClient , err := p . newK8sClient ( p . LabelSelector )
2016-02-08 20:57:32 +00:00
if err != nil {
return err
}
pool . Go ( func ( stop chan bool ) {
operation := func ( ) error {
2018-08-23 07:40:03 +00:00
stopWatch := make ( chan struct { } , 1 )
defer close ( stopWatch )
eventsChan , err := k8sClient . WatchAll ( p . Namespaces , stopWatch )
if err != nil {
log . Errorf ( "Error watching kubernetes events: %v" , err )
timer := time . NewTimer ( 1 * time . Second )
select {
case <- timer . C :
return err
case <- stop :
return nil
}
}
2016-02-08 20:57:32 +00:00
for {
2018-08-23 07:40:03 +00:00
select {
case <- stop :
return nil
case event := <- eventsChan :
log . Debugf ( "Received Kubernetes event kind %T" , event )
templateObjects , err := p . loadIngresses ( k8sClient )
if err != nil {
2016-05-19 18:09:01 +00:00
return err
}
2018-08-23 07:40:03 +00:00
if reflect . DeepEqual ( p . lastConfiguration . Get ( ) , templateObjects ) {
log . Debugf ( "Skipping Kubernetes event kind %T" , event )
} else {
p . lastConfiguration . Set ( templateObjects )
configurationChan <- types . ConfigMessage {
ProviderName : "kubernetes" ,
Configuration : p . loadConfig ( * templateObjects ) ,
2016-04-25 14:56:06 +00:00
}
2016-02-08 20:57:32 +00:00
}
}
}
}
notify := func ( err error , time time . Duration ) {
2017-06-27 23:32:19 +00:00
log . Errorf ( "Provider connection error: %s; retrying in %s" , err , time )
2016-02-08 20:57:32 +00:00
}
2016-12-08 12:32:12 +00:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
2016-02-08 20:57:32 +00:00
if err != nil {
2017-06-27 23:32:19 +00:00
log . Errorf ( "Cannot connect to Provider: %s" , err )
2016-02-08 20:57:32 +00:00
}
} )
return nil
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) loadIngresses ( k8sClient Client ) ( * types . Configuration , error ) {
2017-10-10 14:26:03 +00:00
ingresses := k8sClient . GetIngresses ( )
2016-11-11 22:50:20 +00:00
2018-07-03 16:58:03 +00:00
templateObjects := & types . Configuration {
2017-05-26 15:03:14 +00:00
Backends : map [ string ] * types . Backend { } ,
Frontends : map [ string ] * types . Frontend { } ,
2016-04-19 17:23:08 +00:00
}
2018-01-25 19:51:06 +00:00
2018-12-20 19:08:03 +00:00
tlsConfigs := map [ string ] * tls . Configuration { }
2016-04-19 17:23:08 +00:00
for _ , i := range ingresses {
2018-10-04 07:58:03 +00:00
ingressClass , err := getStringSafeValue ( i . Annotations , annotationKubernetesIngressClass , "" )
if err != nil {
log . Errorf ( "Misconfigured ingress class for ingress %s/%s: %v" , i . Namespace , i . Name , err )
continue
}
2017-03-03 19:30:22 +00:00
2018-02-01 18:04:04 +00:00
if ! p . shouldProcessIngress ( ingressClass ) {
2017-03-03 19:30:22 +00:00
continue
}
2018-12-20 19:08:03 +00:00
if err = getTLS ( i , k8sClient , tlsConfigs ) ; err != nil {
2018-01-07 23:36:03 +00:00
log . Errorf ( "Error configuring TLS for ingress %s/%s: %v" , i . Namespace , i . Name , err )
continue
}
2018-07-03 16:58:03 +00:00
if i . Spec . Backend != nil {
err := p . addGlobalBackend ( k8sClient , i , templateObjects )
if err != nil {
log . Errorf ( "Error creating global backend for ingress %s/%s: %v" , i . Namespace , i . Name , err )
continue
}
}
2018-07-01 09:26:03 +00:00
var weightAllocator weightAllocator = & defaultWeightAllocator { }
annotationPercentageWeights := getAnnotationName ( i . Annotations , annotationKubernetesServiceWeights )
if _ , ok := i . Annotations [ annotationPercentageWeights ] ; ok {
fractionalAllocator , err := newFractionalWeightAllocator ( i , k8sClient )
if err != nil {
log . Errorf ( "failed to create fractional weight allocator for ingress %s/%s: %v" , i . Namespace , i . Name , err )
continue
}
log . Debugf ( "Created custom weight allocator for %s/%s: %s" , i . Namespace , i . Name , fractionalAllocator )
weightAllocator = fractionalAllocator
}
2016-04-19 17:23:08 +00:00
for _ , r := range i . Spec . Rules {
2016-12-08 12:32:52 +00:00
if r . HTTP == nil {
2017-05-26 15:03:14 +00:00
log . Warn ( "Error in ingress: HTTP is nil" )
2016-12-08 12:32:52 +00:00
continue
}
2017-07-07 19:27:54 +00:00
2016-04-19 17:23:08 +00:00
for _ , pa := range r . HTTP . Paths {
2018-08-14 15:40:04 +00:00
priority := getIntValue ( i . Annotations , annotationKubernetesPriority , 0 )
2018-10-04 07:58:03 +00:00
err := templateSafeString ( r . Host )
if err != nil {
log . Errorf ( "failed to validate host %q for ingress %s/%s: %v" , r . Host , i . Namespace , i . Name , err )
continue
}
err = templateSafeString ( pa . Path )
if err != nil {
log . Errorf ( "failed to validate path %q for ingress %s/%s: %v" , pa . Path , i . Namespace , i . Name , err )
continue
}
2018-01-25 19:51:06 +00:00
baseName := r . Host + pa . Path
2018-10-25 07:50:03 +00:00
if len ( baseName ) == 0 {
baseName = pa . Backend . ServiceName
}
2018-08-14 15:40:04 +00:00
if priority > 0 {
baseName = strconv . Itoa ( priority ) + "-" + baseName
}
2018-01-25 19:51:06 +00:00
if _ , exists := templateObjects . Backends [ baseName ] ; ! exists {
templateObjects . Backends [ baseName ] = & types . Backend {
2016-04-19 17:23:08 +00:00
Servers : make ( map [ string ] types . Server ) ,
2017-01-25 13:11:00 +00:00
LoadBalancer : & types . LoadBalancer {
Method : "wrr" ,
} ,
2016-04-19 17:23:08 +00:00
}
}
2017-02-10 11:05:59 +00:00
2018-01-26 12:29:42 +00:00
annotationAuthRealm := getAnnotationName ( i . Annotations , annotationKubernetesAuthRealm )
if realm := i . Annotations [ annotationAuthRealm ] ; realm != "" && realm != traefikDefaultRealm {
log . Errorf ( "Value for annotation %q on ingress %s/%s invalid: no realm customization supported" , annotationAuthRealm , i . Namespace , i . Name )
2018-01-25 19:51:06 +00:00
delete ( templateObjects . Backends , baseName )
2017-10-12 13:48:03 +00:00
continue
2017-04-23 14:17:20 +00:00
}
2017-02-10 11:05:59 +00:00
2018-10-05 16:36:03 +00:00
var frontend * types . Frontend
if fe , exists := templateObjects . Frontends [ baseName ] ; exists {
frontend = fe
} else {
2018-07-02 09:52:04 +00:00
auth , err := getAuthConfig ( i , k8sClient )
2017-04-23 14:17:20 +00:00
if err != nil {
2018-07-02 09:52:04 +00:00
log . Errorf ( "Failed to retrieve auth configuration for ingress %s/%s: %s" , i . Namespace , i . Name , err )
2017-06-27 23:32:35 +00:00
continue
2017-04-23 14:17:20 +00:00
}
2017-07-29 16:35:23 +00:00
2018-01-26 12:29:42 +00:00
passHostHeader := getBoolValue ( i . Annotations , annotationKubernetesPreserveHost , ! p . DisablePassHostHeaders )
2018-10-29 15:02:06 +00:00
passTLSCert := getBoolValue ( i . Annotations , annotationKubernetesPassTLSCert , p . EnablePassTLSCert ) // Deprecated
2018-01-26 12:29:42 +00:00
entryPoints := getSliceStringValue ( i . Annotations , annotationKubernetesFrontendEntryPoints )
2017-12-21 16:47:50 +00:00
2018-10-05 16:36:03 +00:00
frontend = & types . Frontend {
2018-10-29 15:02:06 +00:00
Backend : baseName ,
PassHostHeader : passHostHeader ,
PassTLSCert : passTLSCert ,
PassTLSClientCert : getPassTLSClientCert ( i ) ,
Routes : make ( map [ string ] types . Route ) ,
Priority : priority ,
WhiteList : getWhiteList ( i ) ,
Redirect : getFrontendRedirect ( i , baseName , pa . Path ) ,
EntryPoints : entryPoints ,
Headers : getHeader ( i ) ,
Errors : getErrorPages ( i ) ,
RateLimit : getRateLimit ( i ) ,
Auth : auth ,
2016-04-19 17:23:08 +00:00
}
}
2017-01-20 13:16:05 +00:00
2018-10-05 16:36:03 +00:00
service , exists , err := k8sClient . GetService ( i . Namespace , pa . Backend . ServiceName )
if err != nil {
log . Errorf ( "Error while retrieving service information from k8s API %s/%s: %v" , i . Namespace , pa . Backend . ServiceName , err )
return nil , err
}
if ! exists {
log . Errorf ( "Service not found for %s/%s" , i . Namespace , pa . Backend . ServiceName )
continue
2016-04-19 17:23:08 +00:00
}
2017-02-06 23:04:30 +00:00
2018-02-19 14:36:03 +00:00
rule , err := getRuleForPath ( pa , i )
if err != nil {
log . Errorf ( "Failed to get rule for ingress %s/%s: %s" , i . Namespace , i . Name , err )
continue
}
2018-10-05 16:36:03 +00:00
2018-02-19 14:36:03 +00:00
if rule != "" {
2018-10-05 16:36:03 +00:00
frontend . Routes [ pa . Path ] = types . Route {
2017-07-07 19:27:54 +00:00
Rule : rule ,
2016-04-19 17:23:08 +00:00
}
}
2017-02-06 23:04:30 +00:00
2018-10-05 16:36:03 +00:00
if len ( r . Host ) > 0 {
if _ , exists := frontend . Routes [ r . Host ] ; ! exists {
frontend . Routes [ r . Host ] = types . Route {
Rule : getRuleForHost ( r . Host ) ,
}
}
2016-04-25 14:56:06 +00:00
}
2016-11-11 22:50:20 +00:00
2018-10-25 07:50:03 +00:00
if len ( frontend . Routes ) == 0 {
frontend . Routes [ "/" ] = types . Route {
Rule : defaultFrontendRule ,
}
}
2018-10-05 16:36:03 +00:00
templateObjects . Frontends [ baseName ] = frontend
2018-01-26 12:29:42 +00:00
templateObjects . Backends [ baseName ] . CircuitBreaker = getCircuitBreaker ( service )
2018-01-25 19:51:06 +00:00
templateObjects . Backends [ baseName ] . LoadBalancer = getLoadBalancer ( service )
2018-01-26 12:29:42 +00:00
templateObjects . Backends [ baseName ] . MaxConn = getMaxConn ( service )
2018-01-25 19:51:06 +00:00
templateObjects . Backends [ baseName ] . Buffering = getBuffering ( service )
2018-10-29 17:42:03 +00:00
templateObjects . Backends [ baseName ] . ResponseForwarding = getResponseForwarding ( service )
2018-01-31 14:32:04 +00:00
2017-12-02 18:28:11 +00:00
protocol := label . DefaultProtocol
2018-07-01 09:26:03 +00:00
2016-05-25 23:53:51 +00:00
for _ , port := range service . Spec . Ports {
if equalPorts ( port , pa . Backend . ServicePort ) {
2018-05-17 16:46:03 +00:00
if port . Port == 443 || strings . HasPrefix ( port . Name , "https" ) {
2016-05-25 23:53:51 +00:00
protocol = "https"
}
2017-07-07 19:27:54 +00:00
2018-07-31 15:12:03 +00:00
protocol = getStringValue ( i . Annotations , annotationKubernetesProtocol , protocol )
switch protocol {
case allowedProtocolHTTPS :
case allowedProtocolH2C :
case label . DefaultProtocol :
default :
log . Errorf ( "Invalid protocol %s/%s specified for Ingress %s - skipping" , annotationKubernetesProtocol , i . Namespace , i . Name )
continue
}
2018-11-06 06:48:03 +00:00
// We have to treat external-name service differently here b/c it doesn't have any endpoints
if service . Spec . Type == corev1 . ServiceTypeExternalName {
2017-02-10 01:25:38 +00:00
url := protocol + "://" + service . Spec . ExternalName
2018-05-17 16:46:03 +00:00
if port . Port != 443 && port . Port != 80 {
url = fmt . Sprintf ( "%s:%d" , url , port . Port )
}
2018-11-06 06:48:03 +00:00
externalNameServiceWeight := weightAllocator . getWeight ( r . Host , pa . Path , pa . Backend . ServiceName )
2018-06-19 20:10:03 +00:00
templateObjects . Backends [ baseName ] . Servers [ url ] = types . Server {
2017-02-10 01:25:38 +00:00
URL : url ,
2018-11-06 06:48:03 +00:00
Weight : externalNameServiceWeight ,
2016-05-20 16:34:57 +00:00
}
2016-05-25 23:53:51 +00:00
} else {
2018-01-25 19:51:06 +00:00
endpoints , exists , err := k8sClient . GetEndpoints ( service . Namespace , service . Name )
2017-04-11 15:10:46 +00:00
if err != nil {
2018-01-25 19:51:06 +00:00
log . Errorf ( "Error retrieving endpoints %s/%s: %v" , service . Namespace , service . Name , err )
2017-04-11 15:10:46 +00:00
return nil , err
}
2017-02-14 19:53:35 +00:00
2017-04-11 15:10:46 +00:00
if ! exists {
2018-01-25 19:51:06 +00:00
log . Warnf ( "Endpoints not found for %s/%s" , service . Namespace , service . Name )
2017-05-15 21:16:35 +00:00
break
2017-03-14 14:59:13 +00:00
}
2017-04-11 15:10:46 +00:00
if len ( endpoints . Subsets ) == 0 {
2018-01-25 19:51:06 +00:00
log . Warnf ( "Endpoints not available for %s/%s" , service . Namespace , service . Name )
2017-05-15 21:16:35 +00:00
break
}
for _ , subset := range endpoints . Subsets {
2018-04-16 12:44:04 +00:00
endpointPort := endpointPortNumber ( port , subset . Ports )
if endpointPort == 0 {
// endpoint port does not match service.
continue
}
2017-05-15 21:16:35 +00:00
for _ , address := range subset . Addresses {
2018-06-05 20:55:19 +00:00
url := protocol + "://" + net . JoinHostPort ( address . IP , strconv . FormatInt ( int64 ( endpointPort ) , 10 ) )
2017-05-15 21:16:35 +00:00
name := url
if address . TargetRef != nil && address . TargetRef . Name != "" {
name = address . TargetRef . Name
}
2018-07-01 09:26:03 +00:00
2018-01-25 19:51:06 +00:00
templateObjects . Backends [ baseName ] . Servers [ name ] = types . Server {
2017-05-15 21:16:35 +00:00
URL : url ,
2018-07-01 09:26:03 +00:00
Weight : weightAllocator . getWeight ( r . Host , pa . Path , pa . Backend . ServiceName ) ,
2016-05-20 16:34:57 +00:00
}
}
2016-04-20 11:26:51 +00:00
}
2016-04-19 17:23:08 +00:00
}
2016-05-25 23:53:51 +00:00
break
2016-04-19 17:23:08 +00:00
}
}
}
}
2018-05-18 12:12:03 +00:00
err = p . updateIngressStatus ( i , k8sClient )
if err != nil {
log . Errorf ( "Cannot update Ingress %s/%s due to error: %v" , i . Namespace , i . Name , err )
}
2016-04-19 17:23:08 +00:00
}
2018-12-20 19:08:03 +00:00
templateObjects . TLS = getTLSConfig ( tlsConfigs )
2018-07-03 16:58:03 +00:00
return templateObjects , nil
2016-04-19 17:23:08 +00:00
}
2018-05-18 12:12:03 +00:00
func ( p * Provider ) updateIngressStatus ( i * extensionsv1beta1 . Ingress , k8sClient Client ) error {
// Only process if an IngressEndpoint has been configured
if p . IngressEndpoint == nil {
return nil
}
if len ( p . IngressEndpoint . PublishedService ) == 0 {
if len ( p . IngressEndpoint . IP ) == 0 && len ( p . IngressEndpoint . Hostname ) == 0 {
return errors . New ( "publishedService or ip or hostname must be defined" )
}
return k8sClient . UpdateIngressStatus ( i . Namespace , i . Name , p . IngressEndpoint . IP , p . IngressEndpoint . Hostname )
}
serviceInfo := strings . Split ( p . IngressEndpoint . PublishedService , "/" )
if len ( serviceInfo ) != 2 {
return fmt . Errorf ( "invalid publishedService format (expected 'namespace/service' format): %s" , p . IngressEndpoint . PublishedService )
}
serviceNamespace , serviceName := serviceInfo [ 0 ] , serviceInfo [ 1 ]
service , exists , err := k8sClient . GetService ( serviceNamespace , serviceName )
if err != nil {
return fmt . Errorf ( "cannot get service %s, received error: %s" , p . IngressEndpoint . PublishedService , err )
}
if exists && service . Status . LoadBalancer . Ingress == nil {
// service exists, but has no Load Balancer status
log . Debugf ( "Skipping updating Ingress %s/%s due to service %s having no status set" , i . Namespace , i . Name , p . IngressEndpoint . PublishedService )
return nil
}
if ! exists {
return fmt . Errorf ( "missing service: %s" , p . IngressEndpoint . PublishedService )
}
return k8sClient . UpdateIngressStatus ( i . Namespace , i . Name , service . Status . LoadBalancer . Ingress [ 0 ] . IP , service . Status . LoadBalancer . Ingress [ 0 ] . Hostname )
}
2017-12-04 10:40:03 +00:00
func ( p * Provider ) loadConfig ( templateObjects types . Configuration ) * types . Configuration {
2017-11-28 12:36:03 +00:00
var FuncMap = template . FuncMap { }
configuration , err := p . GetConfiguration ( "templates/kubernetes.tmpl" , FuncMap , templateObjects )
if err != nil {
log . Error ( err )
2017-11-20 01:12:03 +00:00
}
2017-11-28 12:36:03 +00:00
return configuration
2017-11-20 01:12:03 +00:00
}
2018-07-03 16:58:03 +00:00
func ( p * Provider ) addGlobalBackend ( cl Client , i * extensionsv1beta1 . Ingress , templateObjects * types . Configuration ) error {
// Ensure that we are not duplicating the frontend
if _ , exists := templateObjects . Frontends [ defaultFrontendName ] ; exists {
return errors . New ( "duplicate frontend: " + defaultFrontendName )
}
// Ensure we are not duplicating the backend
if _ , exists := templateObjects . Backends [ defaultBackendName ] ; exists {
return errors . New ( "duplicate backend: " + defaultBackendName )
}
templateObjects . Backends [ defaultBackendName ] = & types . Backend {
Servers : make ( map [ string ] types . Server ) ,
LoadBalancer : & types . LoadBalancer {
Method : "wrr" ,
} ,
}
service , exists , err := cl . GetService ( i . Namespace , i . Spec . Backend . ServiceName )
if err != nil {
return fmt . Errorf ( "error while retrieving service information from k8s API %s/%s: %v" , i . Namespace , i . Spec . Backend . ServiceName , err )
}
if ! exists {
return fmt . Errorf ( "service not found for %s/%s" , i . Namespace , i . Spec . Backend . ServiceName )
}
templateObjects . Backends [ defaultBackendName ] . CircuitBreaker = getCircuitBreaker ( service )
templateObjects . Backends [ defaultBackendName ] . LoadBalancer = getLoadBalancer ( service )
templateObjects . Backends [ defaultBackendName ] . MaxConn = getMaxConn ( service )
templateObjects . Backends [ defaultBackendName ] . Buffering = getBuffering ( service )
2018-10-29 17:42:03 +00:00
templateObjects . Backends [ defaultBackendName ] . ResponseForwarding = getResponseForwarding ( service )
2018-07-03 16:58:03 +00:00
endpoints , exists , err := cl . GetEndpoints ( service . Namespace , service . Name )
if err != nil {
return fmt . Errorf ( "error retrieving endpoint information from k8s API %s/%s: %v" , service . Namespace , service . Name , err )
}
if ! exists {
return fmt . Errorf ( "endpoints not found for %s/%s" , service . Namespace , service . Name )
}
if len ( endpoints . Subsets ) == 0 {
return fmt . Errorf ( "endpoints not available for %s/%s" , service . Namespace , service . Name )
}
for _ , subset := range endpoints . Subsets {
endpointPort := endpointPortNumber ( corev1 . ServicePort { Protocol : "TCP" , Port : int32 ( i . Spec . Backend . ServicePort . IntValue ( ) ) } , subset . Ports )
if endpointPort == 0 {
// endpoint port does not match service.
continue
}
protocol := "http"
for _ , address := range subset . Addresses {
if endpointPort == 443 || strings . HasPrefix ( i . Spec . Backend . ServicePort . String ( ) , "https" ) {
protocol = "https"
}
url := fmt . Sprintf ( "%s://%s" , protocol , net . JoinHostPort ( address . IP , strconv . FormatInt ( int64 ( endpointPort ) , 10 ) ) )
name := url
if address . TargetRef != nil && address . TargetRef . Name != "" {
name = address . TargetRef . Name
}
templateObjects . Backends [ defaultBackendName ] . Servers [ name ] = types . Server {
URL : url ,
Weight : label . DefaultWeight ,
}
}
}
passHostHeader := getBoolValue ( i . Annotations , annotationKubernetesPreserveHost , ! p . DisablePassHostHeaders )
2018-10-29 15:02:06 +00:00
passTLSCert := getBoolValue ( i . Annotations , annotationKubernetesPassTLSCert , p . EnablePassTLSCert ) // Deprecated
2018-07-03 16:58:03 +00:00
priority := getIntValue ( i . Annotations , annotationKubernetesPriority , 0 )
entryPoints := getSliceStringValue ( i . Annotations , annotationKubernetesFrontendEntryPoints )
templateObjects . Frontends [ defaultFrontendName ] = & types . Frontend {
2018-10-29 15:02:06 +00:00
Backend : defaultBackendName ,
PassHostHeader : passHostHeader ,
PassTLSCert : passTLSCert ,
PassTLSClientCert : getPassTLSClientCert ( i ) ,
Routes : make ( map [ string ] types . Route ) ,
Priority : priority ,
WhiteList : getWhiteList ( i ) ,
Redirect : getFrontendRedirect ( i , defaultFrontendName , "/" ) ,
EntryPoints : entryPoints ,
Headers : getHeader ( i ) ,
Errors : getErrorPages ( i ) ,
RateLimit : getRateLimit ( i ) ,
2018-07-03 16:58:03 +00:00
}
templateObjects . Frontends [ defaultFrontendName ] . Routes [ "/" ] = types . Route {
2018-10-25 07:50:03 +00:00
Rule : defaultFrontendRule ,
2018-07-03 16:58:03 +00:00
}
return nil
}
2018-02-19 14:36:03 +00:00
func getRuleForPath ( pa extensionsv1beta1 . HTTPIngressPath , i * extensionsv1beta1 . Ingress ) ( string , error ) {
2017-07-07 19:27:54 +00:00
if len ( pa . Path ) == 0 {
2018-02-19 14:36:03 +00:00
return "" , nil
2017-07-07 19:27:54 +00:00
}
2018-01-26 12:29:42 +00:00
ruleType := getStringValue ( i . Annotations , annotationKubernetesRuleType , ruleTypePathPrefix )
2018-07-06 08:06:04 +00:00
switch ruleType {
case ruleTypePath , ruleTypePathPrefix , ruleTypePathStrip , ruleTypePathPrefixStrip :
case ruleTypeReplacePath :
log . Warnf ( "Using %s as %s will be deprecated in the future. Please use the %s annotation instead" , ruleType , annotationKubernetesRuleType , annotationKubernetesRequestModifier )
default :
return "" , fmt . Errorf ( "cannot use non-matcher rule: %q" , ruleType )
}
2017-11-27 10:22:03 +00:00
rules := [ ] string { ruleType + ":" + pa . Path }
2017-07-07 19:27:54 +00:00
2018-01-26 12:29:42 +00:00
if rewriteTarget := getStringValue ( i . Annotations , annotationKubernetesRewriteTarget , "" ) ; rewriteTarget != "" {
2018-07-12 13:20:04 +00:00
if ruleType == ruleTypeReplacePath {
return "" , fmt . Errorf ( "rewrite-target must not be used together with annotation %q" , annotationKubernetesRuleType )
2018-02-19 14:36:03 +00:00
}
2018-07-31 07:08:03 +00:00
rewriteTargetRule := fmt . Sprintf ( "ReplacePathRegex: ^%s(.*) %s$1" , pa . Path , strings . TrimRight ( rewriteTarget , "/" ) )
2018-07-09 22:26:03 +00:00
rules = append ( rules , rewriteTargetRule )
2018-02-19 14:36:03 +00:00
}
2018-07-06 08:06:04 +00:00
if requestModifier := getStringValue ( i . Annotations , annotationKubernetesRequestModifier , "" ) ; requestModifier != "" {
rule , err := parseRequestModifier ( requestModifier , ruleType )
if err != nil {
return "" , err
}
rules = append ( rules , rule )
}
2018-10-25 07:50:03 +00:00
2018-02-19 14:36:03 +00:00
return strings . Join ( rules , ";" ) , nil
2017-07-07 19:27:54 +00:00
}
2018-07-06 08:06:04 +00:00
func parseRequestModifier ( requestModifier , ruleType string ) ( string , error ) {
trimmedRequestModifier := strings . TrimRight ( requestModifier , " :" )
if trimmedRequestModifier == "" {
return "" , fmt . Errorf ( "rule %q is empty" , requestModifier )
}
// Split annotation to determine modifier type
modifierParts := strings . Split ( trimmedRequestModifier , ":" )
if len ( modifierParts ) < 2 {
return "" , fmt . Errorf ( "rule %q is missing type or value" , requestModifier )
}
modifier := strings . TrimSpace ( modifierParts [ 0 ] )
value := strings . TrimSpace ( modifierParts [ 1 ] )
switch modifier {
case ruleTypeAddPrefix , ruleTypeReplacePath , ruleTypeReplacePathRegex :
if ruleType == ruleTypeReplacePath {
return "" , fmt . Errorf ( "cannot use '%s: %s' and '%s: %s', as this leads to rule duplication, and unintended behavior" ,
annotationKubernetesRuleType , ruleTypeReplacePath , annotationKubernetesRequestModifier , modifier )
}
case "" :
return "" , errors . New ( "cannot use empty rule" )
default :
return "" , fmt . Errorf ( "cannot use non-modifier rule: %q" , modifier )
}
return modifier + ":" + value , nil
}
2017-12-21 14:40:07 +00:00
func getRuleForHost ( host string ) string {
if strings . Contains ( host , "*" ) {
return "HostRegexp:" + strings . Replace ( host , "*" , "{subdomain:[A-Za-z0-9-_]+}" , 1 )
}
return "Host:" + host
}
2018-12-20 19:08:03 +00:00
func getTLS ( ingress * extensionsv1beta1 . Ingress , k8sClient Client , tlsConfigs map [ string ] * tls . Configuration ) error {
2018-01-07 23:36:03 +00:00
for _ , t := range ingress . Spec . TLS {
2019-01-08 08:22:03 +00:00
if t . SecretName == "" {
log . Debugf ( "Skipping TLS sub-section for ingress %s/%s: No secret name provided" , ingress . Namespace , ingress . Name )
continue
}
2018-12-20 19:08:03 +00:00
newEntryPoints := getSliceStringValue ( ingress . Annotations , annotationKubernetesFrontendEntryPoints )
2018-01-07 23:36:03 +00:00
2018-12-20 19:08:03 +00:00
configKey := ingress . Namespace + "/" + t . SecretName
if tlsConfig , tlsExists := tlsConfigs [ configKey ] ; tlsExists {
for _ , entryPoint := range newEntryPoints {
tlsConfig . EntryPoints = mergeEntryPoint ( tlsConfig . EntryPoints , entryPoint )
}
} else {
secret , exists , err := k8sClient . GetSecret ( ingress . Namespace , t . SecretName )
if err != nil {
return fmt . Errorf ( "failed to fetch secret %s/%s: %v" , ingress . Namespace , t . SecretName , err )
}
if ! exists {
return fmt . Errorf ( "secret %s/%s does not exist" , ingress . Namespace , t . SecretName )
}
2018-01-07 23:36:03 +00:00
2018-12-20 19:08:03 +00:00
cert , key , err := getCertificateBlocks ( secret , ingress . Namespace , t . SecretName )
if err != nil {
return err
}
2018-01-07 23:36:03 +00:00
2018-12-20 19:08:03 +00:00
sort . Strings ( newEntryPoints )
tlsConfig = & tls . Configuration {
EntryPoints : newEntryPoints ,
Certificate : & tls . Certificate {
CertFile : tls . FileOrContent ( cert ) ,
KeyFile : tls . FileOrContent ( key ) ,
} ,
}
tlsConfigs [ configKey ] = tlsConfig
2018-01-07 23:36:03 +00:00
}
2018-12-20 19:08:03 +00:00
}
return nil
}
func getTLSConfig ( tlsConfigs map [ string ] * tls . Configuration ) [ ] * tls . Configuration {
var secretNames [ ] string
for secretName := range tlsConfigs {
secretNames = append ( secretNames , secretName )
}
sort . Strings ( secretNames )
2018-01-07 23:36:03 +00:00
2018-12-20 19:08:03 +00:00
var configs [ ] * tls . Configuration
for _ , secretName := range secretNames {
configs = append ( configs , tlsConfigs [ secretName ] )
2018-01-07 23:36:03 +00:00
}
2018-12-20 19:08:03 +00:00
return configs
}
func mergeEntryPoint ( entryPoints [ ] string , newEntryPoint string ) [ ] string {
for _ , ep := range entryPoints {
if ep == newEntryPoint {
return entryPoints
}
}
entryPoints = append ( entryPoints , newEntryPoint )
sort . Strings ( entryPoints )
return entryPoints
2018-01-07 23:36:03 +00:00
}
2018-07-02 09:52:04 +00:00
func getCertificateBlocks ( secret * corev1 . Secret , namespace , secretName string ) ( string , string , error ) {
var missingEntries [ ] string
tlsCrtData , tlsCrtExists := secret . Data [ "tls.crt" ]
if ! tlsCrtExists {
missingEntries = append ( missingEntries , "tls.crt" )
}
tlsKeyData , tlsKeyExists := secret . Data [ "tls.key" ]
if ! tlsKeyExists {
missingEntries = append ( missingEntries , "tls.key" )
}
if len ( missingEntries ) > 0 {
return "" , "" , fmt . Errorf ( "secret %s/%s is missing the following TLS data entries: %s" ,
namespace , secretName , strings . Join ( missingEntries , ", " ) )
}
cert := string ( tlsCrtData [ : ] )
if cert == "" {
missingEntries = append ( missingEntries , "tls.crt" )
}
key := string ( tlsKeyData [ : ] )
if key == "" {
missingEntries = append ( missingEntries , "tls.key" )
}
if len ( missingEntries ) > 0 {
return "" , "" , fmt . Errorf ( "secret %s/%s contains the following empty TLS data entries: %s" ,
namespace , secretName , strings . Join ( missingEntries , ", " ) )
}
return cert , key , nil
}
2018-04-16 12:44:04 +00:00
// endpointPortNumber returns the port to be used for this endpoint. It is zero
// if the endpoint does not match the given service port.
func endpointPortNumber ( servicePort corev1 . ServicePort , endpointPorts [ ] corev1 . EndpointPort ) int32 {
// Is this reasonable to assume?
if len ( endpointPorts ) == 0 {
return servicePort . Port
}
for _ , endpointPort := range endpointPorts {
// For matching endpoints, the port names must correspond, either by
// being empty or non-empty. Multi-port services mandate non-empty
// names and allow us to filter for the right addresses.
if servicePort . Name == endpointPort . Name {
return endpointPort . Port
2016-05-20 16:34:57 +00:00
}
}
2018-04-16 12:44:04 +00:00
return 0
2016-05-20 16:34:57 +00:00
}
2018-02-14 08:56:04 +00:00
func equalPorts ( servicePort corev1 . ServicePort , ingressPort intstr . IntOrString ) bool {
2016-11-11 22:50:20 +00:00
if int ( servicePort . Port ) == ingressPort . IntValue ( ) {
2016-05-18 16:30:42 +00:00
return true
}
if servicePort . Name != "" && servicePort . Name == ingressPort . String ( ) {
return true
}
return false
}
2018-02-01 18:04:04 +00:00
func ( p * Provider ) shouldProcessIngress ( annotationIngressClass string ) bool {
if len ( p . IngressClass ) == 0 {
return len ( annotationIngressClass ) == 0 || annotationIngressClass == traefikDefaultIngressClass
}
return annotationIngressClass == p . IngressClass
2017-03-03 19:30:22 +00:00
}
2017-12-15 10:48:03 +00:00
2018-07-02 09:52:04 +00:00
func getAuthConfig ( i * extensionsv1beta1 . Ingress , k8sClient Client ) ( * types . Auth , error ) {
authType := getStringValue ( i . Annotations , annotationKubernetesAuthType , "" )
if len ( authType ) == 0 {
return nil , nil
}
auth := & types . Auth {
HeaderField : getStringValue ( i . Annotations , annotationKubernetesAuthHeaderField , "" ) ,
}
switch strings . ToLower ( authType ) {
case "basic" :
basic , err := getBasicAuthConfig ( i , k8sClient )
if err != nil {
return nil , err
}
auth . Basic = basic
case "digest" :
digest , err := getDigestAuthConfig ( i , k8sClient )
if err != nil {
return nil , err
}
auth . Digest = digest
case "forward" :
forward , err := getForwardAuthConfig ( i , k8sClient )
if err != nil {
return nil , err
}
auth . Forward = forward
default :
return nil , fmt . Errorf ( "unsupported auth-type on annotation %s: %s" , annotationKubernetesAuthType , authType )
}
return auth , nil
}
func getBasicAuthConfig ( i * extensionsv1beta1 . Ingress , k8sClient Client ) ( * types . Basic , error ) {
credentials , err := getAuthCredentials ( i , k8sClient )
if err != nil {
return nil , err
}
2018-07-16 11:52:03 +00:00
return & types . Basic {
Users : credentials ,
RemoveHeader : getBoolValue ( i . Annotations , annotationKubernetesAuthRemoveHeader , false ) ,
} , nil
2018-07-02 09:52:04 +00:00
}
func getDigestAuthConfig ( i * extensionsv1beta1 . Ingress , k8sClient Client ) ( * types . Digest , error ) {
credentials , err := getAuthCredentials ( i , k8sClient )
if err != nil {
return nil , err
}
2018-07-16 11:52:03 +00:00
return & types . Digest { Users : credentials ,
RemoveHeader : getBoolValue ( i . Annotations , annotationKubernetesAuthRemoveHeader , false ) ,
} , nil
2018-07-02 09:52:04 +00:00
}
func getAuthCredentials ( i * extensionsv1beta1 . Ingress , k8sClient Client ) ( [ ] string , error ) {
authSecret := getStringValue ( i . Annotations , annotationKubernetesAuthSecret , "" )
if authSecret == "" {
return nil , fmt . Errorf ( "auth-secret annotation %s must be set" , annotationKubernetesAuthSecret )
}
auth , err := loadAuthCredentials ( i . Namespace , authSecret , k8sClient )
if err != nil {
return nil , fmt . Errorf ( "failed to load auth credentials: %s" , err )
}
return auth , nil
}
func loadAuthCredentials ( namespace , secretName string , k8sClient Client ) ( [ ] string , error ) {
secret , ok , err := k8sClient . GetSecret ( namespace , secretName )
if err != nil {
return nil , fmt . Errorf ( "failed to fetch secret %q/%q: %s" , namespace , secretName , err )
}
if ! ok {
return nil , fmt . Errorf ( "secret %q/%q not found" , namespace , secretName )
}
if secret == nil {
return nil , fmt . Errorf ( "data for secret %q/%q must not be nil" , namespace , secretName )
}
if len ( secret . Data ) != 1 {
return nil , fmt . Errorf ( "found %d elements for secret %q/%q, must be single element exactly" , len ( secret . Data ) , namespace , secretName )
}
var firstSecret [ ] byte
for _ , v := range secret . Data {
firstSecret = v
break
}
var credentials [ ] string
scanner := bufio . NewScanner ( bytes . NewReader ( firstSecret ) )
for scanner . Scan ( ) {
if cred := scanner . Text ( ) ; len ( cred ) > 0 {
credentials = append ( credentials , cred )
}
}
if len ( credentials ) == 0 {
return nil , fmt . Errorf ( "secret %q/%q does not contain any credentials" , namespace , secretName )
}
return credentials , nil
}
func getForwardAuthConfig ( i * extensionsv1beta1 . Ingress , k8sClient Client ) ( * types . Forward , error ) {
authURL := getStringValue ( i . Annotations , annotationKubernetesAuthForwardURL , "" )
if len ( authURL ) == 0 {
return nil , fmt . Errorf ( "forward authentication requires a url" )
}
forwardAuth := & types . Forward {
Address : authURL ,
TrustForwardHeader : getBoolValue ( i . Annotations , annotationKubernetesAuthForwardTrustHeaders , false ) ,
AuthResponseHeaders : getSliceStringValue ( i . Annotations , annotationKubernetesAuthForwardResponseHeaders ) ,
}
authSecretName := getStringValue ( i . Annotations , annotationKubernetesAuthForwardTLSSecret , "" )
if len ( authSecretName ) > 0 {
authSecretCert , authSecretKey , err := loadAuthTLSSecret ( i . Namespace , authSecretName , k8sClient )
if err != nil {
return nil , fmt . Errorf ( "failed to load auth secret: %s" , err )
}
forwardAuth . TLS = & types . ClientTLS {
Cert : authSecretCert ,
Key : authSecretKey ,
InsecureSkipVerify : getBoolValue ( i . Annotations , annotationKubernetesAuthForwardTLSInsecure , false ) ,
}
}
return forwardAuth , nil
}
func loadAuthTLSSecret ( namespace , secretName string , k8sClient Client ) ( string , string , error ) {
secret , exists , err := k8sClient . GetSecret ( namespace , secretName )
if err != nil {
return "" , "" , fmt . Errorf ( "failed to fetch secret %q/%q: %s" , namespace , secretName , err )
}
if ! exists {
return "" , "" , fmt . Errorf ( "secret %q/%q does not exist" , namespace , secretName )
}
if secret == nil {
return "" , "" , fmt . Errorf ( "data for secret %q/%q must not be nil" , namespace , secretName )
}
if len ( secret . Data ) != 2 {
return "" , "" , fmt . Errorf ( "found %d elements for secret %q/%q, must be two elements exactly" , len ( secret . Data ) , namespace , secretName )
}
return getCertificateBlocks ( secret , namespace , secretName )
}
2018-07-12 13:20:04 +00:00
func getFrontendRedirect ( i * extensionsv1beta1 . Ingress , baseName , path string ) * types . Redirect {
2018-01-31 18:10:04 +00:00
permanent := getBoolValue ( i . Annotations , annotationKubernetesRedirectPermanent , false )
2018-12-26 10:52:03 +00:00
if appRoot := getStringValue ( i . Annotations , annotationKubernetesAppRoot , "" ) ; appRoot != "" && ( path == "/" || path == "" ) {
2019-02-05 16:30:07 +00:00
regex := fmt . Sprintf ( "%s$" , baseName )
if path == "" {
regex = fmt . Sprintf ( "%s/$" , baseName )
}
2018-07-12 13:20:04 +00:00
return & types . Redirect {
2019-02-05 16:30:07 +00:00
Regex : regex ,
2018-07-12 13:20:04 +00:00
Replacement : fmt . Sprintf ( "%s/%s" , strings . TrimRight ( baseName , "/" ) , strings . TrimLeft ( appRoot , "/" ) ) ,
Permanent : permanent ,
}
}
2018-01-26 12:29:42 +00:00
redirectEntryPoint := getStringValue ( i . Annotations , annotationKubernetesRedirectEntryPoint , "" )
if len ( redirectEntryPoint ) > 0 {
return & types . Redirect {
EntryPoint : redirectEntryPoint ,
2018-01-31 18:10:04 +00:00
Permanent : permanent ,
2018-01-26 12:29:42 +00:00
}
}
2017-12-15 10:48:03 +00:00
2018-10-04 07:58:03 +00:00
redirectRegex , err := getStringSafeValue ( i . Annotations , annotationKubernetesRedirectRegex , "" )
2018-08-14 14:48:04 +00:00
if err != nil {
log . Debugf ( "Skipping Redirect on Ingress %s/%s due to invalid regex: %s" , i . Namespace , i . Name , redirectRegex )
return nil
}
2018-10-04 07:58:03 +00:00
redirectReplacement , err := getStringSafeValue ( i . Annotations , annotationKubernetesRedirectReplacement , "" )
2018-08-14 14:48:04 +00:00
if err != nil {
log . Debugf ( "Skipping Redirect on Ingress %s/%s due to invalid replacement: %q" , i . Namespace , i . Name , redirectRegex )
return nil
}
2018-01-26 12:29:42 +00:00
if len ( redirectRegex ) > 0 && len ( redirectReplacement ) > 0 {
2017-12-15 10:48:03 +00:00
return & types . Redirect {
2018-01-26 12:29:42 +00:00
Regex : redirectRegex ,
Replacement : redirectReplacement ,
2018-01-31 18:10:04 +00:00
Permanent : permanent ,
2017-12-15 10:48:03 +00:00
}
}
2018-01-26 12:29:42 +00:00
2017-12-15 10:48:03 +00:00
return nil
}
2018-01-31 14:32:04 +00:00
2018-03-23 16:40:04 +00:00
func getWhiteList ( i * extensionsv1beta1 . Ingress ) * types . WhiteList {
ranges := getSliceStringValue ( i . Annotations , annotationKubernetesWhiteListSourceRange )
if len ( ranges ) <= 0 {
return nil
}
return & types . WhiteList {
2018-08-24 14:20:03 +00:00
SourceRange : ranges ,
IPStrategy : getIPStrategy ( i . Annotations ) ,
}
}
func getIPStrategy ( annotations map [ string ] string ) * types . IPStrategy {
ipStrategy := getBoolValue ( annotations , annotationKubernetesWhiteListIPStrategy , false )
depth := getIntValue ( annotations , annotationKubernetesWhiteListIPStrategyDepth , 0 )
excludedIPs := getSliceStringValue ( annotations , annotationKubernetesWhiteListIPStrategyExcludedIPs )
if depth == 0 && len ( excludedIPs ) == 0 && ! ipStrategy {
return nil
}
return & types . IPStrategy {
Depth : depth ,
ExcludedIPs : excludedIPs ,
2018-03-23 16:40:04 +00:00
}
}
2018-10-29 17:42:03 +00:00
func getResponseForwarding ( service * corev1 . Service ) * types . ResponseForwarding {
flushIntervalValue := getStringValue ( service . Annotations , annotationKubernetesResponseForwardingFlushInterval , "" )
if len ( flushIntervalValue ) == 0 {
return nil
}
return & types . ResponseForwarding {
FlushInterval : flushIntervalValue ,
}
}
2018-02-14 08:56:04 +00:00
func getBuffering ( service * corev1 . Service ) * types . Buffering {
2018-01-26 12:29:42 +00:00
var buffering * types . Buffering
bufferingRaw := getStringValue ( service . Annotations , annotationKubernetesBuffering , "" )
if len ( bufferingRaw ) > 0 {
buffering = & types . Buffering { }
err := yaml . Unmarshal ( [ ] byte ( bufferingRaw ) , buffering )
if err != nil {
log . Error ( err )
return nil
2017-12-21 21:07:37 +00:00
}
}
2018-01-26 12:29:42 +00:00
return buffering
2017-12-21 21:07:37 +00:00
}
2018-02-14 08:56:04 +00:00
func getLoadBalancer ( service * corev1 . Service ) * types . LoadBalancer {
2017-12-21 14:40:07 +00:00
loadBalancer := & types . LoadBalancer {
Method : "wrr" ,
}
2018-01-26 12:29:42 +00:00
if getStringValue ( service . Annotations , annotationKubernetesLoadBalancerMethod , "" ) == "drr" {
2017-12-21 14:40:07 +00:00
loadBalancer . Method = "drr"
}
if stickiness := getStickiness ( service ) ; stickiness != nil {
loadBalancer . Stickiness = stickiness
}
return loadBalancer
}
2018-02-14 08:56:04 +00:00
func getStickiness ( service * corev1 . Service ) * types . Stickiness {
2018-01-26 12:29:42 +00:00
if getBoolValue ( service . Annotations , annotationKubernetesAffinity , false ) {
2017-12-21 14:40:07 +00:00
stickiness := & types . Stickiness { }
2018-01-26 12:29:42 +00:00
if cookieName := getStringValue ( service . Annotations , annotationKubernetesSessionCookieName , "" ) ; len ( cookieName ) > 0 {
2017-12-21 14:40:07 +00:00
stickiness . CookieName = cookieName
}
return stickiness
}
return nil
}
2018-02-14 08:56:04 +00:00
func getHeader ( i * extensionsv1beta1 . Ingress ) * types . Headers {
2018-01-02 17:03:50 +00:00
headers := & types . Headers {
2018-01-26 12:29:42 +00:00
CustomRequestHeaders : getMapValue ( i . Annotations , annotationKubernetesCustomRequestHeaders ) ,
CustomResponseHeaders : getMapValue ( i . Annotations , annotationKubernetesCustomResponseHeaders ) ,
AllowedHosts : getSliceStringValue ( i . Annotations , annotationKubernetesAllowedHosts ) ,
HostsProxyHeaders : getSliceStringValue ( i . Annotations , annotationKubernetesProxyHeaders ) ,
2018-05-14 09:44:03 +00:00
SSLForceHost : getBoolValue ( i . Annotations , annotationKubernetesSSLForceHost , false ) ,
2018-01-26 12:29:42 +00:00
SSLRedirect : getBoolValue ( i . Annotations , annotationKubernetesSSLRedirect , false ) ,
SSLTemporaryRedirect : getBoolValue ( i . Annotations , annotationKubernetesSSLTemporaryRedirect , false ) ,
SSLHost : getStringValue ( i . Annotations , annotationKubernetesSSLHost , "" ) ,
SSLProxyHeaders : getMapValue ( i . Annotations , annotationKubernetesSSLProxyHeaders ) ,
STSSeconds : getInt64Value ( i . Annotations , annotationKubernetesHSTSMaxAge , 0 ) ,
STSIncludeSubdomains : getBoolValue ( i . Annotations , annotationKubernetesHSTSIncludeSubdomains , false ) ,
STSPreload : getBoolValue ( i . Annotations , annotationKubernetesHSTSPreload , false ) ,
ForceSTSHeader : getBoolValue ( i . Annotations , annotationKubernetesForceHSTSHeader , false ) ,
FrameDeny : getBoolValue ( i . Annotations , annotationKubernetesFrameDeny , false ) ,
CustomFrameOptionsValue : getStringValue ( i . Annotations , annotationKubernetesCustomFrameOptionsValue , "" ) ,
ContentTypeNosniff : getBoolValue ( i . Annotations , annotationKubernetesContentTypeNosniff , false ) ,
BrowserXSSFilter : getBoolValue ( i . Annotations , annotationKubernetesBrowserXSSFilter , false ) ,
2018-03-02 13:24:03 +00:00
CustomBrowserXSSValue : getStringValue ( i . Annotations , annotationKubernetesCustomBrowserXSSValue , "" ) ,
2018-01-26 12:29:42 +00:00
ContentSecurityPolicy : getStringValue ( i . Annotations , annotationKubernetesContentSecurityPolicy , "" ) ,
PublicKey : getStringValue ( i . Annotations , annotationKubernetesPublicKey , "" ) ,
ReferrerPolicy : getStringValue ( i . Annotations , annotationKubernetesReferrerPolicy , "" ) ,
IsDevelopment : getBoolValue ( i . Annotations , annotationKubernetesIsDevelopment , false ) ,
2017-12-21 14:40:07 +00:00
}
2018-01-02 17:03:50 +00:00
if ! headers . HasSecureHeadersDefined ( ) && ! headers . HasCustomHeadersDefined ( ) {
return nil
}
return headers
2017-12-21 14:40:07 +00:00
}
2018-02-14 08:56:04 +00:00
func getMaxConn ( service * corev1 . Service ) * types . MaxConn {
2018-01-26 12:29:42 +00:00
amount := getInt64Value ( service . Annotations , annotationKubernetesMaxConnAmount , - 1 )
extractorFunc := getStringValue ( service . Annotations , annotationKubernetesMaxConnExtractorFunc , "" )
2017-12-21 21:44:06 +00:00
if amount >= 0 && len ( extractorFunc ) > 0 {
return & types . MaxConn {
ExtractorFunc : extractorFunc ,
Amount : amount ,
}
}
return nil
}
2018-01-26 12:29:42 +00:00
2018-02-14 08:56:04 +00:00
func getCircuitBreaker ( service * corev1 . Service ) * types . CircuitBreaker {
2018-01-26 12:29:42 +00:00
if expression := getStringValue ( service . Annotations , annotationKubernetesCircuitBreakerExpression , "" ) ; expression != "" {
return & types . CircuitBreaker {
Expression : expression ,
}
}
return nil
}
2018-02-14 08:56:04 +00:00
func getErrorPages ( i * extensionsv1beta1 . Ingress ) map [ string ] * types . ErrorPage {
2018-01-26 12:29:42 +00:00
var errorPages map [ string ] * types . ErrorPage
pagesRaw := getStringValue ( i . Annotations , annotationKubernetesErrorPages , "" )
if len ( pagesRaw ) > 0 {
errorPages = make ( map [ string ] * types . ErrorPage )
err := yaml . Unmarshal ( [ ] byte ( pagesRaw ) , errorPages )
if err != nil {
log . Error ( err )
return nil
}
}
return errorPages
}
2018-02-14 08:56:04 +00:00
func getRateLimit ( i * extensionsv1beta1 . Ingress ) * types . RateLimit {
2018-01-26 12:29:42 +00:00
var rateLimit * types . RateLimit
rateRaw := getStringValue ( i . Annotations , annotationKubernetesRateLimit , "" )
if len ( rateRaw ) > 0 {
rateLimit = & types . RateLimit { }
err := yaml . Unmarshal ( [ ] byte ( rateRaw ) , rateLimit )
if err != nil {
log . Error ( err )
return nil
}
}
return rateLimit
}
2018-10-04 07:58:03 +00:00
2018-10-29 15:02:06 +00:00
func getPassTLSClientCert ( i * extensionsv1beta1 . Ingress ) * types . TLSClientHeaders {
var passTLSClientCert * types . TLSClientHeaders
passRaw := getStringValue ( i . Annotations , annotationKubernetesPassTLSClientCert , "" )
if len ( passRaw ) > 0 {
passTLSClientCert = & types . TLSClientHeaders { }
err := yaml . Unmarshal ( [ ] byte ( passRaw ) , passTLSClientCert )
if err != nil {
log . Error ( err )
return nil
}
}
return passTLSClientCert
}
2018-10-04 07:58:03 +00:00
func templateSafeString ( value string ) error {
_ , err := strconv . Unquote ( ` " ` + value + ` " ` )
return err
}