vault/physical/mysql/mysql_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

347 lines
10 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package mysql
import (
"bytes"
"os"
"strings"
"testing"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/physical"
_ "github.com/go-sql-driver/mysql"
mysql "github.com/go-sql-driver/mysql"
mysqlhelper "github.com/hashicorp/vault/helper/testhelpers/mysql"
)
func TestMySQLPlaintextCatch(t *testing.T) {
address := os.Getenv("MYSQL_ADDR")
if address == "" {
t.SkipNow()
}
database := os.Getenv("MYSQL_DB")
if database == "" {
database = "test"
}
table := os.Getenv("MYSQL_TABLE")
if table == "" {
table = "test"
}
username := os.Getenv("MYSQL_USERNAME")
password := os.Getenv("MYSQL_PASSWORD")
// Run vault tests
var buf bytes.Buffer
log.DefaultOutput = &buf
logger := logging.NewVaultLogger(log.Debug)
NewMySQLBackend(map[string]string{
"address": address,
"database": database,
"table": table,
"username": username,
"password": password,
"plaintext_connection_allowed": "false",
}, logger)
str := buf.String()
dataIdx := strings.IndexByte(str, ' ')
rest := str[dataIdx+1:]
if !strings.Contains(rest, "credentials will be sent in plaintext") {
t.Fatalf("No warning of plaintext credentials occurred")
}
}
func TestMySQLBackend(t *testing.T) {
address := os.Getenv("MYSQL_ADDR")
if address == "" {
t.SkipNow()
}
database := os.Getenv("MYSQL_DB")
if database == "" {
database = "test"
}
table := os.Getenv("MYSQL_TABLE")
if table == "" {
table = "test"
}
username := os.Getenv("MYSQL_USERNAME")
password := os.Getenv("MYSQL_PASSWORD")
// Run vault tests
logger := logging.NewVaultLogger(log.Debug)
b, err := NewMySQLBackend(map[string]string{
"address": address,
"database": database,
"table": table,
"username": username,
"password": password,
"plaintext_connection_allowed": "true",
"max_connection_lifetime": "1",
}, logger)
if err != nil {
t.Fatalf("Failed to create new backend: %v", err)
}
defer func() {
mysql := b.(*MySQLBackend)
_, err := mysql.client.Exec("DROP TABLE IF EXISTS " + mysql.dbTable + " ," + mysql.dbLockTable)
if err != nil {
t.Fatalf("Failed to drop table: %v", err)
}
}()
physical.ExerciseBackend(t, b)
physical.ExerciseBackend_ListPrefix(t, b)
}
func TestMySQLHABackend(t *testing.T) {
address := os.Getenv("MYSQL_ADDR")
if address == "" {
t.SkipNow()
}
database := os.Getenv("MYSQL_DB")
if database == "" {
database = "test"
}
table := os.Getenv("MYSQL_TABLE")
if table == "" {
table = "test"
}
username := os.Getenv("MYSQL_USERNAME")
password := os.Getenv("MYSQL_PASSWORD")
// Run vault tests
logger := logging.NewVaultLogger(log.Debug)
config := map[string]string{
"address": address,
"database": database,
"table": table,
"username": username,
"password": password,
"ha_enabled": "true",
"plaintext_connection_allowed": "true",
}
b, err := NewMySQLBackend(config, logger)
if err != nil {
t.Fatalf("Failed to create new backend: %v", err)
}
defer func() {
mysql := b.(*MySQLBackend)
_, err := mysql.client.Exec("DROP TABLE IF EXISTS " + mysql.dbTable + " ," + mysql.dbLockTable)
if err != nil {
t.Fatalf("Failed to drop table: %v", err)
}
}()
b2, err := NewMySQLBackend(config, logger)
if err != nil {
t.Fatalf("Failed to create new backend: %v", err)
}
physical.ExerciseHABackend(t, b.(physical.HABackend), b2.(physical.HABackend))
}
// TestMySQLHABackend_LockFailPanic is a regression test for the panic shown in
// https://github.com/hashicorp/vault/issues/8203 and patched in
// https://github.com/hashicorp/vault/pull/8229
func TestMySQLHABackend_LockFailPanic(t *testing.T) {
cleanup, connURL := mysqlhelper.PrepareTestContainer(t, false, "secret")
cfg, err := mysql.ParseDSN(connURL)
if err != nil {
t.Fatal(err)
}
if err := mysqlhelper.TestCredsExist(t, connURL, cfg.User, cfg.Passwd); err != nil {
t.Fatalf("Could not connect with new credentials: %s", err)
}
table := "test"
logger := logging.NewVaultLogger(log.Debug)
config := map[string]string{
"address": cfg.Addr,
"database": cfg.DBName,
"table": table,
"username": cfg.User,
"password": cfg.Passwd,
"ha_enabled": "true",
"plaintext_connection_allowed": "true",
}
b, err := NewMySQLBackend(config, logger)
if err != nil {
t.Fatalf("Failed to create new backend: %v", err)
}
b2, err := NewMySQLBackend(config, logger)
if err != nil {
t.Fatalf("Failed to create new backend: %v", err)
}
b1ha := b.(physical.HABackend)
b2ha := b2.(physical.HABackend)
// Copied from ExerciseHABackend - ensuring things are normal at this point
// Get the lock
lock, err := b1ha.LockWith("foo", "bar")
if err != nil {
t.Fatalf("initial lock: %v", err)
}
// Attempt to lock
leaderCh, err := lock.Lock(nil)
if err != nil {
t.Fatalf("lock attempt 1: %v", err)
}
if leaderCh == nil {
t.Fatalf("missing leaderCh")
}
// Check the value
held, val, err := lock.Value()
if err != nil {
t.Fatalf("err: %v", err)
}
if !held {
t.Errorf("should be held")
}
if val != "bar" {
t.Errorf("expected value bar: %v", err)
}
// Second acquisition should fail
lock2, err := b2ha.LockWith("foo", "baz")
if err != nil {
t.Fatalf("lock 2: %v", err)
}
stopCh := make(chan struct{})
time.AfterFunc(10*time.Second, func() {
close(stopCh)
})
// Attempt to lock - can't lock because lock1 is held - this is normal
leaderCh2, err := lock2.Lock(stopCh)
if err != nil {
t.Fatalf("stop lock 2: %v", err)
}
if leaderCh2 != nil {
t.Errorf("should not have gotten leaderCh: %v", leaderCh2)
}
// end normal
// Clean up the database. When Lock() is called, a new connection is created
// using the configuration. If that connection cannot be created, there was a
// panic due to not returning with the connection error. Here we intentionally
// break the config for b2, so a new connection can't be made, which would
// trigger the panic shown in https://github.com/hashicorp/vault/issues/8203
cleanup()
stopCh2 := make(chan struct{})
time.AfterFunc(10*time.Second, func() {
close(stopCh2)
})
leaderCh2, err = lock2.Lock(stopCh2)
if err == nil {
t.Fatalf("expected error, got none, leaderCh2=%v", leaderCh2)
}
}
func TestValidateDBTable(t *testing.T) {
type testCase struct {
database string
table string
expectErr bool
}
tests := map[string]testCase{
"empty database & table": {"", "", true},
"empty database": {"", "a", true},
"empty table": {"a", "", true},
"ascii database": {"abcde", "a", false},
"ascii table": {"a", "abcde", false},
"ascii database & table": {"abcde", "abcde", false},
"only whitespace db": {" ", "a", true},
"only whitespace table": {"a", " ", true},
"whitespace prefix db": {" bcde", "a", true},
"whitespace middle db": {"ab de", "a", true},
"whitespace suffix db": {"abcd ", "a", true},
"whitespace prefix table": {"a", " bcde", true},
"whitespace middle table": {"a", "ab de", true},
"whitespace suffix table": {"a", "abcd ", true},
"backtick prefix db": {"`bcde", "a", true},
"backtick middle db": {"ab`de", "a", true},
"backtick suffix db": {"abcd`", "a", true},
"backtick prefix table": {"a", "`bcde", true},
"backtick middle table": {"a", "ab`de", true},
"backtick suffix table": {"a", "abcd`", true},
"single quote prefix db": {"'bcde", "a", true},
"single quote middle db": {"ab'de", "a", true},
"single quote suffix db": {"abcd'", "a", true},
"single quote prefix table": {"a", "'bcde", true},
"single quote middle table": {"a", "ab'de", true},
"single quote suffix table": {"a", "abcd'", true},
"double quote prefix db": {`"bcde`, "a", true},
"double quote middle db": {`ab"de`, "a", true},
"double quote suffix db": {`abcd"`, "a", true},
"double quote prefix table": {"a", `"bcde`, true},
"double quote middle table": {"a", `ab"de`, true},
"double quote suffix table": {"a", `abcd"`, true},
"0x0000 prefix db": {str(0x0000, 'b', 'c'), "a", true},
"0x0000 middle db": {str('a', 0x0000, 'c'), "a", true},
"0x0000 suffix db": {str('a', 'b', 0x0000), "a", true},
"0x0000 prefix table": {"a", str(0x0000, 'b', 'c'), true},
"0x0000 middle table": {"a", str('a', 0x0000, 'c'), true},
"0x0000 suffix table": {"a", str('a', 'b', 0x0000), true},
"unicode > 0xFFFF prefix db": {str(0x10000, 'b', 'c'), "a", true},
"unicode > 0xFFFF middle db": {str('a', 0x10000, 'c'), "a", true},
"unicode > 0xFFFF suffix db": {str('a', 'b', 0x10000), "a", true},
"unicode > 0xFFFF prefix table": {"a", str(0x10000, 'b', 'c'), true},
"unicode > 0xFFFF middle table": {"a", str('a', 0x10000, 'c'), true},
"unicode > 0xFFFF suffix table": {"a", str('a', 'b', 0x10000), true},
"non-printable prefix db": {str(0x0001, 'b', 'c'), "a", true},
"non-printable middle db": {str('a', 0x0001, 'c'), "a", true},
"non-printable suffix db": {str('a', 'b', 0x0001), "a", true},
"non-printable prefix table": {"a", str(0x0001, 'b', 'c'), true},
"non-printable middle table": {"a", str('a', 0x0001, 'c'), true},
"non-printable suffix table": {"a", str('a', 'b', 0x0001), true},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
err := validateDBTable(test.database, test.table)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
})
}
}
func str(r ...rune) string {
return string(r)
}