diff --git a/docs/resources/key_manager_key.md b/docs/resources/key_manager_key.md new file mode 100644 index 0000000000..d5b0c4f583 --- /dev/null +++ b/docs/resources/key_manager_key.md @@ -0,0 +1,91 @@ +--- +subcategory: "Key Manager" +page_title: "Scaleway: scaleway_key_manager_key" +--- +# Resource: scaleway_key_manager_key + +Provides a Scaleway Key Manager Key resource. +This resource allows you to create and manage cryptographic keys in Scaleway Key Manager (KMS). + +## Example Usage + +```terraform +resource "scaleway_key_manager_key" "main" { + name = "my-kms-key" + region = "fr-par" + project_id = "your-project-id" # optional, will use provider default if omitted + usage = "symmetric_encryption" + description = "Key for encrypting secrets" + tags = ["env:prod", "kms"] + unprotected = true + + rotation_policy { + rotation_period = "720h" # 30 days + } +} +``` + +## Argument Reference + +The following arguments are supported: + +- `name` (String) – The name of the key. +- `region` (String) – The region in which to create the key (e.g., `fr-par`). +- `project_id` (String, Optional) – The ID of the project the key belongs to. +- `usage` (String, **Required**) – The usage of the key. Valid values are: + - `symmetric_encryption` + - `asymmetric_encryption` + - `asymmetric_signing` +- `description` (String, Optional) – A description for the key. +- `tags` (List of String, Optional) – A list of tags to assign to the key. +- `unprotected` (Boolean, Optional) – If `true`, the key can be deleted. Defaults to `false` (protected). +- `origin` (String, Optional) – The origin of the key. Valid values are: + - `scaleway_kms` (default) + - `external` +- `rotation_policy` (Block, Optional) – Rotation policy for the key: + - `rotation_period` (String, Optional) – The period between key rotations (e.g., `"720h"` for 30 days). + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `id` – The ID of the key. +- `state` – The state of the key (e.g., `enabled`). +- `created_at` – The date and time when the key was created. +- `updated_at` – The date and time when the key was last updated. +- `rotation_count` – The number of times the key has been rotated. +- `protected` – Whether the key is protected from deletion. +- `locked` – Whether the key is locked. +- `rotated_at` – The date and time when the key was last rotated. +- `origin_read` – The origin of the key as returned by the API. +- `region_read` – The region of the key as returned by the API. +- `rotation_policy` (Block) + - `rotation_period` – The period between key rotations. + - `next_rotation_at` – The date and time of the next scheduled rotation. + +## Import + +You can import a key using its ID and region: + +```shell +terraform import scaleway_key_manager_key.main fr-par/11111111-2222-3333-4444-555555555555 +``` + +## Notes + +- **Protection**: By default, keys are protected and cannot be deleted. To allow deletion, set `unprotected = true` when creating the key. +- **Rotation Policy**: The `rotation_policy` block allows you to set automatic rotation for your key. +- **Origin**: The `origin` argument is optional and defaults to `scaleway_kms`. Use `external` if you want to import an external key (see Scaleway documentation for details). +- **Project and Region**: If not specified, `project_id` and `region` will default to the provider configuration. + +## Example: Asymmetric Key + +```terraform +resource "scaleway_key_manager_key" "asym" { + name = "asymmetric-key" + region = "fr-par" + usage = "asymmetric_signing" + description = "Key for signing documents" + unprotected = true +} +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 980d585e2b..aab96723da 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -32,6 +32,7 @@ import ( "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/ipam" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/jobs" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/k8s" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/keymanager" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/lb" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/marketplace" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/mnq" @@ -195,6 +196,7 @@ func Provider(config *Config) plugin.ProviderFunc { "scaleway_k8s_acl": k8s.ResourceACL(), "scaleway_k8s_cluster": k8s.ResourceCluster(), "scaleway_k8s_pool": k8s.ResourcePool(), + "scaleway_key_manager_key": keymanager.ResourceKeyManagerKey(), "scaleway_lb": lb.ResourceLb(), "scaleway_lb_acl": lb.ResourceACL(), "scaleway_lb_backend": lb.ResourceBackend(), diff --git a/internal/services/keymanager/helpers.go b/internal/services/keymanager/helpers.go new file mode 100644 index 0000000000..b0da6e7f3d --- /dev/null +++ b/internal/services/keymanager/helpers.go @@ -0,0 +1,118 @@ +package keymanager + +import ( + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + key_manager "github.com/scaleway/scaleway-sdk-go/api/key_manager/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/meta" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/types" +) + +func UsageToString(u *key_manager.KeyUsage) string { + if u == nil { + return "" + } + + if u.SymmetricEncryption != nil { + return "symmetric_encryption" + } + + if u.AsymmetricEncryption != nil { + return "asymmetric_encryption" + } + + if u.AsymmetricSigning != nil { + return "asymmetric_signing" + } + + return "" +} + +func newKeyManagerAPI(d *schema.ResourceData, m any) (*key_manager.API, scw.Region, error) { + api := key_manager.NewAPI(meta.ExtractScwClient(m)) + + region, err := meta.ExtractRegion(d, m) + if err != nil { + return nil, "", err + } + + return api, region, nil +} + +func NewKeyManagerAPIWithRegionAndID(m any, id string) (*key_manager.API, scw.Region, string, error) { + region, keyID, err := regional.ParseID(id) + if err != nil { + return nil, "", "", err + } + + client := key_manager.NewAPI(meta.ExtractScwClient(m)) + + return client, region, keyID, nil +} + +func ExpandKeyUsage(usage string) *key_manager.KeyUsage { + switch usage { + case "symmetric_encryption": + alg := key_manager.KeyAlgorithmSymmetricEncryptionAes256Gcm + + return &key_manager.KeyUsage{SymmetricEncryption: &alg} + case "asymmetric_encryption": + alg := key_manager.KeyAlgorithmAsymmetricEncryptionRsaOaep3072Sha256 + + return &key_manager.KeyUsage{AsymmetricEncryption: &alg} + case "asymmetric_signing": + alg := key_manager.KeyAlgorithmAsymmetricSigningEcP256Sha256 + + return &key_manager.KeyUsage{AsymmetricSigning: &alg} + default: + return nil + } +} + +func ExpandKeyRotationPolicy(v any) (*key_manager.KeyRotationPolicy, error) { + list, ok := v.([]any) + if !ok || len(list) == 0 { + return nil, nil + } + + m, ok := list[0].(map[string]any) + if !ok { + return nil, nil + } + + periodStr, ok := m["rotation_period"].(string) + if !ok || periodStr == "" { + return nil, nil + } + + period, err := time.ParseDuration(periodStr) + if err != nil { + return nil, err + } + + return &key_manager.KeyRotationPolicy{ + RotationPeriod: scw.NewDurationFromTimeDuration(period), + }, nil +} + +func FlattenKeyRotationPolicy(rp *key_manager.KeyRotationPolicy) []map[string]any { + if rp == nil { + return nil + } + + var periodStr string + + if rp.RotationPeriod != nil { + periodStr = rp.RotationPeriod.ToTimeDuration().String() + } + + return []map[string]any{ + { + "rotation_period": periodStr, + "next_rotation_at": types.FlattenTime(rp.NextRotationAt), + }, + } +} diff --git a/internal/services/keymanager/key_resource.go b/internal/services/keymanager/key_resource.go new file mode 100644 index 0000000000..355eb0d192 --- /dev/null +++ b/internal/services/keymanager/key_resource.go @@ -0,0 +1,223 @@ +package keymanager + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + key_manager "github.com/scaleway/scaleway-sdk-go/api/key_manager/v1alpha1" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/types" +) + +func ResourceKeyManagerKey() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceKeyManagerKeyCreate, + ReadContext: resourceKeyManagerKeyRead, + UpdateContext: resourceKeyManagerKeyUpdate, + DeleteContext: resourceKeyManagerKeyDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the key.", + }, + "project_id": account.ProjectIDSchema(), + "region": regional.Schema(), + "usage": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "symmetric_encryption", "asymmetric_encryption", "asymmetric_signing", + }, false), + Description: "Key usage. Keys with a usage set to 'symmetric_encryption' can encrypt and decrypt data using the AES-256-GCM key algorithm. Possible values: symmetric_encryption, asymmetric_encryption, asymmetric_signing.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Description of the key.", + }, + "tags": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of the key's tags.", + }, + "rotation_policy": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Key rotation policy.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rotation_period": {Type: schema.TypeString, Optional: true, Description: "Time interval between two key rotations. The minimum duration is 24 hours and the maximum duration is 1 year (876000 hours)."}, + "next_rotation_at": {Type: schema.TypeString, Computed: true, Description: "Timestamp indicating the next scheduled rotation."}, + }, + }, + }, + "unprotected": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "If true, the key is not protected against deletion.", + }, + "origin": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "scaleway_kms", "external", + }, false), + Description: "Origin of the key material. Possible values: scaleway_kms (Key Manager generates the key material), external (key material comes from an external source).", + }, + // Computed fields + "id": {Type: schema.TypeString, Computed: true, Description: "ID of the key."}, + "state": {Type: schema.TypeString, Computed: true, Description: "State of the key. See the Key.State enum for possible values."}, + "created_at": {Type: schema.TypeString, Computed: true, Description: "Key creation date."}, + "updated_at": {Type: schema.TypeString, Computed: true, Description: "Key last modification date."}, + "rotation_count": {Type: schema.TypeInt, Computed: true, Description: "The rotation count tracks the number of times the key has been rotated."}, + "protected": {Type: schema.TypeBool, Computed: true, Description: "Returns true if key protection is applied to the key."}, + "locked": {Type: schema.TypeBool, Computed: true, Description: "Returns true if the key is locked."}, + "rotated_at": {Type: schema.TypeString, Computed: true, Description: "Key last rotation date."}, + }, + } +} + +func resourceKeyManagerKeyCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + api, region, err := newKeyManagerAPI(d, m) + if err != nil { + return diag.FromErr(err) + } + + createReq := &key_manager.CreateKeyRequest{ + Region: region, + ProjectID: d.Get("project_id").(string), + Name: types.ExpandStringPtr(d.Get("name")), + Description: types.ExpandStringPtr(d.Get("description")), + Unprotected: d.Get("unprotected").(bool), + } + + if v, ok := d.GetOk("tags"); ok { + createReq.Tags = types.ExpandStrings(v) + } + + if v, ok := d.GetOk("rotation_policy"); ok { + rp, err := ExpandKeyRotationPolicy(v) + if err != nil { + return diag.Errorf("invalid rotation_period: %v", err) + } + + createReq.RotationPolicy = rp + } + + if v, ok := d.GetOk("origin"); ok { + createReq.Origin = key_manager.KeyOrigin(v.(string)) + } + + createReq.Usage = ExpandKeyUsage(d.Get("usage").(string)) + + key, err := api.CreateKey(createReq) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(regional.NewIDString(key.Region, key.ID)) + + return resourceKeyManagerKeyRead(ctx, d, m) +} + +func resourceKeyManagerKeyRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client, region, keyID, err := NewKeyManagerAPIWithRegionAndID(m, d.Id()) + if err != nil { + return diag.FromErr(err) + } + + key, err := client.GetKey(&key_manager.GetKeyRequest{ + Region: region, + KeyID: keyID, + }) + if err != nil { + return diag.FromErr(err) + } + + _ = d.Set("name", key.Name) + _ = d.Set("project_id", key.ProjectID) + _ = d.Set("region", key.Region.String()) + _ = d.Set("usage", UsageToString(key.Usage)) + _ = d.Set("description", key.Description) + _ = d.Set("tags", key.Tags) + _ = d.Set("rotation_count", int(key.RotationCount)) + _ = d.Set("created_at", types.FlattenTime(key.CreatedAt)) + _ = d.Set("updated_at", types.FlattenTime(key.UpdatedAt)) + _ = d.Set("protected", key.Protected) + _ = d.Set("locked", key.Locked) + _ = d.Set("rotated_at", types.FlattenTime(key.RotatedAt)) + _ = d.Set("rotation_policy", FlattenKeyRotationPolicy(key.RotationPolicy)) + + return nil +} + +func resourceKeyManagerKeyUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client, region, keyID, err := NewKeyManagerAPIWithRegionAndID(m, d.Id()) + if err != nil { + return diag.FromErr(err) + } + + updateReq := &key_manager.UpdateKeyRequest{ + Region: region, + KeyID: keyID, + } + + if d.HasChange("name") { + name := d.Get("name").(string) + updateReq.Name = &name + } + + if d.HasChange("description") { + desc := d.Get("description").(string) + updateReq.Description = &desc + } + + if d.HasChange("tags") { + tags := types.ExpandStrings(d.Get("tags")) + updateReq.Tags = &tags + } + + if d.HasChange("rotation_policy") { + if v, ok := d.GetOk("rotation_policy"); ok { + rp, err := ExpandKeyRotationPolicy(v) + if err != nil { + return diag.Errorf("invalid rotation_period: %v", err) + } + + updateReq.RotationPolicy = rp + } + } + + _, err = client.UpdateKey(updateReq) + if err != nil { + return diag.FromErr(err) + } + + return resourceKeyManagerKeyRead(ctx, d, m) +} + +func resourceKeyManagerKeyDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client, region, keyID, err := NewKeyManagerAPIWithRegionAndID(m, d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteKey(&key_manager.DeleteKeyRequest{ + Region: region, + KeyID: keyID, + }) + if err != nil { + return diag.FromErr(err) + } + + d.SetId("") + + return nil +} diff --git a/internal/services/keymanager/key_resource_test.go b/internal/services/keymanager/key_resource_test.go new file mode 100644 index 0000000000..21b6723661 --- /dev/null +++ b/internal/services/keymanager/key_resource_test.go @@ -0,0 +1,125 @@ +package keymanager_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + key_manager "github.com/scaleway/scaleway-sdk-go/api/key_manager/v1alpha1" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/keymanager" +) + +func TestAccKeyManagerKey_Basic(t *testing.T) { + tt := acctest.NewTestTools(t) + defer tt.Cleanup() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: tt.ProviderFactories, + CheckDestroy: IsKeyManagerKeyDestroyed(tt), + Steps: []resource.TestStep{ + { + Config: ` + resource "scaleway_key_manager_key" "main" { + name = "tf-test-kms-key-unprotected-a" + region = "fr-par" + usage = "symmetric_encryption" + description = "Test key" + tags = ["tf", "test"] + unprotected = true + } + `, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "name", "tf-test-kms-key-unprotected-a"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "region", "fr-par"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "usage", "symmetric_encryption"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "description", "Test key"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "tags.0", "tf"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "tags.1", "test"), + ), + }, + }, + }) +} + +func TestAccKeyManagerKey_Update(t *testing.T) { + tt := acctest.NewTestTools(t) + defer tt.Cleanup() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: tt.ProviderFactories, + CheckDestroy: IsKeyManagerKeyDestroyed(tt), + Steps: []resource.TestStep{ + { + Config: ` + resource "scaleway_key_manager_key" "main" { + name = "tf-test-kms-key-update" + region = "fr-par" + usage = "symmetric_encryption" + description = "Test key" + tags = ["tf", "test"] + unprotected = true + } + `, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "name", "tf-test-kms-key-update"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "description", "Test key"), + ), + }, + { + Config: ` + resource "scaleway_key_manager_key" "main" { + name = "tf-test-kms-key-updated" + region = "fr-par" + usage = "symmetric_encryption" + description = "Test key updated" + tags = ["tf", "updated"] + unprotected = true + } + `, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "name", "tf-test-kms-key-updated"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "description", "Test key updated"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "tags.1", "updated"), + ), + }, + }, + }) +} + +func IsKeyManagerKeyDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { + return func(state *terraform.State) error { + for _, rs := range state.RootModule().Resources { + if rs.Type != "scaleway_key_manager_key" { + continue + } + + client, region, keyID, err := keymanager.NewKeyManagerAPIWithRegionAndID(tt.Meta, rs.Primary.ID) + if err != nil { + return err + } + + key, err := client.GetKey(&key_manager.GetKeyRequest{ + Region: region, + KeyID: keyID, + }) + if err == nil { + if key.DeletionRequestedAt != nil { + continue + } + + return fmt.Errorf("Key (%s) still exists", rs.Primary.ID) + } + + if !httperrors.Is404(err) { + return err + } + } + + return nil + } +} diff --git a/internal/services/keymanager/testdata/key-manager-key-basic.cassette.yaml b/internal/services/keymanager/testdata/key-manager-key-basic.cassette.yaml new file mode 100644 index 0000000000..8cf0119cc5 --- /dev/null +++ b/internal/services/keymanager/testdata/key-manager-key-basic.cassette.yaml @@ -0,0 +1,248 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 230 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: '{"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","name":"tf-test-kms-key-unprotected-a","usage":{"symmetric_encryption":"aes_256_gcm"},"description":"Test key","tags":["tf","test"],"unprotected":true,"origin":"unknown_origin"}' + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 522 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:20.817469Z","deletion_requested_at":null,"description":"Test key","id":"2b83cb06-a9ed-4470-8373-f1c49e16a3c0","locked":false,"name":"tf-test-kms-key-unprotected-a","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:20.822761Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:20.822761Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "522" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:20 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - c7ba6837-b9c3-451d-9590-38ffdc9ddde5 + status: 200 OK + code: 200 + duration: 422.550041ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/2b83cb06-a9ed-4470-8373-f1c49e16a3c0 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 522 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:20.817469Z","deletion_requested_at":null,"description":"Test key","id":"2b83cb06-a9ed-4470-8373-f1c49e16a3c0","locked":false,"name":"tf-test-kms-key-unprotected-a","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:20.822761Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:20.822761Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "522" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:20 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 3eb75847-5c1a-403f-90a0-f23e06decc1e + status: 200 OK + code: 200 + duration: 112.297084ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/2b83cb06-a9ed-4470-8373-f1c49e16a3c0 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 522 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:20.817469Z","deletion_requested_at":null,"description":"Test key","id":"2b83cb06-a9ed-4470-8373-f1c49e16a3c0","locked":false,"name":"tf-test-kms-key-unprotected-a","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:20.822761Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:20.822761Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "522" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:21 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 36525a48-a1d4-4ab7-a971-dfe88d8b6383 + status: 200 OK + code: 200 + duration: 112.372792ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/2b83cb06-a9ed-4470-8373-f1c49e16a3c0 + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:22 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 1cd8b3c4-c4f3-47fd-a01a-b502def826df + status: 204 No Content + code: 204 + duration: 133.9035ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/2b83cb06-a9ed-4470-8373-f1c49e16a3c0 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 547 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:20.817469Z","deletion_requested_at":"2025-07-09T09:35:22.767315Z","description":"Test key","id":"2b83cb06-a9ed-4470-8373-f1c49e16a3c0","locked":false,"name":"tf-test-kms-key-unprotected-a","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:20.822761Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:20.822761Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "547" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:22 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 7b64ce80-a1ac-4ddc-a874-3714414eed1f + status: 200 OK + code: 200 + duration: 165.161125ms diff --git a/internal/services/keymanager/testdata/key-manager-key-update.cassette.yaml b/internal/services/keymanager/testdata/key-manager-key-update.cassette.yaml new file mode 100644 index 0000000000..a9398c5ba6 --- /dev/null +++ b/internal/services/keymanager/testdata/key-manager-key-update.cassette.yaml @@ -0,0 +1,446 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 223 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: '{"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","name":"tf-test-kms-key-update","usage":{"symmetric_encryption":"aes_256_gcm"},"description":"Test key","tags":["tf","test"],"unprotected":true,"origin":"unknown_origin"}' + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 515 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-update","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:30.174818Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "515" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:30 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 4510b348-2a9c-4983-b6bf-2fd411b362a8 + status: 200 OK + code: 200 + duration: 385.926458ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 515 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-update","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:30.174818Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "515" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:30 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 8f1d4755-3e75-4b88-afbf-99c28e355b9d + status: 200 OK + code: 200 + duration: 120.929042ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 515 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-update","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:30.174818Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "515" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:31 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 10b3d5c6-4ae2-4057-b708-34693696b577 + status: 200 OK + code: 200 + duration: 139.53725ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 515 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-update","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","test"],"updated_at":"2025-07-09T09:35:30.174818Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "515" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:32 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - a3719c3c-2172-4088-b283-177b7bf701f1 + status: 200 OK + code: 200 + duration: 144.34225ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 91 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: '{"name":"tf-test-kms-key-updated","description":"Test key updated","tags":["tf","updated"]}' + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: PATCH + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 527 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key updated","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-updated","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","updated"],"updated_at":"2025-07-09T09:35:32.949117Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "527" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:32 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 69a08da8-19d7-4d26-a998-9cc49ffeb132 + status: 200 OK + code: 200 + duration: 153.157042ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 527 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key updated","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-updated","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","updated"],"updated_at":"2025-07-09T09:35:32.949117Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "527" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:33 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 0a8668cb-820f-4acc-860a-b7bbe0a1dd51 + status: 200 OK + code: 200 + duration: 141.29925ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 527 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":null,"description":"Test key updated","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-updated","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","updated"],"updated_at":"2025-07-09T09:35:32.949117Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "527" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:33 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 7da18c74-4479-4409-991e-d5a75b9fc183 + status: 200 OK + code: 200 + duration: 110.767042ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:34 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 6aa8845f-bc7c-426b-bb58-56efd5bda6e7 + status: 204 No Content + code: 204 + duration: 137.206042ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/9187cb2c-30e3-4e00-b35a-8c289f389e2c + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 552 + uncompressed: false + body: '{"created_at":"2025-07-09T09:35:30.170991Z","deletion_requested_at":"2025-07-09T09:35:34.815199Z","description":"Test key updated","id":"9187cb2c-30e3-4e00-b35a-8c289f389e2c","locked":false,"name":"tf-test-kms-key-updated","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-07-09T09:35:30.174818Z","rotation_count":1,"rotation_policy":null,"state":"enabled","tags":["tf","updated"],"updated_at":"2025-07-09T09:35:32.949117Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "552" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Wed, 09 Jul 2025 09:35:34 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 53739f2c-95fc-484c-b235-c4c5c5ca7be7 + status: 200 OK + code: 200 + duration: 116.598666ms