Skip to content

Commit

Permalink
Add Azure Secret Backend resource
Browse files Browse the repository at this point in the history
  • Loading branch information
petems committed Aug 5, 2019
1 parent a504240 commit ec76c29
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 0 deletions.
4 changes: 4 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ var (
Resource: awsSecretBackendRoleResource(),
PathInventory: []string{"/aws/roles/{name}"},
},
"vault_azure_secret_backend": {
Resource: azureSecretBackendResource(),
PathInventory: []string{"/azure/config"},
},
"vault_azure_auth_backend_config": {
Resource: azureAuthBackendConfigResource(),
PathInventory: []string{"/auth/azure/config"},
Expand Down
183 changes: 183 additions & 0 deletions vault/resource_azure_secret_backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package vault

import (
"fmt"
"log"
"strings"

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

func azureSecretBackendResource() *schema.Resource {
return &schema.Resource{
Create: azureSecretBackendCreate,
Read: azureSecretBackendRead,
Update: azureSecretBackendCreate,
Delete: azureSecretBackendDelete,
Exists: azureSecretBackendExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"path": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "azure",
Description: "Path to mount the backend at.",
ValidateFunc: func(v interface{}, k string) (ws []string, errs []error) {
value := v.(string)
if strings.HasSuffix(value, "/") {
errs = append(errs, fmt.Errorf("path cannot end in '/'"))
}
return
},
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return old+"/" == new || new+"/" == old
},
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: "Human-friendly description of the mount for the backend.",
},
"subscription_id": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Sensitive: true,
Description: "The subscription id for the Azure Active Directory.",
},
"tenant_id": {
Type: schema.TypeString,
Required: true,
Description: "The tenant id for the Azure Active Directory organization.",
Sensitive: true,
},
"client_id": {
Type: schema.TypeString,
Optional: true,
Description: "The client id for credentials to query the Azure APIs. Currently read permissions to query compute resources are required.",
Sensitive: true,
},
"client_secret": {
Type: schema.TypeString,
Optional: true,
Description: "The client secret for credentials to query the Azure APIs",
Sensitive: true,
},
"environment": {
Type: schema.TypeString,
Optional: true,
Default: "AzurePublicCloud",
Description: "The Azure cloud environment. Valid values: AzurePublicCloud, AzureUSGovernmentCloud, AzureChinaCloud, AzureGermanCloud.",
},
},
}
}

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

path := d.Get("path").(string)
description := d.Get("description").(string)
tenantID := d.Get("tenant_id").(string)
clientID := d.Get("client_id").(string)
clientSecret := d.Get("client_secret").(string)
environment := d.Get("environment").(string)
subscriptionID := d.Get("subscription_id").(string)

configPath := azureSecretBackendPath(path)

data := map[string]interface{}{
"tenant_id": tenantID,
"client_id": clientID,
"client_secret": clientSecret,
"environment": environment,
"subscription_id": subscriptionID,
}

d.Partial(true)
log.Printf("[DEBUG] Mounting Azure backend at %q", path)
err := client.Sys().Mount(path, &api.MountInput{
Type: "azure",
Description: description,
Config: api.MountConfigInput{},
})
if err != nil {
return fmt.Errorf("error mounting to %q: %s", path, err)
}
log.Printf("[DEBUG] Mounted Azure backend at %q", path)
d.SetId(path)

d.SetPartial("path")

log.Printf("[DEBUG] Writing Azure configuration to %q", configPath)
if _, err := client.Logical().Write(configPath, data); err != nil {
return fmt.Errorf("error writing Azure configuration for %q: %s", path, err)
}
log.Printf("[DEBUG] Wrote Azure configuration to %q", configPath)
d.Partial(false)

return azureSecretBackendRead(d, meta)
}

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

path := d.Id()

log.Printf("[DEBUG] Reading Azure backend mount %q from Vault", path)
mounts, err := client.Sys().ListMounts()
if err != nil {
return fmt.Errorf("error reading mount %q: %s", path, err)
}
log.Printf("[DEBUG] Read Azure backend mount %q from Vault", path)

// the API always returns the path with a trailing slash, so let's make
// sure we always specify it as a trailing slash.
mount, ok := mounts[strings.Trim(path, "/")+"/"]
if !ok {
log.Printf("[WARN] Mount %q not found, removing backend from state.", path)
d.SetId("")
return nil
}

d.Set("path", path)
d.Set("description", mount.Description)

return nil
}

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

path := d.Id()

