package mysql import ( "context" "fmt" "log" "reflect" "testing" logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" mysqlhelper "github.com/hashicorp/vault/helper/testhelpers/mysql" "github.com/hashicorp/vault/sdk/logical" "github.com/mitchellh/mapstructure" ) func TestBackend_config_connection(t *testing.T) { var resp *logical.Response var err error config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } configData := map[string]interface{}{ "connection_url": "sample_connection_url", "max_open_connections": 9, "max_idle_connections": 7, "verify_connection": false, } configReq := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/connection", Storage: config.StorageView, Data: configData, } resp, err = b.HandleRequest(context.Background(), configReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } configReq.Operation = logical.ReadOperation resp, err = b.HandleRequest(context.Background(), configReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } delete(configData, "verify_connection") delete(configData, "connection_url") if !reflect.DeepEqual(configData, resp.Data) { t.Fatalf("bad: expected:%#v\nactual:%#v\n", configData, resp.Data) } } func TestBackend_basic(t *testing.T) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, connURL := mysqlhelper.PrepareTestContainer(t, false, "secret") defer cleanup() connData := map[string]interface{}{ "connection_url": connURL, } // for wildcard based mysql user logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t, connData, false), testAccStepRole(t, true), testAccStepReadCreds(t, "web"), }, }) } func TestBackend_basicHostRevoke(t *testing.T) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, connURL := mysqlhelper.PrepareTestContainer(t, false, "secret") defer cleanup() connData := map[string]interface{}{ "connection_url": connURL, } // for host based mysql user logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t, connData, false), testAccStepRole(t, false), testAccStepReadCreds(t, "web"), }, }) } func TestBackend_roleCrud(t *testing.T) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, connURL := mysqlhelper.PrepareTestContainer(t, false, "secret") defer cleanup() connData := map[string]interface{}{ "connection_url": connURL, } logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t, connData, false), // test SQL with wildcard based user testAccStepRole(t, true), testAccStepReadRole(t, "web", testRoleWildCard), testAccStepDeleteRole(t, "web"), // test SQL with host based user testAccStepRole(t, false), testAccStepReadRole(t, "web", testRoleHost), testAccStepDeleteRole(t, "web"), }, }) } func TestBackend_leaseWriteRead(t *testing.T) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, connURL := mysqlhelper.PrepareTestContainer(t, false, "secret") defer cleanup() connData := map[string]interface{}{ "connection_url": connURL, } logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t, connData, false), testAccStepWriteLease(t), testAccStepReadLease(t), }, }) } func testAccStepConfig(t *testing.T, d map[string]interface{}, expectError bool) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "config/connection", Data: d, ErrorOk: true, Check: func(resp *logical.Response) error { if expectError { if resp.Data == nil { return fmt.Errorf("data is nil") } var e struct { Error string `mapstructure:"error"` } if err := mapstructure.Decode(resp.Data, &e); err != nil { return err } if len(e.Error) == 0 { return fmt.Errorf("expected error, but write succeeded") } return nil } else if resp != nil && resp.IsError() { return fmt.Errorf("got an error response: %v", resp.Error()) } return nil }, } } func testAccStepRole(t *testing.T, wildCard bool) logicaltest.TestStep { pathData := make(map[string]interface{}) if wildCard { pathData = map[string]interface{}{ "sql": testRoleWildCard, } } else { pathData = map[string]interface{}{ "sql": testRoleHost, "revocation_sql": testRevocationSQL, } } return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "roles/web", Data: pathData, } } func testAccStepDeleteRole(t *testing.T, n string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.DeleteOperation, Path: "roles/" + n, } } func testAccStepReadCreds(t *testing.T, name string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "creds/" + name, Check: func(resp *logical.Response) error { var d struct { Username string `mapstructure:"username"` Password string `mapstructure:"password"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { return err } log.Printf("[WARN] Generated credentials: %v", d) return nil }, } } func testAccStepReadRole(t *testing.T, name string, sql string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "roles/" + name, Check: func(resp *logical.Response) error { if resp == nil { if sql == "" { return nil } return fmt.Errorf("bad: %#v", resp) } var d struct { SQL string `mapstructure:"sql"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { return err } if d.SQL != sql { return fmt.Errorf("bad: %#v", resp) } return nil }, } } func testAccStepWriteLease(t *testing.T) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "config/lease", Data: map[string]interface{}{ "lease": "1h5m", "lease_max": "24h", }, } } func testAccStepReadLease(t *testing.T) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "config/lease", Check: func(resp *logical.Response) error { if resp.Data["lease"] != "1h5m0s" || resp.Data["lease_max"] != "24h0m0s" { return fmt.Errorf("bad: %#v", resp) } return nil }, } } const testRoleWildCard = ` CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON *.* TO '{{name}}'@'%'; ` const testRoleHost = ` CREATE USER '{{name}}'@'10.1.1.2' IDENTIFIED BY '{{password}}'; GRANT SELECT ON *.* TO '{{name}}'@'10.1.1.2'; ` const testRevocationSQL = ` REVOKE ALL PRIVILEGES, GRANT OPTION FROM '{{name}}'@'10.1.1.2'; DROP USER '{{name}}'@'10.1.1.2'; `