From d9e8686467a4503716a6a70ecf1fdc528d0255d1 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Mon, 30 Nov 2020 15:43:26 -0500 Subject: [PATCH 1/9] Add Nomad secret backend --- util/util.go | 14 + vault/data_source_nomad_credentials.go | 71 +++++ vault/data_source_nomad_credentials_test.go | 96 +++++++ vault/provider.go | 16 ++ vault/resource_nomad_secret_backend.go | 304 ++++++++++++++++++++ vault/resource_nomad_secret_backend_test.go | 96 +++++++ vault/resource_nomad_secret_lease.go | 163 +++++++++++ vault/resource_nomad_secret_lease_test.go | 59 ++++ vault/resource_nomad_secret_role.go | 239 +++++++++++++++ vault/resource_nomad_secret_role_test.go | 144 ++++++++++ website/docs/d/nomad_access_token.html.md | 64 +++++ website/docs/r/nomad_secret_backend.html.md | 75 +++++ website/docs/r/nomad_secret_lease.html.md | 60 ++++ website/docs/r/nomad_secret_role.html.md | 72 +++++ 14 files changed, 1473 insertions(+) create mode 100644 vault/data_source_nomad_credentials.go create mode 100644 vault/data_source_nomad_credentials_test.go create mode 100644 vault/resource_nomad_secret_backend.go create mode 100644 vault/resource_nomad_secret_backend_test.go create mode 100644 vault/resource_nomad_secret_lease.go create mode 100644 vault/resource_nomad_secret_lease_test.go create mode 100644 vault/resource_nomad_secret_role.go create mode 100644 vault/resource_nomad_secret_role_test.go create mode 100644 website/docs/d/nomad_access_token.html.md create mode 100644 website/docs/r/nomad_secret_backend.html.md create mode 100644 website/docs/r/nomad_secret_lease.html.md create mode 100644 website/docs/r/nomad_secret_role.html.md diff --git a/util/util.go b/util/util.go index 84eaf968c..229d169f4 100644 --- a/util/util.go +++ b/util/util.go @@ -136,6 +136,20 @@ func GetTestADCreds(t *testing.T) (string, string, string) { return adBindDN, adBindPass, adURL } +func GetTestNomadCreds(t *testing.T) (string, string) { + address := os.Getenv("NOMAD_ADDR") + token := os.Getenv("NOMAD_TOKEN") + + if address == "" { + t.Skip("NOMAD_ADDR not set") + } + if token == "" { + t.Skip("NOMAD_TOKEN not set") + } + + return address, token +} + func TestCheckResourceAttrJSON(name, key, expectedValue string) resource.TestCheckFunc { return func(s *terraform.State) error { resourceState, ok := s.RootModule().Resources[name] diff --git a/vault/data_source_nomad_credentials.go b/vault/data_source_nomad_credentials.go new file mode 100644 index 000000000..cf32d192b --- /dev/null +++ b/vault/data_source_nomad_credentials.go @@ -0,0 +1,71 @@ +package vault + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/vault/api" +) + +func nomadAccessCredentialsDataSource() *schema.Resource { + return &schema.Resource{ + Read: readNomadCredsResource, + Schema: map[string]*schema.Schema{ + "backend": { + Type: schema.TypeString, + Required: true, + Description: "Nomad secret backend to generate tokens from.", + }, + "role": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the role.", + }, + "accessor_id": { + Type: schema.TypeString, + Computed: true, + Description: "The public identifier for a specific token. It can be used to look up information about a token or to revoke a token.", + }, + "secret_id": { + Type: schema.TypeString, + Computed: true, + Description: "Used to make requests to Nomad and should be kept private.", + }, + }, + } +} + +func readNomadCredsResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + backend := d.Get("backend").(string) + role := d.Get("role").(string) + path := fmt.Sprintf("%s/creds/%s", backend, role) + + secret, err := client.Logical().Read(path) + if err != nil { + return fmt.Errorf("error reading from Vault: %s", err) + } + log.Printf("[DEBUG] Read %q from Vault", path) + + if secret == nil { + return fmt.Errorf("no role found at %q", path) + } + + accessorID := secret.Data["accessor_id"].(string) + if accessorID == "" { + return fmt.Errorf("accessor_id is not set in response") + } + + secretID := secret.Data["secret_id"].(string) + if secretID == "" { + return fmt.Errorf("secret_id is not set in response") + } + + d.SetId(accessorID) + d.Set("accessor_id", accessorID) + d.Set("secret_id", secretID) + + return nil +} diff --git a/vault/data_source_nomad_credentials_test.go b/vault/data_source_nomad_credentials_test.go new file mode 100644 index 000000000..9fb7ef656 --- /dev/null +++ b/vault/data_source_nomad_credentials_test.go @@ -0,0 +1,96 @@ +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-provider-vault/util" +) + +func TestAccDataSourceNomadAccessCredentialsClientBasic(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceNomadAccessCredentialsConfig(backend, address, token, "test"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.vault_nomad_access_token.token", "secret_id"), + resource.TestCheckResourceAttrSet("data.vault_nomad_access_token.token", "accessor_id"), + ), + }, + }, + }) +} + +func TestAccDataSourceNomadAccessCredentialsManagementBasic(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceNomadAccessCredentialsManagementConfig(backend, address, token, "test"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.vault_nomad_access_token.token", "secret_id"), + resource.TestCheckResourceAttrSet("data.vault_nomad_access_token.token", "accessor_id"), + ), + }, + }, + }) +} + +func testAccDataSourceNomadAccessCredentialsConfig(backend, address, token, role string) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "%s" + token = "%s" +} + +resource "vault_nomad_secret_role" "test" { + backend = vault_nomad_secret_backend.config.backend + role = "%s" + policies = ["reaodnly"] +} + +data "vault_nomad_access_token" "token" { + backend = vault_nomad_secret_backend.config.backend + role = vault_nomad_secret_role.test.role +} +`, backend, address, token, role) +} + +func testAccDataSourceNomadAccessCredentialsManagementConfig(backend, address, token, role string) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "%s" + token = "%s" +} + +resource "vault_nomad_secret_role" "test" { + backend = vault_nomad_secret_backend.config.backend + role = "%s" + type = "management" +} + +data "vault_nomad_access_token" "token" { + backend = vault_nomad_secret_backend.config.backend + role = vault_nomad_secret_role.test.role +} +`, backend, address, token, role) +} diff --git a/vault/provider.go b/vault/provider.go index 26bfae830..f3e95c5ca 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -231,6 +231,10 @@ var ( Resource: adAccessCredentialsDataSource(), PathInventory: []string{"/ad/creds/{role}"}, }, + "vault_nomad_access_token": { + Resource: nomadAccessCredentialsDataSource(), + PathInventory: []string{"/nomad/creds/{role}"}, + }, "vault_aws_access_credentials": { Resource: awsAccessCredentialsDataSource(), PathInventory: []string{"/aws/creds"}, @@ -466,6 +470,18 @@ var ( Resource: ldapAuthBackendGroupResource(), PathInventory: []string{"/auth/ldap/groups/{name}"}, }, + "vault_nomad_secret_backend": { + Resource: nomadSecretAccessBackendResource(), + PathInventory: []string{"/nomad"}, + }, + "vault_nomad_secret_lease": { + Resource: nomadSecretLeaseBackendResource(), + PathInventory: []string{"/nomad/config/lease"}, + }, + "vault_nomad_secret_role": { + Resource: nomadSecretBackendRoleResource(), + PathInventory: []string{"/nomad/role/{role}"}, + }, "vault_policy": { Resource: policyResource(), PathInventory: []string{"/sys/policy/{name}"}, diff --git a/vault/resource_nomad_secret_backend.go b/vault/resource_nomad_secret_backend.go new file mode 100644 index 000000000..e609e623a --- /dev/null +++ b/vault/resource_nomad_secret_backend.go @@ -0,0 +1,304 @@ +package vault + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-provider-vault/util" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/vault/api" +) + +func nomadSecretAccessBackendResource() *schema.Resource { + fields := map[string]*schema.Schema{ + "backend": { + Type: schema.TypeString, + Default: "nomad", + ForceNew: true, + Optional: true, + Description: "The mount path for the Nomad backend.", + StateFunc: func(v interface{}) string { + return strings.Trim(v.(string), "/") + }, + }, + "address": { + Type: schema.TypeString, + Required: true, + Description: `Specifies the address of the Nomad instance, provided as "protocol://host:port" like "http://127.0.0.1:4646".`, + }, + "ca_cert": { + Type: schema.TypeString, + Optional: true, + Description: `CA certificate to use when verifying Nomad server certificate, must be x509 PEM encoded.`, + }, + "client_cert": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: `Client certificate used for Nomad's TLS communication, must be x509 PEM encoded and if this is set you need to also set client_key.`, + }, + "client_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: `Client key used for Nomad's TLS communication, must be x509 PEM encoded and if this is set you need to also set client_cert.`, + }, + "default_lease_ttl_seconds": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: `Default lease duration for secrets in seconds.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Human-friendly description of the mount for the backend.`, + }, + "local": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Description: `Mark the secrets engine as local-only. Local engines are not replicated or removed by replication.Tolerance duration to use when checking the last rotation time.`, + }, + "max_lease_ttl_seconds": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Maximum possible lease duration for secrets in seconds.", + }, + "max_token_name_length": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: `Specifies the maximum length to use for the name of the Nomad token generated with Generate Credential. If omitted, 0 is used and ignored, defaulting to the max value allowed by the Nomad version.`, + }, + "token": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + Description: `Specifies the Nomad Management token to use.`, + }, + } + return &schema.Resource{ + Create: createNomadAccessConfigResource, + Update: updateNomadAccessConfigResource, + Read: readNomadAccessConfigResource, + Delete: deleteNomadAccessConfigResource, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: fields, + } +} + +func createNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + backend := d.Get("backend").(string) + description := d.Get("description").(string) + defaultTTL := d.Get("default_lease_ttl_seconds").(int) + local := d.Get("local").(bool) + maxTTL := d.Get("max_lease_ttl_seconds").(int) + + log.Printf("[DEBUG] Mounting Nomad backend at %q", backend) + err := client.Sys().Mount(backend, &api.MountInput{ + Type: "nomad", + Description: description, + Local: local, + Config: api.MountConfigInput{ + DefaultLeaseTTL: fmt.Sprintf("%ds", defaultTTL), + MaxLeaseTTL: fmt.Sprintf("%ds", maxTTL), + }, + }) + if err != nil { + return fmt.Errorf("error mounting to %q: %s", backend, err) + } + + log.Printf("[DEBUG] Mounted Nomad backend at %q", backend) + d.SetId(backend) + + data := map[string]interface{}{} + if v, ok := d.GetOkExists("address"); ok { + data["address"] = v + } + + if v, ok := d.GetOkExists("ca_cert"); ok { + data["ca_cert"] = v + } + + if v, ok := d.GetOkExists("client_cert"); ok { + data["client_cert"] = v + } + + if v, ok := d.GetOkExists("client_key"); ok { + data["client_key"] = v + } + + if v, ok := d.GetOkExists("max_token_name_length"); ok { + data["max_token_name_length"] = v + } + + if v, ok := d.GetOkExists("token"); ok { + data["token"] = v + } + + configPath := fmt.Sprintf("%s/config/access", backend) + log.Printf("[DEBUG] Writing %q", configPath) + if _, err := client.Logical().Write(configPath, data); err != nil { + return fmt.Errorf("error writing %q: %s", configPath, err) + } + + log.Printf("[DEBUG] Wrote %q", configPath) + return readNomadAccessConfigResource(d, meta) +} + +func readNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + + path := d.Id() + log.Printf("[DEBUG] Reading %q", path) + + mountResp, err := client.Sys().MountConfig(path) + if err != nil && util.Is404(err) { + log.Printf("[WARN] %q not found, removing from state", path) + d.SetId("") + return nil + } else if err != nil { + return fmt.Errorf("error reading %q: %s", path, err) + } + + d.Set("backend", d.Id()) + d.Set("default_lease_ttl_seconds", mountResp.DefaultLeaseTTL) + d.Set("max_lease_ttl_seconds", mountResp.MaxLeaseTTL) + + configPath := fmt.Sprintf("%s/config/access", d.Id()) + log.Printf("[DEBUG] Reading %q", configPath) + + resp, err := client.Logical().Read(configPath) + if err != nil { + return fmt.Errorf("error reading %q: %s", configPath, err) + } + log.Printf("[DEBUG] Read %q", configPath) + if resp == nil { + log.Printf("[WARN] %q not found, removing from state", configPath) + d.SetId("") + return nil + } + + if val, ok := resp.Data["address"]; ok { + if err := d.Set("address", val); err != nil { + return fmt.Errorf("error setting state key 'address': %s", err) + } + } + + if val, ok := resp.Data["ca_cert"]; ok { + if err := d.Set("ca_cert", val); err != nil { + return fmt.Errorf("error setting state key 'ca_cert': %s", err) + } + } + + if val, ok := resp.Data["client_cert"]; ok { + if err := d.Set("client_cert", val); err != nil { + return fmt.Errorf("error setting state key 'client_cert': %s", err) + } + } + + if val, ok := resp.Data["client_key"]; ok { + if err := d.Set("client_key", val); err != nil { + return fmt.Errorf("error setting state key 'client_key': %s", err) + } + } + + if val, ok := resp.Data["max_token_name_length"]; ok { + if err := d.Set("max_token_name_length", val); err != nil { + return fmt.Errorf("error setting state key 'max_token_name_length': %s", err) + } + } + + if val, ok := resp.Data["token"]; ok { + if err := d.Set("token", val); err != nil { + return fmt.Errorf("error setting state key 'token': %s", err) + } + } + + return nil +} + +func updateNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) error { + backend := d.Id() + + client := meta.(*api.Client) + defaultTTL := d.Get("default_lease_ttl_seconds").(int) + maxTTL := d.Get("max_lease_ttl_seconds").(int) + tune := api.MountConfigInput{} + data := map[string]interface{}{} + + if defaultTTL != 0 { + tune.DefaultLeaseTTL = fmt.Sprintf("%ds", defaultTTL) + data["default_lease_ttl_seconds"] = defaultTTL + } + + if maxTTL != 0 { + tune.MaxLeaseTTL = fmt.Sprintf("%ds", maxTTL) + data["max_lease_ttl_seconds"] = maxTTL + } + + if tune.DefaultLeaseTTL != "0" || tune.MaxLeaseTTL != "0" { + err := client.Sys().TuneMount(backend, tune) + if err != nil { + return fmt.Errorf("error mounting to %q: %s", backend, err) + } + } + + vaultPath := fmt.Sprintf("%s/config/access", backend) + log.Printf("[DEBUG] Updating %q", vaultPath) + + if raw, ok := d.GetOk("address"); ok { + data["address"] = raw + } + + if raw, ok := d.GetOk("ca_cert"); ok { + data["ca_cert"] = raw + } + + if raw, ok := d.GetOk("client_cert"); ok { + data["client_cert"] = raw + } + + if raw, ok := d.GetOk("client_key"); ok { + data["client_key"] = raw + } + + if raw, ok := d.GetOk("max_token_name_length"); ok { + data["max_token_name_length"] = raw + } + + if raw, ok := d.GetOk("token"); ok { + data["token"] = raw + } + + if _, err := client.Logical().Write(vaultPath, data); err != nil { + return fmt.Errorf("error updating template auth backend role %q: %s", vaultPath, err) + } + + log.Printf("[DEBUG] Updated %q", vaultPath) + return readNomadAccessConfigResource(d, meta) +} + +func deleteNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + vaultPath := d.Id() + log.Printf("[DEBUG] Unmounting Nomad backend %q", vaultPath) + + err := client.Sys().Unmount(vaultPath) + if err != nil && util.Is404(err) { + log.Printf("[WARN] %q not found, removing from state", vaultPath) + d.SetId("") + return fmt.Errorf("error unmounting Nomad backend from %q: %s", vaultPath, err) + } else if err != nil { + return fmt.Errorf("error unmounting Nomad backend from %q: %s", vaultPath, err) + } + log.Printf("[DEBUG] Unmounted Nomad backend %q", vaultPath) + return nil +} diff --git a/vault/resource_nomad_secret_backend_test.go b/vault/resource_nomad_secret_backend_test.go new file mode 100644 index 000000000..2fb6af5a1 --- /dev/null +++ b/vault/resource_nomad_secret_backend_test.go @@ -0,0 +1,96 @@ +package vault + +import ( + "fmt" + "strings" + "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/terraform-provider-vault/util" + "github.com/hashicorp/vault/api" +) + +func TestNomadSecretBackend(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + PreventPostDestroyRefresh: true, + CheckDestroy: testAccNomadSecretBackendCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testNomadSecretBackendInitialConfig(backend, address, token), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "backend", backend), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "description", "test description"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "default_lease_ttl_seconds", "3600"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_lease_ttl_seconds", "7200"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "address", address), + ), + }, + { + Config: testNomadSecretBackendUpdateConfig(backend, "foobar", token), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "backend", backend), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "description", "test description"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "default_lease_ttl_seconds", "7200"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_lease_ttl_seconds", "14400"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "address", "foobar"), + ), + }, + }, + }) +} + +func testAccNomadSecretBackendCheckDestroy(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_nomad_secret_backend" { + continue + } + for backend, mount := range mounts { + backend = strings.Trim(backend, "/") + rsBackend := strings.Trim(rs.Primary.Attributes["backend"], "/") + if mount.Type == "nomad" && backend == rsBackend { + return fmt.Errorf("Mount %q still exists", rsBackend) + } + } + } + return nil +} + +func testNomadSecretBackendInitialConfig(backend, address, token string) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "%s" + token = "%s" +} +`, backend, address, token) +} + +func testNomadSecretBackendUpdateConfig(backend, address, token string) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "7200" + max_lease_ttl_seconds = "14000" + address = "%s" + token = "%s" +} +`, backend, address, token) +} diff --git a/vault/resource_nomad_secret_lease.go b/vault/resource_nomad_secret_lease.go new file mode 100644 index 000000000..f61751f1f --- /dev/null +++ b/vault/resource_nomad_secret_lease.go @@ -0,0 +1,163 @@ +package vault + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/hashicorp/terraform-provider-vault/util" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/vault/api" +) + +var ( + nomadSecretBackendFromPathRegex = regexp.MustCompile("^(.+)/config/lease") +) + +func nomadSecretLeaseBackendResource() *schema.Resource { + fields := map[string]*schema.Schema{ + "backend": { + Type: schema.TypeString, + Default: "nomad", + ForceNew: true, + Optional: true, + Description: "The mount path for the Nomad backend.", + StateFunc: func(v interface{}) string { + return strings.Trim(v.(string), "/") + }, + }, + "max_ttl": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Maximum possible lease duration for secrets in seconds.", + }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Maximum possible lease duration for secrets in seconds.", + }, + } + return &schema.Resource{ + Create: createNomadLeaseConfigResource, + Update: updateNomadLeaseConfigResource, + Read: readNomadLeaseConfigResource, + Delete: deleteNomadLeaseConfigResource, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: fields, + } +} + +func createNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + backend := d.Get("backend").(string) + configPath := fmt.Sprintf("%s/config/lease", backend) + + log.Printf("[DEBUG] Creating %q", configPath) + + data := map[string]interface{}{} + if v, ok := d.GetOkExists("max_ttl"); ok { + data["max_ttl"] = v + } + + if v, ok := d.GetOkExists("ttl"); ok { + data["ttl"] = v + } + + log.Printf("[DEBUG] Writing %q", configPath) + if _, err := client.Logical().Write(configPath, data); err != nil { + return fmt.Errorf("error writing %q: %s", configPath, err) + } + d.SetId(configPath) + log.Printf("[DEBUG] Wrote %q", configPath) + return readNomadLeaseConfigResource(d, meta) +} + +func readNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + configPath := d.Id() + log.Printf("[DEBUG] Reading %q", configPath) + + backend, err := nomadSecretBackendFromPath(configPath) + if err != nil { + return fmt.Errorf("invalid lease config ID %q: %s", configPath, err) + } + d.Set("backend", backend) + + resp, err := client.Logical().Read(configPath) + if err != nil { + return fmt.Errorf("error reading %q: %s", configPath, err) + } + log.Printf("[DEBUG] Read %q", configPath) + + if resp == nil { + log.Printf("[WARN] %q not found, removing from state", configPath) + d.SetId("") + return nil + } + + if val, ok := resp.Data["max_ttl"]; ok { + if err := d.Set("max_ttl", val); err != nil { + return fmt.Errorf("error setting state key 'max_ttl': %s", err) + } + } + + if val, ok := resp.Data["ttl"]; ok { + if err := d.Set("ttl", val); err != nil { + return fmt.Errorf("error setting state key 'ttl': %s", err) + } + } + + return nil +} + +func updateNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + configPath := d.Id() + log.Printf("[DEBUG] Updating %q", configPath) + + data := map[string]interface{}{} + if raw, ok := d.GetOk("max_ttl"); ok { + data["max_ttl"] = raw + } + if raw, ok := d.GetOk("ttl"); ok { + data["ttl"] = raw + } + if _, err := client.Logical().Write(configPath, data); err != nil { + return fmt.Errorf("error updating lease config %q: %s", configPath, err) + } + log.Printf("[DEBUG] Updated %q", configPath) + return readNomadLeaseConfigResource(d, meta) +} + +func deleteNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + configPath := d.Id() + log.Printf("[DEBUG] Deleting %q", configPath) + + if _, err := client.Logical().Delete(configPath); err != nil && !util.Is404(err) { + return fmt.Errorf("error deleting %q: %s", configPath, err) + } else if err != nil { + log.Printf("[DEBUG] %q not found, removing from state", configPath) + d.SetId("") + return nil + } + log.Printf("[DEBUG] Deleted lease config %q", configPath) + return nil +} + +func nomadSecretBackendFromPath(path string) (string, error) { + if !nomadSecretBackendFromPathRegex.MatchString(path) { + return "", fmt.Errorf("no backend found") + } + res := nomadSecretBackendFromPathRegex.FindStringSubmatch(path) + if len(res) != 2 { + return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res)) + } + return res[1], nil +} diff --git a/vault/resource_nomad_secret_lease_test.go b/vault/resource_nomad_secret_lease_test.go new file mode 100644 index 000000000..9d1c293e1 --- /dev/null +++ b/vault/resource_nomad_secret_lease_test.go @@ -0,0 +1,59 @@ +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-provider-vault/util" +) + +func TestAccNomadSecretLease(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + PreventPostDestroyRefresh: true, + CheckDestroy: testAccNomadSecretBackendCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testNomadSecretBackendInitialLeaseConfig(backend, address, token, 3600, 1800), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "backend", backend), + resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "max_ttl", "3600"), + resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "ttl", "1800"), + ), + }, + { + Config: testNomadSecretBackendInitialLeaseConfig(backend, address, token, 7200, 3600), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "backend", backend), + resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "max_ttl", "7200"), + resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "ttl", "3600"), + ), + }, + }, + }) +} + +func testNomadSecretBackendInitialLeaseConfig(backend, address, token string, maxTTL, ttl int) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "%s" + token = "%s" +} + +resource "vault_nomad_secret_lease" "test" { + backend = vault_nomad_secret_backend.config.backend + max_ttl = %d + ttl = %d +} +`, backend, address, token, maxTTL, ttl) +} diff --git a/vault/resource_nomad_secret_role.go b/vault/resource_nomad_secret_role.go new file mode 100644 index 000000000..b9f8375d3 --- /dev/null +++ b/vault/resource_nomad_secret_role.go @@ -0,0 +1,239 @@ +package vault + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-provider-vault/util" + "github.com/hashicorp/vault/api" +) + +var ( + nomadSecretBackendFromRolePathRegex = regexp.MustCompile("^(.+)/role/.+$") + nomadSecretBackendRoleNameFromPathRegex = regexp.MustCompile("^.+/role/(.+$)") +) + +func nomadSecretBackendRoleResource() *schema.Resource { + fields := map[string]*schema.Schema{ + "backend": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The mount path for the Nomad backend.", + StateFunc: func(v interface{}) string { + return strings.Trim(v.(string), "/") + }, + }, + "role": { + Type: schema.TypeString, + Required: true, + Description: `Name of the role.`, + ForceNew: true, + }, + "global": { + Type: schema.TypeBool, + Computed: true, + Optional: true, + Description: `Specifies if the token should be global.`, + }, + "policies": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + Optional: true, + Description: `Comma separated list of Nomad policies the token is going to be created against. These need to be created beforehand in Nomad.`, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: `Specifies the type of token to create when using this role. Valid values are "client" or "management".`, + }, + } + return &schema.Resource{ + Create: createNomadRoleResource, + Update: updateNomadRoleResource, + Read: readNomadRoleResource, + Delete: deleteNomadRoleResource, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: fields, + } +} + +func createNomadRoleResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + backend := d.Get("backend").(string) + role := d.Get("role").(string) + roleType := d.Get("type").(string) + + if roleType == "" { + roleType = "client" + } + + rolePath := fmt.Sprintf("%s/role/%s", backend, role) + + log.Printf("[DEBUG] Creating %q", rolePath) + + data := map[string]interface{}{} + data["type"] = roleType + + if v, ok := d.GetOkExists("global"); ok { + data["global"] = v + } + + // Policies are required if role type is 'client', so settiing up + // to enforce that here. + if v, ok := d.GetOkExists("policies"); ok { + if roleType == "client" { + data["policies"] = v + } + } + + if roleType == "client" && data["policies"] == nil { + return fmt.Errorf("error creating role %s: policies are required when role type is 'client'", role) + } + + // Policies not supported when role type is 'management' + if roleType == "management" && data["policies"] != nil { + return fmt.Errorf("error creating role %s: policies should be empty when using management tokens", role) + } + + log.Printf("[DEBUG] Writing %q", rolePath) + if _, err := client.Logical().Write(rolePath, data); err != nil { + return fmt.Errorf("error writing %q: %s", rolePath, err) + } + d.SetId(rolePath) + log.Printf("[DEBUG] Wrote %q", rolePath) + return readNomadRoleResource(d, meta) +} + +func readNomadRoleResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + rolePath := d.Id() + log.Printf("[DEBUG] Reading %q", rolePath) + + backend, err := nomadSecretBackendFromRolePath(rolePath) + if err != nil { + return fmt.Errorf("invalid role ID for backend %q: %s", rolePath, err) + } + d.Set("backend", backend) + + roleName, err := nomadSecretBackendRoleNameFromPath(rolePath) + if err != nil { + return fmt.Errorf("invalid role ID %q: %s", rolePath, err) + } + d.Set("role", roleName) + + resp, err := client.Logical().Read(rolePath) + if err != nil { + return fmt.Errorf("error reading %q: %s", rolePath, err) + } + log.Printf("[DEBUG] Read %q", rolePath) + + if resp == nil { + log.Printf("[WARN] %q not found, removing from state", rolePath) + d.SetId("") + return nil + } + + if val, ok := resp.Data["global"]; ok { + if err := d.Set("global", val); err != nil { + return fmt.Errorf("error setting state key 'global': %s", err) + } + } + + if val, ok := resp.Data["policies"]; ok { + if err := d.Set("policies", val); err != nil { + return fmt.Errorf("error setting state key 'policies': %s", err) + } + } + + if val, ok := resp.Data["type"]; ok { + if err := d.Set("type", val); err != nil { + return fmt.Errorf("error setting state key 'type': %s", err) + } + } + + return nil +} + +func updateNomadRoleResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + rolePath := d.Id() + roleType := d.Get("type").(string) + if roleType == "" { + roleType = "client" + } + + roleName, err := nomadSecretBackendRoleNameFromPath(rolePath) + if err != nil { + return fmt.Errorf("invalid role ID %q: %s", rolePath, err) + } + + log.Printf("[DEBUG] Updating %q", rolePath) + + data := map[string]interface{}{} + data["type"] = roleType + + if raw, ok := d.GetOk("global"); ok { + data["global"] = raw + } + if raw, ok := d.GetOk("policies"); ok { + if roleType == "client" { + data["policies"] = raw + } + } + + if roleType == "client" && data["policies"] == "" { + return fmt.Errorf("error updating role %s: policies are required when role type is 'client'", roleName) + } + + if _, err := client.Logical().Write(rolePath, data); err != nil { + return fmt.Errorf("error updating role %q: %s", rolePath, err) + } + log.Printf("[DEBUG] Updated %q", rolePath) + return readNomadRoleResource(d, meta) +} + +func deleteNomadRoleResource(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + rolePath := d.Id() + log.Printf("[DEBUG] Deleting %q", rolePath) + + if _, err := client.Logical().Delete(rolePath); err != nil && !util.Is404(err) { + return fmt.Errorf("error deleting %q: %s", rolePath, err) + } else if err != nil { + log.Printf("[DEBUG] %q not found, removing from state", rolePath) + d.SetId("") + return nil + } + log.Printf("[DEBUG] Deleted template auth backend role %q", rolePath) + return nil +} + +func nomadSecretBackendRoleNameFromPath(path string) (string, error) { + if !nomadSecretBackendRoleNameFromPathRegex.MatchString(path) { + return "", fmt.Errorf("no name found") + } + res := nomadSecretBackendRoleNameFromPathRegex.FindStringSubmatch(path) + if len(res) != 2 { + return "", fmt.Errorf("unexpected number of matches (%d) for name", len(res)) + } + return res[1], nil +} + +func nomadSecretBackendFromRolePath(path string) (string, error) { + if !nomadSecretBackendFromRolePathRegex.MatchString(path) { + return "", fmt.Errorf("no backend found") + } + res := nomadSecretBackendFromRolePathRegex.FindStringSubmatch(path) + if len(res) != 2 { + return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res)) + } + return res[1], nil +} diff --git a/vault/resource_nomad_secret_role_test.go b/vault/resource_nomad_secret_role_test.go new file mode 100644 index 000000000..55dfe1304 --- /dev/null +++ b/vault/resource_nomad_secret_role_test.go @@ -0,0 +1,144 @@ +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/terraform-provider-vault/util" + "github.com/hashicorp/vault/api" +) + +func TestAccNomadSecretBackendRoleClientBasic(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + CheckDestroy: testAccNomadSecretBackendRoleCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testNomadSecretBackendRoleClientConfig(backend, address, token, "bob", "readonly", true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "role", "bob"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "policies.#", "1"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "policies.0", "readonly"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "global", "true"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "type", "client"), + ), + }, + }, + }) +} + +func TestAccNomadSecretBackendRoleManagementBasic(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + CheckDestroy: testAccNomadSecretBackendRoleCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testNomadSecretBackendRoleManagementConfig(backend, address, token, "bob", false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "role", "bob"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "policies.#", "0"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "global", "false"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "type", "management"), + ), + }, + }, + }) +} + +func TestAccNomadSecretBackendRoleImport(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-nomad") + address, token := util.GetTestNomadCreds(t) + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { util.TestAccPreCheck(t) }, + CheckDestroy: testAccADSecretBackendRoleCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testNomadSecretBackendRoleClientConfig(backend, address, token, "bob", "readonly", true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "role", "bob"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "policies.#", "1"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "policies.0", "readonly"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "global", "true"), + resource.TestCheckResourceAttr("vault_nomad_secret_role.test", "type", "client"), + ), + }, + { + ResourceName: "vault_nomad_secret_role.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNomadSecretBackendRoleCheckDestroy(s *terraform.State) error { + client := testProvider.Meta().(*api.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vault_nomad_secret_role" { + continue + } + secret, err := client.Logical().Read(rs.Primary.ID) + if err != nil { + return err + } + if secret != nil { + return fmt.Errorf("role %q still exists", rs.Primary.ID) + } + } + return nil +} + +func testNomadSecretBackendRoleClientConfig(backend, address, token, role, policies string, global bool) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "%s" + token = "%s" +} + +resource "vault_nomad_secret_role" "test" { + backend = vault_nomad_secret_backend.config.backend + role = "%s" + type = "client" + policies = ["%s"] + global = "%t" +} +`, backend, address, token, role, policies, global) +} + +func testNomadSecretBackendRoleManagementConfig(backend, address, token, role string, global bool) string { + return fmt.Sprintf(` +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "%s" + token = "%s" +} + +resource "vault_nomad_secret_role" "test" { + backend = vault_nomad_secret_backend.config.backend + role = "%s" + type = "management" + global = "%t" +} +`, backend, address, token, role, global) +} diff --git a/website/docs/d/nomad_access_token.html.md b/website/docs/d/nomad_access_token.html.md new file mode 100644 index 000000000..d684acade --- /dev/null +++ b/website/docs/d/nomad_access_token.html.md @@ -0,0 +1,64 @@ +--- +layout: "vault" +page_title: "Vault: vault_nomad_access_token data source" +sidebar_current: "docs-vault-datasource-nomad-access-token" +description: |- + Generates tokens for Nomad. +--- + +# vault\_nomad\_access\_token + +Generates tokens for Nomad. + +~> **Important** All data retrieved from Vault will be +written in cleartext to state file generated by Terraform, will appear in +the console output when Terraform runs, and may be included in plan files +if secrets are interpolated into any resource attributes. +Protect these artifacts accordingly. See +[the main provider documentation](../index.html) +for more details. + +## Example Usage + +```hcl +resource "vault_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "ae20ceaa-... + token = "%s" +} + +resource "vault_nomad_secret_role" "test" { + backend = vault_nomad_secret_backend.config.backend + role = "test" + type = "client" + policies = ["readonly"] +} + +data "vault_nomad_access_token" "token" { + backend = vault_nomad_secret_backend.config.backend + role = vault_nomad_secret_role.test.role + depends_on = [vault_nomad_secret_role.test] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `backend` - (Required) The path to the Nomad secret backend to +read credentials from, with no leading or trailing `/`s. + +* `role` - (Required) The name of the Nomad secret backend role to generate +a token for, with no leading or trailing `/`s. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `accessor_id` - The public identifier for a specific token. It can be used +to look up information about a token or to revoke a token. + +* `secret_id` - The token to be used when making requests to Nomad and should be kept private. \ No newline at end of file diff --git a/website/docs/r/nomad_secret_backend.html.md b/website/docs/r/nomad_secret_backend.html.md new file mode 100644 index 000000000..f019db37f --- /dev/null +++ b/website/docs/r/nomad_secret_backend.html.md @@ -0,0 +1,75 @@ +--- +layout: "vault" +page_title: "Vault: vault_nomad_secret_backend resource" +sidebar_current: "docs-vault-resource-nomad-secret-backend" +description: |- + Creates a Nomad secret backend for Vault. +--- + +# vault\_nomad\_secret\_backend + +Creates a Nomad Secret Backend for Vault. The Nomad secret backend for Vault +generates Nomad ACL tokens dynamically based on pre-existing Nomad ACL policies. + +~> **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_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "ae20ceaa-... + token = "%s" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `backend` - (Optional) The unique path this backend should be mounted at. Must +not begin or end with a `/`. Defaults to `nomad`. + +* `address` - (Required) Specifies the address of the Nomad instance, provided +as "protocol://host:port" like "http://127.0.0.1:4646". + +* `ca_cert` - (Optional) CA certificate to use when verifying the Nomad server certificate, must be +x509 PEM encoded. + +* `client_cert` - (Optional) Client certificate to provide to the Nomad server, must be x509 PEM encoded. + +* `client_key` - (Optional) Client certificate key to provide to the Nomad server, must be x509 PEM encoded. + +* `default_lease_ttl_seconds` - (Optional) Default lease duration for secrets in seconds. + +* `description` - (Optional) Human-friendly description of the mount for the Active Directory backend. + +* `local` - (Optional) Mark the secrets engine as local-only. Local engines are not replicated or removed by +replication.Tolerance duration to use when checking the last rotation time. + +* `max_token_name_length` - (Optional) Specifies the maximum length to use for the name of the Nomad token +generated with Generate Credential. If omitted, 0 is used and ignored, defaulting to the max value allowed +by the Nomad version. + +* `token` - (Required) Specifies the Nomad Management token to use. + + +## Attributes Reference + +No additional attributes are exported by this resource. + +## Import + +Nomad secret backend can be imported using the `backend`, e.g. + +``` +$ terraform import vault_nomad_secret_backend.nomad nomad +``` diff --git a/website/docs/r/nomad_secret_lease.html.md b/website/docs/r/nomad_secret_lease.html.md new file mode 100644 index 000000000..ab1066f7d --- /dev/null +++ b/website/docs/r/nomad_secret_lease.html.md @@ -0,0 +1,60 @@ +--- +layout: "vault" +page_title: "Vault: vault_nomad_secret_lease resource" +sidebar_current: "docs-vault-resource-nomad-secret-lease" +description: |- + Creates a Nomad role. +--- + +# vault\_nomad\_secret\_lease + +Configures the leases associated with generated tokens. + +~> **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_nomad_secret_backend" "config" { + backend = "nomad" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "https://0.0.0.0:4646" + token = "ae20ceaa-... +} +resource "vault_nomad_secret_lease" "lease" { + backend = vault_nomad_secret_backend.config.backend + max_ttl = 60 + ttl = 30 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `backend` - (Optional) The unique path this backend should be mounted at. Must +not begin or end with a `/`. Defaults to `nomad`. + +* `max_ttl` - (Optional) Maximum possible lease duration for secrets in seconds. + +* `ttl` - (Optional) Specifies the ttl of the lease for the generated token. + + +## Attributes Reference + +No additional attributes are exported by this resource. + +## Import + +Nomad secret lease can be imported using the `backend`, e.g. + +``` +$ terraform import vault_nomad_secret_lease.lease nomad +``` diff --git a/website/docs/r/nomad_secret_role.html.md b/website/docs/r/nomad_secret_role.html.md new file mode 100644 index 000000000..64ac4f502 --- /dev/null +++ b/website/docs/r/nomad_secret_role.html.md @@ -0,0 +1,72 @@ +--- +layout: "vault" +page_title: "Vault: vault_nomad_secret_role resource" +sidebar_current: "docs-vault-resource-nomad-secret-role" +description: |- + Creates a Nomad role. +--- + +# vault\_nomad\_secret\_role + +Creates a Vault role for a Nomad token. This role configures how generated tokens +will function. + +~> **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_nomad_secret_backend" "config" { + backend = "%s" + description = "test description" + default_lease_ttl_seconds = "3600" + max_lease_ttl_seconds = "7200" + address = "ae20ceaa-... + token = "%s" +} + +resource "vault_nomad_secret_role" "test" { + backend = vault_nomad_secret_backend.config.backend + role = "test" + type = "client" + policies = ["readonly"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `backend` - (Optional) The unique path this backend should be mounted at. Must +not begin or end with a `/`. Defaults to `nomad`. + +* `role` - (Required) The name to identify this role within the backend. +Must be unique within the backend. + +* `global` - (Optional) Specifies if the generated token should be global. Defaults to +false. + +* `policies` - (Optional) List of policies attached to the generated token. This setting is only used +when `type` is 'client'. + +* `type` - (Optional) Specifies the type of token to create when using this role. Valid +settings are 'client' and 'management'. Defaults to 'client'. + + + +## Attributes Reference + +No additional attributes are exported by this resource. + +## Import + +Nomad secret role can be imported using the `backend`, e.g. + +``` +$ terraform import vault_nomad_secret_role.bob nomad/role/bob +``` From 98b38ee8d16522661cc0e28baf769aa3defdb691 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Tue, 1 Dec 2020 20:41:10 -0500 Subject: [PATCH 2/9] Update vault/resource_nomad_secret_backend.go Co-authored-by: Theron Voran --- vault/resource_nomad_secret_backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault/resource_nomad_secret_backend.go b/vault/resource_nomad_secret_backend.go index e609e623a..33fd9c2b8 100644 --- a/vault/resource_nomad_secret_backend.go +++ b/vault/resource_nomad_secret_backend.go @@ -60,7 +60,7 @@ func nomadSecretAccessBackendResource() *schema.Resource { Type: schema.TypeBool, Required: false, Optional: true, - Description: `Mark the secrets engine as local-only. Local engines are not replicated or removed by replication.Tolerance duration to use when checking the last rotation time.`, + Description: `Mark the secrets engine as local-only. Local engines are not replicated or removed by replication. Tolerance duration to use when checking the last rotation time.`, }, "max_lease_ttl_seconds": { Type: schema.TypeInt, From 8a817a1a27b257a749775318d26ec59139744df9 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Tue, 1 Dec 2020 20:46:31 -0500 Subject: [PATCH 3/9] Update vault/resource_nomad_secret_role.go Co-authored-by: Theron Voran --- vault/resource_nomad_secret_role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault/resource_nomad_secret_role.go b/vault/resource_nomad_secret_role.go index b9f8375d3..5eceffc7e 100644 --- a/vault/resource_nomad_secret_role.go +++ b/vault/resource_nomad_secret_role.go @@ -86,7 +86,7 @@ func createNomadRoleResource(d *schema.ResourceData, meta interface{}) error { data["global"] = v } - // Policies are required if role type is 'client', so settiing up + // Policies are required if role type is 'client', so setting up // to enforce that here. if v, ok := d.GetOkExists("policies"); ok { if roleType == "client" { From 2ac09f186974ce858070fc4cd0995d18aeb80f9f Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Wed, 2 Dec 2020 10:36:07 -0500 Subject: [PATCH 4/9] Change PathInventory to config/access --- vault/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault/provider.go b/vault/provider.go index f3e95c5ca..e2b539510 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -472,7 +472,7 @@ var ( }, "vault_nomad_secret_backend": { Resource: nomadSecretAccessBackendResource(), - PathInventory: []string{"/nomad"}, + PathInventory: []string{"/nomad/config/access"}, }, "vault_nomad_secret_lease": { Resource: nomadSecretLeaseBackendResource(), From 496d0d58a7e6da61b01c9ea3c3f1e8e9895648b7 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:03:39 -0500 Subject: [PATCH 5/9] Add more paths to inventory --- vault/provider.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vault/provider.go b/vault/provider.go index e2b539510..c65d20761 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -471,8 +471,11 @@ var ( PathInventory: []string{"/auth/ldap/groups/{name}"}, }, "vault_nomad_secret_backend": { - Resource: nomadSecretAccessBackendResource(), - PathInventory: []string{"/nomad/config/access"}, + Resource: nomadSecretAccessBackendResource(), + PathInventory: []string{ + "/nomad", + "/nomad/config/access", + }, }, "vault_nomad_secret_lease": { Resource: nomadSecretLeaseBackendResource(), From 247f849b6bd79269dbcaa5491952bd90eb01d3de Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:45:40 -0500 Subject: [PATCH 6/9] Merge lease config with backend resource --- vault/provider.go | 5 +- vault/resource_nomad_secret_backend.go | 83 +++++++++- vault/resource_nomad_secret_backend_test.go | 16 +- vault/resource_nomad_secret_lease.go | 163 -------------------- vault/resource_nomad_secret_lease_test.go | 59 ------- website/docs/d/nomad_access_token.html.md | 6 +- website/docs/r/nomad_secret_backend.html.md | 13 +- website/docs/r/nomad_secret_lease.html.md | 60 ------- website/docs/r/nomad_secret_role.html.md | 6 +- 9 files changed, 106 insertions(+), 305 deletions(-) delete mode 100644 vault/resource_nomad_secret_lease.go delete mode 100644 vault/resource_nomad_secret_lease_test.go delete mode 100644 website/docs/r/nomad_secret_lease.html.md diff --git a/vault/provider.go b/vault/provider.go index c65d20761..614d904da 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -475,12 +475,9 @@ var ( PathInventory: []string{ "/nomad", "/nomad/config/access", + "/nomad/config/lease", }, }, - "vault_nomad_secret_lease": { - Resource: nomadSecretLeaseBackendResource(), - PathInventory: []string{"/nomad/config/lease"}, - }, "vault_nomad_secret_role": { Resource: nomadSecretBackendRoleResource(), PathInventory: []string{"/nomad/role/{role}"}, diff --git a/vault/resource_nomad_secret_backend.go b/vault/resource_nomad_secret_backend.go index 33fd9c2b8..b9db884dc 100644 --- a/vault/resource_nomad_secret_backend.go +++ b/vault/resource_nomad_secret_backend.go @@ -74,12 +74,24 @@ func nomadSecretAccessBackendResource() *schema.Resource { Computed: true, Description: `Specifies the maximum length to use for the name of the Nomad token generated with Generate Credential. If omitted, 0 is used and ignored, defaulting to the max value allowed by the Nomad version.`, }, + "max_ttl": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Maximum possible lease duration for secrets in seconds.", + }, "token": { Type: schema.TypeString, Required: true, Sensitive: true, Description: `Specifies the Nomad Management token to use.`, }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Maximum possible lease duration for secrets in seconds.", + }, } return &schema.Resource{ Create: createNomadAccessConfigResource, @@ -149,7 +161,22 @@ func createNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("error writing %q: %s", configPath, err) } - log.Printf("[DEBUG] Wrote %q", configPath) + dataLease := map[string]interface{}{} + if v, ok := d.GetOkExists("max_ttl"); ok { + dataLease["max_ttl"] = v + } + + if v, ok := d.GetOkExists("ttl"); ok { + dataLease["ttl"] = v + } + + configLeasePath := fmt.Sprintf("%s/config/lease", backend) + log.Printf("[DEBUG] Writing %q", configLeasePath) + if _, err := client.Logical().Write(configLeasePath, dataLease); err != nil { + return fmt.Errorf("error writing %q: %s", configLeasePath, err) + } + + log.Printf("[DEBUG] Wrote %q", configLeasePath) return readNomadAccessConfigResource(d, meta) } @@ -222,6 +249,32 @@ func readNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) err } } + configLeasePath := fmt.Sprintf("%s/config/lease", d.Id()) + log.Printf("[DEBUG] Reading %q", configLeasePath) + + resp, err = client.Logical().Read(configLeasePath) + if err != nil { + return fmt.Errorf("error reading %q: %s", configLeasePath, err) + } + log.Printf("[DEBUG] Read %q", configLeasePath) + if resp == nil { + log.Printf("[WARN] %q not found, removing from state", configLeasePath) + d.SetId("") + return nil + } + + if val, ok := resp.Data["max_ttl"]; ok { + if err := d.Set("max_ttl", val); err != nil { + return fmt.Errorf("error setting state key 'max_ttl': %s", err) + } + } + + if val, ok := resp.Data["ttl"]; ok { + if err := d.Set("ttl", val); err != nil { + return fmt.Errorf("error setting state key 'ttl': %s", err) + } + } + return nil } @@ -251,8 +304,8 @@ func updateNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) e } } - vaultPath := fmt.Sprintf("%s/config/access", backend) - log.Printf("[DEBUG] Updating %q", vaultPath) + configPath := fmt.Sprintf("%s/config/access", backend) + log.Printf("[DEBUG] Updating %q", configPath) if raw, ok := d.GetOk("address"); ok { data["address"] = raw @@ -278,11 +331,29 @@ func updateNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) e data["token"] = raw } - if _, err := client.Logical().Write(vaultPath, data); err != nil { - return fmt.Errorf("error updating template auth backend role %q: %s", vaultPath, err) + if _, err := client.Logical().Write(configPath, data); err != nil { + return fmt.Errorf("error updating access config %q: %s", configPath, err) + } + log.Printf("[DEBUG] Updated %q", configPath) + + configLeasePath := fmt.Sprintf("%s/config/lease", backend) + log.Printf("[DEBUG] Updating %q", configLeasePath) + + dataLease := map[string]interface{}{} + + if raw, ok := d.GetOk("max_ttl"); ok { + dataLease["max_ttl"] = raw + } + + if raw, ok := d.GetOk("ttl"); ok { + dataLease["ttl"] = raw + } + + if _, err := client.Logical().Write(configLeasePath, dataLease); err != nil { + return fmt.Errorf("error updating lease config %q: %s", configLeasePath, err) } - log.Printf("[DEBUG] Updated %q", vaultPath) + log.Printf("[DEBUG] Updated %q", configLeasePath) return readNomadAccessConfigResource(d, meta) } diff --git a/vault/resource_nomad_secret_backend_test.go b/vault/resource_nomad_secret_backend_test.go index 2fb6af5a1..8e5cfad58 100644 --- a/vault/resource_nomad_secret_backend_test.go +++ b/vault/resource_nomad_secret_backend_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/vault/api" ) -func TestNomadSecretBackend(t *testing.T) { +func TestAccNomadSecretBackend(t *testing.T) { backend := acctest.RandomWithPrefix("tf-test-nomad") address, token := util.GetTestNomadCreds(t) @@ -30,6 +30,8 @@ func TestNomadSecretBackend(t *testing.T) { resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "default_lease_ttl_seconds", "3600"), resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_lease_ttl_seconds", "7200"), resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "address", address), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_ttl", "60"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "ttl", "30"), ), }, { @@ -40,6 +42,8 @@ func TestNomadSecretBackend(t *testing.T) { resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "default_lease_ttl_seconds", "7200"), resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_lease_ttl_seconds", "14400"), resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "address", "foobar"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_ttl", "90"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "ttl", "60"), ), }, }, @@ -71,26 +75,30 @@ func testAccNomadSecretBackendCheckDestroy(s *terraform.State) error { func testNomadSecretBackendInitialConfig(backend, address, token string) string { return fmt.Sprintf(` -resource "vault_nomad_secret_backend" "config" { +resource "vault_nomad_secret_backend" "test" { backend = "%s" description = "test description" default_lease_ttl_seconds = "3600" max_lease_ttl_seconds = "7200" address = "%s" token = "%s" + max_ttl = "60" + ttl = "30" } `, backend, address, token) } func testNomadSecretBackendUpdateConfig(backend, address, token string) string { return fmt.Sprintf(` -resource "vault_nomad_secret_backend" "config" { +resource "vault_nomad_secret_backend" "test" { backend = "%s" description = "test description" default_lease_ttl_seconds = "7200" - max_lease_ttl_seconds = "14000" + max_lease_ttl_seconds = "14400" address = "%s" token = "%s" + max_ttl = "90" + ttl = "60" } `, backend, address, token) } diff --git a/vault/resource_nomad_secret_lease.go b/vault/resource_nomad_secret_lease.go deleted file mode 100644 index f61751f1f..000000000 --- a/vault/resource_nomad_secret_lease.go +++ /dev/null @@ -1,163 +0,0 @@ -package vault - -import ( - "fmt" - "log" - "regexp" - "strings" - - "github.com/hashicorp/terraform-provider-vault/util" - - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/vault/api" -) - -var ( - nomadSecretBackendFromPathRegex = regexp.MustCompile("^(.+)/config/lease") -) - -func nomadSecretLeaseBackendResource() *schema.Resource { - fields := map[string]*schema.Schema{ - "backend": { - Type: schema.TypeString, - Default: "nomad", - ForceNew: true, - Optional: true, - Description: "The mount path for the Nomad backend.", - StateFunc: func(v interface{}) string { - return strings.Trim(v.(string), "/") - }, - }, - "max_ttl": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - Description: "Maximum possible lease duration for secrets in seconds.", - }, - "ttl": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - Description: "Maximum possible lease duration for secrets in seconds.", - }, - } - return &schema.Resource{ - Create: createNomadLeaseConfigResource, - Update: updateNomadLeaseConfigResource, - Read: readNomadLeaseConfigResource, - Delete: deleteNomadLeaseConfigResource, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: fields, - } -} - -func createNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { - client := meta.(*api.Client) - backend := d.Get("backend").(string) - configPath := fmt.Sprintf("%s/config/lease", backend) - - log.Printf("[DEBUG] Creating %q", configPath) - - data := map[string]interface{}{} - if v, ok := d.GetOkExists("max_ttl"); ok { - data["max_ttl"] = v - } - - if v, ok := d.GetOkExists("ttl"); ok { - data["ttl"] = v - } - - log.Printf("[DEBUG] Writing %q", configPath) - if _, err := client.Logical().Write(configPath, data); err != nil { - return fmt.Errorf("error writing %q: %s", configPath, err) - } - d.SetId(configPath) - log.Printf("[DEBUG] Wrote %q", configPath) - return readNomadLeaseConfigResource(d, meta) -} - -func readNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { - client := meta.(*api.Client) - configPath := d.Id() - log.Printf("[DEBUG] Reading %q", configPath) - - backend, err := nomadSecretBackendFromPath(configPath) - if err != nil { - return fmt.Errorf("invalid lease config ID %q: %s", configPath, err) - } - d.Set("backend", backend) - - resp, err := client.Logical().Read(configPath) - if err != nil { - return fmt.Errorf("error reading %q: %s", configPath, err) - } - log.Printf("[DEBUG] Read %q", configPath) - - if resp == nil { - log.Printf("[WARN] %q not found, removing from state", configPath) - d.SetId("") - return nil - } - - if val, ok := resp.Data["max_ttl"]; ok { - if err := d.Set("max_ttl", val); err != nil { - return fmt.Errorf("error setting state key 'max_ttl': %s", err) - } - } - - if val, ok := resp.Data["ttl"]; ok { - if err := d.Set("ttl", val); err != nil { - return fmt.Errorf("error setting state key 'ttl': %s", err) - } - } - - return nil -} - -func updateNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { - client := meta.(*api.Client) - configPath := d.Id() - log.Printf("[DEBUG] Updating %q", configPath) - - data := map[string]interface{}{} - if raw, ok := d.GetOk("max_ttl"); ok { - data["max_ttl"] = raw - } - if raw, ok := d.GetOk("ttl"); ok { - data["ttl"] = raw - } - if _, err := client.Logical().Write(configPath, data); err != nil { - return fmt.Errorf("error updating lease config %q: %s", configPath, err) - } - log.Printf("[DEBUG] Updated %q", configPath) - return readNomadLeaseConfigResource(d, meta) -} - -func deleteNomadLeaseConfigResource(d *schema.ResourceData, meta interface{}) error { - client := meta.(*api.Client) - configPath := d.Id() - log.Printf("[DEBUG] Deleting %q", configPath) - - if _, err := client.Logical().Delete(configPath); err != nil && !util.Is404(err) { - return fmt.Errorf("error deleting %q: %s", configPath, err) - } else if err != nil { - log.Printf("[DEBUG] %q not found, removing from state", configPath) - d.SetId("") - return nil - } - log.Printf("[DEBUG] Deleted lease config %q", configPath) - return nil -} - -func nomadSecretBackendFromPath(path string) (string, error) { - if !nomadSecretBackendFromPathRegex.MatchString(path) { - return "", fmt.Errorf("no backend found") - } - res := nomadSecretBackendFromPathRegex.FindStringSubmatch(path) - if len(res) != 2 { - return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res)) - } - return res[1], nil -} diff --git a/vault/resource_nomad_secret_lease_test.go b/vault/resource_nomad_secret_lease_test.go deleted file mode 100644 index 9d1c293e1..000000000 --- a/vault/resource_nomad_secret_lease_test.go +++ /dev/null @@ -1,59 +0,0 @@ -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-provider-vault/util" -) - -func TestAccNomadSecretLease(t *testing.T) { - backend := acctest.RandomWithPrefix("tf-test-nomad") - address, token := util.GetTestNomadCreds(t) - - resource.Test(t, resource.TestCase{ - Providers: testProviders, - PreCheck: func() { util.TestAccPreCheck(t) }, - PreventPostDestroyRefresh: true, - CheckDestroy: testAccNomadSecretBackendCheckDestroy, - Steps: []resource.TestStep{ - { - Config: testNomadSecretBackendInitialLeaseConfig(backend, address, token, 3600, 1800), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "backend", backend), - resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "max_ttl", "3600"), - resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "ttl", "1800"), - ), - }, - { - Config: testNomadSecretBackendInitialLeaseConfig(backend, address, token, 7200, 3600), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "backend", backend), - resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "max_ttl", "7200"), - resource.TestCheckResourceAttr("vault_nomad_secret_lease.test", "ttl", "3600"), - ), - }, - }, - }) -} - -func testNomadSecretBackendInitialLeaseConfig(backend, address, token string, maxTTL, ttl int) string { - return fmt.Sprintf(` -resource "vault_nomad_secret_backend" "config" { - backend = "%s" - description = "test description" - default_lease_ttl_seconds = "3600" - max_lease_ttl_seconds = "7200" - address = "%s" - token = "%s" -} - -resource "vault_nomad_secret_lease" "test" { - backend = vault_nomad_secret_backend.config.backend - max_ttl = %d - ttl = %d -} -`, backend, address, token, maxTTL, ttl) -} diff --git a/website/docs/d/nomad_access_token.html.md b/website/docs/d/nomad_access_token.html.md index d684acade..bceb17bbc 100644 --- a/website/docs/d/nomad_access_token.html.md +++ b/website/docs/d/nomad_access_token.html.md @@ -22,12 +22,12 @@ for more details. ```hcl resource "vault_nomad_secret_backend" "config" { - backend = "%s" + backend = "nomad" description = "test description" default_lease_ttl_seconds = "3600" max_lease_ttl_seconds = "7200" - address = "ae20ceaa-... - token = "%s" + address = "https://127.0.0.1:4646" + token = "ae20ceaa-..." } resource "vault_nomad_secret_role" "test" { diff --git a/website/docs/r/nomad_secret_backend.html.md b/website/docs/r/nomad_secret_backend.html.md index f019db37f..6ee264275 100644 --- a/website/docs/r/nomad_secret_backend.html.md +++ b/website/docs/r/nomad_secret_backend.html.md @@ -22,12 +22,14 @@ for more details. ```hcl resource "vault_nomad_secret_backend" "config" { - backend = "%s" + backend = "nomad" description = "test description" default_lease_ttl_seconds = "3600" max_lease_ttl_seconds = "7200" - address = "ae20ceaa-... - token = "%s" + max_ttl = "240" + address = "https://127.0.0.1:4646" + token = "ae20ceaa-... + ttl = "120" } ``` @@ -59,8 +61,13 @@ replication.Tolerance duration to use when checking the last rotation time. generated with Generate Credential. If omitted, 0 is used and ignored, defaulting to the max value allowed by the Nomad version. +* `max_ttl` - (Optional) Maximum possible lease duration for secrets in seconds. + * `token` - (Required) Specifies the Nomad Management token to use. +* `ttl` - (Optional) Specifies the ttl of the lease for the generated token. + + ## Attributes Reference diff --git a/website/docs/r/nomad_secret_lease.html.md b/website/docs/r/nomad_secret_lease.html.md deleted file mode 100644 index ab1066f7d..000000000 --- a/website/docs/r/nomad_secret_lease.html.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: "vault" -page_title: "Vault: vault_nomad_secret_lease resource" -sidebar_current: "docs-vault-resource-nomad-secret-lease" -description: |- - Creates a Nomad role. ---- - -# vault\_nomad\_secret\_lease - -Configures the leases associated with generated tokens. - -~> **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_nomad_secret_backend" "config" { - backend = "nomad" - description = "test description" - default_lease_ttl_seconds = "3600" - max_lease_ttl_seconds = "7200" - address = "https://0.0.0.0:4646" - token = "ae20ceaa-... -} -resource "vault_nomad_secret_lease" "lease" { - backend = vault_nomad_secret_backend.config.backend - max_ttl = 60 - ttl = 30 -} -``` - -## Argument Reference - -The following arguments are supported: - -* `backend` - (Optional) The unique path this backend should be mounted at. Must -not begin or end with a `/`. Defaults to `nomad`. - -* `max_ttl` - (Optional) Maximum possible lease duration for secrets in seconds. - -* `ttl` - (Optional) Specifies the ttl of the lease for the generated token. - - -## Attributes Reference - -No additional attributes are exported by this resource. - -## Import - -Nomad secret lease can be imported using the `backend`, e.g. - -``` -$ terraform import vault_nomad_secret_lease.lease nomad -``` diff --git a/website/docs/r/nomad_secret_role.html.md b/website/docs/r/nomad_secret_role.html.md index 64ac4f502..de5fefb7c 100644 --- a/website/docs/r/nomad_secret_role.html.md +++ b/website/docs/r/nomad_secret_role.html.md @@ -22,12 +22,12 @@ for more details. ```hcl resource "vault_nomad_secret_backend" "config" { - backend = "%s" + backend = "nomad" description = "test description" default_lease_ttl_seconds = "3600" max_lease_ttl_seconds = "7200" - address = "ae20ceaa-... - token = "%s" + address = "https://127.0.0.1:4646" + token = "ae20ceaa-..." } resource "vault_nomad_secret_role" "test" { From 3d0c71e468db456a02ce4ab7f9d22b2033cd4092 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Thu, 3 Dec 2020 14:27:00 -0500 Subject: [PATCH 7/9] Fix updating mount parameters --- vault/resource_nomad_secret_backend.go | 19 +++------ vault/resource_nomad_secret_backend_test.go | 43 ++++++++++----------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/vault/resource_nomad_secret_backend.go b/vault/resource_nomad_secret_backend.go index b9db884dc..e008eca93 100644 --- a/vault/resource_nomad_secret_backend.go +++ b/vault/resource_nomad_secret_backend.go @@ -282,26 +282,19 @@ func updateNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) e backend := d.Id() client := meta.(*api.Client) - defaultTTL := d.Get("default_lease_ttl_seconds").(int) - maxTTL := d.Get("max_lease_ttl_seconds").(int) tune := api.MountConfigInput{} data := map[string]interface{}{} - if defaultTTL != 0 { - tune.DefaultLeaseTTL = fmt.Sprintf("%ds", defaultTTL) - data["default_lease_ttl_seconds"] = defaultTTL - } - - if maxTTL != 0 { - tune.MaxLeaseTTL = fmt.Sprintf("%ds", maxTTL) - data["max_lease_ttl_seconds"] = maxTTL - } + if d.HasChange("default_lease_ttl_seconds") || d.HasChange("max_lease_ttl_seconds") { + tune.DefaultLeaseTTL = fmt.Sprintf("%ds", d.Get("default_lease_ttl_seconds")) + tune.MaxLeaseTTL = fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds")) - if tune.DefaultLeaseTTL != "0" || tune.MaxLeaseTTL != "0" { + log.Printf("[DEBUG] Updating mount lease TTLs for %q", backend) err := client.Sys().TuneMount(backend, tune) if err != nil { - return fmt.Errorf("error mounting to %q: %s", backend, err) + return fmt.Errorf("error updating mount TTLs for %q: %s", backend, err) } + log.Printf("[DEBUG] Updated lease TTLs for %q", backend) } configPath := fmt.Sprintf("%s/config/access", backend) diff --git a/vault/resource_nomad_secret_backend_test.go b/vault/resource_nomad_secret_backend_test.go index 8e5cfad58..0e2759f51 100644 --- a/vault/resource_nomad_secret_backend_test.go +++ b/vault/resource_nomad_secret_backend_test.go @@ -23,7 +23,7 @@ func TestAccNomadSecretBackend(t *testing.T) { CheckDestroy: testAccNomadSecretBackendCheckDestroy, Steps: []resource.TestStep{ { - Config: testNomadSecretBackendInitialConfig(backend, address, token), + Config: testNomadSecretBackendConfig(backend, address, token, 60, 30, 3600, 7200), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "backend", backend), resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "description", "test description"), @@ -35,7 +35,7 @@ func TestAccNomadSecretBackend(t *testing.T) { ), }, { - Config: testNomadSecretBackendUpdateConfig(backend, "foobar", token), + Config: testNomadSecretBackendConfig(backend, "foobar", token, 90, 60, 7200, 14400), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "backend", backend), resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "description", "test description"), @@ -46,6 +46,18 @@ func TestAccNomadSecretBackend(t *testing.T) { resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "ttl", "60"), ), }, + { + Config: testNomadSecretBackendConfig(backend, "foobar", token, 0, 0, -1, -1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "backend", backend), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "description", "test description"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "default_lease_ttl_seconds", "-1"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_lease_ttl_seconds", "-1"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "address", "foobar"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "max_ttl", "0"), + resource.TestCheckResourceAttr("vault_nomad_secret_backend.test", "ttl", "0"), + ), + }, }, }) } @@ -73,32 +85,17 @@ func testAccNomadSecretBackendCheckDestroy(s *terraform.State) error { return nil } -func testNomadSecretBackendInitialConfig(backend, address, token string) string { - return fmt.Sprintf(` -resource "vault_nomad_secret_backend" "test" { - backend = "%s" - description = "test description" - default_lease_ttl_seconds = "3600" - max_lease_ttl_seconds = "7200" - address = "%s" - token = "%s" - max_ttl = "60" - ttl = "30" -} -`, backend, address, token) -} - -func testNomadSecretBackendUpdateConfig(backend, address, token string) string { +func testNomadSecretBackendConfig(backend, address, token string, maxTTL, ttl, defaultLease, maxLease int) string { return fmt.Sprintf(` resource "vault_nomad_secret_backend" "test" { backend = "%s" description = "test description" - default_lease_ttl_seconds = "7200" - max_lease_ttl_seconds = "14400" address = "%s" token = "%s" - max_ttl = "90" - ttl = "60" + max_ttl = "%d" + ttl = "%d" + default_lease_ttl_seconds = "%d" + max_lease_ttl_seconds = "%d" } -`, backend, address, token) +`, backend, address, token, maxTTL, ttl, defaultLease, maxLease) } From 9a611db221fc74b6e68d4652b1dc71114c207217 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Tue, 8 Dec 2020 16:44:39 -0500 Subject: [PATCH 8/9] Remove required flag from token/address --- vault/resource_nomad_secret_backend.go | 14 +++++++------- website/docs/r/nomad_secret_backend.html.md | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vault/resource_nomad_secret_backend.go b/vault/resource_nomad_secret_backend.go index e008eca93..d520966ce 100644 --- a/vault/resource_nomad_secret_backend.go +++ b/vault/resource_nomad_secret_backend.go @@ -25,7 +25,7 @@ func nomadSecretAccessBackendResource() *schema.Resource { }, "address": { Type: schema.TypeString, - Required: true, + Optional: true, Description: `Specifies the address of the Nomad instance, provided as "protocol://host:port" like "http://127.0.0.1:4646".`, }, "ca_cert": { @@ -82,7 +82,7 @@ func nomadSecretAccessBackendResource() *schema.Resource { }, "token": { Type: schema.TypeString, - Required: true, + Optional: true, Sensitive: true, Description: `Specifies the Nomad Management token to use.`, }, @@ -243,11 +243,11 @@ func readNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) err } } - if val, ok := resp.Data["token"]; ok { - if err := d.Set("token", val); err != nil { - return fmt.Errorf("error setting state key 'token': %s", err) - } - } + // if val, ok := resp.Data["token"]; ok { + // if err := d.Set("token", val); err != nil { + // return fmt.Errorf("error setting state key 'token': %s", err) + // } + // } configLeasePath := fmt.Sprintf("%s/config/lease", d.Id()) log.Printf("[DEBUG] Reading %q", configLeasePath) diff --git a/website/docs/r/nomad_secret_backend.html.md b/website/docs/r/nomad_secret_backend.html.md index 6ee264275..598265b0d 100644 --- a/website/docs/r/nomad_secret_backend.html.md +++ b/website/docs/r/nomad_secret_backend.html.md @@ -8,7 +8,7 @@ description: |- # vault\_nomad\_secret\_backend -Creates a Nomad Secret Backend for Vault. The Nomad secret backend for Vault +Creates a Nomad Secret Backend for Vault. The Nomad secret backend for Vault generates Nomad ACL tokens dynamically based on pre-existing Nomad ACL policies. ~> **Important** All data provided in the resource configuration will be @@ -40,7 +40,7 @@ The following arguments are supported: * `backend` - (Optional) The unique path this backend should be mounted at. Must not begin or end with a `/`. Defaults to `nomad`. -* `address` - (Required) Specifies the address of the Nomad instance, provided +* `address` - (Optional) Specifies the address of the Nomad instance, provided as "protocol://host:port" like "http://127.0.0.1:4646". * `ca_cert` - (Optional) CA certificate to use when verifying the Nomad server certificate, must be @@ -57,13 +57,13 @@ x509 PEM encoded. * `local` - (Optional) Mark the secrets engine as local-only. Local engines are not replicated or removed by replication.Tolerance duration to use when checking the last rotation time. -* `max_token_name_length` - (Optional) Specifies the maximum length to use for the name of the Nomad token -generated with Generate Credential. If omitted, 0 is used and ignored, defaulting to the max value allowed +* `max_token_name_length` - (Optional) Specifies the maximum length to use for the name of the Nomad token +generated with Generate Credential. If omitted, 0 is used and ignored, defaulting to the max value allowed by the Nomad version. * `max_ttl` - (Optional) Maximum possible lease duration for secrets in seconds. -* `token` - (Required) Specifies the Nomad Management token to use. +* `token` - (Optional) Specifies the Nomad Management token to use. * `ttl` - (Optional) Specifies the ttl of the lease for the generated token. From 537b2d5268eb1dbf20451f558d5eb3708e677f41 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Thu, 10 Dec 2020 09:35:34 -0500 Subject: [PATCH 9/9] Remove commented code --- vault/resource_nomad_secret_backend.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vault/resource_nomad_secret_backend.go b/vault/resource_nomad_secret_backend.go index d520966ce..6b2e4bf1b 100644 --- a/vault/resource_nomad_secret_backend.go +++ b/vault/resource_nomad_secret_backend.go @@ -243,12 +243,6 @@ func readNomadAccessConfigResource(d *schema.ResourceData, meta interface{}) err } } - // if val, ok := resp.Data["token"]; ok { - // if err := d.Set("token", val); err != nil { - // return fmt.Errorf("error setting state key 'token': %s", err) - // } - // } - configLeasePath := fmt.Sprintf("%s/config/lease", d.Id()) log.Printf("[DEBUG] Reading %q", configLeasePath)