Skip to content

Commit

Permalink
Add support for HMAC type to vault_transit_secret_backend_key (#2034)
Browse files Browse the repository at this point in the history
* Add support for HMAC type to vault_transit_secret_backend_key

* Add key_size as it is necessary for HMAC. Default to 0 bits as that
  is the default for all key types _except_ HMAC currently.

* Removing some `key_type` validation and updating CHANGELOG.md

* Removed ValidateFunc to rely on Vault's internal validation
* Updated CHANGELOG.md
* Slightly tweaked the description of `key_size`
* Wrapped the `data["key_size"]` allocation in a check for Vault version
* Added `SkipFunc` blocks to the `hmac` tests

* Add PreCheck and remove SkipFunc to fix Vault 1.11 acceptance testing.
  • Loading branch information
roberteckert authored Oct 3, 2023
1 parent 7f71cc3 commit 95a3230
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

FEATURES:
* Add support for setting `not_before_duration` argument on `vault_ssh_secret_backend_role`: ([#2019](https://github.com/hashicorp/terraform-provider-vault/pull/2019))
* Add support for `hmac` key type and key_size to `vault_transit_secret_backend_key`: ([#2034](https://github.com/hashicorp/terraform-provider-vault/pull/2034/))

BUGS:
* Fix duplicate timestamp and incorrect level messages: ([#2031](https://github.com/hashicorp/terraform-provider-vault/pull/2031))
Expand Down
56 changes: 42 additions & 14 deletions vault/resource_transit_secret_backend_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ func transitSecretBackendKeyResource() *schema.Resource {
"type": {
Type: schema.TypeString,
Optional: true,
Description: "Specifies the type of key to create. The currently-supported types are: aes128-gcm96, aes256-gcm96, chacha20-poly1305, ed25519, ecdsa-p256, ecdsa-p384, ecdsa-p521, rsa-2048, rsa-3072, rsa-4096",
Description: "Specifies the type of key to create. The currently-supported types are: aes128-gcm96, aes256-gcm96, chacha20-poly1305, ed25519, ecdsa-p256, ecdsa-p384, ecdsa-p521, hmac, rsa-2048, rsa-3072, rsa-4096",
ForceNew: true,
Default: "aes256-gcm96",
ValidateFunc: validation.StringInSlice([]string{"aes128-gcm96", "aes256-gcm96", "chacha20-poly1305", "ed25519", "ecdsa-p256", "ecdsa-p384", "ecdsa-p521", "rsa-2048", "rsa-3072", "rsa-4096"}, false),
ValidateFunc: validation.StringInSlice([]string{"aes128-gcm96", "aes256-gcm96", "chacha20-poly1305", "ed25519", "ecdsa-p256", "ecdsa-p384", "ecdsa-p521", "hmac", "rsa-2048", "rsa-3072", "rsa-4096"}, false),
},
"keys": {
Type: schema.TypeList,
Expand All @@ -110,6 +110,12 @@ func transitSecretBackendKeyResource() *schema.Resource {
Elem: schema.TypeString,
},
},
"key_size": {
Type: schema.TypeInt,
Optional: true,
Description: "The key size in bytes for algorithms that allow variable key sizes. Currently only applicable to HMAC; this value must be between 32 and 512.",
Default: 0,
},
"latest_version": {
Type: schema.TypeInt,
Computed: true,
Expand Down Expand Up @@ -223,10 +229,14 @@ func transitSecretBackendKeyCreate(d *schema.ResourceData, meta interface{}) err
"auto_rotate_period": autoRotatePeriod,
}

if provider.IsAPISupported(meta, provider.VaultVersion112) {
data["key_size"] = d.Get("key_size").(int)
}

log.Printf("[DEBUG] Creating encryption key %s on transit secret backend %q", name, backend)
_, err := client.Logical().Write(path, data)
if err != nil {
return fmt.Errorf("error creating encryption key %s for transit secret backend %q: %s", name, backend, err)
return fmt.Errorf("error creating encryption key %s for transit secret backend %q: %s with key size %d", name, backend, err, d.Get("key_size").(int))
}
log.Printf("[DEBUG] Setting configuration for encryption key %s on transit secret backend %q", name, backend)
_, conferr := client.Logical().Write(path+"/config", configData)
Expand Down Expand Up @@ -317,24 +327,42 @@ func transitSecretBackendKeyRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("expected min_encryption_version %q to be a number, and it isn't", secret.Data["min_encryption_version"])
}

ikeys := secret.Data["keys"].(map[string]interface{})
ikeys := secret.Data["keys"]
keys := []interface{}{}
for _, v := range ikeys {
// Data structure of "keys" differs depending on encryption key type. Sometimes it's a single integer hash,
// and other times it's a full map of values describing the key version's creation date, name, and public key.

if sv, ok := v.(map[string]interface{}); ok { // for key types of rsa-2048, rsa-3072, rsa-4096, ed25519, ecdsa-p256, ecdsa-p384 or ecdsa-p521
keys = append(keys, sv)
} else if sv, ok := v.(json.Number); ok { // for key types of aes128-gcm96, aes256-gcm96 or chacha20-poly1305
m := make(map[string]interface{})
m["id"] = sv
keys = append(keys, m)
if ikeys != nil || secret.Data["type"] != "hmac" { // hmac type does not return keys
ikeys := secret.Data["keys"].(map[string]interface{})
for _, v := range ikeys {
// Data structure of "keys" differs depending on encryption key type. Sometimes it's a single integer hash,
// and other times it's a full map of values describing the key version's creation date, name, and public key.
if sv, ok := v.(map[string]interface{}); ok { // for key types of rsa-2048, rsa-3072, rsa-4096, ed25519, ecdsa-p256, ecdsa-p384 or ecdsa-p521
keys = append(keys, sv)
} else if sv, ok := v.(json.Number); ok { // for key types of aes128-gcm96, aes256-gcm96 or chacha20-poly1305
m := make(map[string]interface{})
m["id"] = sv
keys = append(keys, m)
}
}

}

if err := d.Set("keys", keys); err != nil {
return err
}

if provider.IsAPISupported(meta, provider.VaultVersion112) {
// On read, key_size will be nil if the encryption key type is not HMAC. Choosing not to set it in those cases.
keySize := secret.Data["key_size"]
if keySize != nil || secret.Data["type"] == "hmac" {
keySize, err := secret.Data["key_size"].(json.Number).Int64()
if err != nil {
return fmt.Errorf("expected key_size %q to be a number, and it isn't", secret.Data["key_size"])
}
if err := d.Set("key_size", keySize); err != nil {
return err
}
}
}

if err := d.Set("backend", backend); err != nil {
return err
}
Expand Down
100 changes: 99 additions & 1 deletion vault/resource_transit_secret_backend_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestTransitSecretBackendKey_basic(t *testing.T) {
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"auto_rotate_interval"},
ImportStateVerifyIgnore: []string{"auto_rotate_interval", "key_size"},
},
{
Config: testTransitSecretBackendKeyConfig_invalidUpdates(name, backend),
Expand Down Expand Up @@ -136,6 +136,70 @@ func TestTransitSecretBackendKey_rsa4096(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "supports_encryption", "true"),
resource.TestCheckResourceAttr(resourceName, "supports_signing", "true"),
resource.TestCheckResourceAttr(resourceName, "min_decryption_version", "1"),
resource.TestCheckResourceAttr(resourceName, "min_encryption_version", "0"),
resource.TestCheckResourceAttr(resourceName, "deletion_allowed", "true"),
resource.TestCheckResourceAttr(resourceName, "exportable", "false"),
resource.TestCheckResourceAttr(resourceName, "allow_plaintext_backup", "false"),
resource.TestCheckResourceAttr(resourceName, "auto_rotate_period", "0"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"key_size"},
},
},
})
}

func TestTransitSecretBackendKey_hmac(t *testing.T) {
backend := acctest.RandomWithPrefix("transit")
name := acctest.RandomWithPrefix("key")
resourceName := "vault_transit_secret_backend_key.test"
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() {
testutil.TestAccPreCheck(t)
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion112)
},
CheckDestroy: testTransitSecretBackendKeyCheckDestroy,
Steps: []resource.TestStep{
{
Config: testTransitSecretBackendKeyConfig_hmac(name, backend),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "backend", backend),
resource.TestCheckResourceAttr(resourceName, "name", name),
resource.TestCheckResourceAttr(resourceName, "deletion_allowed", "true"),
resource.TestCheckResourceAttr(resourceName, "convergent_encryption", "false"),
resource.TestCheckResourceAttr(resourceName, "derived", "false"),
resource.TestCheckResourceAttr(resourceName, "exportable", "false"),
resource.TestCheckResourceAttr(resourceName, "key_size", "32"),
resource.TestCheckResourceAttr(resourceName, "latest_version", "1"),
resource.TestCheckResourceAttr(resourceName, "type", "hmac"),
resource.TestCheckResourceAttr(resourceName, "supports_decryption", "false"),
resource.TestCheckResourceAttr(resourceName, "supports_derivation", "false"),
resource.TestCheckResourceAttr(resourceName, "supports_encryption", "false"),
resource.TestCheckResourceAttr(resourceName, "supports_signing", "false"),
resource.TestCheckResourceAttr(resourceName, "auto_rotate_period", "0"),
),
},
{
Config: testTransitSecretBackendKeyConfig_hmacupdated(name, backend),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "backend", backend),
resource.TestCheckResourceAttr(resourceName, "name", name),
resource.TestCheckResourceAttr(resourceName, "deletion_allowed", "true"),
resource.TestCheckResourceAttr(resourceName, "convergent_encryption", "false"),
resource.TestCheckResourceAttr(resourceName, "derived", "false"),
resource.TestCheckResourceAttr(resourceName, "key_size", "32"),
resource.TestCheckResourceAttr(resourceName, "latest_version", "1"),
resource.TestCheckResourceAttr(resourceName, "type", "hmac"),
resource.TestCheckResourceAttr(resourceName, "supports_decryption", "false"),
resource.TestCheckResourceAttr(resourceName, "supports_derivation", "false"),
resource.TestCheckResourceAttr(resourceName, "supports_encryption", "false"),
resource.TestCheckResourceAttr(resourceName, "supports_signing", "false"),
resource.TestCheckResourceAttr(resourceName, "min_decryption_version", "1"),
resource.TestCheckResourceAttr(resourceName, "min_encryption_version", "1"),
resource.TestCheckResourceAttr(resourceName, "deletion_allowed", "true"),
resource.TestCheckResourceAttr(resourceName, "exportable", "true"),
Expand Down Expand Up @@ -184,6 +248,23 @@ resource "vault_transit_secret_backend_key" "test" {
`, path, name)
}

func testTransitSecretBackendKeyConfig_hmac(name, path string) string {
return fmt.Sprintf(`
resource "vault_mount" "transit" {
path = "%s"
type = "transit"
}
resource "vault_transit_secret_backend_key" "test" {
backend = vault_mount.transit.path
name = "%s"
deletion_allowed = true
type = "hmac"
key_size = 32
}
`, path, name)
}

func testTransitSecretBackendKeyConfig_rsa4096updated(name, path string) string {
return fmt.Sprintf(`
resource "vault_mount" "transit" {
Expand All @@ -196,6 +277,23 @@ resource "vault_transit_secret_backend_key" "test" {
name = "%s"
deletion_allowed = true
type = "rsa-4096"
}
`, path, name)
}

func testTransitSecretBackendKeyConfig_hmacupdated(name, path string) string {
return fmt.Sprintf(`
resource "vault_mount" "transit" {
path = "%s"
type = "transit"
}
resource "vault_transit_secret_backend_key" "test" {
backend = vault_mount.transit.path
name = "%s"
deletion_allowed = true
type = "hmac"
key_size = 32
min_decryption_version = 1
min_encryption_version = 1
exportable = true
Expand Down
8 changes: 5 additions & 3 deletions website/docs/r/transit_secret_backend_key.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The following arguments are supported:

* `name` - (Required) The name to identify this key within the backend. Must be unique within the backend.

* `type` - (Optional) Specifies the type of key to create. The currently-supported types are: `aes128-gcm96`, `aes256-gcm96` (default), `chacha20-poly1305`, `ed25519`, `ecdsa-p256`, `ecdsa-p384`, `ecdsa-p521`, `rsa-2048`, `rsa-3072` and `rsa-4096`.
* `type` - (Optional) Specifies the type of key to create. The currently-supported types are: `aes128-gcm96`, `aes256-gcm96` (default), `chacha20-poly1305`, `ed25519`, `ecdsa-p256`, `ecdsa-p384`, `ecdsa-p521`, `hmac`, `rsa-2048`, `rsa-3072` and `rsa-4096`.
* Refer to the Vault documentation on transit key types for more information: [Key Types](https://www.vaultproject.io/docs/secrets/transit#key-types)

* `deletion_allowed` - (Optional) Specifies if the keyring is allowed to be deleted. Must be set to 'true' before terraform will be able to destroy keys.
Expand All @@ -53,14 +53,16 @@ The following arguments are supported:

* `allow_plaintext_backup` - (Optional) Enables taking backup of entire keyring in the plaintext format. Once set, this cannot be disabled.
* Refer to Vault API documentation on key backups for more information: [Backup Key](https://www.vaultproject.io/api-docs/secret/transit#backup-key)

* `min_decryption_version` - (Optional) Minimum key version to use for decryption.

* `min_encryption_version` - (Optional) Minimum key version to use for encryption

* `auto_rotate_period` - (Optional) Amount of seconds the key should live before being automatically rotated.
A value of 0 disables automatic rotation for the key.

* `key_size` - (Optional) The key size in bytes for algorithms that allow variable key sizes. Currently only applicable to HMAC, where it must be between 32 and 512 bytes.

## Attributes Reference

* `keys` - List of key versions in the keyring. This attribute is zero-indexed and will contain a map of values depending on the `type` of the encryption key.
Expand All @@ -69,7 +71,7 @@ The following arguments are supported:
* `name` - Name of keychain
* `creation_time` - ISO 8601 format timestamp indicating when the key version was created
* `public_key` - This is the base64-encoded public key for use outside of Vault.

* `latest_version` - Latest key version available. This value is 1-indexed, so if `latest_version` is `1`, then the key's information can be referenced from `keys` by selecting element `0`

* `min_available_version` - Minimum key version available for use. If keys have been archived by increasing `min_decryption_version`, this attribute will reflect that change.
Expand Down

0 comments on commit 95a3230

Please sign in to comment.