Skip to content

Commit 724e9be

Browse files
[Connector APIs] Enforce index prefix for managed connectors (#117778)
* [Connector APIs] Enforce manage connector index prefix * Update ConnectorIndexServiceTests to check for prefix * Update docs/changelog/117778.yaml * Fix accidental changes to muted tests * Review feedback * Review feedback --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 4fbe306 commit 724e9be

File tree

14 files changed

+351
-41
lines changed

14 files changed

+351
-41
lines changed

docs/changelog/117778.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 117778
2+
summary: "[Connector APIs] Enforce index prefix for managed connectors"
3+
area: Extract&Transform
4+
type: feature
5+
issues: []

x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/10_connector_put.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,19 @@ setup:
152152
service_type: super-connector
153153

154154

155+
---
156+
'Create Connector - Invalid Managed Connector Index Prefix':
157+
- do:
158+
catch: "bad_request"
159+
connector.put:
160+
connector_id: test-connector-test-managed
161+
body:
162+
index_name: wrong-prefix-index
163+
name: my-connector
164+
language: pl
165+
is_native: true
166+
service_type: super-connector
167+
155168
---
156169
'Create Connector - Id returned as part of response':
157170
- do:

x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/130_connector_update_index_name.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,18 @@ setup:
151151

152152
- match: { index_name: content-search-2-test }
153153

154+
---
155+
"Update Managed Connector Index Name - Bad Prefix":
156+
- do:
157+
connector.put:
158+
connector_id: test-connector-2
159+
body:
160+
is_native: true
161+
service_type: super-connector
162+
163+
- do:
164+
catch: "bad_request"
165+
connector.update_index_name:
166+
connector_id: test-connector-2
167+
body:
168+
index_name: wrong-prefix-search-2-test

x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/140_connector_update_native.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,43 @@ setup:
7373
field_1: test
7474
field_2: something
7575

76+
---
77+
"Update Connector Native - changing connector to Elastic-managed wrong index name":
78+
79+
- do:
80+
connector.put:
81+
connector_id: test-connector-1
82+
body:
83+
is_native: false
84+
index_name: super-connector
85+
86+
- do:
87+
catch: "bad_request"
88+
connector.update_native:
89+
connector_id: test-connector-1
90+
body:
91+
is_native: true
92+
93+
---
94+
"Update Connector Native - changing connector to Elastic-managed correct index name":
95+
96+
- do:
97+
connector.put:
98+
connector_id: test-connector-1
99+
body:
100+
is_native: false
101+
index_name: content-super-connector
102+
103+
- do:
104+
connector.update_native:
105+
connector_id: test-connector-1
106+
body:
107+
is_native: true
108+
109+
- match: { result: updated }
110+
111+
- do:
112+
connector.get:
113+
connector_id: test-connector-1
114+
115+
- match: { is_native: true }

x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/15_connector_post.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ setup:
103103
service_type: super-connector
104104

105105

106+
---
107+
'Create Connector - Invalid Managed Connector Index Prefix':
108+
- do:
109+
catch: "bad_request"
110+
connector.post:
111+
body:
112+
index_name: wrong-prefix-index
113+
name: my-connector
114+
language: pl
115+
is_native: true
116+
service_type: super-connector
117+
106118
---
107119
'Create Connector - Index name used by another connector':
108120
- do:

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
7777
import static org.elasticsearch.xpack.application.connector.ConnectorFiltering.fromXContentBytesConnectorFiltering;
7878
import static org.elasticsearch.xpack.application.connector.ConnectorFiltering.sortFilteringRulesByOrder;
79+
import static org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry.MANAGED_CONNECTOR_INDEX_PREFIX;
7980

8081
/**
8182
* A service that manages persistent {@link Connector} configurations.
@@ -807,38 +808,71 @@ public void updateConnectorLastSyncStats(UpdateConnectorLastSyncStatsAction.Requ
807808
}
808809

809810
/**
810-
* Updates the is_native property of a {@link Connector}. It always sets the {@link ConnectorStatus} to
811-
* CONFIGURED.
811+
* Updates the is_native property of a {@link Connector}. It sets the {@link ConnectorStatus} to
812+
* CONFIGURED when connector is in CONNECTED state to indicate that connector needs to reconnect.
812813
*
813814
* @param request The request for updating the connector's is_native property.
814815
* @param listener The listener for handling responses, including successful updates or errors.
815816
*/
816817
public void updateConnectorNative(UpdateConnectorNativeAction.Request request, ActionListener<UpdateResponse> listener) {
817818
try {
818819
String connectorId = request.getConnectorId();
820+
boolean isNative = request.isNative();
819821

820-
final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc(
821-
new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
822-
.id(connectorId)
823-
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
824-
.source(
825-
Map.of(
826-
Connector.IS_NATIVE_FIELD.getPreferredName(),
827-
request.isNative(),
828-
Connector.STATUS_FIELD.getPreferredName(),
829-
ConnectorStatus.CONFIGURED.toString()
830-
)
831-
)
822+
getConnector(connectorId, listener.delegateFailure((l, connector) -> {
832823

833-
);
834-
client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (l, updateResponse) -> {
835-
if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
836-
l.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
824+
String indexName = getConnectorIndexNameFromSearchResult(connector);
825+
826+
boolean doesNotHaveContentPrefix = indexName != null && isValidManagedConnectorIndexName(indexName) == false;
827+
// Ensure attached content index is prefixed correctly
828+
if (isNative && doesNotHaveContentPrefix) {
829+
l.onFailure(
830+
new ElasticsearchStatusException(
831+
"The index name ["
832+
+ indexName
833+
+ "] attached to the connector ["
834+
+ connectorId
835+
+ "] must start with the required prefix: ["
836+
+ MANAGED_CONNECTOR_INDEX_PREFIX
837+
+ "] to be Elastic-managed. Please update the attached index first to comply with this requirement.",
838+
RestStatus.BAD_REQUEST
839+
)
840+
);
837841
return;
838842
}
839-
l.onResponse(updateResponse);
840-
}));
841843

844+
ConnectorStatus status = getConnectorStatusFromSearchResult(connector);
845+
846+
// If connector was connected already, change its status to CONFIGURED as we need to re-connect
847+
boolean isConnected = status == ConnectorStatus.CONNECTED;
848+
boolean isValidTransitionToConfigured = ConnectorStateMachine.isValidTransition(status, ConnectorStatus.CONFIGURED);
849+
if (isConnected && isValidTransitionToConfigured) {
850+
status = ConnectorStatus.CONFIGURED;
851+
}
852+
853+
final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).setRefreshPolicy(
854+
WriteRequest.RefreshPolicy.IMMEDIATE
855+
)
856+
.doc(
857+
new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
858+
.id(connectorId)
859+
.source(
860+
Map.of(
861+
Connector.IS_NATIVE_FIELD.getPreferredName(),
862+
isNative,
863+
Connector.STATUS_FIELD.getPreferredName(),
864+
status.toString()
865+
)
866+
)
867+
);
868+
client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (ll, updateResponse) -> {
869+
if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
870+
ll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
871+
return;
872+
}
873+
ll.onResponse(updateResponse);
874+
}));
875+
}));
842876
} catch (Exception e) {
843877
listener.onFailure(e);
844878
}
@@ -896,22 +930,45 @@ public void updateConnectorIndexName(UpdateConnectorIndexNameAction.Request requ
896930
return;
897931
}
898932

