From aef5d7030e77042e918641ba86d8f5d7faed4f07 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Wed, 2 Nov 2022 08:44:59 -0700 Subject: [PATCH 1/5] Redis - Add TFVP Support --- ...urce_database_secret_backend_connection.go | 121 ++++++++++++++++++ ...database_secret_backend_connection_test.go | 62 +++++++++ .../r/database_secret_backend_connection.md | 20 +++ website/docs/r/database_secrets_mount.md | 23 +++- 4 files changed, 225 insertions(+), 1 deletion(-) diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index e55f1a58d..9c32a2bb8 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -95,6 +95,10 @@ var ( name: "snowflake", defaultPluginName: "snowflake" + dbPluginSuffix, } + dbEngineRedis = &dbEngine{ + name: "redis", + defaultPluginName: "redis" + dbPluginSuffix, + } dbEngineRedisElastiCache = &dbEngine{ name: "redis_elasticache", defaultPluginName: "redis-elasticache" + dbPluginSuffix, @@ -120,6 +124,7 @@ var ( dbEnginePostgres, dbEngineOracle, dbEngineSnowflake, + dbEngineRedis, dbEngineRedisElastiCache, dbEngineRedshift, } @@ -561,6 +566,57 @@ func getDatabaseSchema(typ schema.ValueType) schemaMap { MaxItems: 1, ConflictsWith: util.CalculateConflictsWith(dbEngineOracle.Name(), dbEngineTypes), }, + dbEngineRedis.name: { + Type: typ, + Optional: true, + Description: "Connection parameters for the redis-database-plugin plugin.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "host": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the host to connect to", + }, + "port": { + Type: schema.TypeInt, + Optional: true, + Description: "The transport port to use to connect to Redis.", + Default: 6379, + ValidateFunc: validation.IsPortNumber, + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the username for Vault to use.", + }, + "password": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the password corresponding to the given username.", + Sensitive: true, + }, + "tls": { + Type: schema.TypeBool, + Optional: true, + Description: "Specifies whether to use TLS when connecting to Redis.", + Default: false, + }, + "insecure_tls": { + Type: schema.TypeBool, + Optional: true, + Description: "Specifies whether to skip verification of the server certificate when using TLS.", + Default: false, + }, + "ca_cert": { + Type: schema.TypeString, + Optional: true, + Description: "The path to a PEM-encoded CA cert file to use to verify the Redis server's identity", + }, + }, + }, + MaxItems: 1, + ConflictsWith: util.CalculateConflictsWith(dbEngineRedis.Name(), dbEngineTypes), + }, dbEngineRedisElastiCache.name: { Type: typ, Optional: true, @@ -872,6 +928,8 @@ func getDatabaseAPIDataForEngine(engine *dbEngine, idx int, d *schema.ResourceDa setDatabaseConnectionDataWithDisableEscaping(d, prefix, data) case dbEngineElasticSearch: setElasticsearchDatabaseConnectionData(d, prefix, data) + case dbEngineRedis: + setRedisDatabaseConnectionData(d, prefix, data) case dbEngineRedisElastiCache: setRedisElastiCacheDatabaseConnectionData(d, prefix, data) case dbEngineSnowflake: @@ -991,6 +1049,43 @@ func getMySQLConnectionDetailsFromResponse(d *schema.ResourceData, prefix string return result } +func getRedisConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, resp *api.Secret) map[string]interface{} { + details := resp.Data["connection_details"] + data, ok := details.(map[string]interface{}) + if !ok { + return nil + } + result := map[string]interface{}{} + + if v, ok := data["host"]; ok { + result["host"] = v.(string) + } + if v, ok := data["port"]; ok { + port, _ := v.(json.Number).Int64() + result["port"] = port + } + if v, ok := data["username"]; ok { + result["username"] = v.(string) + } + if v, ok := data["password"]; ok { + result["password"] = v.(string) + } else if v, ok := d.GetOk(prefix + "password"); ok { + // keep the password we have in state/config if the API doesn't return one + result["password"] = v.(string) + } + if v, ok := data["tls"]; ok { + result["tls"] = v.(bool) + } + if v, ok := data["insecure_tls"]; ok { + result["insecure_tls"] = v.(bool) + } + if v, ok := data["ca_cert"]; ok { + result["ca_cert"] = v.(string) + } + + return result +} + func getRedisElastiCacheConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, resp *api.Secret) map[string]interface{} { details := resp.Data["connection_details"] data, ok := details.(map[string]interface{}) @@ -1254,6 +1349,30 @@ func setMySQLDatabaseConnectionData(d *schema.ResourceData, prefix string, data } } +func setRedisDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) { + if v, ok := d.GetOk(prefix + "host"); ok { + data["host"] = v.(string) + } + if v, ok := d.GetOk(prefix + "port"); ok { + data["port"] = v.(int) + } + 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 + "tls"); ok { + data["tls"] = v.(bool) + } + if v, ok := d.GetOk(prefix + "insecure_tls"); ok { + data["insecure_tls"] = v.(bool) + } + if v, ok := d.GetOk(prefix + "ca_cert"); ok { + data["ca_cert"] = v.(string) + } +} + func setRedisElastiCacheDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) { if v, ok := d.GetOk(prefix + "url"); ok { data["url"] = v.(string) @@ -1649,6 +1768,8 @@ func getDBConnectionConfig(d *schema.ResourceData, engine *dbEngine, idx int, result = getElasticsearchConnectionDetailsFromResponse(d, prefix, resp) case dbEngineSnowflake: result = getSnowflakeConnectionDetailsFromResponse(d, prefix, resp) + case dbEngineRedis: + result = getRedisConnectionDetailsFromResponse(d, prefix, resp) case dbEngineRedisElastiCache: result = getRedisElastiCacheConnectionDetailsFromResponse(d, prefix, resp) case dbEngineRedshift: diff --git a/vault/resource_database_secret_backend_connection_test.go b/vault/resource_database_secret_backend_connection_test.go index 9460889bb..f28f41ef0 100644 --- a/vault/resource_database_secret_backend_connection_test.go +++ b/vault/resource_database_secret_backend_connection_test.go @@ -842,6 +842,45 @@ func TestAccDatabaseSecretBackendConnection_snowflake(t *testing.T) { }) } +func TestAccDatabaseSecretBackendConnection_redis(t *testing.T) { + MaybeSkipDBTests(t, dbEngineRedis) + + values := testutil.SkipTestEnvUnset(t, "REDIS_HOST", "REDIS_USERNAME", "REDIS_PASSWORD") + host := values[0] + username := values[1] + password := values[2] + + backend := acctest.RandomWithPrefix("tf-test-db") + pluginName := dbEngineRedis.DefaultPluginName() + name := acctest.RandomWithPrefix("db") + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDatabaseSecretBackendConnectionConfig_redis(name, backend, host, username, password), + Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName, + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.#", "1"), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "allowed_roles.0", "*"), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "verify_connection", "true"), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.host", host), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.port", "6379"), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.username", username), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.password", password), + ), + }, + { + ResourceName: testDefaultDatabaseSecretBackendResource, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"verify_connection", "redis.0.password"}, + }, + }, + }) +} + func TestAccDatabaseSecretBackendConnection_redisElastiCache(t *testing.T) { MaybeSkipDBTests(t, dbEngineRedisElastiCache) @@ -1453,6 +1492,29 @@ resource "vault_database_secret_backend_connection" "test" { `, path, name, url, username, password, userTempl) } +func testAccDatabaseSecretBackendConnectionConfig_redis(name, path, host, username, password string) string { + config := fmt.Sprintf(` +resource "vault_mount" "db" { + path = "%s" + type = "database" +} +`, path) + + config += fmt.Sprintf(` +resource "vault_database_secret_backend_connection" "test" { + backend = vault_mount.db.path + name = "%s" + allowed_roles = ["*"] + redis { + host = "%s" + username = "%s" + password = "%s" + } +}`, name, host, username, password) + + return config +} + func testAccDatabaseSecretBackendConnectionConfig_redis_elasticache(name, path, connURL string) string { config := fmt.Sprintf(` resource "vault_mount" "db" { diff --git a/website/docs/r/database_secret_backend_connection.md b/website/docs/r/database_secret_backend_connection.md index 6a5281172..4808daa65 100644 --- a/website/docs/r/database_secret_backend_connection.md +++ b/website/docs/r/database_secret_backend_connection.md @@ -92,6 +92,8 @@ The following arguments are supported: * `influxdb` - (Optional) A nested block containing configuration options for InfluxDB connections. +* `redis` - (Optional) A nested block containing configuration options for Redis connections. + * `redis_elasticache` - (Optional) A nested block containing configuration options for Redis ElastiCache connections. Exactly one of the nested blocks of configuration options must be supplied. @@ -167,6 +169,24 @@ Exactly one of the nested blocks of configuration options must be supplied. * `connect_timeout` - (Optional) The number of seconds to use as a connection timeout. +### Redis Configuration Options + +* `host` - (Required) The host to connect to. + +* `username` - (Required) The username to authenticate with. + +* `password` - (Required) The password to authenticate with. + +* `port` - (Required) The default port to connect to if no port is specified as + part of the host. + +* `tls` - (Optional) Whether to use TLS when connecting to Redis. + +* `insecure_tls` - (Optional) Whether to skip verification of the server + certificate when using TLS. + +* `ca_cert` - (Optional) The path to a PEM-encoded CA cert file to use to verify the Redis server's identity. + ### Redis ElastiCache Configuration Options * `url` - (Required) The url to connect to including the port; e.g. master.my-cluster.xxxxxx.use1.cache.amazonaws.com:6379. diff --git a/website/docs/r/database_secrets_mount.md b/website/docs/r/database_secrets_mount.md index 72d507ed4..3d05aac54 100644 --- a/website/docs/r/database_secrets_mount.md +++ b/website/docs/r/database_secrets_mount.md @@ -163,7 +163,10 @@ Supported list of database secrets engines that can be configured: * `influxdb` - (Optional) A nested block containing configuration options for InfluxDB connections. *See [Configuration Options](#influxdb-configuration-options) for more info* -* `redis_elasticache` - (Optional) A nested block containing configuration options for InfluxDB connections. +* `redis` - (Optional) A nested block containing configuration options for Redis connections. + *See [Configuration Options](#redis-configuration-options) for more info* + +* `redis_elasticache` - (Optional) A nested block containing configuration options for Redis ElastiCache connections. *See [Configuration Options](#redis-elasticache-configuration-options) for more info* ### Cassandra Configuration Options @@ -397,6 +400,24 @@ Supported list of database secrets engines that can be configured: * `username_template` - (Optional) For Vault v1.7+. The template to use for username generation. See [Vault docs](https://www.vaultproject.io/docs/concepts/username-templating) +### Redis Configuration Options + +* `host` - (Required) The host to connect to. + +* `username` - (Required) The username to authenticate with. + +* `password` - (Required) The password to authenticate with. + +* `port` - (Optional) The default port to connect to if no port is specified as + part of the host. + +* `tls` - (Optional) Whether to use TLS when connecting to Redis. + +* `insecure_tls` - (Optional) Whether to skip verification of the server + certificate when using TLS. + +* `ca_cert` - (Optional) The path to a PEM-encoded CA cert file to use to verify the Redis server's identity. + ### Redis ElastiCache Configuration Options * `url` - (Required) The configuration endpoint for the ElastiCache cluster to connect to. From 8563a8c5233e40de6833a1b7d4a9ce0317d7b1ed Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Wed, 2 Nov 2022 09:25:54 -0700 Subject: [PATCH 2/5] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2397d40..0e7e7ddf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ IMPROVEMENTS: * Add support for externally managed Group Member IDs to Vault Identity Group: ([#1630](https://github.com/hashicorp/terraform-provider-vault/pull/1630)) * Support configuring vault version handling: ([#1646](https://github.com/hashicorp/terraform-provider-vault/pull/1646)) +* Add Redis database secrets engine support: ([#1659](https://github.com/hashicorp/terraform-provider-vault/pull/1659)) BUGS: * Ensure that namespaced github auth mounts are destroyed: ([#1637](https://github.com/hashicorp/terraform-provider-vault/pull/1637)) From 8dadf48c39de2f7e94c13a89ca3dcc76890491c1 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Fri, 4 Nov 2022 15:49:37 -0700 Subject: [PATCH 3/5] update with suggestions --- CHANGELOG.md | 1 - vault/resource_database_secret_backend_connection.go | 3 ++- vault/resource_database_secret_backend_connection_test.go | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7e7ddf4..4e2397d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ IMPROVEMENTS: * Add support for externally managed Group Member IDs to Vault Identity Group: ([#1630](https://github.com/hashicorp/terraform-provider-vault/pull/1630)) * Support configuring vault version handling: ([#1646](https://github.com/hashicorp/terraform-provider-vault/pull/1646)) -* Add Redis database secrets engine support: ([#1659](https://github.com/hashicorp/terraform-provider-vault/pull/1659)) BUGS: * Ensure that namespaced github auth mounts are destroyed: ([#1637](https://github.com/hashicorp/terraform-provider-vault/pull/1637)) diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index 9c32a2bb8..0d0c90c5d 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -610,7 +610,8 @@ func getDatabaseSchema(typ schema.ValueType) schemaMap { "ca_cert": { Type: schema.TypeString, Optional: true, - Description: "The path to a PEM-encoded CA cert file to use to verify the Redis server's identity", + Description: "The contents of a PEM-encoded CA cert file to use to verify the Redis server's identity", + Default: "", }, }, }, diff --git a/vault/resource_database_secret_backend_connection_test.go b/vault/resource_database_secret_backend_connection_test.go index f28f41ef0..92edc2a8b 100644 --- a/vault/resource_database_secret_backend_connection_test.go +++ b/vault/resource_database_secret_backend_connection_test.go @@ -869,6 +869,8 @@ func TestAccDatabaseSecretBackendConnection_redis(t *testing.T) { resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.port", "6379"), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.username", username), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.password", password), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.tls", "false"), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "redis.0.insecure_tls", "false"), ), }, { From e9095547fa8c45676855120a9a5f72fe87372ec1 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Tue, 8 Nov 2022 16:22:40 -0700 Subject: [PATCH 4/5] update w suggestions --- ...source_database_secret_backend_connection.go | 17 +++++++++-------- .../r/database_secret_backend_connection.md | 2 +- website/docs/r/database_secrets_mount.md | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index 0d0c90c5d..4f7c4ac81 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -602,16 +602,17 @@ func getDatabaseSchema(typ schema.ValueType) schemaMap { Default: false, }, "insecure_tls": { - Type: schema.TypeBool, - Optional: true, - Description: "Specifies whether to skip verification of the server certificate when using TLS.", - Default: false, + Type: schema.TypeBool, + Optional: true, + Description: "Specifies whether to skip verification of the server " + + "certificate when using TLS.", + Default: false, }, "ca_cert": { - Type: schema.TypeString, - Optional: true, - Description: "The contents of a PEM-encoded CA cert file to use to verify the Redis server's identity", - Default: "", + Type: schema.TypeString, + Optional: true, + Description: "The contents of a PEM-encoded CA cert file " + + "to use to verify the Redis server's identity", }, }, }, diff --git a/website/docs/r/database_secret_backend_connection.md b/website/docs/r/database_secret_backend_connection.md index 4808daa65..85c22a6c8 100644 --- a/website/docs/r/database_secret_backend_connection.md +++ b/website/docs/r/database_secret_backend_connection.md @@ -185,7 +185,7 @@ Exactly one of the nested blocks of configuration options must be supplied. * `insecure_tls` - (Optional) Whether to skip verification of the server certificate when using TLS. -* `ca_cert` - (Optional) The path to a PEM-encoded CA cert file to use to verify the Redis server's identity. +* `ca_cert` - (Optional) The contents of a PEM-encoded CA cert file to use to verify the Redis server's identity. ### Redis ElastiCache Configuration Options diff --git a/website/docs/r/database_secrets_mount.md b/website/docs/r/database_secrets_mount.md index 3d05aac54..29bdbebaa 100644 --- a/website/docs/r/database_secrets_mount.md +++ b/website/docs/r/database_secrets_mount.md @@ -416,7 +416,7 @@ Supported list of database secrets engines that can be configured: * `insecure_tls` - (Optional) Whether to skip verification of the server certificate when using TLS. -* `ca_cert` - (Optional) The path to a PEM-encoded CA cert file to use to verify the Redis server's identity. +* `ca_cert` - (Optional) The contents of a PEM-encoded CA cert file to use to verify the Redis server's identity. ### Redis ElastiCache Configuration Options From 200af011687422cbc4398671ba7b26e460127a49 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Tue, 8 Nov 2022 16:29:05 -0700 Subject: [PATCH 5/5] fix --- vault/resource_database_secret_backend_connection.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index 4f7c4ac81..36d8263b7 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -612,7 +612,8 @@ func getDatabaseSchema(typ schema.ValueType) schemaMap { Type: schema.TypeString, Optional: true, Description: "The contents of a PEM-encoded CA cert file " + - "to use to verify the Redis server's identity", + "to use to verify the Redis server's identity.", + Default: false, }, }, },