1
0
mirror of synced 2025-12-19 09:50:33 -05:00
Files
terraform-google-sql/test/example_mysql_public_ip_test.go

288 lines
10 KiB
Go

package test
import (
"crypto/tls"
"crypto/x509"
"database/sql"
"fmt"
"path/filepath"
"strings"
"testing"
"time"
mydialer "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql"
"github.com/go-sql-driver/mysql"
"github.com/gruntwork-io/terratest/modules/gcp"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const NAME_PREFIX_PUBLIC = "mysql-public"
const EXAMPLE_NAME_PUBLIC = "mysql-public-ip"
const EXAMPLE_NAME_CERT = "client-certificate"
func TestMySqlPublicIP(t *testing.T) {
t.Parallel()
//os.Setenv("SKIP_bootstrap", "true")
//os.Setenv("SKIP_deploy", "true")
//os.Setenv("SKIP_validate_outputs", "true")
//os.Setenv("SKIP_sql_tests", "true")
//os.Setenv("SKIP_proxy_tests", "true")
//os.Setenv("SKIP_deploy_cert", "true")
//os.Setenv("SKIP_redeploy", "true")
//os.Setenv("SKIP_ssl_sql_tests", "true")
//os.Setenv("SKIP_teardown_cert", "true")
//os.Setenv("SKIP_teardown", "true")
_examplesDir := test_structure.CopyTerraformFolderToTemp(t, "../", "examples")
exampleDir := filepath.Join(_examplesDir, EXAMPLE_NAME_PUBLIC)
certExampleDir := filepath.Join(_examplesDir, EXAMPLE_NAME_CERT)
// BOOTSTRAP VARIABLES FOR THE TESTS
test_structure.RunTestStage(t, "bootstrap", func() {
projectId := gcp.GetGoogleProjectIDFromEnvVar(t)
region := getRandomRegion(t, projectId)
test_structure.SaveString(t, exampleDir, KEY_REGION, region)
test_structure.SaveString(t, exampleDir, KEY_PROJECT, projectId)
})
// AT THE END OF THE TESTS, RUN `terraform destroy`
// TO CLEAN UP ANY RESOURCES THAT WERE CREATED
defer test_structure.RunTestStage(t, "teardown", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
terraform.Destroy(t, terraformOptions)
})
defer test_structure.RunTestStage(t, "teardown_cert", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, certExampleDir)
terraform.Destroy(t, terraformOptions)
})
test_structure.RunTestStage(t, "deploy", func() {
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_PUBLIC)
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
})
// VALIDATE MODULE OUTPUTS
test_structure.RunTestStage(t, "validate_outputs", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
instanceNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_INSTANCE_NAME)
dbNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_DB_NAME)
proxyConnectionFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PROXY_CONNECTION)
expectedDBConn := fmt.Sprintf("%s:%s:%s", projectId, region, instanceNameFromOutput)
assert.True(t, strings.HasPrefix(instanceNameFromOutput, NAME_PREFIX_PUBLIC))
assert.Equal(t, DB_NAME, dbNameFromOutput)
assert.Equal(t, expectedDBConn, proxyConnectionFromOutput)
})
// TEST REGULAR SQL CLIENT
test_structure.RunTestStage(t, "sql_tests", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
publicIp := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PUBLIC_IP)
connectionString := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s", DB_USER, DB_PASS, publicIp, DB_NAME)
// Does not actually open up the connection - just returns a DB ref
logger.Logf(t, "Connecting to: %s", publicIp)
db, err := sql.Open("mysql",
connectionString)
require.NoError(t, err, "Failed to open DB connection")
// Make sure we clean up properly
defer db.Close()
// Run ping to actually test the connection
logger.Log(t, "Ping the DB")
if err = db.Ping(); err != nil {
t.Fatalf("Failed to ping DB: %v", err)
}
// Create table if not exists
logger.Logf(t, "Create table: %s", MYSQL_CREATE_TEST_TABLE_WITH_AUTO_INCREMENT_STATEMENT)
if _, err = db.Exec(MYSQL_CREATE_TEST_TABLE_WITH_AUTO_INCREMENT_STATEMENT); err != nil {
t.Fatalf("Failed to create table: %v", err)
}
// Clean up
logger.Logf(t, "Empty table: %s", SQL_EMPTY_TEST_TABLE_STATEMENT)
if _, err = db.Exec(SQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
t.Fatalf("Failed to clean up table: %v", err)
}
// Insert data to check that our auto-increment flags worked
logger.Logf(t, "Insert data: %s", MYSQL_INSERT_TEST_ROW)
stmt, err := db.Prepare(MYSQL_INSERT_TEST_ROW)
require.NoError(t, err, "Failed to prepare statement")
// Execute the statement
res, err := stmt.Exec("Grunt")
require.NoError(t, err, "Failed to execute statement")
// Get the last insert id
lastId, err := res.LastInsertId()
require.NoError(t, err, "Failed to get last insert id")
// Since we set the auto increment to 5, modulus should always be 0
assert.Equal(t, int64(0), int64(lastId%5))
})
// TEST CLOUD SQL PROXY
test_structure.RunTestStage(t, "proxy_tests", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
proxyConn := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PROXY_CONNECTION)
logger.Logf(t, "Connecting to: %s via Cloud SQL Proxy", proxyConn)
// Use the Cloud SQL Proxy for queries
// See https://cloud.google.com/sql/docs/mysql/sql-proxy
cfg := mydialer.Cfg(proxyConn, DB_USER, DB_PASS)
cfg.DBName = DB_NAME
cfg.ParseTime = true
const timeout = 10 * time.Second
cfg.Timeout = timeout
cfg.ReadTimeout = timeout
cfg.WriteTimeout = timeout
// Dial in. This one actually pings the database already
db, err := mydialer.DialCfg(cfg)
require.NoError(t, err, "Failed to open Proxy DB connection")
// Make sure we clean up properly
defer db.Close()
// Run ping to actually test the connection
logger.Log(t, "Ping the DB")
if err = db.Ping(); err != nil {
t.Fatalf("Failed to ping DB via Proxy: %v", err)
}
// Insert data to check that our auto-increment flags worked
logger.Logf(t, "Insert data: %s", MYSQL_INSERT_TEST_ROW)
stmt, err := db.Prepare(MYSQL_INSERT_TEST_ROW)
require.NoError(t, err, "Failed to prepare proxy statement")
// Execute the statement
res, err := stmt.Exec("Grunt2")
require.NoError(t, err, "Failed to execute proxy statement")
// Get the last insert id
lastId, err := res.LastInsertId()
require.NoError(t, err, "Failed to get last proxy insert id")
// Since we set the auto increment to 5, modulus should always be 0
assert.Equal(t, int64(0), int64(lastId%5))
})
// CREATE CLIENT CERT
test_structure.RunTestStage(t, "deploy_cert", func() {
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
instanceNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_INSTANCE_NAME)
commonName := fmt.Sprintf("%s-client", instanceNameFromOutput)
terraformOptionsForCert := createTerratestOptionsForClientCert(projectId, region, certExampleDir, commonName, instanceNameFromOutput)
test_structure.SaveTerraformOptions(t, certExampleDir, terraformOptionsForCert)
terraform.InitAndApply(t, terraformOptionsForCert)
})
// REDEPLOY WITH FORCED SSL SETTINGS
test_structure.RunTestStage(t, "redeploy", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
// Force secure connections
terraformOptions.Vars["require_ssl"] = true
terraform.InitAndApply(t, terraformOptions)
})
// RUN TESTS WITH SECURED CONNECTION
test_structure.RunTestStage(t, "ssl_sql_tests", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
terraformOptionsForCert := test_structure.LoadTerraformOptions(t, certExampleDir)
//********************************************************
// First test that we're not allowed to connect over insecure connection
//********************************************************
publicIp := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PUBLIC_IP)
connectionString := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s", DB_USER, DB_PASS, publicIp, DB_NAME)
// Does not actually open up the connection - just returns a DB ref
logger.Logf(t, "Connecting to: %s", publicIp)
db, err := sql.Open("mysql",
connectionString)
require.NoError(t, err, "Failed to open DB connection")
// Make sure we clean up properly
defer db.Close()
// Run ping to actually test the connection
logger.Log(t, "Ping the DB with forced SSL")
if err = db.Ping(); err != nil {
logger.Logf(t, "Not allowed to ping %s as expected.", publicIp)
} else {
t.Fatalf("Ping %v succeeded against the odds.", publicIp)
}
//********************************************************
// Test connection over secure connection
//********************************************************
// Prepare certificates
rootCertPool := x509.NewCertPool()
serverCertB := []byte(terraform.Output(t, terraformOptions, OUTPUT_MASTER_CA_CERT))
clientCertB := []byte(terraform.Output(t, terraformOptionsForCert, OUTPUT_CLIENT_CA_CERT))
clientPKB := []byte(terraform.Output(t, terraformOptionsForCert, OUTPUT_CLIENT_PRIVATE_KEY))
if ok := rootCertPool.AppendCertsFromPEM(serverCertB); !ok {
t.Fatal("Failed to append PEM.")
}
clientCert := make([]tls.Certificate, 0, 1)
certs, err := tls.X509KeyPair(clientCertB, clientPKB)
require.NoError(t, err, "Failed to create key pair")
clientCert = append(clientCert, certs)
// Register MySQL certificate config
// To avoid certificate validation errors complaining about
// missing IP SANs, we set 'InsecureSkipVerify: true'
mysql.RegisterTLSConfig("custom", &tls.Config{
RootCAs: rootCertPool,
Certificates: clientCert,
InsecureSkipVerify: true,
})
// Prepare the secure connection string and ping the DB
sslConnectionString := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?tls=custom", DB_USER, DB_PASS, publicIp, DB_NAME)
db, err = sql.Open("mysql", sslConnectionString)
// Run ping to actually test the connection with the SSL config
logger.Log(t, "Ping the DB with forced SSL")
if err = db.Ping(); err != nil {
t.Fatalf("Failed to ping DB with forced SSL: %v", err)
}
})
}