Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Manual Backport to 2.x] Primary setup for Multi-tenancy (#3307) #3366

Merged
merged 2 commits into from
Jan 10, 2025
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
1 change: 0 additions & 1 deletion .github/workflows/CI-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ jobs:
with:
role-to-assume: ${{ secrets.ML_ROLE }}
aws-region: us-west-2

- name: Checkout MLCommons
uses: actions/checkout@v4
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class CommonValue {
public static final String UNDEPLOYED = "undeployed";
public static final String NOT_FOUND = "not_found";

/** The field name containing the tenant id */
public static final String TENANT_ID_FIELD = "tenant_id";

public static final String MASTER_KEY = "master_key";
public static final String CREATE_TIME_FIELD = "create_time";
public static final String LAST_UPDATE_TIME_FIELD = "last_update_time";
Expand Down Expand Up @@ -63,4 +66,5 @@ public class CommonValue {
public static final Version VERSION_2_16_0 = Version.fromString("2.16.0");
public static final Version VERSION_2_17_0 = Version.fromString("2.17.0");
public static final Version VERSION_2_18_0 = Version.fromString("2.18.0");
public static final Version VERSION_2_19_0 = Version.fromString("2.19.0");
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public abstract class AbstractConnector implements Connector {
protected Instant lastUpdateTime;
@Setter
protected ConnectorClientConfig connectorClientConfig;
@Setter
protected String tenantId;

protected Map<String, String> createDecryptedHeaders(Map<String, String> headers) {
if (headers == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public interface Connector extends ToXContentObject, Writeable {

String getName();

String getTenantId();

void setTenantId(String tenantId);

String getProtocol();

void setCreatedTime(Instant createdTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public class Constants {
public static final String AD_TRAINING_DATA_SIZE = "trainingDataSize";
public static final String AD_ANOMALY_SCORE_THRESHOLD = "anomalyScoreThreshold";
public static final String AD_DATE_FORMAT = "dateFormat";
public static final String TENANT_ID_HEADER = "x-tenant-id";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.ml.common.settings;

/**
* Interface for handling settings changes in the OpenSearch ML plugin.
*/
public interface SettingsChangeListener {
/**
* Callback method that gets triggered when the multi-tenancy setting changes.
*
* @param isEnabled A boolean value indicating the new state of the multi-tenancy setting:
* <ul>
* <li><code>true</code> if multi-tenancy is enabled</li>
* <li><code>false</code> if multi-tenancy is disabled</li>
* </ul>
*/
void onMultiTenancyEnabledChanged(boolean isEnabled);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
package org.opensearch.ml.common.transport.connector;

import static org.opensearch.action.ValidateActions.addValidationError;
import static org.opensearch.ml.common.CommonValue.VERSION_2_19_0;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;

import org.opensearch.Version;
import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
Expand All @@ -26,24 +28,34 @@
public class MLConnectorGetRequest extends ActionRequest {

String connectorId;
String tenantId;
boolean returnContent;

@Builder
public MLConnectorGetRequest(String connectorId, boolean returnContent) {
public MLConnectorGetRequest(String connectorId, String tenantId, boolean returnContent) {
this.connectorId = connectorId;
this.tenantId = tenantId;
this.returnContent = returnContent;
}

public MLConnectorGetRequest(StreamInput in) throws IOException {
super(in);
Version streamInputVersion = in.getVersion();
this.connectorId = in.readString();
if (streamInputVersion.onOrAfter(VERSION_2_19_0)) {
this.tenantId = in.readOptionalString();
}
this.returnContent = in.readBoolean();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
Version streamOutputVersion = out.getVersion();
out.writeString(this.connectorId);
if (streamOutputVersion.onOrAfter(VERSION_2_19_0)) {
out.writeOptionalString(this.tenantId);
}
out.writeBoolean(returnContent);
}

Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml-agent.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 2
"schema_version": 3
},
"properties": {
"name": {
Expand Down Expand Up @@ -33,6 +33,9 @@
"is_hidden": {
"type": "boolean"
},
"tenant_id": {
"type": "keyword"
},
"created_time": {
"type": "date",
"format": "strict_date_time||epoch_millis"
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml-config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 4
"schema_version": 5
},
"properties": {
"master_key": {
Expand All @@ -9,6 +9,9 @@
"config_type": {
"type": "keyword"
},
"tenant_id": {
"type": "keyword"
},
"ml_configuration": {
"type": "flat_object"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 3
"schema_version": 4
},
"properties": {
"name": {
Expand Down Expand Up @@ -30,6 +30,9 @@
"client_config": {
"type": "flat_object"
},
"tenant_id": {
"type": "keyword"
},
"actions": {
"type": "flat_object"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 2
"schema_version": 3
},
"properties": {
"name": {
Expand All @@ -21,6 +21,9 @@
"model_group_id": {
"type": "keyword"
},
"tenant_id": {
"type": "keyword"
},
"backend_roles": {
"type": "text",
"fields": {
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml-model.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 11
"schema_version": 12
},
"properties": {
"algorithm": {
Expand Down Expand Up @@ -63,6 +63,9 @@
"is_hidden": {
"type": "boolean"
},
"tenant_id": {
"type": "keyword"
},
"model_config": {
"properties": {
"model_type": {
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml-task.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 3
"schema_version": 4
},
"properties": {
"model_id": {
Expand Down Expand Up @@ -38,6 +38,9 @@
"error": {
"type": "text"
},
"tenant_id": {
"type": "keyword"
},
"is_async": {
"type": "boolean"
},
Expand Down
1 change: 1 addition & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
2 changes: 2 additions & 0 deletions ml-algorithms/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ lombok {
configurations.all {
resolutionStrategy.force 'com.google.protobuf:protobuf-java:3.25.5'
resolutionStrategy.force 'org.apache.commons:commons-compress:1.26.0'
resolutionStrategy.force 'software.amazon.awssdk:bom:2.29.12'
}


jacocoTestReport {
reports {
xml.getRequired().set(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ public void setUp() {
when(metadata.indices()).thenReturn(Map.of(ML_AGENT_INDEX, agentindexMetadata, ML_MEMORY_META_INDEX, memorymetaindexMetadata));
when(agentindexMetadata.mapping()).thenReturn(agentmappingMetadata);
when(memorymetaindexMetadata.mapping()).thenReturn(memorymappingMetadata);
when(agentmappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, Integer.valueOf(2))));
when(memorymappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, Integer.valueOf(2))));
when(agentmappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, 3)));
when(memorymappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, 2)));
settings = Settings.builder().put("test_key", 10).build();
threadContext = new ThreadContext(settings);
when(client.threadPool()).thenReturn(threadPool);
Expand Down
13 changes: 13 additions & 0 deletions plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ dependencies {

implementation group: 'org.opensearch', name: 'opensearch', version: "${opensearch_version}"
implementation "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
// Multi-tenant SDK Client
implementation "org.opensearch:opensearch-remote-metadata-sdk:${opensearch_version}"

implementation "org.opensearch:common-utils:${common_utils_version}"
implementation("com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}")
implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}")
Expand Down Expand Up @@ -336,6 +339,7 @@ jacocoTestCoverageVerification {
check.dependsOn jacocoTestCoverageVerification

configurations.all {
exclude group: "org.jetbrains", module: "annotations"
resolutionStrategy.force 'org.apache.commons:commons-lang3:3.10'
resolutionStrategy.force 'commons-logging:commons-logging:1.2'
resolutionStrategy.force 'org.objenesis:objenesis:3.2'
Expand All @@ -348,6 +352,15 @@ configurations.all {
resolutionStrategy.force 'org.slf4j:slf4j-api:1.7.36'
resolutionStrategy.force 'org.codehaus.plexus:plexus-utils:3.3.0'
exclude group: "org.jetbrains", module: "annotations"
resolutionStrategy.force "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
resolutionStrategy.force "org.apache.httpcomponents.core5:httpcore5:5.3.1"
resolutionStrategy.force "org.apache.httpcomponents.core5:httpcore5-h2:5.3.1"
resolutionStrategy.force "org.apache.httpcomponents.client5:httpclient5:5.4.1"
resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
resolutionStrategy.force "com.fasterxml.jackson.core:jackson-core:${versions.jackson_databind}"
resolutionStrategy.force "org.apache.logging.log4j:log4j-api:2.24.2"
resolutionStrategy.force "org.apache.logging.log4j:log4j-core:2.24.2"
resolutionStrategy.force "jakarta.json:jakarta.json-api:2.1.3"
}

apply plugin: 'com.netflix.nebula.ospackage'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static org.opensearch.ml.utils.MLNodeUtils.createXContentParserFromRegistry;
import static org.opensearch.ml.utils.RestActionUtils.getFetchSourceContext;

import java.util.Objects;

import org.opensearch.OpenSearchStatusException;
import org.opensearch.action.ActionRequest;
import org.opensearch.action.get.GetRequest;
Expand Down Expand Up @@ -65,6 +67,7 @@ public GetConnectorTransportAction(
protected void doExecute(Task task, ActionRequest request, ActionListener<MLConnectorGetResponse> actionListener) {
MLConnectorGetRequest mlConnectorGetRequest = MLConnectorGetRequest.fromActionRequest(request);
String connectorId = mlConnectorGetRequest.getConnectorId();
String tenantId = mlConnectorGetRequest.getTenantId();
FetchSourceContext fetchSourceContext = getFetchSourceContext(mlConnectorGetRequest.isReturnContent());
GetRequest getRequest = new GetRequest(ML_CONNECTOR_INDEX).id(connectorId).fetchSourceContext(fetchSourceContext);
User user = RestActionUtils.getUserContext(client);
Expand All @@ -77,6 +80,15 @@ protected void doExecute(Task task, ActionRequest request, ActionListener<MLConn
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
Connector mlConnector = Connector.createConnector(parser);
mlConnector.removeCredential();
if (!Objects.equals(tenantId, mlConnector.getTenantId())) {
actionListener
.onFailure(
new OpenSearchStatusException(
"You don't have permission to access this connector",
RestStatus.FORBIDDEN
)
);
}
if (connectorAccessControlHelper.hasPermission(user, mlConnector)) {
actionListener.onResponse(MLConnectorGetResponse.builder().mlConnector(mlConnector).build());
} else {
Expand Down
Loading
Loading