2017-04-17 12:50:02 +02:00
package kubernetes
2016-02-08 21:57:32 +01:00
import (
2017-04-23 16:17:20 +02:00
"bufio"
"bytes"
"errors"
2017-07-03 10:06:32 +02:00
"flag"
2017-03-07 13:09:11 +01:00
"fmt"
2018-06-05 02:28:03 -06:00
"net"
2017-03-07 13:09:11 +01:00
"os"
2016-06-22 18:31:14 +02:00
"reflect"
2016-05-18 17:30:42 +01:00
"strconv"
2016-04-25 16:56:06 +02:00
"strings"
2016-02-08 21:57:32 +01:00
"text/template"
"time"
2016-09-12 21:06:21 +02:00
2016-12-30 09:21:13 +01:00
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
2016-11-11 23:50:20 +01:00
"github.com/containous/traefik/log"
2017-04-17 12:50:02 +02:00
"github.com/containous/traefik/provider"
2017-12-02 19:28:11 +01:00
"github.com/containous/traefik/provider/label"
2016-11-11 23:50:20 +01:00
"github.com/containous/traefik/safe"
2018-01-08 00:36:03 +01:00
"github.com/containous/traefik/tls"
2016-11-11 23:50:20 +01:00
"github.com/containous/traefik/types"
2018-01-26 13:29:42 +01:00
"gopkg.in/yaml.v2"
2018-02-14 16:56:04 +08:00
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
2018-04-12 11:14:05 +02:00
"k8s.io/apimachinery/pkg/labels"
2018-02-14 16:56:04 +08:00
"k8s.io/apimachinery/pkg/util/intstr"
2016-02-08 21:57:32 +01:00
)
2017-04-17 12:50:02 +02:00
var _ provider . Provider = ( * Provider ) ( nil )
2016-08-16 19:13:18 +02:00
2017-02-07 00:04:30 +01:00
const (
2018-02-01 10:04:04 -08:00
ruleTypePathPrefix = "PathPrefix"
ruleTypeReplacePath = "ReplacePath"
traefikDefaultRealm = "traefik"
traefikDefaultIngressClass = "traefik"
2017-02-07 00:04:30 +01:00
)
2017-04-17 12:50:02 +02:00
// Provider holds configurations of the provider.
type Provider struct {
2017-10-02 10:32:02 +02:00
provider . BaseProvider ` mapstructure:",squash" export:"true" `
2017-04-17 12:50:02 +02: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)" `
2017-10-02 10:32:02 +02:00
DisablePassHostHeaders bool ` description:"Kubernetes disable PassHost Headers" export:"true" `
2017-11-20 02:12:03 +01:00
EnablePassTLSCert bool ` description:"Kubernetes enable Pass TLS Client Certs" export:"true" `
2017-10-02 10:32:02 +02:00
Namespaces Namespaces ` description:"Kubernetes namespaces" export:"true" `
2018-04-12 11:14:05 +02:00
LabelSelector string ` description:"Kubernetes Ingress label selector to use" export:"true" `
2018-02-01 10:04:04 -08:00
IngressClass string ` description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true" `
2016-06-22 18:31:14 +02:00
lastConfiguration safe . Safe
2016-02-08 21:57:32 +01:00
}
2018-04-12 11:14:05 +02: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 13:09:11 +01:00
withEndpoint := ""
2017-04-17 12:50:02 +02:00
if p . Endpoint != "" {
withEndpoint = fmt . Sprintf ( " with endpoint %v" , p . Endpoint )
2016-02-08 21:57:32 +01:00
}
2017-03-07 13:09:11 +01:00
2018-04-12 11:14:05 +02:00
var cl * clientImpl
2017-03-07 13:09:11 +01:00
if os . Getenv ( "KUBERNETES_SERVICE_HOST" ) != "" && os . Getenv ( "KUBERNETES_SERVICE_PORT" ) != "" {
2018-01-15 16:04:05 +01:00
log . Infof ( "Creating in-cluster Provider client%s" , withEndpoint )
2018-04-12 11:14:05 +02: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 13:09:11 +01:00
}
2018-04-12 11:14:05 +02:00
return cl , err
2016-04-19 19:23:08 +02:00
}
2017-04-17 12:50:02 +02:00
// Provide allows the k8s provider to provide configurations to traefik
2016-04-19 19:23:08 +02:00
// using the given configuration channel.
2017-04-17 12:50:02 +02:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool , constraints types . Constraints ) error {
2017-07-03 10:06:32 +02: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 19:28:11 +01:00
err := flag . Set ( "logtostderr" , "true" )
if err != nil {
return err
}
2017-07-03 10:06:32 +02:00
2018-02-01 10:04:04 -08:00
// We require that IngressClasses start with `traefik` to reduce chances of
// conflict with other Ingress Providers
if len ( p . IngressClass ) > 0 && ! strings . HasPrefix ( p . IngressClass , traefikDefaultIngressClass ) {
return fmt . Errorf ( "value for IngressClass has to be empty or start with the prefix %q, instead found %q" , traefikDefaultIngressClass , p . IngressClass )
}
2018-04-12 11:14:05 +02:00
log . Debugf ( "Using Ingress label selector: %q" , p . LabelSelector )
k8sClient , err := p . newK8sClient ( p . LabelSelector )
2016-02-08 21:57:32 +01:00
if err != nil {
return err
}
2017-04-17 12:50:02 +02:00
p . Constraints = append ( p . Constraints , constraints ... )
2016-02-08 21:57:32 +01:00
pool . Go ( func ( stop chan bool ) {
operation := func ( ) error {
for {
2016-12-03 21:20:39 +01:00
stopWatch := make ( chan struct { } , 1 )
2016-05-19 20:09:01 +02:00
defer close ( stopWatch )
2018-04-12 11:14:05 +02:00
eventsChan , err := k8sClient . WatchAll ( p . Namespaces , stopWatch )
2016-04-25 16:56:06 +02:00
if err != nil {
log . Errorf ( "Error watching kubernetes events: %v" , err )
2016-05-19 20:09:01 +02:00
timer := time . NewTimer ( 1 * time . Second )
select {
case <- timer . C :
return err
case <- stop :
return nil
}
2016-04-25 16:56:06 +02:00
}
for {
select {
case <- stop :
return nil
case event := <- eventsChan :
2017-10-10 16:26:03 +02:00
log . Debugf ( "Received Kubernetes event kind %T" , event )
2017-04-17 12:50:02 +02:00
templateObjects , err := p . loadIngresses ( k8sClient )
2016-04-25 16:56:06 +02:00
if err != nil {
return err
}
2017-04-17 12:50:02 +02:00
if reflect . DeepEqual ( p . lastConfiguration . Get ( ) , templateObjects ) {
2017-10-10 16:26:03 +02:00
log . Debugf ( "Skipping Kubernetes event kind %T" , event )
2016-06-22 18:31:14 +02:00
} else {
2017-04-17 12:50:02 +02:00
p . lastConfiguration . Set ( templateObjects )
2016-06-22 18:31:14 +02:00
configurationChan <- types . ConfigMessage {
ProviderName : "kubernetes" ,
2017-04-17 12:50:02 +02:00
Configuration : p . loadConfig ( * templateObjects ) ,
2016-06-22 18:31:14 +02:00
}
2016-04-25 16:56:06 +02:00
}
2016-02-08 21:57:32 +01:00
}
}
}
}
notify := func ( err error , time time . Duration ) {
2017-06-28 01:32:19 +02:00
log . Errorf ( "Provider connection error: %s; retrying in %s" , err , time )
2016-02-08 21:57:32 +01:00
}
2016-12-08 13:32:12 +01:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
2016-02-08 21:57:32 +01:00
if err != nil {
2017-06-28 01:32:19 +02:00
log . Errorf ( "Cannot connect to Provider: %s" , err )
2016-02-08 21:57:32 +01:00
}
} )
return nil
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) loadIngresses ( k8sClient Client ) ( * types . Configuration , error ) {
2017-10-10 16:26:03 +02:00
ingresses := k8sClient . GetIngresses ( )
2016-11-11 23:50:20 +01:00
2016-04-19 19:23:08 +02:00
templateObjects := types . Configuration {
2017-05-26 17:03:14 +02:00
Backends : map [ string ] * types . Backend { } ,
Frontends : map [ string ] * types . Frontend { } ,
2016-04-19 19:23:08 +02:00
}
2018-01-25 20:51:06 +01:00
2016-04-19 19:23:08 +02:00
for _ , i := range ingresses {
2018-01-26 13:29:42 +01:00
annotationIngressClass := getAnnotationName ( i . Annotations , annotationKubernetesIngressClass )
ingressClass := i . Annotations [ annotationIngressClass ]
2017-03-03 11:30:22 -08:00
2018-02-01 10:04:04 -08:00
if ! p . shouldProcessIngress ( ingressClass ) {
2017-03-03 11:30:22 -08:00
continue
}
2018-01-24 11:57:06 +01:00
tlsSection , err := getTLS ( i , k8sClient )
2018-01-08 00:36:03 +01:00
if err != nil {
log . Errorf ( "Error configuring TLS for ingress %s/%s: %v" , i . Namespace , i . Name , err )
continue
}
2018-01-24 11:57:06 +01:00
templateObjects . TLS = append ( templateObjects . TLS , tlsSection ... )
2018-01-08 00:36:03 +01:00
2016-04-19 19:23:08 +02:00
for _ , r := range i . Spec . Rules {
2016-12-08 13:32:52 +01:00
if r . HTTP == nil {
2017-05-26 17:03:14 +02:00
log . Warn ( "Error in ingress: HTTP is nil" )
2016-12-08 13:32:52 +01:00
continue
}
2017-07-07 15:27:54 -04:00
2016-04-19 19:23:08 +02:00
for _ , pa := range r . HTTP . Paths {
2018-01-25 20:51:06 +01:00
baseName := r . Host + pa . Path
if _ , exists := templateObjects . Backends [ baseName ] ; ! exists {
templateObjects . Backends [ baseName ] = & types . Backend {
2016-04-19 19:23:08 +02:00
Servers : make ( map [ string ] types . Server ) ,
2017-01-25 08:11:00 -05:00
LoadBalancer : & types . LoadBalancer {
Method : "wrr" ,
} ,
2016-04-19 19:23:08 +02:00
}
}
2017-02-10 03:05:59 -08:00
2018-01-26 13:29:42 +01: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 20:51:06 +01:00
delete ( templateObjects . Backends , baseName )
2017-10-12 15:48:03 +02:00
continue
2017-04-23 16:17:20 +02:00
}
2017-02-10 03:05:59 -08:00
2018-01-25 20:51:06 +01:00
if _ , exists := templateObjects . Frontends [ baseName ] ; ! exists {
2017-04-23 16:17:20 +02:00
basicAuthCreds , err := handleBasicAuthConfig ( i , k8sClient )
if err != nil {
2018-01-25 20:51:06 +01:00
log . Errorf ( "Failed to retrieve basic auth configuration for ingress %s/%s: %s" , i . Namespace , i . Name , err )
2017-06-28 01:32:35 +02:00
continue
2017-04-23 16:17:20 +02:00
}
2017-07-29 19:35:23 +03:00
2018-01-26 13:29:42 +01:00
passHostHeader := getBoolValue ( i . Annotations , annotationKubernetesPreserveHost , ! p . DisablePassHostHeaders )
passTLSCert := getBoolValue ( i . Annotations , annotationKubernetesPassTLSCert , p . EnablePassTLSCert )
priority := getIntValue ( i . Annotations , annotationKubernetesPriority , 0 )
entryPoints := getSliceStringValue ( i . Annotations , annotationKubernetesFrontendEntryPoints )
2017-12-21 17:47:50 +01:00
2018-01-25 20:51:06 +01:00
templateObjects . Frontends [ baseName ] = & types . Frontend {
2018-03-23 17:40:04 +01:00
Backend : baseName ,
PassHostHeader : passHostHeader ,
PassTLSCert : passTLSCert ,
Routes : make ( map [ string ] types . Route ) ,
Priority : priority ,
BasicAuth : basicAuthCreds ,
WhiteList : getWhiteList ( i ) ,
Redirect : getFrontendRedirect ( i ) ,
EntryPoints : entryPoints ,
Headers : getHeader ( i ) ,
Errors : getErrorPages ( i ) ,
RateLimit : getRateLimit ( i ) ,
2016-04-19 19:23:08 +02:00
}
}
2017-01-20 14:16:05 +01:00
2017-12-21 15:40:07 +01:00
if len ( r . Host ) > 0 {
2018-01-25 20:51:06 +01:00
if _ , exists := templateObjects . Frontends [ baseName ] . Routes [ r . Host ] ; ! exists {
templateObjects . Frontends [ baseName ] . Routes [ r . Host ] = types . Route {
2017-12-21 15:40:07 +01:00
Rule : getRuleForHost ( r . Host ) ,
2016-05-25 13:16:19 +01:00
}
2016-04-19 19:23:08 +02:00
}
}
2017-02-07 00:04:30 +01:00
2018-02-19 09:36:03 -05: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 )
delete ( templateObjects . Frontends , baseName )
continue
}
if rule != "" {
2018-01-25 20:51:06 +01:00
templateObjects . Frontends [ baseName ] . Routes [ pa . Path ] = types . Route {
2017-07-07 15:27:54 -04:00
Rule : rule ,
2016-04-19 19:23:08 +02:00
}
}
2017-02-07 00:04:30 +01:00
2018-01-25 20:51:06 +01:00
service , exists , err := k8sClient . GetService ( i . Namespace , pa . Backend . ServiceName )
2017-03-17 08:34:34 -07:00
if err != nil {
2018-01-25 20:51:06 +01:00
log . Errorf ( "Error while retrieving service information from k8s API %s/%s: %v" , i . Namespace , pa . Backend . ServiceName , err )
2017-03-17 08:34:34 -07:00
return nil , err
}
if ! exists {
2018-01-25 20:51:06 +01:00
log . Errorf ( "Service not found for %s/%s" , i . Namespace , pa . Backend . ServiceName )
delete ( templateObjects . Frontends , baseName )
2016-05-26 00:53:51 +01:00
continue
2016-04-25 16:56:06 +02:00
}
2016-11-11 23:50:20 +01:00
2018-01-26 13:29:42 +01:00
templateObjects . Backends [ baseName ] . CircuitBreaker = getCircuitBreaker ( service )
2018-01-25 20:51:06 +01:00
templateObjects . Backends [ baseName ] . LoadBalancer = getLoadBalancer ( service )
2018-01-26 13:29:42 +01:00
templateObjects . Backends [ baseName ] . MaxConn = getMaxConn ( service )
2018-01-25 20:51:06 +01:00
templateObjects . Backends [ baseName ] . Buffering = getBuffering ( service )
2018-01-31 15:32:04 +01:00
2017-12-02 19:28:11 +01:00
protocol := label . DefaultProtocol
2016-05-26 00:53:51 +01:00
for _ , port := range service . Spec . Ports {
if equalPorts ( port , pa . Backend . ServicePort ) {
if port . Port == 443 {
protocol = "https"
}
2017-07-07 15:27:54 -04:00
2017-02-09 17:25:38 -08:00
if service . Spec . Type == "ExternalName" {
url := protocol + "://" + service . Spec . ExternalName
name := url
2018-01-25 20:51:06 +01:00
templateObjects . Backends [ baseName ] . Servers [ name ] = types . Server {
2017-02-09 17:25:38 -08:00
URL : url ,
2018-04-11 16:30:04 +02:00
Weight : label . DefaultWeight ,
2016-05-20 17:34:57 +01:00
}
2016-05-26 00:53:51 +01:00
} else {
2018-01-25 20:51:06 +01:00
endpoints , exists , err := k8sClient . GetEndpoints ( service . Namespace , service . Name )
2017-04-11 17:10:46 +02:00
if err != nil {
2018-01-25 20:51:06 +01:00
log . Errorf ( "Error retrieving endpoints %s/%s: %v" , service . Namespace , service . Name , err )
2017-04-11 17:10:46 +02:00
return nil , err
}
2017-02-14 11:53:35 -08:00
2017-04-11 17:10:46 +02:00
if ! exists {
2018-01-25 20:51:06 +01:00
log . Warnf ( "Endpoints not found for %s/%s" , service . Namespace , service . Name )
2017-05-15 23:16:35 +02:00
break
2017-03-14 15:59:13 +01:00
}
2017-04-11 17:10:46 +02:00
if len ( endpoints . Subsets ) == 0 {
2018-01-25 20:51:06 +01:00
log . Warnf ( "Endpoints not available for %s/%s" , service . Namespace , service . Name )
2017-05-15 23:16:35 +02:00
break
}
for _ , subset := range endpoints . Subsets {
for _ , address := range subset . Addresses {
2018-06-05 02:28:03 -06:00
url := protocol + "://" + net . JoinHostPort ( address . IP , strconv . Itoa ( endpointPortNumber ( port , subset . Ports ) ) )
2017-05-15 23:16:35 +02:00
name := url
if address . TargetRef != nil && address . TargetRef . Name != "" {
name = address . TargetRef . Name
}
2018-01-25 20:51:06 +01:00
templateObjects . Backends [ baseName ] . Servers [ name ] = types . Server {
2017-05-15 23:16:35 +02:00
URL : url ,
2018-04-11 16:30:04 +02:00
Weight : label . DefaultWeight ,
2016-05-20 17:34:57 +01:00
}
}
2016-04-20 13:26:51 +02:00
}
2016-04-19 19:23:08 +02:00
}
2016-05-26 00:53:51 +01:00
break
2016-04-19 19:23:08 +02:00
}
}
}
}
}
return & templateObjects , nil
}
2017-12-04 11:40:03 +01:00
func ( p * Provider ) loadConfig ( templateObjects types . Configuration ) * types . Configuration {
2017-11-28 06:36:03 -06:00
var FuncMap = template . FuncMap { }
configuration , err := p . GetConfiguration ( "templates/kubernetes.tmpl" , FuncMap , templateObjects )
if err != nil {
log . Error ( err )
2017-11-20 02:12:03 +01:00
}
2017-11-28 06:36:03 -06:00
return configuration
2017-11-20 02:12:03 +01:00
}
2018-02-19 09:36:03 -05:00
func getRuleForPath ( pa extensionsv1beta1 . HTTPIngressPath , i * extensionsv1beta1 . Ingress ) ( string , error ) {
2017-07-07 15:27:54 -04:00
if len ( pa . Path ) == 0 {
2018-02-19 09:36:03 -05:00
return "" , nil
2017-07-07 15:27:54 -04:00
}
2018-01-26 13:29:42 +01:00
ruleType := getStringValue ( i . Annotations , annotationKubernetesRuleType , ruleTypePathPrefix )
2017-11-27 18:22:03 +08:00
rules := [ ] string { ruleType + ":" + pa . Path }
2017-07-07 15:27:54 -04:00
2018-02-19 09:36:03 -05:00
var pathReplaceAnnotation string
if ruleType == ruleTypeReplacePath {
pathReplaceAnnotation = annotationKubernetesRuleType
}
2018-01-26 13:29:42 +01:00
if rewriteTarget := getStringValue ( i . Annotations , annotationKubernetesRewriteTarget , "" ) ; rewriteTarget != "" {
2018-02-19 09:36:03 -05:00
if pathReplaceAnnotation != "" {
return "" , fmt . Errorf ( "rewrite-target must not be used together with annotation %q" , pathReplaceAnnotation )
}
2017-11-27 18:22:03 +08:00
rules = append ( rules , ruleTypeReplacePath + ":" + rewriteTarget )
2018-02-19 09:36:03 -05:00
pathReplaceAnnotation = annotationKubernetesRewriteTarget
2017-07-07 15:27:54 -04:00
}
2018-02-23 18:36:03 +01:00
if rootPath := getStringValue ( i . Annotations , annotationKubernetesAppRoot , "" ) ; rootPath != "" && pa . Path == "/" {
2018-02-19 09:36:03 -05:00
if pathReplaceAnnotation != "" {
return "" , fmt . Errorf ( "app-root must not be used together with annotation %q" , pathReplaceAnnotation )
}
rules = append ( rules , ruleTypeReplacePath + ":" + rootPath )
}
return strings . Join ( rules , ";" ) , nil
2017-07-07 15:27:54 -04:00
}
2017-12-21 15:40:07 +01: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-02-14 16:56:04 +08:00
func handleBasicAuthConfig ( i * extensionsv1beta1 . Ingress , k8sClient Client ) ( [ ] string , error ) {
2018-01-26 13:29:42 +01:00
annotationAuthType := getAnnotationName ( i . Annotations , annotationKubernetesAuthType )
authType , exists := i . Annotations [ annotationAuthType ]
2017-04-23 16:17:20 +02:00
if ! exists {
return nil , nil
}
2017-12-02 19:28:11 +01:00
2017-04-23 16:17:20 +02:00
if strings . ToLower ( authType ) != "basic" {
2017-06-28 01:32:19 +02:00
return nil , fmt . Errorf ( "unsupported auth-type on annotation ingress.kubernetes.io/auth-type: %q" , authType )
2017-04-23 16:17:20 +02:00
}
2017-12-02 19:28:11 +01:00
2018-01-26 13:29:42 +01:00
authSecret := getStringValue ( i . Annotations , annotationKubernetesAuthSecret , "" )
2017-04-23 16:17:20 +02:00
if authSecret == "" {
2017-06-28 01:32:19 +02:00
return nil , errors . New ( "auth-secret annotation ingress.kubernetes.io/auth-secret must be set" )
2017-04-23 16:17:20 +02:00
}
2017-12-02 19:28:11 +01:00
2017-04-23 16:17:20 +02:00
basicAuthCreds , err := loadAuthCredentials ( i . Namespace , authSecret , k8sClient )
if err != nil {
2017-06-28 01:32:19 +02:00
return nil , fmt . Errorf ( "failed to load auth credentials: %s" , err )
2017-04-23 16:17:20 +02:00
}
2017-12-02 19:28:11 +01:00
2017-04-23 16:17:20 +02:00
return basicAuthCreds , nil
}
func loadAuthCredentials ( namespace , secretName string , k8sClient Client ) ( [ ] string , error ) {
secret , ok , err := k8sClient . GetSecret ( namespace , secretName )
switch { // keep order of case conditions
case err != nil :
return nil , fmt . Errorf ( "failed to fetch secret %q/%q: %s" , namespace , secretName , err )
case ! ok :
return nil , fmt . Errorf ( "secret %q/%q not found" , namespace , secretName )
case secret == nil :
2017-06-28 01:32:19 +02:00
return nil , fmt . Errorf ( "data for secret %q/%q must not be nil" , namespace , secretName )
2017-04-23 16:17:20 +02:00
case len ( secret . Data ) != 1 :
2017-06-28 01:32:19 +02:00
return nil , fmt . Errorf ( "found %d elements for secret %q/%q, must be single element exactly" , len ( secret . Data ) , namespace , secretName )
2017-04-23 16:17:20 +02:00
default :
}
var firstSecret [ ] byte
for _ , v := range secret . Data {
firstSecret = v
break
}
creds := make ( [ ] string , 0 )
scanner := bufio . NewScanner ( bytes . NewReader ( firstSecret ) )
for scanner . Scan ( ) {
if cred := scanner . Text ( ) ; cred != "" {
creds = append ( creds , cred )
}
}
2017-06-28 01:32:19 +02:00
if len ( creds ) == 0 {
return nil , fmt . Errorf ( "secret %q/%q does not contain any credentials" , namespace , secretName )
}
2017-04-23 16:17:20 +02:00
return creds , nil
}
2018-02-14 16:56:04 +08:00
func getTLS ( ingress * extensionsv1beta1 . Ingress , k8sClient Client ) ( [ ] * tls . Configuration , error ) {
2018-01-08 00:36:03 +01:00
var tlsConfigs [ ] * tls . Configuration
for _ , t := range ingress . Spec . TLS {
tlsSecret , exists , err := k8sClient . GetSecret ( ingress . Namespace , t . SecretName )
if err != nil {
return nil , fmt . Errorf ( "failed to fetch secret %s/%s: %v" , ingress . Namespace , t . SecretName , err )
}
if ! exists {
return nil , fmt . Errorf ( "secret %s/%s does not exist" , ingress . Namespace , t . SecretName )
}
tlsCrtData , tlsCrtExists := tlsSecret . Data [ "tls.crt" ]
tlsKeyData , tlsKeyExists := tlsSecret . Data [ "tls.key" ]
var missingEntries [ ] string
if ! tlsCrtExists {
missingEntries = append ( missingEntries , "tls.crt" )
}
if ! tlsKeyExists {
missingEntries = append ( missingEntries , "tls.key" )
}
if len ( missingEntries ) > 0 {
2018-01-26 13:29:42 +01:00
return nil , fmt . Errorf ( "secret %s/%s is missing the following TLS data entries: %s" ,
ingress . Namespace , t . SecretName , strings . Join ( missingEntries , ", " ) )
2018-01-08 00:36:03 +01:00
}
2018-01-26 13:29:42 +01:00
entryPoints := getSliceStringValue ( ingress . Annotations , annotationKubernetesFrontendEntryPoints )
2018-01-08 00:36:03 +01:00
tlsConfig := & tls . Configuration {
EntryPoints : entryPoints ,
Certificate : & tls . Certificate {
CertFile : tls . FileOrContent ( tlsCrtData ) ,
KeyFile : tls . FileOrContent ( tlsKeyData ) ,
} ,
}
tlsConfigs = append ( tlsConfigs , tlsConfig )
}
return tlsConfigs , nil
}
2018-02-14 16:56:04 +08:00
func endpointPortNumber ( servicePort corev1 . ServicePort , endpointPorts [ ] corev1 . EndpointPort ) int {
2016-05-20 17:34:57 +01:00
if len ( endpointPorts ) > 0 {
2018-03-23 17:40:04 +01:00
// name is optional if there is only one port
2016-05-20 17:34:57 +01:00
port := endpointPorts [ 0 ]
for _ , endpointPort := range endpointPorts {
if servicePort . Name == endpointPort . Name {
port = endpointPort
}
}
return int ( port . Port )
}
2016-11-11 23:50:20 +01:00
return int ( servicePort . Port )
2016-05-20 17:34:57 +01:00
}
2018-02-14 16:56:04 +08:00
func equalPorts ( servicePort corev1 . ServicePort , ingressPort intstr . IntOrString ) bool {
2016-11-11 23:50:20 +01:00
if int ( servicePort . Port ) == ingressPort . IntValue ( ) {
2016-05-18 17:30:42 +01:00
return true
}
if servicePort . Name != "" && servicePort . Name == ingressPort . String ( ) {
return true
}
return false
}
2018-02-01 10:04:04 -08: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 11:30:22 -08:00
}
2017-12-15 11:48:03 +01:00
2018-02-14 16:56:04 +08:00
func getFrontendRedirect ( i * extensionsv1beta1 . Ingress ) * types . Redirect {
2018-01-31 19:10:04 +01:00
permanent := getBoolValue ( i . Annotations , annotationKubernetesRedirectPermanent , false )
2018-01-26 13:29:42 +01:00
redirectEntryPoint := getStringValue ( i . Annotations , annotationKubernetesRedirectEntryPoint , "" )
if len ( redirectEntryPoint ) > 0 {
return & types . Redirect {
EntryPoint : redirectEntryPoint ,
2018-01-31 19:10:04 +01:00
Permanent : permanent ,
2018-01-26 13:29:42 +01:00
}
}
2017-12-15 11:48:03 +01:00
2018-01-26 13:29:42 +01:00
redirectRegex := getStringValue ( i . Annotations , annotationKubernetesRedirectRegex , "" )
redirectReplacement := getStringValue ( i . Annotations , annotationKubernetesRedirectReplacement , "" )
if len ( redirectRegex ) > 0 && len ( redirectReplacement ) > 0 {
2017-12-15 11:48:03 +01:00
return & types . Redirect {
2018-01-26 13:29:42 +01:00
Regex : redirectRegex ,
Replacement : redirectReplacement ,
2018-01-31 19:10:04 +01:00
Permanent : permanent ,
2017-12-15 11:48:03 +01:00
}
}
2018-01-26 13:29:42 +01:00
2017-12-15 11:48:03 +01:00
return nil
}
2018-01-31 15:32:04 +01:00
2018-03-23 17:40:04 +01:00
func getWhiteList ( i * extensionsv1beta1 . Ingress ) * types . WhiteList {
ranges := getSliceStringValue ( i . Annotations , annotationKubernetesWhiteListSourceRange )
if len ( ranges ) <= 0 {
return nil
}
return & types . WhiteList {
SourceRange : ranges ,
UseXForwardedFor : getBoolValue ( i . Annotations , annotationKubernetesWhiteListUseXForwardedFor , false ) ,
}
}
2018-02-14 16:56:04 +08:00
func getBuffering ( service * corev1 . Service ) * types . Buffering {
2018-01-26 13:29:42 +01: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 22:07:37 +01:00
}
}
2018-01-26 13:29:42 +01:00
return buffering
2017-12-21 22:07:37 +01:00
}
2018-02-14 16:56:04 +08:00
func getLoadBalancer ( service * corev1 . Service ) * types . LoadBalancer {
2017-12-21 15:40:07 +01:00
loadBalancer := & types . LoadBalancer {
Method : "wrr" ,
}
2018-01-26 13:29:42 +01:00
if getStringValue ( service . Annotations , annotationKubernetesLoadBalancerMethod , "" ) == "drr" {
2017-12-21 15:40:07 +01:00
loadBalancer . Method = "drr"
}
if sticky := service . Annotations [ label . TraefikBackendLoadBalancerSticky ] ; len ( sticky ) > 0 {
2018-01-26 13:29:42 +01:00
log . Warnf ( "Deprecated configuration found: %s. Please use %s." , label . TraefikBackendLoadBalancerSticky , annotationKubernetesAffinity )
2017-12-21 15:40:07 +01:00
loadBalancer . Sticky = strings . EqualFold ( strings . TrimSpace ( sticky ) , "true" )
}
if stickiness := getStickiness ( service ) ; stickiness != nil {
loadBalancer . Stickiness = stickiness
}
return loadBalancer
}
2018-02-14 16:56:04 +08:00
func getStickiness ( service * corev1 . Service ) * types . Stickiness {
2018-01-26 13:29:42 +01:00
if getBoolValue ( service . Annotations , annotationKubernetesAffinity , false ) {
2017-12-21 15:40:07 +01:00
stickiness := & types . Stickiness { }
2018-01-26 13:29:42 +01:00
if cookieName := getStringValue ( service . Annotations , annotationKubernetesSessionCookieName , "" ) ; len ( cookieName ) > 0 {
2017-12-21 15:40:07 +01:00
stickiness . CookieName = cookieName
}
return stickiness
}
return nil
}
2018-02-14 16:56:04 +08:00
func getHeader ( i * extensionsv1beta1 . Ingress ) * types . Headers {
2018-01-02 18:03:50 +01:00
headers := & types . Headers {
2018-01-26 13:29:42 +01:00
CustomRequestHeaders : getMapValue ( i . Annotations , annotationKubernetesCustomRequestHeaders ) ,
CustomResponseHeaders : getMapValue ( i . Annotations , annotationKubernetesCustomResponseHeaders ) ,
AllowedHosts : getSliceStringValue ( i . Annotations , annotationKubernetesAllowedHosts ) ,
HostsProxyHeaders : getSliceStringValue ( i . Annotations , annotationKubernetesProxyHeaders ) ,
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 14:24:03 +01:00
CustomBrowserXSSValue : getStringValue ( i . Annotations , annotationKubernetesCustomBrowserXSSValue , "" ) ,
2018-01-26 13:29:42 +01: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 15:40:07 +01:00
}
2018-01-02 18:03:50 +01:00
if ! headers . HasSecureHeadersDefined ( ) && ! headers . HasCustomHeadersDefined ( ) {
return nil
}
return headers
2017-12-21 15:40:07 +01:00
}
2018-02-14 16:56:04 +08:00
func getMaxConn ( service * corev1 . Service ) * types . MaxConn {
2018-01-26 13:29:42 +01:00
amount := getInt64Value ( service . Annotations , annotationKubernetesMaxConnAmount , - 1 )
extractorFunc := getStringValue ( service . Annotations , annotationKubernetesMaxConnExtractorFunc , "" )
2017-12-21 22:44:06 +01:00
if amount >= 0 && len ( extractorFunc ) > 0 {
return & types . MaxConn {
ExtractorFunc : extractorFunc ,
Amount : amount ,
}
}
return nil
}
2018-01-26 13:29:42 +01:00
2018-02-14 16:56:04 +08:00
func getCircuitBreaker ( service * corev1 . Service ) * types . CircuitBreaker {
2018-01-26 13:29:42 +01:00
if expression := getStringValue ( service . Annotations , annotationKubernetesCircuitBreakerExpression , "" ) ; expression != "" {
return & types . CircuitBreaker {
Expression : expression ,
}
}
return nil
}
2018-02-14 16:56:04 +08:00
func getErrorPages ( i * extensionsv1beta1 . Ingress ) map [ string ] * types . ErrorPage {
2018-01-26 13:29:42 +01: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 16:56:04 +08:00
func getRateLimit ( i * extensionsv1beta1 . Ingress ) * types . RateLimit {
2018-01-26 13:29:42 +01: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
}