log.Printf("[DEBUG] Unmounting Azure backend %q", path)
err := client.Sys().Unmount(path)
if err != nil {
return fmt.Errorf("error unmounting Azure backend from %q: %s", path, err)
}
log.Printf("[DEBUG] Unmounted Azure backend %q", path)
return nil
}

func azureSecretBackendExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*api.Client)
path := d.Id()
log.Printf("[DEBUG] Checking if Azure backend exists at %q", path)
mounts, err := client.Sys().ListMounts()
if err != nil {
return true, fmt.Errorf("error retrieving list of mounts: %s", err)
}
log.Printf("[DEBUG] Checked if Azure backend exists at %q", path)
_, ok := mounts[strings.Trim(path, "/")+"/"]
return ok, nil
}

func azureSecretBackendPath(path string) string {
return strings.Trim(path, "/") + "/config"
}
69 changes: 69 additions & 0 deletions vault/resource_azure_secret_backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package vault

import (
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/vault/api"
)

func TestAzureSecretBackend(t *testing.T) {
path := acctest.RandomWithPrefix("tf-test-azure")
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccAzureSecretBackendCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAzureSecretBackend_initialConfig(path),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("vault_azure_secret_backend.test", "path", path),
resource.TestCheckResourceAttr("vault_azure_secret_backend.test", "subscription_id", "11111111-2222-3333-4444-111111111111"),
resource.TestCheckResourceAttr("vault_azure_secret_backend.test", "tenant_id", "11111111-2222-3333-4444-222222222222"),
resource.TestCheckResourceAttr("vault_azure_secret_backend.test", "client_id", "11111111-2222-3333-4444-333333333333"),
resource.TestCheckResourceAttr("vault_azure_secret_backend.test", "client_secret", "12345678901234567890"),
resource.TestCheckResourceAttr("vault_azure_secret_backend.test", "environment", "AzurePublicCloud"),
),
},
},
})
}

func testAccAzureSecretBackendCheckDestroy(s *terraform.State) error {
client := testProvider.Meta().(*api.Client)

mounts, err := client.Sys().ListMounts()
if err != nil {
return err
}

for _, rs := range s.RootModule().Resources {
if rs.Type != "vault_azure_secret_backend" {
continue
}
for path, mount := range mounts {
path = strings.Trim(path, "/")
rsPath := strings.Trim(rs.Primary.Attributes["path"], "/")
if mount.Type == "azure" && path == rsPath {
return fmt.Errorf("Mount %q still exists", path)
}
}
}
return nil
}

func testAzureSecretBackend_initialConfig(path string) string {
return fmt.Sprintf(`
resource "vault_azure_secret_backend" "test" {
path = "%s"
subscription_id = "11111111-2222-3333-4444-111111111111"
tenant_id = "11111111-2222-3333-4444-222222222222"
client_id = "11111111-2222-3333-4444-333333333333"
client_secret = "12345678901234567890"
environment = "AzurePublicCloud"
}`, path)
}
46 changes: 46 additions & 0 deletions website/docs/r/azure_secret_backend.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
layout: "vault"
page_title: "Vault: vault_azure_secret_backend resource"
sidebar_current: "docs-vault-resource-azure-secret-backend"
description: |-
Creates an azure secret backend for Vault.
---

# vault\_azure\_secret\_backend

Creates an Azure Secret Backend for Vault.

The Azure secrets engine dynamically generates Azure service principals and role assignments. Vault roles can be mapped to one or more Azure roles, providing a simple, flexible way to manage the permissions granted to generated service principals.

~> **Important** All data provided in the resource configuration will be
written in cleartext to state and plan files generated by Terraform, and
will appear in the console output when Terraform runs. Protect these
artifacts accordingly. See
[the main provider documentation](../index.html)
for more details.

## Example Usage

```hcl
resource "vault_azure_secret_backend" "azure" {
subscription_id = "11111111-2222-3333-4444-111111111111"
tenant_id = "11111111-2222-3333-4444-222222222222"
client_id = "11111111-2222-3333-4444-333333333333"
client_secret = "12345678901234567890"
environment = "AzurePublicCloud"
}
```

## Argument Reference

The following arguments are supported:

- `subscription_id` (`string: <required>`) - The subscription id for the Azure Active Directory.
- `tenant_id` (`string: <required>`) - The tenant id for the Azure Active Directory.
- `client_id` (`string:""`) - The OAuth2 client id to connect to Azure.
- `client_secret` (`string:""`) - The OAuth2 client secret to connect to Azure.
- `environment` (`string:""`) - The Azure environment.

## Attributes Reference

No additional attributes are exported by this resource.

0 comments on commit ec76c29

Please sign in to comment.