-
Notifications
You must be signed in to change notification settings - Fork 535
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 support to set default issuers configuration for PKI Secrets Engine #1937
Changes from all commits
30b3e65
32d41b9
efa876a
2198ae1
c270c76
db5859b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vault | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"regexp" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
"github.com/hashicorp/terraform-provider-vault/internal/consts" | ||
"github.com/hashicorp/terraform-provider-vault/internal/provider" | ||
) | ||
|
||
var pkiSecretBackendFromConfigIssuersPathRegex = regexp.MustCompile("^(.+)/config/issuers") | ||
|
||
const ( | ||
fieldDefaultFollowsLatestIssuer = "default_follows_latest_issuer" | ||
) | ||
|
||
func pkiSecretBackendConfigIssuers() *schema.Resource { | ||
return &schema.Resource{ | ||
CreateContext: provider.MountCreateContextWrapper(pkiSecretBackendConfigIssuersCreateUpdate, provider.VaultVersion111), | ||
UpdateContext: pkiSecretBackendConfigIssuersCreateUpdate, | ||
DeleteContext: pkiSecretBackendConfigIssuersDelete, | ||
ReadContext: provider.ReadContextWrapper(pkiSecretBackendConfigIssuersRead), | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
consts.FieldBackend: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "Full path where PKI backend is mounted.", | ||
}, | ||
consts.FieldDefault: { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: "Specifies the default issuer by ID.", | ||
}, | ||
fieldDefaultFollowsLatestIssuer: { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Computed: true, | ||
Description: "Specifies whether a root creation or an issuer " + | ||
"import operation updates the default issuer to the newly added issuer.", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func pkiSecretBackendConfigIssuersCreateUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client, e := provider.GetClient(d, meta) | ||
if e != nil { | ||
return diag.FromErr(e) | ||
} | ||
|
||
backend := d.Get(consts.FieldBackend).(string) | ||
|
||
path := fmt.Sprintf("%s/config/issuers", backend) | ||
|
||
fields := []string{ | ||
consts.FieldDefault, | ||
fieldDefaultFollowsLatestIssuer, | ||
} | ||
|
||
data := map[string]interface{}{} | ||
for _, k := range fields { | ||
data[k] = d.Get(k) | ||
} | ||
|
||
_, err := client.Logical().WriteWithContext(ctx, path, data) | ||
if err != nil { | ||
return diag.Errorf("error writing data to %q, err=%s", path, err) | ||
} | ||
|
||
d.SetId(path) | ||
|
||
return pkiSecretBackendConfigIssuersRead(ctx, d, meta) | ||
} | ||
|
||
func pkiSecretBackendConfigIssuersRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client, e := provider.GetClient(d, meta) | ||
if e != nil { | ||
return diag.FromErr(e) | ||
} | ||
|
||
path := d.Id() | ||
|
||
// get backend from full path | ||
backend, err := pkiSecretBackendFromConfigIssuersPath(path) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
// set backend and keyID | ||
if err := d.Set(consts.FieldBackend, backend); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
log.Printf("[DEBUG] Reading %s from Vault", path) | ||
resp, err := client.Logical().ReadWithContext(ctx, path) | ||
if err != nil { | ||
return diag.Errorf("error reading from Vault: %s", err) | ||
} | ||
if resp == nil { | ||
log.Printf("[WARN] default issuer data (%s) not found, removing from state", path) | ||
d.SetId("") | ||
return nil | ||
} | ||
|
||
fields := []string{ | ||
consts.FieldDefault, | ||
fieldDefaultFollowsLatestIssuer, | ||
} | ||
|
||
for _, k := range fields { | ||
if err := d.Set(k, resp.Data[k]); err != nil { | ||
return diag.Errorf("error setting state key %q for PKI Secret Config Issuers, err=%s", | ||
k, err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func pkiSecretBackendConfigIssuersDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
return nil | ||
} | ||
|
||
func pkiSecretBackendFromConfigIssuersPath(path string) (string, error) { | ||
if !pkiSecretBackendFromConfigIssuersPathRegex.MatchString(path) { | ||
return "", fmt.Errorf("no backend found") | ||
} | ||
res := pkiSecretBackendFromConfigIssuersPathRegex.FindStringSubmatch(path) | ||
if len(res) != 2 { | ||
return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res)) | ||
} | ||
return res[1], nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vault | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
|
||
"github.com/hashicorp/terraform-provider-vault/internal/consts" | ||
"github.com/hashicorp/terraform-provider-vault/internal/provider" | ||
"github.com/hashicorp/terraform-provider-vault/testutil" | ||
) | ||
|
||
func TestAccPKISecretBackendConfigIssuers_basic(t *testing.T) { | ||
vinay-gopalan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
t.Parallel() | ||
|
||
backend := acctest.RandomWithPrefix("tf-test-pki") | ||
resourceType := "vault_pki_secret_backend_config_issuers" | ||
resourceName := resourceType + ".test" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
ProviderFactories: providerFactories, | ||
PreCheck: func() { | ||
testutil.TestAccPreCheck(t) | ||
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion111) | ||
}, | ||
CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypePKI, consts.FieldBackend), | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccPKISecretBackendConfigIssuers_basic(backend, ""), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, backend), | ||
resource.TestCheckResourceAttr(resourceName, fieldDefaultFollowsLatestIssuer, "false"), | ||
resource.TestCheckResourceAttrSet(resourceName, consts.FieldDefault), | ||
), | ||
}, | ||
{ | ||
Config: testAccPKISecretBackendConfigIssuers_basic(backend, `default_follows_latest_issuer = true`), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, backend), | ||
resource.TestCheckResourceAttr(resourceName, fieldDefaultFollowsLatestIssuer, "true"), | ||
resource.TestCheckResourceAttrSet(resourceName, consts.FieldDefault), | ||
), | ||
}, | ||
testutil.GetImportTestStep(resourceName, false, nil), | ||
}, | ||
}) | ||
} | ||
|
||
func testAccPKISecretBackendConfigIssuers_basic(path, extraFields string) string { | ||
return fmt.Sprintf(` | ||
resource "vault_mount" "test" { | ||
path = "%s" | ||
type = "pki" | ||
description = "PKI secret engine mount" | ||
} | ||
|
||
resource "vault_pki_secret_backend_root_cert" "test" { | ||
backend = vault_mount.test.path | ||
type = "internal" | ||
common_name = "test" | ||
ttl = "86400" | ||
} | ||
|
||
resource "vault_pki_secret_backend_issuer" "test" { | ||
backend = vault_mount.test.path | ||
issuer_ref = vault_pki_secret_backend_root_cert.test.issuer_id | ||
} | ||
|
||
resource "vault_pki_secret_backend_config_issuers" "test" { | ||
backend = vault_mount.test.path | ||
default = vault_pki_secret_backend_issuer.test.issuer_id | ||
%s | ||
}`, path, extraFields) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
layout: "vault" | ||
page_title: "Vault: vault_pki_secret_backend_config_issuers resource" | ||
sidebar_current: "docs-vault-resource-pki-secret-backend-config-issuers" | ||
description: |- | ||
Allows setting the value of the default issuer. | ||
--- | ||
|
||
# vault\_pki\_secret\_backend\_config\_issuers | ||
|
||
Allows setting the value of the default issuer. For more information, see the | ||
[Vault documentation](https://developer.hashicorp.com/vault/api-docs/secret/pki#set-issuers-configuration) | ||
|
||
## Example Usage | ||
|
||
```hcl | ||
resource "vault_mount" "pki" { | ||
path = "pki" | ||
type = "pki" | ||
default_lease_ttl_seconds = 3600 | ||
max_lease_ttl_seconds = 86400 | ||
} | ||
|
||
resource "vault_pki_secret_backend_root_cert" "root" { | ||
backend = vault_mount.pki.path | ||
type = "internal" | ||
common_name = "test" | ||
ttl = "86400" | ||
} | ||
|
||
resource "vault_pki_secret_backend_issuer" "example" { | ||
backend = vault_pki_secret_backend_root_cert.root.backend | ||
issuer_ref = vault_pki_secret_backend_root_cert.root.issuer_id | ||
issuer_name = "example-issuer" | ||
} | ||
|
||
resource "vault_pki_secret_backend_config_issuers" "config" { | ||
backend = vault_mount.pki.path | ||
default = vault_pki_secret_backend_issuer.example.issuer_id | ||
default_follows_latest_issuer = true | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `namespace` - (Optional) The namespace to provision the resource in. | ||
The value should not contain leading or trailing forward slashes. | ||
The `namespace` is always relative to the provider's configured [namespace](/docs/providers/vault#namespace). | ||
*Available only for Vault Enterprise*. | ||
|
||
* `backend` - (Required) The path the PKI secret backend is mounted at, with no | ||
leading or trailing `/`s. | ||
|
||
* `default` - (Required) Specifies the default issuer using the issuer ID. | ||
**NOTE:** It is recommended to only set the default issuer using the ID. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Vault, one can provide both the issuer name and issuer ID to set this field. However, since the Vault response only ever returns the issuer ID for this field, there can be a drift in the TF state if we provide an issuer name but Vault returns an issuer ID. I opted to document for now that we recommend only using the ID (since that is fairly easy to do in TF), but in the future if users feel like they specifically want to be able to set this parameter by the issuer name we can always add an additional read-only schema field that will store the ID separately (thereby removing any drifts) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! thanks for the context! |
||
While Vault does allow passing in the issuer name, this can lead to possible drifts in the Terraform state. | ||
|
||
* `default_follows_latest_issuer` - (Optional) Specifies whether a root creation | ||
or an issuer import operation updates the default issuer to the newly added issuer. | ||
|
||
|
||
## Attributes Reference | ||
|
||
No additional attributes are exported by this resource. | ||
|
||
## Import | ||
|
||
PKI secret backend config issuers can be imported using the path, e.g. | ||
|
||
``` | ||
$ terraform import vault_pki_secret_backend_issuer.config pki/config/issuers | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this might be a good spot for an example to showcase that if a schema field seems very specific to only a single resource, we need not export it out of the package and needlessly bulk up our
consts
package. Instead, we can define an unexported constant within the same file. However, if this feels a bit unclean, I'm happy to move it to the constants package