Skip to content

Commit

Permalink
Add auth0_encryption_keys resource to allow rekeying of encryption ke…
Browse files Browse the repository at this point in the history
…ys (auth0#1031)

* Add auth0_encryption_key resource to allow rekeying of tenant encryption keys

* Slight renaming, and use key_rotation_id instead of rekey attribute

* Rename auth0_encryption_key to auth0_encryption_key_manager

Signed-off-by: BryanLewis-AtOkta <[email protected]>
  • Loading branch information
bryanlewis-okta committed Oct 24, 2024
1 parent f188374 commit 8886fec
Show file tree
Hide file tree
Showing 8 changed files with 851 additions and 3 deletions.
59 changes: 59 additions & 0 deletions docs/resources/encryption_key_manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
page_title: "Resource: auth0_encryption_key_manager"
description: |-
Resource to allow the rekeying of your tenant master key.
---

# Resource: auth0_encryption_key_manager

Resource to allow the rekeying of your tenant master key.

## Example Usage

```terraform
resource "auth0_encryption_key_manager" "my_encryption_key_manager_initial" {
key_rotation_id = "da9f2f3b-1c7e-4245-8982-9a25da8407c4"
}
resource "auth0_encryption_key_manager" "my_encryption_key_manager_rekey" {
key_rotation_id = "68feba2c-7768-40f3-9d71-4b91e0233abf"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `key_rotation_id` (String) If this value is changed, the encryption keys will be rotated. A UUID is recommended for the `key_rotation_id`.

### Read-Only

- `encryption_keys` (List of Object) All encryption keys. (see [below for nested schema](#nestedatt--encryption_keys))
- `id` (String) The ID of this resource.

<a id="nestedatt--encryption_keys"></a>
### Nested Schema for `encryption_keys`

Read-Only:

- `created_at` (String)
- `key_id` (String)
- `parent_key_id` (String)
- `state` (String)
- `type` (String)
- `updated_at` (String)

## Import

Import is supported using the following syntax:

```shell
# As this is not a resource identifiable by an ID within the Auth0 Management API,
# auth0_encryption_key_manager can be imported using a random string.
#
# We recommend [Version 4 UUID](https://www.uuidgenerator.net/version4)
#
# Example:
terraform import auth0_encryption_key_manager.my_key_manager "6f0519ad-ea35-44a3-9b0e-ac9c631612c2"
```
7 changes: 7 additions & 0 deletions examples/resources/auth0_encryption_key_manager/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# As this is not a resource identifiable by an ID within the Auth0 Management API,
# auth0_encryption_key_manager can be imported using a random string.
#
# We recommend [Version 4 UUID](https://www.uuidgenerator.net/version4)
#
# Example:
terraform import auth0_encryption_key_manager.my_key_manager "6f0519ad-ea35-44a3-9b0e-ac9c631612c2"
8 changes: 8 additions & 0 deletions examples/resources/auth0_encryption_key_manager/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "auth0_encryption_key_manager" "my_encryption_key_manager_initial" {
key_rotation_id = "da9f2f3b-1c7e-4245-8982-9a25da8407c4"
}

resource "auth0_encryption_key_manager" "my_encryption_key_manager_rekey" {
key_rotation_id = "68feba2c-7768-40f3-9d71-4b91e0233abf"
}

22 changes: 22 additions & 0 deletions internal/auth0/encryptionkeymanager/flatten.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package encryptionkeymanager

import (
"github.com/auth0/go-auth0/management"
)

func flattenEncryptionKeys(keys []*management.EncryptionKey) []interface{} {
var result []interface{}
const timeRFC3339WithMilliseconds = "2006-01-02T15:04:05.000Z07:00"

for _, key := range keys {
result = append(result, map[string]interface{}{
"key_id": key.GetKID(),
"parent_key_id": key.GetParentKID(),
"type": key.GetType(),
"state": key.GetState(),
"created_at": key.GetCreatedAt().Format(timeRFC3339WithMilliseconds),
"updated_at": key.GetUpdatedAt().Format(timeRFC3339WithMilliseconds),
})
}
return result
}
109 changes: 109 additions & 0 deletions internal/auth0/encryptionkeymanager/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package encryptionkeymanager

import (
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/auth0/terraform-provider-auth0/internal/config"
)

// NewEncryptionKeyManagerResource will return a new auth0_encryption_key_manager resource.
func NewEncryptionKeyManagerResource() *schema.Resource {
return &schema.Resource{
CreateContext: createEncryptionKeyManager,
UpdateContext: updateEncryptionKeyManager,
ReadContext: readEncryptionKeyManager,
DeleteContext: deleteEncryptionKeyManager,
Description: "Resource to allow the rekeying of your tenant master key.",
Schema: map[string]*schema.Schema{
"key_rotation_id": {
Type: schema.TypeString,
Optional: true,
Description: "If this value is changed, the encryption keys will be rotated. A UUID is recommended for the `key_rotation_id`.",
},
"encryption_keys": {
Type: schema.TypeList,
Computed: true,
Description: "All encryption keys.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key_id": {
Type: schema.TypeString,
Computed: true,
Description: "The key ID of the encryption key.",
},
"type": {
Type: schema.TypeString,
Computed: true,
Description: "The type of the encryption key. One of " +
"`customer-provided-root-key`, `environment-root-key`, " +
"or `tenant-master-key`.",
},
"state": {
Type: schema.TypeString,
Computed: true,
Description: "The state of the encryption key. One of " +
"`pre-activation`, `active`, `deactivated`, or `destroyed`.",
},
"parent_key_id": {
Type: schema.TypeString,
Computed: true,
Description: "The key ID of the parent wrapping key.",
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "The ISO 8601 formatted date the encryption key was created.",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "The ISO 8601 formatted date the encryption key was updated.",
},
},
},
},
},
}
}

func createEncryptionKeyManager(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
data.SetId(id.UniqueId())

return updateEncryptionKeyManager(ctx, data, meta)
}

func updateEncryptionKeyManager(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()

if !data.IsNewResource() && data.HasChange("key_rotation_id") {
keyRotationID := data.GetRawConfig().GetAttr("key_rotation_id")
if !keyRotationID.IsNull() && len(keyRotationID.AsString()) > 0 {
if err := api.EncryptionKey.Rekey(ctx); err != nil {
return diag.FromErr(err)
}
}
}

return readEncryptionKeyManager(ctx, data, meta)
}

func readEncryptionKeyManager(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()

encryptionKeys, err := api.EncryptionKey.List(ctx)
if err != nil {
return diag.FromErr(err)
}

data.SetId(id.UniqueId())

return diag.FromErr(data.Set("encryption_keys", flattenEncryptionKeys(encryptionKeys.Keys)))
}

func deleteEncryptionKeyManager(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
return nil
}
183 changes: 183 additions & 0 deletions internal/auth0/encryptionkeymanager/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package encryptionkeymanager_test

import (
"fmt"
"regexp"
"strconv"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stretchr/testify/assert"

"github.com/auth0/terraform-provider-auth0/internal/acctest"
)

const testAccEncryptionKeyManagerCreate = `
resource "auth0_encryption_key_manager" "my_key_manager" { }
`

const testAccEncryptionKeyManagerFirstRotation = `
resource "auth0_encryption_key_manager" "my_key_manager" {
key_rotation_id = "initial_value"
}
`

const testAccEncryptionKeyManagerSecondRotation = `
resource "auth0_encryption_key_manager" "my_key_manager" {
key_rotation_id = "changed_value"
}
`

const testAccEncryptionKeyManagerUnsetRotation = `
resource "auth0_encryption_key_manager" "my_key_manager" {
}
`

func TestAccEncryptionKeyManager(t *testing.T) {
initialKey := make(map[string]string)
firstRotationKey := make(map[string]string)
secondRotationKey := make(map[string]string)
unsetRotationKey := make(map[string]string)

acctest.Test(t, resource.TestCase{
Steps: []resource.TestStep{
{
Config: testAccEncryptionKeyManagerCreate,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("auth0_encryption_key_manager.my_key_manager", "encryption_keys.#", regexp.MustCompile("^[1-9][0-9]*")),
extractActiveKey("auth0_encryption_key_manager.my_key_manager", "encryption_keys", "tenant-master-key", &initialKey),
func(_ *terraform.State) error {
keyID, ok := initialKey["key_id"]
assert.True(t, ok && len(keyID) > 0, "key_id should exist")
parentKeyID, ok := initialKey["parent_key_id"]
assert.True(t, ok && len(parentKeyID) > 0, "parent_key_id should exist")
assert.Equal(t, initialKey["type"], "tenant-master-key")
assert.Equal(t, initialKey["state"], "active")
createdAt, ok := initialKey["created_at"]
assert.True(t, ok && len(createdAt) > 0, "created_at should exist")
updatedAt, ok := initialKey["updated_at"]
assert.True(t, ok && len(updatedAt) > 0, "updated_at should exist")

return nil
},
),
},
{
Config: testAccEncryptionKeyManagerFirstRotation,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("auth0_encryption_key_manager.my_key_manager", "encryption_keys.#", regexp.MustCompile("^[1-9][0-9]*")),
extractActiveKey("auth0_encryption_key_manager.my_key_manager", "encryption_keys", "tenant-master-key", &firstRotationKey),
func(_ *terraform.State) error {
keyID, ok := firstRotationKey["key_id"]
assert.True(t, ok && len(keyID) > 0, "key_id should exist")
assert.NotEqual(t, firstRotationKey["key_id"], initialKey["key_id"])
parentKeyID, ok := firstRotationKey["parent_key_id"]
assert.True(t, ok && len(parentKeyID) > 0, "parent_key_id should exist")
assert.Equal(t, firstRotationKey["type"], "tenant-master-key")
assert.Equal(t, firstRotationKey["state"], "active")
createdAt, ok := firstRotationKey["created_at"]
assert.True(t, ok && len(createdAt) > 0, "created_at should exist")
updatedAt, ok := firstRotationKey["updated_at"]
assert.True(t, ok && len(updatedAt) > 0, "updated_at should exist")

return nil
},
),
},
{
Config: testAccEncryptionKeyManagerSecondRotation,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("auth0_encryption_key_manager.my_key_manager", "encryption_keys.#", regexp.MustCompile("^[1-9][0-9]*")),
extractActiveKey("auth0_encryption_key_manager.my_key_manager", "encryption_keys", "tenant-master-key", &secondRotationKey),
func(_ *terraform.State) error {
keyID, ok := secondRotationKey["key_id"]
assert.True(t, ok && len(keyID) > 0, "key_id should exist")
assert.NotEqual(t, secondRotationKey["key_id"], firstRotationKey["key_id"])
parentKeyID, ok := secondRotationKey["parent_key_id"]
assert.True(t, ok && len(parentKeyID) > 0, "parent_key_id should exist")
assert.Equal(t, secondRotationKey["type"], "tenant-master-key")
assert.Equal(t, secondRotationKey["state"], "active")
createdAt, ok := secondRotationKey["created_at"]
assert.True(t, ok && len(createdAt) > 0, "created_at should exist")
updatedAt, ok := secondRotationKey["updated_at"]
assert.True(t, ok && len(updatedAt) > 0, "updated_at should exist")

return nil
},
),
},
{
Config: testAccEncryptionKeyManagerUnsetRotation,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("auth0_encryption_key_manager.my_key_manager", "encryption_keys.#", regexp.MustCompile("^[1-9][0-9]*")),
extractActiveKey("auth0_encryption_key_manager.my_key_manager", "encryption_keys", "tenant-master-key", &unsetRotationKey),
func(_ *terraform.State) error {
keyID, ok := unsetRotationKey["key_id"]
assert.True(t, ok && len(keyID) > 0, "key_id should exist")
assert.Equal(t, unsetRotationKey["key_id"], secondRotationKey["key_id"])
parentKeyID, ok := unsetRotationKey["parent_key_id"]
assert.True(t, ok && len(parentKeyID) > 0, "parent_key_id should exist")
assert.Equal(t, unsetRotationKey["type"], "tenant-master-key")
assert.Equal(t, unsetRotationKey["state"], "active")
createdAt, ok := unsetRotationKey["created_at"]
assert.True(t, ok && len(createdAt) > 0, "created_at should exist")
updatedAt, ok := unsetRotationKey["updated_at"]
assert.True(t, ok && len(updatedAt) > 0, "updated_at should exist")

return nil
},
),
},
},
})
}

func extractActiveKey(resource, attribute, keyType string, keyMapPtr *map[string]string) resource.TestCheckFunc {
return func(state *terraform.State) error {
clear(*keyMapPtr)

tfResource, ok := state.RootModule().Resources[resource]
if !ok {
return fmt.Errorf("extractActiveKey: failed to find resource with name: %q", resource)
}
countValue, ok := tfResource.Primary.Attributes[fmt.Sprintf("%s.#", attribute)]
if !ok {
return fmt.Errorf("extractActiveKey: failed to find attribute with name: %q", attribute)
}
count, err := strconv.Atoi(countValue)
if err != nil {
return err
}
fmt.Printf("DEBUG: CRAIG: extract count: %d\n", count)
for i := range count {
stateValue, ok := tfResource.Primary.Attributes[keyName(attribute, i, "state")]
if !ok {
return fmt.Errorf("extractActiveKey: failed to find state for attribute with name: %q", attribute)
}
if stateValue != "active" {
continue
}
typeValue, ok := tfResource.Primary.Attributes[keyName(attribute, i, "type")]
if !ok {
return fmt.Errorf("extractActiveKey: failed to find type for attribute with name: %q", attribute)
}
if typeValue != keyType {
continue
}
for key, value := range tfResource.Primary.Attributes {
if strings.HasPrefix(key, keyName(attribute, i, "")) {
foundKey, _ := strings.CutPrefix(key, keyName(attribute, i, ""))
(*keyMapPtr)[foundKey] = value
}
}
return nil
}
return fmt.Errorf("extractActiveKey: active key of type %q not found", keyType)
}
}

func keyName(attribute string, index int, key string) string {
return fmt.Sprintf("%s.%d.%s", attribute, index, key)
}
Loading

0 comments on commit 8886fec

Please sign in to comment.