Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import org.opensearch.commons.authuser.User;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.securityanalytics.settings.SecurityAnalyticsSettings;
import org.opensearch.securityanalytics.transport.QueryUtils;
import org.opensearch.securityanalytics.threatIntel.action.SASearchTIFSourceConfigsAction;
import org.opensearch.securityanalytics.threatIntel.action.SASearchTIFSourceConfigsRequest;
import org.opensearch.securityanalytics.threatIntel.service.DefaultTifSourceConfigLoaderService;
Expand Down Expand Up @@ -66,6 +68,14 @@ protected void doExecute(Task task, SASearchTIFSourceConfigsRequest request, Act
actionListener.onFailure(new OpenSearchStatusException("Do not have permissions to resource", RestStatus.FORBIDDEN));
return;
}

SearchSourceBuilder source = request.getSearchSourceBuilder();
if (source != null && source.query() != null && QueryUtils.containsTermsLookup(source.query())) {
actionListener.onFailure(new OpenSearchStatusException(
"Terms lookup queries referencing external indices are not permitted", RestStatus.FORBIDDEN));
return;
}

this.threadPool.getThreadContext().stashContext(); // stash context to make calls as admin client

StepListener<Void> defaultTifConfigsLoadedListener;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.index.IndexNotFoundException;
import org.opensearch.search.SearchHit;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.securityanalytics.settings.SecurityAnalyticsSettings;
import org.opensearch.securityanalytics.threatIntel.action.monitor.SearchThreatIntelMonitorAction;
import org.opensearch.securityanalytics.threatIntel.action.monitor.request.SearchThreatIntelMonitorRequest;
import org.opensearch.securityanalytics.threatIntel.sacommons.monitor.ThreatIntelMonitorDto;
import org.opensearch.securityanalytics.threatIntel.util.ThreatIntelMonitorUtils;
import org.opensearch.securityanalytics.transport.QueryUtils;
import org.opensearch.securityanalytics.transport.SecureTransportAction;
import org.opensearch.tasks.Task;
import org.opensearch.threadpool.ThreadPool;
Expand Down Expand Up @@ -77,6 +79,14 @@ protected void doExecute(Task task, SearchThreatIntelMonitorRequest request, Act
listener.onFailure(new OpenSearchStatusException("Do not have permissions to resource", RestStatus.FORBIDDEN));
return;
}

SearchSourceBuilder source = request.searchRequest().source();
if (source != null && source.query() != null && QueryUtils.containsTermsLookup(source.query())) {
listener.onFailure(new OpenSearchStatusException(
"Terms lookup queries referencing external indices are not permitted", RestStatus.FORBIDDEN));
return;
}

this.threadPool.getThreadContext().stashContext();

//TODO change search request to fetch threat intel monitors
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.securityanalytics.transport;

import org.apache.lucene.search.BooleanClause;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.index.query.QueryBuilderVisitor;
import org.opensearch.index.query.TermsQueryBuilder;

