Skip to content

Commit

Permalink
Derive dynamic cryptographic configurations from *service.Connector (
Browse files Browse the repository at this point in the history
…#43937)

* Derive dynamic cryptographic configurations from *service.Connector

* Clean up unused code

* Added missing godocs

* fix TextProxyGRPCServers
  • Loading branch information
espadolini authored Jul 25, 2024
1 parent 4da0d10 commit 7cb9cba
Show file tree
Hide file tree
Showing 24 changed files with 622 additions and 465 deletions.
26 changes: 9 additions & 17 deletions lib/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,6 @@ func (c *TLSServerConfig) CheckAndSetDefaults() error {
if c.TLS == nil {
return trace.BadParameter("missing parameter TLS")
}
c.TLS.ClientAuth = tls.VerifyClientCertIfGiven
if c.TLS.ClientCAs == nil {
return trace.BadParameter("missing parameter TLS.ClientCAs")
}
if c.TLS.RootCAs == nil {
return trace.BadParameter("missing parameter TLS.RootCAs")
}
if len(c.TLS.Certificates) == 0 {
return trace.BadParameter("missing parameter TLS.Certificates")
}
if c.GetClientCertificate == nil {
return trace.BadParameter("missing parameter GetClientCertificate")
}
Expand Down Expand Up @@ -210,9 +200,6 @@ func NewTLSServer(ctx context.Context, cfg TLSServerConfig) (*TLSServer, error)
authMiddleware.Wrap(apiServer)
// Wrap sets the next middleware in chain to the authMiddleware
limiter.WrapHandle(authMiddleware)
// force client auth if given
cfg.TLS.ClientAuth = tls.VerifyClientCertIfGiven
cfg.TLS.NextProtos = []string{http2.NextProtoTLS}

securityHeaderHandler := httplib.MakeSecurityHeaderHandler(limiter)
tracingHandler := httplib.MakeTracingHandler(securityHeaderHandler, teleport.ComponentAuth)
Expand All @@ -234,8 +221,13 @@ func NewTLSServer(ctx context.Context, cfg TLSServerConfig) (*TLSServer, error)
}),
}

tlsConfig := cfg.TLS.Clone()
// force client auth if given
tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
tlsConfig.NextProtos = []string{http2.NextProtoTLS}

