From d07c8ece5e1c17b7e534bf5e9086968cbb2c4604 Mon Sep 17 00:00:00 2001
From: petems
Date: Tue, 11 Jun 2019 16:14:11 +0100
Subject: [PATCH] Adds MFA Duo configuration
---
vault/provider.go | 5 ++
vault/resource_mfa_duo.go | 158 +++++++++++++++++++++++++++++++++
vault/resource_mfa_duo_test.go | 62 +++++++++++++
website/docs/r/mfa_duo.html.md | 61 +++++++++++++
website/vault.erb | 4 +
5 files changed, 290 insertions(+)
create mode 100644 vault/resource_mfa_duo.go
create mode 100644 vault/resource_mfa_duo_test.go
create mode 100644 website/docs/r/mfa_duo.html.md
diff --git a/vault/provider.go b/vault/provider.go
index ccf5295135..26d394c415 100644
--- a/vault/provider.go
+++ b/vault/provider.go
@@ -354,6 +354,11 @@ var (
PathInventory: []string{"/sys/policies/rgp/{name}"},
EnterpriseOnly: true,
},
+ "vault_mfa_duo": {
+ Resource: mfaDuoResource(),
+ PathInventory: []string{"/sys/mfa/method/duo/{name}"},
+ EnterpriseOnly: true,
+ },
"vault_mount": {
Resource: mountResource(),
PathInventory: []string{"/sys/mounts/{path}"},
diff --git a/vault/resource_mfa_duo.go b/vault/resource_mfa_duo.go
new file mode 100644
index 0000000000..8aad29dc80
--- /dev/null
+++ b/vault/resource_mfa_duo.go
@@ -0,0 +1,158 @@
+package vault
+
+import (
+ "fmt"
+ "log"
+ "strings"
+
+ "github.com/hashicorp/terraform/helper/schema"
+ "github.com/hashicorp/vault/api"
+)
+
+func mfaDuoResource() *schema.Resource {
+ return &schema.Resource{
+ Create: mfaDuoWrite,
+ Update: mfaDuoWrite,
+ Delete: mfaDuoDelete,
+ Read: mfaDuoRead,
+ Importer: &schema.ResourceImporter{
+ State: schema.ImportStatePassthrough,
+ },
+
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Name of the MFA method.",
+ ValidateFunc: validateNoTrailingSlash,
+ },
+ "mount_accessor": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The mount to tie this method to for use in automatic mappings. The mapping will use the Name field of Aliases associated with this mount as the username in the mapping.",
+ },
+ "username_format": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "A format string for mapping Identity names to MFA method names. Values to substitute should be placed in `{{}}`.",
+ },
+ "secret_key": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Secret key for Duo.",
+ },
+ "integration_key": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Integration key for Duo.",
+ },
+ "api_hostname": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "API hostname for Duo.",
+ },
+ "push_info": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Push information for Duo.",
+ },
+ },
+ }
+}
+
+func mfaDuoWrite(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*api.Client)
+
+ name := d.Get("name").(string)
+
+ data := map[string]interface{}{}
+ mfaDuoUpdateFields(d, data)
+
+ log.Printf("[DEBUG] Writing role %q to MFA Duo auth backend", name)
+ d.SetId(name)
+
+ log.Printf("[DEBUG] Creating mfaDuo %s in Vault", name)
+ _, err := client.Logical().Write(mfaDuoPath(name), data)
+
+ if err != nil {
+ return fmt.Errorf("error writing to Vault: %s", err)
+ }
+
+ return mfaDuoRead(d, meta)
+}
+
+func mfaDuoDelete(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*api.Client)
+
+ name := d.Get("name").(string)
+
+ log.Printf("[DEBUG] Deleting mfaDuo %s from Vault", mfaDuoPath(name))
+
+ _, err := client.Logical().Delete(mfaDuoPath(name))
+
+ if err != nil {
+ return fmt.Errorf("error deleting from Vault: %s", err)
+ }
+
+ return nil
+}
+
+func mfaDuoRead(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*api.Client)
+
+ name := d.Get("name").(string)
+
+ resp, err := client.Logical().Read(mfaDuoPath(name))
+
+ if err != nil {
+ return fmt.Errorf("error reading from Vault: %s", err)
+ }
+
+ log.Printf("[DEBUG] Read MFA Duo config %q", mfaDuoPath(name))
+
+ d.Set("mount_accessor", resp.Data["mount_accessor"])
+ d.Set("username_format", resp.Data["username_format"])
+ d.Set("api_hostname", resp.Data["api_hostname"])
+
+ // When you push the data up, it's push_info
+ // when vault responds, it's pushinfo :(
+ d.Set("push_info", resp.Data["pushinfo"])
+
+ // secret_key and integration_key, can't read out from the api
+ // So... if it drifts, it drift.
+
+ d.SetId(name)
+
+ return nil
+}
+
+func mfaDuoUpdateFields(d *schema.ResourceData, data map[string]interface{}) {
+ if v, ok := d.GetOk("mount_accessor"); ok {
+ data["mount_accessor"] = v.(string)
+ }
+
+ if v, ok := d.GetOk("username_format"); ok {
+ data["username_format"] = v.(string)
+ }
+
+ if v, ok := d.GetOk("secret_key"); ok {
+ data["secret_key"] = v.(string)
+ }
+
+ if v, ok := d.GetOk("integration_key"); ok {
+ data["integration_key"] = v.(string)
+ }
+
+ if v, ok := d.GetOk("api_hostname"); ok {
+ data["api_hostname"] = v.(string)
+ }
+
+ if v, ok := d.GetOk("push_info"); ok {
+ data["push_info"] = v.(string)
+ }
+
+}
+
+func mfaDuoPath(name string) string {
+ return "sys/mfa/method/duo/" + strings.Trim(name, "/") + "/"
+}
diff --git a/vault/resource_mfa_duo_test.go b/vault/resource_mfa_duo_test.go
new file mode 100644
index 0000000000..f9288ee3af
--- /dev/null
+++ b/vault/resource_mfa_duo_test.go
@@ -0,0 +1,62 @@
+package vault
+
+import (
+ "fmt"
+ "testing"
+
+ "os"
+
+ "github.com/hashicorp/terraform/helper/acctest"
+ "github.com/hashicorp/terraform/helper/resource"
+)
+
+func TestMFADuoBasic(t *testing.T) {
+
+ isEnterprise := os.Getenv("TF_ACC_ENTERPRISE")
+ if isEnterprise == "" {
+ t.Skip("TF_ACC_ENTERPRISE is not set, test is applicable only for Enterprise version of Vault")
+ }
+
+ mfaDuoPath := acctest.RandomWithPrefix("mfa-duo")
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testProviders,
+ Steps: []resource.TestStep{
+ {
+ Config: testMFADuoConfig(mfaDuoPath),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("vault_mfa_duo.test", "name", mfaDuoPath),
+ resource.TestCheckResourceAttr("vault_mfa_duo.test", "secret_key", "8C7THtrIigh2rPZQMbguugt8IUftWhMRCOBzbuyz"),
+ resource.TestCheckResourceAttr("vault_mfa_duo.test", "integration_key", "BIACEUEAXI20BNWTEYXT"),
+ resource.TestCheckResourceAttr("vault_mfa_duo.test", "api_hostname", "api-2b5c39f5.duosecurity.com"),
+ resource.TestCheckResourceAttr("vault_mfa_duo.test", "username_format", "user@example.com"),
+ resource.TestCheckResourceAttr("vault_mfa_duo.test", "push_info", "from=loginortal&domain=example.com"),
+ ),
+ },
+ },
+ })
+}
+
+func testMFADuoConfig(path string) string {
+
+ userPassPath := acctest.RandomWithPrefix("userpass")
+
+ return fmt.Sprintf(`
+resource "vault_auth_backend" "userpass" {
+ type = "userpass"
+ path = %q
+}
+
+resource "vault_mfa_duo" "test" {
+ name = %q
+ mount_accessor = "${vault_auth_backend.userpass.accessor}"
+ secret_key = "8C7THtrIigh2rPZQMbguugt8IUftWhMRCOBzbuyz"
+ integration_key = "BIACEUEAXI20BNWTEYXT"
+ api_hostname = "api-2b5c39f5.duosecurity.com"
+ username_format = "user@example.com"
+ push_info = "from=loginortal&domain=example.com"
+}
+`, userPassPath, path)
+
+}
diff --git a/website/docs/r/mfa_duo.html.md b/website/docs/r/mfa_duo.html.md
new file mode 100644
index 0000000000..d5118d422c
--- /dev/null
+++ b/website/docs/r/mfa_duo.html.md
@@ -0,0 +1,61 @@
+---
+layout: "vault"
+page_title: "Vault: vault_mfa_duo resource"
+sidebar_current: "docs-vault-resource-mfa-duo"
+description: |-
+ Managing the MFA Duo method configuration
+---
+
+# vault\_mfa-duo
+
+Provides a resource to manage [Duo MFA](https://www.vaultproject.io/docs/enterprise/mfa/mfa-duo.html).
+
+**Note** this feature is available only with Vault Enterprise.
+
+## Example Usage
+
+```hcl
+resource "vault_auth_backend" "userpass" {
+ type = "userpass"
+ path = "userpass"
+}
+
+resource "vault_mfa_duo" "my_duo" {
+ name = "my_duo"
+ mount_accessor = "${vault_auth_backend.userpass.accessor}"
+ secret_key = "8C7THtrIigh2rPZQMbguugt8IUftWhMRCOBzbuyz"
+ integration_key = "BIACEUEAXI20BNWTEYXT"
+ api_hostname = "api-2b5c39f5.duosecurity.com"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+- `name` `(string: )` – Name of the MFA method.
+
+- `mount_accessor` `(string: )` - The mount to tie this method to for use in automatic mappings. The mapping will use the Name field of Aliases associated with this mount as the username in the mapping.
+
+- `username_format` `(string)` - A format string for mapping Identity names to MFA method names. Values to substitute should be placed in `{{}}`. For example, `"{{alias.name}}@example.com"`. If blank, the Alias's Name field will be used as-is. Currently-supported mappings:
+ - alias.name: The name returned by the mount configured via the `mount_accessor` parameter
+ - entity.name: The name configured for the Entity
+ - alias.metadata.``: The value of the Alias's metadata parameter
+ - entity.metadata.``: The value of the Entity's metadata parameter
+
+- `secret_key` `(string: )` - Secret key for Duo.
+
+- `integration_key` `(string: )` - Integration key for Duo.
+
+- `api_hostname` `(string: )` - API hostname for Duo.
+
+- `push_info` `(string)` - Push information for Duo.
+
+## Import
+
+Mounts can be imported using the `path`, e.g.
+
+```
+$ terraform import vault_mfa_duo.my_duo my_duo
+```
+
diff --git a/website/vault.erb b/website/vault.erb
index c53d2b2b16..23c382b5cc 100644
--- a/website/vault.erb
+++ b/website/vault.erb
@@ -211,6 +211,10 @@
vault_ldap_auth_backend_group
+ >
+ vault_mfa_duo
+
+
>
vault_mount