Skip to content

Commit b0cdcbe

Browse files
authored
Elasticsearch support in vault_database_secret_backend_connection (hashicorp#704)
* Add basic elasticsearch variables and handling * Added elasticsearch to documentation * Added basic acceptance test, and elastic node to docker-compose * Mark elasticsearch url, username and password properties as required
1 parent 3f89dde commit b0cdcbe

5 files changed

+154
-1
lines changed

.test-env

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export VAULT_ADDR="http://localhost:8200"
22
export VAULT_TOKEN="TEST"
33
export MYSQL_URL="vault:vault@tcp(mysql:3306)/"
4+
export ELASTIC_URL="http://elastic:9200"
5+
export ELASTIC_USERNAME="elastic"
6+
export ELASTIC_PASSWORD="elastic"

docker-compose.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ services:
2121
MYSQL_DATABASE: "main"
2222
MYSQL_USER: "vault"
2323
MYSQL_PASSWORD: "vault"
24+
25+
elastic:
26+
image: elasticsearch:7.6.0
27+
ports:
28+
- "9200:9200"
29+
environment:
30+
"discovery.type": "single-node"
31+
"xpack.security.enabled": "true"
32+
"ELASTIC_PASSWORD": "elastic"

vault/resource_database_secret_backend_connection.go

+77-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
var (
1717
databaseSecretBackendConnectionBackendFromPathRegex = regexp.MustCompile("^(.+)/config/.+$")
1818
databaseSecretBackendConnectionNameFromPathRegex = regexp.MustCompile("^.+/config/(.+$)")
19-
dbBackendTypes = []string{"cassandra", "hana", "mongodb", "mssql", "mysql", "mysql_rds", "mysql_aurora", "mysql_legacy", "postgresql", "oracle"}
19+
dbBackendTypes = []string{"cassandra", "hana", "mongodb", "mssql", "mysql", "mysql_rds", "mysql_aurora", "mysql_legacy", "postgresql", "oracle", "elasticsearch"}
2020
)
2121

2222
func databaseSecretBackendConnectionResource() *schema.Resource {
@@ -66,6 +66,34 @@ func databaseSecretBackendConnectionResource() *schema.Resource {
6666
Sensitive: true,
6767
},
6868

69+
"elasticsearch": {
70+
Type: schema.TypeList,
71+
Optional: true,
72+
Description: "Connection parameters for the elasticsearch-database-plugin.",
73+
Elem: &schema.Resource{
74+
Schema: map[string]*schema.Schema{
75+
"url": {
76+
Type: schema.TypeString,
77+
Required: true,
78+
Description: "The URL for Elasticsearch's API",
79+
},
80+
"username": {
81+
Type: schema.TypeString,
82+
Required: true,
83+
Description: "The username to be used in the connection URL",
84+
},
85+
"password": {
86+
Type: schema.TypeString,
87+
Required: true,
88+
Description: "The password to be used in the connection URL",
89+
Sensitive: true,
90+
},
91+
},
92+
},
93+
MaxItems: 1,
94+
ConflictsWith: util.CalculateConflictsWith("elasticsearch", dbBackendTypes),
95+
},
96+
6997
"cassandra": {
7098
Type: schema.TypeList,
7199
Optional: true,
@@ -281,6 +309,8 @@ func getDatabasePluginName(d *schema.ResourceData) (string, error) {
281309
return "oracle-database-plugin", nil
282310
case len(d.Get("postgresql").([]interface{})) > 0:
283311
return "postgresql-database-plugin", nil
312+
case len(d.Get("elasticsearch").([]interface{})) > 0:
313+
return "elasticsearch-database-plugin", nil
284314
default:
285315
return "", fmt.Errorf("at least one database plugin must be configured")
286316
}
@@ -353,6 +383,8 @@ func getDatabaseAPIData(d *schema.ResourceData) (map[string]interface{}, error)
353383
setDatabaseConnectionData(d, "oracle.0.", data)
354384
case "postgresql-database-plugin":
355385
setDatabaseConnectionData(d, "postgresql.0.", data)
386+
case "elasticsearch-database-plugin":
387+
setElasticsearchDatabaseConnectionData(d, "elasticsearch.0.", data)
356388
}
357389

358390
return data, nil
@@ -399,6 +431,34 @@ func getConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, res
399431
return []map[string]interface{}{result}
400432
}
401433

434+
func getElasticsearchConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, resp *api.Secret) []map[string]interface{} {
435+
details := resp.Data["connection_details"]
436+
data, ok := details.(map[string]interface{})
437+
if !ok {
438+
return nil
439+
}
440+
result := map[string]interface{}{}
441+
if v, ok := d.GetOk(prefix + "url"); ok {
442+
result["url"] = v.(string)
443+
} else {
444+
if v, ok := data["url"]; ok {
445+
result["url"] = v.(string)
446+
}
447+
}
448+
449+
if v, ok := data["username"]; ok {
450+
result["username"] = v.(string)
451+
}
452+
if v, ok := data["password"]; ok {
453+
result["password"] = v.(string)
454+
} else if v, ok := d.GetOk(prefix + "password"); ok {
455+
// keep the password we have in state/config if the API doesn't return one
456+
result["password"] = v.(string)
457+
}
458+
459+
return []map[string]interface{}{result}
460+
}
461+
402462
func setDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) {
403463
if v, ok := d.GetOk(prefix + "connection_url"); ok {
404464
data["connection_url"] = v.(string)
@@ -414,6 +474,20 @@ func setDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[s
414474
}
415475
}
416476

477+
func setElasticsearchDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) {
478+
if v, ok := d.GetOk(prefix + "url"); ok {
479+
data["url"] = v.(string)
480+
}
481+
482+
if v, ok := d.GetOk(prefix + "username"); ok {
483+
data["username"] = v.(string)
484+
}
485+
486+
if v, ok := d.GetOk(prefix + "password"); ok {
487+
data["password"] = v.(string)
488+
}
489+
}
490+
417491
func databaseSecretBackendConnectionCreate(d *schema.ResourceData, meta interface{}) error {
418492
client := meta.(*api.Client)
419493

@@ -564,6 +638,8 @@ func databaseSecretBackendConnectionRead(d *schema.ResourceData, meta interface{
564638
d.Set("oracle", getConnectionDetailsFromResponse(d, "oracle.0.", resp))
565639
case "postgresql-database-plugin":
566640
d.Set("postgresql", getConnectionDetailsFromResponse(d, "postgresql.0.", resp))
641+
case "elasticsearch-database-plugin":
642+
d.Set("elasticsearch", getElasticsearchConnectionDetailsFromResponse(d, "elasticsearch.0.", resp))
567643
}
568644

569645
if err != nil {

vault/resource_database_secret_backend_connection_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,38 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) {
372372
})
373373
}
374374

