diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index 4112fcc40..61e6075e3 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -226,7 +226,7 @@ func databaseSecretBackendConnectionResource() *schema.Resource { Type: schema.TypeList, Optional: true, Description: "Connection parameters for the mysql-database-plugin plugin.", - Elem: connectionStringResource(), + Elem: mysqlConnectionStringResource(), MaxItems: 1, ConflictsWith: util.CalculateConflictsWith("mysql", dbBackendTypes), }, @@ -315,6 +315,22 @@ func connectionStringResource() *schema.Resource { } } +func mysqlConnectionStringResource() *schema.Resource { + r := connectionStringResource() + r.Schema["tls_certificate_key"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "x509 certificate for connecting to the database. This must be a PEM encoded version of the private key and the certificate combined.", + Sensitive: true, + } + r.Schema["tls_ca"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "x509 CA file for validating the certificate presented by the MySQL server. Must be PEM encoded.", + } + return r +} + func getDatabasePluginName(d *schema.ResourceData) (string, error) { switch { case len(d.Get("cassandra").([]interface{})) > 0: @@ -412,7 +428,7 @@ func getDatabaseAPIData(d *schema.ResourceData) (map[string]interface{}, error) case "mssql-database-plugin": setDatabaseConnectionData(d, "mssql.0.", data) case "mysql-database-plugin": - setDatabaseConnectionData(d, "mysql.0.", data) + setMySQLDatabaseConnectionData(d, "mysql.0.", data) case "mysql-rds-database-plugin": setDatabaseConnectionData(d, "mysql_rds.0.", data) case "mysql-aurora-database-plugin": @@ -471,6 +487,31 @@ func getConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, res return []map[string]interface{}{result} } +func getMySQLConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, resp *api.Secret) []map[string]interface{} { + commonDetails := getConnectionDetailsFromResponse(d, prefix, resp) + details := resp.Data["connection_details"] + data, ok := details.(map[string]interface{}) + if !ok { + return nil + } + result := commonDetails[0] + if v, ok := d.GetOk(prefix + "tls_certificate_key"); ok { + result["tls_certificate_key"] = v.(string) + } else { + if v, ok := data["tls_certificate_key"]; ok { + result["tls_certificate_key"] = v.(string) + } + } + if v, ok := d.GetOk(prefix + "tls_ca"); ok { + result["tls_ca"] = v.(string) + } else { + if v, ok := data["tls_ca"]; ok { + result["tls_ca"] = v.(string) + } + } + return []map[string]interface{}{result} +} + func getElasticsearchConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, resp *api.Secret) []map[string]interface{} { details := resp.Data["connection_details"] data, ok := details.(map[string]interface{}) @@ -514,6 +555,16 @@ func setDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[s } } +func setMySQLDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) { + setDatabaseConnectionData(d, prefix, data) + if v, ok := d.GetOk(prefix + "tls_certificate_key"); ok { + data["tls_certificate_key"] = v.(string) + } + if v, ok := d.GetOk(prefix + "tls_ca"); ok { + data["tls_ca"] = v.(string) + } +} + func setElasticsearchDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) { if v, ok := d.GetOk(prefix + "url"); ok { data["url"] = v.(string) @@ -684,7 +735,7 @@ func databaseSecretBackendConnectionRead(d *schema.ResourceData, meta interface{ case "mssql-database-plugin": d.Set("mssql", getConnectionDetailsFromResponse(d, "mssql.0.", resp)) case "mysql-database-plugin": - d.Set("mysql", getConnectionDetailsFromResponse(d, "mysql.0.", resp)) + d.Set("mysql", getMySQLConnectionDetailsFromResponse(d, "mysql.0.", resp)) case "mysql-rds-database-plugin": d.Set("mysql_rds", getConnectionDetailsFromResponse(d, "mysql_rds.0.", resp)) case "mysql-aurora-database-plugin": diff --git a/vault/resource_database_secret_backend_connection_test.go b/vault/resource_database_secret_backend_connection_test.go index 983c30a1c..8f9fa2997 100644 --- a/vault/resource_database_secret_backend_connection_test.go +++ b/vault/resource_database_secret_backend_connection_test.go @@ -460,6 +460,52 @@ func TestAccDatabaseSecretBackendConnectionTemplatedUpdateExcludePassword_mysql( }) } +func TestAccDatabaseSecretBackendConnection_mysql_tls(t *testing.T) { + tls_ca := os.Getenv("MYSQL_CA") + if tls_ca == "" { + t.Skip("MYSQL_CA not set") + } + connURL := os.Getenv("MYSQL_URL") + if connURL == "" { + t.Skip("MYSQL_URL not set") + } + tls_certificate_key := os.Getenv("MYSQL_CERTIFICATE_KEY") + if tls_certificate_key == "" { + t.Skip("MYSQL_CERTIFICATE_KEY not set") + } + backend := acctest.RandomWithPrefix("tf-test-db") + name := acctest.RandomWithPrefix("db") + password := acctest.RandomWithPrefix("password") + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDatabaseSecretBackendConnectionConfig_mysql_tls(name, backend, connURL, password, tls_ca, tls_certificate_key), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "name", name), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "backend", backend), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "allowed_roles.#", "2"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "allowed_roles.0", "dev"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "allowed_roles.1", "prod"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "root_rotation_statements.#", "1"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "root_rotation_statements.0", "FOOBAR"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "verify_connection", "true"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "mysql.0.connection_url", connURL), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "mysql.0.max_open_connections", "2"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "mysql.0.max_idle_connections", "0"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "mysql.0.max_connection_lifetime", "0"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "data.%", "1"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "data.password", password), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "mysql.0.tls_ca", tls_ca+"\n"), + resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "mysql.0.tls_certificate_key", tls_certificate_key+"\n"), + ), + }, + }, + }) +} + func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) { connURL := os.Getenv("POSTGRES_URL") if connURL == "" { @@ -723,6 +769,36 @@ resource "vault_database_secret_backend_connection" "test" { `, path, name, connURL, connLifetime, password) } +func testAccDatabaseSecretBackendConnectionConfig_mysql_tls(name, path, connURL, password, tls_ca, tls_certificate_key string) string { + return fmt.Sprintf(` +resource "vault_mount" "db" { + path = "%s" + type = "database" +} + +resource "vault_database_secret_backend_connection" "test" { + backend = "${vault_mount.db.path}" + name = "%s" + allowed_roles = ["dev", "prod"] + root_rotation_statements = ["FOOBAR"] + + mysql { + connection_url = "%s" + tls_ca = <