Skip to content

Commit 87411d2

Browse files
committed
Add support for TDE key rotation
1 parent cff0547 commit 87411d2

File tree

4 files changed

+77
-20
lines changed

4 files changed

+77
-20
lines changed

.github/workflows/e2e.yaml

+4-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ jobs:
147147
region: ${{ steps.credentials.outputs.region }}
148148
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
149149
- name: cleanup
150-
if: ${{ always() && matrix.test.cloud == 'aws' && matrix.test.name == 'private_endpoint' }}
150+
if: ${{ always() && matrix.test.cloud == 'aws' }}
151151
uses: ./.github/actions/cleanup-aws
152152
with:
153153
service_name: ${{steps.name.outputs.test_name}}
@@ -218,13 +218,15 @@ jobs:
218218
skip_build: "false"
219219
region: ${{ steps.credentials.outputs.region }}
220220
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
221+
221222
- name: cleanup
222-
if: ${{ always() && matrix.test.cloud == 'aws' && matrix.test.name == 'private_endpoint' }}
223+
if: ${{ always() && matrix.test.cloud == 'aws' }}
223224
uses: ./.github/actions/cleanup-aws
224225
with:
225226
service_name: ${{steps.name.outputs.test_name}}
226227
aws_region: ${{ steps.credentials.outputs.region }}
227228
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
229+
228230
- name: Mark error
229231
id: status
230232
if: failure()

.github/workflows/release.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ jobs:
136136
region: ${{ steps.credentials.outputs.region }}
137137
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
138138

139+
- name: cleanup
140+
if: ${{ always() && matrix.test.cloud == 'aws' }}
141+
uses: ./.github/actions/cleanup-aws
142+
with:
143+
service_name: "[e2e]-${{ matrix.test.name }}-${{ matrix.tf_release }}-${{ matrix.test.cloud }}-${{ needs.token.outputs.token }}"
144+
aws_region: ${{ steps.credentials.outputs.region }}
145+
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
146+
139147
- name: Mark error
140148
id: status
141149
if: failure()
@@ -190,6 +198,14 @@ jobs:
190198
region: ${{ steps.credentials.outputs.region }}
191199
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
192200

201+
- name: cleanup
202+
if: ${{ always() && matrix.test.cloud == 'aws' }}
203+
uses: ./.github/actions/cleanup-aws
204+
with:
205+
service_name: "[upg]-${{ matrix.test.name }}-${{ matrix.tf_release }}-${{ matrix.test.cloud }}-${{ needs.token.outputs.token }}"
206+
aws_region: ${{ steps.credentials.outputs.region }}
207+
aws_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
208+
193209
- name: Mark error
194210
id: status
195211
if: failure()

