feat: use dedicated entrypoint for the tunnels

Co-authored-by: Fernandez Ludovic <[ldez@users.noreply.github.com](mailto:ldez@users.noreply.github.com)>
This commit is contained in:
Baptiste Mayelle 2022-05-18 17:22:08 +02:00 committed by GitHub
parent 619621f239
commit 86cc6df374
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 386 additions and 384 deletions

View file

@ -34,6 +34,7 @@ import (
"github.com/traefik/traefik/v2/pkg/pilot"
"github.com/traefik/traefik/v2/pkg/provider/acme"
"github.com/traefik/traefik/v2/pkg/provider/aggregator"
"github.com/traefik/traefik/v2/pkg/provider/hub"
"github.com/traefik/traefik/v2/pkg/provider/traefik"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server"
@ -215,8 +216,6 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
}
if staticConfiguration.Pilot != nil {
log.WithoutContext().Warn("Traefik Pilot is deprecated and will be removed soon. Please check our Blog for migration instructions later this year")
version.PilotEnabled = staticConfiguration.Pilot.Dashboard
}
@ -363,7 +362,7 @@ func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string
var defaultEntryPoints []string
for name, cfg := range staticConfiguration.EntryPoints {
// Traefik Hub entryPoint should not be part of the set of default entryPoints.
if staticConfiguration.Hub != nil && staticConfiguration.Hub.EntryPoint == name {
if hub.APIEntrypoint == name || hub.TunnelEntrypoint == name {
continue
}

View file

@ -457,10 +457,3 @@ the value for the method label becomes `EXTENSION_METHOD`, instead of the reques
### Tracing
In `v2.6.1`, the Datadog tags added to a span changed from `service.name` to `traefik.service.name` and from `router.name` to `traefik.router.name`.
## v2.7
### Traefik Pilot
In `v2.7`, the `pilot.token` and `pilot.dashboard` options are deprecated.
Please check the [feature deprecation page](../deprecation/features.md) and our Blog for migration instructions later this year.

View file

@ -5,11 +5,6 @@ description: "Learn how to connect Traefik Proxy with Pilot, a SaaS platform tha
# Plugins and Traefik Pilot
!!! warning "Traefik Pilot Deprecation"
Traefik Pilot is deprecated and will be removed soon.
Please check our Blog for migration instructions later this year.
Traefik Pilot is a software-as-a-service (SaaS) platform that connects to Traefik to extend its capabilities.
It offers a number of features to enhance observability and control of Traefik through a global control plane and dashboard, including:

View file

@ -222,9 +222,6 @@ The maximal depth of DNS recursive resolving (Default: ```5```)
`--hub`:
Traefik Hub configuration. (Default: ```false```)
`--hub.entrypoint`:
Entrypoint that exposes data for Traefik Hub. It should be a dedicated one, and not used by any router. (Default: ```traefik-hub```)
`--hub.tls.ca`:
The certificate authority authenticates the Traefik Hub Agent certificate.

View file

@ -222,9 +222,6 @@ The maximal depth of DNS recursive resolving (Default: ```5```)
`TRAEFIK_HUB`:
Traefik Hub configuration. (Default: ```false```)
`TRAEFIK_HUB_ENTRYPOINT`:
Entrypoint that exposes data for Traefik Hub. It should be a dedicated one, and not used by any router. (Default: ```traefik-hub```)
`TRAEFIK_HUB_TLS_CA`:
The certificate authority authenticates the Traefik Hub Agent certificate.

View file

@ -426,7 +426,6 @@
dashboard = true
[hub]
entrypoint = "foobar"
[hub.tls]
insecure = true
ca = "foobar"

View file

@ -447,7 +447,6 @@ pilot:
token: foobar
dashboard: true
hub:
entrypoint: foobar
tls:
insecure: true
ca: foobar

View file

@ -1,295 +0,0 @@
# Traefik Hub (Experimental)
## Overview
Once the Traefik Hub Experimental feature is enabled in Traefik,
Traefik and its local agent communicate together.
This agent can:
* get the Traefik metrics to display them in the Traefik Hub UI
* secure the Traefik routers
* provide ACME certificates to Traefik
* transfer requests from the SaaS Platform to Traefik (and then avoid the users to expose directly their infrastructure on the internet)
!!! warning "Traefik Hub EntryPoint"
When the Traefik Hub feature is enabled, Traefik exposes some services meant for the Traefik Hub Agent on a dedicated entryPoint (on port `9900` by default).
Given their sensitive nature, those services should not be publicly exposed.
Also this dedicated entryPoint, regardless of how it is created (default, or user-defined), should not be used by anything other than the Hub Agent.
!!! important "Learn More About Traefik Hub"
This section is intended only as a brief overview for Traefik users who are not familiar with Traefik Hub.
To explore all that Traefik Hub has to offer, please consult the [Traefik Hub Documentation](https://doc.traefik.io/traefik-hub).
!!! Note "Prerequisites"
* Traefik Hub is compatible with Traefik Proxy 2.7 or later.
* The Traefik Hub Agent must be installed to connect to the Traefik Hub platform.
* Activate this feature in the experimental section of the static configuration.
!!! example "Minimal Static Configuration to Activate Traefik Hub"
```yaml tab="File (YAML)"
experimental:
hub: true
hub:
tls:
insecure: true
metrics:
prometheus: {}
```
```toml tab="File (TOML)"
[experimental]
hub = true
[hub]
[hub.tls]
insecure = true
[metrics]
[metrics.prometheus]
```
```bash tab="CLI"
--experimental.hub
--hub.tls.insecure=true
--metrics.prometheus=true
```
## Configuration
### `entryPoint`
_Optional, Default="traefik-hub"_
Defines the entryPoint that exposes data for Traefik Hub Agent.
!!! info
* If no entryPoint is defined, a default `traefik-hub` entryPoint is created (on port `9900`).
* If defined, the value must match an existing entryPoint name.
* This dedicated Traefik Hub entryPoint should not be used by anything other than Traefik Hub.
```yaml tab="File (YAML)"
entryPoints:
hub-ep: ":8000"
hub:
entryPoint: "hub-ep"
```
```toml tab="File (TOML)"
[entryPoints.hub-ep]
address = ":8000"
[hub]
entryPoint = "hub-ep"
```
```bash tab="CLI"
--entrypoints.hub-ep.address=:8000
--hub.entrypoint=hub-ep
```
### `tls`
_Required, Default=None_
This section allows configuring mutual TLS connection between Traefik Proxy and the Traefik Hub Agent.
The key and the certificate are the credentials for Traefik Proxy as a TLS client.
The certificate authority authenticates the Traefik Hub Agent certificate.
!!! note "Certificate Domain"
The certificate must be valid for the `proxy.traefik` domain.
!!! note "Certificates Definition"
Certificates can be defined either by their content or their path.
!!! note "Insecure Mode"
The `insecure` option is mutually exclusive with any other option.
```yaml tab="File (YAML)"
hub:
tls:
ca: /path/to/ca.pem
cert: /path/to/cert.pem
key: /path/to/key.pem
```
```toml tab="File (TOML)"
[hub.tls]
ca= "/path/to/ca.pem"
cert= "/path/to/cert.pem"
key= "/path/to/key.pem"
```
```bash tab="CLI"
--hub.tls.ca=/path/to/ca.pem
--hub.tls.cert=/path/to/cert.pem
--hub.tls.key=/path/to/key.pem
```
### `tls.ca`
The certificate authority authenticates the Traefik Hub Agent certificate.
```yaml tab="File (YAML)"
hub:
tls:
ca: |-
-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
-----END CERTIFICATE-----
```
```toml tab="File (TOML)"
[hub.tls]
ca = """-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
-----END CERTIFICATE-----"""
```
```bash tab="CLI"
--hub.tls.ca=-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
-----END CERTIFICATE-----
```
### `tls.cert`
The TLS certificate for Traefik Proxy as a TLS client.
!!! note "Certificate Domain"
The certificate must be valid for the `proxy.traefik` domain.
```yaml tab="File (YAML)"
hub:
tls:
cert: |-
-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
-----END CERTIFICATE-----
```
```toml tab="File (TOML)"
[hub.tls]
cert = """-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
-----END CERTIFICATE-----"""
```
```bash tab="CLI"
--hub.tls.cert=-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
-----END CERTIFICATE-----
```
### `tls.key`
The TLS key for Traefik Proxy as a TLS client.
```yaml tab="File (YAML)"
hub:
tls:
key: |-
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
-----END PRIVATE KEY-----
```
```toml tab="File (TOML)"
[hub.tls]
key = """-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
-----END PRIVATE KEY-----"""
```
```bash tab="CLI"
--hub.tls.key=-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
-----END PRIVATE KEY-----
```
### `tls.insecure`
_Optional, Default=false_
Enables an insecure TLS connection that uses default credentials,
and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent.
The `insecure` option is mutually exclusive with any other option.
!!! warning "Security Consideration"
Do not use this setup in production.
This option implies sensitive data can be exposed to potential malicious third-party programs.
```yaml tab="File (YAML)"
hub:
tls:
insecure: true
```
```toml tab="File (TOML)"
[hub.tls]
insecure = true
```
```bash tab="CLI"
--hub.tls.insecure=true
```

View file

@ -136,7 +136,6 @@ nav:
- 'InFlightConn': 'middlewares/tcp/inflightconn.md'
- 'IpWhitelist': 'middlewares/tcp/ipwhitelist.md'
- 'Plugins & Traefik Pilot': 'plugins/index.md'
- 'Traefik Hub': 'traefik-hub/index.md'
- 'Operations':
- 'CLI': 'operations/cli.md'
- 'Dashboard' : 'operations/dashboard.md'

53
pkg/config/static/hub.go Normal file
View file

@ -0,0 +1,53 @@
package static
import (
"errors"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/provider/hub"
)
func (c *Configuration) initHubProvider() error {
// Hub provider is an experimental feature. It requires the experimental flag to be enabled before continuing.
if c.Experimental == nil || !c.Experimental.Hub {
return errors.New("the experimental flag for Hub is not set")
}
if _, ok := c.EntryPoints[hub.TunnelEntrypoint]; !ok {
var ep EntryPoint
ep.SetDefaults()
ep.Address = ":9901"
c.EntryPoints[hub.TunnelEntrypoint] = &ep
log.WithoutContext().Infof("The entryPoint %q is created on port 9901 to allow exposition of services.", hub.TunnelEntrypoint)
}
if c.Hub.TLS == nil {
return nil
}
if c.Hub.TLS.Insecure && (c.Hub.TLS.CA != "" || c.Hub.TLS.Cert != "" || c.Hub.TLS.Key != "") {
return errors.New("mTLS configuration for Hub and insecure TLS for Hub are mutually exclusive")
}
if !c.Hub.TLS.Insecure && (c.Hub.TLS.CA == "" || c.Hub.TLS.Cert == "" || c.Hub.TLS.Key == "") {
return errors.New("incomplete mTLS configuration for Hub")
}
if c.Hub.TLS.Insecure {
log.WithoutContext().Warn("Hub is in `insecure` mode. Do not run in production with this setup.")
}
if _, ok := c.EntryPoints[hub.APIEntrypoint]; !ok {
var ep EntryPoint
ep.SetDefaults()
ep.Address = ":9900"
c.EntryPoints[hub.APIEntrypoint] = &ep
log.WithoutContext().Infof("The entryPoint %q is created on port 9900 to allow Traefik to communicate with the Hub Agent for Traefik.", hub.APIEntrypoint)
}
c.EntryPoints[hub.APIEntrypoint].HTTP.TLS = &TLSConfig{
Options: "traefik-hub",
}
return nil
}

View file

@ -1,7 +1,6 @@
package static
// Pilot Configuration related to Traefik Pilot.
// Deprecated.
type Pilot struct {
Token string `description:"Traefik Pilot token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
Dashboard bool `description:"Enable Traefik Pilot in the dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty"`

View file

@ -1,7 +1,6 @@
package static
import (
"errors"
"fmt"
stdlog "log"
"strings"
@ -78,7 +77,6 @@ type Configuration struct {
CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"`
// Deprecated.
Pilot *Pilot `description:"Traefik Pilot configuration." json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty" export:"true"`
Hub *hub.Provider `description:"Traefik Hub configuration." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
@ -201,7 +199,7 @@ type Providers struct {
// It also takes care of maintaining backwards compatibility.
func (c *Configuration) SetEffectiveConfiguration() {
// Creates the default entry point if needed
if len(c.EntryPoints) == 0 || (c.Hub != nil && len(c.EntryPoints) == 1 && c.EntryPoints[c.Hub.EntryPoint] != nil) {
if !c.hasUserDefinedEntrypoint() {
ep := &EntryPoint{Address: ":80"}
ep.SetDefaults()
// TODO: double check this tomorrow
@ -287,6 +285,21 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.initACMEProvider()
}
func (c *Configuration) hasUserDefinedEntrypoint() bool {
if len(c.EntryPoints) == 0 {
return false
}
switch len(c.EntryPoints) {
case 1:
return c.EntryPoints[hub.TunnelEntrypoint] == nil
case 2:
return c.EntryPoints[hub.TunnelEntrypoint] == nil || c.EntryPoints[hub.APIEntrypoint] == nil
default:
return true
}
}
func (c *Configuration) initACMEProvider() {
for _, resolver := range c.CertificatesResolvers {
if resolver.ACME != nil {
@ -297,46 +310,6 @@ func (c *Configuration) initACMEProvider() {
legolog.Logger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "legolog: ", 0)
}
func (c *Configuration) initHubProvider() error {
// Hub provider is an experimental feature. Require the experimental flag to be enabled before continuing.
if c.Experimental == nil || !c.Experimental.Hub {
return errors.New("experimental flag for Hub not set")
}
if c.Hub.TLS == nil {
return errors.New("no TLS configuration defined for Hub")
}
if c.Hub.TLS.Insecure && (c.Hub.TLS.CA != "" || c.Hub.TLS.Cert != "" || c.Hub.TLS.Key != "") {
return errors.New("mTLS configuration for Hub and insecure TLS for Hub are mutually exclusive")
}
if !c.Hub.TLS.Insecure && (c.Hub.TLS.CA == "" || c.Hub.TLS.Cert == "" || c.Hub.TLS.Key == "") {
return errors.New("incomplete mTLS configuration for Hub")
}
if c.Hub.TLS.Insecure {
log.WithoutContext().Warn("Hub is in `insecure` mode. Do not run in production with this setup.")
}
// Creates the internal Hub entry point if needed.
if c.Hub.EntryPoint == hub.DefaultEntryPointName {
if _, ok := c.EntryPoints[hub.DefaultEntryPointName]; !ok {
var ep EntryPoint
ep.SetDefaults()
ep.Address = ":9900"
c.EntryPoints[hub.DefaultEntryPointName] = &ep
log.WithoutContext().Infof("The entryPoint %q is created on port 9900 to allow Traefik to communicate with the Hub Agent for Traefik.", hub.DefaultEntryPointName)
}
}
c.EntryPoints[c.Hub.EntryPoint].HTTP.TLS = &TLSConfig{
Options: "traefik-hub",
}
return nil
}
// ValidateConfiguration validate that configuration is coherent.
func (c *Configuration) ValidateConfiguration() error {
var acmeEmail string

View file

@ -0,0 +1,88 @@
package static
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/traefik/traefik/v2/pkg/provider/hub"
)
func TestHasEntrypoint(t *testing.T) {
tests := []struct {
desc string
entryPoints map[string]*EntryPoint
assert assert.BoolAssertionFunc
}{
{
desc: "no user defined entryPoints",
assert: assert.False,
},
{
desc: "user defined entryPoints",
entryPoints: map[string]*EntryPoint{
"foo": {},
},
assert: assert.True,
},
{
desc: "user defined entryPoints + hub entryPoint (tunnel)",
entryPoints: map[string]*EntryPoint{
"foo": {},
hub.TunnelEntrypoint: {},
},
assert: assert.True,
},
{
desc: "hub entryPoint (tunnel)",
entryPoints: map[string]*EntryPoint{
hub.TunnelEntrypoint: {},
},
assert: assert.False,
},
{
desc: "user defined entryPoints + hub entryPoint (api)",
entryPoints: map[string]*EntryPoint{
"foo": {},
hub.APIEntrypoint: {},
},
assert: assert.True,
},
{
desc: "hub entryPoint (api)",
entryPoints: map[string]*EntryPoint{
hub.APIEntrypoint: {},
},
assert: assert.True,
},
{
desc: "user defined entryPoints + hub entryPoints (tunnel, api)",
entryPoints: map[string]*EntryPoint{
"foo": {},
hub.TunnelEntrypoint: {},
hub.APIEntrypoint: {},
},
assert: assert.True,
},
{
desc: "hub entryPoints (tunnel, api)",
entryPoints: map[string]*EntryPoint{
hub.TunnelEntrypoint: {},
hub.APIEntrypoint: {},
},
assert: assert.False,
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
cfg := &Configuration{
EntryPoints: test.entryPoints,
}
test.assert(t, cfg.hasUserDefinedEntrypoint())
})
}
}

View file

@ -17,13 +17,15 @@ import (
var _ provider.Provider = (*Provider)(nil)
// DefaultEntryPointName is the name of the default internal entry point.
const DefaultEntryPointName = "traefik-hub"
// Entrypoints created for Hub.
const (
APIEntrypoint = "traefikhub-api"
TunnelEntrypoint = "traefikhub-tunl"
)
// Provider holds configurations of the provider.
type Provider struct {
EntryPoint string `description:"Entrypoint that exposes data for Traefik Hub. It should be a dedicated one, and not used by any router." json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
TLS *TLS `description:"TLS configuration for mTLS communication between Traefik and Hub Agent." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
TLS *TLS `description:"TLS configuration for mTLS communication between Traefik and Hub Agent." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
server *http.Server
}
@ -36,11 +38,6 @@ type TLS struct {
Key ttls.FileOrContent `description:"The TLS key for Traefik Proxy as a TLS client." json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.EntryPoint = DefaultEntryPointName
}
// Init the provider.
func (p *Provider) Init() error {
return nil
@ -59,7 +56,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Poo
return fmt.Errorf("creating Hub Agent HTTP client: %w", err)
}
p.server = &http.Server{Handler: newHandler(p.EntryPoint, port, configurationChan, p.TLS, client)}
p.server = &http.Server{Handler: newHandler(APIEntrypoint, port, configurationChan, p.TLS, client)}
// TODO: this is going to be leaky (because no context to make it terminate)
// if/when Provide lifecycle differs with Traefik lifecycle.
@ -70,7 +67,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Poo
}
}()
exposeAPIAndMetrics(configurationChan, p.EntryPoint, port, p.TLS)
exposeAPIAndMetrics(configurationChan, APIEntrypoint, port, p.TLS)
return nil
}

View file

@ -2,6 +2,8 @@ FROM node:14.16
# Current Active LTS release according to (https://nodejs.org/en/about/releases/)
ENV WEBUI_DIR /src/webui
ARG ARG_PLATFORM_URL=https://pilot.traefik.io
ENV PLATFORM_URL=${ARG_PLATFORM_URL}
RUN mkdir -p $WEBUI_DIR
COPY package.json $WEBUI_DIR/

View file

@ -118,11 +118,13 @@ module.exports = function (ctx) {
env: process.env.APP_ENV === 'development'
? { // staging:
APP_ENV: JSON.stringify(process.env.APP_ENV),
APP_API: JSON.stringify(process.env.APP_API || '/api')
APP_API: JSON.stringify(process.env.APP_API || '/api'),
PLATFORM_URL: JSON.stringify(process.env.PLATFORM_URL || 'https://pilot.traefik.io')
}
: { // production:
APP_ENV: JSON.stringify(process.env.APP_ENV),
APP_API: JSON.stringify(process.env.APP_API || '/api')
APP_API: JSON.stringify(process.env.APP_API || '/api'),
PLATFORM_URL: JSON.stringify(process.env.PLATFORM_URL || 'https://pilot.traefik.io')
},
uglifyOptions: {
compress: {

View file

@ -1,17 +1,26 @@
<template>
<div id="q-app">
<router-view />
<platform-panel
v-if="pilotEnabled" />
</div>
</template>
<script>
import { APP } from './_helpers/APP'
import PlatformPanel from './components/platform/PlatformPanel'
import { mapGetters } from 'vuex'
export default {
name: 'App',
components: {
PlatformPanel
},
computed: {
...mapGetters('core', { coreVersion: 'version' })
...mapGetters('core', { coreVersion: 'version' }),
pilotEnabled () {
return this.coreVersion.pilotEnabled
}
},
beforeCreate () {
// Set vue instance

View file

@ -1,7 +1,8 @@
const APP = {
config: {
env: process.env.APP_ENV,
apiUrl: process.env.APP_API
apiUrl: process.env.APP_API,
platformUrl: process.env.PLATFORM_URL
}
}

View file

@ -15,7 +15,6 @@
</q-tabs>
<div class="right-menu">
<q-tabs>
<q-btn type="a" href="https://hub.traefik.io/" target="_blank" flat no-caps label="Go to Hub Dashboard →" class="btn-menu btn-hub" />
<q-btn @click="$q.dark.toggle()" stretch flat no-caps icon="invert_colors" :label="`${$q.dark.isActive ? 'Light' : 'Dark'} theme`" class="btn-menu" />
<q-btn stretch flat icon="eva-question-mark-circle-outline">
<q-menu anchor="bottom left" auto-close>
@ -29,6 +28,8 @@
</q-menu>
</q-btn>
</q-tabs>
<platform-auth-state
v-if="pilotEnabled" />
</div>
</q-toolbar>
</div>
@ -44,10 +45,12 @@
<script>
import config from '../../../package'
import PlatformAuthState from '../platform/PlatformAuthState'
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'NavBar',
components: { PlatformAuthState },
computed: {
...mapGetters('core', { coreVersion: 'version' }),
version () {
@ -56,6 +59,9 @@ export default {
? this.coreVersion.Version
: this.coreVersion.Version.substring(0, 7)
},
pilotEnabled () {
return this.coreVersion.pilotEnabled
},
parsedVersion () {
if (!this.version) {
return 'master'
@ -138,11 +144,6 @@ export default {
font-weight: 600;
}
.btn-hub {
color: #0e204c;
background: #deea48;
}
.q-item {
padding: 0;
}

View file

@ -0,0 +1,82 @@
<template>
<div class="iframe-wrapper" v-if="isOnline">
<iframe
id="platform-auth-state"
:src="iFrameUrl"
v-if="renderIrame"
v-resize="resizeOpts"
height="64px"
frameBorder="0"
/>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import qs from 'query-string'
import '../../_directives/resize'
export default {
name: 'PlatformPanel',
data () {
return {
renderIrame: true,
resizeOpts: {
log: false,
onMessage: ({ iframe, message }) => {
if (typeof message === 'string') {
// 1st condition for backward compatibility
if (message === 'open:profile') {
this.openPlatform('/')
} else if (message.includes('open:')) {
this.openPlatform(message.split('open:')[1])
} else if (message === 'logout') {
this.closePlatform()
}
}
}
}
}
},
created () {
this.getInstanceInfos()
},
computed: {
...mapGetters('platform', { isPlatformOpen: 'isOpen', platformPath: 'path' }),
...mapGetters('core', { instanceInfos: 'version' }),
isOnline () {
return window.navigator.onLine
},
iFrameUrl () {
const instanceInfos = JSON.stringify(this.instanceInfos)
const authRedirectUrl = `${window.location.href.split('?')[0]}?platform=${this.platformPath}`
return qs.stringifyUrl({ url: `${this.platformUrl}/partials/auth-state`, query: { authRedirectUrl, instanceInfos } })
}
},
methods: {
...mapActions('platform', { openPlatform: 'open' }, { closePlatform: 'close' }),
...mapActions('core', { getInstanceInfos: 'getVersion' })
},
watch: {
isPlatformOpen (isOpen, wasOpen) {
if (!isOpen && wasOpen) {
this.renderIrame = false
this.$nextTick().then(() => {
this.renderIrame = true
})
}
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
#platform-auth-state {
width: 1px;
min-width: 296px;
}
</style>

View file

@ -0,0 +1,112 @@
<template>
<side-panel
:isOpen="isPlatformOpen"
@onClose="closePlatform()"
v-if="isOnline"
>
<div class="iframe-wrapper">
<iframe
id="platform-iframe"
:src="iFrameUrl"
v-resize="resizeOpts"
style="position: relative; height: 100%; width: 100%;"
frameBorder="0"
scrolling="yes"
@load="onIFrameLoad"
/>
</div>
</side-panel>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import qs from 'query-string'
import SidePanel from '../_commons/SidePanel'
import Helps from '../../_helpers/Helps'
import '../../_directives/resize'
export default {
name: 'PlatformPanel',
components: {
SidePanel
},
data () {
return {
resizeOpts: {
log: false,
resize: false,
scrolling: true,
onMessage: ({ iframe, message }) => {
if (typeof message === 'string') {
// 1st condition for backward compatibility
if (message === 'open:profile') {
this.openPlatform('/')
} else if (message.includes('open:')) {
this.openPlatform(message.split('open:')[1])
} else if (message === 'logout') {
this.closePlatform()
}
} else if (message.type) {
switch (message.type) {
case 'copy-to-clipboard':
navigator.clipboard.writeText(message.value)
break
}
} else if (message.isAuthenticated) {
this.isAuthenticated = message.isAuthenticated
}
}
}
}
},
created () {
this.getInstanceInfos()
},
computed: {
...mapGetters('platform', { isPlatformOpen: 'isOpen', platformPath: 'path' }),
...mapGetters('core', { instanceInfos: 'version' }),
iFrameUrl () {
const instanceInfos = JSON.stringify(this.instanceInfos)
const authRedirectUrl = `${window.location.href.split('?')[0]}?platform=${this.platformPath}`
return qs.stringifyUrl({ url: `${this.platformUrl}${this.platformPath}`, query: { authRedirectUrl, instanceInfos } })
},
isOnline () {
return window.navigator.onLine
}
},
methods: {
...mapActions('platform', { openPlatform: 'open', closePlatform: 'close' }),
...mapActions('core', { getInstanceInfos: 'getVersion' })
},
watch: {
$route (to, from) {
const wasOpen = from.query && from.query.platform
const isOpen = to.query && to.query.platform
if (!wasOpen && isOpen) {
this.openPlatform(to.query.platform)
}
},
isPlatformOpen (newValue, oldValue) {
if (newValue !== oldValue) {
document.querySelector('body').style.overflow = newValue ? 'hidden' : 'visible'
this.$router.push({
path: this.$route.path,
query: Helps.removeEmptyObjects({
...this.$route.query,
platform: this.platformPath ? this.platformPath : undefined
})
})
}
}
}
}
</script>
<style scoped>
.iframe-wrapper {
height: 100%;
}
</style>