Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a5ded4b
Introduces resource sharing model as a feature flag
DarshitChanpura Jan 20, 2025
f965695
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Feb 26, 2025
914a58f
Introduces resource sharing model as a feature flag
DarshitChanpura Mar 9, 2025
4f2481d
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Mar 9, 2025
e69f55b
Updates build.gradle
DarshitChanpura Mar 17, 2025
916210c
Updates method signature to conform to client
DarshitChanpura Mar 18, 2025
10165b2
Resolves jar hell
DarshitChanpura Mar 18, 2025
2cba59e
Updates gradle dependency and conforms to changes in client
DarshitChanpura Mar 31, 2025
68a2a31
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Mar 31, 2025
88a1fce
Upgrades qualifier to beta1
DarshitChanpura Mar 31, 2025
f2ef874
Updates to security SPI changes
DarshitChanpura Apr 3, 2025
64096e7
Registers forecaster as resource
DarshitChanpura Apr 7, 2025
fbee2ed
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Apr 7, 2025
9e2169b
Consumes feature flag exposed by SPI
DarshitChanpura Apr 8, 2025
6db57cb
Aligns with main
DarshitChanpura Apr 8, 2025
c25b75c
Adds changelog entry
DarshitChanpura Apr 8, 2025
d15e648
Adds todo to remove feature flag once feature is GA
DarshitChanpura Apr 8, 2025
bcedab4
Addresses PR comments
DarshitChanpura Apr 9, 2025
c582233
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Apr 14, 2025
dfa9cd9
Adds filterByBackendRole control to resource sharing feature
DarshitChanpura Apr 14, 2025
bbbe15c
Fixes a compilation and a runtime bug
DarshitChanpura Apr 14, 2025
bed1788
Fix getName
DarshitChanpura Apr 14, 2025
83aab0e
Skips verification of permission on creation
DarshitChanpura Apr 15, 2025
000eb52
Conforms to SPI changes
DarshitChanpura Apr 15, 2025
9cb1555
Conforms to SPI changes in security plugin
DarshitChanpura Apr 16, 2025
27b4d3a
Address PR comments
DarshitChanpura Apr 21, 2025
e165cce
Reflects changes in SPI
DarshitChanpura Apr 22, 2025
db46191
Fix index name
DarshitChanpura Apr 24, 2025
e088b60
Adds integ tests when resource-sharing feature-flag is enabled
DarshitChanpura Apr 24, 2025
2f16d58
Refactored unwanted changes
DarshitChanpura Apr 24, 2025
a5b7f49
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Apr 25, 2025
90aefe5
Fixes onResponse in actionHandler
DarshitChanpura Apr 26, 2025
438e0d2
Adds unit tests for resource-sharing related files and updates secure…
DarshitChanpura Apr 29, 2025
b43877f
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura Apr 29, 2025
a013a36
Merge remote-tracking branch 'upstream/main' into intro-resource-perm…
DarshitChanpura May 21, 2025
8ec0d53
Adds a new test flow that runs with resource sharing feature enabled
DarshitChanpura May 21, 2025
9cbd07f
Uses correct method name
DarshitChanpura May 21, 2025
3bf6b16
Reverts to implementation
DarshitChanpura May 21, 2025
9091a4f
Fix 409 when running feature enabled tests
DarshitChanpura May 21, 2025
7e3eaf3
Fix coverage for AbstractTimeSeriesActionHandler
DarshitChanpura May 23, 2025
a4c97c8
Fix coverage for BaseDeleteConfigTransportAction
DarshitChanpura May 23, 2025
f891de8
Cleans SecureRestADIT test class
DarshitChanpura May 23, 2025
e830f18
Parallelize security tests workflow
DarshitChanpura May 23, 2025
8291654
Boost BaseDeleteConfigTransportAction coverage
DarshitChanpura May 23, 2025
e576417
Rename method
DarshitChanpura May 29, 2025
6069238
Clarifies enable and disable filter by scenarios for create detector …
DarshitChanpura Jun 3, 2025
7e99253
Pass concrete configIndex name via constructor
DarshitChanpura Jun 3, 2025
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
31 changes: 19 additions & 12 deletions .github/workflows/test_security.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Security test workflow
# This workflow is triggered on pull requests to main
# This workflow is triggered on pull requests or pushes to any branch
on:
pull_request:
branches:
Expand All @@ -16,34 +16,41 @@ jobs:

security-test:
needs: Get-CI-Image-Tag
# This job runs on Linux
runs-on: ubuntu-latest
container:
# using the same image which is used by opensearch-build team to build the OpenSearch Distribution
# this image tag is subject to change as more dependencies and updates will arrive over time
image: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-version-linux }}
options: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-options }}

