Skip to content

Commit 687e6d9

Browse files
Introduces resource permissions for detectors
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
1 parent 926b2e1 commit 687e6d9

11 files changed

+144
-168
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.opensearch.ad.constant;
2+
3+
import org.opensearch.accesscontrol.resources.ResourceAccessScope;
4+
5+
public enum ADResourceScope implements ResourceAccessScope {
6+
AD_READ_ACCESS("ad_read_access"),
7+
AD_FULL_ACCESS("ad_full_access");
8+
9+
private final String scopeName;
10+
11+
ADResourceScope(String scopeName) {
12+
this.scopeName = scopeName;
13+
}
14+
15+
public String getScopeName() {
16+
return scopeName;
17+
}
18+
}

src/main/java/org/opensearch/ad/transport/IndexAnomalyDetectorTransportAction.java

+6-29
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
import static org.opensearch.ad.constant.ADCommonMessages.FAIL_TO_CREATE_DETECTOR;
1515
import static org.opensearch.ad.constant.ADCommonMessages.FAIL_TO_UPDATE_DETECTOR;
1616
import static org.opensearch.ad.settings.AnomalyDetectorSettings.AD_FILTER_BY_BACKEND_ROLES;
17-
import static org.opensearch.timeseries.util.ParseUtils.checkFilterByBackendRoles;
18-
import static org.opensearch.timeseries.util.ParseUtils.getConfig;
17+
import static org.opensearch.timeseries.util.ParseUtils.*;
1918
import static org.opensearch.timeseries.util.RestHandlerUtils.wrapRestActionListener;
2019

2120
import java.util.List;
@@ -45,7 +44,6 @@
4544
import org.opensearch.rest.RestRequest;
4645
import org.opensearch.search.builder.SearchSourceBuilder;
4746
import org.opensearch.tasks.Task;
48-
import org.opensearch.timeseries.common.exception.TimeSeriesException;
4947
import org.opensearch.timeseries.feature.SearchFeatureDao;
5048
import org.opensearch.timeseries.function.ExecutorFunction;
5149
import org.opensearch.timeseries.util.ParseUtils;
@@ -100,48 +98,25 @@ protected void doExecute(Task task, IndexAnomalyDetectorRequest request, ActionL
10098
String errorMessage = method == RestRequest.Method.PUT ? FAIL_TO_UPDATE_DETECTOR : FAIL_TO_CREATE_DETECTOR;
10199
ActionListener<IndexAnomalyDetectorResponse> listener = wrapRestActionListener(actionListener, errorMessage);
102100
try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) {
103-
resolveUserAndExecute(user, detectorId, method, listener, (detector) -> adExecute(request, user, detector, context, listener));
101+
resolveUserAndExecute(detectorId, method, listener, (detector) -> adExecute(request, user, detector, context, listener));
104102
} catch (Exception e) {
105103
LOG.error(e);
106104
listener.onFailure(e);
107105
}
108106
}
109107

