Files
teleport/lib/config/database_test.go
T
Gavin Frazar 5c752c4df8 Fixup teleport db configure create (#20968)
* add missing flags
* fix config generation bugs
* always wrap generated strings value in quotes
* parse and check generated config
* update tests for db agent config gen
2023-02-11 00:54:07 +00:00

474 lines
16 KiB
Go

// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bytes"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
apiutils "github.com/gravitational/teleport/api/utils"
)
func TestMakeDatabaseConfig(t *testing.T) {
t.Run("Global", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
NodeName: "testlocal",
DataDir: "/var/lib/data",
ProxyServer: "localhost:3080",
AuthToken: "/tmp/token.txt",
CAPins: []string{"pin-1", "pin-2"},
}
configString, err := MakeDatabaseAgentConfigString(flags)
require.NoError(t, err)
fileConfig, err := ReadConfig(bytes.NewBuffer([]byte(configString)))
require.NoError(t, err)
require.Equal(t, flags.NodeName, fileConfig.NodeName)
require.Equal(t, flags.DataDir, fileConfig.DataDir)
require.Equal(t, flags.ProxyServer, fileConfig.ProxyServer)
require.Equal(t, flags.AuthToken, fileConfig.AuthToken)
require.ElementsMatch(t, flags.CAPins, fileConfig.CAPin)
})
t.Run("RDSAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
RDSDiscoveryRegions: []string{"us-west-1", "us-west-2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"rds"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.RDSDiscoveryRegions, databases.AWSMatchers[0].Regions)
})
t.Run("RDSProxyAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
RDSProxyDiscoveryRegions: []string{"us-west-1", "us-west-2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"rdsproxy"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.RDSProxyDiscoveryRegions, databases.AWSMatchers[0].Regions)
})
t.Run("RedshiftAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
RedshiftDiscoveryRegions: []string{"us-west-1", "us-west-2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"redshift"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.RedshiftDiscoveryRegions, databases.AWSMatchers[0].Regions)
})
t.Run("RedshiftServerlessAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
RedshiftServerlessDiscoveryRegions: []string{"us-west-1", "us-west-2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"redshift-serverless"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.RedshiftServerlessDiscoveryRegions, databases.AWSMatchers[0].Regions)
})
t.Run("ElastiCacheAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
ElastiCacheDiscoveryRegions: []string{"us-west-1", "us-west-2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"elasticache"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.ElastiCacheDiscoveryRegions, databases.AWSMatchers[0].Regions)
})
t.Run("MemoryDBAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
MemoryDBDiscoveryRegions: []string{"us-west-1", "us-west-2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"memorydb"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.MemoryDBDiscoveryRegions, databases.AWSMatchers[0].Regions)
})
t.Run("AWS discovery tags", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
RedshiftServerlessDiscoveryRegions: []string{"us-west-1", "us-west-2"},
AWSRawTags: "teleport.dev/discovery=true,env=prod",
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AWSMatchers, 1)
require.ElementsMatch(t, []string{"redshift-serverless"}, databases.AWSMatchers[0].Types)
require.ElementsMatch(t, flags.RedshiftServerlessDiscoveryRegions, databases.AWSMatchers[0].Regions)
require.Equal(t, map[string]apiutils.Strings{
"teleport.dev/discovery": {"true"},
"env": {"prod"},
}, databases.AWSMatchers[0].Tags)
})
t.Run("AzureMySQLAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
AzureMySQLDiscoveryRegions: []string{"eastus", "eastus2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AzureMatchers, 1)
require.ElementsMatch(t, []string{"mysql"}, databases.AzureMatchers[0].Types)
require.ElementsMatch(t, flags.AzureMySQLDiscoveryRegions, databases.AzureMatchers[0].Regions)
})
t.Run("AzurePostgresAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
AzurePostgresDiscoveryRegions: []string{"eastus", "eastus2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AzureMatchers, 1)
require.ElementsMatch(t, []string{"postgres"}, databases.AzureMatchers[0].Types)
require.ElementsMatch(t, flags.AzurePostgresDiscoveryRegions, databases.AzureMatchers[0].Regions)
})
t.Run("AzureSQLServerAutoDiscovery", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
AzureSQLServerDiscoveryRegions: []string{"eastus", "eastus2"},
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AzureMatchers, 1)
require.ElementsMatch(t, []string{"sqlserver"}, databases.AzureMatchers[0].Types)
require.ElementsMatch(t, flags.AzureSQLServerDiscoveryRegions, databases.AzureMatchers[0].Regions)
})
t.Run("Azure discovery tags,subscriptions,resource_groups", func(t *testing.T) {
t.Parallel()
flags := DatabaseSampleFlags{
AzureRedisDiscoveryRegions: []string{"eastus", "eastus2"},
AzureSubscriptions: []string{"sub1", "sub2"},
AzureResourceGroups: []string{"group1", "group2"},
AzureRawTags: "TeleportDiscovery=true,Env=prod",
}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.AzureMatchers, 1)
require.ElementsMatch(t, []string{"redis"}, databases.AzureMatchers[0].Types)
require.ElementsMatch(t, flags.AzureRedisDiscoveryRegions, databases.AzureMatchers[0].Regions)
require.ElementsMatch(t, flags.AzureSubscriptions, databases.AzureMatchers[0].Subscriptions)
require.ElementsMatch(t, flags.AzureResourceGroups, databases.AzureMatchers[0].ResourceGroups)
require.Equal(t, map[string]apiutils.Strings{
"TeleportDiscovery": {"true"},
"Env": {"prod"},
}, databases.AzureMatchers[0].ResourceTags)
})
t.Run("StaticDatabase", func(t *testing.T) {
t.Parallel()
tempdir := t.TempDir()
pemfile := filepath.Join(tempdir, "db-ca.pem")
os.WriteFile(pemfile, []byte{}, 0777)
keytabfile := filepath.Join(tempdir, "db.keytab")
os.WriteFile(keytabfile, []byte{}, 0777)
tests := map[string]struct {
flags DatabaseSampleFlags
wantCommandLabels []CommandLabel
wantStaticLabels map[string]string
requireFn require.ErrorAssertionFunc
}{
"SelfHosted": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "localhost:5432",
StaticDatabaseRawLabels: `env=prod,arch=[5m2s:/bin/uname -m "p1 p2"]`,
DatabaseCACertFile: pemfile,
},
wantStaticLabels: map[string]string{
"env": "prod",
},
wantCommandLabels: []CommandLabel{
{
Name: "arch",
Period: time.Minute*5 + time.Second*2,
Command: []string{"/bin/uname", "-m", `"p1 p2"`},
},
},
requireFn: require.NoError,
},
"AWSKeyspaces": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "cassandra",
StaticDatabaseURI: "cassandra.us-west-1.amazonaws.com",
DatabaseCACertFile: pemfile,
DatabaseAWSRegion: "us-west-1",
DatabaseAWSAccountID: "123456789012",
DatabaseAWSExternalID: "1234567890",
},
requireFn: require.NoError,
},
"AWSKeyspacesDeriveURIFromAWSRegion": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "cassandra",
StaticDatabaseURI: "",
DatabaseCACertFile: pemfile,
DatabaseAWSRegion: "us-west-1",
DatabaseAWSAccountID: "123456789012",
DatabaseAWSExternalID: "1234567890",
},
requireFn: require.NoError,
},
"AWSRedshift": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "redshift-cluster-1.abcdefghijklmnop.us-west-1.redshift.amazonaws.com:5439",
DatabaseAWSRegion: "us-west-1",
DatabaseAWSRedshiftClusterID: "redshift-cluster-1",
},
requireFn: require.NoError,
},
"AWSRDSInstance": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "rds-instance-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432",
DatabaseAWSRegion: "us-west-1",
DatabaseAWSRDSInstanceID: "rsd-instance-1",
},
requireFn: require.NoError,
},
"AWSRDSCluster": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "aurora-cluster-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432",
DatabaseAWSRegion: "us-west-1",
DatabaseAWSRDSClusterID: "aurora-cluster-1",
},
requireFn: require.NoError,
},
"AWSMemoryDB": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "redis",
StaticDatabaseURI: "clustercfg.my-memorydb.xxxxxx.memorydb.us-east-1.amazonaws.com:6379",
DatabaseAWSRegion: "us-west-1",
DatabaseAWSMemoryDBClusterName: "my-memorydb",
},
requireFn: require.NoError,
},
"AWSElastieCache": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "redis",
StaticDatabaseURI: "master.redis-cluster-example.abcdef.usw1.cache.amazonaws.com:6379",
DatabaseAWSRegion: "us-west-1",
DatabaseAWSElastiCacheGroupID: "redis-cluster-example",
},
requireFn: require.NoError,
},
"AD": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "localhost:5432",
DatabaseADDomain: "EXAMPLE.com",
DatabaseADSPN: "MSSQLSvc/ec2amaz-4kn05du.dbadir.teleportdemo.net:1433",
DatabaseADKeytabFile: keytabfile,
},
requireFn: require.NoError,
},
"GCP": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "localhost:5432",
DatabaseGCPProjectID: "xxx-1234",
DatabaseGCPInstanceID: "example",
},
requireFn: require.NoError,
},
"DynamoDBDeriveURIFromAWSRegion": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "dynamodb",
DatabaseAWSAccountID: "123456789012",
DatabaseAWSRegion: "us-west-1",
},
requireFn: require.NoError,
},
"MissingName": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "postgres://localhost:5432",
},
requireFn: require.Error,
},
"MissingProtocol": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "",
StaticDatabaseURI: "postgres://localhost:5432",
},
requireFn: require.Error,
},
"MissingRequiredURI": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "",
},
requireFn: require.Error,
},
"InvalidLabels": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "postgres",
StaticDatabaseURI: "postgres://localhost:5432",
StaticDatabaseRawLabels: "abc",
},
requireFn: require.Error,
},
"MissingRequiredAWSAccount": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "dynamodb",
StaticDatabaseURI: "dynamodb.us-west-1.amazonaws.com",
DatabaseAWSRegion: "us-west-1",
DatabaseAWSAccountID: "",
},
requireFn: require.Error,
},
"MissingAWSRegionAndURI": {
flags: DatabaseSampleFlags{
StaticDatabaseName: "sample",
StaticDatabaseProtocol: "dynamodb",
DatabaseAWSAccountID: "123456789012",
},
requireFn: require.Error,
},
}
for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
t.Parallel()
configString, err := MakeDatabaseAgentConfigString(tt.flags)
tt.requireFn(t, err)
if err != nil {
return
}
fc, err := ReadConfig(bytes.NewBuffer([]byte(configString)))
require.NoError(t, err)
require.True(t, fc.Databases.Enabled())
databases := fc.Databases
require.Len(t, databases.Databases, 1)
got := databases.Databases[0]
require.Equal(t, tt.flags.StaticDatabaseName, got.Name)
require.Equal(t, tt.flags.StaticDatabaseProtocol, got.Protocol)
require.Equal(t, tt.flags.StaticDatabaseURI, got.URI)
require.Equal(t, tt.wantStaticLabels, got.StaticLabels)
require.ElementsMatch(t, tt.wantCommandLabels, got.DynamicLabels)
require.Equal(t, tt.flags.DatabaseAWSRegion, got.AWS.Region)
require.Equal(t, tt.flags.DatabaseAWSAccountID, got.AWS.AccountID)
require.Equal(t, tt.flags.DatabaseAWSExternalID, got.AWS.ExternalID)
require.Equal(t, tt.flags.DatabaseAWSRedshiftClusterID, got.AWS.Redshift.ClusterID)
require.Equal(t, tt.flags.DatabaseAWSRDSClusterID, got.AWS.RDS.ClusterID)
require.Equal(t, tt.flags.DatabaseAWSRDSInstanceID, got.AWS.RDS.InstanceID)
require.Equal(t, tt.flags.DatabaseAWSElastiCacheGroupID, got.AWS.ElastiCache.ReplicationGroupID)
require.Equal(t, tt.flags.DatabaseAWSMemoryDBClusterName, got.AWS.MemoryDB.ClusterName)
require.Equal(t, tt.flags.DatabaseADDomain, got.AD.Domain)
require.Equal(t, tt.flags.DatabaseADSPN, got.AD.SPN)
require.Equal(t, tt.flags.DatabaseADKeytabFile, got.AD.KeytabFile)
require.Equal(t, tt.flags.DatabaseGCPProjectID, got.GCP.ProjectID)
require.Equal(t, tt.flags.DatabaseGCPInstanceID, got.GCP.InstanceID)
require.Equal(t, tt.flags.DatabaseCACertFile, got.TLS.CACertFile)
})
}
})
t.Run("resource matchers", func(t *testing.T) {
t.Run("empty", func(t *testing.T) {
flags := DatabaseSampleFlags{}
databases := generateAndParseConfig(t, flags)
require.Len(t, databases.ResourceMatchers, 0)
})
t.Run("multiple labels", func(t *testing.T) {
flags := DatabaseSampleFlags{
DynamicResourcesRawLabels: []string{
"env=dev",
"env=prod,name=my-name",
},
}
databases := generateAndParseConfig(t, flags)
require.Equal(t, []ResourceMatcher{
{
Labels: types.Labels{
"env": apiutils.Strings{"dev"},
},
},
{
Labels: types.Labels{
"name": apiutils.Strings{"my-name"},
"env": apiutils.Strings{"prod"},
},
},
}, databases.ResourceMatchers)
})
})
}
// generateAndParse generetes config using provided flags, parse them using
// `ReadConfig`, checks if the Database service is enable and return it.
func generateAndParseConfig(t *testing.T, flags DatabaseSampleFlags) Databases {
configString, err := MakeDatabaseAgentConfigString(flags)
require.NoError(t, err)
fileConfig, err := ReadConfig(bytes.NewBuffer([]byte(configString)))
require.NoError(t, err)
require.True(t, fileConfig.Databases.Enabled())
return fileConfig.Databases
}