Skip to content

Commit

Permalink
Elasticsearch support (#15768)
Browse files Browse the repository at this point in the history
Co-authored-by: Anton Miniailo <anton@goteleport.com>
Co-authored-by: Marek Smoliński <marek@goteleport.com>
  • Loading branch information
3 people authored Sep 5, 2022
1 parent e3a9a7c commit 44b89c7
Show file tree
Hide file tree
Showing 14 changed files with 692 additions and 12 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/denisenkom/go-mssqldb v0.11.0
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc
github.com/dustin/go-humanize v1.0.0
github.com/elastic/go-elasticsearch/v8 v8.4.0
github.com/flynn/hid v0.0.0-20190502022136-f1b9b6cc019a
github.com/flynn/u2f v0.0.0-20180613185708-15554eb68e5d
github.com/fsouza/fake-gcs-server v1.19.5
Expand Down Expand Up @@ -184,6 +185,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elastic/elastic-transport-go/v8 v8.1.0 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/elastic-transport-go/v8 v8.1.0 h1:NeqEz1ty4RQz+TVbUrpSU7pZ48XkzGWQj02k5koahIE=
github.com/elastic/elastic-transport-go/v8 v8.1.0/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI=
github.com/elastic/go-elasticsearch/v8 v8.4.0 h1:Rn1mcqaIMcNT43hnx2H62cIFZ+B6mjWtzj85BDKrvCE=
github.com/elastic/go-elasticsearch/v8 v8.4.0/go.mod h1:yY52i2Vj0unLz+N3Nwx1gM5LXwoj3h2dgptNGBYkMLA=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
Expand Down
27 changes: 27 additions & 0 deletions lib/client/db/dbcmd/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const (
mssqlBin = "mssql-cli"
// snowsqlBin is the Snowflake client program name.
snowsqlBin = "snowsql"
// curlBin is the path to `curl`, which is used as Elasticsearch client.
curlBin = "curl"
)

// Execer is an abstraction of Go's exec module, as this one doesn't specify any interfaces.
Expand Down Expand Up @@ -173,6 +175,9 @@ func (c *CLICommandBuilder) GetConnectCommand() (*exec.Cmd, error) {

case defaults.ProtocolSnowflake:
return c.getSnowflakeCommand(), nil

case defaults.ProtocolElasticsearch:
return c.getElasticsearchCommand(), nil
}

return nil, trace.BadParameter("unsupported database protocol: %v", c.db)
Expand Down Expand Up @@ -505,6 +510,28 @@ func (c *CLICommandBuilder) getSnowflakeCommand() *exec.Cmd {
return cmd
}

func (c *CLICommandBuilder) getElasticsearchCommand() *exec.Cmd {
if c.options.noTLS {
return c.options.exe.Command(curlBin, fmt.Sprintf("http://%v:%v/", c.host, c.port))
}

args := []string{
fmt.Sprintf("https://%v:%v/", c.host, c.port),
"--key", c.profile.KeyPath(),
"--cert", c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName),
}

if c.tc.InsecureSkipVerify {
args = append(args, "--insecure")
}

if c.options.caPath != "" {
args = append(args, []string{"--cacert", c.options.caPath}...)
}

return c.options.exe.Command(curlBin, args...)
}

