Skip to content

Commit

Permalink
Adds MFA Duo configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
petems committed Aug 2, 2019
1 parent 2075b54 commit d07c8ec
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 0 deletions.
5 changes: 5 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}"},
Expand Down
158 changes: 158 additions & 0 deletions vault/resource_mfa_duo.go
Original file line number Diff line number Diff line change
@@ -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, "/") + "/"
}
62 changes: 62 additions & 0 deletions vault/resource_mfa_duo_test.go
Original file line number Diff line number Diff line change
@@ -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)

}
61 changes: 61 additions & 0 deletions website/docs/r/mfa_duo.html.md
Original file line number Diff line number Diff line change
@@ -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: <required>)` – Name of the MFA method.

- `mount_accessor` `(string: <required>)` - 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.`<key>`: The value of the Alias's metadata parameter
- entity.metadata.`<key>`: The value of the Entity's metadata parameter

- `secret_key` `(string: <required>)` - Secret key for Duo.

- `integration_key` `(string: <required>)` - Integration key for Duo.

- `api_hostname` `(string: <required>)` - 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
```

4 changes: 4 additions & 0 deletions website/vault.erb
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@
<a href="/docs/providers/vault/r/ldap_auth_backend_group.html">vault_ldap_auth_backend_group</a>
</li>

<li<%= sidebar_current("docs-vault-resource-mfa-duo") %>>
<a href="/docs/providers/vault/r/mfa_duo.html">vault_mfa_duo</a>
</li>

<li<%= sidebar_current("docs-vault-resource-mount") %>>
<a href="/docs/providers/vault/r/mount.html">vault_mount</a>
</li>
Expand Down

0 comments on commit d07c8ec

Please sign in to comment.