1
0
Fork 0

Support SPIFFE mTLS between Traefik and Backend servers

This commit is contained in:
Julien Levesy 2022-10-14 17:16:08 +02:00 committed by GitHub
parent 33f0aed5ea
commit b39ce8cc58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 736 additions and 24 deletions

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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,
}
}

View file

@ -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",

View file

@ -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

View file

@ -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
}

View file

@ -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,

View file

@ -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)

View file

@ -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()

View file

@ -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
}
}

View file

@ -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
}