traefik/pkg/provider/traefik/internal.go
2024-03-12 12:08:03 +01:00

390 lines
11 KiB
Go

package traefik
import (
"context"
"fmt"
"math"
"net"
"regexp"
"time"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/static"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/tls"
)
const defaultInternalEntryPointName = "traefik"
var _ provider.Provider = (*Provider)(nil)
// Provider is a provider.Provider implementation that provides the internal routers.
type Provider struct {
staticCfg static.Configuration
}
// New creates a new instance of the internal provider.
func New(staticCfg static.Configuration) *Provider {
return &Provider{staticCfg: staticCfg}
}
// ThrottleDuration returns the throttle duration.
func (i Provider) ThrottleDuration() time.Duration {
return 0
}
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
func (i *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
ctx := log.With().Str(logs.ProviderName, "internal").Logger().WithContext(context.Background())
configurationChan <- dynamic.Message{
ProviderName: "internal",
Configuration: i.createConfiguration(ctx),
}
return nil
}
// Init the provider.
func (i *Provider) Init() error {
return nil
}
func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configuration {
cfg := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
ServersTransports: make(map[string]*dynamic.ServersTransport),
},
TCP: &dynamic.TCPConfiguration{
Routers: make(map[string]*dynamic.TCPRouter),
Services: make(map[string]*dynamic.TCPService),
Models: make(map[string]*dynamic.TCPModel),
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
},
TLS: &dynamic.TLSConfiguration{
Stores: make(map[string]tls.Store),
Options: make(map[string]tls.Options),
},
}
i.apiConfiguration(cfg)
i.pingConfiguration(cfg)
i.restConfiguration(cfg)
i.prometheusConfiguration(cfg)
i.entryPointModels(cfg)
i.redirection(ctx, cfg)
i.serverTransport(cfg)
i.serverTransportTCP(cfg)
i.acme(cfg)
cfg.HTTP.Services["noop"] = &dynamic.Service{}
return cfg
}
func (i *Provider) acme(cfg *dynamic.Configuration) {
var eps []string
uniq := map[string]struct{}{}
for _, resolver := range i.staticCfg.CertificatesResolvers {
if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" {
if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; !ok {
eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
}
}
}
if len(eps) > 0 {
rt := &dynamic.Router{
Rule: "PathPrefix(`/.well-known/acme-challenge/`)",
EntryPoints: eps,
Service: "acme-http@internal",
Priority: math.MaxInt,
}
cfg.HTTP.Routers["acme-http"] = rt
cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
}
}
func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) {
for name, ep := range i.staticCfg.EntryPoints {
if ep.HTTP.Redirections == nil {
continue
}
logger := log.Ctx(ctx).With().Str(logs.EntryPointName, name).Logger()
def := ep.HTTP.Redirections
if def.EntryPoint == nil || def.EntryPoint.To == "" {
logger.Error().Msg("Unable to create redirection: the entry point or the port is missing")
continue
}
port, err := i.getRedirectPort(name, def)
if err != nil {
logger.Error().Err(err).Send()
continue
}
rtName := provider.Normalize(name + "-to-" + def.EntryPoint.To)
mdName := "redirect-" + rtName
rt := &dynamic.Router{
Rule: "HostRegexp(`^.+$`)",
EntryPoints: []string{name},
Middlewares: []string{mdName},
Service: "noop@internal",
Priority: def.EntryPoint.Priority,
}
cfg.HTTP.Routers[rtName] = rt
rs := &dynamic.Middleware{
RedirectScheme: &dynamic.RedirectScheme{
Scheme: def.EntryPoint.Scheme,
Port: port,
Permanent: def.EntryPoint.Permanent,
},
}
cfg.HTTP.Middlewares[mdName] = rs
}
}
func (i *Provider) getRedirectPort(name string, def *static.Redirections) (string, error) {
exp := regexp.MustCompile(`^:(\d+)$`)
if exp.MatchString(def.EntryPoint.To) {
_, port, err := net.SplitHostPort(def.EntryPoint.To)
if err != nil {
return "", fmt.Errorf("invalid port value: %w", err)
}
return port, nil
}
return i.getEntryPointPort(name, def)
}
func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (string, error) {
dst, ok := i.staticCfg.EntryPoints[def.EntryPoint.To]
if !ok {
return "", fmt.Errorf("'to' entry point field references a non-existing entry point: %s", def.EntryPoint.To)
}
_, port, err := net.SplitHostPort(dst.GetAddress())
if err != nil {
return "", fmt.Errorf("invalid entry point %q address %q: %w",
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)
}
return port, nil
}
func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
defaultRuleSyntax := ""
if i.staticCfg.Core != nil && i.staticCfg.Core.DefaultRuleSyntax != "" {
defaultRuleSyntax = i.staticCfg.Core.DefaultRuleSyntax
}
for name, ep := range i.staticCfg.EntryPoints {
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" {
continue
}
m := &dynamic.Model{
Middlewares: ep.HTTP.Middlewares,
}
if ep.HTTP.TLS != nil {
m.TLS = &dynamic.RouterTLSConfig{
Options: ep.HTTP.TLS.Options,
CertResolver: ep.HTTP.TLS.CertResolver,
Domains: ep.HTTP.TLS.Domains,
}
}
m.DefaultRuleSyntax = defaultRuleSyntax
cfg.HTTP.Models[name] = m
if cfg.TCP == nil {
continue
}
mTCP := &dynamic.TCPModel{
DefaultRuleSyntax: defaultRuleSyntax,
}
cfg.TCP.Models[name] = mTCP
}
}
func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.API == nil {
return
}
if i.staticCfg.API.Insecure {
cfg.HTTP.Routers["api"] = &dynamic.Router{
EntryPoints: []string{defaultInternalEntryPointName},
Service: "api@internal",
Priority: math.MaxInt - 1,
Rule: "PathPrefix(`/api`)",
}
if i.staticCfg.API.Dashboard {
cfg.HTTP.Routers["dashboard"] = &dynamic.Router{
EntryPoints: []string{defaultInternalEntryPointName},
Service: "dashboard@internal",
Priority: math.MaxInt - 2,
Rule: "PathPrefix(`/`)",
Middlewares: []string{"dashboard_redirect@internal", "dashboard_stripprefix@internal"},
}
cfg.HTTP.Middlewares["dashboard_redirect"] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `^(http:\/\/(\[[\w:.]+\]|[\w\._-]+)(:\d+)?)\/$`,
Replacement: "${1}/dashboard/",
Permanent: true,
},
}
cfg.HTTP.Middlewares["dashboard_stripprefix"] = &dynamic.Middleware{
StripPrefix: &dynamic.StripPrefix{Prefixes: []string{"/dashboard/", "/dashboard"}},
}
}
if i.staticCfg.API.Debug {
cfg.HTTP.Routers["debug"] = &dynamic.Router{
EntryPoints: []string{defaultInternalEntryPointName},
Service: "api@internal",
Priority: math.MaxInt - 1,
Rule: "PathPrefix(`/debug`)",
}
}
}
cfg.HTTP.Services["api"] = &dynamic.Service{}
if i.staticCfg.API.Dashboard {
cfg.HTTP.Services["dashboard"] = &dynamic.Service{}
}
}
func (i *Provider) pingConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Ping == nil {
return
}
if !i.staticCfg.Ping.ManualRouting {
cfg.HTTP.Routers["ping"] = &dynamic.Router{
EntryPoints: []string{i.staticCfg.Ping.EntryPoint},
Service: "ping@internal",
Priority: math.MaxInt,
Rule: "PathPrefix(`/ping`)",
}
}
cfg.HTTP.Services["ping"] = &dynamic.Service{}
}
func (i *Provider) restConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Providers == nil || i.staticCfg.Providers.Rest == nil {
return
}
if i.staticCfg.Providers.Rest.Insecure {
cfg.HTTP.Routers["rest"] = &dynamic.Router{
EntryPoints: []string{defaultInternalEntryPointName},
Service: "rest@internal",
Priority: math.MaxInt,
Rule: "PathPrefix(`/api/providers`)",
}
}
cfg.HTTP.Services["rest"] = &dynamic.Service{}
}
func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Metrics == nil || i.staticCfg.Metrics.Prometheus == nil {
return
}
if !i.staticCfg.Metrics.Prometheus.ManualRouting {
cfg.HTTP.Routers["prometheus"] = &dynamic.Router{
EntryPoints: []string{i.staticCfg.Metrics.Prometheus.EntryPoint},
Service: "prometheus@internal",
Priority: math.MaxInt,
Rule: "PathPrefix(`/metrics`)",
}
}
cfg.HTTP.Services["prometheus"] = &dynamic.Service{}
}
func (i *Provider) serverTransport(cfg *dynamic.Configuration) {
if i.staticCfg.ServersTransport == nil {
return
}
st := &dynamic.ServersTransport{
InsecureSkipVerify: i.staticCfg.ServersTransport.InsecureSkipVerify,
RootCAs: i.staticCfg.ServersTransport.RootCAs,
MaxIdleConnsPerHost: i.staticCfg.ServersTransport.MaxIdleConnsPerHost,
}
if i.staticCfg.ServersTransport.Spiffe != nil {
st.Spiffe = &dynamic.Spiffe{
IDs: i.staticCfg.ServersTransport.Spiffe.IDs,
TrustDomain: i.staticCfg.ServersTransport.Spiffe.TrustDomain,
}
}
if i.staticCfg.ServersTransport.ForwardingTimeouts != nil {
st.ForwardingTimeouts = &dynamic.ForwardingTimeouts{
DialTimeout: i.staticCfg.ServersTransport.ForwardingTimeouts.DialTimeout,
ResponseHeaderTimeout: i.staticCfg.ServersTransport.ForwardingTimeouts.ResponseHeaderTimeout,
IdleConnTimeout: i.staticCfg.ServersTransport.ForwardingTimeouts.IdleConnTimeout,
}
}
cfg.HTTP.ServersTransports["default"] = st
}
func (i *Provider) serverTransportTCP(cfg *dynamic.Configuration) {
if i.staticCfg.TCPServersTransport == nil {
return
}
st := &dynamic.TCPServersTransport{
DialTimeout: i.staticCfg.TCPServersTransport.DialTimeout,
DialKeepAlive: i.staticCfg.TCPServersTransport.DialKeepAlive,
}
if i.staticCfg.TCPServersTransport.TLS != nil {
st.TLS = &dynamic.TLSClientConfig{
InsecureSkipVerify: i.staticCfg.TCPServersTransport.TLS.InsecureSkipVerify,
RootCAs: i.staticCfg.TCPServersTransport.TLS.RootCAs,
}
if i.staticCfg.TCPServersTransport.TLS.Spiffe != nil {
st.TLS.Spiffe = &dynamic.Spiffe{
IDs: i.staticCfg.ServersTransport.Spiffe.IDs,
TrustDomain: i.staticCfg.ServersTransport.Spiffe.TrustDomain,
}
}
}
cfg.TCP.ServersTransports["default"] = st
}