Skip to content

Commit

Permalink
Add database secret backend role resource.
Browse files Browse the repository at this point in the history
Add a resource that will allow the creation and management of database
secret backend roles. These roles are what are ultimately used to
generate temporary credentials for the database.

This relies on #37, which should be merged before this.
  • Loading branch information
paddycarver committed Nov 13, 2017
1 parent 4e86156 commit 7bef763
Show file tree
Hide file tree
Showing 5 changed files with 484 additions and 0 deletions.
1 change: 1 addition & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func Provider() terraform.ResourceProvider {
"vault_aws_secret_backend": awsSecretBackendResource(),
"vault_aws_secret_backend_role": awsSecretBackendRoleResource(),
"vault_database_secret_backend_connection": databaseSecretBackendConnectionResource(),
"vault_database_secret_backend_role": databaseSecretBackendRoleResource(),
"vault_generic_secret": genericSecretResource(),
"vault_policy": policyResource(),
"vault_mount": mountResource(),
Expand Down
228 changes: 228 additions & 0 deletions vault/resource_database_secret_backend_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package vault

import (
"encoding/json"
"fmt"
"log"
"regexp"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/vault/api"
)

var (
databaseSecretBackendRoleBackendFromPathRegex = regexp.MustCompile("^(.+)/roles/.+$")
databaseSecretBackendRoleNameFromPathRegex = regexp.MustCompile("^.+/roles/(.+$)")
)

func databaseSecretBackendRoleResource() *schema.Resource {
return &schema.Resource{
Create: databaseSecretBackendRoleWrite,
Read: databaseSecretBackendRoleRead,
Update: databaseSecretBackendRoleWrite,
Delete: databaseSecretBackendRoleDelete,
Exists: databaseSecretBackendRoleExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Unique name for the role.",
},
"backend": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The path of the Database Secret Backend the role belongs to.",
},
"db_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Database connection to use for this role.",
},
"default_ttl": {
Type: schema.TypeInt,
Optional: true,
Description: "Default TTL for leases associated with this role, in seconds.",
},
"max_ttl": {
Type: schema.TypeInt,
Optional: true,
Description: "Maximum TTL for leases associated with this role, in seconds.",
},
"creation_statements": {
Type: schema.TypeString,
Required: true,
Description: "Database statements to execute to create and configure a user.",
},
"revocation_statements": {
Type: schema.TypeString,
Optional: true,
Description: "Database statements to execute to revoke a user.",
},
"rollback_statements": {
Type: schema.TypeString,
Optional: true,
Description: "Database statements to execute to rollback a create operation in the event of an error.",
},
"renew_statements": {
Type: schema.TypeString,
Optional: true,
Description: "Database statements to execute to renew a user.",
},
},
}
}

func databaseSecretBackendRoleWrite(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

backend := d.Get("backend").(string)
name := d.Get("name").(string)

path := databaseSecretBackendRolePath(backend, name)

data := map[string]interface{}{
"db_name": d.Get("db_name").(string),
"creation_statements": d.Get("creation_statements").(string),
}

if v, ok := d.GetOkExists("default_ttl"); ok {
data["default_ttl"] = v.(int)
}
if v, ok := d.GetOkExists("max_ttl"); ok {
data["max_ttl"] = v.(int)
}
if v, ok := d.GetOkExists("revocation_statements"); ok {
data["revocation_statements"] = v.(string)
}
if v, ok := d.GetOkExists("rollback_statements"); ok {
data["rollback_statements"] = v.(string)
}
if v, ok := d.GetOkExists("renew_statements"); ok {
data["renew_statements"] = v.(string)
}

log.Printf("[DEBUG] Creating role %q on database backend %q", name, backend)
_, err := client.Logical().Write(path, data)
if err != nil {
return fmt.Errorf("Error creating role %q for backend %q: %s", name, backend, err)
}
log.Printf("[DEBUG] Created role %q on AWS backend %q", name, backend)

d.SetId(path)
return databaseSecretBackendRoleRead(d, meta)
}

func databaseSecretBackendRoleRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

path := d.Id()

name, err := databaseSecretBackendRoleNameFromPath(path)
if err != nil {
log.Printf("[WARN] Removing database role %q because its ID is invalid", path)
d.SetId("")
return fmt.Errorf("Invalid role ID %q: %s", path, err)
}

backend, err := databaseSecretBackendRoleBackendFromPath(path)
if err != nil {
log.Printf("[WARN] Removing database role %q because its ID is invalid", path)
d.SetId("")
return fmt.Errorf("Invalid role ID %q: %s", path, err)
}

log.Printf("[DEBUG] Reading role from %q", path)
secret, err := client.Logical().Read(path)
if err != nil {
return fmt.Errorf("Error reading role %q: %s", path, err)
}
log.Printf("[DEBUG] Read role from %q", path)
if secret == nil {
log.Printf("[WARN] Role %q not found, removing from state", path)
d.SetId("")
return nil
}
d.Set("backend", backend)
d.Set("name", name)
d.Set("db_name", secret.Data["db_name"])
d.Set("creation_statements", secret.Data["creation_statements"])
d.Set("revocation_statements", secret.Data["revocation_statements"])
d.Set("rollback_statements", secret.Data["rollback_statements"])
d.Set("renew_statements", secret.Data["renew_statements"])

if v, ok := secret.Data["default_ttl"]; ok {
n, err := v.(json.Number).Int64()
if err != nil {
return fmt.Errorf("Unexpected value %q for default_ttl of %q", v, path)
}
d.Set("default_ttl", n)
}
if v, ok := secret.Data["max_ttl"]; ok {
n, err := v.(json.Number).Int64()
if err != nil {
return fmt.Errorf("Unexpected value %q for max_ttl of %q", v, path)
}
d.Set("max_ttl", n)
}
return nil
}

func databaseSecretBackendRoleDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

path := d.Id()
log.Printf("[DEBUG] Deleting role %q", path)
_, err := client.Logical().Delete(path)
if err != nil {
return fmt.Errorf("Error deleting role %q: %s", path, err)
}
log.Printf("[DEBUG] Deleted role %q", path)
return nil
}

func databaseSecretBackendRoleExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*api.Client)

path := d.Id()
log.Printf("[DEBUG] Checking if %q exists", path)
secret, err := client.Logical().Read(path)
if err != nil {
return true, fmt.Errorf("Error checking if %q exists: %s", path, err)
}
log.Printf("[DEBUG] Checked if %q exists", path)
return secret != nil, nil
}

func databaseSecretBackendRolePath(backend, name string) string {
return strings.Trim(backend, "/") + "/roles/" + strings.Trim(name, "/")
}

func databaseSecretBackendRoleNameFromPath(path string) (string, error) {
if !databaseSecretBackendRoleNameFromPathRegex.MatchString(path) {
return "", fmt.Errorf("no name found")
}
res := databaseSecretBackendRoleNameFromPathRegex.FindStringSubmatch(path)
if len(res) != 2 {
return "", fmt.Errorf("unexpected number of matches (%d) for name", len(res))
}
return res[1], nil
}

func databaseSecretBackendRoleBackendFromPath(path string) (string, error) {
if !databaseSecretBackendRoleBackendFromPathRegex.MatchString(path) {
return "", fmt.Errorf("no backend found")
}
res := databaseSecretBackendRoleBackendFromPathRegex.FindStringSubmatch(path)
if len(res) != 2 {
return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res))
}
return res[1], nil
}
Loading

0 comments on commit 7bef763

Please sign in to comment.