strategy:
matrix:
# empty string = disabled, flag = enabled
resource_sharing_flag: ["", "-Dresource_sharing.enabled=true"]

steps:
- name: Run start commands
run: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-command }}
# This step uses the setup-java Github action: https://github.com/actions/setup-java

- name: Set Up JDK
uses: actions/setup-java@v4
with:
distribution: temurin # Temurin is a distribution of adoptium
distribution: temurin
java-version: 21
# index-management

- name: Checkout Branch
uses: actions/checkout@v4

- name: Run integration tests
run: |
chown -R 1000:1000 `pwd`
su `id -un 1000` -c "./gradlew integTest -Dsecurity=true -Dhttps=true --tests '*IT'"
su `id -un 1000` -c "./gradlew integTest \
-Dsecurity=true \
-Dhttps=true \
${{ matrix.resource_sharing_flag }} \
--tests '*IT'"

- name: Upload failed logs
uses: actions/upload-artifact@v4
if: failure()
uses: actions/upload-artifact@v4
with:
name: logs
overwrite: 'true'
path: build/testclusters/integTest-*/logs/*
name: logs-${{ matrix.resource_sharing_flag != '' && 'resource-sharing' || 'no-resource-sharing' }}
path: build/testclusters/integTest-*/logs/*
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
## [Unreleased 3.x](https://github.com/opensearch-project/anomaly-detection/compare/3.0...HEAD)
### Features
### Enhancements
- Use Centralized Resource Access Control framework provided by security plugin ([#1400](https://github.com/opensearch-project/anomaly-detection/pull/1400))
### Bug Fixes
### Infrastructure
### Documentation
Expand Down
14 changes: 9 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,14 @@ configurations {
}

dependencies {
compileOnly group: 'org.opensearch', name:'opensearch-security-spi', version:"${opensearch_build}"
implementation "org.opensearch:opensearch:${opensearch_version}"
compileOnly "org.opensearch.plugin:opensearch-scripting-painless-spi:${opensearch_version}"
compileOnly "org.opensearch:opensearch-job-scheduler-spi:${job_scheduler_version}"
implementation "org.opensearch:common-utils:${common_utils_version}"
implementation "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
implementation group: 'com.google.guava', name: 'guava', version:'32.1.3-jre'
implementation group: 'com.google.guava', name: 'failureaccess', version:'1.0.2'
implementation group: 'com.google.guava', name: 'guava', version:'33.4.5-jre'
implementation group: 'com.google.guava', name: 'failureaccess', version:'1.0.3'
implementation group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.11.0'
implementation group: 'com.yahoo.datasketches', name: 'sketches-core', version: '0.13.4'
Expand Down Expand Up @@ -241,7 +242,7 @@ opensearchplugin {
name 'opensearch-anomaly-detection'
description 'OpenSearch anomaly detector plugin'
classname 'org.opensearch.timeseries.TimeSeriesAnalyticsPlugin'
extendedPlugins = ['lang-painless', 'opensearch-job-scheduler']
extendedPlugins = ['lang-painless', 'opensearch-job-scheduler', 'opensearch-security;optional=true']
}

// Handle case where older versions of esplugin doesn't expose the joda time version it uses
Expand All @@ -261,9 +262,10 @@ configurations.all {
force "com.google.code.gson:gson:2.11.0"
force "junit:junit:4.13.2"

force "com.google.guava:guava:32.1.3-jre" // CVE for 31.1
force "com.google.guava:guava:33.4.5-jre" // CVE for 31.1
force("com.fasterxml.jackson.core:jackson-core:${jacksonVersion}")
force "org.eclipse.platform:org.eclipse.core.runtime:3.29.0" // CVE for < 3.29.0
force "org.ow2.asm:asm:9.7.1"
}
}

Expand Down Expand Up @@ -547,7 +549,9 @@ def configureClusterPlugins(cluster, jobSchedulerFile, securityPluginFile, secur
node.setting("plugins.security.check_snapshot_restore_write_privileges", "true")
node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]")
node.setting("plugins.security.system_indices.enabled", "true")
// node.setting("plugins.security.system_indices.indices", "[\".opendistro-ism-config\"]")
if (System.getProperty("resource_sharing.enabled") == "true") {
node.setting("plugins.security.experimental.resource_sharing.enabled", "true")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public AnomalyDetectorJobTransportAction(
FAIL_TO_START_DETECTOR,
FAIL_TO_STOP_DETECTOR,
AnomalyDetector.class,
adIndexJobActionHandler
adIndexJobActionHandler,
ADIndex.CONFIG.getIndexName()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public DeleteAnomalyDetectorTransportAction(
AnalysisType.AD,
ADCommonName.DETECTION_STATE_INDEX,
AnomalyDetector.class,
ADTaskType.HISTORICAL_DETECTOR_TASK_TYPES
ADTaskType.HISTORICAL_DETECTOR_TASK_TYPES,
ADIndex.CONFIG.getIndexName()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public GetAnomalyDetectorTransportAction(
ADTaskType.HISTORICAL_HC_DETECTOR.name(),
ADTaskType.HISTORICAL_SINGLE_ENTITY.name(),
AnomalyDetectorSettings.AD_FILTER_BY_BACKEND_ROLES,
adTaskProfileRunner
adTaskProfileRunner,
ADIndex.CONFIG.getIndexName()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import static org.opensearch.ad.settings.AnomalyDetectorSettings.AD_FILTER_BY_BACKEND_ROLES;
import static org.opensearch.timeseries.util.ParseUtils.checkFilterByBackendRoles;
import static org.opensearch.timeseries.util.ParseUtils.getConfig;
import static org.opensearch.timeseries.util.ParseUtils.shouldUseResourceAuthz;
import static org.opensearch.timeseries.util.ParseUtils.verifyResourceAccessAndProcessRequest;
import static org.opensearch.timeseries.util.RestHandlerUtils.wrapRestActionListener;

import java.util.List;
Expand All @@ -27,6 +29,7 @@
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.HandledTransportAction;
import org.opensearch.action.support.WriteRequest;
import org.opensearch.ad.indices.ADIndex;
import org.opensearch.ad.indices.ADIndexManagement;
import org.opensearch.ad.model.AnomalyDetector;
import org.opensearch.ad.rest.handler.IndexAnomalyDetectorActionHandler;
Expand Down Expand Up @@ -99,8 +102,35 @@ protected void doExecute(Task task, IndexAnomalyDetectorRequest request, ActionL
RestRequest.Method method = request.getMethod();
String errorMessage = method == RestRequest.Method.PUT ? FAIL_TO_UPDATE_DETECTOR : FAIL_TO_CREATE_DETECTOR;
ActionListener<IndexAnomalyDetectorResponse> listener = wrapRestActionListener(actionListener, errorMessage);

// TODO: Remove following and any other conditional check, post GA for Resource Authz.
boolean shouldEvaluateWithNewAuthz = shouldUseResourceAuthz(settings);

try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) {
resolveUserAndExecute(user, detectorId, method, listener, (detector) -> adExecute(request, user, detector, context, listener));
verifyResourceAccessAndProcessRequest(
user,
ADIndex.CONFIG.getIndexName(),
detectorId,
shouldEvaluateWithNewAuthz,
listener,
args -> indexDetector(
user,
detectorId,
method,
listener,
detector -> adExecute(request, user, detector, context, listener)
),
new Object[] {},
(fallbackArgs) -> resolveUserAndExecute(
user,
detectorId,
method,
listener,
(detector) -> adExecute(request, user, detector, context, listener)
),
new Object[] {}
);

} catch (Exception e) {
LOG.error(e);
listener.onFailure(e);
Expand All @@ -124,33 +154,44 @@ private void resolveUserAndExecute(
return;
}
}
if (method == RestRequest.Method.PUT) {
// requestedUser == null means security is disabled or user is superadmin. In this case we don't need to
// check if request user have access to the detector or not. But we still need to get current detector for
// this case, so we can keep current detector's user data.
boolean filterByBackendRole = requestedUser == null ? false : filterByEnabled;
// Update detector request, check if user has permissions to update the detector
// Get detector and verify backend roles
getConfig(
requestedUser,
detectorId,
listener,
function,
client,
clusterService,
xContentRegistry,
filterByBackendRole,
AnomalyDetector.class
);
} else {
// Create Detector. No need to get current detector.
function.accept(null);
}

indexDetector(requestedUser, detectorId, method, listener, function);
} catch (Exception e) {
listener.onFailure(e);
}
}

private void indexDetector(
User requestedUser,
String detectorId,
RestRequest.Method method,
ActionListener<IndexAnomalyDetectorResponse> listener,
Consumer<AnomalyDetector> function
) {
if (method == RestRequest.Method.PUT) {
// requestedUser == null means security is disabled or user is superadmin. In this case we don't need to
// check if request user have access to the detector or not. But we still need to get current detector for
// this case, so we can keep current detector's user data.
boolean filterByBackendRole = requestedUser == null ? false : filterByEnabled;
// Update detector request, check if user has permissions to update the detector
// Get detector and verify backend roles
getConfig(
requestedUser,
detectorId,
listener,
function,
client,
clusterService,
xContentRegistry,
filterByBackendRole,
AnomalyDetector.class
);
} else {
// Create Detector. No need to get current detector.
function.accept(null);
}
}

protected void adExecute(
IndexAnomalyDetectorRequest request,
User user,
Expand All @@ -175,6 +216,8 @@ protected void adExecute(
checkIndicesAndExecute(detector.getIndices(), () -> {
// Don't replace detector's user when update detector
// Github issue: https://github.com/opensearch-project/anomaly-detection/issues/124
// TODO this and similar code should be updated to remove reference to a user

User detectorUser = currentDetector == null ? user : currentDetector.getUser();
IndexAnomalyDetectorActionHandler indexAnomalyDetectorActionHandler = new IndexAnomalyDetectorActionHandler(
clusterService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import static org.opensearch.ad.settings.AnomalyDetectorSettings.MAX_CONCURRENT_PREVIEW;
import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.opensearch.timeseries.util.ParseUtils.resolveUserAndExecute;
import static org.opensearch.timeseries.util.ParseUtils.shouldUseResourceAuthz;
import static org.opensearch.timeseries.util.ParseUtils.verifyResourceAccessAndProcessRequest;
import static org.opensearch.timeseries.util.RestHandlerUtils.wrapRestActionListener;

import java.io.IOException;
Expand All @@ -34,6 +36,7 @@
import org.opensearch.action.support.HandledTransportAction;
import org.opensearch.ad.AnomalyDetectorRunner;
import org.opensearch.ad.constant.ADCommonMessages;
import org.opensearch.ad.indices.ADIndex;
import org.opensearch.ad.model.AnomalyDetector;
import org.opensearch.ad.model.AnomalyResult;
import org.opensearch.ad.settings.AnomalyDetectorSettings;
Expand Down Expand Up @@ -65,6 +68,7 @@ public class PreviewAnomalyDetectorTransportAction extends
private final AnomalyDetectorRunner anomalyDetectorRunner;
private final ClusterService clusterService;
private final Client client;
private final Settings settings;
private final NamedXContentRegistry xContentRegistry;
private volatile Integer maxAnomalyFeatures;
private volatile Boolean filterByEnabled;
Expand All @@ -85,6 +89,7 @@ public PreviewAnomalyDetectorTransportAction(
super(PreviewAnomalyDetectorAction.NAME, transportService, actionFilters, PreviewAnomalyDetectorRequest::new);
this.clusterService = clusterService;
this.client = client;
this.settings = settings;
this.anomalyDetectorRunner = anomalyDetectorRunner;
this.xContentRegistry = xContentRegistry;
maxAnomalyFeatures = MAX_ANOMALY_FEATURES.get(settings);
Expand All @@ -105,18 +110,34 @@ protected void doExecute(
String detectorId = request.getId();
User user = ParseUtils.getUserContext(client);
ActionListener<PreviewAnomalyDetectorResponse> listener = wrapRestActionListener(actionListener, FAIL_TO_PREVIEW_DETECTOR);

// TODO: Remove following and any other conditional check, post GA for Resource Authz.
boolean shouldEvaluateWithNewAuthz = shouldUseResourceAuthz(settings);

try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) {
resolveUserAndExecute(
// Call the verifyResourceAccessAndProcessRequest method
verifyResourceAccessAndProcessRequest(
user,
ADIndex.CONFIG.getIndexName(),
detectorId,
filterByEnabled,
shouldEvaluateWithNewAuthz,
listener,
(anomalyDetector) -> previewExecute(request, context, listener),
client,
clusterService,
xContentRegistry,
AnomalyDetector.class
args -> previewExecute(request, context, listener),
new Object[] {},
(fallbackArgs) -> resolveUserAndExecute(
user,
detectorId,
filterByEnabled,
listener,
ad -> previewExecute(request, context, listener),
client,
clusterService,
xContentRegistry,
AnomalyDetector.class
),
new Object[] {}
);

} catch (Exception e) {
logger.error(e);
listener.onFailure(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public DeleteForecasterTransportAction(
AnalysisType.FORECAST,
ForecastIndex.STATE.getIndexName(),
Forecaster.class,
ForecastTaskType.RUN_ONCE_TASK_TYPES
ForecastTaskType.RUN_ONCE_TASK_TYPES,
ForecastIndex.CONFIG.getIndexName()
);
}
}
Loading
Loading