Skip to content

Commit

Permalink
Add ability to run Mongo proxy on separate listener (gravitational#9194)
Browse files Browse the repository at this point in the history
  • Loading branch information
smallinsky authored Dec 14, 2021
1 parent 3df447f commit f906831
Show file tree
Hide file tree
Showing 16 changed files with 188 additions and 1 deletion.
4 changes: 4 additions & 0 deletions api/client/webclient/webclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ type DBProxySettings struct {
MySQLListenAddr string `json:"mysql_listen_addr,omitempty"`
// MySQLPublicAddr is advertised to MySQL clients.
MySQLPublicAddr string `json:"mysql_public_addr,omitempty"`
// MongoListenAddr is Mongo proxy listen address.
MongoListenAddr string `json:"mongo_listen_addr,omitempty"`
// MongoPublicAddr is advertised to Mongo clients.
MongoPublicAddr string `json:"mongo_public_addr,omitempty"`
}

// AuthenticationSettings contains information about server authentication
Expand Down
3 changes: 3 additions & 0 deletions api/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ type Profile struct {
// MySQLProxyAddr is the host:port the MySQL proxy can be accessed at.
MySQLProxyAddr string `yaml:"mysql_proxy_addr,omitempty"`

// MongoProxyAddr is the host:port the Mongo proxy can be accessed at.
MongoProxyAddr string `yaml:"mongo_proxy_addr,omitempty"`

// Username is the Teleport username for the client.
Username string `yaml:"user,omitempty"`

Expand Down
30 changes: 30 additions & 0 deletions integration/db_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,36 @@ func TestDatabaseAccessPostgresSeparateListener(t *testing.T) {
require.NoError(t, err)
}

// TestDatabaseAccessMongoSeparateListener tests mongo proxy listener running on separate port.
func TestDatabaseAccessMongoSeparateListener(t *testing.T) {
pack := setupDatabaseTest(t,
withPortSetupDatabaseTest(separateMongoPortSetup),
)

// Connect to the database service in root cluster.
client, err := mongodb.MakeTestClient(context.Background(), common.TestClientConfig{
AuthClient: pack.root.cluster.GetSiteAPI(pack.root.cluster.Secrets.SiteName),
AuthServer: pack.root.cluster.Process.GetAuthServer(),
Address: net.JoinHostPort(Loopback, pack.root.cluster.GetPortMongo()),
Cluster: pack.root.cluster.Secrets.SiteName,
Username: pack.root.user.GetName(),
RouteToDatabase: tlsca.RouteToDatabase{
ServiceName: pack.root.mongoService.Name,
Protocol: pack.root.mongoService.Protocol,
Username: "admin",
},
})
require.NoError(t, err)

// Execute a query.
_, err = client.Database("test").Collection("test").Find(context.Background(), bson.M{})
require.NoError(t, err)

// Disconnect.
err = client.Disconnect(context.Background())
require.NoError(t, err)
}

func waitForAuditEventTypeWithBackoff(t *testing.T, cli *auth.Server, startTime time.Time, eventType string) []apievents.AuditEvent {
max := time.Second
timeout := time.After(max)
Expand Down
4 changes: 4 additions & 0 deletions integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,10 @@ func (i *TeleInstance) GenerateConfig(t *testing.T, trustedSecrets []*InstanceSe
// Postgres proxy port was configured on a separate listener.
tconf.Proxy.PostgresAddr.Addr = net.JoinHostPort(i.Hostname, i.GetPortPostgres())
}
if i.Mongo != nil {
// Mongo proxy port was configured on a separate listener.
tconf.Proxy.MongoAddr.Addr = net.JoinHostPort(i.Hostname, i.GetPortMongo())
}
}
tconf.AuthServers = append(tconf.AuthServers, tconf.Auth.SSHAddr)
tconf.Auth.StorageConfig = backend.Config{
Expand Down
14 changes: 14 additions & 0 deletions integration/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ func separatePostgresPortSetup() *InstancePorts {
}
}

func separateMongoPortSetup() *InstancePorts {
return &InstancePorts{
Web: newInstancePort(),
SSH: newInstancePort(),
Auth: newInstancePort(),
SSHProxy: newInstancePort(),
ReverseTunnel: newInstancePort(),
MySQL: newInstancePort(),
Mongo: newInstancePort(),
}
}

type InstancePorts struct {
Host string
Web *InstancePort
Expand All @@ -110,6 +122,7 @@ type InstancePorts struct {
ReverseTunnel *InstancePort
MySQL *InstancePort
Postgres *InstancePort
Mongo *InstancePort

isSinglePortSetup bool
}
Expand All @@ -121,6 +134,7 @@ func (i *InstancePorts) GetPortProxy() string { return i.SSHProxy.String
func (i *InstancePorts) GetPortWeb() string { return i.Web.String() }
func (i *InstancePorts) GetPortMySQL() string { return i.MySQL.String() }
func (i *InstancePorts) GetPortPostgres() string { return i.Postgres.String() }
func (i *InstancePorts) GetPortMongo() string { return i.Mongo.String() }
func (i *InstancePorts) GetPortReverseTunnel() string { return i.ReverseTunnel.String() }

func (i *InstancePorts) GetSSHAddr() string {
Expand Down
36 changes: 35 additions & 1 deletion lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ type Config struct {
// PostgresProxyAddr is the host:port the Postgres proxy can be accessed at.
PostgresProxyAddr string

// MongoProxyAddr is the host:port the Mongo proxy can be accessed at.
MongoProxyAddr string

// MySQLProxyAddr is the host:port the MySQL proxy can be accessed at.
MySQLProxyAddr string

Expand Down Expand Up @@ -800,6 +803,7 @@ func (c *Config) LoadProfile(profileDir string, proxyName string) error {
c.SSHProxyAddr = cp.SSHProxyAddr
c.PostgresProxyAddr = cp.PostgresProxyAddr
c.MySQLProxyAddr = cp.MySQLProxyAddr
c.MongoProxyAddr = cp.MongoProxyAddr
c.TLSRoutingEnabled = cp.TLSRoutingEnabled

c.LocalForwardPorts, err = ParsePortForwardSpec(cp.ForwardedPorts)
Expand Down Expand Up @@ -831,6 +835,7 @@ func (c *Config) SaveProfile(dir string, makeCurrent bool) error {
cp.KubeProxyAddr = c.KubeProxyAddr
cp.PostgresProxyAddr = c.PostgresProxyAddr
cp.MySQLProxyAddr = c.MySQLProxyAddr
cp.MongoProxyAddr = c.MongoProxyAddr
cp.ForwardedPorts = c.LocalForwardPorts.String()
cp.SiteName = c.SiteName
cp.TLSRoutingEnabled = c.TLSRoutingEnabled
Expand Down Expand Up @@ -996,6 +1001,17 @@ func (c *Config) PostgresProxyHostPort() (string, int) {
return c.WebProxyHostPort()
}

// MongoProxyHostPort returns the host and port of Mongo proxy.
func (c *Config) MongoProxyHostPort() (string, int) {
if c.MongoProxyAddr != "" {
addr, err := utils.ParseAddr(c.MongoProxyAddr)
if err == nil {
return addr.Host(), addr.Port(defaults.MongoListenPort)
}
}
return c.WebProxyHostPort()
}

// MySQLProxyHostPort returns the host and port of MySQL proxy.
func (c *Config) MySQLProxyHostPort() (string, int) {
if c.MySQLProxyAddr != "" {
Expand All @@ -1016,7 +1032,7 @@ func (c *Config) DatabaseProxyHostPort(db tlsca.RouteToDatabase) (string, int) {
case defaults.ProtocolMySQL:
return c.MySQLProxyHostPort()
case defaults.ProtocolMongoDB:
return c.WebProxyHostPort()
return c.MongoProxyHostPort()
}
return c.WebProxyHostPort()
}
Expand Down Expand Up @@ -2663,6 +2679,24 @@ func (tc *TeleportClient) applyProxySettings(proxySettings webclient.ProxySettin
tc.PostgresProxyAddr = net.JoinHostPort(webProxyHost, strconv.Itoa(webProxyPort))
}

// Read Mongo proxy settings.
switch {
case proxySettings.DB.MongoPublicAddr != "":
addr, err := utils.ParseAddr(proxySettings.DB.MongoPublicAddr)
if err != nil {
return trace.BadParameter("failed to parse Mongo public address received from server: %q, contact your administrator for help",
proxySettings.DB.MongoPublicAddr)
}
tc.MongoProxyAddr = net.JoinHostPort(addr.Host(), strconv.Itoa(addr.Port(tc.WebProxyPort())))
case proxySettings.DB.MongoListenAddr != "":
addr, err := utils.ParseAddr(proxySettings.DB.MongoListenAddr)
if err != nil {
return trace.BadParameter("failed to parse Mongo listen address received from server: %q, contact your administrator for help",
proxySettings.DB.MongoListenAddr)
}
tc.MongoProxyAddr = net.JoinHostPort(tc.WebProxyHost(), strconv.Itoa(addr.Port(defaults.MongoListenPort)))
}

// Read MySQL proxy settings if enabled on the server.
switch {
case proxySettings.DB.MySQLPublicAddr != "":
Expand Down
18 changes: 18 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,13 @@ func applyProxyConfig(fc *FileConfig, cfg *service.Config) error {
}
cfg.Proxy.PostgresAddr = *addr
}
if fc.Proxy.MongoAddr != "" {
addr, err := utils.ParseHostPortAddr(fc.Proxy.MongoAddr, int(defaults.MongoListenPort))
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.MongoAddr = *addr
}

// This is the legacy format. Continue to support it forever, but ideally
// users now use the list format below.
Expand Down Expand Up @@ -815,6 +822,17 @@ func applyProxyConfig(fc *FileConfig, cfg *service.Config) error {
cfg.Proxy.MySQLPublicAddrs = addrs
}

if len(fc.Proxy.MongoPublicAddr) != 0 {
if fc.Proxy.MongoAddr == "" {
return trace.BadParameter("mongo_listen_addr must be set when mongo_public_addr is set")
}
addrs, err := utils.AddrsFromStrings(fc.Proxy.MongoPublicAddr, defaults.MongoListenPort)
if err != nil {
return trace.Wrap(err)
}
cfg.Proxy.MongoPublicAddrs = addrs
}

acme, err := fc.Proxy.ACME.Parse()
if err != nil {
return trace.Wrap(err)
Expand Down
3 changes: 3 additions & 0 deletions lib/config/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,10 +679,13 @@ func TestApplyConfig(t *testing.T) {
require.Equal(t, "tcp://webhost:3080", cfg.Proxy.WebAddr.FullAddress())
require.Equal(t, "tcp://tunnelhost:1001", cfg.Proxy.ReverseTunnelListenAddr.FullAddress())
require.Equal(t, "tcp://webhost:3336", cfg.Proxy.MySQLAddr.FullAddress())
require.Equal(t, "tcp://webhost:27017", cfg.Proxy.MongoAddr.FullAddress())
require.Len(t, cfg.Proxy.PostgresPublicAddrs, 1)
require.Equal(t, "tcp://postgres.example:5432", cfg.Proxy.PostgresPublicAddrs[0].FullAddress())
require.Len(t, cfg.Proxy.MySQLPublicAddrs, 1)
require.Equal(t, "tcp://mysql.example:3306", cfg.Proxy.MySQLPublicAddrs[0].FullAddress())
require.Len(t, cfg.Proxy.MongoPublicAddrs, 1)
require.Equal(t, "tcp://mongo.example:27017", cfg.Proxy.MongoPublicAddrs[0].FullAddress())

require.Equal(t, "tcp://127.0.0.1:3000", cfg.DiagnosticAddr.FullAddress())

Expand Down
6 changes: 6 additions & 0 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,12 @@ type Proxy struct {
// PostgresPublicAddr is the hostport the proxy advertises for Postgres
// client connections.
PostgresPublicAddr apiutils.Strings `yaml:"postgres_public_addr,omitempty"`

// MongoAddr is Mongo proxy listen address.
MongoAddr string `yaml:"mongo_listen_addr,omitempty"`
// MongoPublicAddr is the hostport the proxy advertises for Mongo
// client connections.
MongoPublicAddr apiutils.Strings `yaml:"mongo_public_addr,omitempty"`
}

// ACME configures ACME protocol - automatic X.509 certificates
Expand Down
2 changes: 2 additions & 0 deletions lib/config/testdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ proxy_service:
postgres_public_addr: postgres.example:5432
mysql_listen_addr: webhost:3336
mysql_public_addr: mysql.example:3306
mongo_listen_addr: webhost:27017
mongo_public_addr: mongo.example:27017
`

// NoServicesConfigString is a configuration file with no services enabled
Expand Down
3 changes: 3 additions & 0 deletions lib/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ const (
// PostgresListenPort is the default listen port for PostgreSQL proxy.
PostgresListenPort = 5432

// MongoListenPort is the default listen port for Mongo proxy.
MongoListenPort = 27017

// MetricsListenPort is the default listen port for the metrics service.
MetricsListenPort = 3081

Expand Down
7 changes: 7 additions & 0 deletions lib/service/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ type ProxyConfig struct {
// PostgresAddr is address of Postgres proxy.
PostgresAddr utils.NetAddr

// MongoAddr is address of Mongo proxy.
MongoAddr utils.NetAddr

Limiter limiter.Config

// PublicAddrs is a list of the public addresses the proxy advertises
Expand All @@ -386,6 +389,10 @@ type ProxyConfig struct {
// advertises for MySQL clients.
MySQLPublicAddrs []utils.NetAddr

// MongoPublicAddrs is a list of the public addresses the proxy
// advertises for Mongo clients.
MongoPublicAddrs []utils.NetAddr

// Kube specifies kubernetes proxy configuration
Kube KubeProxyConfig

Expand Down
1 change: 1 addition & 0 deletions lib/service/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var (
listenerProxyTunnel = listenerType(teleport.Component(teleport.ComponentProxy, "tunnel"))
listenerProxyMySQL = listenerType(teleport.Component(teleport.ComponentProxy, "mysql"))
listenerProxyPostgres = listenerType(teleport.Component(teleport.ComponentProxy, "postgres"))
listenerProxyMongo = listenerType(teleport.Component(teleport.ComponentProxy, "mongo"))
listenerMetrics = listenerType(teleport.ComponentMetrics)
listenerWindowsDesktop = listenerType(teleport.ComponentWindowsDesktop)
)
Expand Down
7 changes: 7 additions & 0 deletions lib/service/proxy_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func (p *proxySettings) buildProxySettings(proxyListenerMode types.ProxyListener
proxySettings.DB.PostgresListenAddr = p.cfg.Proxy.PostgresAddr.String()
}

if !p.cfg.Proxy.MongoAddr.IsEmpty() {
proxySettings.DB.MongoListenAddr = p.cfg.Proxy.MongoAddr.String()
}

if p.cfg.Proxy.Kube.Enabled {
proxySettings.Kube.ListenAddr = p.cfg.Proxy.Kube.ListenAddr.String()
}
Expand Down Expand Up @@ -117,6 +121,9 @@ func (p *proxySettings) setProxyPublicAddressesSettings(settings *webclient.Prox
if len(p.cfg.Proxy.MySQLPublicAddrs) > 0 {
settings.DB.MySQLPublicAddr = p.cfg.Proxy.MySQLPublicAddrs[0].String()
}
if len(p.cfg.Proxy.MongoPublicAddrs) > 0 {
settings.DB.MongoPublicAddr = p.cfg.Proxy.MongoPublicAddrs[0].String()
}
settings.DB.PostgresPublicAddr = p.getPostgresPublicAddr()
}

Expand Down
24 changes: 24 additions & 0 deletions lib/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2526,6 +2526,8 @@ type dbListeners struct {
postgres net.Listener
// mysql serves MySQL clients.
mysql net.Listener
// mongo serves Mongo clients.
mongo net.Listener
// tls serves database clients that use plain TLS handshake.
tls net.Listener
}
Expand All @@ -2546,6 +2548,9 @@ func (l *dbListeners) Close() {
if l.tls != nil {
l.tls.Close()
}
if l.mongo != nil {
l.mongo.Close()
}
}

// Close closes all proxy listeners.
Expand Down Expand Up @@ -2603,6 +2608,15 @@ func (process *TeleportProcess) setupProxyListeners() (*proxyListeners, error) {
listeners.db.mysql = listener
}

if !cfg.Proxy.MongoAddr.IsEmpty() && !cfg.Proxy.DisableDatabaseProxy {
process.log.Debugf("Setup Proxy: Mongo proxy address: %v.", cfg.Proxy.MongoAddr.Addr)
listener, err := process.importOrCreateListener(listenerProxyMongo, cfg.Proxy.MongoAddr.Addr)
if err != nil {
return nil, trace.Wrap(err)
}
listeners.db.mongo = listener
}

switch {
case cfg.Proxy.DisableWebService && cfg.Proxy.DisableReverseTunnel:
process.log.Debugf("Setup Proxy: Reverse tunnel proxy and web proxy are disabled.")
Expand Down Expand Up @@ -3170,6 +3184,16 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
return nil
})
}

if listeners.db.mongo != nil {
process.RegisterCriticalFunc("proxy.db.mongo", func() error {
log.Infof("Starting Database Mongo proxy server on %v.", cfg.Proxy.MongoAddr.Addr)
if err := dbProxyServer.ServeMongo(listeners.db.mongo, tlsConfigWeb.Clone()); err != nil {
log.WithError(err).Warn("Database Mongo proxy server exited with error.")
}
return nil
})
}
}

var alpnServer *alpnproxy.Proxy
Expand Down
27 changes: 27 additions & 0 deletions lib/srv/db/proxyserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,33 @@ func (s *ProxyServer) ServeMySQL(listener net.Listener) error {
}
}

// ServeMongo starts accepting Mongo client connections.
func (s *ProxyServer) ServeMongo(listener net.Listener, tlsConfig *tls.Config) error {
s.log.Debug("Started Mongo proxy.")
defer s.log.Debug("Mongo proxy exited.")
for {
clientConn, err := listener.Accept()
if err != nil {
if utils.IsOKNetworkError(err) || trace.IsConnectionProblem(err) {
return nil
}
return trace.Wrap(err)
}
go func() {
defer clientConn.Close()
tlsConn := tls.Server(clientConn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
s.log.WithError(err).Error("Mongo TLS handshake failed.")
return
}
err := s.handleConnection(tlsConn)
if err != nil {
s.log.WithError(err).Error("Failed to handle Mongo client connection.")
}
}()
}
}

// ServeTLS starts accepting database connections that use plain TLS connection.
func (s *ProxyServer) ServeTLS(listener net.Listener) error {
s.log.Debug("Started database TLS proxy.")
Expand Down

0 comments on commit f906831

Please sign in to comment.