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"
|
|
|
|
"os"
|
2016-06-22 16:31:14 +00:00
|
|
|
"reflect"
|
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
|
|
|
|
2016-12-30 08:21:13 +00:00
|
|
|
"github.com/cenk/backoff"
|
|
|
|
"github.com/containous/traefik/job"
|
2016-11-11 22:50:20 +00:00
|
|
|
"github.com/containous/traefik/log"
|
2017-04-17 10:50:02 +00:00
|
|
|
"github.com/containous/traefik/provider"
|
2017-12-02 18:28:11 +00:00
|
|
|
"github.com/containous/traefik/provider/label"
|
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"
|
2016-11-11 22:50:20 +00:00
|
|
|
"github.com/containous/traefik/types"
|
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"
|
|
|
|
"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-02-01 18:04:04 +00:00
|
|
|
ruleTypePathPrefix = "PathPrefix"
|
|
|
|
ruleTypeReplacePath = "ReplacePath"
|
|
|
|
traefikDefaultRealm = "traefik"
|
|
|
|
traefikDefaultIngressClass = "traefik"
|
2017-02-06 23:04:30 +00:00
|
|
|
)
|
|
|
|
|
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"`
|
2017-04-17 10:50:02 +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)"`
|
2017-10-02 08:32:02 +00:00
|
|
|
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers" export:"true"`
|
2017-11-20 01:12:03 +00:00
|
|
|
EnablePassTLSCert bool `description:"Kubernetes enable Pass TLS Client Certs" export:"true"`
|
2017-10-02 08:32:02 +00:00
|
|
|
Namespaces Namespaces `description:"Kubernetes namespaces" export:"true"`
|
|
|
|
LabelSelector string `description:"Kubernetes api label selector to use" export:"true"`
|
2018-02-01 18:04:04 +00:00
|
|
|
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
|
2016-06-22 16:31:14 +00:00
|
|
|
lastConfiguration safe.Safe
|
2016-02-08 20:57:32 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 10:40:03 +00:00
|
|
|
func (p *Provider) newK8sClient() (Client, error) {
|
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
|
|
|
|
|
|
|
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)
|
2017-04-17 10:50:02 +00:00
|
|
|
return NewInClusterClient(p.Endpoint)
|
2017-03-07 12:09:11 +00:00
|
|
|
}
|
|
|
|
|
2018-01-15 15:04:05 +00:00
|
|
|
log.Infof("Creating cluster-external Provider client%s", withEndpoint)
|
2017-04-17 10:50:02 +00:00
|
|
|
return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
|
2016-04-19 17:23:08 +00:00
|
|
|
}
|
|
|
|
|
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.
|
2017-04-17 10:50:02 +00:00
|
|
|
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) 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-02-01 18:04:04 +00: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)
|
|
|
|
}
|
|
|
|
|
2017-04-17 10:50:02 +00:00
|
|
|
k8sClient, err := p.newK8sClient()
|
2016-02-08 20:57:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-04-17 10:50:02 +00:00
|
|
|
p.Constraints = append(p.Constraints, constraints...)
|
2016-02-08 20:57:32 +00:00
|
|
|
|
|
|
|
pool.Go(func(stop chan bool) {
|
|
|
|
operation := func() error {
|
|
|
|
for {
|
2016-12-03 20:20:39 +00:00
|
|
|
stopWatch := make(chan struct{}, 1)
|
2016-05-19 18:09:01 +00:00
|
|
|
defer close(stopWatch)
|
2017-04-17 10:50:02 +00:00
|
|
|
log.Debugf("Using label selector: '%s'", p.LabelSelector)
|
2017-10-10 14:26:03 +00:00
|
|
|
eventsChan, err := k8sClient.WatchAll(p.Namespaces, p.LabelSelector, stopWatch)
|
2016-04-25 14:56:06 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error watching kubernetes events: %v", err)
|
2016-05-19 18:09:01 +00:00
|
|
|
timer := time.NewTimer(1 * time.Second)
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
return err
|
|
|
|
case <-stop:
|
|
|
|
return nil
|
|
|
|
}
|
2016-04-25 14:56:06 +00:00
|
|
|
}
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-stop:
|
|
|
|
return nil
|
|
|
|
case event := <-eventsChan:
|
2017-10-10 14:26:03 +00:00
|
|
|
log.Debugf("Received Kubernetes event kind %T", event)
|
2017-04-17 10:50:02 +00:00
|
|
|
templateObjects, err := p.loadIngresses(k8sClient)
|
2016-04-25 14:56:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-04-17 10:50:02 +00:00
|
|
|
if reflect.DeepEqual(p.lastConfiguration.Get(), templateObjects) {
|
2017-10-10 14:26:03 +00:00
|
|
|
log.Debugf("Skipping Kubernetes event kind %T", event)
|
2016-06-22 16:31:14 +00:00
|
|
|
} else {
|
2017-04-17 10:50:02 +00:00
|
|
|
p.lastConfiguration.Set(templateObjects)
|
2016-06-22 16:31:14 +00:00
|
|
|
configurationChan <- types.ConfigMessage{
|
|
|
|
ProviderName: "kubernetes",
|
2017-04-17 10:50:02 +00:00
|
|
|
Configuration: p.loadConfig(*templateObjects),
|
2016-06-22 16:31:14 +00:00
|
|
|
}
|
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
|
|
|
|
2016-04-19 17:23:08 +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
|
|
|
|
2016-04-19 17:23:08 +00:00
|
|
|
for _, i := range ingresses {
|
2018-01-26 12:29:42 +00:00
|
|
|
annotationIngressClass := getAnnotationName(i.Annotations, annotationKubernetesIngressClass)
|
|
|
|
ingressClass := i.Annotations[annotationIngressClass]
|
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-01-24 10:57:06 +00:00
|
|
|
tlsSection, err := getTLS(i, k8sClient)
|
2018-01-07 23:36:03 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error configuring TLS for ingress %s/%s: %v", i.Namespace, i.Name, err)
|
|
|
|
continue
|
|
|
|
}
|
2018-01-24 10:57:06 +00:00
|
|
|
templateObjects.TLS = append(templateObjects.TLS, tlsSection...)
|
2018-01-07 23:36:03 +00:00
|
|
|
|
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-01-25 19:51:06 +00:00
|
|
|
baseName := r.Host + pa.Path
|
|
|
|
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-01-25 19:51:06 +00:00
|
|
|
if _, exists := templateObjects.Frontends[baseName]; !exists {
|
2017-04-23 14:17:20 +00:00
|
|
|
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
|
|
|
|
if err != nil {
|
2018-01-25 19:51:06 +00:00
|
|
|
log.Errorf("Failed to retrieve basic 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)
|
|
|
|
passTLSCert := getBoolValue(i.Annotations, annotationKubernetesPassTLSCert, p.EnablePassTLSCert)
|
|
|
|
priority := getIntValue(i.Annotations, annotationKubernetesPriority, 0)
|
|
|
|
entryPoints := getSliceStringValue(i.Annotations, annotationKubernetesFrontendEntryPoints)
|
|
|
|
whitelistSourceRange := getSliceStringValue(i.Annotations, annotationKubernetesWhitelistSourceRange)
|
2017-12-21 16:47:50 +00:00
|
|
|
|
2018-01-25 19:51:06 +00:00
|
|
|
templateObjects.Frontends[baseName] = &types.Frontend{
|
|
|
|
Backend: baseName,
|
2017-11-20 01:12:03 +00:00
|
|
|
PassHostHeader: passHostHeader,
|
|
|
|
PassTLSCert: passTLSCert,
|
2017-04-30 09:22:07 +00:00
|
|
|
Routes: make(map[string]types.Route),
|
2017-07-29 16:35:23 +00:00
|
|
|
Priority: priority,
|
2017-04-30 09:22:07 +00:00
|
|
|
BasicAuth: basicAuthCreds,
|
|
|
|
WhitelistSourceRange: whitelistSourceRange,
|
2017-12-15 10:48:03 +00:00
|
|
|
Redirect: getFrontendRedirect(i),
|
2017-11-20 01:12:03 +00:00
|
|
|
EntryPoints: entryPoints,
|
2017-12-21 14:40:07 +00:00
|
|
|
Headers: getHeader(i),
|
2018-01-26 12:29:42 +00:00
|
|
|
Errors: getErrorPages(i),
|
2017-12-21 21:07:37 +00:00
|
|
|
RateLimit: getRateLimit(i),
|
2016-04-19 17:23:08 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-20 13:16:05 +00:00
|
|
|
|
2017-12-21 14:40:07 +00:00
|
|
|
if len(r.Host) > 0 {
|
2018-01-25 19:51:06 +00:00
|
|
|
if _, exists := templateObjects.Frontends[baseName].Routes[r.Host]; !exists {
|
|
|
|
templateObjects.Frontends[baseName].Routes[r.Host] = types.Route{
|
2017-12-21 14:40:07 +00:00
|
|
|
Rule: getRuleForHost(r.Host),
|
2016-05-25 12:16:19 +00:00
|
|
|
}
|
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)
|
|
|
|
delete(templateObjects.Frontends, baseName)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if rule != "" {
|
2018-01-25 19:51:06 +00:00
|
|
|
templateObjects.Frontends[baseName].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-01-25 19:51:06 +00:00
|
|
|
service, exists, err := k8sClient.GetService(i.Namespace, pa.Backend.ServiceName)
|
2017-03-17 15:34:34 +00:00
|
|
|
if err != nil {
|
2018-01-25 19:51:06 +00:00
|
|
|
log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.Namespace, pa.Backend.ServiceName, err)
|
2017-03-17 15:34:34 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !exists {
|
2018-01-25 19:51:06 +00:00
|
|
|
log.Errorf("Service not found for %s/%s", i.Namespace, pa.Backend.ServiceName)
|
|
|
|
delete(templateObjects.Frontends, baseName)
|
2016-05-25 23:53:51 +00:00
|
|
|
continue
|
2016-04-25 14:56:06 +00:00
|
|
|
}
|
2016-11-11 22:50:20 +00:00
|
|
|
|
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-01-31 14:32:04 +00:00
|
|
|
|
2017-12-02 18:28:11 +00:00
|
|
|
protocol := label.DefaultProtocol
|
2016-05-25 23:53:51 +00:00
|
|
|
for _, port := range service.Spec.Ports {
|
|
|
|
if equalPorts(port, pa.Backend.ServicePort) {
|
|
|
|
if port.Port == 443 {
|
|
|
|
protocol = "https"
|
|
|
|
}
|
2017-07-07 19:27:54 +00:00
|
|
|
|
2017-02-10 01:25:38 +00:00
|
|
|
if service.Spec.Type == "ExternalName" {
|
|
|
|
url := protocol + "://" + service.Spec.ExternalName
|
|
|
|
name := url
|
|
|
|
|
2018-01-25 19:51:06 +00:00
|
|
|
templateObjects.Backends[baseName].Servers[name] = types.Server{
|
2017-02-10 01:25:38 +00:00
|
|
|
URL: url,
|
2016-05-25 23:53:51 +00:00
|
|
|
Weight: 1,
|
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 {
|
|
|
|
for _, address := range subset.Addresses {
|
|
|
|
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
|
|
|
|
name := url
|
|
|
|
if address.TargetRef != nil && address.TargetRef.Name != "" {
|
|
|
|
name = address.TargetRef.Name
|
|
|
|
}
|
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,
|
|
|
|
Weight: 1,
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &templateObjects, nil
|
|
|
|
}
|
|
|
|
|
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-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)
|
2017-11-27 10:22:03 +00:00
|
|
|
rules := []string{ruleType + ":" + pa.Path}
|
2017-07-07 19:27:54 +00:00
|
|
|
|
2018-02-19 14:36:03 +00:00
|
|
|
var pathReplaceAnnotation string
|
|
|
|
if ruleType == ruleTypeReplacePath {
|
|
|
|
pathReplaceAnnotation = annotationKubernetesRuleType
|
|
|
|
}
|
|
|
|
|
2018-01-26 12:29:42 +00:00
|
|
|
if rewriteTarget := getStringValue(i.Annotations, annotationKubernetesRewriteTarget, ""); rewriteTarget != "" {
|
2018-02-19 14:36:03 +00:00
|
|
|
if pathReplaceAnnotation != "" {
|
|
|
|
return "", fmt.Errorf("rewrite-target must not be used together with annotation %q", pathReplaceAnnotation)
|
|
|
|
}
|
2017-11-27 10:22:03 +00:00
|
|
|
rules = append(rules, ruleTypeReplacePath+":"+rewriteTarget)
|
2018-02-19 14:36:03 +00:00
|
|
|
pathReplaceAnnotation = annotationKubernetesRewriteTarget
|
2017-07-07 19:27:54 +00:00
|
|
|
}
|
|
|
|
|
2018-02-23 17:36:03 +00:00
|
|
|
if rootPath := getStringValue(i.Annotations, annotationKubernetesAppRoot, ""); rootPath != "" && pa.Path == "/" {
|
2018-02-19 14:36:03 +00: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 19:27:54 +00:00
|
|
|
}
|
|
|
|
|
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-02-14 08:56:04 +00:00
|
|
|
func handleBasicAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) ([]string, error) {
|
2018-01-26 12:29:42 +00:00
|
|
|
annotationAuthType := getAnnotationName(i.Annotations, annotationKubernetesAuthType)
|
|
|
|
authType, exists := i.Annotations[annotationAuthType]
|
2017-04-23 14:17:20 +00:00
|
|
|
if !exists {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2017-12-02 18:28:11 +00:00
|
|
|
|
2017-04-23 14:17:20 +00:00
|
|
|
if strings.ToLower(authType) != "basic" {
|
2017-06-27 23:32:19 +00:00
|
|
|
return nil, fmt.Errorf("unsupported auth-type on annotation ingress.kubernetes.io/auth-type: %q", authType)
|
2017-04-23 14:17:20 +00:00
|
|
|
}
|
2017-12-02 18:28:11 +00:00
|
|
|
|
2018-01-26 12:29:42 +00:00
|
|
|
authSecret := getStringValue(i.Annotations, annotationKubernetesAuthSecret, "")
|
2017-04-23 14:17:20 +00:00
|
|
|
if authSecret == "" {
|
2017-06-27 23:32:19 +00:00
|
|
|
return nil, errors.New("auth-secret annotation ingress.kubernetes.io/auth-secret must be set")
|
2017-04-23 14:17:20 +00:00
|
|
|
}
|
2017-12-02 18:28:11 +00:00
|
|
|
|
2017-04-23 14:17:20 +00:00
|
|
|
basicAuthCreds, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
|
|
|
|
if err != nil {
|
2017-06-27 23:32:19 +00:00
|
|
|
return nil, fmt.Errorf("failed to load auth credentials: %s", err)
|
2017-04-23 14:17:20 +00:00
|
|
|
}
|
2017-12-02 18:28:11 +00:00
|
|
|
|
2017-04-23 14:17:20 +00: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-27 23:32:19 +00:00
|
|
|
return nil, fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
2017-04-23 14:17:20 +00:00
|
|
|
case len(secret.Data) != 1:
|
2017-06-27 23:32:19 +00: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 14:17:20 +00: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-27 23:32:19 +00:00
|
|
|
if len(creds) == 0 {
|
|
|
|
return nil, fmt.Errorf("secret %q/%q does not contain any credentials", namespace, secretName)
|
|
|
|
}
|
|
|
|
|
2017-04-23 14:17:20 +00:00
|
|
|
return creds, nil
|
|
|
|
}
|
|
|
|
|
2018-02-14 08:56:04 +00:00
|
|
|
func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Configuration, error) {
|
2018-01-07 23:36:03 +00: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 12:29:42 +00: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-07 23:36:03 +00:00
|
|
|
}
|
|
|
|
|
2018-01-26 12:29:42 +00:00
|
|
|
entryPoints := getSliceStringValue(ingress.Annotations, annotationKubernetesFrontendEntryPoints)
|
2018-01-07 23:36:03 +00: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 08:56:04 +00:00
|
|
|
func endpointPortNumber(servicePort corev1.ServicePort, endpointPorts []corev1.EndpointPort) int {
|
2016-05-20 16:34:57 +00:00
|
|
|
if len(endpointPorts) > 0 {
|
|
|
|
//name is optional if there is only one port
|
|
|
|
port := endpointPorts[0]
|
|
|
|
for _, endpointPort := range endpointPorts {
|
|
|
|
if servicePort.Name == endpointPort.Name {
|
|
|
|
port = endpointPort
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return int(port.Port)
|
|
|
|
}
|
2016-11-11 22:50:20 +00:00
|
|
|
return int(servicePort.Port)
|
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-02-14 08:56:04 +00:00
|
|
|
func getFrontendRedirect(i *extensionsv1beta1.Ingress) *types.Redirect {
|
2018-01-31 18:10:04 +00:00
|
|
|
permanent := getBoolValue(i.Annotations, annotationKubernetesRedirectPermanent, false)
|
|
|
|
|
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-01-26 12:29:42 +00:00
|
|
|
redirectRegex := getStringValue(i.Annotations, annotationKubernetesRedirectRegex, "")
|
|
|
|
redirectReplacement := getStringValue(i.Annotations, annotationKubernetesRedirectReplacement, "")
|
|
|
|
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-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 sticky := service.Annotations[label.TraefikBackendLoadBalancerSticky]; len(sticky) > 0 {
|
2018-01-26 12:29:42 +00:00
|
|
|
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, annotationKubernetesAffinity)
|
2017-12-21 14:40:07 +00:00
|
|
|
loadBalancer.Sticky = strings.EqualFold(strings.TrimSpace(sticky), "true")
|
|
|
|
}
|
|
|
|
|
|
|
|
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),
|
|
|
|
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),
|
|
|
|
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
|
|
|
|
}
|