docs/resources/service.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ resource "clickhouse_service" "service" {
6161
- `endpoints` (Attributes) Allow to enable and configure additional endpoints (read protocols) to expose on the ClickHouse service. (see [below for nested schema](#nestedatt--endpoints))
6262
- `idle_scaling` (Boolean) When set to true the service is allowed to scale down to zero when idle.
6363
- `idle_timeout_minutes` (Number) Set minimum idling timeout (in minutes). Must be greater than or equal to 5 minutes. Must be set if idle_scaling is enabled.
64-
- `max_replica_memory_gb` (Number) Maximum memory of a single replica during auto-scaling in Gb. Must be a multiple of 8. `max_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.
64+
- `max_replica_memory_gb` (Number) Maximum memory of a single replica during auto-scaling in Gb. Must be a multiple of 4 greater than or equal to 8. `max_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.
6565
- `max_total_memory_gb` (Number, Deprecated) Maximum total memory of all workers during auto-scaling in Gb. Must be a multiple of 12 and lower than 360 for non paid services or 720 for paid services.
66-
- `min_replica_memory_gb` (Number) Minimum memory of a single replica during auto-scaling in Gb. Must be a multiple of 8. `min_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.
66+
- `min_replica_memory_gb` (Number) Minimum memory of a single replica during auto-scaling in Gb. Must be a multiple of 4 greater than or equal to 8. `min_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.
6767
- `min_total_memory_gb` (Number, Deprecated) Minimum total memory of all workers during auto-scaling in Gb. Must be a multiple of 12 and greater than 24.
6868
- `num_replicas` (Number) Number of replicas for the service. Must be between 3 and 20. Contact support to enable this feature.
6969
- `password` (String, Sensitive) Password for the default user. One of either `password` or `password_hash` must be specified.
@@ -158,7 +158,7 @@ Optional:
158158
<a id="nestedatt--transparent_data_encryption"></a>
159159
### Nested Schema for `transparent_data_encryption`
160160

161-
Required:
161+
Optional:
162162

163163
- `enabled` (Boolean) If true, TDE is enabled for the service.
164164

pkg/resource/service.go

+54-15
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var (
4141
_ resource.Resource = &ServiceResource{}
4242
_ resource.ResourceWithConfigure = &ServiceResource{}
4343
_ resource.ResourceWithImportState = &ServiceResource{}
44+
_ resource.ResourceWithModifyPlan = &ServiceResource{}
4445
)
4546

4647
//go:embed descriptions/service.md
@@ -283,12 +284,12 @@ func (r *ServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, re
283284
DeprecationMessage: "Please use max_replica_memory_gb instead",
284285
},
285286
"min_replica_memory_gb": schema.Int64Attribute{
286-
Description: "Minimum memory of a single replica during auto-scaling in Gb. Must be a multiple of 8. `min_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.",
287+
Description: "Minimum memory of a single replica during auto-scaling in Gb. Must be a multiple of 4 greater than or equal to 8. `min_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.",
287288
Optional: true,
288289
Computed: true,
289290
},
290291
"max_replica_memory_gb": schema.Int64Attribute{
291-
Description: "Maximum memory of a single replica during auto-scaling in Gb. Must be a multiple of 8. `max_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.",
292+
Description: "Maximum memory of a single replica during auto-scaling in Gb. Must be a multiple of 4 greater than or equal to 8. `max_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services.",
292293
Optional: true,
293294
Computed: true,
294295
},
@@ -339,6 +340,7 @@ func (r *ServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, re
339340
"transparent_data_encryption": schema.SingleNestedAttribute{
340341
Description: "Configuration of the Transparent Data Encryption (TDE) feature. Requires an organization with the Enterprise plan.",
341342
Optional: true,
343+
Computed: true,
342344
Attributes: map[string]schema.Attribute{
343345
"role_id": schema.StringAttribute{
344346
Computed: true,
@@ -348,10 +350,14 @@ func (r *ServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, re
348350
},
349351
},
350352
"enabled": schema.BoolAttribute{
351-
Required: true,
353+
Optional: true,
354+
Computed: true, // To allow client side defaulting.
352355
Description: "If true, TDE is enabled for the service.",
353356
},
354357
},
358+
PlanModifiers: []planmodifier.Object{
359+
objectplanmodifier.UseStateForUnknown(),
360+
},
355361
Validators: []validator.Object{
356362
objectvalidator.ConflictsWith(path.Expressions{path.MatchRoot("warehouse_id")}...),
357363
},
@@ -541,10 +547,10 @@ func (r *ServiceResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
541547
isEnabled = false
542548
}
543549

544-
if !plan.TransparentEncryptionData.IsNull() && !plan.TransparentEncryptionData.IsUnknown() {
545-
planTDE := models.TransparentEncryptionData{}
546-
plan.TransparentEncryptionData.As(ctx, &planTDE, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: false, UnhandledUnknownAsEmpty: false})
547-
wantEnabled = planTDE.Enabled.ValueBool()
550+
if !config.TransparentEncryptionData.IsNull() && !config.TransparentEncryptionData.IsUnknown() {
551+
configTDE := models.TransparentEncryptionData{}
552+
config.TransparentEncryptionData.As(ctx, &configTDE, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: false, UnhandledUnknownAsEmpty: false})
553+
wantEnabled = configTDE.Enabled.ValueBool()
548554
} else {
549555
wantEnabled = false
550556
}
@@ -555,7 +561,7 @@ func (r *ServiceResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
555561
resp.Diagnostics.AddAttributeError(
556562
path.Root("transparent_data_encryption.enabled"),
557563
"Invalid Update",
558-
"It is not possible to disable TDE (Transparend data encryption) on an existing service.",
564+
"It is not possible to disable TDE (Transparent data encryption) on an existing service.",
559565
)
560566
}
561567

@@ -564,9 +570,18 @@ func (r *ServiceResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
564570
resp.Diagnostics.AddAttributeError(
565571
path.Root("transparent_data_encryption.enabled"),
566572
"Invalid Update",
567-
"It is not possible to enable TDE (Transparend data encryption) on an existing service.",
573+
"It is not possible to enable TDE (Transparent data encryption) on an existing service.",
568574
)
569575
}
576+
577+
if !wantEnabled {
578+
// Mark RoleID as known null.
579+
planTDE := models.TransparentEncryptionData{}
580+
plan.TransparentEncryptionData.As(ctx, &planTDE, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: false, UnhandledUnknownAsEmpty: false})
581+
planTDE.RoleID = types.StringNull()
582+
plan.TransparentEncryptionData = planTDE.ObjectValue()
583+
resp.Plan.Set(ctx, plan)
584+
}
570585
}
571586

572587
if plan.Tier.ValueString() == api.TierDevelopment {
@@ -774,6 +789,27 @@ func (r *ServiceResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
774789
resp.Plan.Set(ctx, plan)
775790
}
776791
}
792+
793+
var defaultTDE = false
794+
{
795+
if config.TransparentEncryptionData.IsNull() || config.TransparentEncryptionData.IsUnknown() {
796+
defaultTDE = true
797+
} else {
798+
cfg := models.TransparentEncryptionData{}
799+
diag := config.TransparentEncryptionData.As(ctx, &cfg, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: false, UnhandledUnknownAsEmpty: false})
800+
if diag.HasError() {
801+
return
802+
}
803+
defaultTDE = cfg.Enabled.IsUnknown() || cfg.Enabled.IsNull()
804+
}
805+
if defaultTDE {
806+
plan.TransparentEncryptionData = models.TransparentEncryptionData{
807+
Enabled: types.BoolValue(false),
808+
RoleID: types.StringNull(),
809+
}.ObjectValue()
810+
resp.Plan.Set(ctx, plan)
811+
}
812+
}
777813
}
778814

