Repository: traefik/traefik (language: go)

Issue:
defaultEntryPoints in v2
Feature

### What did you expect to see?

An ability to set default entry points for the services which does not specify it. This is mainly a security feature which gives an administrator a way to reliably divide services into types (public, private, etc.). So that private service will not be published to the world accidentally without specifying entry point. Or event stricter - service will not be published (available) at all if no entry point is specified for it and `defaultEntryPoints` is empty. Of course saving current behavior like `defaultEntryPoints = *`. This could also be divided into default http/tcp/udp entry points if this make sense. 

This was suggested earlier in https://github.com/containous/traefik/issues/5672 but the reason of the decision is quite confusing, since it is closed now, writing here:
what is the problem in filtering by entry point across all possible variants (current) compared to filtering across a part of them? Incorrect configuration of default/service entry points might lead to unexpected results I guess, but shouldn't it be a problem of an administrator (correct and wide documentation) rather than a software restriction? The same time this would be nice to have a security foolproof since there are environments where static (main) configuration and services are controlled by different people/departments.

Thanks.


Discussion / hints:
As I understand from existing issues and responses in GitHub, the main problem to return this option is it will affect TCP and UDP routers.
But what about having 3 variables?
```
defaultHttpEntryPoints
defaultTcpEntryPoints
defaultUdpEntryPoints
```

I would guess that most people only care about http anyway.  I don't have hard numbers but that seems like the obvious default use case.  But having three options is OK anyhow.

I very much agree with OP.  For example, I'm now adding some `tls.options` on entrypoints but those only work if you specify `Host` rule for the router.  You forget it and it will fall back to default or no tls at all, and bam your client cert verification doesn't work and anyone can access your service.  This is *very* disturbing to me as there is no way to be secure by default.  You simply have to remember to explicitly add entrypoints and rules to be secure.

This is *bad* design.  In 2021 with all the hacking from all over we should *really* be moving to secure by default.
This is a **very** surprising default behaviour by Traefik. Having a route "just work" is a nice convenient feature but has potentially disastrous consequences - without any way to prevent it by the administrator.

In a world where "self-service" is much more common, lots of times the control over which routes to expose to the world goes to the developers and not the sysadmins. In such reality, it's much more likely that the user will omit the entry point config and won't even notice that his service is now potentially exposed on additional entry points. They might not even know that other entry points exist.

If you don't want to add the config for default entry points, at least consider the option to allow disabling the default behaviour to force users to specify the entry points in an "opt-in" fashion instead of the existing "opt-out".

Without having either of these knobs, my workaround & suggestion to others would be to never mix "public" & "private" traefik instances.
Hello! I would like to tackle this and create a PR for it. Is there anything in particular to consider, such as the things @BulatSaif mentioned about having 3 variables?
Hello @jilleJr,

> Hello! I would like to tackle this and create a PR for it.

Sure don't hesitate to give it a go! 

> Is there anything in particular to consider, such as the things @BulatSaif mentioned about having 3 variables?

We want to keep things simple for the beginning and stick with the current behavior. This means that we would prefer to have only one `defaultEntryPoints` setting which will be used to define the default entry points for HTTP and TCP routers.

