Skip to content

Commit 6c826f0

Browse files
feat(keymanager): add encrypt ephemeral resource
1 parent 6900a12 commit 6c826f0

File tree

6 files changed

+1009
-3
lines changed

6 files changed

+1009
-3
lines changed

docs/actions/key_manager_key_rotate_action.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ page_title: "Scaleway: scaleway_key_manager_key_rotate_action"
1010

1111
### Required
1212

13-
- `key_id` (String) ID of the key to rotate (UUID format)
13+
- `key_id` (String) ID of the key to rotate. Can be a plain UUID or a regional ID.
1414

1515
### Optional
1616

internal/services/keymanager/action_rotate_key_action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func (a *RotateKeyAction) Schema(ctx context.Context, req action.SchemaRequest,
6565
"region": regional.SchemaAttribute("Region of the key. If not set, the region is derived from the key_id when possible or from the provider configuration."),
6666
"key_id": schema.StringAttribute{
6767
Required: true,
68-
Description: "ID of the key to rotate (UUID format)",
68+
Description: "ID of the key to rotate. Can be a plain UUID or a regional ID.",
6969
Validators: []validator.String{
7070
verify.IsStringUUIDOrUUIDWithLocality(),
7171
},
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package keymanager
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
9+
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
13+
key_manager "github.com/scaleway/scaleway-sdk-go/api/key_manager/v1alpha1"
14+
"github.com/scaleway/scaleway-sdk-go/scw"
15+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
16+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
17+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
18+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
19+
)
20+
21+
var (
22+
_ ephemeral.EphemeralResource = (*EncryptEphemeralResource)(nil)
23+
_ ephemeral.EphemeralResourceWithConfigure = (*EncryptEphemeralResource)(nil)
24+
)
25+
26+
type EncryptEphemeralResource struct {
27+
keyManagerAPI *key_manager.API
28+
meta *meta.Meta
29+
}
30+
31+
func NewEncryptEphemeralResource() ephemeral.EphemeralResource {
32+
return &EncryptEphemeralResource{}
33+
}
34+
35+
func (r *EncryptEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
36+
if req.ProviderData == nil {
37+
return
38+
}
39+
40+
m, ok := req.ProviderData.(*meta.Meta)
41+
if !ok {
42+
resp.Diagnostics.AddError(
43+
"Unexpected Ephemeral Resource Configure Type",
44+
fmt.Sprintf("Expected *meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData),
45+
)
46+
47+
return
48+
}
49+
50+
client := m.ScwClient()
51+
r.keyManagerAPI = key_manager.NewAPI(client)
52+
r.meta = m
53+
}
54+
55+
func (r *EncryptEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
56+
resp.TypeName = req.ProviderTypeName + "_key_manager_encrypt"
57+
}
58+
59+
type EncryptEphemeralResourceModel struct {
60+
Region types.String `tfsdk:"region"`
61+
KeyID types.String `tfsdk:"key_id"`
62+
Plaintext types.String `tfsdk:"plaintext"`
63+
AssociatedData types.Object `tfsdk:"associated_data"`
64+
// Output
65+
Ciphertext types.String `tfsdk:"ciphertext"`
66+
}
67+
68+
type AssociatedDataModel struct {
69+
Value types.String `tfsdk:"value"`
70+
}
71+
72+
func (r *EncryptEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
73+
resp.Schema = schema.Schema{
74+
Attributes: map[string]schema.Attribute{
75+
"region": regional.SchemaAttribute("Region of the key. If not set, the region is derived from the key_id when possible or from the provider configuration."),
76+
"key_id": schema.StringAttribute{
77+
Required: true,
78+
Description: "ID of the key to use for encryption. Can be a plain UUID or a regional ID.",
79+
Validators: []validator.String{
80+
verify.IsStringUUIDOrUUIDWithLocality(),
81+
},
82+
},
83+
"plaintext": schema.StringAttribute{
84+
Required: true,
85+
Description: "Plaintext data to encrypt. Data size must be between 1 and 65535 bytes.",
86+
Sensitive: true,
87+
},
88+
"associated_data": schema.ObjectAttribute{
89+
Optional: true,
90+
Description: "Additional authenticated data. Additional data which will not be encrypted, but authenticated and appended to the encrypted payload. Only supported by keys with a usage set to `symmetric_encryption`.",
91+
AttributeTypes: map[string]attr.Type{
92+
"value": types.StringType,
93+
},
94+
},
95+
"ciphertext": schema.StringAttribute{
96+
Computed: true,
97+
Description: "Key's encrypted data.",
98+
},
99+
},
100+
}
101+
}
102+
103+
func (r *EncryptEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
104+
var data EncryptEphemeralResourceModel
105+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
106+
107+
if resp.Diagnostics.HasError() {
108+
return
109+
}
110+
111+
if r.keyManagerAPI == nil {
112+
resp.Diagnostics.AddError(
113+
"Unconfigured keymanagerAPI",
114+
"The ephemeral resource was not properly configured. The Scaleway client is missing. "+
115+
"This is usually a bug in the provider. Please report it to the maintainers.",
116+
)
117+
118+
return
119+
}
120+
121+
keyID := locality.ExpandID(data.KeyID.ValueString())
122+
plaintext := data.Plaintext.ValueString()
123+
124+
var region scw.Region
125+
126+
var err error
127+
128+
if !data.Region.IsNull() && data.Region.ValueString() != "" {
129+
region = scw.Region(data.Region.ValueString())
130+
} else {
131+
// Try to derive region from the key_id if it is a regional ID
132+
if derivedRegion, id, parseErr := regional.ParseID(keyID); parseErr == nil {
133+
region = derivedRegion
134+
keyID = id
135+
} else {
136+
// Use default region from provider configuration
137+
defaultRegion, exists := r.meta.ScwClient().GetDefaultRegion()
138+
if !exists {
139+
resp.Diagnostics.AddError(
140+
"Missing region",
141+
"The region attribute is required to encrypt with a key. Please provide it explicitly or configure a default region in the provider.",
142+
)
143+
144+
return
145+
}
146+
147+
region = defaultRegion
148+
}
149+
}
150+
151+
var associatedData []byte
152+
153+
if !data.AssociatedData.IsNull() && !data.AssociatedData.IsUnknown() {
154+
var assocDataModel AssociatedDataModel
155+
156+
diags := data.AssociatedData.As(ctx, &assocDataModel, basetypes.ObjectAsOptions{
157+
UnhandledNullAsEmpty: true,
158+
UnhandledUnknownAsEmpty: true,
159+
})
160+
resp.Diagnostics.Append(diags...)
161+
162+
if resp.Diagnostics.HasError() {
163+
return
164+
}
165+
166+
associatedData = []byte(assocDataModel.Value.ValueString())
167+
}
168+
169+
encryptReq := &key_manager.EncryptRequest{
170+
Region: region,
171+
KeyID: keyID,
172+
Plaintext: []byte(plaintext),
173+
AssociatedData: &associatedData,
174+
}
175+
176+
encryptResp, err := r.keyManagerAPI.Encrypt(encryptReq)
177+
if err != nil {
178+
resp.Diagnostics.AddError(
179+
"Error executing Key Manager Encrypt action",
180+
fmt.Sprintf("%s", err),
181+
)
182+
183+
return
184+
}
185+
186+
data.Ciphertext = types.StringValue(string(encryptResp.Ciphertext))
187+
188+
resp.Result.Set(ctx, &data)
189+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package keymanager_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
8+
)
9+
10+
func TestAccEncryptEphemeralResource_Basic(t *testing.T) {
11+
tt := acctest.NewTestTools(t)
12+
defer tt.Cleanup()
13+
14+
resource.ParallelTest(t, resource.TestCase{
15+
ProtoV6ProviderFactories: tt.ProviderFactories,
16+
Steps: []resource.TestStep{
17+
{
18+
Config: `
19+
resource "scaleway_key_manager_key" "test_key" {
20+
name = "tf-test-encrypt-key"
21+
region = "fr-par"
22+
usage = "symmetric_encryption"
23+
algorithm = "aes_256_gcm"
24+
unprotected = true
25+
}
26+
27+
ephemeral "scaleway_key_manager_encrypt" "test_encrypt" {
28+
key_id = scaleway_key_manager_key.test_key.id
29+
plaintext = "test plaintext data"
30+
region = "fr-par"
31+
}
32+
33+
resource "scaleway_secret" "main" {
34+
name = "test-encrypted-secret"
35+
}
36+
37+
resource "scaleway_secret_version" "v1" {
38+
description = "version1"
39+
secret_id = scaleway_secret.main.id
40+
data_wo = ephemeral.scaleway_key_manager_encrypt.test_encrypt.ciphertext
41+
}
42+
43+
data "scaleway_secret_version" "data_v1" {
44+
secret_id = scaleway_secret.main.id
45+
revision = "1"
46+
depends_on = [scaleway_secret_version.v1]
47+
}
48+
`,
49+
Check: resource.ComposeTestCheckFunc(
50+
resource.TestCheckResourceAttrSet("data.scaleway_secret_version.data_v1", "data"),
51+
),
52+
},
53+
},
54+
})
55+
}

0 commit comments

Comments
 (0)