Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v9] Fix ALPN panic on empty db handler #10662

Merged
merged 4 commits into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion lib/srv/alpnproxy/common/protocols.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ limitations under the License.
package common

import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/trace"
"golang.org/x/crypto/acme"

"github.com/gravitational/teleport/lib/defaults"
)

// Protocol is the TLS ALPN protocol type.
Expand Down Expand Up @@ -105,3 +106,17 @@ func ToALPNProtocol(dbProtocol string) (Protocol, error) {
return "", trace.NotImplemented("%q protocol is not supported", dbProtocol)
}
}

// IsDBTLSProtocol returns if DB protocol has supported native TLS protocol.
// where connection can be TLS terminated on ALPN proxy side.
// For protocol like MySQL or Postgres where custom TLS implementation is used the incoming
// connection needs to be forwarded to proxy database service where custom TLS handler is invoked
// to terminated DB connection.
func IsDBTLSProtocol(protocol Protocol) bool {
switch protocol {
case ProtocolMongoDB, ProtocolRedisDB, ProtocolSQLServer:
return true
default:
return false
}
}
14 changes: 4 additions & 10 deletions lib/srv/alpnproxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ func (h *HandlerDecs) handle(ctx context.Context, conn net.Conn, info Connection
if h.HandlerWithConnInfo != nil {
return h.HandlerWithConnInfo(ctx, conn, info)
}
if h.Handler == nil {
return trace.BadParameter("failed to find ALPN handler for ALPN: %v, SNI %v", info.ALPN, info.SNI)
}
return h.Handler(ctx, conn)
}

Expand Down Expand Up @@ -434,15 +437,6 @@ func (p *Proxy) databaseHandlerWithTLSTermination(ctx context.Context, conn net.
return trace.Wrap(p.handleDatabaseConnection(ctx, tlsConn, info))
}

func isDBTLSProtocol(protocol common.Protocol) bool {
switch protocol {
case common.ProtocolMongoDB, common.ProtocolRedisDB:
return true
default:
return false
}
}

func (p *Proxy) getHandlerDescBaseOnClientHelloMsg(clientHelloInfo *tls.ClientHelloInfo) (*HandlerDecs, error) {
if shouldRouteToKubeService(clientHelloInfo.ServerName) {
if p.cfg.Router.kubeHandler == nil {
Expand All @@ -464,7 +458,7 @@ func (p *Proxy) getHandleDescBasedOnALPNVal(clientHelloInfo *tls.ClientHelloInfo

for _, v := range clientProtocols {
protocol := common.Protocol(v)
if isDBTLSProtocol(protocol) {
if common.IsDBTLSProtocol(protocol) {
return &HandlerDecs{
MatchFunc: MatchByProtocol(protocol),
HandlerWithConnInfo: p.databaseHandlerWithTLSTermination,
Expand Down
59 changes: 59 additions & 0 deletions lib/srv/alpnproxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,65 @@ func TestProxyTLSDatabaseHandler(t *testing.T) {
})
}

// TestProxyRouteToDatabase tests db connection with protocol registered without any handler.
// ALPN router leverages empty handler to route the connection to DBHandler
// based on TLS RouteToDatabase identity entry.
func TestProxyRouteToDatabase(t *testing.T) {
t.Parallel()
const (
databaseHandleResponse = "database handler response"
)

suite := NewSuite(t)
clientCert := mustGenCertSignedWithCA(t, suite.ca,
withIdentity(tlsca.Identity{
Username: "test-user",
Groups: []string{"test-group"},
RouteToDatabase: tlsca.RouteToDatabase{
ServiceName: "mongo-test-database",
},
}),
)

suite.router.AddDBTLSHandler(func(ctx context.Context, conn net.Conn) error {
defer conn.Close()
_, err := fmt.Fprint(conn, databaseHandleResponse)
require.NoError(t, err)
return nil
})
suite.router.Add(HandlerDecs{
MatchFunc: MatchByProtocol(common.ProtocolReverseTunnel),
})

suite.Start(t)

t.Run("dial with user certs with RouteToDatabase info", func(t *testing.T) {
conn, err := tls.Dial("tcp", suite.GetServerAddress(), &tls.Config{
NextProtos: []string{string(common.ProtocolReverseTunnel)},
RootCAs: suite.GetCertPool(),
ServerName: "localhost",
Certificates: []tls.Certificate{
clientCert,
},
})
require.NoError(t, err)
mustReadFromConnection(t, conn, databaseHandleResponse)
mustCloseConnection(t, conn)
})

t.Run("dial with no user certs", func(t *testing.T) {
conn, err := tls.Dial("tcp", suite.GetServerAddress(), &tls.Config{
NextProtos: []string{string(common.ProtocolReverseTunnel)},
RootCAs: suite.GetCertPool(),
ServerName: "localhost",
})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, conn.Close())
})
})
}

// TestLocalProxyPostgresProtocol tests Proxy Postgres connection forwarded by LocalProxy.
// Client connects to LocalProxy with raw connection where downstream Proxy connection is upgraded to TLS with
// ALPN value set to ProtocolPostgres.
Expand Down