Skip to content

Commit

Permalink
[VAULT-2533] Add Redshift support to Provider (#1279)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinay-gopalan authored Jan 5, 2022
1 parent 2d666af commit 4cce352
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.2.0 (Unreleased)
IMPROVEMENTS:
* `resource/database_secret_backend_connection`: Add support for configuring Redshift databases ([#1279](https://github.com/hashicorp/terraform-provider-vault/pull/1279))

## 3.1.1 (December 22, 2021)
BUGS:
* Prevent new `entity` read failures when the `VAULT_TOKEN` environment variable is not set ([#1270](https://github.com/hashicorp/terraform-provider-vault/pull/1270))
Expand Down
56 changes: 32 additions & 24 deletions vault/resource_database_secret_backend_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

type connectionStringConfig struct {
excludeUsernameTemplate bool
includeUserPass bool
}

const (
Expand All @@ -36,6 +37,7 @@ const (
dbBackendPostgres = "postgresql"
dbBackendOracle = "oracle"
dbBackendSnowflake = "snowflake"
dbBackendRedshift = "redshift"
)

var (
Expand All @@ -56,6 +58,7 @@ var (
dbBackendPostgres,
dbBackendOracle,
dbBackendSnowflake,
dbBackendRedshift,
}
)

Expand Down Expand Up @@ -388,11 +391,20 @@ func databaseSecretBackendConnectionResource() *schema.Resource {
ConflictsWith: util.CalculateConflictsWith(dbBackendOracle, dbBackendTypes),
},

"redshift": {
Type: schema.TypeList,
Optional: true,
Description: "Connection parameters for the redshift-database-plugin plugin.",
Elem: connectionStringResource(&connectionStringConfig{includeUserPass: true}),
MaxItems: 1,
ConflictsWith: util.CalculateConflictsWith(dbBackendRedshift, dbBackendTypes),
},

"snowflake": {
Type: schema.TypeList,
Optional: true,
Description: "Connection parameters for the snowflake-database-plugin plugin.",
Elem: snowflakeConnectionStringResource(),
Elem: connectionStringResource(&connectionStringConfig{includeUserPass: true}),
MaxItems: 1,
ConflictsWith: util.CalculateConflictsWith(dbBackendSnowflake, dbBackendTypes),
},
Expand Down Expand Up @@ -437,6 +449,19 @@ func connectionStringResource(config *connectionStringConfig) *schema.Resource {
},
},
}
if config.includeUserPass {
res.Schema["username"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The root credential username used in the connection URL",
}
res.Schema["password"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The root credential password used in the connection URL",
Sensitive: true,
}
}

if !config.excludeUsernameTemplate {
res.Schema["username_template"] = &schema.Schema{
Expand Down Expand Up @@ -475,22 +500,6 @@ func mssqlConnectionStringResource() *schema.Resource {
return r
}

func snowflakeConnectionStringResource() *schema.Resource {
r := connectionStringResource(&connectionStringConfig{})
r.Schema["username"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The AccountAdmin level user using to connect to snowflake",
}
r.Schema["password"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The password with the provided user",
Sensitive: true,
}
return r
}

func getDatabasePluginName(d *schema.ResourceData) (string, error) {
switch {
case len(d.Get(dbBackendCassandra).([]interface{})) > 0:
Expand Down Expand Up @@ -521,6 +530,8 @@ func getDatabasePluginName(d *schema.ResourceData) (string, error) {
return "elasticsearch-database-plugin", nil
case len(d.Get(dbBackendSnowflake).([]interface{})) > 0:
return "snowflake-database-plugin", nil
case len(d.Get(dbBackendRedshift).([]interface{})) > 0:
return "redshift-database-plugin", nil
default:
return "", fmt.Errorf("at least one database plugin must be configured")
}
Expand Down Expand Up @@ -608,7 +619,9 @@ func getDatabaseAPIData(d *schema.ResourceData) (map[string]interface{}, error)
case "elasticsearch-database-plugin":
setElasticsearchDatabaseConnectionData(d, "elasticsearch.0.", data)
case "snowflake-database-plugin":
setSnowflakeDatabaseConnectionData(d, "snowflake.0.", data)
setDatabaseConnectionDataWithUserPass(d, "snowflake.0.", data)
case "redshift-database-plugin":
setDatabaseConnectionDataWithUserPass(d, "redshift.0.", data)
}

return data, nil
Expand Down Expand Up @@ -907,19 +920,14 @@ func setInfluxDBDatabaseConnectionData(d *schema.ResourceData, prefix string, da
}
}

func setSnowflakeDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) {
func setDatabaseConnectionDataWithUserPass(d *schema.ResourceData, prefix string, data map[string]interface{}) {
setDatabaseConnectionData(d, prefix, data)
if v, ok := d.GetOk(prefix + "username"); ok {
data["username"] = v.(string)
}

if v, ok := d.GetOk(prefix + "password"); ok {
data["password"] = v.(string)
}

if v, ok := d.GetOk(prefix + "username_template"); ok {
data["username_template"] = v.(string)
}
}

func databaseSecretBackendConnectionCreate(d *schema.ResourceData, meta interface{}) error {
Expand Down
53 changes: 53 additions & 0 deletions vault/resource_database_secret_backend_connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,41 @@ func TestAccDatabaseSecretBackendConnection_snowflake(t *testing.T) {
})
}

func TestAccDatabaseSecretBackendConnection_redshift(t *testing.T) {
MaybeSkipDBTests(t, dbBackendRedshift)

url := os.Getenv("REDSHIFT_URL")
if url == "" {
t.Skip("REDSHIFT_URL not set")
}
backend := acctest.RandomWithPrefix("tf-test-db")
name := acctest.RandomWithPrefix("db")
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() { testutil.TestAccPreCheck(t) },
CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAccDatabaseSecretBackendConnectionConfig_redshift(name, backend, url),
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", "redshift.0.connection_url", url),
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "redshift.0.max_open_connections", "2"),
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "redshift.0.max_idle_connections", "0"),
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "redshift.0.max_connection_lifetime", "0"),
),
},
},
})
}