899-
final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc(
900-
new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
901-
.id(connectorId)
902-
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
903-
.source(new HashMap<>() {
904-
{
905-
put(Connector.INDEX_NAME_FIELD.getPreferredName(), request.getIndexName());
906-
}
907-
})
908-
);
909-
client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (ll, updateResponse) -> {
910-
if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
911-
ll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
933+
getConnector(connectorId, l.delegateFailure((ll, connector) -> {
934+
935+
Boolean isNativeConnector = getConnectorIsNativeFlagFromSearchResult(connector);
936+
Boolean doesNotHaveContentPrefix = indexName != null && isValidManagedConnectorIndexName(indexName) == false;
937+
938+
if (isNativeConnector && doesNotHaveContentPrefix) {
939+
ll.onFailure(
940+
new ElasticsearchStatusException(
941+
"Index attached to an Elastic-managed connector must start with the prefix: ["
942+
+ MANAGED_CONNECTOR_INDEX_PREFIX
943+
+ "]. The index name in the payload ["
944+
+ indexName
945+
+ "] doesn't comply with this requirement.",
946+
RestStatus.BAD_REQUEST
947+
)
948+
);
912949
return;
913950
}
914-
ll.onResponse(updateResponse);
951+
952+
final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc(
953+
new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
954+
.id(connectorId)
955+
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
956+
.source(new HashMap<>() {
957+
{
958+
put(Connector.INDEX_NAME_FIELD.getPreferredName(), request.getIndexName());
959+
}
960+
})
961+
);
962+
client.update(
963+
updateRequest,
964+
new DelegatingIndexNotFoundActionListener<>(connectorId, listener, (lll, updateResponse) -> {
965+
if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
966+
lll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
967+
return;
968+
}
969+
lll.onResponse(updateResponse);
970+
})
971+
);
915972
}));
916973
}));
917974

