Skip to content

Commit 9f40e53

Browse files
authored
Validate Logstash pipeline ID when creating. (elastic#135378) (elastic#135575)
* Validate Logstash pipeline ID when creating. * Checkstyle issue fix. * Apply Exford comma. (cherry picked from commit 9b9e665)
1 parent 8b24b91 commit 9f40e53

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;
@@ -23,12 +24,17 @@
2324

2425
import java.io.IOException;
2526
import java.util.List;
27+
import java.util.regex.Pattern;
2628

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

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

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

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

0 commit comments

Comments
 (0)