server.clientTLSConfigGenerator, err = NewClientTLSConfigGenerator(ClientTLSConfigGeneratorConfig{
TLS: server.cfg.TLS.Clone(),
TLS: tlsConfig,
ClusterName: localClusterName.GetClusterName(),
PermitRemoteClusters: true,
AccessPoint: server.cfg.AccessPoint,
Expand All @@ -244,10 +236,10 @@ func NewTLSServer(ctx context.Context, cfg TLSServerConfig) (*TLSServer, error)
return nil, trace.Wrap(err)
}

server.cfg.TLS.GetConfigForClient = server.clientTLSConfigGenerator.GetConfigForClient
tlsConfig.GetConfigForClient = server.clientTLSConfigGenerator.GetConfigForClient

server.grpcServer, err = NewGRPCServer(GRPCServerConfig{
TLS: server.cfg.TLS,
TLS: tlsConfig,
Middleware: authMiddleware,
APIConfig: cfg.APIConfig,
UnaryInterceptors: authMiddleware.UnaryInterceptors(),
Expand All @@ -258,7 +250,7 @@ func NewTLSServer(ctx context.Context, cfg TLSServerConfig) (*TLSServer, error)
}

server.mux, err = multiplexer.NewTLSListener(multiplexer.TLSListenerConfig{
Listener: tls.NewListener(cfg.Listener, server.cfg.TLS),
Listener: tls.NewListener(cfg.Listener, tlsConfig),
ID: cfg.ID,
})
if err != nil {
Expand Down
26 changes: 17 additions & 9 deletions lib/kube/proxy/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,18 @@ type ForwarderConfig struct {
TracerProvider oteltrace.TracerProvider
// Tracer is used to start spans.
tracer oteltrace.Tracer
// ConnTLSConfig is the TLS client configuration to use when connecting to
// the upstream Teleport proxy or Kubernetes service when forwarding requests
// using the forward identity (i.e. proxy impersonating a user) method.
ConnTLSConfig *tls.Config
// GetConnTLSCertificate returns the TLS client certificate to use when
// connecting to the upstream Teleport proxy or Kubernetes service when
// forwarding requests using the forward identity (i.e. proxy impersonating
// a user) method. Paired with GetConnTLSRoots and ConnTLSCipherSuites to
// generate the correct [*tls.Config] on demand.
GetConnTLSCertificate utils.GetCertificateFunc
// GetConnTLSRoots returns the [*x509.CertPool] used to validate TLS
// connections to the upstream Teleport proxy or Kubernetes service.
GetConnTLSRoots utils.GetRootsFunc
// ConnTLSCipherSuites optionally contains a list of TLS ciphersuites to use
// when connecting to the upstream Teleport Proxy or Kubernetes service.
ConnTLSCipherSuites []uint16
// ClusterFeaturesGetter is a function that returns the Teleport cluster licensed features.
// It is used to determine if the cluster is licensed for Kubernetes usage.
ClusterFeatures ClusterFeaturesGetter
Expand Down Expand Up @@ -247,12 +255,12 @@ func (f *ForwarderConfig) CheckAndSetDefaults() error {
switch f.KubeServiceType {
case KubeService:
case ProxyService, LegacyProxyService:
if f.ConnTLSConfig == nil {
return trace.BadParameter("missing parameter TLSConfig")
if f.GetConnTLSCertificate == nil {
return trace.BadParameter("missing parameter GetConnTLSCertificate")
}
if f.GetConnTLSRoots == nil {
return trace.BadParameter("missing parameter GetConnTLSRoots")
}
// Reset the ServerName to ensure that the proxy does not use the
// proxy's hostname as the SNI when connecting to the Kubernetes service.
f.ConnTLSConfig.ServerName = ""
default:
return trace.BadParameter("unknown value for KubeServiceType")
}
Expand Down
40 changes: 28 additions & 12 deletions lib/kube/proxy/forwarder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import (
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/utils/tlsutils"
"github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/testauthority"
Expand Down Expand Up @@ -1646,17 +1647,18 @@ func TestForwarderTLSConfigCAs(t *testing.T) {
clusterName := "leaf"

// Create a cert pool with the cert from fixtures.TLSCACertPEM
caCert, err := tlsutils.ParseCertificatePEM([]byte(fixtures.TLSCACertPEM))
require.NoError(t, err)
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM([]byte(fixtures.TLSCACertPEM))
certPool.AddCert(caCert)

// create the tls config used by the forwarder
originalTLSConfig := &tls.Config{}
// create the auth server mock client
clock := clockwork.NewFakeClock()
cl, err := newMockCSRClient(clock)
require.NoError(t, err)
cl.leafClusterName = clusterName

var getConnTLSRootsCalled bool
f := &Forwarder{
cfg: ForwarderConfig{
Keygen: testauthority.New(),
Expand All @@ -1665,24 +1667,38 @@ func TestForwarderTLSConfigCAs(t *testing.T) {
tracer: otel.Tracer(teleport.ComponentKube),
KubeServiceType: ProxyService,
CachingAuthClient: cl,
ConnTLSConfig: originalTLSConfig,

GetConnTLSCertificate: func() (*tls.Certificate, error) {
return nil, nil
},
GetConnTLSRoots: func() (*x509.CertPool, error) {
getConnTLSRootsCalled = true
return x509.NewCertPool(), nil
},
},
log: logrus.NewEntry(logrus.New()),
ctx: context.Background(),
}

// generate tlsConfig for the leaf cluster
tlsConfig, err := f.getTLSConfigForLeafCluster(clusterName)
require.NoError(t, err)
// ensure that the tlsConfig is a clone of the originalTLSConfig
require.NotSame(t, originalTLSConfig, tlsConfig, "expected tlsConfig to be different from originalTLSConfig")
// ensure that the tlsConfig has the certPool as the RootCAs
require.True(t, tlsConfig.RootCAs.Equal(certPool), "expected root CAs to be equal to certPool")
_ = tlsConfig.VerifyConnection(tls.ConnectionState{
ServerName: "nonempty",
PeerCertificates: []*x509.Certificate{
caCert,
},
})
require.False(t, getConnTLSRootsCalled)

// generate tlsConfig for the local cluster
_, localTLSConfig, err := f.newLocalClusterTransport(clusterName)
require.NoError(t, err)
// ensure that the localTLSConfig is a clone of the originalTLSConfig
require.NotSame(t, originalTLSConfig, localTLSConfig, "expected localTLSConfig pointer to be different from originalTLSConfig")
// ensure that the localTLSConfig doesn't have the certPool as the RootCAs
require.False(t, localTLSConfig.RootCAs.Equal(certPool), "root CAs should not include certPool")
_ = localTLSConfig.VerifyConnection(tls.ConnectionState{
ServerName: "nonempty",
PeerCertificates: []*x509.Certificate{
caCert,
},
})
require.True(t, getConnTLSRootsCalled)
}
10 changes: 0 additions & 10 deletions lib/kube/proxy/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,6 @@ func (c *TLSServerConfig) CheckAndSetDefaults() error {
if c.TLS == nil {
return trace.BadParameter("missing parameter TLS")
}
c.TLS.ClientAuth = tls.RequireAndVerifyClientCert
if c.TLS.ClientCAs == nil {
return trace.BadParameter("missing parameter TLS.ClientCAs")
}
if c.TLS.RootCAs == nil {
return trace.BadParameter("missing parameter TLS.RootCAs")
}
if len(c.TLS.Certificates) == 0 {
return trace.BadParameter("missing parameter TLS.Certificates")
}
if c.AccessPoint == nil {
return trace.BadParameter("missing parameter AccessPoint")
}
Expand Down
42 changes: 30 additions & 12 deletions lib/kube/proxy/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/reversetunnelclient"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
)

Expand Down Expand Up @@ -195,24 +195,31 @@ func (f *Forwarder) getTLSConfigForLeafCluster(clusterName string) (*tls.Config,
ctx, cancel := context.WithTimeout(f.ctx, 5*time.Second)
defer cancel()
// Get the host CA for the target cluster from Auth to ensure we trust the
// leaf proxy certificate.
hostCA, err := f.cfg.CachingAuthClient.GetCertAuthority(ctx, types.CertAuthID{
// leaf proxy certificate at the current time.
_, err := f.cfg.CachingAuthClient.GetCertAuthority(ctx, types.CertAuthID{
Type: types.HostCA,
DomainName: clusterName,
}, false)
if err != nil {
return nil, trace.Wrap(err)
}

pool := x509.NewCertPool()
for _, certAuthority := range services.GetTLSCerts(hostCA) {
if ok := pool.AppendCertsFromPEM(certAuthority); !ok {
return nil, trace.BadParameter("failed to append certificates, check that kubeconfig has correctly encoded certificate authority data")
tlsConfig := utils.TLSConfig(f.cfg.ConnTLSCipherSuites)
tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
tlsCert, err := f.cfg.GetConnTLSCertificate()
if err != nil {
return nil, trace.Wrap(err)
}
return tlsCert, nil
}
// Clone the TLS config and set the root CAs to the leaf host CA pool.
tlsConfig := f.cfg.ConnTLSConfig.Clone()
tlsConfig.RootCAs = pool
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyConnection = utils.VerifyConnectionWithRoots(func() (*x509.CertPool, error) {
pool, _, err := authclient.ClientCertPool(f.ctx, f.cfg.CachingAuthClient, clusterName, types.HostCA)
if err != nil {
return nil, trace.Wrap(err)
}
return pool, nil
})

return tlsConfig, nil
}
Expand Down Expand Up @@ -258,17 +265,28 @@ func (f *Forwarder) remoteClusterDialer(clusterName string) dialContextFunc {
// newLocalClusterTransport returns a new [http.Transport] (https://golang.org/pkg/net/http/#Transport)
// that can be used to dial Kubernetes Service in a local Teleport cluster.
func (f *Forwarder) newLocalClusterTransport(kubeClusterName string) (http.RoundTripper, *tls.Config, error) {
tlsConfig := utils.TLSConfig(f.cfg.ConnTLSCipherSuites)
tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
tlsCert, err := f.cfg.GetConnTLSCertificate()
if err != nil {
return nil, trace.Wrap(err)
}
return tlsCert, nil
}
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyConnection = utils.VerifyConnectionWithRoots(f.cfg.GetConnTLSRoots)

dialFn := f.localClusterDialer(kubeClusterName)
// Create a new HTTP/2 transport that will be used to dial the remote cluster.
h2Transport, err := newH2Transport(f.cfg.ConnTLSConfig, dialFn)
h2Transport, err := newH2Transport(tlsConfig, dialFn)
if err != nil {
return nil, nil, trace.Wrap(err)
}

return instrumentedRoundtripper(
f.cfg.KubeServiceType,
auth.NewImpersonatorRoundTripper(h2Transport),
), f.cfg.ConnTLSConfig.Clone(), nil
), tlsConfig.Clone(), nil
}

// localClusterDialer returns a dialer that can be used to dial Kubernetes Service
Expand Down
14 changes: 12 additions & 2 deletions lib/kube/proxy/utils_testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package proxy

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"net"
"net/http"
Expand Down Expand Up @@ -313,6 +315,9 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo
require.NoError(t, err)
proxyTLSConfig, err := proxyServerIdentity.TLSConfig(nil)
require.NoError(t, err)
require.Len(t, proxyTLSConfig.Certificates, 1)
require.NotNil(t, proxyTLSConfig.RootCAs)

// Create kubernetes service server.
testCtx.KubeProxy, err = NewTLSServer(TLSServerConfig{
ForwarderConfig: ForwarderConfig{
Expand Down Expand Up @@ -349,8 +354,13 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo
LockWatcher: testCtx.lockWatcher,
Clock: clockwork.NewRealClock(),
ClusterFeatures: features,
ConnTLSConfig: proxyTLSConfig.Clone(),
PROXYSigner: &multiplexer.PROXYSigner{},
GetConnTLSCertificate: func() (*tls.Certificate, error) {
return &proxyTLSConfig.Certificates[0], nil
},
GetConnTLSRoots: func() (*x509.CertPool, error) {
return proxyTLSConfig.RootCAs, nil
},
PROXYSigner: &multiplexer.PROXYSigner{},
},
TLS: proxyTLSConfig.Clone(),
AccessPoint: client,
Expand Down
Loading

0 comments on commit 7cb9cba

Please sign in to comment.