@@ -1064,6 +1121,18 @@ private ConnectorStatus getConnectorStatusFromSearchResult(ConnectorSearchResult
10641121
return ConnectorStatus.connectorStatus((String) searchResult.getResultMap().get(Connector.STATUS_FIELD.getPreferredName()));
10651122
}
10661123

1124+
private Boolean getConnectorIsNativeFlagFromSearchResult(ConnectorSearchResult searchResult) {
1125+
return (Boolean) searchResult.getResultMap().get(Connector.IS_NATIVE_FIELD.getPreferredName());
1126+
}
1127+
1128+
private String getConnectorIndexNameFromSearchResult(ConnectorSearchResult searchResult) {
1129+
return (String) searchResult.getResultMap().get(Connector.INDEX_NAME_FIELD.getPreferredName());
1130+
}
1131+
1132+
private boolean isValidManagedConnectorIndexName(String indexName) {
1133+
return indexName.startsWith(MANAGED_CONNECTOR_INDEX_PREFIX);
1134+
}
1135+
10671136
@SuppressWarnings("unchecked")
10681137
private Map<String, Object> getConnectorConfigurationFromSearchResult(ConnectorSearchResult searchResult) {
10691138
return (Map<String, Object>) searchResult.getResultMap().get(Connector.CONFIGURATION_FIELD.getPreferredName());

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorTemplateRegistry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class ConnectorTemplateRegistry extends IndexTemplateRegistry {
4545
public static final String ACCESS_CONTROL_INDEX_NAME_PATTERN = ".search-acl-filter-*";
4646
public static final String ACCESS_CONTROL_TEMPLATE_NAME = "search-acl-filter";
4747

48+
public static final String MANAGED_CONNECTOR_INDEX_PREFIX = "content-";
49+
4850
// Pipeline constants
4951

5052
public static final String ENT_SEARCH_GENERIC_PIPELINE_NAME = "ent-search-generic-ingestion";

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/ConnectorActionRequest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020

2121
import static org.elasticsearch.action.ValidateActions.addValidationError;
22+
import static org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry.MANAGED_CONNECTOR_INDEX_PREFIX;
2223

2324
/**
2425
* Abstract base class for action requests targeting the connectors index. Implements {@link org.elasticsearch.action.IndicesRequest}
@@ -52,6 +53,32 @@ public ActionRequestValidationException validateIndexName(String indexName, Acti
5253
return validationException;
5354
}
5455

56+
/**
57+
* Validates that the given index name starts with the required prefix for Elastic-managed connectors.
58+
* If the index name does not start with the required prefix, the validation exception is updated with an error message.
59+
*
60+
* @param indexName The index name to validate. If null, no validation is performed.
61+
* @param validationException The exception to accumulate validation errors.
62+
* @return The updated or original {@code validationException} with any new validation errors added,
63+
* if the index name does not start with the required prefix.
64+
*/
65+
public ActionRequestValidationException validateManagedConnectorIndexPrefix(
66+
String indexName,
67+
ActionRequestValidationException validationException
68+
) {
69+
if (indexName != null && indexName.startsWith(MANAGED_CONNECTOR_INDEX_PREFIX) == false) {
70+
return addValidationError(
71+
"Index ["
72+
+ indexName
73+
+ "] is invalid. Index attached to an Elastic-managed connector must start with the prefix: ["
74+
+ MANAGED_CONNECTOR_INDEX_PREFIX
75+
+ "]",
76+
validationException
77+
);
78+
}
79+
return validationException;
80+
}
81+
5582
@Override
5683
public String[] indices() {
5784
return new String[] { ConnectorTemplateRegistry.CONNECTOR_INDEX_NAME_PATTERN };

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/PostConnectorAction.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ public ActionRequestValidationException validate() {
127127

128128
validationException = validateIndexName(indexName, validationException);
129129

130+
if (Boolean.TRUE.equals(isNative)) {
131+
validationException = validateManagedConnectorIndexPrefix(indexName, validationException);
132+
}
133+
130134
return validationException;
131135
}
132136

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/PutConnectorAction.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ public ActionRequestValidationException validate() {
147147

148148
validationException = validateIndexName(indexName, validationException);
149149

150+
if (Boolean.TRUE.equals(isNative)) {
151+
validationException = validateManagedConnectorIndexPrefix(indexName, validationException);
152+
}
153+
150154
return validationException;
151155
}
152156

0 commit comments

Comments
 (0)