From c3b3c8ae2af475c136af0baef72067bc753650f6 Mon Sep 17 00:00:00 2001
From: petems
Date: Fri, 4 Dec 2020 15:22:27 +0000
Subject: [PATCH] Adding new resource for password policy
* https://www.vaultproject.io/docs/concepts/password-policies
Co-authored-by: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com>
---
vault/password_policy.go | 109 +++++++++++++++++++++++++
vault/provider.go | 4 +
vault/resource_password_policy.go | 47 +++++++++++
vault/resource_password_policy_test.go | 89 ++++++++++++++++++++
website/docs/r/password_policy.html.md | 48 +++++++++++
5 files changed, 297 insertions(+)
create mode 100644 vault/password_policy.go
create mode 100644 vault/resource_password_policy.go
create mode 100644 vault/resource_password_policy_test.go
create mode 100644 website/docs/r/password_policy.html.md
diff --git a/vault/password_policy.go b/vault/password_policy.go
new file mode 100644
index 000000000..fea93818a
--- /dev/null
+++ b/vault/password_policy.go
@@ -0,0 +1,109 @@
+package vault
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "log"
+
+ "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+ "github.com/hashicorp/vault/api"
+)
+
+func readPasswordPolicy(client *api.Client, name string) (map[string]interface{}, error) {
+ r := client.NewRequest("GET", fmt.Sprintf("/v1/sys/policies/password/%s", name))
+
+ ctx, cancelFunc := context.WithCancel(context.Background())
+ defer cancelFunc()
+ resp, err := client.RawRequestWithContext(ctx, r)
+ if resp != nil {
+ defer resp.Body.Close()
+ if resp.StatusCode == 404 {
+ return nil, nil
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ secret, err := api.ParseSecret(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ if secret == nil || secret.Data == nil {
+ return nil, errors.New("data from server response is empty")
+ }
+ return secret.Data, nil
+}
+
+func passwordPolicyDelete(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*api.Client)
+
+ name := d.Id()
+
+ log.Printf("[DEBUG] Deleting %s password policy from Vault", name)
+
+ r := client.NewRequest("DELETE", fmt.Sprintf("/v1/sys/policies/password/%s", name))
+
+ ctx, cancelFunc := context.WithCancel(context.Background())
+ defer cancelFunc()
+ resp, err := client.RawRequestWithContext(ctx, r)
+ if err == nil {
+ defer resp.Body.Close()
+ }
+
+ return err
+}
+
+func passwordPolicyRead(attributes []string, d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*api.Client)
+
+ name := d.Id()
+
+ policy, err := readPasswordPolicy(client, name)
+
+ if err != nil {
+ return fmt.Errorf("error reading from Vault: %s", err)
+ }
+
+ for _, value := range attributes {
+ d.Set(value, policy[value])
+ }
+ d.Set("name", name)
+
+ return nil
+}
+
+func passwordPolicyWrite(attributes []string, d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*api.Client)
+
+ name := d.Get("name").(string)
+
+ log.Printf("[DEBUG] Writing %s password policy to Vault", name)
+
+ body := map[string]interface{}{}
+ for _, value := range attributes {
+ body[value] = d.Get(value)
+ }
+
+ r := client.NewRequest("PUT", fmt.Sprintf("/v1/sys/policies/password/%s", name))
+ if err := r.SetJSONBody(body); err != nil {
+ return err
+ }
+
+ ctx, cancelFunc := context.WithCancel(context.Background())
+ defer cancelFunc()
+ resp, err := client.RawRequestWithContext(ctx, r)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if err != nil {
+ return fmt.Errorf("error writing to Vault: %s", err)
+ }
+
+ d.SetId(name)
+
+ return passwordPolicyRead(attributes, d, meta)
+}
diff --git a/vault/provider.go b/vault/provider.go
index 26bfae830..50aa8b130 100644
--- a/vault/provider.go
+++ b/vault/provider.go
@@ -561,6 +561,10 @@ var (
Resource: rabbitmqSecretBackendRoleResource(),
PathInventory: []string{"/rabbitmq/roles/{name}"},
},
+ "vault_password_policy": {
+ Resource: passwordPolicyResource(),
+ PathInventory: []string{"/sys/policy/password/{name}"},
+ },
"vault_pki_secret_backend": {
Resource: pkiSecretBackendResource(),
PathInventory: []string{UnknownPath},
diff --git a/vault/resource_password_policy.go b/vault/resource_password_policy.go
new file mode 100644
index 000000000..03969e375
--- /dev/null
+++ b/vault/resource_password_policy.go
@@ -0,0 +1,47 @@
+package vault
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+)
+
+var passwordPolicyAttributes = []string{"policy"}
+
+func passwordPolicyResource() *schema.Resource {
+ return &schema.Resource{
+ Create: resourcePasswordPolicyWrite,
+ Update: resourcePasswordPolicyWrite,
+ Delete: resourcePasswordPolicyDelete,
+ Read: resourcePasswordPolicyRead,
+
+ Importer: &schema.ResourceImporter{
+ State: schema.ImportStatePassthrough,
+ },
+
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Name of the password policy.",
+ },
+
+ "policy": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The password policy document",
+ },
+ },
+ }
+}
+
+func resourcePasswordPolicyWrite(d *schema.ResourceData, meta interface{}) error {
+ return passwordPolicyWrite(passwordPolicyAttributes, d, meta)
+}
+
+func resourcePasswordPolicyDelete(d *schema.ResourceData, meta interface{}) error {
+ return passwordPolicyDelete(d, meta)
+}
+
+func resourcePasswordPolicyRead(d *schema.ResourceData, meta interface{}) error {
+ return passwordPolicyRead(passwordPolicyAttributes, d, meta)
+}
diff --git a/vault/resource_password_policy_test.go b/vault/resource_password_policy_test.go
new file mode 100644
index 000000000..8f983ea44
--- /dev/null
+++ b/vault/resource_password_policy_test.go
@@ -0,0 +1,89 @@
+package vault
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/terraform"
+ "github.com/hashicorp/vault/api"
+)
+
+func TestAccPasswordPolicy(t *testing.T) {
+
+ policyName := acctest.RandomWithPrefix("test-policy")
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testProviders,
+ CheckDestroy: testAccPasswordPolicyCheckDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccPasswordPolicy(policyName, "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\n"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("vault_password_policy.test", "name", policyName),
+ resource.TestCheckResourceAttrSet("vault_password_policy.test", "policy"),
+ ),
+ },
+ {
+ Config: testAccPasswordPolicy(policyName, "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\nrule \"charset\" {\n charset = \"1234567890\"\nmin-chars = 1\n}\n"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("vault_password_policy.test", "name", policyName),
+ resource.TestCheckResourceAttrSet("vault_password_policy.test", "policy"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccPasswordPolicy_import(t *testing.T) {
+ policyName := acctest.RandomWithPrefix("test-policy")
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testProviders,
+ CheckDestroy: testAccPasswordPolicyCheckDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccPasswordPolicy(policyName, "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\n"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("vault_password_policy.test", "name", policyName),
+ resource.TestCheckResourceAttrSet("vault_password_policy.test", "policy"),
+ ),
+ },
+ {
+ ResourceName: "vault_password_policy.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func testAccPasswordPolicyCheckDestroy(s *terraform.State) error {
+ client := testProvider.Meta().(*api.Client)
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "vault_password_policy" {
+ continue
+ }
+ name := rs.Primary.Attributes["name"]
+ data, err := client.Logical().Read(fmt.Sprintf("sys/policies/password/%s", name))
+ if err != nil {
+ return err
+ }
+ if data != nil {
+ return fmt.Errorf("Password policy %s still exists", name)
+ }
+ }
+ return nil
+}
+
+func testAccPasswordPolicy(policyName string, policy string) string {
+ return fmt.Sprintf(`
+resource "vault_password_policy" "test" {
+ name = "%s"
+ policy = <