Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bootstrap field to Consul backend resources #1571

Merged
merged 12 commits into from
Aug 11, 2022
39 changes: 32 additions & 7 deletions vault/resource_consul_secret_backend.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vault

import (
"context"
"fmt"
"log"
"strings"
Expand All @@ -13,11 +14,12 @@ import (

func consulSecretBackendResource() *schema.Resource {
return &schema.Resource{
Create: consulSecretBackendCreate,
Read: ReadWrapper(consulSecretBackendRead),
Update: consulSecretBackendUpdate,
Delete: consulSecretBackendDelete,
Exists: consulSecretBackendExists,
Create: consulSecretBackendCreate,
Read: ReadWrapper(consulSecretBackendRead),
Update: consulSecretBackendUpdate,
Delete: consulSecretBackendDelete,
Exists: consulSecretBackendExists,
CustomizeDiff: consulSecretsBackendCustomizeDiff,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Expand Down Expand Up @@ -67,9 +69,15 @@ func consulSecretBackendResource() *schema.Resource {
"token": {
Type: schema.TypeString,
Optional: true,
Description: "Specifies the Consul ACL token to use. This must be a management type token.",
Description: "Specifies the Consul token to use when managing or issuing new tokens.",
Sensitive: true,
},
"bootstrap": {
Type: schema.TypeBool,
Optional: true,
Description: "Denotes a backend resource that is used to bootstrap the Consul ACL system. Only one resource may be used to bootstrap.",
Default: false,
},
"ca_cert": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -157,7 +165,7 @@ func consulSecretBackendCreate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Wrote Consul configuration to %q", configPath)
d.Partial(false)

return nil
return consulSecretBackendRead(d, meta)
robmonte marked this conversation as resolved.
Show resolved Hide resolved
}

func consulSecretBackendRead(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -288,3 +296,20 @@ func consulSecretBackendExists(d *schema.ResourceData, meta interface{}) (bool,
func consulSecretBackendConfigPath(backend string) string {
return strings.Trim(backend, "/") + "/config/access"
}

func consulSecretsBackendCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error {
newToken := diff.Get("token").(string)
newBootstrap := diff.Get("bootstrap").(bool)

// If the user sets bootstrap to false but doesn't provide a token, disallow it.
if newToken == "" && !newBootstrap {
return fmt.Errorf("field 'bootstrap' must be set to true when 'token' is unspecified")
}

// If the user sets bootstrap to true and also provides a token, disallow it.
if newToken != "" && newBootstrap {
return fmt.Errorf("field 'bootstrap' must be set to false when 'token' is specified")
}

return nil
}
40 changes: 29 additions & 11 deletions vault/resource_consul_secret_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func TestConsulSecretBackend(t *testing.T) {
resource.TestCheckNoResourceAttr(resourceName, "client_key"),
),
},
testutil.GetImportTestStep(resourceName, false, "token", "ca_cert", "client_cert", "client_key"),
testutil.GetImportTestStep(resourceName, false,
"token", "bootstrap", "ca_cert", "client_cert", "client_key"),
{
Config: testConsulSecretBackend_initialConfigLocal(path, token),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -59,7 +60,8 @@ func TestConsulSecretBackend(t *testing.T) {
resource.TestCheckNoResourceAttr(resourceName, "client_key"),
),
},
testutil.GetImportTestStep(resourceName, false, "token", "ca_cert", "client_cert", "client_key"),
testutil.GetImportTestStep(resourceName, false,
"token", "bootstrap", "ca_cert", "client_cert", "client_key"),
{
Config: testConsulSecretBackend_updateConfig(path, token),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -76,7 +78,8 @@ func TestConsulSecretBackend(t *testing.T) {
resource.TestCheckNoResourceAttr(resourceName, "client_key"),
),
},
testutil.GetImportTestStep(resourceName, false, "token", "ca_cert", "client_cert", "client_key"),
testutil.GetImportTestStep(resourceName, false,
"token", "bootstrap", "ca_cert", "client_cert", "client_key"),
{
Config: testConsulSecretBackend_updateConfig_addCerts(path, token),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -93,7 +96,8 @@ func TestConsulSecretBackend(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "client_key", "FAKE-CLIENT-CERT-KEY-MATERIAL"),
),
},
testutil.GetImportTestStep(resourceName, false, "token", "ca_cert", "client_cert", "client_key"),
testutil.GetImportTestStep(resourceName, false,
"token", "bootstrap", "ca_cert", "client_cert", "client_key"),
{
Config: testConsulSecretBackend_updateConfig_updateCerts(path, token),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -110,7 +114,8 @@ func TestConsulSecretBackend(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "client_key", "UPDATED-FAKE-CLIENT-CERT-KEY-MATERIAL"),
),
},
testutil.GetImportTestStep(resourceName, false, "token", "ca_cert", "client_cert", "client_key"),
testutil.GetImportTestStep(resourceName, false,
"token", "bootstrap", "ca_cert", "client_cert", "client_key"),
},
})
}
Expand All @@ -134,13 +139,22 @@ func TestConsulSecretBackend_Bootstrap(t *testing.T) {
CheckDestroy: testAccConsulSecretBackendCheckDestroy,
Steps: []resource.TestStep{
{
Config: testConsulSecretBackend_bootstrapConfig(path, consulAddr),
Config: testConsulSecretBackend_bootstrapConfig(path, consulAddr, "", false),
ExpectError: regexp.MustCompile("field 'bootstrap' must be set to true when 'token' is unspecified"),
},
{
Config: testConsulSecretBackend_bootstrapConfig(path, consulAddr, "token", true),
ExpectError: regexp.MustCompile("field 'bootstrap' must be set to false when 'token' is specified"),
},
{
Config: testConsulSecretBackend_bootstrapConfig(path, consulAddr, "", true),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, consts.FieldPath, path),
resource.TestCheckResourceAttr(resourceName, "address", consulAddr),
resource.TestCheckResourceAttr(resourceName, "bootstrap", "true"),
),
},
testutil.GetImportTestStep(resourceName, false),
testutil.GetImportTestStep(resourceName, false, "token", "bootstrap"),
{
Config: testConsulSecretBackend_bootstrapAddRole(path, consulAddr),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -150,9 +164,9 @@ func TestConsulSecretBackend_Bootstrap(t *testing.T) {
resource.TestCheckTypeSetElemAttr(resourceRoleName, "consul_policies.*", "global-management"),
),
},
testutil.GetImportTestStep(resourceName, false),
testutil.GetImportTestStep(resourceName, false, "token", "bootstrap"),
{
Config: testConsulSecretBackend_bootstrapConfig(path+"-new", consulAddr),
Config: testConsulSecretBackend_bootstrapConfig(path+"-new", consulAddr, "", true),
ExpectError: regexp.MustCompile(`Token not provided and failed to bootstrap ACLs`),
},
},
Expand Down Expand Up @@ -211,13 +225,16 @@ resource "vault_consul_secret_backend" "test" {
}`, path, token)
}

func testConsulSecretBackend_bootstrapConfig(path, addr string) string {
func testConsulSecretBackend_bootstrapConfig(path, addr, token string, bootstrap bool) string {
return fmt.Sprintf(`
resource "vault_consul_secret_backend" "test" {
path = "%s"
description = "test description"
address = "%s"
}`, path, addr)
token = "%s"
bootstrap = %t
}
`, path, addr, token, bootstrap)
}

