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 auth0_encryption_key_manager resource to allow rekeying of encryption keys #1031

Merged
merged 3 commits into from
Oct 3, 2024
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
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": {
acwest marked this conversation as resolved.
Show resolved Hide resolved
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
Loading