diff --git a/vault/resource_mount.go b/vault/resource_mount.go index 5569840bae..c5913d4f59 100644 --- a/vault/resource_mount.go +++ b/vault/resource_mount.go @@ -83,6 +83,15 @@ func mountResource() *schema.Resource { ForceNew: false, Description: "Specifies mount type specific options that are passed to the backend", }, + + "seal_wrap": { + Type: schema.TypeBool, + Required: false, + Optional: true, + ForceNew: true, + Computed: true, + Description: "Enable seal wrapping for the mount, causing values stored by the mount to be wrapped by the seal's encryption capability", + }, }, } } @@ -97,8 +106,9 @@ func mountWrite(d *schema.ResourceData, meta interface{}) error { DefaultLeaseTTL: fmt.Sprintf("%ds", d.Get("default_lease_ttl_seconds")), MaxLeaseTTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds")), }, - Local: d.Get("local").(bool), - Options: opts(d), + Local: d.Get("local").(bool), + Options: opts(d), + SealWrap: d.Get("seal_wrap").(bool), } path := d.Get("path").(string) @@ -207,6 +217,7 @@ func mountRead(d *schema.ResourceData, meta interface{}) error { d.Set("accessor", mount.Accessor) d.Set("local", mount.Local) d.Set("options", mount.Options) + d.Set("seal_wrap", mount.SealWrap) return nil } diff --git a/vault/resource_mount_test.go b/vault/resource_mount_test.go index 3368f1b3fb..5179363b69 100644 --- a/vault/resource_mount_test.go +++ b/vault/resource_mount_test.go @@ -14,6 +14,7 @@ type mountConfig struct { path string mountType string version string + seal_wrap bool } func TestZeroTTLDoesNotCauseUpdate(t *testing.T) { @@ -86,6 +87,26 @@ func TestResourceMount_Local(t *testing.T) { }) } +// Test SealWrap flag + +func TestResourceMount_SealWrap(t *testing.T) { + path := "example-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testResourceMount_InitialConfigSealWrap(path), + Check: testResourceMount_InitialCheckSealWrap(path), + }, + { + Config: testResourceMount_UpdateConfigSealWrap, + Check: testResourceMount_UpdateCheckSealWrap, + }, + }, + }) +} + func TestResourceMount_KVV2(t *testing.T) { path := acctest.RandomWithPrefix("example") kvv2Cfg := fmt.Sprintf(` @@ -335,6 +356,99 @@ func testResourceMount_UpdateCheckLocalMount(s *terraform.State) error { return nil } +func testResourceMount_InitialConfigSealWrap(path string) string { + return fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "kv" + description = "Example local mount for testing" + default_lease_ttl_seconds = 3600 + max_lease_ttl_seconds = 36000 + options = { + version = "1" + } + seal_wrap = true +} +`, path) +} + +func testResourceMount_InitialCheckSealWrap(expectedPath string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_mount.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state") + } + + instanceState := resourceState.Primary + if instanceState == nil { + return fmt.Errorf("resource has no primary instance") + } + + path := instanceState.ID + + if path != instanceState.Attributes["path"] { + return fmt.Errorf("id %q doesn't match path %q", path, instanceState.Attributes["path"]) + } + + if path != expectedPath { + return fmt.Errorf("unexpected path %q, expected %q", path, expectedPath) + } + + mount, err := findMount(path) + if err != nil { + return fmt.Errorf("error reading back mount %q: %s", path, err) + } + + if wanted := true; mount.SealWrap != wanted { + return fmt.Errorf("seal_wrap is %v; wanted %t", mount.SealWrap, wanted) + } + + return nil + } +} + +var testResourceMount_UpdateConfigSealWrap = ` + +resource "vault_mount" "test" { + path = "remountingExample" + type = "kv" + description = "Example mount for testing" + default_lease_ttl_seconds = 7200 + max_lease_ttl_seconds = 72000 + options = { + version = "1" + } + seal_wrap = false +} + +` + +func testResourceMount_UpdateCheckSealWrap(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_mount.test"] + instanceState := resourceState.Primary + + path := instanceState.ID + + if path != instanceState.Attributes["path"] { + return fmt.Errorf("id doesn't match path") + } + + if path != "remountingExample" { + return fmt.Errorf("unexpected path value") + } + + mount, err := findMount(path) + if err != nil { + return fmt.Errorf("error reading back mount: %s", err) + } + + if wanted := false; mount.SealWrap != wanted { + return fmt.Errorf("seal_wrap is %v; wanted %t", mount.SealWrap, wanted) + } + + return nil +} + func findMount(path string) (*api.MountOutput, error) { client := testProvider.Meta().(*api.Client) diff --git a/website/docs/r/mount.html.md b/website/docs/r/mount.html.md index 9d0cf4fa0c..a3ae4f74ee 100644 --- a/website/docs/r/mount.html.md +++ b/website/docs/r/mount.html.md @@ -37,6 +37,8 @@ The following arguments are supported: * `options` - (Optional) Specifies mount type specific options that are passed to the backend +* `seal_wrap` - (Optional) Boolean flag that can be explicitly set to true to enable seal wrapping for the mount, causing values stored by the mount to be wrapped by the seal's encryption capability + ## Attributes Reference In addition to the fields above, the following attributes are exported: