Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for configuring username and password fields for select DB Engines #1331

Merged
merged 13 commits into from
Feb 10, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ IMPROVEMENTS:
* `resource/pki_secret_backend_root_sign_intermediate`: Update schema for `ca_chain` from string to a list of
`issuing_ca` and `certificate`, add new `certificate_bundle` attribute that provides the concatenation of the
intermediate and issuing CA certificates (PEM encoded) ([#1330](https://github.com/hashicorp/terraform-provider-vault/pull/1330))
* `resource/database_secret_backend_connection`: Add `username` and `password` support to Redshift, Hana, Postgres and MSSQL ([#1331](https://github.com/hashicorp/terraform-provider-vault/pull/1331))

## 3.2.1 (January 20, 2022)
BUGS:
Expand Down
47 changes: 35 additions & 12 deletions vault/resource_database_secret_backend_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ func databaseSecretBackendConnectionResource() *schema.Resource {
Description: "Connection parameters for the hana-database-plugin plugin.",
Elem: connectionStringResource(&connectionStringConfig{
excludeUsernameTemplate: true,
includeUserPass: true,
}),
MaxItems: 1,
ConflictsWith: util.CalculateConflictsWith(dbEngineHana.Name(), dbEngineTypes),
Expand Down Expand Up @@ -533,10 +534,12 @@ func databaseSecretBackendConnectionResource() *schema.Resource {
},

dbEnginePostgres.name: {
Type: schema.TypeList,
Optional: true,
Description: "Connection parameters for the postgresql-database-plugin plugin.",
Elem: connectionStringResource(&connectionStringConfig{}),
Type: schema.TypeList,
Optional: true,
Description: "Connection parameters for the postgresql-database-plugin plugin.",
Elem: connectionStringResource(&connectionStringConfig{
includeUserPass: true,
}),
MaxItems: 1,
ConflictsWith: util.CalculateConflictsWith(dbEnginePostgres.Name(), dbEngineTypes),
},
Expand Down Expand Up @@ -650,7 +653,9 @@ func mysqlConnectionStringResource() *schema.Resource {
}

func mssqlConnectionStringResource() *schema.Resource {
r := connectionStringResource(&connectionStringConfig{})
r := connectionStringResource(&connectionStringConfig{
includeUserPass: true,
})
r.Schema["contained_db"] = &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -729,7 +734,7 @@ func getDatabaseAPIData(d *schema.ResourceData) (map[string]interface{}, error)
case dbEngineInfluxDB:
setInfluxDBDatabaseConnectionData(d, "influxdb.0.", data)
case dbEngineHana:
setDatabaseConnectionData(d, "hana.0.", data)
setDatabaseConnectionDataWithUserPass(d, "hana.0.", data)
case dbEngineMongoDB:
setDatabaseConnectionData(d, "mongodb.0.", data)
case dbEngineMongoDBAtlas:
Expand All @@ -755,7 +760,7 @@ func getDatabaseAPIData(d *schema.ResourceData) (map[string]interface{}, error)
case dbEngineOracle:
setDatabaseConnectionData(d, "oracle.0.", data)
case dbEnginePostgres:
setDatabaseConnectionData(d, "postgresql.0.", data)
setDatabaseConnectionDataWithUserPass(d, "postgresql.0.", data)
case dbEngineElasticSearch:
setElasticsearchDatabaseConnectionData(d, "elasticsearch.0.", data)
case dbEngineSnowflake:
Expand Down Expand Up @@ -818,7 +823,7 @@ func getConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, res
}

func getMSSQLConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, resp *api.Secret) ([]map[string]interface{}, error) {
result := getConnectionDetailsFromResponse(d, prefix, resp)
result := getConnectionDetailsFromResponseWithUserPass(d, prefix, resp)
if result == nil {
return nil, nil
}
Expand All @@ -831,6 +836,7 @@ func getMSSQLConnectionDetailsFromResponse(d *schema.ResourceData, prefix string
}
result[0]["contained_db"] = containedDB
}

return result, nil
}

Expand Down Expand Up @@ -1015,6 +1021,23 @@ func getSnowflakeConnectionDetailsFromResponse(d *schema.ResourceData, prefix st
return []map[string]interface{}{result}
}

func getConnectionDetailsFromResponseWithUserPass(d *schema.ResourceData, prefix string, resp *api.Secret) []map[string]interface{} {
result := getConnectionDetailsFromResponse(d, prefix, resp)
if result == nil {
return nil
}

details := resp.Data["connection_details"].(map[string]interface{})
if v, ok := details["username"]; ok {
result[0]["username"] = v.(string)
}
if v, ok := d.GetOk(prefix + "password"); ok {
result[0]["password"] = v.(string)
}

return result
}

func setDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) {
if v, ok := d.GetOk(prefix + "connection_url"); ok {
data["connection_url"] = v.(string)
Expand All @@ -1034,7 +1057,7 @@ func setDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[s
}

func setMSSQLDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) {
setDatabaseConnectionData(d, prefix, data)
setDatabaseConnectionDataWithUserPass(d, prefix, data)
if v, ok := d.GetOk(prefix + "contained_db"); ok {
// TODO:
// we have to pass string value here due to an issue with the
Expand Down Expand Up @@ -1284,7 +1307,7 @@ func databaseSecretBackendConnectionRead(d *schema.ResourceData, meta interface{
case dbEngineInfluxDB:
d.Set("influxdb", getInfluxDBConnectionDetailsFromResponse(d, "influxdb.0.", resp))
case dbEngineHana:
d.Set("hana", getConnectionDetailsFromResponse(d, "hana.0.", resp))
d.Set("hana", getConnectionDetailsFromResponseWithUserPass(d, "hana.0.", resp))
case dbEngineMongoDB:
d.Set("mongodb", getConnectionDetailsFromResponse(d, "mongodb.0.", resp))
case dbEngineMongoDBAtlas:
Expand Down Expand Up @@ -1321,13 +1344,13 @@ func databaseSecretBackendConnectionRead(d *schema.ResourceData, meta interface{
case dbEngineOracle:
d.Set("oracle", getConnectionDetailsFromResponse(d, "oracle.0.", resp))
case dbEnginePostgres:
d.Set("postgresql", getConnectionDetailsFromResponse(d, "postgresql.0.", resp))
d.Set("postgresql", getConnectionDetailsFromResponseWithUserPass(d, "postgresql.0.", resp))
case dbEngineElasticSearch:
d.Set("elasticsearch", getElasticsearchConnectionDetailsFromResponse(d, "elasticsearch.0.", resp))
case dbEngineSnowflake:
d.Set("snowflake", getSnowflakeConnectionDetailsFromResponse(d, "snowflake.0.", resp))
case dbEngineRedshift:
d.Set("redshift", getConnectionDetailsFromResponse(d, "redshift.0.", resp))
d.Set("redshift", getConnectionDetailsFromResponseWithUserPass(d, "redshift.0.", resp))
default:
return fmt.Errorf("no response handler for dbEngine: %s", db)
}
Expand Down
62 changes: 54 additions & 8 deletions vault/resource_database_secret_backend_connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"log"
"net/url"
"os"
"reflect"
"strings"
Expand Down Expand Up @@ -46,7 +47,7 @@ func TestAccDatabaseSecretBackendConnection_import(t *testing.T) {
CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAccDatabaseSecretBackendConnectionConfig_postgresql(name, backend, connURL, userTempl),
Config: testAccDatabaseSecretBackendConnectionConfig_import(name, backend, connURL, userTempl),
Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName,
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.#", "2"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.0", "dev"),
Expand Down Expand Up @@ -322,13 +323,19 @@ func TestAccDatabaseSecretBackendConnection_mssql(t *testing.T) {
pluginName := dbEngineMSSQL.DefaultPluginName()
name := acctest.RandomWithPrefix("db")

parsedURL, err := url.Parse(connURL)
if err != nil {
t.Fatal(err)
}

username := parsedURL.User.Username()
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() { testutil.TestAccPreCheck(t) },
CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAccDatabaseSecretBackendConnectionConfig_mssql(name, backend, connURL, pluginName, false),
Config: testAccDatabaseSecretBackendConnectionConfig_mssql(name, backend, pluginName, parsedURL, false),
benashz marked this conversation as resolved.
Show resolved Hide resolved
Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName,
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.#", "2"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.0", "dev"),
Expand All @@ -340,11 +347,12 @@ func TestAccDatabaseSecretBackendConnection_mssql(t *testing.T) {
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.max_open_connections", "2"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.max_idle_connections", "0"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.max_connection_lifetime", "0"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.username", username),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.contained_db", "false"),
),
},
{
Config: testAccDatabaseSecretBackendConnectionConfig_mssql(name, backend, connURL, pluginName, true),
Config: testAccDatabaseSecretBackendConnectionConfig_mssql(name, backend, pluginName, parsedURL, true),
Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName,
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "plugin_name", pluginName),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.#", "2"),
Expand All @@ -357,6 +365,7 @@ func TestAccDatabaseSecretBackendConnection_mssql(t *testing.T) {
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.max_open_connections", "2"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.max_idle_connections", "0"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.max_connection_lifetime", "0"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.username", username),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "mssql.0.contained_db", "true"),
),
},
Expand Down Expand Up @@ -631,7 +640,12 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) {
// TODO: make these fatal once we auto provision the required test infrastructure.
values := testutil.SkipTestEnvUnset(t, "POSTGRES_URL")
connURL := values[0]
parsedURL, err := url.Parse(connURL)
if err != nil {
t.Fatal(err)
}

username := parsedURL.User.Username()
backend := acctest.RandomWithPrefix("tf-test-db")
pluginName := dbEnginePostgres.DefaultPluginName()
name := acctest.RandomWithPrefix("db")
Expand All @@ -642,7 +656,7 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) {
CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAccDatabaseSecretBackendConnectionConfig_postgresql(name, backend, connURL, userTempl),
Config: testAccDatabaseSecretBackendConnectionConfig_postgresql(name, backend, userTempl, parsedURL),
Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName,
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.#", "2"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.0", "dev"),
Expand All @@ -654,6 +668,7 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) {
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.max_open_connections", "2"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.max_idle_connections", "0"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.max_connection_lifetime", "0"),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.username", username),
resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.username_template", userTempl),
),
},
Expand Down Expand Up @@ -848,6 +863,27 @@ resource "vault_database_secret_backend_connection" "test" {
`, path, name, host, username, password)
}

func testAccDatabaseSecretBackendConnectionConfig_import(name, path, connURL, userTempl 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"]

postgresql {
connection_url = "%s"
username_template = "%s"
}
}
`, path, name, connURL, userTempl)
}

func testAccDatabaseSecretBackendConnectionConfig_influxdb(name, path, host, username, password string) string {
return fmt.Sprintf(`
resource "vault_mount" "db" {
Expand Down Expand Up @@ -955,18 +991,24 @@ resource "vault_database_secret_backend_connection" "test" {
`, path, name, connURL)
}

func testAccDatabaseSecretBackendConnectionConfig_mssql(name, path, connURL, pluginName string, containedDB bool) string {
func testAccDatabaseSecretBackendConnectionConfig_mssql(name, path, pluginName string, parsedURL *url.URL, containedDB bool) string {
var config string
password, _ := parsedURL.User.Password()

if containedDB {
config = `
mssql {
connection_url = "%s"
username = "%s"
password = "%s"
contained_db = true
}`
} else {
config = `
mssql {
connection_url = "%s"
username = "%s"
password = "%s"
}`
}

Expand All @@ -984,7 +1026,7 @@ resource "vault_database_secret_backend_connection" "test" {
root_rotation_statements = ["FOOBAR"]
%s
}
`, path, pluginName, name, fmt.Sprintf(config, connURL))
`, path, pluginName, name, fmt.Sprintf(config, parsedURL.String(), parsedURL.User.Username(), password))

return result
}
Expand Down Expand Up @@ -1153,7 +1195,9 @@ resource "vault_database_secret_backend_connection" "test" {
`, path, name, connURL)
}

func testAccDatabaseSecretBackendConnectionConfig_postgresql(name, path, connURL, userTempl string) string {
func testAccDatabaseSecretBackendConnectionConfig_postgresql(name, path, userTempl string, parsedURL *url.URL) string {
password, _ := parsedURL.User.Password()

return fmt.Sprintf(`
resource "vault_mount" "db" {
path = "%s"
Expand All @@ -1168,10 +1212,12 @@ resource "vault_database_secret_backend_connection" "test" {

postgresql {
connection_url = "%s"
username = "%s"
password = "%s"
username_template = "%s"
}
}
`, path, name, connURL, userTempl)
`, path, name, parsedURL.String(), parsedURL.User.Username(), password, userTempl)
}

func testAccDatabaseSecretBackendConnectionConfig_snowflake(name, path, url, username, password, userTempl string) string {
Expand Down