func testAccDatabaseSecretBackendConnectionCheckDestroy(s *terraform.State) error {
client := testProvider.Meta().(*api.Client)

Expand Down Expand Up @@ -1078,6 +1113,24 @@ resource "vault_database_secret_backend_connection" "test" {
`, path, name, url, username, password, userTempl)
}

func testAccDatabaseSecretBackendConnectionConfig_redshift(name, path, connURL 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"]
redshift {
connection_url = "%s"
}
}
`, path, name, connURL)
}

func newMySQLConnection(t *testing.T, connURL string, username string, password string) *sql.DB {
dbURL := dbutil.QueryHelper(connURL, map[string]string{
"username": username,
Expand Down
21 changes: 21 additions & 0 deletions website/docs/r/database_secret_backend_connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,27 @@ See the [Vault

* `username_template` - (Optional) - [Template](https://www.vaultproject.io/docs/concepts/username-templating) describing how dynamic usernames are generated.

### Redshift Configuration Options

* `connection_url` - (Required) Specifies the Redshift DSN. See
the [Vault
docs](https://www.vaultproject.io/api-docs/secret/databases/redshift#sample-payload)
for an example.

* `max_open_connections` - (Optional) The maximum number of open connections to
the database.

* `max_idle_connections` - (Optional) The maximum number of idle connections to
the database.

* `max_connection_lifetime` - (Optional) The maximum amount of time a connection may be reused.

* `username` - (Optional) The root credential username used in the connection URL.

* `password` - (Optional) The root credential password used in the connection URL.

* `username_template` - (Optional) - [Template](https://www.vaultproject.io/docs/concepts/username-templating) describing how dynamic usernames are generated.

## Attributes Reference

No additional attributes are exported by this resource.
Expand Down

0 comments on commit 4cce352

Please sign in to comment.