type connectionCommandOpts struct {
localProxyPort int
localProxyHost string
Expand Down
3 changes: 3 additions & 0 deletions lib/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ const (
ProtocolSQLServer = "sqlserver"
// ProtocolSnowflake is the Snowflake REST database protocol.
ProtocolSnowflake = "snowflake"
// ProtocolElasticsearch is the Elasticsearch database protocol.
ProtocolElasticsearch = "elasticsearch"
)

// DatabaseProtocols is a list of all supported database protocols.
Expand All @@ -493,6 +495,7 @@ var DatabaseProtocols = []string{
ProtocolRedis,
ProtocolSnowflake,
ProtocolSQLServer,
ProtocolElasticsearch,
}

// ReadableDatabaseProtocol returns a more human readable string of the
Expand Down
4 changes: 4 additions & 0 deletions lib/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ func TestSetupProxyTLSConfig(t *testing.T) {
"teleport-redis-ping",
"teleport-sqlserver-ping",
"teleport-snowflake-ping",
"teleport-elasticsearch-ping",
"teleport-proxy-ssh",
"teleport-reversetunnel",
"teleport-auth@",
Expand All @@ -511,6 +512,7 @@ func TestSetupProxyTLSConfig(t *testing.T) {
"teleport-redis",
"teleport-sqlserver",
"teleport-snowflake",
"teleport-elasticsearch",
},
},
{
Expand All @@ -523,6 +525,7 @@ func TestSetupProxyTLSConfig(t *testing.T) {
"teleport-redis-ping",
"teleport-sqlserver-ping",
"teleport-snowflake-ping",
"teleport-elasticsearch-ping",
// Ensure h2 has precedence over http/1.1.
"h2",
"http/1.1",
Expand All @@ -536,6 +539,7 @@ func TestSetupProxyTLSConfig(t *testing.T) {
"teleport-redis",
"teleport-sqlserver",
"teleport-snowflake",
"teleport-elasticsearch",
},
},
}
Expand Down
7 changes: 7 additions & 0 deletions lib/srv/alpnproxy/common/protocols.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const (
// ProtocolSnowflake is TLS ALPN protocol value used to indicate Snowflake protocol.
ProtocolSnowflake Protocol = "teleport-snowflake"

// ProtocolElasticsearch is TLS ALPN protocol value used to indicate Elasticsearch protocol.
ProtocolElasticsearch Protocol = "teleport-elasticsearch"

// ProtocolProxySSH is TLS ALPN protocol value used to indicate Proxy SSH protocol.
ProtocolProxySSH Protocol = "teleport-proxy-ssh"

Expand Down Expand Up @@ -125,6 +128,8 @@ func ToALPNProtocol(dbProtocol string) (Protocol, error) {
return ProtocolSQLServer, nil
case defaults.ProtocolSnowflake:
return ProtocolSnowflake, nil
case defaults.ProtocolElasticsearch:
return ProtocolElasticsearch, nil
default:
return "", trace.NotImplemented("%q protocol is not supported", dbProtocol)
}
Expand All @@ -141,6 +146,7 @@ func IsDBTLSProtocol(protocol Protocol) bool {
ProtocolRedisDB,
ProtocolSQLServer,
ProtocolSnowflake,
ProtocolElasticsearch,
}

return slices.Contains(
Expand All @@ -157,6 +163,7 @@ var DatabaseProtocols = []Protocol{
ProtocolRedisDB,
ProtocolSQLServer,
ProtocolSnowflake,
ProtocolElasticsearch,
}

// ProtocolsWithPingSupport is the list of protocols that Ping connection is
Expand Down
58 changes: 49 additions & 9 deletions lib/srv/db/access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"time"

mssql "github.com/denisenkom/go-mssqldb"
elastic "github.com/elastic/go-elasticsearch/v8"
mysqlclient "github.com/go-mysql-org/go-mysql/client"
mysqllib "github.com/go-mysql-org/go-mysql/mysql"
goredis "github.com/go-redis/redis/v8"
Expand Down Expand Up @@ -61,6 +62,7 @@ import (
alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
"github.com/gravitational/teleport/lib/srv/db/cloud"
"github.com/gravitational/teleport/lib/srv/db/common"
"github.com/gravitational/teleport/lib/srv/db/elasticsearch"
"github.com/gravitational/teleport/lib/srv/db/mongodb"
"github.com/gravitational/teleport/lib/srv/db/mysql"
"github.com/gravitational/teleport/lib/srv/db/postgres"
Expand Down Expand Up @@ -1230,6 +1232,8 @@ type testContext struct {
sqlServer map[string]testSQLServer
// snowflake is a collection of Snowflake databases the test uses.
snowflake map[string]testSnowflake
// elasticsearch is a collection of Elasticsearch databases the test uses.
elasticsearch map[string]testElasticsearch
// clock to override clock in tests.
clock clockwork.FakeClock
}
Expand Down Expand Up @@ -1281,6 +1285,13 @@ type testSnowflake struct {
resource types.Database
}

type testElasticsearch struct {
// db is the test elasticsearch database server.
db *elasticsearch.TestServer
// resource is the resource representing this elasticsearch database.
resource types.Database
}

// startProxy starts all proxy services required to handle connections.
func (c *testContext) startProxy() {
// Start multiplexer.
Expand Down Expand Up @@ -1618,6 +1629,34 @@ func (c *testContext) snowflakeClient(ctx context.Context, teleportUser, dbServi
return db, proxy, nil
}

// elasticsearchClient returns an Elasticsearch test DB client.
func (c *testContext) elasticsearchClient(ctx context.Context, teleportUser, dbService, dbUser string) (*elastic.Client, *alpnproxy.LocalProxy, error) {
route := tlsca.RouteToDatabase{
ServiceName: dbService,
Protocol: defaults.ProtocolElasticsearch,
Username: dbUser,
}

proxy, err := c.startLocalALPNProxy(ctx, c.webListener.Addr().String(), teleportUser, route)
if err != nil {
return nil, nil, trace.Wrap(err)
}

db, err := elasticsearch.MakeTestClient(ctx, common.TestClientConfig{
AuthClient: c.authClient,
AuthServer: c.authServer,
Address: proxy.GetAddr(),
Cluster: c.clusterName,
Username: teleportUser,
RouteToDatabase: route,
})
if err != nil {
return nil, nil, trace.Wrap(err)
}

return db, proxy, nil
}

// createUserAndRole creates Teleport user and role with specified names
// and allowed database users/names properties.
func (c *testContext) createUserAndRole(ctx context.Context, t *testing.T, userName, roleName string, dbUsers, dbNames []string) (types.User, types.Role) {
Expand Down Expand Up @@ -1670,15 +1709,16 @@ func init() {

func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDatabaseOption) *testContext {
testCtx := &testContext{
clusterName: "root.example.com",
hostID: uuid.New().String(),
postgres: make(map[string]testPostgres),
mysql: make(map[string]testMySQL),
mongo: make(map[string]testMongoDB),
redis: make(map[string]testRedis),
sqlServer: make(map[string]testSQLServer),
snowflake: make(map[string]testSnowflake),
clock: clockwork.NewFakeClockAt(time.Now()),
clusterName: "root.example.com",
hostID: uuid.New().String(),
postgres: make(map[string]testPostgres),
mysql: make(map[string]testMySQL),
mongo: make(map[string]testMongoDB),
redis: make(map[string]testRedis),
sqlServer: make(map[string]testSQLServer),
snowflake: make(map[string]testSnowflake),
elasticsearch: make(map[string]testElasticsearch),
clock: clockwork.NewFakeClockAt(time.Now()),
}
t.Cleanup(func() { testCtx.Close() })

Expand Down
5 changes: 5 additions & 0 deletions lib/srv/db/common/role/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ func DatabaseRoleMatchers(dbProtocol string, user, database string) services.Rol
return services.RoleMatchers{
&services.DatabaseUserMatcher{User: user},
}
case defaults.ProtocolElasticsearch:
// Elasticsearch integration doesn't support schema access control.
return services.RoleMatchers{
&services.DatabaseUserMatcher{User: user},
}
default:
return services.RoleMatchers{
&services.DatabaseUserMatcher{User: user},
Expand Down
Loading

0 comments on commit 44b89c7

Please sign in to comment.