Skip to content

Commit 3e28c92

Browse files
authored
validate that index patterns are not allowed in create/update doc level monitor (#829)
* validate that index patterns are not allowed in create/update doc level monitor Signed-off-by: Surya Sashank Nistala <[email protected]> * change validation error wording Signed-off-by: Surya Sashank Nistala <[email protected]> * fix error wording Signed-off-by: Surya Sashank Nistala <[email protected]> --------- Signed-off-by: Surya Sashank Nistala <[email protected]>
1 parent 333ff04 commit 3e28c92

File tree

4 files changed

+176
-2
lines changed

4 files changed

+176
-2
lines changed

src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package org.opensearch.commons.alerting.action
33
import org.opensearch.action.ActionRequest
44
import org.opensearch.action.ActionRequestValidationException
55
import org.opensearch.action.support.WriteRequest
6+
import org.opensearch.commons.alerting.model.DocLevelMonitorInput
67
import org.opensearch.commons.alerting.model.Monitor
8+
import org.opensearch.commons.alerting.util.IndexPatternUtils
79
import org.opensearch.core.common.io.stream.StreamInput
810
import org.opensearch.core.common.io.stream.StreamOutput
911
import org.opensearch.rest.RestRequest
1012
import java.io.IOException
13+
import java.util.Locale
1114

1215
class IndexMonitorRequest : ActionRequest {
1316
val monitorId: String
@@ -48,9 +51,22 @@ class IndexMonitorRequest : ActionRequest {
4851
)
4952

5053
override fun validate(): ActionRequestValidationException? {
54+
if (isDocLevelMonitor() && hasDocLeveMonitorInput()) {
55+
val docLevelMonitorInput = monitor.inputs[0] as DocLevelMonitorInput
56+
if (docLevelMonitorInput.indices.stream().anyMatch { IndexPatternUtils.containsPatternSyntax(it) }) {
57+
val actionValidationException = ActionRequestValidationException()
58+
actionValidationException.addValidationError("Index patterns are not supported for doc level monitors.")
59+
return actionValidationException
60+
}
61+
}
5162
return null
5263
}
5364

65+
private fun hasDocLeveMonitorInput() = monitor.inputs.isNotEmpty() && monitor.inputs[0] is DocLevelMonitorInput
66+
67+
private fun isDocLevelMonitor() =
68+
monitor.monitorType.isNotBlank() && Monitor.MonitorType.valueOf(this.monitor.monitorType.uppercase(Locale.ROOT)) == Monitor.MonitorType.DOC_LEVEL_MONITOR
69+
5470
@Throws(IOException::class)
5571
override fun writeTo(out: StreamOutput) {
5672
out.writeString(monitorId)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.opensearch.commons.alerting.util
2+
3+
/**
4+
* Validates that index patterns, wildcards and regex are not used in index names.
5+
*/
6+
object IndexPatternUtils {
7+
private val PATTERN_SPECIAL_CHARS = setOf(
8+
'*', // wildcard for any number of characters
9+
'?', // wildcard for single character
10+
'+', // one or more quantifier
11+
'[', // character class start
12+
']', // character class end
13+
'(', // group start
14+
')', // group end
15+
'{', // range quantifier start
16+
'}', // range quantifier end
17+
'|', // OR operator
18+
'\\', // escape character
19+
'.', // any character
20+
'^', // start anchor/negation in character class
21+
'$' // end anchor
22+
)
23+
24+
fun containsPatternSyntax(indexName: String): Boolean {
25+
if (indexName.isEmpty() || indexName == "_all") {
26+
return true
27+
}
28+
29+
// Check for date math expression <...>
30+
if (indexName.startsWith("<") && indexName.endsWith(">")) {
31+
return true
32+
}
33+
34+
var i = 0
35+
while (i < indexName.length) {
36+
when (val currentChar = indexName[i]) {
37+
'\\' -> i += 2 // Skip escaped character
38+
in PATTERN_SPECIAL_CHARS -> return true
39+
else -> i++
40+
}
41+
}
42+
return false
43+
}
44+
}

src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ fun randomDocumentLevelMonitor(
162162
inputs: List<Input> = listOf(DocLevelMonitorInput("description", listOf("index"), emptyList())),
163163
schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES),
164164
enabled: Boolean = Random().nextBoolean(),
165-
triggers: List<Trigger> = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomQueryLevelTrigger() },
165+
triggers: List<Trigger> = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomDocumentLevelTrigger() },
166166
enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null,
167167
lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS),
168168
withMetadata: Boolean = false

src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package org.opensearch.commons.alerting.action
22

33
import org.junit.jupiter.api.Assertions
44
import org.junit.jupiter.api.Test
5+
import org.opensearch.action.ActionRequestValidationException
56
import org.opensearch.action.support.WriteRequest
67
import org.opensearch.common.io.stream.BytesStreamOutput
78
import org.opensearch.common.settings.Settings
9+
import org.opensearch.commons.alerting.model.DocLevelMonitorInput
10+
import org.opensearch.commons.alerting.model.Monitor
811
import org.opensearch.commons.alerting.model.SearchInput
912
import org.opensearch.commons.alerting.randomBucketLevelMonitor
13+
import org.opensearch.commons.alerting.randomDocumentLevelMonitor
1014
import org.opensearch.commons.alerting.randomQueryLevelMonitor
1115
import org.opensearch.commons.utils.recreateObject
1216
import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput
@@ -80,7 +84,10 @@ class IndexMonitorRequestTests {
8084
recreateObject(bucketLevelMonitorRequest) { IndexMonitorRequest(it) }
8185
}
8286

