Skip to content

Commit 2cb7585

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

File tree

6 files changed

+1001
-3
lines changed

6 files changed

+1001
-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: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+
return
47+
}
48+
49+
client := m.ScwClient()
50+
r.keyManagerAPI = key_manager.NewAPI(client)
51+
r.meta = m
52+
}
53+
54+
func (r *EncryptEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
55+
resp.TypeName = req.ProviderTypeName + "_key_manager_encrypt"
56+
}
57+
58+
type EncryptEphemeralResourceModel struct {
59+
Region types.String `tfsdk:"region"`
60+
KeyID types.String `tfsdk:"key_id"`
61+
Plaintext types.String `tfsdk:"plaintext"`
62+
AssociatedData types.Object `tfsdk:"associated_data"`
63+
// Output
64+
Ciphertext types.String `tfsdk:"ciphertext"`
65+
}
66+
67+
type AssociatedDataModel struct {
68+
Value types.String `tfsdk:"value"`
69+
}
70+
71+
func (r *EncryptEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
72+
resp.Schema = schema.Schema{
73+
Attributes: map[string]schema.Attribute{
74+
"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."),
75+
"key_id": schema.StringAttribute{
76+
Required: true,
77+
Description: "ID of the key to use for encryption. Can be a plain UUID or a regional ID.",
78+
Validators: []validator.String{
79+
verify.IsStringUUIDOrUUIDWithLocality(),
80+
},
81+
},
82+
"plaintext": schema.StringAttribute{
83+
Required: true,
84+
Description: "Plaintext data to encrypt. Data size must be between 1 and 65535 bytes.",
85+
Sensitive: true,
86+
},
87+
"associated_data": schema.ObjectAttribute{
88+
Optional: true,
89+
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`.",
90+
AttributeTypes: map[string]attr.Type{
91+
"value": types.StringType,
92+
},
93+
},
94+
"ciphertext": schema.StringAttribute{
95+
Computed: true,
96+
Description: "Key's encrypted data.",
97+
},
98+
},
99+
}
100+
}
101+
102+
func (r *EncryptEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
103+
var data EncryptEphemeralResourceModel
104+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
105+
106+
if resp.Diagnostics.HasError() {
107+
return
108+
}
109+
110+
if r.keyManagerAPI == nil {
111+
resp.Diagnostics.AddError(
112+
"Unconfigured keymanagerAPI",
113+
"The ephemeral resource was not properly configured. The Scaleway client is missing. "+
114+
"This is usually a bug in the provider. Please report it to the maintainers.",
115+
)
116+
return
117+
}
118+
119+
keyID := locality.ExpandID(data.KeyID.ValueString())
120+
plaintext := data.Plaintext.ValueString()
121+
122+
var region scw.Region
123+
var err error
124+
125+
if !data.Region.IsNull() && data.Region.ValueString() != "" {
126+
region = scw.Region(data.Region.ValueString())
127+
} else {
128+
// Try to derive region from the key_id if it is a regional ID
129+
if derivedRegion, id, parseErr := regional.ParseID(keyID); parseErr == nil {
130+
region = derivedRegion
131+
keyID = id
132+
} else {
133+
// Use default region from provider configuration
134+
defaultRegion, exists := r.meta.ScwClient().GetDefaultRegion()
135+
if !exists {
136+
resp.Diagnostics.AddError(
137+
"Missing region",
138+
"The region attribute is required to encrypt with a key. Please provide it explicitly or configure a default region in the provider.",
139+
)
140+
return
141+
}
142+
region = defaultRegion
143+
}
144+
}
145+
146+
var associatedData []byte
147+
148+
if !data.AssociatedData.IsNull() && !data.AssociatedData.IsUnknown() {
149+
var assocDataModel AssociatedDataModel
150+
diags := data.AssociatedData.As(ctx, &assocDataModel, basetypes.ObjectAsOptions{
151+
UnhandledNullAsEmpty: true,
152+
UnhandledUnknownAsEmpty: true,
153+
})
154+
resp.Diagnostics.Append(diags...)
155+
if resp.Diagnostics.HasError() {
156+
return
157+
}
158+
159+
associatedData = []byte(assocDataModel.Value.ValueString())
160+
}
161+
162+
encryptReq := &key_manager.EncryptRequest{
163+
Region: region,
164+
KeyID: keyID,
165+
Plaintext: []byte(plaintext),
166+
AssociatedData: &associatedData,
167+
}
168+
169+
encryptResp, err := r.keyManagerAPI.Encrypt(encryptReq)
170+
if err != nil {
171+
resp.Diagnostics.AddError(
172+
"Error executing Key Manager Encrypt action",
173+
fmt.Sprintf("%s", err),
174+
)
175+
return
176+
}
177+
178+
data.Ciphertext = types.StringValue(string(encryptResp.Ciphertext))
179+
180+
resp.Result.Set(ctx, &data)
181+
}
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)