110108
private void resolveUserAndExecute(
111-
User requestedUser,
112109
String detectorId,
113110
RestRequest.Method method,
114111
ActionListener<IndexAnomalyDetectorResponse> listener,
115112
Consumer<AnomalyDetector> function
116113
) {
117114
try {
118-
// Check if user has backend roles
119-
// When filter by is enabled, block users creating/updating detectors who do not have backend roles.
120-
if (filterByEnabled) {
121-
String error = checkFilterByBackendRoles(requestedUser);
122-
if (error != null) {
123-
listener.onFailure(new TimeSeriesException(error));
124-
return;
125-
}
126-
}
115+
127116
if (method == RestRequest.Method.PUT) {
128-
// requestedUser == null means security is disabled or user is superadmin. In this case we don't need to
129-
// check if request user have access to the detector or not. But we still need to get current detector for
130-
// this case, so we can keep current detector's user data.
131-
boolean filterByBackendRole = requestedUser == null ? false : filterByEnabled;
132117
// Update detector request, check if user has permissions to update the detector
133118
// Get detector and verify backend roles
134-
getConfig(
135-
requestedUser,
136-
detectorId,
137-
listener,
138-
function,
139-
client,
140-
clusterService,
141-
xContentRegistry,
142-
filterByBackendRole,
143-
AnomalyDetector.class
144-
);
119+
getConfig(detectorId, listener, function, client, clusterService, xContentRegistry, AnomalyDetector.class);
145120
} else {
146121
// Create Detector. No need to get current detector.
147122
function.accept(null);
@@ -175,6 +150,8 @@ protected void adExecute(
175150
checkIndicesAndExecute(detector.getIndices(), () -> {
176151
// Don't replace detector's user when update detector
177152
// Github issue: https://github.com/opensearch-project/anomaly-detection/issues/124
153+
// TODO this and similar code should be updated to remove reference to a user
154+
178155
User detectorUser = currentDetector == null ? user : currentDetector.getUser();
179156
IndexAnomalyDetectorActionHandler indexAnomalyDetectorActionHandler = new IndexAnomalyDetectorActionHandler(
180157
clusterService,

src/main/java/org/opensearch/ad/transport/PreviewAnomalyDetectorTransportAction.java

-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import org.opensearch.common.inject.Inject;
4444
import org.opensearch.common.settings.Settings;
4545
import org.opensearch.common.util.concurrent.ThreadContext;
46-
import org.opensearch.commons.authuser.User;
4746
import org.opensearch.core.action.ActionListener;
4847
import org.opensearch.core.rest.RestStatus;
4948
import org.opensearch.core.xcontent.NamedXContentRegistry;
@@ -55,7 +54,6 @@
5554
import org.opensearch.timeseries.common.exception.TimeSeriesException;
5655
import org.opensearch.timeseries.constant.CommonMessages;
5756
import org.opensearch.timeseries.constant.CommonName;
58-
import org.opensearch.timeseries.util.ParseUtils;
5957
import org.opensearch.timeseries.util.RestHandlerUtils;
6058
import org.opensearch.transport.TransportService;
6159

@@ -103,13 +101,10 @@ protected void doExecute(
103101
ActionListener<PreviewAnomalyDetectorResponse> actionListener
104102
) {
105103
String detectorId = request.getId();
106-
User user = ParseUtils.getUserContext(client);
107104
ActionListener<PreviewAnomalyDetectorResponse> listener = wrapRestActionListener(actionListener, FAIL_TO_PREVIEW_DETECTOR);
108105
try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) {
109106
resolveUserAndExecute(
110-
user,
111107
detectorId,
112-
filterByEnabled,
113108
listener,
114109
(anomalyDetector) -> previewExecute(request, context, listener),
115110
client,

src/main/java/org/opensearch/forecast/transport/ForecastRunOnceTransportAction.java

-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.opensearch.common.settings.Settings;
3333
import org.opensearch.common.unit.TimeValue;
3434
import org.opensearch.common.util.concurrent.ThreadContext;
35-
import org.opensearch.commons.authuser.User;
3635
import org.opensearch.core.action.ActionListener;
3736
import org.opensearch.core.xcontent.NamedXContentRegistry;
3837
import org.opensearch.forecast.constant.ForecastCommonMessages;
@@ -71,7 +70,6 @@
7170
import org.opensearch.timeseries.stats.StatNames;
7271
import org.opensearch.timeseries.task.TaskCacheManager;
7372
import org.opensearch.timeseries.transport.ResultProcessor;
74-
import org.opensearch.timeseries.util.ParseUtils;
7573
import org.opensearch.timeseries.util.SecurityClientUtil;
7674
import org.opensearch.transport.TransportService;
7775

@@ -154,13 +152,10 @@ public ForecastRunOnceTransportAction(
154152
@Override
155153
protected void doExecute(Task task, ForecastResultRequest request, ActionListener<ForecastResultResponse> listener) {
156154
String forecastID = request.getConfigId();
157-
User user = ParseUtils.getUserContext(client);
158155
try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) {
159156

160157
resolveUserAndExecute(
161-
user,
162158
forecastID,
163-
filterByEnabled,
164159
listener,
165160
(forecaster) -> executeRunOnce(forecastID, request, listener),
166161
client,

src/main/java/org/opensearch/forecast/transport/IndexForecasterTransportAction.java

+2-30
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
import static org.opensearch.forecast.constant.ForecastCommonMessages.FAIL_TO_CREATE_FORECASTER;
1515
import static org.opensearch.forecast.constant.ForecastCommonMessages.FAIL_TO_UPDATE_FORECASTER;
1616
import static org.opensearch.forecast.settings.ForecastSettings.FORECAST_FILTER_BY_BACKEND_ROLES;
17-
import static org.opensearch.timeseries.util.ParseUtils.checkFilterByBackendRoles;
18-
import static org.opensearch.timeseries.util.ParseUtils.getConfig;
19-
import static org.opensearch.timeseries.util.ParseUtils.getUserContext;
17+
import static org.opensearch.timeseries.util.ParseUtils.*;
2018
import static org.opensearch.timeseries.util.RestHandlerUtils.wrapRestActionListener;
2119

2220
import java.util.List;
@@ -100,7 +98,6 @@ protected void doExecute(Task task, IndexForecasterRequest request, ActionListen
10098
ActionListener<IndexForecasterResponse> listener = wrapRestActionListener(actionListener, errorMessage);
10199
try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) {
102100
resolveUserAndExecute(
103-
user,
104101
forecasterId,
105102
method,
106103
listener,
@@ -113,41 +110,16 @@ protected void doExecute(Task task, IndexForecasterRequest request, ActionListen
113110
}
114111

115112
private void resolveUserAndExecute(
116-
User requestedUser,
117113
String forecasterId,
118114
RestRequest.Method method,
119115
ActionListener<IndexForecasterResponse> listener,
120116
Consumer<Forecaster> function
121117
) {
122118
try {
123-
// requestedUser == null means security is disabled or user is superadmin. In this case we don't need to
124-
// check if request user have access to the forecaster or not. But we still need to get current forecaster for
125-
// this case, so we can keep current forecaster's user data.
126-
boolean filterByBackendRole = requestedUser == null ? false : filterByEnabled;
127-
128-
// Check if user has backend roles
129-
// When filter by is enabled, block users creating/updating detectors who do not have backend roles.
130-
if (filterByEnabled) {
131-
String error = checkFilterByBackendRoles(requestedUser);
132-
if (error != null) {
133-
listener.onFailure(new IllegalArgumentException(error));
134-
return;
135-
}
136-
}
137119
if (method == RestRequest.Method.PUT) {
138120
// Update forecaster request, check if user has permissions to update the forecaster
139121
// Get forecaster and verify backend roles
140-
getConfig(
141-
requestedUser,
142-
forecasterId,
143-
listener,
144-
function,
145-
client,
146-
clusterService,
147-
xContentRegistry,
148-
filterByBackendRole,
149-
Forecaster.class
150-
);
122+
getConfig(forecasterId, listener, function, client, clusterService, xContentRegistry, Forecaster.class);
151123
} else {
152124
// Create Detector. No need to get current detector.
153125
function.accept(null);

src/main/java/org/opensearch/timeseries/TimeSeriesAnalyticsPlugin.java

+65-5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.logging.log4j.LogManager;
4343
import org.apache.logging.log4j.Logger;
4444
import org.opensearch.SpecialPermission;
45+
import org.opensearch.accesscontrol.resources.ResourceService;
4546
import org.opensearch.action.ActionRequest;
4647
import org.opensearch.ad.ADJobProcessor;
4748
import org.opensearch.ad.ADTaskProfileRunner;
@@ -161,6 +162,10 @@
161162
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
162163
import org.opensearch.cluster.node.DiscoveryNodes;
163164
import org.opensearch.cluster.service.ClusterService;
165+
import org.opensearch.common.inject.Inject;
166+
import org.opensearch.common.lifecycle.Lifecycle;
167+
import org.opensearch.common.lifecycle.LifecycleComponent;
168+
import org.opensearch.common.lifecycle.LifecycleListener;
164169
import org.opensearch.common.settings.ClusterSettings;
165170
import org.opensearch.common.settings.IndexScopedSettings;
166171
import org.opensearch.common.settings.Setting;
@@ -267,10 +272,7 @@
267272
import org.opensearch.jobscheduler.spi.ScheduledJobRunner;
268273
import org.opensearch.monitor.jvm.JvmInfo;
269274
import org.opensearch.monitor.jvm.JvmService;
270-
import org.opensearch.plugins.ActionPlugin;
271-
import org.opensearch.plugins.Plugin;
272-
import org.opensearch.plugins.ScriptPlugin;
273-
import org.opensearch.plugins.SystemIndexPlugin;
275+
import org.opensearch.plugins.*;
274276
import org.opensearch.repositories.RepositoriesService;
275277
import org.opensearch.rest.RestController;
276278
import org.opensearch.rest.RestHandler;
@@ -327,7 +329,13 @@
327329
/**
328330
* Entry point of time series analytics plugin.
329331
*/
330-
public class TimeSeriesAnalyticsPlugin extends Plugin implements ActionPlugin, ScriptPlugin, SystemIndexPlugin, JobSchedulerExtension {
332+
public class TimeSeriesAnalyticsPlugin extends Plugin
333+
implements
334+
ActionPlugin,
335+
ScriptPlugin,
336+
SystemIndexPlugin,
337+
JobSchedulerExtension,
338+
ResourcePlugin {
331339

332340
private static final Logger LOG = LogManager.getLogger(TimeSeriesAnalyticsPlugin.class);
333341

@@ -1758,4 +1766,56 @@ public void close() {
17581766
}
17591767
}
17601768
}
1769+
1770+
@Override
1771+
public String getResourceType() {
1772+
return "detectors";
1773+
}
1774+
1775+
@Override
1776+
public String getResourceIndex() {
1777+
return CommonName.CONFIG_INDEX;
1778+
}
1779+
1780+
@Override
1781+
public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() {
1782+
final List<Class<? extends LifecycleComponent>> services = new ArrayList<>(1);
1783+
services.add(GuiceHolder.class);
1784+
return services;
1785+
}
1786+
1787+
public static class GuiceHolder implements LifecycleComponent {
1788+
1789+
private static ResourceService resourceService;
1790+
1791+
@Inject
1792+
public GuiceHolder(final ResourceService resourceService) {
1793+
GuiceHolder.resourceService = resourceService;
1794+
}
1795+
1796+
public static ResourceService getResourceService() {
1797+
return resourceService;
1798+
}
1799+
1800+
@Override
1801+
public void close() {}
1802+
1803+
@Override
1804+
public Lifecycle.State lifecycleState() {
1805+
return null;
1806+
}
1807+
1808+
@Override
1809+
public void addLifecycleListener(LifecycleListener listener) {}
1810+
1811+
@Override
1812+
public void removeLifecycleListener(LifecycleListener listener) {}
1813+
1814+
@Override
1815+
public void start() {}
1816+
1817+
@Override
1818+
public void stop() {}
1819+
1820+
}
17611821
}

0 commit comments

Comments
 (0)