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

[POC] [Security Manager Replacement] GraalVM sandboxing #16863

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
43 changes: 43 additions & 0 deletions libs/espresso-sm/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import org.opensearch.gradle.info.BuildParams

apply plugin: 'opensearch.publish'

base {
archivesName = 'esspresso-sm'
}

dependencies {
implementation "org.graalvm.polyglot:polyglot:24.0.2"
implementation "org.graalvm.sdk:nativeimage:24.0.2"
implementation "org.graalvm.sdk:collections:24.0.2"
implementation "org.graalvm.sdk:word:24.0.2"
implementation "org.graalvm.sdk:jniutils:24.0.2"
implementation "org.graalvm.llvm:llvm-api:24.0.2"
implementation "org.graalvm.llvm:llvm-language:24.0.2"
implementation "org.graalvm.llvm:llvm-language-native:24.0.2"
implementation "org.graalvm.llvm:llvm-language-native-resources:24.0.2"
implementation "org.graalvm.llvm:llvm-language-nfi:24.0.2"
implementation "org.graalvm.truffle:truffle-api:24.0.2"
implementation "org.graalvm.truffle:truffle-compiler:24.0.2"
implementation "org.graalvm.truffle:truffle-nfi:24.0.2"
implementation "org.graalvm.truffle:truffle-nfi-libffi:24.0.2"
implementation "org.graalvm.truffle:truffle-runtime:24.0.2"
implementation "org.graalvm.espresso:espresso-language:24.0.2"
implementation "org.graalvm.espresso:espresso-libs-resources-linux-amd64:24.0.2"
implementation "org.graalvm.espresso:espresso-runtime-resources-linux-amd64:24.0.2"
}

tasks.named('forbiddenApisMain').configure {
replaceSignatureFiles 'jdk-signatures'
}
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/collections-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bdb21160902f8c0e7014aa8bdbb37c10546c32d3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2e6b7b0643b7aaafe0a1a730834a3beb66eeabb6
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
14daa53850647f16e505ce23a72c0f2223ac2df0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b35e24aafc0e5e5b62fa112766194ba789d9a75c
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/jniutils-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d9d4eaf0a5c9108f6b8ebc61f19cdf37729ef9f2
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/llvm-api-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4f0cdc3ccd34eea004a5cace48d4edc605dc7a9a
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/llvm-language-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
28ccc9c029b437cddb910c4cdd9d8fe86148d404
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b52c4781fa9fda812011822ff10489190950443f
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b0a849ec57bf3e4fc0f6e92070977ef0bd5f3570
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
52280dc5f2707cdfcb2ee44231869d2b88d47f7d
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/nativeimage-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bb27abb17f5edded52ebfb1dd34782db5a496ce2
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/polyglot-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
c07243f549d629ba443655b45dce715d81d5e29c
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/truffle-api-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8242c9a693858fff8e1c3e27b6e4b32c03251aa2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ea63dc2675fc98798486ea6d47d39a911b416435
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/truffle-nfi-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
92aaaa8349645ed43b08c0b79881db539b831525
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dafcf7e45574d1443cd587b28935c5dde7cd1f5c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6b2cfb06b40fc4f273ed74b3303b21a432ae2e6c
1 change: 1 addition & 0 deletions libs/espresso-sm/licenses/word-24.0.2.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b927dbdc83ba16b3ccafc351a544d3d987124e92
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.opensearch.espresso.sandbox;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotAccess;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.IOAccess;

