Repository: cassandra Updated Branches: refs/heads/cassandra-3.11 d2248f206 -> bd0804065 refs/heads/trunk 39bcdcd32 -> 2ad06d65b
Allow logging implementation to be interchanged for embedded testing patch by Eric Hubert; reviewed by jasobrown for CASSANDRA-13396 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/bd080406 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/bd080406 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/bd080406 Branch: refs/heads/cassandra-3.11 Commit: bd0804065daaa01ba478c0ed97f7411f1180eef9 Parents: d2248f2 Author: Eric Hubert <eric.hub...@optivo.com> Authored: Fri Mar 23 18:24:17 2018 -0700 Committer: Jason Brown <jasedbr...@gmail.com> Committed: Mon Mar 26 04:08:54 2018 -0700 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/functions/JavaBasedUDFunction.java | 3 + .../cql3/functions/ScriptBasedUDFunction.java | 2 + .../cql3/functions/SecurityThreadGroup.java | 53 ---- .../functions/ThreadAwareSecurityManager.java | 270 ------------------- .../cassandra/security/SecurityThreadGroup.java | 53 ++++ .../security/ThreadAwareSecurityManager.java | 214 +++++++++++++++ .../cassandra/service/CassandraDaemon.java | 2 +- .../cassandra/service/StorageService.java | 51 +--- .../utils/logging/LogbackLoggingSupport.java | 146 ++++++++++ .../cassandra/utils/logging/LoggingSupport.java | 34 +++ .../utils/logging/LoggingSupportFactory.java | 42 +++ .../logging/NoOpFallbackLoggingSupport.java | 30 +++ .../org/apache/cassandra/cql3/CQLTester.java | 2 +- 14 files changed, 532 insertions(+), 371 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 987b9f7..5646081 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.11.3 + * Allow logging implementation to be interchanged for embedded testing (CASSANDRA-13396) * SASI tokenizer for simple delimiter based entries (CASSANDRA-14247) * Fix Loss of digits when doing CAST from varint/bigint to decimal (CASSANDRA-14170) * RateBasedBackPressure unnecessarily invokes a lock on the Guava RateLimiter (CASSANDRA-14163) http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java index 8f12899..feb17e3 100644 --- a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java @@ -40,6 +40,7 @@ import java.util.regex.Pattern; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.ByteStreams; import com.google.common.reflect.TypeToken; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +51,8 @@ import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.security.SecurityThreadGroup; +import org.apache.cassandra.security.ThreadAwareSecurityManager; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.*; import org.eclipse.jdt.internal.compiler.Compiler; http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java index c568972..41035a4 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java @@ -35,6 +35,8 @@ import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.security.SecurityThreadGroup; +import org.apache.cassandra.security.ThreadAwareSecurityManager; import org.apache.cassandra.transport.ProtocolVersion; final class ScriptBasedUDFunction extends UDFunction http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java b/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java deleted file mode 100644 index 8f50dc8..0000000 --- a/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.util.Set; - -/** - * Used by {@link ThreadAwareSecurityManager} to determine whether access-control checks needs to be performed. - */ -public final class SecurityThreadGroup extends ThreadGroup -{ - private final Set<String> allowedPackages; - private final ThreadInitializer threadInitializer; - - public SecurityThreadGroup(String name, Set<String> allowedPackages, ThreadInitializer threadInitializer) - { - super(name); - this.allowedPackages = allowedPackages; - this.threadInitializer = threadInitializer; - } - - public void initializeThread() - { - threadInitializer.initializeThread(); - } - - public boolean isPackageAllowed(String pkg) - { - return allowedPackages == null || allowedPackages.contains(pkg); - } - - @FunctionalInterface - interface ThreadInitializer - { - void initializeThread(); - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java b/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java deleted file mode 100644 index 9c5b95b..0000000 --- a/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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; -import io.netty.util.concurrent.FastThreadLocal; - -/** - * 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 FastThreadLocal<Boolean> initializedThread = new FastThreadLocal<>(); - - 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); - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/security/SecurityThreadGroup.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/security/SecurityThreadGroup.java b/src/java/org/apache/cassandra/security/SecurityThreadGroup.java new file mode 100644 index 0000000..c57a7b7 --- /dev/null +++ b/src/java/org/apache/cassandra/security/SecurityThreadGroup.java @@ -0,0 +1,53 @@ +/* + * 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.security; + +import java.util.Set; + +/** + * Used by {@link ThreadAwareSecurityManager} to determine whether access-control checks needs to be performed. + */ +public final class SecurityThreadGroup extends ThreadGroup +{ + private final Set<String> allowedPackages; + private final ThreadInitializer threadInitializer; + + public SecurityThreadGroup(String name, Set<String> allowedPackages, ThreadInitializer threadInitializer) + { + super(name); + this.allowedPackages = allowedPackages; + this.threadInitializer = threadInitializer; + } + + public void initializeThread() + { + threadInitializer.initializeThread(); + } + + public boolean isPackageAllowed(String pkg) + { + return allowedPackages == null || allowedPackages.contains(pkg); + } + + @FunctionalInterface + public interface ThreadInitializer + { + void initializeThread(); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/security/ThreadAwareSecurityManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/security/ThreadAwareSecurityManager.java b/src/java/org/apache/cassandra/security/ThreadAwareSecurityManager.java new file mode 100644 index 0000000..c9402f1 --- /dev/null +++ b/src/java/org/apache/cassandra/security/ThreadAwareSecurityManager.java @@ -0,0 +1,214 @@ +/* + * 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.security; + +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 io.netty.util.concurrent.FastThreadLocal; + +import org.apache.cassandra.utils.logging.LoggingSupportFactory; + +/** + * Custom {@link SecurityManager} and {@link Policy} implementation that only performs access checks + * if explicitly enabled. + * <p> + * This implementation gives no measurable performance penalty + * (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 +{ + public 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()); + LoggingSupportFactory.getLoggingSupport().onStartup(); + installed = true; + } + + 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 FastThreadLocal<Boolean> initializedThread = new FastThreadLocal<>(); + + private ThreadAwareSecurityManager() + { + } + + public 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); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/service/CassandraDaemon.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index 3dbf3d8..d9bd5c3 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -51,7 +51,6 @@ import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.config.SchemaConstants; -import org.apache.cassandra.cql3.functions.ThreadAwareSecurityManager; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.db.*; import org.apache.cassandra.db.commitlog.CommitLog; @@ -69,6 +68,7 @@ import org.apache.cassandra.schema.LegacySchemaMigrator; import org.apache.cassandra.thrift.ThriftServer; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.*; +import org.apache.cassandra.security.ThreadAwareSecurityManager; /** * The <code>CassandraDaemon</code> is an abstraction for a Cassandra daemon http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/service/StorageService.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index b743bf3..373493c 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -46,11 +46,6 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.jmx.JMXConfiguratorMBean; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.Appender; -import ch.qos.logback.core.hook.DelayingShutdownHook; import org.apache.cassandra.auth.AuthKeyspace; import org.apache.cassandra.auth.AuthMigrationListener; import org.apache.cassandra.batchlog.BatchRemoveVerbHandler; @@ -96,6 +91,7 @@ import org.apache.cassandra.thrift.cassandraConstants; import org.apache.cassandra.tracing.TraceKeyspace; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.*; +import org.apache.cassandra.utils.logging.LoggingSupportFactory; import org.apache.cassandra.utils.progress.ProgressEvent; import org.apache.cassandra.utils.progress.ProgressEventType; import org.apache.cassandra.utils.progress.jmx.JMXProgressSupport; @@ -645,10 +641,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (FBUtilities.isWindows) WindowsTimer.endTimerPeriod(DatabaseDescriptor.getWindowsTimerInterval()); - // Cleanup logback - DelayingShutdownHook logbackHook = new DelayingShutdownHook(); - logbackHook.setContext((LoggerContext)LoggerFactory.getILoggerFactory()); - logbackHook.run(); + LoggingSupportFactory.getLoggingSupport().onShutdown(); } }, "StorageServiceShutdownHook"); Runtime.getRuntime().addShutdownHook(drainOnShutdown); @@ -3811,50 +3804,16 @@ public class StorageService extends NotificationBroadcasterSupport implements IE public void setLoggingLevel(String classQualifier, String rawLevel) throws Exception { - ch.qos.logback.classic.Logger logBackLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(classQualifier); - - // if both classQualifer and rawLevel are empty, reload from configuration - if (StringUtils.isBlank(classQualifier) && StringUtils.isBlank(rawLevel) ) - { - JMXConfiguratorMBean jmxConfiguratorMBean = JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(), - new ObjectName("ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator"), - JMXConfiguratorMBean.class); - jmxConfiguratorMBean.reloadDefaultConfiguration(); - return; - } - // classQualifer is set, but blank level given - else if (StringUtils.isNotBlank(classQualifier) && StringUtils.isBlank(rawLevel) ) - { - if (logBackLogger.getLevel() != null || hasAppenders(logBackLogger)) - logBackLogger.setLevel(null); - return; - } - - ch.qos.logback.classic.Level level = ch.qos.logback.classic.Level.toLevel(rawLevel); - logBackLogger.setLevel(level); - logger.info("set log level to {} for classes under '{}' (if the level doesn't look like '{}' then the logger couldn't parse '{}')", level, classQualifier, rawLevel, rawLevel); + LoggingSupportFactory.getLoggingSupport().setLoggingLevel(classQualifier, rawLevel); } /** * @return the runtime logging levels for all the configured loggers */ @Override - public Map<String,String>getLoggingLevels() - { - Map<String, String> logLevelMaps = Maps.newLinkedHashMap(); - LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); - for (ch.qos.logback.classic.Logger logger : lc.getLoggerList()) - { - if(logger.getLevel() != null || hasAppenders(logger)) - logLevelMaps.put(logger.getName(), logger.getLevel().toString()); - } - return logLevelMaps; - } - - private boolean hasAppenders(ch.qos.logback.classic.Logger logger) + public Map<String,String> getLoggingLevels() { - Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders(); - return it.hasNext(); + return LoggingSupportFactory.getLoggingSupport().getLoggingLevels(); } /** http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/utils/logging/LogbackLoggingSupport.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/utils/logging/LogbackLoggingSupport.java b/src/java/org/apache/cassandra/utils/logging/LogbackLoggingSupport.java new file mode 100644 index 0000000..3229460 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/logging/LogbackLoggingSupport.java @@ -0,0 +1,146 @@ +package org.apache.cassandra.utils.logging; + +import java.lang.management.ManagementFactory; +import java.security.AccessControlException; +import java.util.Iterator; +import java.util.Map; + +import javax.management.JMX; +import javax.management.ObjectName; + +import org.apache.cassandra.security.ThreadAwareSecurityManager; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Maps; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.jmx.JMXConfiguratorMBean; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.TurboFilterList; +import ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter; +import ch.qos.logback.classic.turbo.TurboFilter; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.hook.DelayingShutdownHook; + +/** + * Encapsulates all logback-specific implementations in a central place. + * Generally, the Cassandra code-base should be logging-backend agnostic and only use slf4j-api. + * This class MUST NOT be used directly, but only via {@link LoggingSupportFactory} which dynamically loads and + * instantiates an appropriate implementation according to the used slf4j binding. + */ +public class LogbackLoggingSupport implements LoggingSupport +{ + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(LogbackLoggingSupport.class); + + @Override + public void onStartup() + { + // 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 + // executing 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 configuration, + // while executing sandboxed UDF code. + Logger logbackLogger = (Logger) LoggerFactory.getLogger(ThreadAwareSecurityManager.class); + 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; + } + } + } + + @Override + public void onShutdown() + { + DelayingShutdownHook logbackHook = new DelayingShutdownHook(); + logbackHook.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); + logbackHook.run(); + } + + @Override + public void setLoggingLevel(String classQualifier, String rawLevel) throws Exception + { + Logger logBackLogger = (Logger) LoggerFactory.getLogger(classQualifier); + + // if both classQualifier and rawLevel are empty, reload from configuration + if (StringUtils.isBlank(classQualifier) && StringUtils.isBlank(rawLevel)) + { + JMXConfiguratorMBean jmxConfiguratorMBean = JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(), + new ObjectName("ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator"), + JMXConfiguratorMBean.class); + jmxConfiguratorMBean.reloadDefaultConfiguration(); + return; + } + // classQualifier is set, but blank level given + else if (StringUtils.isNotBlank(classQualifier) && StringUtils.isBlank(rawLevel)) + { + if (logBackLogger.getLevel() != null || hasAppenders(logBackLogger)) + logBackLogger.setLevel(null); + return; + } + + Level level = Level.toLevel(rawLevel); + logBackLogger.setLevel(level); + logger.info("set log level to {} for classes under '{}' (if the level doesn't look like '{}' then the logger couldn't parse '{}')", level, classQualifier, rawLevel, rawLevel); + } + + @Override + public Map<String, String> getLoggingLevels() + { + Map<String, String> logLevelMaps = Maps.newLinkedHashMap(); + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + for (Logger logBackLogger : lc.getLoggerList()) + { + if (logBackLogger.getLevel() != null || hasAppenders(logBackLogger)) + logLevelMaps.put(logBackLogger.getName(), logBackLogger.getLevel().toString()); + } + return logLevelMaps; + } + + private boolean hasAppenders(Logger logBackLogger) + { + Iterator<Appender<ILoggingEvent>> it = logBackLogger.iteratorForAppenders(); + return it.hasNext(); + } + + /** + * 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 (ThreadAwareSecurityManager.isSecuredThread()) + return false; + return super.changeDetected(now); + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/utils/logging/LoggingSupport.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/utils/logging/LoggingSupport.java b/src/java/org/apache/cassandra/utils/logging/LoggingSupport.java new file mode 100644 index 0000000..3855ca7 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/logging/LoggingSupport.java @@ -0,0 +1,34 @@ +package org.apache.cassandra.utils.logging; + +import java.util.Map; + +/** + * Common abstraction of functionality which can be implemented for different logging backend implementations (slf4j bindings). + * Concrete implementations are dynamically loaded and instantiated by {@link LoggingSupportFactory#getLoggingSupport()}. + */ +public interface LoggingSupport +{ + /** + * Hook used to execute logging implementation specific customization at Cassandra startup time. + */ + default void onStartup() {} + + /** + * Hook used to execute logging implementation specific customization at Cassandra shutdown time. + */ + default void onShutdown() {} + + /** + * Changes the given logger to the given log level. + * + * @param classQualifier the class qualifier or logger name + * @param rawLevel the string representation of a log level + * @throws Exception an exception which may occur while changing the given logger to the given log level. + */ + void setLoggingLevel(String classQualifier, String rawLevel) throws Exception; + + /** + * @return a map of logger names and their associated log level as string representations. + */ + Map<String, String> getLoggingLevels(); +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/utils/logging/LoggingSupportFactory.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/utils/logging/LoggingSupportFactory.java b/src/java/org/apache/cassandra/utils/logging/LoggingSupportFactory.java new file mode 100644 index 0000000..3e7adab --- /dev/null +++ b/src/java/org/apache/cassandra/utils/logging/LoggingSupportFactory.java @@ -0,0 +1,42 @@ +package org.apache.cassandra.utils.logging; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.utils.FBUtilities; + +/** + * Dynamically loads and instantiates an appropriate {@link LoggingSupport} implementation according to the used slf4j binding. + * For production use, this should always be {@link LogbackLoggingSupport}. + */ +public class LoggingSupportFactory +{ + private static final Logger logger = LoggerFactory.getLogger(LoggingSupportFactory.class); + + private static volatile LoggingSupport loggingSupport; + + private LoggingSupportFactory() {} + + /** + * @return An appropriate {@link LoggingSupport} implementation according to the used slf4j binding. + */ + public static LoggingSupport getLoggingSupport() + { + if (loggingSupport == null) + { + // unfortunately, this is the best way to determine if logback is being used for logger + String loggerFactoryClass = LoggerFactory.getILoggerFactory().getClass().getName(); + if (loggerFactoryClass.contains("logback")) + { + loggingSupport = FBUtilities.instanceOrConstruct("org.apache.cassandra.utils.logging.LogbackLoggingSupport", "LogbackLoggingSupport"); + } + else + { + loggingSupport = new NoOpFallbackLoggingSupport(); + logger.warn("You are using Cassandra with an unsupported deployment. The intended logging implementation library logback is not used by slf4j. Detected slf4j logger factory: {}. " + + "You will not be able to dynamically manage log levels via JMX and may have performance or other issues.", loggerFactoryClass); + } + } + return loggingSupport; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/src/java/org/apache/cassandra/utils/logging/NoOpFallbackLoggingSupport.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/utils/logging/NoOpFallbackLoggingSupport.java b/src/java/org/apache/cassandra/utils/logging/NoOpFallbackLoggingSupport.java new file mode 100644 index 0000000..4865e09 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/logging/NoOpFallbackLoggingSupport.java @@ -0,0 +1,30 @@ +package org.apache.cassandra.utils.logging; + +import java.util.Collections; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A fallback implementation with empty implementations which ensures other slf4j bindings (logging implementations) + * than the default supported framework can be used. This loses functionality, but is perfectly fine for most + * integration test requirements of applications using an embedded cassandra server. + */ +public class NoOpFallbackLoggingSupport implements LoggingSupport +{ + private static final Logger logger = LoggerFactory.getLogger(NoOpFallbackLoggingSupport.class); + + @Override + public void setLoggingLevel(String classQualifier, String rawLevel) throws Exception + { + logger.warn("The log level was not changed, because you are using an unsupported slf4j logging implementation for which this functionality was not implemented."); + } + + @Override + public Map<String, String> getLoggingLevels() + { + logger.warn("An empty map of logger names and their logging levels was returned, because you are using an unsupported slf4j logging implementation for which this functionality was not implemented."); + return Collections.emptyMap(); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/bd080406/test/unit/org/apache/cassandra/cql3/CQLTester.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index d150dac..192cbbc 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -48,7 +48,6 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.config.SchemaConstants; import org.apache.cassandra.cql3.functions.FunctionName; -import org.apache.cassandra.cql3.functions.ThreadAwareSecurityManager; import org.apache.cassandra.cql3.statements.ParsedStatement; import org.apache.cassandra.db.*; import org.apache.cassandra.db.commitlog.CommitLog; @@ -67,6 +66,7 @@ import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.security.ThreadAwareSecurityManager; import static junit.framework.Assert.assertNotNull; --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org