Repository: traefik/traefik (language: go)

Issue:
Support Spiffe mTLS
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you expect to see?

Hello Traefik team!

## Context and Feature Description

We've been recently investigating [SPIFFE](https://spiffe.io/) and it's runtime [SPIRE](https://spiffe.io/docs/latest/spire-about/spire-concepts/) to deliver cryptographic materials to our workload so they can securely prove their identity and trust each other.

For service to service communication, we are able to instrument our workload to reach this goal relatively easily. But we still need to figure out how to secure the first hop between the ingress controller and our workload.

I can see there's [some in progress work on the serversTransport configuration](https://github.com/traefik/traefik/issues/7826) regarding making Traefik able to validate Spiffe certificates, but we would like to propose to go one step further and have Traefik actually integrate with the SPIFFE workload API, retrieve its SVIDs and present them to the backend server when it establishes connector, the goal being having Traefik trust the backend server, but also the backend server trust Traefik.

On top of that, we could implement some sort of authorization by placing constraints on the spiffeID  presented by backend server (either an allow list or allowing a trust domain).

I've been fiddling around and managed to make a PoC of this feature in Traefik, in the context of a Kubernetes cluster running SPIRE, here's a few links:

- [A demo repository](https://github.com/jlevesy/spire-testbed) which demonstrates the Traefik -> HTTPS server -> gRPC server use case, everything over SPIFFE. 
- [The code changes in Traefik](https://github.com/traefik/traefik/compare/master...jlevesy:traefik:jly/poc-spiffe-support) to add the support in Traefik's core (minimal changes in the roundtripper manager) and in the Kubernetes CRD provider.

The cool thing with SPIFFE is that it works in k8s, but also in many other contexts (swarm, nomad, on prem...), as long as a workload API is reachable and Traefik is properly registered as workload. For the record, I reckon that only Envoy and Nginx plus are supporting this.

## How to implement this Feature

Disclaimer: that's how I see this feature I'm highly biased by my use case 😅 , I do believe this deserves some more thinking as SPIFFE can allow to do many things.

### Configuration Changes

#### Static Configuration

First of all, we need to tell Traefik how to reach a SPIFFE agent, the recommended way is to reach it using a unix socket. As it is server transport related, this parameter could fit under the `serversTransport` section of the static configuration.

```yaml
# Static config
serversTransport:
  spiffeAgentSocket: unix:///run/spire/sockets/agent.sock
```

If this parameter is defined, then Traefik establishes a connection to the argent workload API and waits for it's first SVID to be delivered before starting. The [spiffe-go library already takes care of this](https://github.com/traefik/traefik/compare/master...jlevesy:traefik:jly/poc-spiffe-support#diff-33566ca985cfa8547d2ba20a4467571961d2254f2efee309d84d22e2e37ce401R261),

This requires to make the spiffe socket reachable in Traefik container, on k8s this translates by the following [changes in the deployment manifest](https://github.com/jlevesy/spire-testbed/blob/main/k8s/traefik/1-traefik.yaml#L168).

#### Dynamic Configuration

What we need here is:

- Tell Traefik, per transport if SpiffeMTLS needs to be enabled
- Optionally do some authorization on Traefik side
   - Either pass down an allow list of  `SpiffeIDs` that could be presented by the backend server
   - Or pass an allowed trust domain, which will make Traefik allow any SpiffeIDs that comes from the given trust domain. If both options are given, the SpiffeID list is the more specific here so it would take precedence.

Which would translates to the following changes in the `ServersTransport` structure dynamic config : 

- Add an `EnableSpiffeMTLS` boolean that tells if or not SPIFFE mTLS needs to be used for this transport.
- Add an optional `ServerSpiffeIDs` string slice, that allows to pass down a list of allowed SpiffeIDs for the backend servers
- Add an optional `ServerSpiffeTrustDomain` string that allows to pass down an allowed trust domain.

[Here's an example of this configuration using the KubernetesCRD + Ingress providers](https://github.com/jlevesy/spire-testbed/blob/main/k8s/echo-gateway/1-service.yaml).

### Code Changes

The [go-spiffe library](https://github.com/spiffe/go-spiffe) makes this integration rather easy from a code point of view as it provides the building blocks to get a standard `tls.Config` easilly. Here's are the important parts:

- If a spiffe agent socket path is given, then spin up an x509SSource during boot and pass it down to the  RoundTripperManager. [here's the change](https://github.com/jlevesy/traefik/blob/jly/poc-spiffe-support/cmd/traefik/traefik.go#L261)
- From there in the roundtripper manager, build a tlsconfig based on the given x509Source if it is given and an optional authorizer if  the configuration tells to do so. [here's the change](https://github.com/jlevesy/traefik/blob/jly/poc-spiffe-support/pkg/server/service/roundtripper.go#L149)

## Additional Notes

I do think that this could be also done for TCP routing, but I haven't explored this path yet.

Happy to talk about this, and work on this feature if you folks think it's relevant!

Best, and thanks for the hard work anyway!


Discussion / hints:

Fix patch:
diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go
index 42b48b2d8e..a8216d48be 100644
--- a/cmd/traefik/traefik.go
+++ b/cmd/traefik/traefik.go
@@ -19,6 +19,7 @@ import (
 	"github.com/go-acme/lego/v4/challenge"
 	gokitmetrics "github.com/go-kit/kit/metrics"
 	"github.com/sirupsen/logrus"
+	"github.com/spiffe/go-spiffe/v2/workloadapi"
 	"github.com/traefik/paerser/cli"
 	"github.com/traefik/traefik/v2/cmd"
 	"github.com/traefik/traefik/v2/cmd/healthcheck"
@@ -257,7 +258,28 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
 
 	// Service manager factory
 
-	roundTripperManager := service.NewRoundTripperManager()
+	var spiffeX509Source *workloadapi.X509Source
+	if staticConfiguration.Spiffe != nil && staticConfiguration.Spiffe.WorkloadAPIAddr != "" {
+		log.WithoutContext().
+			WithField("workloadAPIAddr", staticConfiguration.Spiffe.WorkloadAPIAddr).
+			Info("Waiting on SPIFFE SVID delivery")
+
+		spiffeX509Source, err = workloadapi.NewX509Source(
+			ctx,
+			workloadapi.WithClientOptions(
+				workloadapi.WithAddr(
+					staticConfiguration.Spiffe.WorkloadAPIAddr,
+				),
+			),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("unable to create SPIFFE x509 source: %w", err)
+		}
+
+		log.WithoutContext().Info("Successfully obtained SPIFFE SVID.")
+	}
+
+	roundTripperManager := service.NewRoundTripperManager(spiffeX509Source)
 	acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider)
 	managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler)
 
diff --git a/docs/content/https/spiffe.md b/docs/content/https/spiffe.md
new file mode 100644
index 0000000000..9d70e5a209
--- /dev/null
+++ b/docs/content/https/spiffe.md
@@ -0,0 +1,54 @@
+---
+title: "Traefik SPIFFE Documentation"
+description: "Learn how to configure Traefik to use SPIFFE. Read the technical documentation."
+---
+
+# SPIFFE
+
+Secure the backend connection with SPIFFE.
+{: .subtitle }
+
+[SPIFFE](https://spiffe.io/docs/latest/spiffe-about/overview/) (Secure Production Identity Framework For Everyone), 
+provides a secure identity in the form of a specially crafted X.509 certificate, 
+to every workload in an environment.
+
+Traefik is able to connect to the Workload API to obtain an x509-SVID used to secure the connection with SPIFFE enabled backends.
+
+## Configuration
+
+### General
+
+Enabling SPIFFE is part of the [static configuration](../getting-started/configuration-overview.md#the-static-configuration).
+It can be defined by using a file (YAML or TOML) or CLI arguments.
+
+### Workload API
+
+The `workloadAPIAddr` configuration defines the address of the SPIFFE [Workload API](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-workload-api).
+
+!!! info "Enabling SPIFFE in ServersTransports"
+
+    Enabling SPIFFE does not imply that backend connections are going to use it automatically.
+    Each [ServersTransport](../routing/services/index.md#serverstransport_1) that is meant to be secured with SPIFFE must [explicitly](../routing/services/index.md#spiffe) enable it.
+
+!!! warning "SPIFFE can cause Traefik to stall"
+	When using SPIFFE,
+	Traefik will wait for the first SVID to be delivered before starting.
+	If Traefik is hanging when waiting on SPIFFE SVID delivery,
+	please double check that it is correctly registered as workload in your SPIFFE infrastructure.
+
+```yaml tab="File (YAML)"
+## Static configuration
+spiffe:
+    workloadAPIAddr: localhost
+```
+
+```toml tab="File (TOML)"
+## Static configuration
+[spiffe]
+    workloadAPIAddr: localhost
+```
+
+```bash tab="CLI"
+## Static configuration
+--spiffe.workloadAPIAddr=localhost
+```
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index 4634afea2c..c197e75cbb 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -301,12 +301,18 @@
       [[http.serversTransports.ServersTransport0.certificates]]
         certFile = "foobar"
         keyFile = "foobar"
+
       [http.serversTransports.ServersTransport0.forwardingTimeouts]
         dialTimeout = "42s"
         responseHeaderTimeout = "42s"
         idleConnTimeout = "42s"
         readIdleTimeout = "42s"
         pingTimeout = "42s"
+
+      [http.serversTransports.ServersTransport0.spiffe]
+        ids = ["foobar", "foobar"]
+        trustDomain = "foobar"
+
     [http.serversTransports.ServersTransport1]
       serverName = "foobar"
       insecureSkipVerify = true
@@ -322,6 +328,7 @@
       [[http.serversTransports.ServersTransport1.certificates]]
         certFile = "foobar"
         keyFile = "foobar"
+
       [http.serversTransports.ServersTransport1.forwardingTimeouts]
         dialTimeout = "42s"
         responseHeaderTimeout = "42s"
@@ -329,6 +336,10 @@
         readIdleTimeout = "42s"
         pingTimeout = "42s"
 
+      [http.serversTransports.ServersTransport1.spiffe]
+        ids = ["foobar", "foobar"]
+        trustDomain = "foobar"
+
 [tcp]
   [tcp.routers]
     [tcp.routers.TCPRouter0]
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index 807bc8a14e..fc446ec0f1 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -345,6 +345,12 @@ http:
         pingTimeout: 42s
       disableHTTP2: true
       peerCertURI: foobar
+      spiffe:
+        ids:
+          - foobar
+          - foobar
+        trustDomain: foobar
+
     ServersTransport1:
       serverName: foobar
       insecureSkipVerify: true
@@ -365,6 +371,12 @@ http:
         pingTimeout: 42s
       disableHTTP2: true
       peerCertURI: foobar
+      spiffe:
+        ids:
+          - foobar
+          - foobar
+        trustDomain: foobar
+
 tcp:
   routers:
     TCPRouter0:
diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
index 17dc336f63..983a678b8a 100644
--- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
+++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
@@ -1679,6 +1679,19 @@ spec:
                 description: ServerName defines the server name used to contact the
                   server.
                 type: string
+              spiffe:
+                description: Spiffe defines the SPIFFE configuration.
+                properties:
+                  ids:
+                    description: IDs defines the allowed SPIFFE IDs (takes precedence
+                      over the SPIFFE TrustDomain).
+                    items:
+                      type: string
+                    type: array
+                  trustDomain:
+                    description: TrustDomain defines the allowed SPIFFE trust domain.
+                    type: string
+                type: object
             type: object
         required:
         - metadata
diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md
index f72e024e41..d83a15476c 100644
--- a/docs/content/reference/dynamic-configuration/kv-ref.md
+++ b/docs/content/reference/dynamic-configuration/kv-ref.md
@@ -186,6 +186,9 @@
 | `traefik/http/serversTransports/ServersTransport0/rootCAs/0` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport0/rootCAs/1` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport0/serverName` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport0/spiffe/ids/0` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport0/spiffe/ids/1` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport0/spiffe/trustDomain` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport1/certificates/0/certFile` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport1/certificates/0/keyFile` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport1/certificates/1/certFile` | `foobar` |
@@ -202,6 +205,9 @@
 | `traefik/http/serversTransports/ServersTransport1/rootCAs/0` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport1/rootCAs/1` | `foobar` |
 | `traefik/http/serversTransports/ServersTransport1/serverName` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport1/spiffe/ids/0` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport1/spiffe/ids/1` | `foobar` |
+| `traefik/http/serversTransports/ServersTransport1/spiffe/trustDomain` | `foobar` |
 | `traefik/http/services/Service01/loadBalancer/healthCheck/followRedirects` | `true` |
 | `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `foobar` |
 | `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1` | `foobar` |
diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml
index afc0384965..f23d345ce8 100644
--- a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml
+++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml
@@ -113,6 +113,19 @@ spec:
                 description: ServerName defines the server name used to contact the
                   server.
                 type: string
+              spiffe:
+                description: Spiffe defines the SPIFFE configuration.
+                properties:
+                  ids:
+                    description: IDs defines the allowed SPIFFE IDs (takes precedence
+                      over the SPIFFE TrustDomain).
+                    items:
+                      type: string
+                    type: array
+                  trustDomain:
+                    description: TrustDomain defines the allowed SPIFFE trust domain.
+                    type: string
+                type: object
             type: object
         required:
         - metadata
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index 444a2ce848..eb7831f52b 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -984,6 +984,18 @@ If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, D
 `--serverstransport.rootcas`:  
 Add cert file for self-signed certificate.
 
+`--serverstransport.spiffe`:  
+Defines the SPIFFE configuration. (Default: ```false```)
+
+`--serverstransport.spiffe.ids`:  
+Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain).
+
+`--serverstransport.spiffe.trustdomain`:  
+Defines the allowed SPIFFE trust domain.
+
+`--spiffe.workloadapiaddr`:  
+Defines the workload API address.
+
 `--tracing`:  
 OpenTracing configuration. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 3cae404b16..e23990497e 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -984,6 +984,18 @@ If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, D
 `TRAEFIK_SERVERSTRANSPORT_ROOTCAS`:  
 Add cert file for self-signed certificate.
 
+`TRAEFIK_SERVERSTRANSPORT_SPIFFE`:  
+Defines the SPIFFE configuration. (Default: ```false```)
+
+`TRAEFIK_SERVERSTRANSPORT_SPIFFE_IDS`:  
+Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain).
+
+`TRAEFIK_SERVERSTRANSPORT_SPIFFE_TRUSTDOMAIN`:  
+Defines the allowed SPIFFE trust domain.
+
+`TRAEFIK_SPIFFE_WORKLOADAPIADDR`:  
+Defines the workload API address.
+
 `TRAEFIK_TRACING`:  
 OpenTracing configuration. (Default: ```false```)
 
diff --git a/docs/content/routing/overview.md b/docs/content/routing/overview.md
index b49c9748bd..3816c1e703 100644
--- a/docs/content/routing/overview.md
+++ b/docs/content/routing/overview.md
@@ -325,6 +325,61 @@ serversTransport:
 --serversTransport.maxIdleConnsPerHost=7
 ```
 
+### `spiffe`
+
+Please note that [SPIFFE](../https/spiffe.md) must be enabled in the static configuration 
+before using it to secure the connection between Traefik and the backends.  
+
+#### `spiffe.ids`
+
+_Optional_
+
+`ids` defines the allowed SPIFFE IDs.
+This takes precedence over the SPIFFE TrustDomain.
+
+```yaml tab="File (YAML)"
+## Static configuration
+serversTransport:
+    spiffe:
+      ids:
+        - spiffe://trust-domain/id1
+        - spiffe://trust-domain/id2
+```
+
+```toml tab="File (TOML)"
+## Static configuration
+[serversTransport.spiffe]
+  ids = ["spiffe://trust-domain/id1", "spiffe://trust-domain/id2"]
+```
+
+```bash tab="CLI"
+## Static configuration
+--serversTransport.spiffe.ids=spiffe://trust-domain/id1,spiffe://trust-domain/id2
+```
+
+#### `spiffe.trustDomain`
+
+_Optional_
+
+`trustDomain` defines the allowed SPIFFE trust domain.
+
+```yaml tab="File (YAML)"
+## Static configuration
+serversTransport:
+  trustDomain: spiffe://trust-domain
+```
+
+```toml tab="File (TOML)"
+## Static configuration
+[serversTransport.spiffe]
+  trustDomain = "spiffe://trust-domain"
+```
+
+```bash tab="CLI"
+## Static configuration
+--serversTransport.spiffe.trustDomain=spiffe://trust-domain
+```
+
 ### `forwardingTimeouts`
 
 `forwardingTimeouts` is about a number of timeouts relevant to when forwarding requests to the backend servers.
diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md
index 7186765f1c..800471cf81 100644
--- a/docs/content/routing/services/index.md
+++ b/docs/content/routing/services/index.md
@@ -776,6 +776,82 @@ spec:
     peerCertURI: foobar
 ```
 
+#### `spiffe`
+
+Please note that [SPIFFE](../../https/spiffe.md) must be enabled in the static configuration
+before using it to secure the connection between Traefik and the backends.
+
+##### `spiffe.ids`
+
+_Optional_
+
+`ids` defines the allowed SPIFFE IDs. 
+This takes precedence over the SPIFFE TrustDomain.
+
+```yaml tab="File (YAML)"
+## Dynamic configuration
+http:
+  serversTransports:
+    mytransport:
+      spiffe:
+        ids:
+          - spiffe://trust-domain/id1
+          - spiffe://trust-domain/id2
+```
+
+```toml tab="File (TOML)"
+## Dynamic configuration
+[http.serversTransports.mytransport.spiffe]
+  ids = ["spiffe://trust-domain/id1", "spiffe://trust-domain/id2"]
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: ServersTransport
+metadata:
+  name: mytransport
+  namespace: default
+
+spec:
+    spiffe:
+      ids:
+        - spiffe://trust-domain/id1
+        - spiffe://trust-domain/id2
+```
+
+##### `spiffe.trustDomain`
+
+_Optional_
+
+`trustDomain` defines the allowed SPIFFE trust domain.
+
+```yaml tab="File (YAML)"
+## Dynamic configuration
+http:
+  serversTransports:
+    mytransport:
+        spiffe:
+          trustDomain: spiffe://trust-domain
+```
+
+```toml tab="File (TOML)"
+## Dynamic configuration
+[http.serversTransports.mytransport.spiffe]
+  trustDomain = "spiffe://trust-domain"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: ServersTransport
+metadata:
+  name: mytransport
+  namespace: default
+
+spec:
+    spiffe:
+      trustDomain: "spiffe://trust-domain"
+```
+
 #### `forwardingTimeouts`
 
 `forwardingTimeouts` are the timeouts applied when forwarding requests to the servers.
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 218154bc3c..549fa367c5 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -110,6 +110,7 @@ nav:
       - 'TLS': 'https/tls.md'
       - 'Let''s Encrypt': 'https/acme.md'
       - 'Tailscale': 'https/tailscale.md'
+      - 'SPIFFE': 'https/spiffe.md'
   - 'Middlewares':
     - 'Overview': 'middlewares/overview.md'
     - 'HTTP':
diff --git a/go.mod b/go.mod
index 35ee71c305..6377a2a2cb 100644
--- a/go.mod
+++ b/go.mod
@@ -57,6 +57,7 @@ require (
 	github.com/prometheus/client_model v0.2.0
 	github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac
 	github.com/sirupsen/logrus v1.8.1
+	github.com/spiffe/go-spiffe/v2 v2.1.1
 	github.com/stretchr/testify v1.8.0
 	github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
 	github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
@@ -76,7 +77,7 @@ require (
 	golang.org/x/text v0.3.7
 	golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
 	golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
-	google.golang.org/grpc v1.41.0
+	google.golang.org/grpc v1.46.0
 	gopkg.in/DataDog/dd-trace-go.v1 v1.38.1
 	gopkg.in/fsnotify.v1 v1.4.7
 	gopkg.in/yaml.v3 v3.0.1
@@ -111,7 +112,7 @@ require (
 	github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/semver/v3 v3.1.1 // indirect
-	github.com/Microsoft/go-winio v0.5.1 // indirect
+	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/Microsoft/hcsshim v0.8.23 // indirect
 	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
 	github.com/Shopify/sarama v1.23.1 // indirect
@@ -311,6 +312,7 @@ require (
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
 	github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
+	github.com/zeebo/errs v1.2.2 // indirect
 	go.elastic.co/apm/module/apmhttp v1.13.1 // indirect
 	go.elastic.co/fastjson v1.1.0 // indirect
 	go.etcd.io/etcd/api/v3 v3.5.4 // indirect
diff --git a/go.sum b/go.sum
index e15dae5df0..9ae168cae3 100644
--- a/go.sum
+++ b/go.sum
@@ -153,8 +153,9 @@ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JP
 github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
 github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
 github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
 github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
@@ -338,8 +339,12 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -629,6 +634,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
@@ -1890,6 +1896,8 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
+github.com/spiffe/go-spiffe/v2 v2.1.1 h1:RT9kM8MZLZIsPTH+HKQEP5yaAk3yd/VBzlINaRjXs8k=
+github.com/spiffe/go-spiffe/v2 v2.1.1/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg=
 github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
 github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -2059,6 +2067,8 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go
 github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
 github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
 github.com/zclconf/go-cty v1.7.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o=
+github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
+github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
@@ -2676,6 +2686,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
 google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -2724,8 +2735,10 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -2794,6 +2807,7 @@ gopkg.in/olivere/elastic.v5 v5.0.84/go.mod h1:LXF6q9XNBxpMqrcgax95C6xyARXWbbCXUr
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
 gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml
index 17dc336f63..983a678b8a 100644
--- a/integration/fixtures/k8s/01-traefik-crd.yml
+++ b/integration/fixtures/k8s/01-traefik-crd.yml
@@ -1679,6 +1679,19 @@ spec:
                 description: ServerName defines the server name used to contact the
                   server.
                 type: string
+              spiffe:
+                description: Spiffe defines the SPIFFE configuration.
+                properties:
+                  ids:
+                    description: IDs defines the allowed SPIFFE IDs (takes precedence
+                      over the SPIFFE TrustDomain).
+                    items:
+                      type: string
+                    type: array
+                  trustDomain:
+                    description: TrustDomain defines the allowed SPIFFE trust domain.
+                    type: string
+                type: object
             type: object
         required:
         - metadata
diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go
index 376223e1c5..f93c1a6a39 100644
--- a/pkg/config/dynamic/http_config.go
+++ b/pkg/config/dynamic/http_config.go
@@ -250,6 +250,17 @@ type ServersTransport struct {
 	ForwardingTimeouts  *ForwardingTimeouts        `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"`
 	DisableHTTP2        bool                       `description:"Disable HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"`
 	PeerCertURI         string                     `description:"URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"`
+	Spiffe              *Spiffe                    `description:"Define the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+}
+
+// +k8s:deepcopy-gen=true
+
+// Spiffe holds the SPIFFE configuration.
+type Spiffe struct {
+	// IDs defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain).
+	IDs []string `description:"Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain)." json:"ids,omitempty" toml:"ids,omitempty" yaml:"ids,omitempty"`
+	// TrustDomain defines the allowed SPIFFE trust domain.
+	TrustDomain string `description:"Defines the allowed SPIFFE trust domain." json:"trustDomain,omitempty" yaml:"trustDomain,omitempty" toml:"trustDomain,omitempty"`
 }
 
 // +k8s:deepcopy-gen=true
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index 5870481802..adba5a4ee6 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -1161,6 +1161,11 @@ func (in *ServersTransport) DeepCopyInto(out *ServersTransport) {
 		*out = new(ForwardingTimeouts)
 		**out = **in
 	}
+	if in.Spiffe != nil {
+		in, out := &in.Spiffe, &out.Spiffe
+		*out = new(Spiffe)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
@@ -1231,6 +1236,27 @@ func (in *SourceCriterion) DeepCopy() *SourceCriterion {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Spiffe) DeepCopyInto(out *Spiffe) {
+	*out = *in
+	if in.IDs != nil {
+		in, out := &in.IDs, &out.IDs
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spiffe.
+func (in *Spiffe) DeepCopy() *Spiffe {
+	if in == nil {
+		return nil
+	}
+	out := new(Spiffe)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Sticky) DeepCopyInto(out *Sticky) {
 	*out = *in
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index 4d803b2ad3..4a2558b8b1 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -84,6 +84,13 @@ type Configuration struct {
 	Hub *hub.Provider `description:"Traefik Hub configuration." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 
 	Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"`
+
+	Spiffe *SpiffeClientConfig `description:"SPIFFE integration configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" export:"true"`
+}
+
+// SpiffeClientConfig defines the SPIFFE client configuration.
+type SpiffeClientConfig struct {
+	WorkloadAPIAddr string `description:"Defines the workload API address." json:"workloadAPIAddr,omitempty" toml:"workloadAPIAddr,omitempty" yaml:"workloadAPIAddr,omitempty"`
 }
 
 // CertificateResolver contains the configuration for the different types of certificates resolver.
@@ -104,6 +111,13 @@ type ServersTransport struct {
 	RootCAs             []tls.FileOrContent `description:"Add cert file for self-signed certificate." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"`
 	MaxIdleConnsPerHost int                 `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"`
 	ForwardingTimeouts  *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"`
+	Spiffe              *Spiffe             `description:"Defines the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+}
+
+// Spiffe holds the SPIFFE configuration.
+type Spiffe struct {
+	IDs         []string `description:"Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain)." json:"ids,omitempty" toml:"ids,omitempty" yaml:"ids,omitempty"`
+	TrustDomain string   `description:"Defines the allowed SPIFFE trust domain." json:"trustDomain,omitempty" yaml:"trustDomain,omitempty" toml:"trustDomain,omitempty"`
 }
 
 // API holds the API configuration.
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index 36178ce985..d3dc851bd8 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -21,6 +21,7 @@ import (
 	"github.com/vulcand/oxy/roundrobin"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials/insecure"
 	healthpb "google.golang.org/grpc/health/grpc_health_v1"
 	"google.golang.org/grpc/status"
 )
@@ -317,7 +318,7 @@ func checkHealthGRPC(serverURL *url.URL, backend *BackendConfig) error {
 	var opts []grpc.DialOption
 	switch backend.Options.Scheme {
 	case "http", "h2c", "":
-		opts = append(opts, grpc.WithInsecure())
+		opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), backend.Options.Timeout)
diff --git a/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml b/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml
index 04d9b4e052..fdbf7b930f 100644
--- a/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml
+++ b/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml
@@ -112,6 +112,11 @@ spec:
     idleConnTimeout: 42ms
     readIdleTimeout: 42s
     pingTimeout: 42s
+  spiffe:
+    ids:
+      - spiffe://foo/buz
+      - spiffe://bar/biz
+    trustDomain: spiffe://lol
 
 ---
 apiVersion: traefik.containo.us/v1alpha1
@@ -144,4 +149,3 @@ spec:
         - name: whoamitls
           port: 443
           serversTransport: default-test
-
diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go
index a43345e675..ff6c6b5887 100644
--- a/pkg/provider/kubernetes/crd/kubernetes.go
+++ b/pkg/provider/kubernetes/crd/kubernetes.go
@@ -381,6 +381,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
 			MaxIdleConnsPerHost: serversTransport.Spec.MaxIdleConnsPerHost,
 			ForwardingTimeouts:  forwardingTimeout,
 			PeerCertURI:         serversTransport.Spec.PeerCertURI,
+			Spiffe:              serversTransport.Spec.Spiffe,
 		}
 	}
 
diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go
index 20bc7d18a1..10486a426a 100644
--- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go
+++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go
@@ -1,6 +1,7 @@
 package v1alpha1
 
 import (
+	"github.com/traefik/traefik/v2/pkg/config/dynamic"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
 )
@@ -42,6 +43,8 @@ type ServersTransportSpec struct {
 	DisableHTTP2 bool `json:"disableHTTP2,omitempty"`
 	// PeerCertURI defines the peer cert URI used to match against SAN URI during the peer certificate verification.
 	PeerCertURI string `json:"peerCertURI,omitempty"`
+	// Spiffe defines the SPIFFE configuration.
+	Spiffe *dynamic.Spiffe `json:"spiffe,omitempty"`
 }
 
 // +k8s:deepcopy-gen=true
diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go
index 5cc7903fcd..5dbd716215 100644
--- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go
@@ -1142,6 +1142,11 @@ func (in *ServersTransportSpec) DeepCopyInto(out *ServersTransportSpec) {
 		*out = new(ForwardingTimeouts)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Spiffe != nil {
+		in, out := &in.Spiffe, &out.Spiffe
+		*out = new(dynamic.Spiffe)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go
index 47d2c30232..0c8d5aca03 100644
--- a/pkg/provider/traefik/internal.go
+++ b/pkg/provider/traefik/internal.go
@@ -322,6 +322,13 @@ func (i *Provider) serverTransport(cfg *dynamic.Configuration) {
 		MaxIdleConnsPerHost: i.staticCfg.ServersTransport.MaxIdleConnsPerHost,
 	}
 
+	if i.staticCfg.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,
diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go
index 68b3b0bbf0..b155a5632f 100644
--- a/pkg/server/service/roundtripper.go
+++ b/pkg/server/service/roundtripper.go
@@ -11,6 +11,10 @@ import (
 	"sync"
 	"time"
 
+	"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
+	"github.com/spiffe/go-spiffe/v2/spiffeid"
+	"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
+	"github.com/spiffe/go-spiffe/v2/svid/x509svid"
 	"github.com/traefik/traefik/v2/pkg/config/dynamic"
 	"github.com/traefik/traefik/v2/pkg/log"
 	traefiktls "github.com/traefik/traefik/v2/pkg/tls"
@@ -26,11 +30,18 @@ func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, erro
 	return t.Transport.RoundTrip(req)
 }
 
+// SpiffeX509Source allows to retrieve a x509 SVID and bundle.
+type SpiffeX509Source interface {
+	x509svid.Source
+	x509bundle.Source
+}
+
 // NewRoundTripperManager creates a new RoundTripperManager.
-func NewRoundTripperManager() *RoundTripperManager {
+func NewRoundTripperManager(spiffeX509Source SpiffeX509Source) *RoundTripperManager {
 	return &RoundTripperManager{
-		roundTrippers: make(map[string]http.RoundTripper),
-		configs:       make(map[string]*dynamic.ServersTransport),
+		roundTrippers:    make(map[string]http.RoundTripper),
+		configs:          make(map[string]*dynamic.ServersTransport),
+		spiffeX509Source: spiffeX509Source,
 	}
 }
 
@@ -39,6 +50,8 @@ type RoundTripperManager struct {
 	rtLock        sync.RWMutex
 	roundTrippers map[string]http.RoundTripper
 	configs       map[string]*dynamic.ServersTransport
+
+	spiffeX509Source SpiffeX509Source
 }
 
 // Update updates the roundtrippers configurations.
@@ -59,7 +72,7 @@ func (r *RoundTripperManager) Update(newConfigs map[string]*dynamic.ServersTrans
 		}
 
 		var err error
-		r.roundTrippers[configName], err = createRoundTripper(newConfig)
+		r.roundTrippers[configName], err = r.createRoundTripper(newConfig)
 		if err != nil {
 			log.WithoutContext().Errorf("Could not configure HTTP Transport %s, fallback on default transport: %v", configName, err)
 			r.roundTrippers[configName] = http.DefaultTransport
@@ -72,7 +85,7 @@ func (r *RoundTripperManager) Update(newConfigs map[string]*dynamic.ServersTrans
 		}
 
 		var err error
-		r.roundTrippers[newConfigName], err = createRoundTripper(newConfig)
+		r.roundTrippers[newConfigName], err = r.createRoundTripper(newConfig)
 		if err != nil {
 			log.WithoutContext().Errorf("Could not configure HTTP Transport %s, fallback on default transport: %v", newConfigName, err)
 			r.roundTrippers[newConfigName] = http.DefaultTransport
@@ -102,7 +115,7 @@ func (r *RoundTripperManager) Get(name string) (http.RoundTripper, error) {
 // For the settings that can't be configured in Traefik it uses the default http.Transport settings.
 // An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost in Traefik at this point in time.
 // Setting this value to the default of 100 could lead to confusing behavior and backwards compatibility issues.
-func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) {
+func (r *RoundTripperManager) createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) {
 	if cfg == nil {
 		return nil, errors.New("no transport configuration given")
 	}
@@ -132,7 +145,24 @@ func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error
 		transport.IdleConnTimeout = time.Duration(cfg.ForwardingTimeouts.IdleConnTimeout)
 	}
 
+	if cfg.Spiffe != nil {
+		if r.spiffeX509Source == nil {
+			return nil, errors.New("SPIFFE is enabled for this transport, but not configured")
+		}
+
+		spiffeAuthorizer, err := buildSpiffeAuthorizer(cfg.Spiffe)
+		if err != nil {
+			return nil, fmt.Errorf("unable to build SPIFFE authorizer: %w", err)
+		}
+
+		transport.TLSClientConfig = tlsconfig.MTLSClientConfig(r.spiffeX509Source, r.spiffeX509Source, spiffeAuthorizer)
+	}
+
 	if cfg.InsecureSkipVerify || len(cfg.RootCAs) > 0 || len(cfg.ServerName) > 0 || len(cfg.Certificates) > 0 || cfg.PeerCertURI != "" {
+		if transport.TLSClientConfig != nil {
+			return nil, errors.New("TLS and SPIFFE configuration cannot be defined at the same time")
+		}
+
 		transport.TLSClientConfig = &tls.Config{
 			ServerName:         cfg.ServerName,
 			InsecureSkipVerify: cfg.InsecureSkipVerify,
@@ -173,3 +203,31 @@ func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {
 
 	return roots
 }
+
+func buildSpiffeAuthorizer(cfg *dynamic.Spiffe) (tlsconfig.Authorizer, error) {
+	switch {
+	case len(cfg.IDs) > 0:
+		spiffeIDs := make([]spiffeid.ID, 0, len(cfg.IDs))
+		for _, rawID := range cfg.IDs {
+			id, err := spiffeid.FromString(rawID)
+			if err != nil {
+				return nil, fmt.Errorf("invalid SPIFFE ID: %w", err)
+			}
+
+			spiffeIDs = append(spiffeIDs, id)
+		}
+
+		return tlsconfig.AuthorizeOneOf(spiffeIDs...), nil
+
+	case cfg.TrustDomain != "":
+		trustDomain, err := spiffeid.TrustDomainFromString(cfg.TrustDomain)
+		if err != nil {
+			return nil, fmt.Errorf("invalid SPIFFE trust domain: %w", err)
+		}
+
+		return tlsconfig.AuthorizeMemberOf(trustDomain), nil
+
+	default:
+		return tlsconfig.AuthorizeAny(), nil
+	}
+}


Test patch:
diff --git a/integration/grpc_test.go b/integration/grpc_test.go
index 496f4b3712..ab1c7f899a 100644
--- a/integration/grpc_test.go
+++ b/integration/grpc_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/traefik/traefik/v2/pkg/log"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/credentials/insecure"
 )
 
 var (
@@ -94,7 +95,7 @@ func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) {
 }
 
 func getHelloClientGRPCh2c() (helloworld.GreeterClient, func() error, error) {
-	conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithInsecure())
+	conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithTransportCredentials(insecure.NewCredentials()))
 	if err != nil {
 		return nil, func() error { return nil }, err
 	}
diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go
index 7465c8faef..60c0794064 100644
--- a/pkg/provider/kubernetes/crd/kubernetes_test.go
+++ b/pkg/provider/kubernetes/crd/kubernetes_test.go
@@ -3916,6 +3916,13 @@ func TestLoadIngressRoutes(t *testing.T) {
 								PingTimeout:           ptypes.Duration(42 * time.Second),
 							},
 							PeerCertURI: "foo://bar",
+							Spiffe: &dynamic.Spiffe{
+								IDs: []string{
+									"spiffe://foo/buz",
+									"spiffe://bar/biz",
+								},
+								TrustDomain: "spiffe://lol",
+							},
 						},
 						"default-test": {
 							ServerName: "test",
diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go
index 867da0a9b6..383fac0c29 100644
--- a/pkg/server/router/router_test.go
+++ b/pkg/server/router/router_test.go
@@ -312,7 +312,7 @@ func TestRouterManager_Get(t *testing.T) {
 				},
 			})
 
-			roundTripperManager := service.NewRoundTripperManager()
+			roundTripperManager := service.NewRoundTripperManager(nil)
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
@@ -418,7 +418,7 @@ func TestAccessLog(t *testing.T) {
 				},
 			})
 
-			roundTripperManager := service.NewRoundTripperManager()
+			roundTripperManager := service.NewRoundTripperManager(nil)
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
@@ -713,7 +713,7 @@ func TestRuntimeConfiguration(t *testing.T) {
 				},
 			})
 
-			roundTripperManager := service.NewRoundTripperManager()
+			roundTripperManager := service.NewRoundTripperManager(nil)
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
@@ -788,7 +788,7 @@ func TestProviderOnMiddlewares(t *testing.T) {
 		},
 	})
 
-	roundTripperManager := service.NewRoundTripperManager()
+	roundTripperManager := service.NewRoundTripperManager(nil)
 	roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go
index 0c6697e38d..0afa695843 100644
--- a/pkg/server/routerfactory_test.go
+++ b/pkg/server/routerfactory_test.go
@@ -48,7 +48,7 @@ func TestReuseService(t *testing.T) {
 		),
 	)
 
-	roundTripperManager := service.NewRoundTripperManager()
+	roundTripperManager := service.NewRoundTripperManager(nil)
 	roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 	managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
 	tlsManager := tls.NewManager()
@@ -184,7 +184,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
 				},
 			}
 
-			roundTripperManager := service.NewRoundTripperManager()
+			roundTripperManager := service.NewRoundTripperManager(nil)
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 			managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
 			tlsManager := tls.NewManager()
@@ -225,7 +225,7 @@ func TestInternalServices(t *testing.T) {
 		),
 	)
 
-	roundTripperManager := service.NewRoundTripperManager()
+	roundTripperManager := service.NewRoundTripperManager(nil)
 	roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 	managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
 	tlsManager := tls.NewManager()
diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go
index a057166aba..37d5436740 100644
--- a/pkg/server/service/roundtripper_test.go
+++ b/pkg/server/service/roundtripper_test.go
@@ -1,14 +1,24 @@
 package service
 
 import (
+	"crypto/rand"
+	"crypto/rsa"
 	"crypto/tls"
 	"crypto/x509"
+	"crypto/x509/pkix"
+	"math/big"
 	"net"
 	"net/http"
 	"net/http/httptest"
+	"net/url"
 	"sync/atomic"
 	"testing"
+	"time"
 
+	"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
+	"github.com/spiffe/go-spiffe/v2/spiffeid"
+	"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
+	"github.com/spiffe/go-spiffe/v2/svid/x509svid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/traefik/traefik/v2/pkg/config/dynamic"
@@ -129,7 +139,7 @@ func TestKeepConnectionWhenSameConfiguration(t *testing.T) {
 	srv.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
 	srv.StartTLS()
 
-	rtManager := NewRoundTripperManager()
+	rtManager := NewRoundTripperManager(nil)
 
 	dynamicConf := map[string]*dynamic.ServersTransport{
 		"test": {
@@ -197,7 +207,7 @@ func TestMTLS(t *testing.T) {
 	}
 	srv.StartTLS()
 
-	rtManager := NewRoundTripperManager()
+	rtManager := NewRoundTripperManager(nil)
 
 	dynamicConf := map[string]*dynamic.ServersTransport{
 		"test": {
@@ -228,6 +238,141 @@ func TestMTLS(t *testing.T) {
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 }
 
+func TestSpiffeMTLS(t *testing.T) {
+	srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		rw.WriteHeader(http.StatusOK)
+	}))
+
+	trustDomain := spiffeid.RequireTrustDomainFromString("spiffe://traefik.test")
+
+	pki, err := newFakeSpiffePKI(trustDomain)
+	require.NoError(t, err)
+
+	clientSVID, err := pki.genSVID(spiffeid.RequireFromPath(trustDomain, "/client"))
+	require.NoError(t, err)
+
+	serverSVID, err := pki.genSVID(spiffeid.RequireFromPath(trustDomain, "/server"))
+	require.NoError(t, err)
+
+	serverSource := fakeSpiffeSource{
+		svid:   serverSVID,
+		bundle: pki.bundle,
+	}
+
+	// go-spiffe's `tlsconfig.MTLSServerConfig` (that should be used here) does not set a certificate on
+	// the returned `tls.Config` and relies instead on `GetCertificate` being always called.
+	// But it turns out that `StartTLS` from `httptest.Server`, enforces a default certificate
+	// if no certificate is previously set on the configured TLS config.
+	// It makes the test server always serve the httptest default certificate, and not the SPIFFE certificate,
+	// as GetCertificate is in that case never called (there's a default cert, and SNI is not used).
+	// To bypass this issue, we're manually extracting the server ceritificate from the server SVID
+	// and use another initialization method that forces serving the server SPIFFE certificate.
+	serverCert, err := tlsconfig.GetCertificate(&serverSource)(nil)
+	require.NoError(t, err)
+	srv.TLS = tlsconfig.MTLSWebServerConfig(
+		serverCert,
+		&serverSource,
+		tlsconfig.AuthorizeAny(),
+	)
+	srv.StartTLS()
+	defer srv.Close()
+
+	clientSource := fakeSpiffeSource{
+		svid:   clientSVID,
+		bundle: pki.bundle,
+	}
+
+	testCases := []struct {
+		desc             string
+		config           dynamic.Spiffe
+		clientSource     SpiffeX509Source
+		wantStatusCode   int
+		wantErrorMessage string
+	}{
+		{
+			desc:           "supports SPIFFE mTLS",
+			config:         dynamic.Spiffe{},
+			clientSource:   &clientSource,
+			wantStatusCode: http.StatusOK,
+		},
+		{
+			desc: "allows expected server SPIFFE ID",
+			config: dynamic.Spiffe{
+				IDs: []string{"spiffe://traefik.test/server"},
+			},
+			clientSource:   &clientSource,
+			wantStatusCode: http.StatusOK,
+		},
+		{
+			desc: "blocks unexpected server SPIFFE ID",
+			config: dynamic.Spiffe{
+				IDs: []string{"spiffe://traefik.test/not-server"},
+			},
+			clientSource:     &clientSource,
+			wantErrorMessage: `unexpected ID "spiffe://traefik.test/server"`,
+		},
+		{
+			desc: "allows expected server trust domain",
+			config: dynamic.Spiffe{
+				TrustDomain: "spiffe://traefik.test",
+			},
+			clientSource:   &clientSource,
+			wantStatusCode: http.StatusOK,
+		},
+		{
+			desc: "denies unexpected server trust domain",
+			config: dynamic.Spiffe{
+				TrustDomain: "spiffe://not-traefik.test",
+			},
+			clientSource:     &clientSource,
+			wantErrorMessage: `unexpected trust domain "traefik.test"`,
+		},
+		{
+			desc: "spiffe IDs allowlist takes precedence",
+			config: dynamic.Spiffe{
+				IDs:         []string{"spiffe://traefik.test/not-server"},
+				TrustDomain: "spiffe://not-traefik.test",
+			},
+			clientSource:     &clientSource,
+			wantErrorMessage: `unexpected ID "spiffe://traefik.test/server"`,
+		},
+		{
+			desc:             "raises an error when spiffe is enabled on the transport but no workloadapi address is given",
+			config:           dynamic.Spiffe{},
+			clientSource:     nil,
+			wantErrorMessage: `remote error: tls: bad certificate`,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			rtManager := NewRoundTripperManager(test.clientSource)
+
+			dynamicConf := map[string]*dynamic.ServersTransport{
+				"test": {
+					Spiffe: &test.config,
+				},
+			}
+
+			rtManager.Update(dynamicConf)
+
+			tr, err := rtManager.Get("test")
+			require.NoError(t, err)
+
+			client := http.Client{Transport: tr}
+
+			resp, err := client.Get(srv.URL)
+			if test.wantErrorMessage != "" {
+				assert.ErrorContains(t, err, test.wantErrorMessage)
+				return
+			}
+
+			require.NoError(t, err)
+			assert.Equal(t, test.wantStatusCode, resp.StatusCode)
+		})
+	}
+}
+
 func TestDisableHTTP2(t *testing.T) {
 	testCases := []struct {
 		desc          string
@@ -269,7 +414,7 @@ func TestDisableHTTP2(t *testing.T) {
 			srv.EnableHTTP2 = test.serverHTTP2
 			srv.StartTLS()
 
-			rtManager := NewRoundTripperManager()
+			rtManager := NewRoundTripperManager(nil)
 
 			dynamicConf := map[string]*dynamic.ServersTransport{
 				"test": {
@@ -293,3 +438,116 @@ func TestDisableHTTP2(t *testing.T) {
 		})
 	}
 }
+
+// fakeSpiffePKI simulates a SPIFFE aware PKI and allows generating multiple valid SVIDs.
+type fakeSpiffePKI struct {
+	caPrivateKey *rsa.PrivateKey
+
+	bundle *x509bundle.Bundle
+}
+
+func newFakeSpiffePKI(trustDomain spiffeid.TrustDomain) (fakeSpiffePKI, error) {
+	caPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		return fakeSpiffePKI{}, err
+	}
+
+	caTemplate := x509.Certificate{
+		SerialNumber: big.NewInt(2000),
+		Subject: pkix.Name{
+			Organization: []string{"spiffe"},
+		},
+		URIs:         []*url.URL{spiffeid.RequireFromPath(trustDomain, "/ca").URL()},
+		NotBefore:    time.Now(),
+		NotAfter:     time.Now().Add(time.Hour),
+		SubjectKeyId: []byte("ca"),
+		KeyUsage: x509.KeyUsageCertSign |
+			x509.KeyUsageCRLSign,
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+		PublicKey:             caPrivateKey.Public(),
+	}
+	if err != nil {
+		return fakeSpiffePKI{}, err
+	}
+
+	caCertDER, err := x509.CreateCertificate(
+		rand.Reader,
+		&caTemplate,
+		&caTemplate,
+		caPrivateKey.Public(),
+		caPrivateKey,
+	)
+	if err != nil {
+		return fakeSpiffePKI{}, err
+	}
+
+	bundle, err := x509bundle.ParseRaw(
+		trustDomain,
+		caCertDER,
+	)
+	if err != nil {
+		return fakeSpiffePKI{}, err
+	}
+
+	return fakeSpiffePKI{
+		bundle:       bundle,
+		caPrivateKey: caPrivateKey,
+	}, nil
+}
+
+func (f *fakeSpiffePKI) genSVID(id spiffeid.ID) (*x509svid.SVID, error) {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		return nil, err
+	}
+
+	template := x509.Certificate{
+		SerialNumber: big.NewInt(200001),
+		URIs:         []*url.URL{id.URL()},
+		NotBefore:    time.Now(),
+		NotAfter:     time.Now().Add(time.Hour),
+		SubjectKeyId: []byte("svid"),
+		KeyUsage: x509.KeyUsageKeyEncipherment |
+			x509.KeyUsageKeyAgreement |
+			x509.KeyUsageDigitalSignature,
+		ExtKeyUsage: []x509.ExtKeyUsage{
+			x509.ExtKeyUsageServerAuth,
+			x509.ExtKeyUsageClientAuth,
+		},
+		BasicConstraintsValid: true,
+		PublicKey:             privateKey.PublicKey,
+	}
+
+	certDER, err := x509.CreateCertificate(
+		rand.Reader,
+		&template,
+		f.bundle.X509Authorities()[0],
+		privateKey.Public(),
+		f.caPrivateKey,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	keyPKCS8, err := x509.MarshalPKCS8PrivateKey(privateKey)
+	if err != nil {
+		return nil, err
+	}
+
+	return x509svid.ParseRaw(certDER, keyPKCS8)
+}
+
+// fakeSpiffeSource allows retrieving staticly an SVID and its associated bundle.
+type fakeSpiffeSource struct {
+	bundle *x509bundle.Bundle
+	svid   *x509svid.SVID
+}
+
+func (s *fakeSpiffeSource) GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*x509bundle.Bundle, error) {
+	return s.bundle, nil
+}
+
+func (s *fakeSpiffeSource) GetX509SVID() (*x509svid.SVID, error) {
+	return s.svid, nil
+}