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 = <