Fix patch:
diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go
index f6866aa59d..42b48b2d8e 100644
--- a/cmd/traefik/traefik.go
+++ b/cmd/traefik/traefik.go
@@ -366,8 +366,24 @@ func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvid
 
 func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string {
 	var defaultEntryPoints []string
+
+	// Determines if at least one EntryPoint is configured to be used by default.
+	var hasDefinedDefaults bool
+	for _, ep := range staticConfiguration.EntryPoints {
+		if ep.AsDefault {
+			hasDefinedDefaults = true
+			break
+		}
+	}
+
 	for name, cfg := range staticConfiguration.EntryPoints {
-		// Traefik Hub entryPoint should not be part of the set of default entryPoints.
+		// By default all entrypoints are considered.
+		// If at least one is flagged, then only flagged entrypoints are included.
+		if hasDefinedDefaults && !cfg.AsDefault {
+			continue
+		}
+
+		// Traefik Hub entryPoint should not be used as a default entryPoint.
 		if hub.APIEntrypoint == name || hub.TunnelEntrypoint == name {
 			continue
 		}
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index 6511cce35d..3e048d2d3a 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -108,6 +108,9 @@ Entry points definition. (Default: ```false```)
 `--entrypoints.<name>.address`:  
 Entry point address.
 
+`--entrypoints.<name>.asdefault`:  
+Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```)
+
 `--entrypoints.<name>.forwardedheaders.insecure`:  
 Trust all forwarded headers. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 6f55fdf8cf..088312ec31 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -108,6 +108,9 @@ Entry points definition. (Default: ```false```)
 `TRAEFIK_ENTRYPOINTS_<NAME>_ADDRESS`:  
 Entry point address.
 
+`TRAEFIK_ENTRYPOINTS_<NAME>_ASDEFAULT`:  
+Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```)
+
 `TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_INSECURE`:  
 Trust all forwarded headers. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index 4fff202b27..698e39493a 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -14,6 +14,7 @@
 [entryPoints]
   [entryPoints.EntryPoint0]
     address = "foobar"
+    asDefault = true
     [entryPoints.EntryPoint0.transport]
       [entryPoints.EntryPoint0.transport.lifeCycle]
         requestAcceptGraceTimeout = "42s"
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index 38b0e26375..a2c753ef13 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -14,6 +14,7 @@ serversTransport:
 entryPoints:
   EntryPoint0:
     address: foobar
+    asDefault: true
     transport:
       lifeCycle:
         requestAcceptGraceTimeout: 42s
diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md
index f20c0b456c..119fa60e3f 100644
--- a/docs/content/routing/entrypoints.md
+++ b/docs/content/routing/entrypoints.md
@@ -233,6 +233,54 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
 
     Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
 
+### AsDefault
+
+_Optional, Default=false_
+
+The `AsDefault` option marks the EntryPoint to be in the list of default EntryPoints.
+EntryPoints in this list are used (by default) on HTTP and TCP routers that do not define their own [EntryPoints option](./routers/index.md#entrypoints).
+
+!!! info "List of default EntryPoints"
+
+    If there is no EntryPoint with the `AsDefault` option set to `true`, 
+    then the list of default EntryPoints includes all HTTP/TCP EntryPoints.
+
+    If at least one EntryPoint has the `AsDefault` option set to `true`,
+    then the list of default EntryPoints includes only EntryPoints that have the `AsDefault` option set to `true`.
+
+    Some built-in EntryPoints are always excluded from the list, namely: `traefik`, `traefikhub-api`, and `traefikhub-tunl`.
+
+!!! warning "Only TCP and HTTP"
+
+    The `AsDefault` option has no effect on UDP EntryPoints.
+    When a UDP router does not define the [EntryPoints option](./routers/index.md#entrypoints_2),
+    it is attached to all available UDP EntryPoints.
+
+??? example "Defining only one EntryPoint as default"
+
+    ```yaml tab="File (yaml)"
+    entryPoints:
+      web:
+        address: ":80"
+      websecure:
+        address: ":443"
+        asDefault: true
+    ```
+
+    ```toml tab="File (TOML)"
+    [entryPoints.web]
+      address = ":80"
+    [entryPoints.websecure]
+      address = ":443"
+      asDefault = true
+    ```
+
+    ```bash tab="CLI"
+    --entrypoints.web.address=:80
+    --entrypoints.websecure.address=:443
+    --entrypoints.websecure.asDefault=true
+    ```
+
 ### HTTP/2
 
 #### `maxConcurrentStreams`
diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md
index 3a5045a27e..4468572669 100644
--- a/docs/content/routing/routers/index.md
+++ b/docs/content/routing/routers/index.md
@@ -94,7 +94,7 @@ or act before forwarding the request to the service.
 
 ### EntryPoints
 
-If not specified, HTTP routers will accept requests from all defined entry points.
+If not specified, HTTP routers will accept requests from all EntryPoints in the [list of default EntryPoints](../entrypoints.md#asdefault).
 If you want to limit the router scope to a set of entry points, set the `entryPoints` option.
 
 ??? example "Listens to Every EntryPoint"
@@ -106,7 +106,7 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
     http:
       routers:
         Router-1:
-          # By default, routers listen to every entry points
+          # By default, routers listen to every EntryPoints.
           rule: "Host(`example.com`)"
           service: "service-1"
     ```
@@ -115,7 +115,7 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
     ## Dynamic configuration
     [http.routers]
       [http.routers.Router-1]
-        # By default, routers listen to every entry points
+        # By default, routers listen to every EntryPoints.
         rule = "Host(`example.com`)"
         service = "service-1"
     ```
@@ -666,12 +666,12 @@ The [supported `provider` table](../../https/acme.md#providers) indicates if the
 
 ### General
 
-If both HTTP routers and TCP routers listen to the same entry points, the TCP routers will apply *before* the HTTP routers.
+If both HTTP routers and TCP routers listen to the same EntryPoint, the TCP routers will apply *before* the HTTP routers.
 If no matching route is found for the TCP routers, then the HTTP routers will take over.
 
 ### EntryPoints
 
-If not specified, TCP routers will accept requests from all defined entry points.
+If not specified, TCP routers will accept requests from all EntryPoints in the [list of default EntryPoints](../entrypoints.md#asdefault)..
 If you want to limit the router scope to a set of entry points, set the entry points option.
 
 ??? info "How to handle Server First protocols?"
@@ -699,7 +699,7 @@ If you want to limit the router scope to a set of entry points, set the entry po
     tcp:
       routers:
         Router-1:
-          # By default, routers listen to every entrypoints
+          # By default, routers listen to every EntryPoints.
           rule: "HostSNI(`example.com`)"
           service: "service-1"
           # will route TLS requests (and ignore non tls requests)
@@ -711,7 +711,7 @@ If you want to limit the router scope to a set of entry points, set the entry po
 
     [tcp.routers]
       [tcp.routers.Router-1]
-        # By default, routers listen to every entrypoints
+        # By default, routers listen to every EntryPoints.
         rule = "HostSNI(`example.com`)"
         service = "service-1"
         # will route TLS requests (and ignore non tls requests)
@@ -751,7 +751,7 @@ If you want to limit the router scope to a set of entry points, set the entry po
     --entrypoints.other.address=:9090
     ```
 
-??? example "Listens to Specific Entry Points"
+??? example "Listens to Specific EntryPoints"
 
     **Dynamic Configuration**
 
@@ -1198,12 +1198,12 @@ So UDP "routers" at this time are pretty much only load-balancers in one form or
 	As expected, a `timeout` is associated to each of these sessions,
 	so that they get cleaned out if they go through a period of inactivity longer than a given duration. 
 	Timeout can be configured using the `entryPoints.name.udp.timeout` option as described 
-	under [entry points](../entrypoints/#udp-options).
+	under [EntryPoints](../entrypoints/#udp-options).
 
 ### EntryPoints
 
-If not specified, UDP routers will accept packets from all defined (UDP) entry points.
-If one wants to limit the router scope to a set of entry points, one should set the entry points option.
+If not specified, UDP routers will accept packets from all defined (UDP) EntryPoints.
+If one wants to limit the router scope to a set of EntryPoints, one should set the `entryPoints` option.
 
 ??? example "Listens to Every Entry Point"
 
@@ -1267,7 +1267,7 @@ If one wants to limit the router scope to a set of entry points, one should set
     --entrypoints.streaming.address=":9191/udp"
     ```
 
-??? example "Listens to Specific Entry Points"
+??? example "Listens to Specific EntryPoints"
 
     **Dynamic Configuration**
 
diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go
index f97d78e223..7863f1bcb0 100644
--- a/pkg/config/static/entrypoints.go
+++ b/pkg/config/static/entrypoints.go
@@ -12,6 +12,7 @@ import (
 // EntryPoint holds the entry point configuration.
 type EntryPoint struct {
 	Address          string                `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
+	AsDefault        bool                  `description:"Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined." json:"asDefault,omitempty" toml:"asDefault,omitempty" yaml:"asDefault,omitempty"`
 	Transport        *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"`
 	ProxyProtocol    *ProxyProtocol        `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	ForwardedHeaders *ForwardedHeaders     `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"`


Test patch:
diff --git a/cmd/traefik/traefik_test.go b/cmd/traefik/traefik_test.go
index 26bc8643c1..c1d6770587 100644
--- a/cmd/traefik/traefik_test.go
+++ b/cmd/traefik/traefik_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/go-kit/kit/metrics"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"github.com/traefik/traefik/v2/pkg/config/static"
 )
 
 // FooCert is a PEM-encoded TLS cert.
@@ -114,3 +115,79 @@ func TestAppendCertMetric(t *testing.T) {
 		})
 	}
 }
+
+func TestGetDefaultsEntrypoints(t *testing.T) {
+	testCases := []struct {
+		desc        string
+		entrypoints static.EntryPoints
+		expected    []string
+	}{
+		{
+			desc: "Skips special names",
+			entrypoints: map[string]*static.EntryPoint{
+				"web": {
+					Address: ":80",
+				},
+				"traefik": {
+					Address: ":8080",
+				},
+				"traefikhub-api": {
+					Address: ":9900",
+				},
+				"traefikhub-tunl": {
+					Address: ":9901",
+				},
+			},
+			expected: []string{"web"},
+		},
+		{
+			desc: "Two EntryPoints not attachable",
+			entrypoints: map[string]*static.EntryPoint{
+				"web": {
+					Address: ":80",
+				},
+				"websecure": {
+					Address: ":443",
+				},
+			},
+			expected: []string{"web", "websecure"},
+		},
+		{
+			desc: "Two EntryPoints only one attachable",
+			entrypoints: map[string]*static.EntryPoint{
+				"web": {
+					Address: ":80",
+				},
+				"websecure": {
+					Address:   ":443",
+					AsDefault: true,
+				},
+			},
+			expected: []string{"websecure"},
+		},
+		{
+			desc: "Two attachable EntryPoints",
+			entrypoints: map[string]*static.EntryPoint{
+				"web": {
+					Address:   ":80",
+					AsDefault: true,
+				},
+				"websecure": {
+					Address:   ":443",
+					AsDefault: true,
+				},
+			},
+			expected: []string{"web", "websecure"},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			actual := getDefaultsEntrypoints(&static.Configuration{
+				EntryPoints: test.entrypoints,
+			})
+
+			assert.ElementsMatch(t, test.expected, actual)
+		})
+	}
+}