83-
val recreatedObject = recreateObject(bucketLevelMonitorRequest, NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables)) { IndexMonitorRequest(it) }
87+
val recreatedObject = recreateObject(
88+
bucketLevelMonitorRequest,
89+
NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables)
90+
) { IndexMonitorRequest(it) }
8491
Assertions.assertEquals(bucketLevelMonitorRequest.monitorId, recreatedObject.monitorId)
8592
Assertions.assertEquals(bucketLevelMonitorRequest.seqNo, recreatedObject.seqNo)
8693
Assertions.assertEquals(bucketLevelMonitorRequest.primaryTerm, recreatedObject.primaryTerm)
@@ -111,4 +118,111 @@ class IndexMonitorRequestTests {
111118
Assertions.assertEquals(RestRequest.Method.PUT, newReq.method)
112119
Assertions.assertNotNull(newReq.monitor)
113120
}
121+
122+
@Test
123+
fun `test doc level monitor with valid index name`() {
124+
val monitor = randomDocumentLevelMonitor().copy(
125+
inputs = listOf(DocLevelMonitorInput(indices = listOf("valid-index"), queries = emptyList())),
126+
triggers = emptyList()
127+
)
128+
val req = IndexMonitorRequest(
129+
"1234",
130+
1L,
131+
2L,
132+
WriteRequest.RefreshPolicy.IMMEDIATE,
133+
RestRequest.Method.POST,
134+
monitor
135+
)
136+
137+
val validationException = req.validate()
138+
Assertions.assertNull(validationException)
139+
}
140+
141+
@Test
142+
fun `test doc level monitor with wildcard index pattern`() {
143+
val monitor = randomDocumentLevelMonitor().copy(
144+
inputs = listOf(DocLevelMonitorInput(indices = listOf("valid, test*", "test*"), queries = emptyList()))
145+
)
146+
val req = IndexMonitorRequest(
147+
"1234",
148+
1L,
149+
2L,
150+
WriteRequest.RefreshPolicy.IMMEDIATE,
151+
RestRequest.Method.POST,
152+
monitor
153+
)
154+
155+
val validationException = req.validate()
156+
Assertions.assertNotNull(validationException)
157+
Assertions.assertTrue(validationException is ActionRequestValidationException)
158+
Assertions.assertTrue(
159+
validationException!!.validationErrors().contains("Index patterns are not supported for doc level monitors.")
160+
?: false
161+
)
162+
}
163+
164+
@Test
165+
fun `test doc level monitor with regex index pattern`() {
166+
val monitor = randomDocumentLevelMonitor().copy(
167+
inputs = listOf(DocLevelMonitorInput(indices = listOf("test[0-9]+"), queries = emptyList())),
168+
triggers = emptyList()
169+
)
170+
val req = IndexMonitorRequest(
171+
"1234",
172+
1L,
173+
2L,
174+
WriteRequest.RefreshPolicy.IMMEDIATE,
175+
RestRequest.Method.POST,
176+
monitor
177+
)
178+
179+
val validationException = req.validate()
180+
Assertions.assertNotNull(validationException)
181+
Assertions.assertTrue(validationException is ActionRequestValidationException)
182+
Assertions.assertTrue(
183+
validationException!!.validationErrors().contains("Index patterns are not supported for doc level monitors.")
184+
)
185+
}
186+
187+
@Test
188+
fun `test doc level monitor with date math index pattern`() {
189+
val monitor = randomDocumentLevelMonitor().copy(
190+
inputs = listOf(DocLevelMonitorInput(indices = listOf("<test-{now/d}>"), queries = emptyList())),
191+
triggers = emptyList()
192+
)
193+
val req = IndexMonitorRequest(
194+
"1234",
195+
1L,
196+
2L,
197+
WriteRequest.RefreshPolicy.IMMEDIATE,
198+
RestRequest.Method.POST,
199+
monitor
200+
)
201+
202+
val validationException = req.validate()
203+
Assertions.assertNotNull(validationException)
204+
Assertions.assertTrue(validationException is ActionRequestValidationException)
205+
Assertions.assertTrue(
206+
validationException!!.validationErrors().contains("Index patterns are not supported for doc level monitors.")
207+
)
208+
}
209+
210+
@Test
211+
fun `test non-doc level monitor with index pattern`() {
212+
val monitor = randomQueryLevelMonitor().copy(
213+
inputs = listOf(SearchInput(listOf("test*"), SearchSourceBuilder())),
214+
monitorType = Monitor.MonitorType.QUERY_LEVEL_MONITOR.name
215+
)
216+
val req = IndexMonitorRequest(
217+
"1234",
218+
1L,
219+
2L,
220+
WriteRequest.RefreshPolicy.IMMEDIATE,
221+
RestRequest.Method.POST,
222+
monitor
223+
)
224+
225+
val validationException = req.validate()
226+
Assertions.assertNull(validationException)
227+
}
114228
}

0 commit comments

Comments
 (0)