375+
func TestAccDatabaseSecretBackendConnection_elasticsearch(t *testing.T) {
376+
connURL := os.Getenv("ELASTIC_URL")
377+
if connURL == "" {
378+
t.Skip("ELASTIC_URL not set")
379+
}
380+
381+
username := os.Getenv("ELASTIC_USERNAME")
382+
password := os.Getenv("ELASTIC_PASSWORD")
383+
backend := acctest.RandomWithPrefix("tf-test-db")
384+
name := acctest.RandomWithPrefix("db")
385+
386+
resource.Test(t, resource.TestCase{
387+
Providers: testProviders,
388+
PreCheck: func() { testAccPreCheck(t) },
389+
CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy,
390+
Steps: []resource.TestStep{
391+
{
392+
Config: testAccDatabaseSecretBackendConnectionConfig_elasticsearch(name, backend, connURL, username, password),
393+
Check: resource.ComposeTestCheckFunc(
394+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "name", name),
395+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "backend", backend),
396+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "allowed_roles.#", "2"),
397+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "allowed_roles.0", "dev"),
398+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "allowed_roles.1", "prod"),
399+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "verify_connection", "true"),
400+
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "elasticsearch.0.url", connURL),
401+
),
402+
},
403+
},
404+
})
405+
}
406+
375407
func testAccDatabaseSecretBackendConnectionCheckDestroy(s *terraform.State) error {
376408
client := testProvider.Meta().(*api.Client)
377409

@@ -437,6 +469,28 @@ resource "vault_database_secret_backend_connection" "test" {
437469
`, path, name, host, username, password)
438470
}
439471

472+
func testAccDatabaseSecretBackendConnectionConfig_elasticsearch(name, path, host, username, password string) string {
473+
return fmt.Sprintf(`
474+
resource "vault_mount" "db" {
475+
path = "%s"
476+
type = "database"
477+
}
478+
479+
resource "vault_database_secret_backend_connection" "test" {
480+
backend = "${vault_mount.db.path}"
481+
name = "%s"
482+
allowed_roles = ["dev", "prod"]
483+
root_rotation_statements = ["FOOBAR"]
484+
485+
elasticsearch {
486+
url = "%s"
487+
username = "%s"
488+
password = "%s"
489+
}
490+
}
491+
`, path, name, host, username, password)
492+
}
493+
440494
func testAccDatabaseSecretBackendConnectionConfig_mongodb(name, path, connURL string) string {
441495
return fmt.Sprintf(`
442496
resource "vault_mount" "db" {

website/docs/r/database_secret_backend_connection.md

+11
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ The following arguments are supported:
7575

7676
* `oracle` - (Optional) A nested block containing configuration options for Oracle connections.
7777

78+
* `elasticsearch` - (Optional) A nested block containing configuration options for Elasticsearch connections.
79+
7880
Exactly one of the nested blocks of configuration options must be supplied.
7981

8082
### Cassandra Configuration Options
@@ -190,6 +192,15 @@ Exactly one of the nested blocks of configuration options must be supplied.
190192
* `max_connection_lifetime` - (Optional) The maximum number of seconds to keep
191193
a connection alive for.
192194

195+
### Elasticsearch Configuration Options
196+
197+
* `url` - (Required) The URL for Elasticsearch's API. https requires certificate
198+
by trusted CA if used.
199+
200+
* `username` - (Required) The username to be used in the connection.
201+
202+
* `password` - (Required) The password to be used in the connection.
203+
193204
## Attributes Reference
194205

195206
No additional attributes are exported by this resource.

0 commit comments

Comments
 (0)