-
Notifications
You must be signed in to change notification settings - Fork 548
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for CMPv2 configuration (#2330)
* add resource and data source for CMPv2 config * fix test * add doc links and changelog * Update website/docs/d/pki_secret_backend_config_cmpv2.html.md Co-authored-by: Steven Clark <[email protected]> * Update website/docs/d/pki_secret_backend_config_cmpv2.html.md Co-authored-by: Steven Clark <[email protected]> * Update website/docs/d/pki_secret_backend_config_cmpv2.html.md Co-authored-by: Steven Clark <[email protected]> * Update website/docs/d/pki_secret_backend_config_cmpv2.html.md Co-authored-by: Steven Clark <[email protected]> * add docs * fix changelog --------- Co-authored-by: Steven Clark <[email protected]>
- Loading branch information
1 parent
fabb0ac
commit 4b62925
Showing
10 changed files
with
715 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vault | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"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" | ||
"github.com/hashicorp/vault/api" | ||
"strings" | ||
) | ||
|
||
func pkiSecretBackendConfigCMPV2DataSource() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: "Reads Vault PKI CMPv2 configuration", | ||
ReadContext: provider.ReadContextWrapper(readPKISecretBackendConfigCMPV2), | ||
Schema: map[string]*schema.Schema{ | ||
consts.FieldBackend: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
Description: "Path where PKI engine is mounted", | ||
}, | ||
consts.FieldEnabled: { | ||
Type: schema.TypeBool, | ||
Computed: true, | ||
Description: "Specifies whether CMPv2 is enabled", | ||
}, | ||
consts.FieldDefaultPathPolicy: { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Can be sign-verbatim or a role given by role:<role_name>", | ||
}, | ||
consts.FieldAuthenticators: { | ||
Type: schema.TypeList, | ||
Computed: true, | ||
Description: "Lists the mount accessors CMPv2 should delegate authentication requests towards", | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"cert": { | ||
Type: schema.TypeMap, | ||
Optional: true, | ||
Description: "The accessor and cert_role properties for cert auth backends", | ||
}, | ||
}, | ||
}, | ||
}, | ||
consts.FieldEnableSentinelParsing: { | ||
Type: schema.TypeBool, | ||
Computed: true, | ||
Description: "If set, parse out fields from the provided CSR making them available for Sentinel policies", | ||
}, | ||
consts.FieldAuditFields: { | ||
Type: schema.TypeList, | ||
Computed: true, | ||
Description: "Fields parsed from the CSR that appear in the audit and can be used by sentinel policies", | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
}, | ||
}, | ||
consts.FieldLastUpdated: { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "A read-only timestamp representing the last time the configuration was updated", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func readPKISecretBackendConfigCMPV2(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
if err := verifyPkiEstFeatureSupported(meta); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
client, err := provider.GetClient(d, meta) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("failed getting client: %w", err)) | ||
} | ||
|
||
backend := d.Get(consts.FieldBackend).(string) | ||
path := pkiSecretBackendConfigCMPV2Path(backend) | ||
|
||
if err := readCMPV2Config(ctx, d, client, path); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readCMPV2Config(ctx context.Context, d *schema.ResourceData, client *api.Client, path string) error { | ||
resp, err := client.Logical().ReadWithContext(ctx, path) | ||
if err != nil { | ||
return fmt.Errorf("error reading from Vault: %w", err) | ||
} | ||
if resp == nil { | ||
return fmt.Errorf("got nil response from Vault from path: %q", path) | ||
} | ||
|
||
d.SetId(path) | ||
|
||
keyComputedFields := []string{ | ||
consts.FieldEnabled, | ||
consts.FieldDefaultPathPolicy, | ||
consts.FieldEnableSentinelParsing, | ||
consts.FieldAuditFields, | ||
consts.FieldLastUpdated, | ||
} | ||
|
||
for _, k := range keyComputedFields { | ||
if fieldVal, ok := resp.Data[k]; ok { | ||
if err := d.Set(k, fieldVal); err != nil { | ||
return fmt.Errorf("failed setting field [%s] with val [%s]: %w", k, fieldVal, err) | ||
} | ||
} | ||
} | ||
|
||
if authenticators, authOk := resp.Data[consts.FieldAuthenticators]; authOk { | ||
if err := d.Set(consts.FieldAuthenticators, []interface{}{authenticators}); err != nil { | ||
return fmt.Errorf("failed setting field [%s] with val [%s]: %w", consts.FieldAuthenticators, authenticators, err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// verifyPkiCMPV2FeatureSupported verifies that we are talking to a Vault enterprise edition | ||
// and its version 1.18.0 or higher, returns nil if the above is met, otherwise an error | ||
func verifyPkiCMPV2FeatureSupported(meta interface{}) error { | ||
currentVersion := meta.(*provider.ProviderMeta).GetVaultVersion() | ||
|
||
minVersion := provider.VaultVersion118 | ||
if !provider.IsAPISupported(meta, minVersion) { | ||
return fmt.Errorf("feature not enabled on current Vault version. min version required=%s; "+ | ||
"current vault version=%s", minVersion, currentVersion) | ||
} | ||
|
||
if !provider.IsEnterpriseSupported(meta) { | ||
return errors.New("feature requires Vault Enterprise") | ||
} | ||
return nil | ||
} | ||
|
||
func pkiSecretBackendConfigCMPV2Path(backend string) string { | ||
return strings.Trim(backend, "/") + "/config/cmp" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// 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 TestAccDataSourcePKISecretConfigCMPV2(t *testing.T) { | ||
backend := acctest.RandomWithPrefix("tf-test-pki-backend") | ||
dataName := "data.vault_pki_secret_backend_config_cmpv2.test" | ||
resource.Test(t, resource.TestCase{ | ||
ProviderFactories: providerFactories, | ||
PreCheck: func() { | ||
testutil.TestAccPreCheck(t) | ||
testutil.TestEntPreCheck(t) | ||
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion118) | ||
}, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testPKISecretEmptyCMPV2ConfigDataSource(backend), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(dataName, consts.FieldBackend, backend), | ||
resource.TestCheckResourceAttrSet(dataName, consts.FieldEnabled), | ||
resource.TestCheckResourceAttrSet(dataName, consts.FieldEnableSentinelParsing), | ||
resource.TestCheckResourceAttrSet(dataName, consts.FieldLastUpdated), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testPKISecretEmptyCMPV2ConfigDataSource(path string) string { | ||
return fmt.Sprintf(` | ||
resource "vault_mount" "test" { | ||
path = "%s" | ||
type = "pki" | ||
description = "PKI secret engine mount" | ||
} | ||
data "vault_pki_secret_backend_config_cmpv2" "test" { | ||
backend = vault_mount.test.path | ||
}`, path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vault | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"strings" | ||
|
||
"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" | ||
) | ||
|
||
func pkiSecretBackendConfigCMPV2Resource() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: "Manages Vault PKI CMPv2 configuration", | ||
CreateContext: provider.MountCreateContextWrapper(pkiSecretBackendConfigCMPV2Write, provider.VaultVersion118), | ||
UpdateContext: pkiSecretBackendConfigCMPV2Write, | ||
ReadContext: pkiSecretBackendConfigCMPV2Read, | ||
DeleteContext: pkiSecretBackendConfigCMPV2Delete, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
consts.FieldBackend: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "The PKI secret backend the resource belongs to", | ||
ForceNew: true, | ||
}, | ||
consts.FieldEnabled: { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Description: "Specifies whether CMPv2 is enabled", | ||
}, | ||
consts.FieldDefaultPathPolicy: { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: "Can be sign-verbatim or a role given by role:<role_name>", | ||
}, | ||
consts.FieldAuthenticators: { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Computed: true, | ||
Description: "Lists the mount accessors CMPv2 should delegate authentication requests towards", | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"cert": { | ||
Type: schema.TypeMap, | ||
Optional: true, | ||
}, | ||
}, | ||
}, | ||
MaxItems: 1, | ||
}, | ||
consts.FieldEnableSentinelParsing: { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Description: "If set, parse out fields from the provided CSR making them available for Sentinel policies", | ||
}, | ||
consts.FieldAuditFields: { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Computed: true, | ||
Description: "Fields parsed from the CSR that appear in the audit and can be used by sentinel policies", | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
}, | ||
}, | ||
consts.FieldLastUpdated: { | ||
Type: schema.TypeString, | ||
Computed: true, // read-only property | ||
Description: "A read-only timestamp representing the last time the configuration was updated", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func pkiSecretBackendConfigCMPV2Write(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
if err := verifyPkiCMPV2FeatureSupported(meta); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
client, e := provider.GetClient(d, meta) | ||
if e != nil { | ||
return diag.FromErr(e) | ||
} | ||
|
||
backend := d.Get(consts.FieldBackend).(string) | ||
path := pkiSecretBackendConfigCMPV2Path(backend) | ||
|
||
fieldsToSet := []string{ | ||
consts.FieldEnabled, | ||
consts.FieldDefaultPathPolicy, | ||
consts.FieldEnableSentinelParsing, | ||
consts.FieldAuditFields, | ||
} | ||
|
||
data := map[string]interface{}{} | ||
for _, field := range fieldsToSet { | ||
if val, ok := d.GetOk(field); ok { | ||
data[field] = val | ||
} | ||
} | ||
|
||
if authenticatorsRaw, ok := d.GetOk(consts.FieldAuthenticators); ok { | ||
authenticators := authenticatorsRaw.([]interface{}) | ||
var authenticator interface{} | ||
if len(authenticators) > 0 { | ||
authenticator = authenticators[0] | ||
} | ||
|
||
data[consts.FieldAuthenticators] = authenticator | ||
} | ||
|
||
log.Printf("[DEBUG] Updating CMPv2 config on PKI secret backend %q:\n%v", backend, data) | ||
_, err := client.Logical().WriteWithContext(ctx, path, data) | ||
if err != nil { | ||
return diag.Errorf("error updating CMPv2 config for PKI secret backend %q: %s", backend, err) | ||
} | ||
log.Printf("[DEBUG] Updated CMPv2 config on PKI secret backend %q", backend) | ||
|
||
d.SetId(path) | ||
|
||
return pkiSecretBackendConfigCMPV2Read(ctx, d, meta) | ||
} | ||
|
||
func pkiSecretBackendConfigCMPV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
id := d.Id() | ||
if id == "" { | ||
return diag.FromErr(fmt.Errorf("no path set for import, id=%q", id)) | ||
} | ||
|
||
backend := strings.TrimSuffix(id, "/config/cmp") | ||
if err := d.Set("backend", backend); err != nil { | ||
return diag.FromErr(fmt.Errorf("failed setting field [%s] with value [%v]: %w", "backend", backend, err)) | ||
} | ||
|
||
if err := verifyPkiCMPV2FeatureSupported(meta); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
client, err := provider.GetClient(d, meta) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("failed getting client: %w", err)) | ||
} | ||
|
||
if err := readCMPV2Config(ctx, d, client, id); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
return nil | ||
} | ||
|
||
func pkiSecretBackendConfigCMPV2Delete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
// There isn't any delete API for the CMPv2 config. | ||
return nil | ||
} |
Oops, something went wrong.