779815
// Create a new resource
@@ -1875,15 +1911,18 @@ func (r *ServiceResource) syncServiceState(ctx context.Context, state *models.Se
18751911
state.EncryptionAssumedRoleIdentifier = types.StringNull()
18761912
}
18771913

1878-
if service.HasTransparentDataEncryption {
1879-
state.TransparentEncryptionData = models.TransparentEncryptionData{
1880-
RoleID: types.StringValue(service.EncryptionRoleID),
1881-
Enabled: types.BoolValue(service.HasTransparentDataEncryption),
1882-
}.ObjectValue()
1914+
tde := models.TransparentEncryptionData{
1915+
Enabled: types.BoolValue(service.HasTransparentDataEncryption),
1916+
}
1917+
1918+
if service.EncryptionRoleID != "" {
1919+
tde.RoleID = types.StringValue(service.EncryptionRoleID)
18831920
} else {
1884-
state.TransparentEncryptionData = types.ObjectNull(models.TransparentEncryptionData{}.ObjectType().AttrTypes)
1921+
tde.RoleID = types.StringNull()
18851922
}
18861923

1924+
state.TransparentEncryptionData = tde.ObjectValue()
1925+
18871926
if service.QueryAPIEndpoints != nil {
18881927
queryApiEndpoint := models.QueryAPIEndpoints{}
18891928

0 commit comments

Comments
 (0)