Skip to content

Commit 5251f0f

Browse files
authored
Security Detection Rule schema validation improvements (#1381)
* Removes rule type specific validations in models_* files in favor of schema validations * Adds validation to enforce index or data_view_id is set (except in cases where they are not supported machine_learning, esql)
1 parent 53f6825 commit 5251f0f

File tree

8 files changed

+895
-198
lines changed

8 files changed

+895
-198
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## [Unreleased]
22

33
- Fix regression restricting the characters in an `elasticstack_elasticsearch_role_mapping` `name`. ([#1373](https://github.com/elastic/terraform-provider-elasticstack/pull/1373))
4+
- Add schema validations to require either (but not both) `index` and `data_view_id` is set for relevant Security Detection Rules
45

56
## [0.12.0] - 2025-10-15
67

internal/fleet/output/schema.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,15 @@ func getSchema() schema.Schema {
152152
int64planmodifier.UseStateForUnknown(),
153153
},
154154
Validators: []validator.Int64{
155-
validators.Int64ConditionalRequirement(
156-
path.Root("kafka").AtName("compression"),
157-
[]string{"gzip"},
158-
),
155+
validators.AllowedIfDependentPathEquals(path.Root("kafka").AtName("compression"), "gzip"),
159156
},
160157
},
161158
"connection_type": schema.StringAttribute{
162159
Description: "Connection type for Kafka output.",
163160
Optional: true,
164161
Validators: []validator.String{
165162
stringvalidator.OneOf("plaintext", "encryption"),
166-
validators.StringConditionalRequirementSingle(
163+
validators.AllowedIfDependentPathEquals(
167164
path.Root("kafka").AtName("auth_type"),
168165
"none",
169166
),

internal/kibana/security_detection_rule/acc_test.go

Lines changed: 188 additions & 31 deletions
Large diffs are not rendered by default.

internal/kibana/security_detection_rule/models_esql.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/google/uuid"
1010
"github.com/hashicorp/terraform-plugin-framework/attr"
1111
"github.com/hashicorp/terraform-plugin-framework/diag"
12-
"github.com/hashicorp/terraform-plugin-framework/path"
1312
"github.com/hashicorp/terraform-plugin-framework/types"
1413
)
1514

@@ -59,35 +58,10 @@ func (e EsqlRuleProcessor) ExtractId(response any) (string, diag.Diagnostics) {
5958
return value.Id.String(), diags
6059
}
6160

62-
// applyEsqlValidations validates that ESQL-specific constraints are met
63-
func (d SecurityDetectionRuleData) applyEsqlValidations(diags *diag.Diagnostics) {
64-
if utils.IsKnown(d.Index) {
65-
diags.AddAttributeError(
66-
path.Root("index"),
67-
"Invalid attribute 'index'",
68-
"ESQL rules do not use index patterns. Please remove the 'index' attribute.",
69-
)
70-
}
71-
72-
if utils.IsKnown(d.Filters) {
73-
diags.AddAttributeError(
74-
path.Root("filters"),
75-
"Invalid attribute 'filters'",
76-
"ESQL rules do not support filters. Please remove the 'filters' attribute.",
77-
)
78-
}
79-
}
80-
8161
func (d SecurityDetectionRuleData) toEsqlRuleCreateProps(ctx context.Context, client clients.MinVersionEnforceable) (kbapi.SecurityDetectionsAPIRuleCreateProps, diag.Diagnostics) {
8262
var diags diag.Diagnostics
8363
var createProps kbapi.SecurityDetectionsAPIRuleCreateProps
8464

85-
// Apply ESQL-specific validations
86-
d.applyEsqlValidations(&diags)
87-
if diags.HasError() {
88-
return createProps, diags
89-
}
90-
9165
esqlRule := kbapi.SecurityDetectionsAPIEsqlRuleCreateProps{
9266
Name: kbapi.SecurityDetectionsAPIRuleName(d.Name.ValueString()),
9367
Description: kbapi.SecurityDetectionsAPIRuleDescription(d.Description.ValueString()),
@@ -153,12 +127,6 @@ func (d SecurityDetectionRuleData) toEsqlRuleUpdateProps(ctx context.Context, cl
153127
var diags diag.Diagnostics
154128
var updateProps kbapi.SecurityDetectionsAPIRuleUpdateProps
155129

156-
// Apply ESQL-specific validations
157-
d.applyEsqlValidations(&diags)
158-
if diags.HasError() {
159-
return updateProps, diags
160-
}
161-
162130
// Parse ID to get space_id and rule_id
163131
compId, resourceIdDiags := clients.CompositeIdFromStrFw(d.Id.ValueString())
164132
diags.Append(resourceIdDiags...)

internal/kibana/security_detection_rule/models_machine_learning.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,10 @@ func (m MachineLearningRuleProcessor) ExtractId(response any) (string, diag.Diag
5959
return value.Id.String(), diags
6060
}
6161

62-
// applyMachineLearningValidations validates that Machine learning-specific constraints are met
63-
func (d SecurityDetectionRuleData) applyMachineLearningValidations(diags *diag.Diagnostics) {
64-
if !utils.IsKnown(d.AnomalyThreshold) {
65-
diags.AddAttributeError(
66-
path.Root("anomaly_threshold"),
67-
"Missing attribute 'anomaly_threshold'",
68-
"Machine learning rules require an 'anomaly_threshold' attribute.",
69-
)
70-
}
71-
}
72-
7362
func (d SecurityDetectionRuleData) toMachineLearningRuleCreateProps(ctx context.Context, client clients.MinVersionEnforceable) (kbapi.SecurityDetectionsAPIRuleCreateProps, diag.Diagnostics) {
7463
var diags diag.Diagnostics
7564
var createProps kbapi.SecurityDetectionsAPIRuleCreateProps
7665

77-
d.applyMachineLearningValidations(&diags)
78-
if diags.HasError() {
79-
return createProps, diags
80-
}
81-
8266
mlRule := kbapi.SecurityDetectionsAPIMachineLearningRuleCreateProps{
8367
Name: kbapi.SecurityDetectionsAPIRuleName(d.Name.ValueString()),
8468
Description: kbapi.SecurityDetectionsAPIRuleDescription(d.Description.ValueString()),
@@ -156,11 +140,6 @@ func (d SecurityDetectionRuleData) toMachineLearningRuleUpdateProps(ctx context.
156140
var diags diag.Diagnostics
157141
var updateProps kbapi.SecurityDetectionsAPIRuleUpdateProps
158142

159-
d.applyMachineLearningValidations(&diags)
160-
if diags.HasError() {
161-
return updateProps, diags
162-
}
163-
164143
// Parse ID to get space_id and rule_id
165144
compId, resourceIdDiags := clients.CompositeIdFromStrFw(d.Id.ValueString())
166145
diags.Append(resourceIdDiags...)

internal/kibana/security_detection_rule/schema.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"context"
55
"regexp"
66

7+
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
78
"github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/utils/validators"
810
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
911
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
1012
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1113
"github.com/hashicorp/terraform-plugin-framework/attr"
14+
"github.com/hashicorp/terraform-plugin-framework/path"
1215
"github.com/hashicorp/terraform-plugin-framework/resource"
1316
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1417
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
@@ -73,6 +76,13 @@ func GetSchema() schema.Schema {
7376
"data_view_id": schema.StringAttribute{
7477
MarkdownDescription: "Data view ID for the rule. Not supported for esql and machine_learning rule types.",
7578
Optional: true,
79+
Validators: []validator.String{
80+
// Enforce that data_view_id is not set if the rule type is ml or esql
81+
validators.ForbiddenIfDependentPathOneOf(
82+
path.Root("type"),
83+
[]string{"machine_learning", "esql"},
84+
),
85+
},
7686
},
7787
"namespace": schema.StringAttribute{
7888
MarkdownDescription: "Alerts index namespace. Available for all rule types.",
@@ -108,6 +118,13 @@ func GetSchema() schema.Schema {
108118
MarkdownDescription: "Indices on which the rule functions.",
109119
Optional: true,
110120
Computed: true,
121+
Validators: []validator.List{
122+
// Enforce that index is not set if the rule type is ml or esql
123+
validators.ForbiddenIfDependentPathOneOf(
124+
path.Root("type"),
125+
[]string{"machine_learning", "esql"},
126+
),
127+
},
111128
},
112129
"enabled": schema.BoolAttribute{
113130
MarkdownDescription: "Determines whether the rule is enabled.",
@@ -302,6 +319,12 @@ func GetSchema() schema.Schema {
302319
MarkdownDescription: "Query and filter context array to define alert conditions as JSON. Supports complex filter structures including bool queries, term filters, range filters, etc. Available for all rule types.",
303320
Optional: true,
304321
CustomType: jsontypes.NormalizedType{},
322+
Validators: []validator.String{
323+
validators.ForbiddenIfDependentPathOneOf(
324+
path.Root("type"),
325+
[]string{"machine_learning", "esql"},
326+
),
327+
},
305328
},
306329
"note": schema.StringAttribute{
307330
MarkdownDescription: "Notes to help investigate alerts produced by the rule.",
@@ -602,6 +625,10 @@ func GetSchema() schema.Schema {
602625
MarkdownDescription: "Anomaly score threshold above which the rule creates an alert. Valid values are from 0 to 100. Required for machine_learning rules.",
603626
Optional: true,
604627
Validators: []validator.Int64{
628+
validators.RequiredIfDependentPathEquals(
629+
path.Root("type"),
630+
"machine_learning",
631+
),
605632
int64validator.Between(0, 100),
606633
},
607634
},
@@ -913,3 +940,41 @@ func getThreatSubtechniqueElementType() attr.Type {
913940
techniqueType := threatType.AttributeTypes()["technique"].(attr.TypeWithElementType).ElementType().(attr.TypeWithAttributeTypes)
914941
return techniqueType.AttributeTypes()["subtechnique"].(attr.TypeWithElementType).ElementType()
915942
}
943+
944+
// ValidateConfig validates the configuration for a security detection rule resource.
945+
// It ensures that the configuration meets the following requirements:
946+
//
947+
// - For rule types "esql" and "machine_learning", no additional validation is performed
948+
// - For other rule types, exactly one of 'index' or 'data_view_id' must be specified
949+
// - Both 'index' and 'data_view_id' cannot be set simultaneously
950+
//
951+
// The function adds appropriate error diagnostics if validation fails.
952+
func (r securityDetectionRuleResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
953+
var data SecurityDetectionRuleData
954+
955+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
956+
957+
if resp.Diagnostics.HasError() {
958+
return
959+
}
960+
961+
if data.Type.ValueString() == "esql" || data.Type.ValueString() == "machine_learning" {
962+
return
963+
}
964+
965+
if utils.IsKnown(data.Index) && utils.IsKnown(data.DataViewId) {
966+
resp.Diagnostics.AddError(
967+
"Invalid Configuration",
968+
"Both 'index' and 'data_view_id' cannot be set at the same time.",
969+
)
970+
971+
}
972+
973+
if !utils.IsKnown(data.Index) && !utils.IsKnown(data.DataViewId) {
974+
resp.Diagnostics.AddError(
975+
"Invalid Configuration",
976+
"One of 'index' or 'data_view_id' must be set.",
977+
)
978+
979+
}
980+
}

0 commit comments

Comments
 (0)