Skip to content

Commit 0f60ab0

Browse files
authored
Validate Logstash pipeline ID when creating. (elastic#135378) (elastic#135577)
* Validate Logstash pipeline ID when creating. * Checkstyle issue fix. * Apply Exford comma. (cherry picked from commit 9b9e665)
1 parent d2cba50 commit 0f60ab0

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

x-pack/plugin/logstash/src/javaRestTest/java/org/elasticsearch/xpack/test/rest/LogstashSystemIndexIT.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,59 @@ public void testMultiplePipelines() throws IOException {
150150
assertThat(listResponseMap.size(), is(ids.size()));
151151
}
152152

153+
public void testValidPipelineIds() throws IOException {
154+
final String pipelineJson = getPipelineJson();
155+
final List<String> validIds = List.of(
156+
"main",
157+
"_internal",
158+
"my_pipeline",
159+
"my-pipeline",
160+
"pipeline123",
161+
"A1",
162+
"_pipeline_1",
163+
"MyPipeline-123",
164+
"main_pipeline_v2"
165+
);
166+
167+
for (String id : validIds) {
168+
createPipeline(id, pipelineJson);
169+
}
170+
171+
refreshAllIndices();
172+
173+
// fetch all pipeline IDs
174+
Request listAll = new Request("GET", "/_logstash/pipeline");
175+
Response listAllResponse = client().performRequest(listAll);
176+
assertThat(listAllResponse.getStatusLine().getStatusCode(), is(200));
177+
Map<String, Object> listResponseMap = XContentHelper.convertToMap(
178+
XContentType.JSON.xContent(),
179+
EntityUtils.toString(listAllResponse.getEntity()),
180+
false
181+
);
182+
for (String id : validIds) {
183+
assertTrue(listResponseMap.containsKey(id));
184+
}
185+
assertThat(listResponseMap.size(), is(validIds.size()));
186+
}
187+
188+
public void testInvalidPipelineIds() throws IOException {
189+
final String pipelineJson = getPipelineJson();
190+
final List<String> invalidPipelineIds = List.of("123pipeline", "-pipeline", "*-pipeline");
191+
192+
for (String id : invalidPipelineIds) {
193+
Request putRequest = new Request("PUT", "/_logstash/pipeline/" + id);
194+
putRequest.setJsonEntity(pipelineJson);
195+
196+
ResponseException exception = expectThrows(ResponseException.class, () -> client().performRequest(putRequest));
197+
198+
Response response = exception.getResponse();
199+
assertThat(response.getStatusLine().getStatusCode(), is(400));
200+
201+
String responseBody = EntityUtils.toString(response.getEntity());
202+
assertThat(responseBody, containsString("Invalid pipeline [" + id + "] ID received"));
203+
}
204+
}
205+
153206
private void createPipeline(String id, String json) throws IOException {
154207
Request putRequest = new Request("PUT", "/_logstash/pipeline/" + id);
155208
putRequest.setJsonEntity(json);

x-pack/plugin/logstash/src/main/java/org/elasticsearch/xpack/logstash/rest/RestPutPipelineAction.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.logstash.rest;
99

1010
import org.elasticsearch.client.internal.node.NodeClient;
11+
import org.elasticsearch.common.Strings;
1112
import org.elasticsearch.common.bytes.BytesArray;
1213
import org.elasticsearch.rest.BaseRestHandler;
1314
import org.elasticsearch.rest.RestRequest;
@@ -24,12 +25,17 @@
2425

2526
import java.io.IOException;
2627
import java.util.List;
28+
import java.util.regex.Pattern;
2729

2830
import static org.elasticsearch.rest.RestRequest.Method.PUT;
2931

3032
@ServerlessScope(Scope.PUBLIC)
3133
public class RestPutPipelineAction extends BaseRestHandler {
3234

35+
// A pipeline ID pattern to validate.
36+
// Reference: https://www.elastic.co/docs/reference/logstash/configuring-centralized-pipelines#wildcard-in-pipeline-id
37+
private static final Pattern PIPELINE_ID_PATTERN = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_-]*");
38+
3339
@Override
3440
public String getName() {
3541
return "logstash_put_pipeline";
@@ -40,9 +46,31 @@ public List<Route> routes() {
4046
return List.of(new Route(PUT, "/_logstash/pipeline/{id}"));
4147
}
4248

49+
/**
50+
* Validates pipeline ID for:
51+
* - must begin with a letter or underscore
52+
* - can contain only letters, underscores, dashes, and numbers
53+
*/
54+
private static void validatePipelineId(String id) {
55+
if (Strings.isEmpty(id)) {
56+
throw new IllegalArgumentException("Pipeline ID cannot be null or empty");
57+
}
58+
59+
if (PIPELINE_ID_PATTERN.matcher(id).matches() == false) {
60+
throw new IllegalArgumentException(
61+
"Invalid pipeline ["
62+
+ id
63+
+ "] ID received. Pipeline ID must begin with a letter or underscore and can contain only letters, "
64+
+ "underscores, dashes, hyphens, and numbers"
65+
);
66+
}
67+
}
68+
4369
@Override
4470
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
4571
final String id = request.param("id");
72+
validatePipelineId(id);
73+
4674
try (XContentParser parser = request.contentParser()) {
4775
// parse pipeline for validation
4876
Pipeline.PARSER.apply(parser, id);

0 commit comments

Comments
 (0)