| /* |
| * 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); |
| } |
| } |