public class Sandbox {
public static void main(String[] args) throws IOException, InterruptedException {
final String opensearchHome = args[0];
System.out.println("OpenSearch home: " + opensearchHome);

final Engine engine = Engine.newBuilder().build();

System.out.println("Host JVM version: " + Runtime.version());
final String plugin = loadPlugin(opensearchHome, engine);
System.out.println(plugin);
}

public static String loadPlugin(String opensearchHome, Engine engine) throws IOException {
// See please:
// - https://github.com/oracle/graal/issues/10239
// -
// https://github.com/oracle/graal/blob/master/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoOptions.java
// -
// https://github.com/oracle/graal/blob/master/espresso/src/com.oracle.truffle.espresso.launcher/src/com/oracle/truffle/espresso/launcher/EspressoLauncher.java
final Context context = Context.newBuilder("java")
.option("java.JavaHome", "/usr/lib/jvm/java-21-openjdk-amd64/")
.option(
"java.Classpath",
("${opensearchHome}/lib/lucene-core-9.12.0.jar:"
+ "${opensearchHome}/lib/opensearch-cli-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/lib/jackson-core-2.17.2.jar:"
+ "${opensearchHome}/lib/jackson-dataformat-cbor-2.17.2.jar:"
+ "${opensearchHome}/lib/jackson-dataformat-smile-2.17.2.jar:"
+ "${opensearchHome}/lib/jackson-dataformat-yaml-2.17.2.jar:"
+ "${opensearchHome}/lib/snakeyaml-2.1.jar:"
+ "${opensearchHome}/lib/opensearch-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/lib/opensearch-core-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/lib/opensearch-common-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/lib/opensearch-x-content-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/lib/opensearch-secure-sm-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/lib/log4j-core-2.21.0.jar:"
+ "${opensearchHome}/lib/log4j-jul-2.21.0.jar:"
+ "${opensearchHome}/lib/log4j-api-2.21.0.jar:"
+ "${opensearchHome}/plugins/identity-shiro/slf4j-api-1.7.36.jar:"
+ "${opensearchHome}/plugins/identity-shiro/passay-1.6.3.jar:"
+ "${opensearchHome}/plugins/identity-shiro/identity-shiro-3.0.0-SNAPSHOT.jar:"
+ "${opensearchHome}/plugins/identity-shiro/shiro-core-1.13.0.jar").replaceAll(
"[$][{]opensearchHome[}]",
opensearchHome
)
)
.option("java.Properties.java.security.manager", "allow")
.allowNativeAccess(true)
.allowCreateThread(true)
.allowHostAccess(HostAccess.NONE)
.allowIO(IOAccess.NONE)
Comment on lines +63 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while i understand this is POC for the shiro plugin, the sandbox restrictions for a different plugin could differ and we'd need to build and load a different context?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if we set allowHostClassLoading(false), we'd still be able to load the plugin.

Copy link
Collaborator Author

@reta reta Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these settings are only "applicable" to embedded context, and once external JVM is started, all restrictions are off (this is why we need SM here).

You can easily see the confirmation here, in theory allowIO(IOAccess.NONE) would cut any I/O off but it does not, the SM does (if we comment out SM initialization, the socket will be created):

	at <java> getSocket()Ljava/net/ServerSocket;(org/opensearch/identity/shiro/ShiroIdentityPlugin.java:144:0)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while i understand this is POC for the shiro plugin, the sandbox restrictions for a different plugin could differ and we'd need to build and load a different context?

This is out of scope for sandbox but in scope of SM of the spawn JVM process. We apply the same security policy that OpenSearch core does now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha.

We apply the same security policy that OpenSearch core does now.

this also means we cannot apply/use the plugin level security policies?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also means we cannot apply/use the plugin level security policies?

Quite an opposite :-) the OpenSearch core does include all plugin security policies into consideration. So it is kind of bootstrapping OpenSearch core + plugin(s), the policies will be glued together (as it does now)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can easily see the confirmation here, in theory allowIO(IOAccess.NONE) would cut any I/O off but it does not, the SM does (if we comment out SM initialization, the socket will be created):

Thanks for pointing that.

.allowPolyglotAccess(PolyglotAccess.newBuilder().allowBindingsAccess("java").build())
.engine(engine)
.build();

final Value runtime = context.getBindings("java").getMember("java.lang.Runtime");
System.out.println("Polyglot JVM version: " + runtime.invokeMember("version").toString());

final Path homePath = new File(opensearchHome).toPath();
final Path configPath = new File(opensearchHome + "/config").toPath();
context.getBindings("java")
.getMember("org.opensearch.bootstrap.QuickBoostrap")
.invokeMember("bootstrap", configPath.toString(), homePath.toString());

final Value securityManager = context.getBindings("java").getMember("java.lang.System").invokeMember("getSecurityManager");
System.out.println("Security Manager? " + securityManager.toString());

final Value settings = context.getBindings("java").getMember("org.opensearch.common.settings.Settings").getMember("EMPTY");
final Value result = context.getBindings("java").getMember("org.opensearch.identity.shiro.ShiroIdentityPlugin");
final Value instance = result.newInstance(settings);
System.out.println("Shiro Plugin? " + instance.toString());

final Value socket = instance.invokeMember("getSocket");
return socket.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.watcher.ResourceWatcherService;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Supplier;
Expand Down Expand Up @@ -138,6 +140,13 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
}
}

/**
* Deliberately introducing the network access attempt to trigger SecurityException
*/
public ServerSocket getSocket() throws IOException {
return new ServerSocket(0);
}

public PluginSubject getPluginSubject(Plugin plugin) {
return new ShiroPluginSubject(threadPool);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.bootstrap;

import org.opensearch.cli.UserException;
import org.opensearch.common.logging.LogConfigurator;
import org.opensearch.common.settings.Settings;
import org.opensearch.env.Environment;
import org.opensearch.node.InternalSettingsPreparer;
import org.opensearch.node.Node;
import org.opensearch.node.NodeValidationException;

import java.nio.file.Paths;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;

/**
* Quick bootstrap
*/
public class QuickBoostrap {
/**
* This method is invoked by {@link OpenSearch#main(String[])} to startup opensearch.
*/
public static void bootstrap(final String configPath, final String homePath) throws BootstrapException, NodeValidationException,
UserException {

// force the class initializer for BootstrapInfo to run before
// the security manager is installed
BootstrapInfo.init();

Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), homePath).build();
final Environment environment = InternalSettingsPreparer.prepareEnvironment(
settings,
Collections.emptyMap(),
Paths.get(configPath),
// HOSTNAME is set by opensearch-env and opensearch-env.bat so it is always available
() -> System.getenv("HOSTNAME")
);

LogConfigurator.setNodeName(Node.NODE_NAME_SETTING.get(environment.settings()));
try {
LogConfigurator.configure(environment);
} catch (IOException e) {
throw new BootstrapException(e);
}

try {
Security.configure(environment, BootstrapSettings.SECURITY_FILTER_BAD_DEFAULTS_SETTING.get(settings));
} catch (IOException | NoSuchAlgorithmException e) {
throw new BootstrapException(e);
}
}
}
Loading