You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2019/12/09 06:55:23 UTC
[ignite] branch master updated: IGNITE-11410 Sandbox for
user-defined code - Fixes #6707.
This is an automated email from the ASF dual-hosted git repository.
alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 3ac9a21 IGNITE-11410 Sandbox for user-defined code - Fixes #6707.
3ac9a21 is described below
commit 3ac9a210d0e720e6e1daeda6f4d13296f7b149b8
Author: d.garus <ga...@gmail.com>
AuthorDate: Mon Dec 9 09:35:06 2019 +0300
IGNITE-11410 Sandbox for user-defined code - Fixes #6707.
Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
.../internal/cluster/ClusterGroupAdapter.java | 12 +-
.../deployment/GridDeploymentClassLoader.java | 17 +-
.../processors/cache/GridCacheAdapter.java | 16 +-
.../processors/cache/GridCacheMapEntry.java | 4 +-
.../cache/distributed/dht/GridDhtCacheAdapter.java | 12 +-
.../cache/query/GridCacheQueryManager.java | 7 +-
.../datastreamer/DataStreamerUpdateJob.java | 13 +-
.../internal/processors/job/GridJobWorker.java | 3 +
.../processors/resource/GridResourceProcessor.java | 12 ++
.../GridResourceProxiedIgniteInjector.java | 88 ++++++++
.../processors/security/GridSecurityProcessor.java | 12 ++
.../processors/security/IgniteSecurity.java | 11 +-
.../security/IgniteSecurityProcessor.java | 25 +++
.../security/NoOpIgniteSecurityProcessor.java | 11 +
.../processors/security/SecurityUtils.java | 122 +++++++++++
.../security/sandbox/AccessControllerSandbox.java | 74 +++++++
.../security/sandbox/IgniteDomainCombiner.java | 52 +++++
.../processors/security/sandbox/IgniteSandbox.java | 46 ++++
.../processors/security/sandbox/NoOpSandbox.java | 36 ++++
.../sandbox/SandboxIgniteComponentProxy.java | 110 ++++++++++
.../ignite/plugin/security/SecuritySubject.java | 12 ++
.../processors/security/AbstractSecurityTest.java | 107 +++++++++-
.../CacheLoadRemoteSecurityContextCheckTest.java | 43 +---
.../compute/ComputePermissionCheckTest.java | 53 +----
.../processors/security/impl/TestSecurityData.java | 22 +-
.../security/impl/TestSecurityPluginProvider.java | 17 +-
.../security/impl/TestSecurityProcessor.java | 21 +-
.../security/impl/TestSecuritySubject.java | 16 ++
.../security/sandbox/AbstractSandboxTest.java | 128 +++++++++++
.../security/sandbox/CacheSandboxTest.java | 134 ++++++++++++
.../security/sandbox/ComputeSandboxTest.java | 146 +++++++++++++
.../security/sandbox/DataStreamerSandboxTest.java | 57 +++++
.../sandbox/DoPrivilegedOnRemoteNodeTest.java | 180 ++++++++++++++++
.../sandbox/IgniteOperationsInsideSandboxTest.java | 234 +++++++++++++++++++++
.../sandbox/SecuritySubjectPermissionsTest.java | 132 ++++++++++++
.../ignite/testsuites/IgniteBasicTestSuite.java | 2 -
.../ignite/testsuites/SecurityTestSuite.java | 15 +-
37 files changed, 1877 insertions(+), 125 deletions(-)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cluster/ClusterGroupAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/cluster/ClusterGroupAdapter.java
index e5bb47d..7579ea2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cluster/ClusterGroupAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cluster/ClusterGroupAdapter.java
@@ -769,8 +769,7 @@ public class ClusterGroupAdapter implements ClusterGroupEx, Externalizable {
private boolean clients;
/** Injected Ignite instance. */
- @IgniteInstanceResource
- private transient Ignite ignite;
+ private transient IgniteKernal ignite;
/**
* @param cacheName Cache name.
@@ -785,7 +784,7 @@ public class ClusterGroupAdapter implements ClusterGroupEx, Externalizable {
/** {@inheritDoc} */
@SuppressWarnings("RedundantIfStatement")
@Override public boolean apply(ClusterNode n) {
- GridDiscoveryManager disco = ((IgniteKernal)ignite).context().discovery();
+ GridDiscoveryManager disco = ignite.context().discovery();
if (affNodes && disco.cacheAffinityNode(n, cacheName))
return true;
@@ -801,6 +800,13 @@ public class ClusterGroupAdapter implements ClusterGroupEx, Externalizable {
return false;
}
+
+ /** */
+ @IgniteInstanceResource
+ private void ignite(Ignite ignite) {
+ if (ignite != null)
+ this.ignite = ignite instanceof IgniteKernal ? (IgniteKernal)ignite : IgnitionEx.gridx(ignite.name());
+ }
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentClassLoader.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentClassLoader.java
index 346c4d2..29fb531 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentClassLoader.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentClassLoader.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.managers.deployment;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -54,6 +56,17 @@ import org.jetbrains.annotations.Nullable;
*/
@SuppressWarnings({"CustomClassloader"})
class GridDeploymentClassLoader extends ClassLoader implements GridDeploymentInfo {
+ /** */
+ private static final ProtectionDomain PROTECTION_DOMAIN;
+
+ static {
+ Permissions perms = new Permissions();
+
+ perms.setReadOnly();
+
+ PROTECTION_DOMAIN = new ProtectionDomain(null, perms);
+ }
+
/** Class loader ID. */
private final IgniteUuid id;
@@ -520,7 +533,9 @@ class GridDeploymentClassLoader extends ClassLoader implements GridDeploymentInf
if (byteMap != null)
byteMap.put(path, byteSrc.array());
- cls = defineClass(name, byteSrc.internalArray(), 0, byteSrc.size());
+ cls = ctx.security().sandbox().enabled()
+ ? defineClass(name, byteSrc.internalArray(), 0, byteSrc.size(), PROTECTION_DOMAIN)
+ : defineClass(name, byteSrc.internalArray(), 0, byteSrc.size());
/* Define package in classloader. See URLClassLoader.defineClass(). */
int i = name.lastIndexOf('.');
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
index 020b3a2..2789357 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
@@ -6687,8 +6687,7 @@ public abstract class GridCacheAdapter<K, V> implements IgniteInternalCache<K, V
protected ComputeJobContext jobCtx;
/** Injected grid instance. */
- @IgniteInstanceResource
- protected Ignite ignite;
+ protected IgniteEx ignite;
/** Affinity topology version. */
protected final AffinityTopologyVersion topVer;
@@ -6712,7 +6711,7 @@ public abstract class GridCacheAdapter<K, V> implements IgniteInternalCache<K, V
if (!waitAffinityReadyFuture())
return null;
- IgniteInternalCache cache = ((IgniteEx)ignite).context().cache().cache(cacheName);
+ IgniteInternalCache cache = ignite.context().cache().cache(cacheName);
return localExecute(cache);
}
@@ -6729,7 +6728,7 @@ public abstract class GridCacheAdapter<K, V> implements IgniteInternalCache<K, V
* @return {@code True} if topology check passed.
*/
private boolean waitAffinityReadyFuture() {
- GridCacheProcessor cacheProc = ((IgniteEx)ignite).context().cache();
+ GridCacheProcessor cacheProc = ignite.context().cache();
AffinityTopologyVersion locTopVer = cacheProc.context().exchange().readyAffinityVersion();
@@ -6741,7 +6740,7 @@ public abstract class GridCacheAdapter<K, V> implements IgniteInternalCache<K, V
fut.listen(new CI1<IgniteInternalFuture<?>>() {
@Override public void apply(IgniteInternalFuture<?> t) {
- ((IgniteEx)ignite).context().closure().runLocalSafe(new Runnable() {
+ ignite.context().closure().runLocalSafe(new Runnable() {
@Override public void run() {
jobCtx.callcc();
}
@@ -6755,6 +6754,13 @@ public abstract class GridCacheAdapter<K, V> implements IgniteInternalCache<K, V
return true;
}
+
+ /** */
+ @IgniteInstanceResource
+ private void ignite(Ignite ignite) {
+ if (ignite != null)
+ this.ignite = ignite instanceof IgniteEx ? (IgniteEx)ignite : IgnitionEx.gridx(ignite.name());
+ }
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
index a064503..b55c121 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
@@ -81,6 +81,7 @@ import org.apache.ignite.internal.processors.cache.version.GridCacheVersionedEnt
import org.apache.ignite.internal.processors.dr.GridDrType;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheFilter;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorClosure;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
import org.apache.ignite.internal.transactions.IgniteTxDuplicateKeyCheckedException;
import org.apache.ignite.internal.transactions.IgniteTxSerializationCheckedException;
import org.apache.ignite.internal.util.IgniteTree;
@@ -6693,7 +6694,8 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
* @return Entry processor return value.
*/
private IgniteBiTuple<Object, Exception> runEntryProcessor(CacheInvokeEntry<Object, Object> invokeEntry) {
- EntryProcessor<Object, Object, ?> entryProcessor = (EntryProcessor<Object, Object, ?>)writeObj;
+ EntryProcessor<Object, Object, ?> entryProcessor = SecurityUtils.sandboxedProxy(
+ entry.context().kernalContext(), EntryProcessor.class, (EntryProcessor<Object, Object, ?>)writeObj);
IgniteThread.onEntryProcessorEntered(true);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java
index da03d00..139c67c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java
@@ -75,6 +75,7 @@ import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.processors.platform.cache.PlatformCacheEntryFilter;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
import org.apache.ignite.internal.util.future.GridCompoundFuture;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
import org.apache.ignite.internal.util.typedef.CI1;
@@ -624,15 +625,22 @@ public abstract class GridDhtCacheAdapter<K, V> extends GridDistributedCacheAdap
final ExpiryPolicy plc = plc0 != null ? plc0 : ctx.expiry();
- if (p != null)
+ final IgniteBiPredicate<K, V> pred;
+
+ if (p != null) {
ctx.kernalContext().resource().injectGeneric(p);
+ pred = SecurityUtils.sandboxedProxy(ctx.kernalContext(), IgniteBiPredicate.class, p);
+ }
+ else
+ pred = null;
+
try {
ctx.store().loadCache(new CI3<KeyCacheObject, Object, GridCacheVersion>() {
@Override public void apply(KeyCacheObject key, Object val, @Nullable GridCacheVersion ver) {
assert ver == null;
- loadEntry(key, val, ver0, p, topVer, replicate, plc);
+ loadEntry(key, val, ver0, pred, topVer, replicate, plc);
}
}, args);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
index 39ce35b9..298841d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
@@ -88,6 +88,7 @@ import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
import org.apache.ignite.internal.processors.query.GridQueryProcessor;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.util.GridBoundedPriorityQueue;
import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
@@ -843,8 +844,10 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
qry.mvccSnapshot(), qry.isDataPageScanEnabled());
}
- ScanQueryIterator iter = new ScanQueryIterator(it, qry, topVer, locPart, keyValFilter, transformer, locNode,
- locNode ? locIters : null, cctx, log);
+ ScanQueryIterator iter = new ScanQueryIterator(it, qry, topVer, locPart,
+ SecurityUtils.sandboxedProxy(cctx.kernalContext(), IgniteBiPredicate.class, keyValFilter),
+ SecurityUtils.sandboxedProxy(cctx.kernalContext(), IgniteClosure.class, transformer),
+ locNode, locNode ? locIters : null, cctx, log);
if (locNode) {
ScanQueryIterator old = locIters.addx(iter);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerUpdateJob.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerUpdateJob.java
index ef9ef2d..4628a11 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerUpdateJob.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerUpdateJob.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.cache.CacheObject;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
import org.apache.ignite.internal.util.lang.GridPlainCallable;
import org.apache.ignite.internal.util.typedef.C1;
import org.apache.ignite.internal.util.typedef.F;
@@ -127,6 +128,8 @@ class DataStreamerUpdateJob implements GridPlainCallable<Object> {
checkSecurityPermission(SecurityPermission.CACHE_REMOVE);
}
+ StreamReceiver receiver = SecurityUtils.sandboxedProxy(ctx, StreamReceiver.class, rcvr);
+
if (unwrapEntries()) {
Collection<Map.Entry> col0 = F.viewReadOnly(col, new C1<DataStreamerEntry, Map.Entry>() {
@Override public Map.Entry apply(DataStreamerEntry e) {
@@ -134,10 +137,10 @@ class DataStreamerUpdateJob implements GridPlainCallable<Object> {
}
});
- rcvr.receive(cache, col0);
+ receiver.receive(cache, col0);
}
else
- rcvr.receive(cache, col);
+ receiver.receive(cache, col);
return null;
}
@@ -163,9 +166,7 @@ class DataStreamerUpdateJob implements GridPlainCallable<Object> {
*/
private void checkSecurityPermission(SecurityPermission perm)
throws org.apache.ignite.plugin.security.SecurityException {
- if (!ctx.security().enabled())
- return;
-
- ctx.security().authorize(cacheName, perm);
+ if (ctx.security().enabled())
+ ctx.security().authorize(cacheName, perm);
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java
index 90e54e5..baf75ae 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java
@@ -53,6 +53,7 @@ import org.apache.ignite.internal.processors.cache.distributed.dht.GridReservabl
import org.apache.ignite.internal.processors.query.GridQueryProcessor;
import org.apache.ignite.internal.processors.security.OperationSecurityContext;
import org.apache.ignite.internal.processors.security.SecurityContext;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
import org.apache.ignite.internal.processors.service.GridServiceNotFoundException;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.processors.timeout.GridTimeoutObject;
@@ -460,6 +461,8 @@ public class GridJobWorker extends GridWorker implements GridTimeoutObject {
if (!internal && ctx.event().isRecordable(EVT_JOB_QUEUED))
recordEvent(EVT_JOB_QUEUED, "Job got queued for computation.");
+
+ job = SecurityUtils.sandboxedProxy(ctx, ComputeJob.class, job);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to initialize job [jobId=" + ses.getJobId() + ", ses=" + ses + ']', e);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProcessor.java
index 6d508fe..d148142 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProcessor.java
@@ -83,6 +83,18 @@ public class GridResourceProcessor extends GridProcessorAdapter {
}
/** {@inheritDoc} */
+ @Override public void onKernalStart(boolean active) throws IgniteCheckedException {
+ super.onKernalStart(active);
+
+ // The IgniteSecurity started for this moment,
+ // and we can make a decision on what instance of Ignite injector should be used.
+ if (ctx.security().sandbox().enabled()) {
+ injectorByAnnotation[GridResourceIoc.ResourceAnnotation.IGNITE_INSTANCE.ordinal()] =
+ new GridResourceProxiedIgniteInjector(ctx.grid());
+ }
+ }
+
+ /** {@inheritDoc} */
@Override public void stop(boolean cancel) {
ioc.undeployAll();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProxiedIgniteInjector.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProxiedIgniteInjector.java
new file mode 100644
index 0000000..de9dc45
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProxiedIgniteInjector.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.resource;
+
+import java.util.concurrent.Callable;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.compute.ComputeJob;
+import org.apache.ignite.compute.ComputeTask;
+import org.apache.ignite.internal.managers.deployment.GridDeployment;
+import org.apache.ignite.lang.IgniteBiClosure;
+import org.apache.ignite.lang.IgniteBiPredicate;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.lang.IgniteClosure;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.lang.IgniteRunnable;
+
+import static org.apache.ignite.internal.processors.security.sandbox.SandboxIgniteComponentProxy.proxy;
+
+/** Ignite instance injector. */
+public class GridResourceProxiedIgniteInjector extends GridResourceBasicInjector<Ignite> {
+ /** Array of classes that should get a proxied instance of Ignite. */
+ private static final Class[] PROXIED_CLASSES = new Class[]{
+ Runnable.class,
+ IgniteRunnable.class,
+ Callable.class,
+ IgniteCallable.class,
+ ComputeTask.class,
+ ComputeJob.class,
+ IgniteClosure.class,
+ IgniteBiClosure.class,
+ IgniteDataStreamer.class,
+ IgnitePredicate.class,
+ IgniteBiPredicate.class,
+ };
+
+ /**
+ * @param rsrc Resource.
+ */
+ public GridResourceProxiedIgniteInjector(Ignite rsrc) {
+ super(rsrc);
+ }
+
+ /** */
+ private Ignite ignite(Object target) {
+ return shouldUseProxy(target) ? proxy(Ignite.class, getResource()) : getResource();
+ }
+
+ /**
+ * @return True if {@code target} should get a proxy instance of Ignite.
+ */
+ private boolean shouldUseProxy(Object target){
+ for (Class cls : PROXIED_CLASSES) {
+ if (cls.isInstance(target))
+ return true;
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void inject(GridResourceField field, Object target, Class<?> depCls, GridDeployment dep)
+ throws IgniteCheckedException {
+ GridResourceUtils.inject(field.getField(), target, ignite(target));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void inject(GridResourceMethod mtd, Object target, Class<?> depCls, GridDeployment dep)
+ throws IgniteCheckedException {
+ GridResourceUtils.inject(mtd.getMethod(), target, ignite(target));
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/GridSecurityProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/GridSecurityProcessor.java
index f8986c2..a80444b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/GridSecurityProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/GridSecurityProcessor.java
@@ -22,6 +22,7 @@ import java.util.UUID;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.processors.GridProcessor;
+import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
import org.apache.ignite.plugin.security.AuthenticationContext;
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityException;
@@ -99,4 +100,15 @@ public interface GridSecurityProcessor extends GridProcessor {
*/
@Deprecated
public boolean enabled();
+
+ /**
+ * If this method returns true and {@link SecurityManager} is installed,
+ * then the user-defined code will be run inside the Sandbox.
+ *
+ * @return True if sandbox is enabled.
+ * @see IgniteSandbox
+ */
+ public default boolean sandboxEnabled() {
+ return false;
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurity.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurity.java
index ea90299..ee73441 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurity.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurity.java
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.UUID;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
import org.apache.ignite.plugin.security.AuthenticationContext;
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityException;
@@ -40,11 +41,6 @@ import org.apache.ignite.plugin.security.SecuritySubject;
* </ul>
*/
public interface IgniteSecurity {
- /** */
- static final String MSG_SEC_PROC_CLS_IS_INVALID = "Local node's grid security processor class " +
- "is not equal to remote node's grid security processor class " +
- "[locNodeId=%s, rmtNodeId=%s, locCls=%s, rmtCls=%s]";
-
/**
* Creates {@link OperationSecurityContext}. All calls of methods {@link #authorize(String, SecurityPermission)} or {@link
* #authorize(SecurityPermission)} will be processed into the context of passed {@link SecurityContext} until
@@ -121,6 +117,11 @@ public interface IgniteSecurity {
}
/**
+ * @return Instance of IgniteSandbox.
+ */
+ public IgniteSandbox sandbox();
+
+ /**
* @return True if IgniteSecurity is a plugin implementation,
* false if it's used a default NoOp implementation.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java
index 782b198..ca23498 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java
@@ -26,6 +26,9 @@ import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.processors.GridProcessor;
+import org.apache.ignite.internal.processors.security.sandbox.AccessControllerSandbox;
+import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
+import org.apache.ignite.internal.processors.security.sandbox.NoOpSandbox;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteFuture;
@@ -42,6 +45,8 @@ import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
import static org.apache.ignite.events.EventType.EVT_NODE_LEFT;
+import static org.apache.ignite.internal.processors.security.SecurityUtils.MSG_SEC_PROC_CLS_IS_INVALID;
+import static org.apache.ignite.internal.processors.security.SecurityUtils.hasSecurityManager;
import static org.apache.ignite.internal.processors.security.SecurityUtils.nodeSecurityContext;
/**
@@ -66,6 +71,9 @@ public class IgniteSecurityProcessor implements IgniteSecurity, GridProcessor {
/** Map of security contexts. Key is the node's id. */
private final Map<UUID, SecurityContext> secCtxs = new ConcurrentHashMap<>();
+ /** Instance of IgniteSandbox. */
+ private IgniteSandbox sandbox;
+
/**
* @param ctx Grid kernal context.
* @param secPrc Security processor.
@@ -152,6 +160,11 @@ public class IgniteSecurityProcessor implements IgniteSecurity, GridProcessor {
}
/** {@inheritDoc} */
+ @Override public IgniteSandbox sandbox() {
+ return sandbox;
+ }
+
+ /** {@inheritDoc} */
@Override public boolean enabled() {
return true;
}
@@ -161,6 +174,18 @@ public class IgniteSecurityProcessor implements IgniteSecurity, GridProcessor {
ctx.addNodeAttribute(ATTR_GRID_SEC_PROC_CLASS, secPrc.getClass().getName());
secPrc.start();
+
+ if (hasSecurityManager() && secPrc.sandboxEnabled())
+ sandbox = new AccessControllerSandbox(this);
+ else {
+ if (secPrc.sandboxEnabled()) {
+ ctx.log(getClass()).warning("GridSecurityProcessor#sandboxEnabled returns true, " +
+ "but system SecurityManager is not defined, " +
+ "that may be a cause of security lack when IgniteCompute or IgniteCache operations perform.");
+ }
+
+ sandbox = new NoOpSandbox();
+ }
}
/** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/NoOpIgniteSecurityProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/NoOpIgniteSecurityProcessor.java
index 7a17dc4..a9cbf58 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/NoOpIgniteSecurityProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/NoOpIgniteSecurityProcessor.java
@@ -22,6 +22,8 @@ import java.util.UUID;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.GridProcessorAdapter;
+import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
+import org.apache.ignite.internal.processors.security.sandbox.NoOpSandbox;
import org.apache.ignite.plugin.security.AuthenticationContext;
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityException;
@@ -32,6 +34,7 @@ import org.apache.ignite.spi.discovery.DiscoveryDataBag;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.internal.processors.security.IgniteSecurityProcessor.ATTR_GRID_SEC_PROC_CLASS;
+import static org.apache.ignite.internal.processors.security.SecurityUtils.MSG_SEC_PROC_CLS_IS_INVALID;
/**
* No operation IgniteSecurity.
@@ -40,6 +43,9 @@ public class NoOpIgniteSecurityProcessor extends GridProcessorAdapter implements
/** No operation security context. */
private final OperationSecurityContext opSecCtx = new OperationSecurityContext(this, null);
+ /** Instance of IgniteSandbox. */
+ private final IgniteSandbox sandbox = new NoOpSandbox();
+
/**
* @param ctx Grid kernal context.
*/
@@ -98,6 +104,11 @@ public class NoOpIgniteSecurityProcessor extends GridProcessorAdapter implements
}
/** {@inheritDoc} */
+ @Override public IgniteSandbox sandbox() {
+ return sandbox;
+ }
+
+ /** {@inheritDoc} */
@Override public boolean enabled() {
return false;
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityUtils.java
index b9a5215..d2b1eaf 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityUtils.java
@@ -17,14 +17,27 @@
package org.apache.ignite.internal.processors.security;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Callable;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.GridInternalWrapper;
+import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.IgniteNodeAttributes;
+import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.marshaller.Marshaller;
import org.apache.ignite.plugin.security.SecurityException;
@@ -34,6 +47,11 @@ import org.apache.ignite.plugin.security.SecurityPermission;
* Security utilities.
*/
public class SecurityUtils {
+ /** */
+ public static final String MSG_SEC_PROC_CLS_IS_INVALID = "Local node's grid security processor class " +
+ "is not equal to remote node's grid security processor class " +
+ "[locNodeId=%s, rmtNodeId=%s, locCls=%s, rmtCls=%s]";
+
/** Default serialization version. */
private static final int DFLT_SERIALIZE_VERSION = isSecurityCompatibilityMode() ? 1 : 2;
@@ -44,6 +62,16 @@ public class SecurityUtils {
}
};
+ /** Permissions that contain {@code AllPermission}. */
+ public static final Permissions ALL_PERMISSIONS;
+
+ static {
+ ALL_PERMISSIONS = new Permissions();
+
+ ALL_PERMISSIONS.add(new AllPermission());
+ ALL_PERMISSIONS.setReadOnly();
+ }
+
/**
* Private constructor.
*/
@@ -113,4 +141,98 @@ public class SecurityUtils {
throw new SecurityException("Failed to get security context.", e);
}
}
+
+ /**
+ * Computes a result in a privileged action.
+ *
+ * @param c Instance of SandboxCallable.
+ * @param <T> Type of result.
+ * @param <E> Type of Exception.
+ * @return Computed result.
+ * @throws E if unable to compute a result.
+ */
+ public static <T, E extends Exception> T doPrivileged(Callable<T> c) throws E {
+ try {
+ return AccessController.doPrivileged((PrivilegedExceptionAction<T>)c::call);
+ }
+ catch (PrivilegedActionException e) {
+ throw (E)e.getException();
+ }
+ }
+
+ /**
+ * @return True if SecurityManager is installed.
+ */
+ public static boolean hasSecurityManager() {
+ return System.getSecurityManager() != null;
+ }
+
+ /**
+ * @return True if class of {@code target} is a system type.
+ */
+ private static boolean isSystemType(GridKernalContext ctx, Object target) {
+ Class cls = target instanceof GridInternalWrapper
+ ? ((GridInternalWrapper)target).userObject().getClass()
+ : target.getClass();
+
+ return ctx.getClass().getClassLoader() == cls.getClassLoader()
+ && ctx.marshallerContext().isSystemType(cls.getName());
+ }
+
+ /**
+ * @return Proxy of {@code instance} if the sandbox is enabled and class of {@code instance} is not a system type
+ * otherwise {@code instance}.
+ */
+ public static <T> T sandboxedProxy(GridKernalContext ctx, final Class cls, final T instance) {
+ if (instance == null)
+ return null;
+
+ Objects.requireNonNull(ctx, "Parameter 'ctx' cannot be null.");
+ Objects.requireNonNull(cls, "Parameter 'cls' cannot be null.");
+
+ final IgniteSandbox sandbox = ctx.security().sandbox();
+
+ if (sandbox.enabled() && !isSystemType(ctx, instance)) {
+ return (T)Proxy.newProxyInstance(sandbox.getClass().getClassLoader(),
+ proxyClasses(cls, instance), new SandboxInvocationHandler(sandbox, instance));
+ }
+
+ return instance;
+ }
+
+ /** Array of proxy classes. */
+ private static <T> Class[] proxyClasses(Class cls, T instance) {
+ return instance instanceof GridInternalWrapper
+ ? new Class[] {cls, GridInternalWrapper.class}
+ : new Class[] {cls};
+ }
+
+ /** */
+ private static class SandboxInvocationHandler<T> implements InvocationHandler {
+ /** */
+ private final IgniteSandbox sandbox;
+
+ /** */
+ private final Object original;
+
+ /** */
+ public SandboxInvocationHandler(IgniteSandbox sandbox, Object original) {
+ this.sandbox = sandbox;
+ this.original = original;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object invoke(Object proxy, Method mtd, Object[] args) throws Throwable {
+ try {
+ if (proxy instanceof GridInternalWrapper &&
+ GridInternalWrapper.class.getMethod(mtd.getName(), mtd.getParameterTypes()) != null)
+ return mtd.invoke(original, args);
+ }
+ catch (NoSuchMethodException e) {
+ // Ignore.
+ }
+
+ return sandbox.execute(() -> (T)mtd.invoke(original, args));
+ }
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/AccessControllerSandbox.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/AccessControllerSandbox.java
new file mode 100644
index 0000000..bb3c4cf
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/AccessControllerSandbox.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.security.IgniteSecurity;
+import org.apache.ignite.internal.processors.security.SecurityContext;
+import org.apache.ignite.plugin.security.SecurityException;
+
+import static org.apache.ignite.internal.processors.security.SecurityUtils.hasSecurityManager;
+
+/**
+ * Sandbox that based on AccessController.
+ */
+public class AccessControllerSandbox implements IgniteSandbox {
+ /** Instance of IgniteSecurity. */
+ private final IgniteSecurity security;
+
+ /** Constructor. */
+ public AccessControllerSandbox(IgniteSecurity security) {
+ this.security = security;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T execute(Callable<T> c) throws IgniteException {
+ Objects.requireNonNull(c);
+
+ if (!hasSecurityManager())
+ throw new SecurityException("SecurityManager was, but it disappeared!");
+
+ final SecurityContext secCtx = security.securityContext();
+
+ assert secCtx != null;
+
+ final AccessControlContext acc = AccessController.doPrivileged(
+ (PrivilegedAction<AccessControlContext>)() -> new AccessControlContext(AccessController.getContext(),
+ new IgniteDomainCombiner(secCtx.subject().sandboxPermissions()))
+ );
+
+ try {
+ return AccessController.doPrivileged((PrivilegedExceptionAction<T>)c::call, acc);
+ }
+ catch (PrivilegedActionException e) {
+ throw new IgniteException(e.getException());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean enabled() {
+ return true;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteDomainCombiner.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteDomainCombiner.java
new file mode 100644
index 0000000..b8cf4f1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteDomainCombiner.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.security.DomainCombiner;
+import java.security.PermissionCollection;
+import java.security.ProtectionDomain;
+
+/**
+ * A {@code IgniteDomainCombiner} updates ProtectionDomains with passed {@code Permissions}.
+ */
+public class IgniteDomainCombiner implements DomainCombiner {
+ /** */
+ private final ProtectionDomain pd;
+
+ /** */
+ public IgniteDomainCombiner(PermissionCollection perms) {
+ pd = new ProtectionDomain(null, perms);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ProtectionDomain[] combine(ProtectionDomain[] currDomains, ProtectionDomain[] assignedDomains) {
+ if (currDomains == null || currDomains.length == 0)
+ return assignedDomains;
+
+ if (assignedDomains == null || assignedDomains.length == 0)
+ return new ProtectionDomain[] {pd};
+
+ ProtectionDomain[] res = new ProtectionDomain[assignedDomains.length + 1];
+
+ res[0] = pd;
+
+ System.arraycopy(assignedDomains, 0, res, 1, assignedDomains.length);
+
+ return res;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteSandbox.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteSandbox.java
new file mode 100644
index 0000000..ab04d52
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteSandbox.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.security.IgniteSecurity;
+import org.apache.ignite.internal.processors.security.SecurityContext;
+import org.apache.ignite.plugin.security.SecuritySubject;
+
+/**
+ * IgniteSandbox executes a user-defined code with restrictions.
+ */
+public interface IgniteSandbox {
+ /**
+ * Executes {@code callable} with constraints defined by current {@code SecuritySubject}.
+ *
+ * @param call Callable to execute.
+ * @return Result of {@code callable}.
+ * @see IgniteSecurity#withContext(UUID)
+ * @see IgniteSecurity#withContext(SecurityContext)
+ * @see SecuritySubject#sandboxPermissions()
+ */
+ public <T> T execute(Callable<T> call) throws IgniteException;
+
+ /**
+ * @return True if the sandbox is enabled.
+ */
+ public boolean enabled();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/NoOpSandbox.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/NoOpSandbox.java
new file mode 100644
index 0000000..15b85d6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/NoOpSandbox.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.util.concurrent.Callable;
+import org.apache.ignite.IgniteException;
+
+/**
+ * No operation Sandbox.
+ */
+public class NoOpSandbox implements IgniteSandbox {
+ /** {@inheritDoc} */
+ @Override public <T> T execute(Callable<T> call) throws IgniteException {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean enabled() {
+ return false;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/SandboxIgniteComponentProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/SandboxIgniteComponentProxy.java
new file mode 100644
index 0000000..c8863eb
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/SandboxIgniteComponentProxy.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteAtomicLong;
+import org.apache.ignite.IgniteAtomicReference;
+import org.apache.ignite.IgniteAtomicSequence;
+import org.apache.ignite.IgniteAtomicStamped;
+import org.apache.ignite.IgniteBinary;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCompute;
+import org.apache.ignite.IgniteCountDownLatch;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.IgniteLock;
+import org.apache.ignite.IgniteQueue;
+import org.apache.ignite.IgniteScheduler;
+import org.apache.ignite.IgniteSemaphore;
+import org.apache.ignite.IgniteSet;
+import org.apache.ignite.IgniteTransactions;
+import org.apache.ignite.cache.affinity.Affinity;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
+
+/** Create instace of Ignite component proxy to use inside the Ignite Sandbox. */
+public final class SandboxIgniteComponentProxy {
+ /** The array of classes that should be proxied. */
+ private static final Class[] PROXIED_CLASSES = new Class[] {
+ Ignite.class,
+ IgniteCache.class,
+ IgniteCompute.class,
+ ExecutorService.class,
+ IgniteScheduler.class,
+ IgniteTransactions.class,
+ IgniteDataStreamer.class,
+ IgniteAtomicSequence.class,
+ IgniteAtomicLong.class,
+ IgniteAtomicReference.class,
+ IgniteAtomicStamped.class,
+ IgniteCountDownLatch.class,
+ IgniteSemaphore.class,
+ IgniteLock.class,
+ IgniteQueue.class,
+ IgniteSet.class,
+ IgniteBinary.class,
+ Affinity.class
+ };
+
+ /**
+ * @return The proxy of {@code instance} to use inside the Ignite Sandbox.
+ */
+ public static <T> T proxy(Class cls, T instance) {
+ Objects.requireNonNull(cls, "Parameter 'cls' cannot be null.");
+ Objects.requireNonNull(instance, "Parameter 'instance' cannot be null.");
+
+ return SecurityUtils.doPrivileged(
+ () -> (T)Proxy.newProxyInstance(cls.getClassLoader(), new Class[] {cls},
+ new SandboxIgniteComponentProxyHandler(instance))
+ );
+ }
+
+ /** */
+ private static class SandboxIgniteComponentProxyHandler implements InvocationHandler {
+ /** */
+ private final Object original;
+
+ /** */
+ public SandboxIgniteComponentProxyHandler(Object original) {
+ this.original = original;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object invoke(Object proxy, Method mtd, Object[] args) throws Throwable {
+ Object res = SecurityUtils.doPrivileged(() -> mtd.invoke(original, args));
+
+ Class cls = proxiedClass(res);
+
+ return cls != null ? proxy(cls, res) : res;
+ }
+
+ /** */
+ private Class proxiedClass(Object obj) {
+ for (Class cls : PROXIED_CLASSES) {
+ if (cls.isInstance(obj))
+ return cls;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java
index 66e3c7c..6b3e6f6 100644
--- a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java
+++ b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java
@@ -19,7 +19,10 @@ package org.apache.ignite.plugin.security;
import java.io.Serializable;
import java.net.InetSocketAddress;
+import java.security.PermissionCollection;
+import java.security.ProtectionDomain;
import java.util.UUID;
+import org.apache.ignite.internal.processors.security.SecurityUtils;
/**
* Security subject representing authenticated node with a set of permissions.
@@ -59,4 +62,13 @@ public interface SecuritySubject extends Serializable {
* @return Authorized permission set for the subject.
*/
public SecurityPermissionSet permissions();
+
+ /**
+ * @return Permissions for SecurityManager checks.
+ */
+ public default PermissionCollection sandboxPermissions() {
+ ProtectionDomain pd = SecurityUtils.doPrivileged(() -> getClass().getProtectionDomain());
+
+ return pd != null ? pd.getPermissions() : SecurityUtils.ALL_PERMISSIONS;
+ }
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/AbstractSecurityTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/AbstractSecurityTest.java
index 13688dc..5907952 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/AbstractSecurityTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/AbstractSecurityTest.java
@@ -17,14 +17,26 @@
package org.apache.ignite.internal.processors.security;
+import java.security.Permissions;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.cache.Cache;
+import javax.cache.configuration.Factory;
+import org.apache.ignite.cache.store.CacheStoreAdapter;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.processors.security.impl.TestSecurityPluginProvider;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.lang.IgniteBiInClosure;
+import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.plugin.security.SecurityPermission;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.NotNull;
import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL;
@@ -79,7 +91,98 @@ public class AbstractSecurityTest extends GridCommonAbstractTest {
* @param isClient Is client.
*/
protected IgniteEx startGrid(String login, SecurityPermissionSet prmSet, boolean isClient) throws Exception {
- return startGrid(getConfiguration(login, new TestSecurityPluginProvider(login, "", prmSet, globalAuth))
- .setClientMode(isClient));
+ return startGrid(login, prmSet, null, isClient);
+ }
+
+ /** */
+ protected IgniteEx startGrid(String login, SecurityPermissionSet prmSet,
+ Permissions sandboxPerms, boolean isClient) throws Exception {
+ return startGrid(getConfiguration(login,
+ new TestSecurityPluginProvider(login, "", prmSet, sandboxPerms, globalAuth))
+ .setClientMode(isClient));
+ }
+
+ /** */
+ protected static class TestFutureAdapter<T> implements Future<T> {
+ /** */
+ private final IgniteFuture<T> igniteFut;
+
+ /** */
+ public TestFutureAdapter(IgniteFuture<T> igniteFut) {
+ this.igniteFut = igniteFut;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean cancel(boolean mayInterruptIfRunning) {
+ return igniteFut.cancel();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isCancelled() {
+ return igniteFut.isCancelled();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isDone() {
+ return igniteFut.isDone();
+ }
+
+ /** {@inheritDoc} */
+ @Override public T get() throws InterruptedException, ExecutionException {
+ return igniteFut.get();
+ }
+
+ /** {@inheritDoc} */
+ @Override public T get(long timeout,
+ @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ return igniteFut.get(timeout, unit);
+ }
+ }
+
+ /** */
+ protected static class TestStoreFactory implements Factory<TestCacheStore> {
+ /** */
+ private final T2<Object, Object> keyVal;
+
+ /** */
+ public TestStoreFactory(Object key, Object val) {
+ keyVal = new T2<>(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public TestCacheStore create() {
+ return new TestCacheStore(keyVal);
+ }
+ }
+
+ /** */
+ private static class TestCacheStore extends CacheStoreAdapter<Object, Object> {
+ /** */
+ private final T2<Object, Object> keyVal;
+
+ /** Constructor. */
+ public TestCacheStore(T2<Object, Object> keyVal) {
+ this.keyVal = keyVal;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void loadCache(IgniteBiInClosure<Object, Object> clo, Object... args) {
+ clo.apply(keyVal.getKey(), keyVal.getValue());
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object load(Object key) {
+ return key;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void write(Cache.Entry<?, ?> entry) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void delete(Object key) {
+ // No-op.
+ }
}
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cache/closure/CacheLoadRemoteSecurityContextCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cache/closure/CacheLoadRemoteSecurityContextCheckTest.java
index 00b0374..fea44fd 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cache/closure/CacheLoadRemoteSecurityContextCheckTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cache/closure/CacheLoadRemoteSecurityContextCheckTest.java
@@ -20,14 +20,10 @@ package org.apache.ignite.internal.processors.security.cache.closure;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
-import javax.cache.Cache;
-import javax.cache.configuration.Factory;
import org.apache.ignite.cache.CacheMode;
-import org.apache.ignite.cache.store.CacheStoreAdapter;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.security.AbstractCacheOperationRemoteSecurityContextCheckTest;
import org.apache.ignite.internal.util.typedef.G;
-import org.apache.ignite.lang.IgniteBiInClosure;
import org.apache.ignite.lang.IgniteRunnable;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,11 +66,11 @@ public class CacheLoadRemoteSecurityContextCheckTest extends AbstractCacheOperat
new CacheConfiguration<Integer, Integer>()
.setName(CACHE_NAME)
.setCacheMode(CacheMode.PARTITIONED)
- .setCacheStoreFactory(new TestStoreFactory()),
+ .setCacheStoreFactory(new TestStoreFactory(1, 1)),
new CacheConfiguration<Integer, Integer>()
.setName(TRANSITION_LOAD_CACHE)
.setCacheMode(CacheMode.PARTITIONED)
- .setCacheStoreFactory(new TestStoreFactory())
+ .setCacheStoreFactory(new TestStoreFactory(1, 1))
};
}
@@ -111,39 +107,4 @@ public class CacheLoadRemoteSecurityContextCheckTest extends AbstractCacheOperat
@Override protected Collection<UUID> nodesToCheck() {
return Collections.singletonList(nodeId(SRV_CHECK));
}
-
- /**
- * Test store factory.
- */
- private static class TestStoreFactory implements Factory<TestCacheStore> {
- /** {@inheritDoc} */
- @Override public TestCacheStore create() {
- return new TestCacheStore();
- }
- }
-
- /**
- * Test cache store.
- */
- private static class TestCacheStore extends CacheStoreAdapter<Integer, Integer> {
- /** {@inheritDoc} */
- @Override public void loadCache(IgniteBiInClosure<Integer, Integer> clo, Object... args) {
- clo.apply(1, 1);
- }
-
- /** {@inheritDoc} */
- @Override public Integer load(Integer key) {
- return key;
- }
-
- /** {@inheritDoc} */
- @Override public void write(Cache.Entry<? extends Integer, ? extends Integer> entry) {
- throw new UnsupportedOperationException();
- }
-
- /** {@inheritDoc} */
- @Override public void delete(Object key) {
- // No-op.
- }
- }
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/compute/ComputePermissionCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/compute/ComputePermissionCheckTest.java
index 1fa6943..df83295 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/compute/ComputePermissionCheckTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/compute/ComputePermissionCheckTest.java
@@ -21,10 +21,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
@@ -40,14 +38,12 @@ import org.apache.ignite.compute.ComputeTask;
import org.apache.ignite.internal.processors.security.AbstractSecurityTest;
import org.apache.ignite.lang.IgniteCallable;
import org.apache.ignite.lang.IgniteClosure;
-import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.lang.IgniteRunnable;
import org.apache.ignite.plugin.security.SecurityException;
import org.apache.ignite.plugin.security.SecurityPermission;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
import org.apache.ignite.plugin.security.SecurityPermissionSetBuilder;
import org.apache.ignite.testframework.GridTestUtils.RunnableX;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -169,11 +165,11 @@ public class ComputePermissionCheckTest extends AbstractSecurityTest {
/** */
private Stream<Supplier<Future>> asyncOperations(Ignite... nodes) {
Function<Ignite, Stream<Supplier<Future>>> nodeOps = (node) -> Stream.of(
- () -> new FutureAdapter<>(node.compute().executeAsync(TEST_COMPUTE_TASK, 0)),
- () -> new FutureAdapter<>(node.compute().broadcastAsync(TEST_CALLABLE)),
- () -> new FutureAdapter<>(node.compute().callAsync(TEST_CALLABLE)),
- () -> new FutureAdapter<>(node.compute().runAsync(TEST_RUNNABLE)),
- () -> new FutureAdapter<>(node.compute().applyAsync(TEST_CLOSURE, new Object())),
+ () -> new TestFutureAdapter<>(node.compute().executeAsync(TEST_COMPUTE_TASK, 0)),
+ () -> new TestFutureAdapter<>(node.compute().broadcastAsync(TEST_CALLABLE)),
+ () -> new TestFutureAdapter<>(node.compute().callAsync(TEST_CALLABLE)),
+ () -> new TestFutureAdapter<>(node.compute().runAsync(TEST_RUNNABLE)),
+ () -> new TestFutureAdapter<>(node.compute().applyAsync(TEST_CLOSURE, new Object())),
() -> node.executorService().submit(TEST_CALLABLE)
);
@@ -221,43 +217,6 @@ public class ComputePermissionCheckTest extends AbstractSecurityTest {
}
}
- /** */
- private static class FutureAdapter<T> implements Future<T> {
- /** Ignite future. */
- private final IgniteFuture<T> igniteFut;
-
- /** */
- public FutureAdapter(IgniteFuture<T> igniteFut) {
- this.igniteFut = igniteFut;
- }
-
- /** {@inheritDoc} */
- @Override public boolean cancel(boolean mayInterruptIfRunning) {
- return igniteFut.cancel();
- }
-
- /** {@inheritDoc} */
- @Override public boolean isCancelled() {
- return igniteFut.isCancelled();
- }
-
- /** {@inheritDoc} */
- @Override public boolean isDone() {
- return igniteFut.isDone();
- }
-
- /** {@inheritDoc} */
- @Override public T get() throws InterruptedException, ExecutionException {
- return igniteFut.get();
- }
-
- /** {@inheritDoc} */
- @Override public T get(long timeout,
- @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- return igniteFut.get(timeout, unit);
- }
- }
-
/**
* Abstract test compute task.
*/
@@ -270,7 +229,7 @@ public class ComputePermissionCheckTest extends AbstractSecurityTest {
return Collections.singletonMap(
new ComputeJob() {
@Override public void cancel() {
- // no-op
+ // No-op.
}
@Override public Object execute() {
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityData.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityData.java
index 66bc171..1c85db0 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityData.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityData.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.security.impl;
+import java.security.Permissions;
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
@@ -33,6 +34,9 @@ public class TestSecurityData {
/** Security permission set. */
private SecurityPermissionSet prmSet;
+ /** */
+ private Permissions sandboxPerms;
+
/**
* Default constructor.
*/
@@ -45,10 +49,12 @@ public class TestSecurityData {
* @param pwd Password.
* @param prmSet Permissions.
*/
- public TestSecurityData(String login, String pwd, SecurityPermissionSet prmSet) {
+ public TestSecurityData(String login, String pwd, SecurityPermissionSet prmSet,
+ Permissions sandboxPerms) {
this.login = login;
this.pwd = pwd;
this.prmSet = prmSet;
+ this.sandboxPerms = sandboxPerms;
}
/**
@@ -56,7 +62,7 @@ public class TestSecurityData {
* @param prmSet Permissions.
*/
public TestSecurityData(String login, SecurityPermissionSet prmSet) {
- this(login, "", prmSet);
+ this(login, "", prmSet, new Permissions());
}
/**
@@ -75,6 +81,18 @@ public class TestSecurityData {
return this;
}
+ /** */
+ public Permissions sandboxPermissions() {
+ return sandboxPerms;
+ }
+
+ /** */
+ public TestSecurityData sandboxPermissions(Permissions perms) {
+ sandboxPerms = perms;
+
+ return this;
+ }
+
/**
* Login.
*/
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityPluginProvider.java
index 5ee18ff..278f18c 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityPluginProvider.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityPluginProvider.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.security.impl;
+import java.security.Permissions;
import java.util.Arrays;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.security.AbstractTestSecurityPluginProvider;
@@ -34,6 +35,9 @@ public class TestSecurityPluginProvider extends AbstractTestSecurityPluginProvid
/** Permissions. */
private final SecurityPermissionSet perms;
+ /** */
+ private final Permissions sandboxPerms;
+
/** Global authentication. */
private final boolean globalAuth;
@@ -41,11 +45,18 @@ public class TestSecurityPluginProvider extends AbstractTestSecurityPluginProvid
private final TestSecurityData[] clientData;
/** */
- public TestSecurityPluginProvider(String login, String pwd, SecurityPermissionSet perms, boolean globalAuth,
- TestSecurityData... clientData) {
+ public TestSecurityPluginProvider(String login, String pwd, SecurityPermissionSet perms,
+ boolean globalAuth, TestSecurityData... clientData) {
+ this(login, pwd, perms, null, globalAuth, clientData);
+ }
+
+ /** */
+ public TestSecurityPluginProvider(String login, String pwd, SecurityPermissionSet perms,
+ Permissions sandboxPerms, boolean globalAuth, TestSecurityData... clientData) {
this.login = login;
this.pwd = pwd;
this.perms = perms;
+ this.sandboxPerms = sandboxPerms != null ? sandboxPerms : new Permissions();
this.globalAuth = globalAuth;
this.clientData = clientData.clone();
}
@@ -53,7 +64,7 @@ public class TestSecurityPluginProvider extends AbstractTestSecurityPluginProvid
/** {@inheritDoc} */
@Override protected GridSecurityProcessor securityProcessor(GridKernalContext ctx) {
return new TestSecurityProcessor(ctx,
- new TestSecurityData(login, pwd, perms),
+ new TestSecurityData(login, pwd, perms, sandboxPerms),
Arrays.asList(clientData),
globalAuth);
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
index b62f97b..294c5c6 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.security.impl;
import java.net.InetSocketAddress;
+import java.security.Permissions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -48,6 +49,9 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
/** Permissions. */
public static final Map<SecurityCredentials, SecurityPermissionSet> PERMS = new ConcurrentHashMap<>();
+ /** Sandbox permissions. */
+ private static final Map<SecurityCredentials, Permissions> SANDBOX_PERMS = new ConcurrentHashMap<>();
+
/** Node security data. */
private final TestSecurityData nodeSecData;
@@ -83,6 +87,7 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
.setAddr(new InetSocketAddress(F.first(node.addresses()), 0))
.setLogin(cred.getLogin())
.setPerms(PERMS.get(cred))
+ .sandboxPermissions(SANDBOX_PERMS.get(cred))
);
}
@@ -103,6 +108,7 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
.setAddr(ctx.address())
.setLogin(ctx.credentials().getLogin())
.setPerms(PERMS.get(ctx.credentials()))
+ .sandboxPermissions(SANDBOX_PERMS.get(ctx.credentials()))
);
}
@@ -140,11 +146,14 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
super.start();
PERMS.put(nodeSecData.credentials(), nodeSecData.getPermissions());
+ SANDBOX_PERMS.put(nodeSecData.credentials(), nodeSecData.sandboxPermissions());
ctx.addNodeAttribute(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, nodeSecData.credentials());
- for (TestSecurityData data : predefinedAuthData)
+ for (TestSecurityData data : predefinedAuthData) {
PERMS.put(data.credentials(), data.getPermissions());
+ SANDBOX_PERMS.put(nodeSecData.credentials(), data.sandboxPermissions());
+ }
}
/** {@inheritDoc} */
@@ -152,8 +161,16 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
super.stop(cancel);
PERMS.remove(nodeSecData.credentials());
+ SANDBOX_PERMS.remove(nodeSecData.credentials());
- for (TestSecurityData data : predefinedAuthData)
+ for (TestSecurityData data : predefinedAuthData) {
PERMS.remove(data.credentials());
+ SANDBOX_PERMS.remove(data.credentials());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean sandboxEnabled() {
+ return true;
}
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java
index 6fa47cf..c41026e 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.security.impl;
import java.net.InetSocketAddress;
+import java.security.PermissionCollection;
import java.util.UUID;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
import org.apache.ignite.plugin.security.SecuritySubject;
@@ -42,6 +43,9 @@ public class TestSecuritySubject implements SecuritySubject {
/** Permissions. */
private SecurityPermissionSet perms;
+ /** Permissions for Sandbox checks. */
+ private PermissionCollection sandboxPerms;
+
/**
* Default constructor.
*/
@@ -136,6 +140,18 @@ public class TestSecuritySubject implements SecuritySubject {
}
/** {@inheritDoc} */
+ @Override public PermissionCollection sandboxPermissions() {
+ return sandboxPerms;
+ }
+
+ /** */
+ public TestSecuritySubject sandboxPermissions(PermissionCollection perms) {
+ sandboxPerms = perms;
+
+ return this;
+ }
+
+ /** {@inheritDoc} */
@Override public String toString() {
return "TestSecuritySubject{" +
"id=" + id +
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/AbstractSandboxTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/AbstractSandboxTest.java
new file mode 100644
index 0000000..1bee9060
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/AbstractSandboxTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.security.AllPermission;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.util.PropertyPermission;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.internal.processors.security.AbstractSecurityTest;
+import org.apache.ignite.testframework.GridTestUtils;
+
+import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+
+/** */
+public abstract class AbstractSandboxTest extends AbstractSecurityTest {
+ /** */
+ protected static final String TEST_CACHE = "test_cache";
+
+ /** */
+ protected static boolean setupSM;
+
+ /** Sever node name. */
+ protected static final String SRV = "srv";
+
+ /** Client node that can write to test property. */
+ protected static final String CLNT_ALLOWED_WRITE_PROP = "clnt_allowed";
+
+ /** Client node that cannot write to the test property. */
+ protected static final String CLNT_FORBIDDEN_WRITE_PROP = "clnt_forbidden";
+
+ /** Test property name. */
+ private static final String PROP_NAME = "test.sandbox.property";
+
+ /** Test property value. */
+ private static final String PROP_VALUE = "propertyValue";
+
+ /** */
+ protected static void controlAction(){
+ System.setProperty(PROP_NAME, PROP_VALUE);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ if (System.getSecurityManager() == null) {
+ Policy.setPolicy(new Policy() {
+ @Override public PermissionCollection getPermissions(CodeSource cs) {
+ Permissions res = new Permissions();
+
+ res.add(new AllPermission());
+
+ return res;
+ }
+ });
+
+ System.setSecurityManager(new SecurityManager());
+
+ setupSM = true;
+ }
+
+ prepareCluster();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTestsStopped() throws Exception {
+ super.afterTestsStopped();
+
+ if (setupSM) {
+ System.setSecurityManager(null);
+ Policy.setPolicy(null);
+ }
+ }
+
+ /** */
+ protected void prepareCluster() throws Exception {
+ Ignite srv = startGrid(SRV, ALLOW_ALL, false);
+
+ Permissions perms = new Permissions();
+
+ perms.add(new PropertyPermission(PROP_NAME, "write"));
+
+ startGrid(CLNT_ALLOWED_WRITE_PROP, ALLOW_ALL, perms, true);
+
+ startGrid(CLNT_FORBIDDEN_WRITE_PROP, ALLOW_ALL, true);
+
+ srv.cluster().active(true);
+ }
+
+ /**
+ * @param r Runnable that runs {@link AbstractSandboxTest#controlAction()}.
+ */
+ protected void runOperation(Runnable r) {
+ System.clearProperty(PROP_NAME);
+
+ r.run();
+
+ assertEquals(PROP_VALUE, System.getProperty(PROP_NAME));
+ }
+
+ /**
+ * @param r RunnableX that that runs {@link AbstractSandboxTest#controlAction()}.
+ */
+ protected void runForbiddenOperation(GridTestUtils.RunnableX r, Class<? extends Throwable> cls) {
+ System.clearProperty(PROP_NAME);
+
+ assertThrowsWithCause(r, cls);
+
+ assertNull(System.getProperty(PROP_NAME));
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/CacheSandboxTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/CacheSandboxTest.java
new file mode 100644
index 0000000..1663ecf
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/CacheSandboxTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.security.AccessControlException;
+import java.util.stream.Stream;
+import javax.cache.processor.EntryProcessorResult;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.cache.CacheEntryProcessor;
+import org.apache.ignite.cache.query.ScanQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.lang.IgniteBiPredicate;
+import org.apache.ignite.testframework.GridTestUtils.RunnableX;
+import org.junit.Test;
+
+import static java.util.Collections.singleton;
+
+/**
+ * Checks that user-defined code for cache operations is executed inside the sandbox.
+ */
+public class CacheSandboxTest extends AbstractSandboxTest {
+ /** */
+ private static final CacheEntryProcessor<Object, Object, Object> TEST_PROC = (entry, o) -> {
+ controlAction();
+
+ return null;
+ };
+
+ /** */
+ private static final IgniteBiPredicate<String, String> TEST_PRED = (a, b) -> {
+ controlAction();
+
+ return true;
+ };
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setCacheConfiguration(
+ new CacheConfiguration<String, String>(TEST_CACHE)
+ .setCacheStoreFactory(new TestStoreFactory("1", "val"))
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ populateCache();
+ }
+
+ /** */
+ @Test
+ public void testEntryProcessor() {
+ entryProcessorOperations(grid(CLNT_ALLOWED_WRITE_PROP)).forEach(this::runOperation);
+ entryProcessorOperations(grid(CLNT_FORBIDDEN_WRITE_PROP))
+ .forEach(r -> runForbiddenOperation(r, AccessControlException.class));
+ }
+
+ /** */
+ @Test
+ public void testScanQuery() {
+ scanQueryOperations(grid(CLNT_ALLOWED_WRITE_PROP)).forEach(this::runOperation);
+ scanQueryOperations(grid(CLNT_FORBIDDEN_WRITE_PROP))
+ .forEach(r -> runForbiddenOperation(r, AccessControlException.class));
+ }
+
+ /** */
+ @Test
+ public void testLoadCache() {
+ runOperation(() -> grid(CLNT_ALLOWED_WRITE_PROP).<String, String>cache(TEST_CACHE).loadCache(TEST_PRED));
+ runForbiddenOperation(() -> grid(CLNT_FORBIDDEN_WRITE_PROP)
+ .<String, String>cache(TEST_CACHE).loadCache(TEST_PRED), AccessControlException.class);
+ }
+
+ /**
+ * @return EntryProcessor operations to test.
+ */
+ private Stream<RunnableX> entryProcessorOperations(Ignite node) {
+ EntryProcessorResult<Object> dflt = () -> null;
+
+ return Stream.of(
+ () -> node.cache(TEST_CACHE).invoke("key", TEST_PROC),
+ () -> node.cache(TEST_CACHE).invokeAll(singleton("key"), TEST_PROC)
+ .getOrDefault("key", dflt).get(),
+ () -> node.cache(TEST_CACHE).invokeAsync("key", TEST_PROC).get(),
+ () -> node.cache(TEST_CACHE).invokeAllAsync(singleton("key"), TEST_PROC).get()
+ .getOrDefault("key", dflt).get()
+ );
+ }
+
+ /**
+ * @return ScanQuery operations to test.
+ */
+ private Stream<RunnableX> scanQueryOperations(Ignite node) {
+ return Stream.of(
+ () -> node.cache(TEST_CACHE).query(new ScanQuery<>((o, o2) -> {
+ controlAction();
+
+ return false;
+ })).getAll(),
+ () -> node.cache(TEST_CACHE).query(new ScanQuery<>((k, v) -> true), e -> {
+ controlAction();
+
+ return null;
+ }).getAll()
+ );
+ }
+
+ /** */
+ private void populateCache() {
+ try (IgniteDataStreamer<String, Integer> cache = grid(SRV).dataStreamer(TEST_CACHE)) {
+ for (int i = 1; i <= 10; i++)
+ cache.addData(Integer.toString(i), i);
+ }
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/ComputeSandboxTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/ComputeSandboxTest.java
new file mode 100644
index 0000000..fe3f27b
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/ComputeSandboxTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.security.AccessControlException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.compute.ComputeJob;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.compute.ComputeJobResultPolicy;
+import org.apache.ignite.compute.ComputeTask;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.lang.IgniteClosure;
+import org.apache.ignite.lang.IgniteRunnable;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+import static java.util.Collections.singletonList;
+
+/**
+ * Checks that user-defined code for compute operations is executed inside the sandbox.
+ */
+public class ComputeSandboxTest extends AbstractSandboxTest {
+ /** */
+ private static final TestComputeTask COMPUTE_TASK = new TestComputeTask();
+
+ /** */
+ private static final IgniteCallable<Object> CALLABLE = () -> {
+ controlAction();
+
+ return null;
+ };
+
+ /** */
+ private static final IgniteClosure<Object, Object> CLOSURE = a -> {
+ controlAction();
+
+ return null;
+ };
+
+ /** */
+ private static final IgniteRunnable RUNNABLE = AbstractSandboxTest::controlAction;
+
+ /** */
+ @Test
+ public void testCompute(){
+ computeOperations(grid(CLNT_ALLOWED_WRITE_PROP)).forEach(this::runOperation);
+ computeOperations(grid(CLNT_FORBIDDEN_WRITE_PROP))
+ .forEach(op -> runForbiddenOperation(op, AccessControlException.class));
+ }
+
+ /** */
+ @Test
+ public void testExecutorService(){
+ executorServiceOperations(grid(CLNT_ALLOWED_WRITE_PROP)).forEach(this::runOperation);
+ executorServiceOperations(grid(CLNT_FORBIDDEN_WRITE_PROP))
+ .forEach(op -> runForbiddenOperation(op, IgniteException.class));
+ }
+
+ /**
+ * @return Stream of Compute operations to test.
+ */
+ private Stream<GridTestUtils.RunnableX> computeOperations(Ignite node) {
+ return Stream.of(
+ () -> node.compute().execute(COMPUTE_TASK, 0),
+ () -> node.compute().broadcast(CALLABLE),
+ () -> node.compute().call(CALLABLE),
+ () -> node.compute().run(RUNNABLE),
+ () -> node.compute().apply(CLOSURE, new Object()),
+
+ () -> new TestFutureAdapter<>(node.compute().executeAsync(COMPUTE_TASK, 0)).get(),
+ () -> new TestFutureAdapter<>(node.compute().broadcastAsync(CALLABLE)).get(),
+ () -> new TestFutureAdapter<>(node.compute().callAsync(CALLABLE)).get(),
+ () -> new TestFutureAdapter<>(node.compute().runAsync(RUNNABLE)).get(),
+ () -> new TestFutureAdapter<>(node.compute().applyAsync(CLOSURE, new Object())).get()
+ );
+ }
+
+ /**
+ * @return Stream of ExecutorService operations to test.
+ */
+ private Stream<GridTestUtils.RunnableX> executorServiceOperations(Ignite node) {
+ return Stream.of(
+ () -> node.executorService().invokeAll(singletonList(CALLABLE))
+ .stream().findFirst().orElseThrow(IgniteException::new).get(),
+ () -> node.executorService().invokeAny(singletonList(CALLABLE)),
+ () -> node.executorService().submit(CALLABLE).get()
+ );
+ }
+
+ /** */
+ static class TestComputeTask implements ComputeTask<Object, Object> {
+ /** {@inheritDoc} */
+ @Override public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> subgrid,
+ Object arg) throws IgniteException {
+ return Collections.singletonMap(
+ new ComputeJob() {
+ @Override public void cancel() {
+ // No-op.
+ }
+
+ @Override public Object execute() {
+ controlAction();
+
+ return null;
+ }
+ }, subgrid.stream().findFirst().orElseThrow(IllegalStateException::new)
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override public ComputeJobResultPolicy result(ComputeJobResult res,
+ List<ComputeJobResult> rcvd) throws IgniteException {
+ if (res.getException() != null)
+ throw res.getException();
+
+ return ComputeJobResultPolicy.REDUCE;
+ }
+
+ /** {@inheritDoc} */
+ @Override public @Nullable Object reduce(List<ComputeJobResult> results) throws IgniteException {
+ return null;
+ }
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DataStreamerSandboxTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DataStreamerSandboxTest.java
new file mode 100644
index 0000000..e842a16
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DataStreamerSandboxTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.security.AccessControlException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Test;
+
+/**
+ * Checks that user-defined code for data streamer is executed inside the sandbox.
+ */
+public class DataStreamerSandboxTest extends AbstractSandboxTest {
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setCacheConfiguration(new CacheConfiguration<Integer, Integer>(TEST_CACHE));
+ }
+
+ /** */
+ @Test
+ public void test() throws Exception {
+ runOperation(operation(grid(CLNT_ALLOWED_WRITE_PROP)));
+ runForbiddenOperation(operation(grid(CLNT_FORBIDDEN_WRITE_PROP)), AccessControlException.class);
+ }
+
+ /**
+ * @return Operation to test.
+ */
+ private GridTestUtils.RunnableX operation(Ignite node) {
+ return () -> {
+ try (IgniteDataStreamer<Integer, Integer> strm = node.dataStreamer(TEST_CACHE)) {
+ strm.receiver((cache, entries) -> controlAction());
+
+ strm.addData(1, 100);
+ }
+ };
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DoPrivilegedOnRemoteNodeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DoPrivilegedOnRemoteNodeTest.java
new file mode 100644
index 0000000..a5f46ef
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DoPrivilegedOnRemoteNodeTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.AccessControlException;
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCompute;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.lang.IgniteRunnable;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.spi.deployment.local.LocalDeploymentSpi;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+
+/**
+ * This test shows that an user-defined code with a privileged block cannot execute a secure-sensitive operation.
+ */
+public class DoPrivilegedOnRemoteNodeTest extends AbstractSandboxTest {
+ /** */
+ private static final String CALLABLE_DO_PRIVELEGED_SRC =
+ "import java.security.AccessController;\n" +
+ "import java.security.PrivilegedAction;\n" +
+ "import org.apache.ignite.lang.IgniteCallable;\n" +
+ "\n" +
+ "public class TestDoPrivilegedIgniteCallable implements IgniteCallable<String> {\n" +
+ " public String call() throws Exception {\n" +
+ " return AccessController.doPrivileged(\n" +
+ " (PrivilegedAction<String>)() -> System.getProperty(\"user.home\")\n" +
+ " );\n" +
+ " }\n" +
+ "}";
+
+ /** */
+ private static final String CALLABLE_SECURITY_UTILS_SRC =
+ "import org.apache.ignite.internal.processors.security.SecurityUtils;\n" +
+ "import org.apache.ignite.lang.IgniteCallable;\n" +
+ "\n" +
+ "public class TestSecurityUtilsCallable implements IgniteCallable<String> {\n" +
+ " @Override public String call() throws Exception {\n" +
+ " return SecurityUtils.doPrivileged(() ->{\n" +
+ " return System.getProperty(\"user.home\");\n" +
+ " });\n" +
+ " }\n" +
+ "}";
+
+ /** Client node name. */
+ private static final String CLNT_NODE = "clnt";
+
+ /** */
+ private Path srcTmpDir;
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setDeploymentSpi(new LocalDeploymentSpi());
+ }
+
+ /** */
+ @Before
+ public void prepare() throws IOException {
+ srcTmpDir = Files.createTempDirectory(getClass().getSimpleName());
+ }
+
+ /** */
+ @After
+ public void cleanup() {
+ U.delete(srcTmpDir);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void prepareCluster() throws Exception {
+ Ignite srv = startGrid("srv", ALLOW_ALL, false);
+
+ startGrid(CLNT_NODE, ALLOW_ALL, true);
+
+ srv.cluster().active(true);
+ }
+
+ /** */
+ @Test
+ public void testDoPrivilegedIgniteCallable() throws Exception {
+ checkCallable(clientCompute(), callable("TestDoPrivilegedIgniteCallable", CALLABLE_DO_PRIVELEGED_SRC));
+ }
+
+ /** */
+ @Test
+ public void testSecurityUtilsCallable() throws Exception {
+ checkCallable(clientCompute(), callable("TestSecurityUtilsCallable", CALLABLE_SECURITY_UTILS_SRC));
+ }
+
+ /** */
+ @Test
+ public void testIgniteProxy(){
+ runForbiddenOperation(() -> clientCompute().broadcast(new TestRunnable() {
+ @Override public void run() {
+ assertTrue(Proxy.isProxyClass(ignite.getClass()));
+
+ ignite.compute().broadcast(() -> System.getProperty("user.home"));
+ }
+ }), AccessControlException.class);
+ }
+
+ /** */
+ private IgniteCompute clientCompute(){
+ Ignite clnt = grid(CLNT_NODE);
+
+ return clnt.compute(clnt.cluster().forRemotes());
+ }
+
+ /** */
+ private void checkCallable(IgniteCompute compute, IgniteCallable<String> c) throws Exception {
+ assertEquals(System.getProperty("user.home"), c.call());
+
+ assertThrowsWithCause(() -> compute.broadcast(c), AccessControlException.class);
+ }
+
+ /** */
+ IgniteCallable<String> callable(String clsName, String src) {
+ try {
+ Files.createDirectories(srcTmpDir);
+
+ File srcFile = new File(srcTmpDir.toFile(), clsName + ".java");
+
+ Path srcFilePath = Files.write(srcFile.toPath(), src.getBytes(StandardCharsets.UTF_8));
+
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+
+ compiler.run(null, null, null, srcFilePath.toString());
+
+ assertTrue("Failed to remove source file.", srcFile.delete());
+
+ URLClassLoader clsLdr = new URLClassLoader(new URL[] {srcTmpDir.toUri().toURL()});
+
+ Class<?> cls = clsLdr.loadClass(clsName);
+
+ return (IgniteCallable<String>)cls.newInstance();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** */
+ abstract static class TestRunnable implements IgniteRunnable{
+ /** */
+ @IgniteInstanceResource
+ protected Ignite ignite;
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/IgniteOperationsInsideSandboxTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/IgniteOperationsInsideSandboxTest.java
new file mode 100644
index 0000000..c7b00b9
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/IgniteOperationsInsideSandboxTest.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCompute;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.CacheEntryProcessor;
+import org.apache.ignite.cache.query.ScanQuery;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.compute.ComputeJob;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.compute.ComputeJobResultPolicy;
+import org.apache.ignite.compute.ComputeTask;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.lang.IgniteClosure;
+import org.apache.ignite.lang.IgniteRunnable;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL;
+
+/**
+ * A user-defined code inside the sandbox can use the public API of Ignite without additional
+ * sandbox permissions.
+ */
+public class IgniteOperationsInsideSandboxTest extends AbstractSandboxTest {
+ /** Test compute task. */
+ private static final ComputeTask<Object, Object> TEST_COMPUTE_TASK = new ComputeTask<Object, Object>() {
+ /** {@inheritDoc} */
+ @Override public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> subgrid, Object arg) {
+ return Collections.singletonMap(
+ new ComputeJob() {
+ @Override public void cancel() {
+ // No-op.
+ }
+
+ @Override public Object execute() {
+ return null;
+ }
+ }, subgrid.stream().findFirst().orElseThrow(IllegalStateException::new)
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override public ComputeJobResultPolicy result(ComputeJobResult res, List<ComputeJobResult> rcvd) {
+ if (res.getException() != null)
+ throw res.getException();
+
+ return ComputeJobResultPolicy.REDUCE;
+ }
+
+ /** {@inheritDoc} */
+ @Override public @Nullable Integer reduce(List<ComputeJobResult> results) {
+ return null;
+ }
+ };
+
+ /** Test callable. */
+ private static final IgniteCallable<Object> TEST_CALLABLE = new IgniteCallable<Object>() {
+ @Override public Object call() {
+ return null;
+ }
+ };
+
+ /** Test runnable. */
+ private static final IgniteRunnable TEST_RUNNABLE = new IgniteRunnable() {
+ @Override public void run() {
+ //No-op.
+ }
+ };
+
+ /** Test closure. */
+ private static final IgniteClosure<Object, Object> TEST_CLOSURE = new IgniteClosure<Object, Object>() {
+ @Override public Object apply(Object o) {
+ return null;
+ }
+ };
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setCacheConfiguration(
+ new CacheConfiguration<String, String>(TEST_CACHE)
+ .setCacheStoreFactory(new TestStoreFactory("1", "val"))
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void prepareCluster() throws Exception {
+ Ignite srv = startGrid(SRV, ALLOW_ALL, false);
+
+ startGrid("srv_2", ALLOW_ALL, false);
+
+ startGrid(CLNT_ALLOWED_WRITE_PROP, ALLOW_ALL, true);
+
+ srv.cluster().active(true);
+ }
+
+ /** */
+ @Test
+ public void testComputeOperations() {
+ compute().broadcast(
+ new TestRunnable() {
+ @Override public void run() {
+ ignite.compute().execute(TEST_COMPUTE_TASK, 0);
+ ignite.compute().broadcast(TEST_CALLABLE);
+ ignite.compute().call(TEST_CALLABLE);
+ ignite.compute().run(TEST_RUNNABLE);
+ ignite.compute().apply(TEST_CLOSURE, new Object());
+ ignite.compute().executeAsync(TEST_COMPUTE_TASK, 0).get();
+ ignite.compute().broadcastAsync(TEST_CALLABLE).get();
+ ignite.compute().callAsync(TEST_CALLABLE).get();
+ ignite.compute().runAsync(TEST_RUNNABLE).get();
+ ignite.compute().applyAsync(TEST_CLOSURE, new Object()).get();
+ try {
+ ignite.executorService().invokeAll(singletonList(TEST_CALLABLE));
+ ignite.executorService().invokeAny(singletonList(TEST_CALLABLE));
+ ignite.executorService().submit(TEST_CALLABLE).get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ throw new IgniteException(e);
+ }
+ }
+ }
+ );
+ }
+
+ /** */
+ @Test
+ public void testCacheOperations() {
+ compute().broadcast(
+ new TestRunnable() {
+ @Override public void run() {
+ IgniteCache<String, String> cache = ignite.cache(TEST_CACHE);
+
+ cache.put("key", "val");
+ cache.putAll(singletonMap("key", "value"));
+ cache.get("key");
+ cache.getAll(Collections.singleton("key"));
+ cache.containsKey("key");
+ cache.remove("key");
+ cache.removeAll(Collections.singleton("key"));
+ cache.clear();
+ cache.replace("key", "value");
+ cache.putIfAbsent("key", "value");
+ cache.getAndPut("key", "value");
+ cache.getAndRemove("key");
+ cache.getAndReplace("key", "value");
+
+ cache.invoke("key", processor());
+ cache.invokeAll(singleton("key"), processor());
+ cache.invokeAsync("key", processor()).get();
+ cache.invokeAllAsync(singleton("key"), processor()).get();
+
+ cache.query(new ScanQuery<String, Integer>()).getAll();
+ }
+ }
+ );
+ }
+
+ /** */
+ @Test
+ public void testDataStreamerOperations() {
+ compute().broadcast(
+ new TestRunnable() {
+ @Override public void run() {
+ try (IgniteDataStreamer<String, String> s = ignite.dataStreamer(TEST_CACHE)) {
+ s.addData("k", "val");
+ s.addData(singletonMap("key", "val"));
+ s.addData((Map.Entry<String, String>)entry());
+ s.addData(singletonList(entry()));
+ }
+ }
+ });
+ }
+
+ /** */
+ private IgniteCompute compute(){
+ Ignite clnt = grid(CLNT_ALLOWED_WRITE_PROP);
+
+ return clnt.compute(clnt.cluster().forRemotes());
+ }
+
+ /** */
+ private CacheEntryProcessor<String, String, String> processor() {
+ return (entry, o) -> {
+ entry.setValue("Val");
+
+ return null;
+ };
+ }
+
+ /**
+ * @return Cache entry for test.
+ */
+ private T2<String, String> entry() {
+ return new T2<>("key", "val");
+ }
+
+ /** */
+ private abstract static class TestRunnable implements IgniteRunnable {
+ @IgniteInstanceResource
+ protected Ignite ignite;
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/SecuritySubjectPermissionsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/SecuritySubjectPermissionsTest.java
new file mode 100644
index 0000000..92e4b66
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/SecuritySubjectPermissionsTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.security.sandbox;
+
+import java.io.FilePermission;
+import java.io.SerializablePermission;
+import java.lang.management.ManagementPermission;
+import java.lang.reflect.ReflectPermission;
+import java.net.SocketPermission;
+import java.security.AccessControlException;
+import java.security.BasicPermission;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.SecurityPermission;
+import java.util.PropertyPermission;
+import javax.management.MBeanPermission;
+import javax.management.MBeanServerPermission;
+import javax.management.MBeanTrustPermission;
+import javax.net.ssl.SSLPermission;
+import org.apache.ignite.Ignite;
+import org.junit.Test;
+
+import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+
+/**
+ * The security subject cannot have access higher than Ignite himself.
+ */
+public class SecuritySubjectPermissionsTest extends AbstractSandboxTest {
+ /** */
+ @Test
+ public void test() throws Exception {
+ Ignite srv = startGrid("srv", ALLOW_ALL, false);
+
+ Permissions perms = new Permissions();
+ // Permission that Ignite and subject do have.
+ perms.add(new TestPermission("common"));
+ // Permission that Ignite does not have.
+ perms.add(new TestPermission("only_subject"));
+
+ Ignite clnt = startGrid("clnt", ALLOW_ALL, perms, true);
+
+ srv.cluster().active(true);
+
+ clnt.compute().broadcast(() -> securityManager().checkPermission(new TestPermission("common")));
+
+ assertThrowsWithCause(() -> clnt.compute().broadcast(
+ () -> {
+ securityManager().checkPermission(new TestPermission("only_subject"));
+
+ fail();
+ }),
+ AccessControlException.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ if (System.getSecurityManager() == null) {
+ Policy.setPolicy(new Policy() {
+ @Override public PermissionCollection getPermissions(CodeSource cs) {
+ Permissions res = new Permissions();
+
+ res.add(new RuntimePermission("*"));
+ res.add(new MBeanServerPermission("*"));
+ res.add(new MBeanPermission("*", "*"));
+ res.add(new MBeanTrustPermission("*"));
+ res.add(new ReflectPermission("*"));
+ res.add(new SSLPermission("*"));
+ res.add(new ManagementPermission("monitor"));
+ res.add(new ManagementPermission("control"));
+ res.add(new SerializablePermission("*"));
+ res.add(new SecurityPermission("*"));
+ res.add(new SocketPermission("*", "connect,accept,listen,resolve"));
+ res.add(new FilePermission("<<ALL FILES>>", "read,write,delete,execute,readlink"));
+ res.add(new PropertyPermission("*", "read,write"));
+
+ res.add(new TestPermission("common"));
+
+ return res;
+ }
+ });
+
+ System.setSecurityManager(new SecurityManager());
+
+ setupSM = true;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTestsStopped() throws Exception {
+ super.afterTestsStopped();
+
+ if (setupSM) {
+ System.setSecurityManager(null);
+ Policy.setPolicy(null);
+ }
+ }
+
+ /** */
+ private SecurityManager securityManager(){
+ SecurityManager sm = System.getSecurityManager();
+
+ assertNotNull(sm);
+
+ return sm;
+ }
+
+ /** Permission for test. */
+ public static class TestPermission extends BasicPermission {
+ /** */
+ public TestPermission(String name) {
+ super(name);
+ }
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index 3ad1402..658172e 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -140,8 +140,6 @@ import org.junit.runners.Suite;
IgnitePlatformsTestSuite.class,
- SecurityTestSuite.class,
-
GridSelfTest.class,
ClusterGroupHostsSelfTest.class,
IgniteMessagingWithClientTest.class,
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
index a4d1815..1d54e75 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
@@ -32,6 +32,12 @@ import org.apache.ignite.internal.processors.security.compute.closure.Distribute
import org.apache.ignite.internal.processors.security.compute.closure.ExecutorServiceRemoteSecurityContextCheckTest;
import org.apache.ignite.internal.processors.security.datastreamer.DataStreamerPermissionCheckTest;
import org.apache.ignite.internal.processors.security.datastreamer.closure.DataStreamerRemoteSecurityContextCheckTest;
+import org.apache.ignite.internal.processors.security.sandbox.CacheSandboxTest;
+import org.apache.ignite.internal.processors.security.sandbox.ComputeSandboxTest;
+import org.apache.ignite.internal.processors.security.sandbox.DataStreamerSandboxTest;
+import org.apache.ignite.internal.processors.security.sandbox.DoPrivilegedOnRemoteNodeTest;
+import org.apache.ignite.internal.processors.security.sandbox.IgniteOperationsInsideSandboxTest;
+import org.apache.ignite.internal.processors.security.sandbox.SecuritySubjectPermissionsTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -45,6 +51,7 @@ import org.junit.runners.Suite;
ScanQueryPermissionCheckTest.class,
EntryProcessorPermissionCheckTest.class,
ComputePermissionCheckTest.class,
+ ThinClientPermissionCheckTest.class,
DistributedClosureRemoteSecurityContextCheckTest.class,
ComputeTaskRemoteSecurityContextCheckTest.class,
@@ -54,9 +61,15 @@ import org.junit.runners.Suite;
EntryProcessorRemoteSecurityContextCheckTest.class,
DataStreamerRemoteSecurityContextCheckTest.class,
CacheLoadRemoteSecurityContextCheckTest.class,
- ThinClientPermissionCheckTest.class,
InvalidServerTest.class,
+
+ CacheSandboxTest.class,
+ DataStreamerSandboxTest.class,
+ ComputeSandboxTest.class,
+ DoPrivilegedOnRemoteNodeTest.class,
+ IgniteOperationsInsideSandboxTest.class,
+ SecuritySubjectPermissionsTest.class
})
public class SecurityTestSuite {
}