/**
* Utility methods for inspecting query trees for security-sensitive patterns.
* Uses the framework's QueryBuilderVisitor to traverse all sub-queries generically,
* ensuring complete coverage of all query types including future additions.
*/
public class QueryUtils {

private QueryUtils() {}

/**
* Checks if a query tree contains any TermsQueryBuilder with a termsLookup
* (i.e., a cross-index terms lookup that could be used to probe data in unauthorized indices).
* Uses QueryBuilder.visit() for complete traversal of all query types.
*/
public static boolean containsTermsLookup(QueryBuilder query) {
if (query == null) {
return false;
}

TermsLookupDetector detector = new TermsLookupDetector();
query.visit(detector);
return detector.found;
}

private static class TermsLookupDetector implements QueryBuilderVisitor {
boolean found = false;

@Override
public void accept(QueryBuilder qb) {
if (qb instanceof TermsQueryBuilder) {
if (((TermsQueryBuilder) qb).termsLookup() != null) {
found = true;
}
}
}

@Override
public QueryBuilderVisitor getChildVisitor(BooleanClause.Occur occur) {
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
*/
package org.opensearch.securityanalytics.transport;

import org.opensearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.opensearch.action.support.master.AcknowledgedResponse;
import org.opensearch.core.action.ActionListener;
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.HandledTransportAction;
import org.opensearch.action.support.master.AcknowledgedResponse;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.inject.Inject;
import org.opensearch.securityanalytics.action.CreateIndexMappingsAction;
Expand All @@ -18,11 +19,14 @@
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.TransportService;

import java.util.Map;

public class TransportCreateIndexMappingsAction extends HandledTransportAction<CreateIndexMappingsRequest, AcknowledgedResponse> {
private MapperService mapperService;
private ClusterService clusterService;

private final ThreadPool threadPool;
private final Client client;


@Inject
Expand All @@ -31,24 +35,35 @@ public TransportCreateIndexMappingsAction(
ActionFilters actionFilters,
ThreadPool threadPool,
MapperService mapperService,
ClusterService clusterService
ClusterService clusterService,
Client client
) {
super(CreateIndexMappingsAction.NAME, transportService, actionFilters, CreateIndexMappingsRequest::new);
this.clusterService = clusterService;
this.mapperService = mapperService;
this.threadPool = threadPool;
this.client = client;
}

@Override
protected void doExecute(Task task, CreateIndexMappingsRequest request, ActionListener<AcknowledgedResponse> actionListener) {
this.threadPool.getThreadContext().stashContext();

mapperService.createMappingAction(
request.getIndexName(),
request.getRuleTopic(),
request.getAliasMappings(),
request.getPartial(),
actionListener
);
// Verify caller has indices:admin/mapping/put on the target index before elevating privileges.
// Issues a no-op PutMappingRequest (empty properties) as the caller — the security plugin
// checks the permission naturally without stashContext, so unauthorized users get 403.
PutMappingRequest putMappingRequest = new PutMappingRequest(request.getIndexName())
.source(Map.of("properties", Map.of()));
client.admin().indices().putMapping(putMappingRequest, ActionListener.wrap(
putMappingResponse -> {
this.threadPool.getThreadContext().stashContext();
mapperService.createMappingAction(
request.getIndexName(),
request.getRuleTopic(),
request.getAliasMappings(),
request.getPartial(),
actionListener
);
},
actionListener::onFailure
));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,27 @@
*/
package org.opensearch.securityanalytics.transport;

import org.opensearch.OpenSearchStatusException;
import org.opensearch.action.admin.indices.mapping.get.GetMappingsRequest;
import org.opensearch.core.action.ActionListener;
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.HandledTransportAction;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.inject.Inject;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.securityanalytics.action.GetIndexMappingsAction;
import org.opensearch.securityanalytics.mapper.MapperService;
import org.opensearch.securityanalytics.action.GetIndexMappingsRequest;
import org.opensearch.securityanalytics.action.GetIndexMappingsResponse;
import org.opensearch.securityanalytics.util.SecurityAnalyticsException;
import org.opensearch.tasks.Task;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.TransportService;
import org.opensearch.client.Client;

public class TransportGetIndexMappingsAction extends HandledTransportAction<GetIndexMappingsRequest, GetIndexMappingsResponse> {
private MapperService mapperService;
private ClusterService clusterService;

private final ThreadPool threadPool;
private final Client client;

@Inject
public TransportGetIndexMappingsAction(
Expand All @@ -34,18 +33,26 @@ public TransportGetIndexMappingsAction(
GetIndexMappingsAction getIndexMappingsAction,
MapperService mapperService,
ClusterService clusterService,
ThreadPool threadPool
ThreadPool threadPool,
Client client
) {
super(getIndexMappingsAction.NAME, transportService, actionFilters, GetIndexMappingsRequest::new);
this.clusterService = clusterService;
this.mapperService = mapperService;
this.threadPool = threadPool;
this.client = client;
}

@Override
protected void doExecute(Task task, GetIndexMappingsRequest request, ActionListener<GetIndexMappingsResponse> actionListener) {
this.threadPool.getThreadContext().stashContext();

mapperService.getMappingAction(request.getIndexName(), actionListener);
// Verify caller has permission on the target index before elevating privileges
GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(request.getIndexName());
client.admin().indices().getMappings(getMappingsRequest, ActionListener.wrap(
getMappingsResponse -> {
this.threadPool.getThreadContext().stashContext();
mapperService.getMappingAction(request.getIndexName(), actionListener);
},
actionListener::onFailure
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.opensearch.securityanalytics.transport;

import org.opensearch.action.admin.indices.mapping.get.GetMappingsRequest;
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.HandledTransportAction;
import org.opensearch.cluster.service.ClusterService;
Expand All @@ -16,11 +17,13 @@
import org.opensearch.tasks.Task;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.TransportService;
import org.opensearch.client.Client;

public class TransportGetMappingsViewAction extends HandledTransportAction<GetMappingsViewRequest, GetMappingsViewResponse> {
private MapperService mapperService;
private ClusterService clusterService;
private final ThreadPool threadPool;
private final Client client;

@Inject
public TransportGetMappingsViewAction(
Expand All @@ -29,17 +32,26 @@ public TransportGetMappingsViewAction(
GetMappingsViewAction getMappingsViewAction,
MapperService mapperService,
ClusterService clusterService,
ThreadPool threadPool
ThreadPool threadPool,
Client client
) {
super(getMappingsViewAction.NAME, transportService, actionFilters, GetMappingsViewRequest::new);
this.clusterService = clusterService;
this.mapperService = mapperService;
this.threadPool = threadPool;
this.client = client;
}

@Override
protected void doExecute(Task task, GetMappingsViewRequest request, ActionListener<GetMappingsViewResponse> actionListener) {
this.threadPool.getThreadContext().stashContext();
this.mapperService.getMappingsViewAction(request.getIndexName(), request.getRuleTopic(), actionListener);
// Verify caller has permission on the target index before elevating privileges
GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(request.getIndexName());
client.admin().indices().getMappings(getMappingsRequest, ActionListener.wrap(
getMappingsResponse -> {
this.threadPool.getThreadContext().stashContext();
this.mapperService.getMappingsViewAction(request.getIndexName(), request.getRuleTopic(), actionListener);
},
actionListener::onFailure
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.search.TotalHits;
import org.opensearch.OpenSearchStatusException;
import org.opensearch.core.action.ActionListener;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.action.search.ShardSearchFailure;
Expand All @@ -19,10 +20,11 @@
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.inject.Inject;
import org.opensearch.commons.notifications.action.SendNotificationRequest;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.index.IndexNotFoundException;
import org.opensearch.search.SearchHit;
import org.opensearch.search.SearchHits;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.internal.InternalSearchResponse;
import org.opensearch.securityanalytics.action.SearchCorrelationRuleAction;
import org.opensearch.securityanalytics.action.SearchCorrelationRuleRequest;
Expand Down Expand Up @@ -82,6 +84,13 @@ public TransportSearchCorrelationRuleAction(

@Override
protected void doExecute(Task task, SearchCorrelationRuleRequest request, ActionListener<SearchResponse> listener) {
SearchSourceBuilder source = request.getSearchRequest().source();
if (source != null && source.query() != null && QueryUtils.containsTermsLookup(source.query())) {
listener.onFailure(new OpenSearchStatusException(
"Terms lookup queries referencing external indices are not permitted in correlation rule search", RestStatus.FORBIDDEN));
return;
}

this.threadPool.getThreadContext().stashContext();

client.search(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

package org.opensearch.securityanalytics.transport;

import org.opensearch.core.action.ActionListener;
import org.opensearch.OpenSearchStatusException;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.HandledTransportAction;
Expand All @@ -17,6 +17,9 @@
import org.opensearch.common.inject.Inject;
import org.opensearch.common.settings.Settings;
import org.opensearch.commons.authuser.User;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.securityanalytics.action.SearchCustomLogTypeAction;
import org.opensearch.securityanalytics.action.SearchCustomLogTypeRequest;
import org.opensearch.securityanalytics.logtype.LogTypeService;
Expand Down Expand Up @@ -71,6 +74,13 @@ protected void doExecute(Task task, SearchCustomLogTypeRequest request, ActionLi
addFilter(user, request.searchRequest().source(), "detector.user.backend_roles.keyword");
}

SearchSourceBuilder source = request.searchRequest().source();
if (source != null && source.query() != null && QueryUtils.containsTermsLookup(source.query())) {
listener.onFailure(new OpenSearchStatusException(
"Terms lookup queries referencing external indices are not permitted in log type search", RestStatus.FORBIDDEN));
return;
}

this.threadPool.getThreadContext().stashContext();
logTypeService.searchLogTypes(request.searchRequest(), new ActionListener<>() {
@Override
Expand All @@ -88,4 +98,4 @@ public void onFailure(Exception e) {
private void setFilterByEnabled(boolean filterByEnabled) {
this.filterByEnabled = filterByEnabled;
}
}
}
Loading