Elasticsearch support in vault_database_secret_backend_connection (#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
This commit is contained in:
Tuomas Vähänen 2020-04-01 23:17:38 +00:00 committed by GitHub
parent 3f89dde8c8
commit b0cdcbe5a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 1 deletions

View file

@ -1,3 +1,6 @@
export VAULT_ADDR="http://localhost:8200"
export VAULT_TOKEN="TEST"
export MYSQL_URL="vault:vault@tcp(mysql:3306)/"
export ELASTIC_URL="http://elastic:9200"
export ELASTIC_USERNAME="elastic"
export ELASTIC_PASSWORD="elastic"

View file

@ -21,3 +21,12 @@ services:
MYSQL_DATABASE: "main"
MYSQL_USER: "vault"
MYSQL_PASSWORD: "vault"
elastic:
image: elasticsearch:7.6.0
ports:
- "9200:9200"
environment:
"discovery.type": "single-node"
"xpack.security.enabled": "true"
"ELASTIC_PASSWORD": "elastic"

View file

@ -16,7 +16,7 @@ import (
var (
databaseSecretBackendConnectionBackendFromPathRegex = regexp.MustCompile("^(.+)/config/.+$")
databaseSecretBackendConnectionNameFromPathRegex = regexp.MustCompile("^.+/config/(.+$)")
dbBackendTypes = []string{"cassandra", "hana", "mongodb", "mssql", "mysql", "mysql_rds", "mysql_aurora", "mysql_legacy", "postgresql", "oracle"}
dbBackendTypes = []string{"cassandra", "hana", "mongodb", "mssql", "mysql", "mysql_rds", "mysql_aurora", "mysql_legacy", "postgresql", "oracle", "elasticsearch"}
)
func databaseSecretBackendConnectionResource() *schema.Resource {
@ -66,6 +66,34 @@ func databaseSecretBackendConnectionResource() *schema.Resource {
Sensitive: true,
},
"elasticsearch": {
Type: schema.TypeList,
Optional: true,
Description: "Connection parameters for the elasticsearch-database-plugin.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"url": {
Type: schema.TypeString,
Required: true,
Description: "The URL for Elasticsearch's API",
},
"username": {
Type: schema.TypeString,
Required: true,
Description: "The username to be used in the connection URL",
},
"password": {
Type: schema.TypeString,
Required: true,
Description: "The password to be used in the connection URL",
Sensitive: true,
},
},
},
MaxItems: 1,
ConflictsWith: util.CalculateConflictsWith("elasticsearch", dbBackendTypes),
},
"cassandra": {
Type: schema.TypeList,
Optional: true,
@ -281,6 +309,8 @@ func getDatabasePluginName(d *schema.ResourceData) (string, error) {
return "oracle-database-plugin", nil
case len(d.Get("postgresql").([]interface{})) > 0:
return "postgresql-database-plugin", nil
case len(d.Get("elasticsearch").([]interface{})) > 0:
return "elasticsearch-database-plugin", nil
default:
return "", fmt.Errorf("at least one database plugin must be configured")
}
@ -353,6 +383,8 @@ func getDatabaseAPIData(d *schema.ResourceData) (map[string]interface{}, error)
setDatabaseConnectionData(d, "oracle.0.", data)
case "postgresql-database-plugin":
setDatabaseConnectionData(d, "postgresql.0.", data)
case "elasticsearch-database-plugin":
setElasticsearchDatabaseConnectionData(d, "elasticsearch.0.", data)
}
return data, nil
@ -399,6 +431,34 @@ func getConnectionDetailsFromResponse(d *schema.ResourceData, prefix string, res
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{})
if !ok {
return nil
}
result := map[string]interface{}{}
if v, ok := d.GetOk(prefix + "url"); ok {
result["url"] = v.(string)
} else {
if v, ok := data["url"]; ok {
result["url"] = v.(string)
}
}
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)
}
return []map[string]interface{}{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)
@ -414,6 +474,20 @@ func setDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[s
}
}
func setElasticsearchDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) {
if v, ok := d.GetOk(prefix + "url"); ok {
data["url"] = v.(string)
}
if v, ok := d.GetOk(prefix + "username"); ok {
data["username"] = v.(string)
}
if v, ok := d.GetOk(prefix + "password"); ok {
data["password"] = v.(string)
}
}
func databaseSecretBackendConnectionCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)
@ -564,6 +638,8 @@ func databaseSecretBackendConnectionRead(d *schema.ResourceData, meta interface{
d.Set("oracle", getConnectionDetailsFromResponse(d, "oracle.0.", resp))
case "postgresql-database-plugin":
d.Set("postgresql", getConnectionDetailsFromResponse(d, "postgresql.0.", resp))
case "elasticsearch-database-plugin":
d.Set("elasticsearch", getElasticsearchConnectionDetailsFromResponse(d, "elasticsearch.0.", resp))
}
if err != nil {

View file

@ -372,6 +372,38 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) {
})
}
func TestAccDatabaseSecretBackendConnection_elasticsearch(t *testing.T) {
connURL := os.Getenv("ELASTIC_URL")
if connURL == "" {
t.Skip("ELASTIC_URL not set")
}
username := os.Getenv("ELASTIC_USERNAME")
password := os.Getenv("ELASTIC_PASSWORD")
backend := acctest.RandomWithPrefix("tf-test-db")
name := acctest.RandomWithPrefix("db")
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAccDatabaseSecretBackendConnectionConfig_elasticsearch(name, backend, connURL, username, password),
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", "verify_connection", "true"),
resource.TestCheckResourceAttr("vault_database_secret_backend_connection.test", "elasticsearch.0.url", connURL),
),
},
},
})
}
func testAccDatabaseSecretBackendConnectionCheckDestroy(s *terraform.State) error {
client := testProvider.Meta().(*api.Client)
@ -437,6 +469,28 @@ resource "vault_database_secret_backend_connection" "test" {
`, path, name, host, username, password)
}
func testAccDatabaseSecretBackendConnectionConfig_elasticsearch(name, path, host, username, password 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"]
elasticsearch {
url = "%s"
username = "%s"
password = "%s"
}
}
`, path, name, host, username, password)
}
func testAccDatabaseSecretBackendConnectionConfig_mongodb(name, path, connURL string) string {
return fmt.Sprintf(`
resource "vault_mount" "db" {

View file

@ -75,6 +75,8 @@ The following arguments are supported:
* `oracle` - (Optional) A nested block containing configuration options for Oracle connections.
* `elasticsearch` - (Optional) A nested block containing configuration options for Elasticsearch connections.
Exactly one of the nested blocks of configuration options must be supplied.
### Cassandra Configuration Options
@ -190,6 +192,15 @@ Exactly one of the nested blocks of configuration options must be supplied.
* `max_connection_lifetime` - (Optional) The maximum number of seconds to keep
a connection alive for.
### Elasticsearch Configuration Options
* `url` - (Required) The URL for Elasticsearch's API. https requires certificate
by trusted CA if used.
* `username` - (Required) The username to be used in the connection.
* `password` - (Required) The password to be used in the connection.
## Attributes Reference
No additional attributes are exported by this resource.