Files
Nibras Ohin 9904cb7613 feat: added db vnet access to be embedded in tbot (#66503)
* feat: added db vnet access to be embedded in tbot

* chore: updated based on pr feedback

* chore: remove unneded check to validate the nubmer of db servers

* chore: moved get resources call to db/vnet package
2026-05-08 16:15:08 +00:00

203 lines
5.6 KiB
Go

// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db_test
import (
"context"
"log/slog"
"testing"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/defaults"
dbvnet "github.com/gravitational/teleport/lib/srv/db/vnet"
"github.com/gravitational/teleport/lib/vnet/db"
)
func TestParse(t *testing.T) {
myPostgresHash := dbvnet.DNSName("my-postgres")
myDBHash := dbvnet.DNSName("my-db")
tests := []struct {
name string
fqdn string
zone string
wantIdentifier string
wantOK bool
}{
{
name: "vnet_dns_name (hash) prefix",
fqdn: myPostgresHash + ".db.proxy.example.com.",
zone: "proxy.example.com",
wantIdentifier: myPostgresHash,
wantOK: true,
},
{
name: "different db hash",
fqdn: myDBHash + ".db.proxy.example.com.",
zone: "proxy.example.com",
wantIdentifier: myDBHash,
wantOK: true,
},
{
name: "literal database name is also accepted",
fqdn: "my-postgres.db.proxy.example.com.",
zone: "proxy.example.com",
wantIdentifier: "my-postgres",
wantOK: true,
},
{
name: "zone already fully qualified",
fqdn: myPostgresHash + ".db.proxy.example.com.",
zone: "proxy.example.com.",
wantIdentifier: myPostgresHash,
wantOK: true,
},
{
name: "no db infix",
fqdn: "app.proxy.example.com.",
zone: "proxy.example.com",
wantOK: false,
},
{
name: "wrong zone",
fqdn: myPostgresHash + ".db.other.example.com.",
zone: "proxy.example.com",
wantOK: false,
},
{
name: "empty prefix",
fqdn: ".db.proxy.example.com.",
zone: "proxy.example.com",
wantOK: false,
},
{
name: "only db infix and zone",
fqdn: "db.proxy.example.com.",
zone: "proxy.example.com",
wantOK: false,
},
{
name: "extra dotted prefix is rejected (no embedded user prefix)",
fqdn: "reader." + myPostgresHash + ".db.proxy.example.com.",
zone: "proxy.example.com",
wantOK: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := db.Parse(tt.fqdn, tt.zone)
require.Equal(t, tt.wantOK, ok)
if !tt.wantOK {
return
}
require.Equal(t, tt.wantIdentifier, got)
})
}
}
func TestPickMatch(t *testing.T) {
t.Parallel()
silent := slog.New(slog.DiscardHandler)
ctx := context.Background()
t.Run("empty servers returns no match", func(t *testing.T) {
got, ok := db.PickMatch(ctx, silent, "foo", nil)
require.False(t, ok)
require.Nil(t, got)
})
t.Run("single server with supported protocol", func(t *testing.T) {
got, ok := db.PickMatch(ctx, silent, "my-pg",
[]types.DatabaseServer{newServer(t, "my-pg", "agent-1", defaults.ProtocolPostgres)})
require.True(t, ok)
require.Equal(t, "my-pg", got.GetName())
})
t.Run("unsupported protocol returns no match", func(t *testing.T) {
got, ok := db.PickMatch(ctx, silent, "my-mongo",
[]types.DatabaseServer{newServer(t, "my-mongo", "agent-1", defaults.ProtocolMongoDB)})
require.False(t, ok)
require.Nil(t, got)
})
t.Run("HA db_servers for same database dedupe to one", func(t *testing.T) {
got, ok := db.PickMatch(ctx, silent, "my-pg", []types.DatabaseServer{
newServer(t, "my-pg", "agent-1", defaults.ProtocolPostgres),
newServer(t, "my-pg", "agent-2", defaults.ProtocolPostgres),
})
require.True(t, ok)
require.Equal(t, "my-pg", got.GetName())
})
t.Run("distinct databases with same identifier picks the first", func(t *testing.T) {
got, ok := db.PickMatch(ctx, silent, "shared-id", []types.DatabaseServer{
newServer(t, "db-a", "agent-1", defaults.ProtocolPostgres),
newServer(t, "db-b", "agent-1", defaults.ProtocolPostgres),
})
require.True(t, ok)
require.Equal(t, "db-a", got.GetName())
})
}
// newServer builds a minimal DatabaseServer suitable for PickMatch tests.
func newServer(t *testing.T, dbName, agentName, protocol string) types.DatabaseServer {
t.Helper()
srv, err := types.NewDatabaseServerV3(types.Metadata{
Name: dbName,
}, types.DatabaseServerSpecV3{
HostID: agentName,
Hostname: agentName,
Database: &types.DatabaseV3{
Metadata: types.Metadata{Name: dbName},
Spec: types.DatabaseSpecV3{
Protocol: protocol,
URI: "localhost:5432",
},
},
})
require.NoError(t, err)
return srv
}
func TestIsUserOptional(t *testing.T) {
supported := []string{
defaults.ProtocolPostgres,
defaults.ProtocolCockroachDB,
defaults.ProtocolMySQL,
defaults.ProtocolSQLServer,
}
for _, p := range supported {
t.Run(p, func(t *testing.T) {
require.True(t, db.IsUserOptional(p))
})
}
unsupported := []string{
defaults.ProtocolMongoDB,
defaults.ProtocolClickHouse,
}
for _, p := range unsupported {
t.Run(p+"_user_required", func(t *testing.T) {
require.False(t, db.IsUserOptional(p))
})
}
}