mirror of https://github.com/apache/cassandra
Merge branch 'cassandra-3.0' into trunk
This commit is contained in:
commit
247bc2e67e
|
|
@ -70,7 +70,7 @@ final class JavaBasedUDFunction extends UDFunction
|
|||
new UDFExecutorService(new NamedThreadFactory("UserDefinedFunctions",
|
||||
Thread.MIN_PRIORITY,
|
||||
udfClassLoader,
|
||||
new SecurityThreadGroup("UserDefinedFunctions", null)),
|
||||
new SecurityThreadGroup("UserDefinedFunctions", null, UDFunction::initializeThread)),
|
||||
"userfunction");
|
||||
|
||||
private static final EcjTargetClassLoader targetClassLoader = new EcjTargetClassLoader();
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ final class ScriptBasedUDFunction extends UDFunction
|
|||
new UDFExecutorService(new NamedThreadFactory("UserDefinedScriptFunctions",
|
||||
Thread.MIN_PRIORITY,
|
||||
udfClassLoader,
|
||||
new SecurityThreadGroup("UserDefinedScriptFunctions", Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedPackagesArray))))),
|
||||
new SecurityThreadGroup("UserDefinedScriptFunctions",
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedPackagesArray))),
|
||||
UDFunction::initializeThread)),
|
||||
"userscripts");
|
||||
|
||||
static
|
||||
|
|
|
|||
|
|
@ -26,15 +26,28 @@ import java.util.Set;
|
|||
public final class SecurityThreadGroup extends ThreadGroup
|
||||
{
|
||||
private final Set<String> allowedPackages;
|
||||
private final ThreadInitializer threadInitializer;
|
||||
|
||||
public SecurityThreadGroup(String name, Set<String> allowedPackages)
|
||||
public SecurityThreadGroup(String name, Set<String> allowedPackages, ThreadInitializer threadInitializer)
|
||||
{
|
||||
super(name);
|
||||
this.allowedPackages = allowedPackages;
|
||||
this.threadInitializer = threadInitializer;
|
||||
}
|
||||
|
||||
public Set<String> getAllowedPackages()
|
||||
public void initializeThread()
|
||||
{
|
||||
return allowedPackages;
|
||||
threadInitializer.initializeThread();
|
||||
}
|
||||
|
||||
public boolean isPackageAllowed(String pkg)
|
||||
{
|
||||
return allowedPackages == null || allowedPackages.contains(pkg);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ThreadInitializer
|
||||
{
|
||||
void initializeThread();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ import java.security.Policy;
|
|||
import java.security.ProtectionDomain;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
|
||||
import sun.security.util.SecurityConstants;
|
||||
|
||||
/**
|
||||
* Custom {@link SecurityManager} and {@link Policy} implementation that only performs access checks
|
||||
|
|
@ -61,6 +58,10 @@ public final class ThreadAwareSecurityManager extends SecurityManager
|
|||
}
|
||||
};
|
||||
|
||||
private static final RuntimePermission CHECK_MEMBER_ACCESS_PERMISSION = new RuntimePermission("accessDeclaredMembers");
|
||||
private static final RuntimePermission MODIFY_THREAD_PERMISSION = new RuntimePermission("modifyThread");
|
||||
private static final RuntimePermission MODIFY_THREADGROUP_PERMISSION = new RuntimePermission("modifyThreadGroup");
|
||||
|
||||
private static volatile boolean installed;
|
||||
|
||||
public static void install()
|
||||
|
|
@ -131,37 +132,59 @@ public final class ThreadAwareSecurityManager extends SecurityManager
|
|||
});
|
||||
}
|
||||
|
||||
private static final ThreadLocal<Boolean> initializedThread = new ThreadLocal<>();
|
||||
|
||||
private ThreadAwareSecurityManager()
|
||||
{
|
||||
}
|
||||
|
||||
private static boolean isSecuredThread()
|
||||
{
|
||||
return Thread.currentThread().getThreadGroup() instanceof SecurityThreadGroup;
|
||||
ThreadGroup tg = Thread.currentThread().getThreadGroup();
|
||||
if (!(tg instanceof SecurityThreadGroup))
|
||||
return false;
|
||||
Boolean threadInitialized = initializedThread.get();
|
||||
if (threadInitialized == null)
|
||||
{
|
||||
initializedThread.set(false);
|
||||
((SecurityThreadGroup) tg).initializeThread();
|
||||
initializedThread.set(true);
|
||||
threadInitialized = true;
|
||||
}
|
||||
return threadInitialized;
|
||||
}
|
||||
|
||||
public void checkAccess(Thread t)
|
||||
{
|
||||
// need to override since the default implementation is kind of ...
|
||||
// need to override since the default implementation only checks the permission if the current thread's
|
||||
// in the root-thread-group
|
||||
|
||||
if (isSecuredThread())
|
||||
throw new AccessControlException("access denied: " + SecurityConstants.MODIFY_THREAD_PERMISSION, SecurityConstants.MODIFY_THREAD_PERMISSION);
|
||||
throw new AccessControlException("access denied: " + MODIFY_THREAD_PERMISSION, MODIFY_THREAD_PERMISSION);
|
||||
super.checkAccess(t);
|
||||
}
|
||||
|
||||
public void checkAccess(ThreadGroup g)
|
||||
{
|
||||
// need to override since the default implementation is kind of ...
|
||||
// need to override since the default implementation only checks the permission if the current thread's
|
||||
// in the root-thread-group
|
||||
|
||||
if (isSecuredThread())
|
||||
throw new AccessControlException("access denied: " + SecurityConstants.MODIFY_THREADGROUP_PERMISSION, SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
|
||||
throw new AccessControlException("access denied: " + MODIFY_THREADGROUP_PERMISSION, MODIFY_THREADGROUP_PERMISSION);
|
||||
super.checkAccess(g);
|
||||
}
|
||||
|
||||
public void checkPermission(Permission perm)
|
||||
{
|
||||
if (isSecuredThread())
|
||||
super.checkPermission(perm);
|
||||
if (!isSecuredThread())
|
||||
return;
|
||||
|
||||
// required by JavaDriver 2.2.0-rc3 and 3.0.0-a2 or newer
|
||||
// code in com.datastax.driver.core.CodecUtils uses Guava stuff, which in turns requires this permission
|
||||
if (CHECK_MEMBER_ACCESS_PERMISSION.equals(perm))
|
||||
return;
|
||||
|
||||
super.checkPermission(perm);
|
||||
}
|
||||
|
||||
public void checkPermission(Permission perm, Object context)
|
||||
|
|
@ -172,13 +195,15 @@ public final class ThreadAwareSecurityManager extends SecurityManager
|
|||
|
||||
public void checkPackageAccess(String pkg)
|
||||
{
|
||||
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
|
||||
if (threadGroup instanceof SecurityThreadGroup)
|
||||
if (!isSecuredThread())
|
||||
return;
|
||||
|
||||
if (!((SecurityThreadGroup) Thread.currentThread().getThreadGroup()).isPackageAllowed(pkg))
|
||||
{
|
||||
Set<String> allowedPackages = ((SecurityThreadGroup) threadGroup).getAllowedPackages();
|
||||
if (allowedPackages != null && !allowedPackages.contains(pkg))
|
||||
throw new AccessControlException("access denied: " + new RuntimePermission("accessClassInPackage." + pkg), new RuntimePermission("accessClassInPackage." + pkg));
|
||||
super.checkPackageAccess(pkg);
|
||||
RuntimePermission perm = new RuntimePermission("accessClassInPackage." + pkg);
|
||||
throw new AccessControlException("access denied: " + perm, perm);
|
||||
}
|
||||
|
||||
super.checkPackageAccess(pkg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,6 +296,14 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct
|
|||
throw new InvalidRequestException("Scripted user-defined functions are disabled in cassandra.yaml - set enable_scripted_user_defined_functions=true to enable if you are aware of the security risks");
|
||||
}
|
||||
|
||||
static void initializeThread()
|
||||
{
|
||||
// Get the TypeCodec stuff in Java Driver initialized.
|
||||
// This is to get the classes loaded outside of the restricted sandbox's security context of a UDF.
|
||||
UDHelper.codecRegistry.codecFor(DataType.inet()).format(InetAddress.getLoopbackAddress());
|
||||
UDHelper.codecRegistry.codecFor(DataType.ascii()).format("");
|
||||
}
|
||||
|
||||
private static final class ThreadIdAndCpuTime extends CompletableFuture<Object>
|
||||
{
|
||||
long threadId;
|
||||
|
|
@ -309,10 +317,6 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct
|
|||
// because class loading would be deferred until setup() is executed - but setup() is called with
|
||||
// limited privileges.
|
||||
threadMXBean.getCurrentThreadCpuTime();
|
||||
//
|
||||
// Get the TypeCodec stuff in Java Driver initialized.
|
||||
UDHelper.codecRegistry.codecFor(DataType.inet()).format(InetAddress.getLoopbackAddress());
|
||||
UDHelper.codecRegistry.codecFor(DataType.ascii()).format("");
|
||||
}
|
||||
|
||||
void setup()
|
||||
|
|
|
|||
|
|
@ -32,15 +32,11 @@ import java.util.UUID;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.TupleType;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.functions.FunctionName;
|
||||
import org.apache.cassandra.exceptions.FunctionExecutionException;
|
||||
import org.apache.cassandra.transport.Server;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
public class UFPureScriptTest extends CQLTester
|
||||
|
|
@ -118,101 +114,6 @@ public class UFPureScriptTest extends CQLTester
|
|||
row(t));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptTupleTypeCollection() throws Throwable
|
||||
{
|
||||
String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>";
|
||||
createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)");
|
||||
|
||||
String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup;$$;");
|
||||
String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getDouble(0);$$;");
|
||||
String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS list<double> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getList(1, java.lang.Double.class);$$;");
|
||||
String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS set<text> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getSet(2, java.lang.String.class);$$;");
|
||||
String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS map<int, boolean> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getMap(3, java.lang.Integer.class, java.lang.Boolean.class);$$;");
|
||||
|
||||
List<Double> list = Arrays.asList(1d, 2d, 3d);
|
||||
Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two"));
|
||||
Map<Integer, Boolean> map = new TreeMap<>();
|
||||
map.put(1, true);
|
||||
map.put(2, false);
|
||||
map.put(3, true);
|
||||
|
||||
Object t = tuple(1d, list, set, map);
|
||||
|
||||
execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t);
|
||||
|
||||
assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(t));
|
||||
assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(1d));
|
||||
assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(list));
|
||||
assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(set));
|
||||
assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(map));
|
||||
|
||||
// same test - but via native protocol
|
||||
// we use protocol V3 here to encode the expected version because the server
|
||||
// always serializes Collections using V3 - see CollectionSerializer's
|
||||
// serialize and deserialize methods.
|
||||
TupleType tType = tupleTypeOf(Server.VERSION_3,
|
||||
DataType.cdouble(),
|
||||
DataType.list(DataType.cdouble()),
|
||||
DataType.set(DataType.text()),
|
||||
DataType.map(DataType.cint(),
|
||||
DataType.cboolean()));
|
||||
TupleValue tup = tType.newValue(1d, list, set, map);
|
||||
for (int version : PROTOCOL_VERSIONS)
|
||||
{
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(tup));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(1d));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(list));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(set));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(map));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptUserType() throws Throwable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.cassandra.cql3.validation.entities;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.TupleType;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.transport.Server;
|
||||
|
||||
public class UFPureScriptTupleCollectionTest extends CQLTester
|
||||
{
|
||||
// Just JavaScript UDFs to check how UDF - especially security/class-loading/sandboxing stuff -
|
||||
// behaves, if no Java UDF has been executed before.
|
||||
|
||||
// Do not add any other test here!
|
||||
// See CASSANDRA-10141
|
||||
|
||||
@Test
|
||||
public void testJavascriptTupleTypeCollection() throws Throwable
|
||||
{
|
||||
String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>";
|
||||
createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)");
|
||||
|
||||
String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup;$$;");
|
||||
String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getDouble(0);$$;");
|
||||
String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS list<double> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getList(1, java.lang.Double.class);$$;");
|
||||
String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS set<text> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getSet(2, java.lang.String.class);$$;");
|
||||
String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS map<int, boolean> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getMap(3, java.lang.Integer.class, java.lang.Boolean.class);$$;");
|
||||
|
||||
List<Double> list = Arrays.asList(1d, 2d, 3d);
|
||||
Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two"));
|
||||
Map<Integer, Boolean> map = new TreeMap<>();
|
||||
map.put(1, true);
|
||||
map.put(2, false);
|
||||
map.put(3, true);
|
||||
|
||||
Object t = tuple(1d, list, set, map);
|
||||
|
||||
execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t);
|
||||
|
||||
assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(t));
|
||||
assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(1d));
|
||||
assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(list));
|
||||
assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(set));
|
||||
assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(map));
|
||||
|
||||
// same test - but via native protocol
|
||||
// we use protocol V3 here to encode the expected version because the server
|
||||
// always serializes Collections using V3 - see CollectionSerializer's
|
||||
// serialize and deserialize methods.
|
||||
TupleType tType = tupleTypeOf(Server.VERSION_3,
|
||||
DataType.cdouble(),
|
||||
DataType.list(DataType.cdouble()),
|
||||
DataType.set(DataType.text()),
|
||||
DataType.map(DataType.cint(),
|
||||
DataType.cboolean()));
|
||||
TupleValue tup = tType.newValue(1d, list, set, map);
|
||||
for (int version : PROTOCOL_VERSIONS)
|
||||
{
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(tup));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(1d));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(list));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(set));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(map));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue