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 support to set default issuers configuration for PKI Secrets Engine #1937

Merged
merged 6 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ const (
FieldRenewStatements = "renew_statements"
FieldCredentialType = "credential_type"
FieldFilename = "filename"

FieldDefault = "default"
/*
common environment variables
*/
Expand Down
4 changes: 4 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,10 @@ var (
Resource: UpdateSchemaResource(pkiSecretBackendIssuerResource()),
PathInventory: []string{"/pki/issuer/{issuer_ref}"},
},
"vault_pki_secret_backend_config_issuers": {
Resource: UpdateSchemaResource(pkiSecretBackendConfigIssuers()),
PathInventory: []string{"/pki/config/issuers"},
},
"vault_quota_lease_count": {
Resource: UpdateSchemaResource(quotaLeaseCountResource()),
PathInventory: []string{"/sys/quotas/lease-count/{name}"},
Expand Down
145 changes: 145 additions & 0 deletions vault/resource_pki_secret_backend_config_issuers.go
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"
Copy link
Contributor Author

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

)

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
}
79 changes: 79 additions & 0 deletions vault/resource_pki_secret_backend_config_issuers_test.go
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)
}
74 changes: 74 additions & 0 deletions website/docs/r/pki_secret_backend_config_issuers.html.md
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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)

Copy link
Contributor

Choose a reason for hiding this comment

The 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
```