blob: 13d1945f437b64ba7ae087e652048cc72561b85f [file] [log] [blame]
/*
* 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.functions;
import java.security.AccessControlException;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.Enumeration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.TurboFilterList;
import ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter;
import ch.qos.logback.classic.turbo.TurboFilter;
/**
* Custom {@link SecurityManager} and {@link Policy} implementation that only performs access checks
* if explicitly enabled.
* <p>
* This implementation gives no measurable performance panalty
* (see <a href="http://cstar.datastax.com/tests/id/1d461628-12ba-11e5-918f-42010af0688f">see cstar test</a>).
* This is better than the penalty of 1 to 3 percent using a standard {@code SecurityManager} with an <i>allow all</i> policy.
* </p>
*/
public final class ThreadAwareSecurityManager extends SecurityManager
{
static final PermissionCollection noPermissions = new PermissionCollection()
{
public void add(Permission permission)
{
throw new UnsupportedOperationException();
}
public boolean implies(Permission permission)
{
return false;
}
public Enumeration<Permission> elements()
{
return Collections.emptyEnumeration();
}
};
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()
{
if (installed)
return;
System.setSecurityManager(new ThreadAwareSecurityManager());
// The default logback configuration in conf/logback.xml allows reloading the
// configuration when the configuration file has changed (every 60 seconds by default).
// This requires logback to use file I/O APIs. But file I/O is not allowed from UDFs.
// I.e. if logback decides to check for a modification of the config file while
// executiing a sandbox thread, the UDF execution and therefore the whole request
// execution will fail with an AccessControlException.
// To work around this, a custom ReconfigureOnChangeFilter is installed, that simply
// prevents this configuration file check and possible reload of the configration,
// while executing sandboxed UDF code.
Logger l = LoggerFactory.getLogger(ThreadAwareSecurityManager.class);
ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) l;
LoggerContext ctx = logbackLogger.getLoggerContext();
TurboFilterList turboFilterList = ctx.getTurboFilterList();
for (int i = 0; i < turboFilterList.size(); i++)
{
TurboFilter turboFilter = turboFilterList.get(i);
if (turboFilter instanceof ReconfigureOnChangeFilter)
{
ReconfigureOnChangeFilter reconfigureOnChangeFilter = (ReconfigureOnChangeFilter) turboFilter;
turboFilterList.set(i, new SMAwareReconfigureOnChangeFilter(reconfigureOnChangeFilter));
break;
}
}
installed = true;
}
/**
* The purpose of this class is to prevent logback from checking for config file change,
* if the current thread is executing a sandboxed thread to avoid {@link AccessControlException}s.
*/
private static class SMAwareReconfigureOnChangeFilter extends ReconfigureOnChangeFilter
{
SMAwareReconfigureOnChangeFilter(ReconfigureOnChangeFilter reconfigureOnChangeFilter)
{
setRefreshPeriod(reconfigureOnChangeFilter.getRefreshPeriod());
setName(reconfigureOnChangeFilter.getName());
setContext(reconfigureOnChangeFilter.getContext());
if (reconfigureOnChangeFilter.isStarted())
{
reconfigureOnChangeFilter.stop();
start();
}
}
protected boolean changeDetected(long now)
{
if (isSecuredThread())
return false;
return super.changeDetected(now);
}
}
static
{
//
// Use own security policy to be easier (and faster) since the C* has no fine grained permissions.
// Either code has access to everything or code has access to nothing (UDFs).
// This also removes the burden to maintain and configure policy files for production, unit tests etc.
//
// Note: a permission is only granted, if there is no objector. This means that
// AccessController/AccessControlContext collect all applicable ProtectionDomains - only if none of these
// applicable ProtectionDomains denies access, the permission is granted.
// A ProtectionDomain can have its origin at an oridinary code-source or provided via a
// AccessController.doPrivileded() call.
//
Policy.setPolicy(new Policy()
{
public PermissionCollection getPermissions(CodeSource codesource)
{
// contract of getPermissions() methods is to return a _mutable_ PermissionCollection
Permissions perms = new Permissions();
if (codesource == null || codesource.getLocation() == null)
return perms;
switch (codesource.getLocation().getProtocol())
{
case "file":
// All JARs and class files reside on the file system - we can safely
// assume that these classes are "good".
perms.add(new AllPermission());
return perms;
}
return perms;
}
public PermissionCollection getPermissions(ProtectionDomain domain)
{
return getPermissions(domain.getCodeSource());
}
public boolean implies(ProtectionDomain domain, Permission permission)
{
CodeSource codesource = domain.getCodeSource();
if (codesource == null || codesource.getLocation() == null)
return false;
switch (codesource.getLocation().getProtocol())
{
case "file":
// All JARs and class files reside on the file system - we can safely
// assume that these classes are "good".
return true;
}
return false;
}
});
}
private static final ThreadLocal<Boolean> initializedThread = new ThreadLocal<>();
private ThreadAwareSecurityManager()
{
}
private static boolean isSecuredThread()
{
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 only checks the permission if the current thread's
// in the root-thread-group
if (isSecuredThread())
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 only checks the permission if the current thread's
// in the root-thread-group
if (isSecuredThread())
throw new AccessControlException("access denied: " + MODIFY_THREADGROUP_PERMISSION, MODIFY_THREADGROUP_PERMISSION);
super.checkAccess(g);
}
public void checkPermission(Permission 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)
{
if (isSecuredThread())
super.checkPermission(perm, context);
}
public void checkPackageAccess(String pkg)
{
if (!isSecuredThread())
return;
if (!((SecurityThreadGroup) Thread.currentThread().getThreadGroup()).isPackageAllowed(pkg))
{
RuntimePermission perm = new RuntimePermission("accessClassInPackage." + pkg);
throw new AccessControlException("access denied: " + perm, perm);
}
super.checkPackageAccess(pkg);
}
}