func testConsulSecretBackend_updateConfig(path, token string) string {
Expand All @@ -237,6 +254,7 @@ resource "vault_consul_secret_backend" "test" {
path = "%s"
description = "test description"
address = "%s"
bootstrap = true
}

resource "vault_consul_secret_backend_role" "test" {
Expand Down
40 changes: 30 additions & 10 deletions website/docs/r/consul_secret_backend.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ description: |-

# vault\_consul\_secret\_backend

Creates a Consul Secret Backend for Vault. Consul secret backends can then issue Consul tokens, once a role has been added to the backend.
Creates a Consul Secret Backend for Vault. Consul secret backends can then issue Consul tokens, once a role has been
added to the backend.

~> **Important** All data provided in the resource configuration will be
written in cleartext to state and plan files generated by Terraform, and
Expand All @@ -19,13 +20,23 @@ for more details.

## Example Usage

#### Creating a standard backend resource:
```hcl
resource "vault_consul_secret_backend" "test" {
path = "consul"
description = "Manages the Consul backend"
address = "127.0.0.1:8500"
token = "4240861b-ce3d-8530-115a-521ff070dd29"
}
```

address = "127.0.0.1:8500"
token = "4240861b-ce3d-8530-115a-521ff070dd29"
#### Creating a backend resource to bootstrap a new Consul instance:
```hcl
resource "vault_consul_secret_backend" "test" {
path = "consul"
description = "Bootstrap the Consul backend"
address = "127.0.0.1:8500"
bootstrap = true
}
```

Expand All @@ -38,13 +49,20 @@ The following arguments are supported:
The `namespace` is always relative to the provider's configured [namespace](/docs/providers/vault#namespace).
*Available only for Vault Enterprise*.

* `token` - (Required) The Consul management token this backend should use to issue new tokens.
* `token` - (Optional) The Consul management token this backend should use to issue new tokens. This field is required
robmonte marked this conversation as resolved.
Show resolved Hide resolved
when `bootstrap` is false.

~> **Important** Because Vault does not support reading the configured token back from the API, Terraform cannot detect
and correct drift on `token`. Changing the value, however, _will_ overwrite the previously stored values.

* `bootstrap` - (Optional) Denotes that the resource is used to bootstrap the Consul ACL system.

~> **Important** Because Vault does not support reading the configured
token back from the API, Terraform cannot detect and correct drift
on `token`. Changing the value, however, _will_ overwrite the previously stored values.
~> **Important** When `bootstrap` is true, Vault will attempt to bootstrap the Consul server. The token returned from
this operation will only ever be known to Vault. If the resource is ever destroyed, the bootstrap token will be lost
and a [Consul reset may be required.](https://learn.hashicorp.com/tutorials/consul/access-control-troubleshoot#reset-the-acl-system)

* `path` - (Optional) The unique location this backend should be mounted at. Must not begin or end with a `/`. Defaults to `consul`.
* `path` - (Optional) The unique location this backend should be mounted at. Must not begin or end with a `/`. Defaults
to `consul`.

* `description` - (Optional) A human-friendly description for this backend.

Expand All @@ -54,9 +72,11 @@ on `token`. Changing the value, however, _will_ overwrite the previously stored

* `ca_cert` - (Optional) CA certificate to use when verifying Consul server certificate, must be x509 PEM encoded.

* `client_cert` - (Optional) Client certificate used for Consul's TLS communication, must be x509 PEM encoded and if this is set you need to also set client_key.
* `client_cert` - (Optional) Client certificate used for Consul's TLS communication, must be x509 PEM encoded and if
this is set you need to also set client_key.

* `client_key` - (Optional) Client key used for Consul's TLS communication, must be x509 PEM encoded and if this is set you need to also set client_cert.
* `client_key` - (Optional) Client key used for Consul's TLS communication, must be x509 PEM encoded and if this is set
you need to also set client_cert.

* `default_lease_ttl_seconds` - (Optional) The default TTL for credentials issued by this backend.

Expand Down