
org.kitei.testing.lessio.LessIOSecurityManager Maven / Gradle / Ivy
The newest version!
/*
* Licensed 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.kitei.testing.lessio;
import static org.kitei.testing.lessio.LessIOUtils.checkNotNull;
import static org.kitei.testing.lessio.LessIOUtils.createGlobMatcher;
import static org.kitei.testing.lessio.LessIOUtils.getCurrentClassPath;
import static org.kitei.testing.lessio.LessIOUtils.hasAnnotations;
import static org.kitei.testing.lessio.LessIOUtils.matchesWhitelistCache;
import static org.kitei.testing.lessio.LessIOUtils.safeClassForNames;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Permission;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.kitei.testing.lessio.LessIOContext.Builder;
public class LessIOSecurityManager
extends SecurityManager
{
private static final boolean SKIP_CHECKS = Boolean.getBoolean("kitei.testing.skip-lessio-checks");
// Updated at SecurityManager init and again at every ClassLoader init.
private final AtomicReference> classpathHolder = new AtomicReference>();
private final LessIONetworkDelegate networkDelegate;
private final LessIOFilesystemDelegate filesystemDelegate;
private final LessIOProcessDelegate processDelegate;
private final Map, Boolean> whitelistCache = Collections.synchronizedMap(new IdentityHashMap, Boolean>(2048));
private final Map, Boolean> testrunnerCache = Collections.synchronizedMap(new IdentityHashMap, Boolean>(128));
public static LessIOContext.Builder defaultContextBuilder()
{
final Builder builder = LessIOContext.builder();
final Collection> junitClasses = safeClassForNames(true,
"org.junit.internal.runners.statements.InvokeMethod",
"org.junit.internal.runners.statements.RunAfters",
"org.junit.internal.runners.statements.RunBefores",
"org.junit.rules.TestRule",
"org.junit.runners.ParentRunner",
"org.junit.runners.model.FrameworkMethod",
"org.junit.runners.model.Statement");
if (!junitClasses.isEmpty()) {
builder.addTestrunnerClasses(junitClasses);
// JUnit accesses /junitXXXX files
builder.addWhitelistedPathGlobs(createGlobMatcher(LessIOUtils.TMP_PATH.resolve("junit*")));
System.err.println("Ready to instrument junit tests.");
}
final Collection> testngClasses = safeClassForNames(true,
"org.testng.TestRunner");
if (!testngClasses.isEmpty()) {
builder.addTestrunnerClasses(testngClasses);
System.err.println("Ready to instrument TestNG tests.");
}
builder.addWhitelistedClasses(
java.lang.ClassLoader.class,
java.net.URLClassLoader.class);
builder.addWhitelistedHosts(
"localhost",
"localhost6",
"localhost.localdomain",
"localhost6.localdomain6",
"127.0.0.1",
"::1");
try {
final InetAddress local = InetAddress.getLocalHost();
builder.addWhitelistedHosts(
local.getHostAddress(),
local.getHostName());
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
builder.addWhitelistedPaths(Paths.get("/dev/random"),
Paths.get("/dev/urandom"));
// Everything on the java.home path is always accessible.
builder.addWhitelistedPathGlobs(createGlobMatcher(Paths.get(System.getProperty("java.home")).resolve("**")));
builder.setLowestEphemeralPort(Integer.getInteger("kitei.testing.low-ephemeral-port", 32768));
builder.setHighestEphemeralPort(Integer.getInteger("kitei.testing.high-ephemeral-port", 61000));
return builder;
}
public LessIOSecurityManager()
{
this(defaultContextBuilder().build());
}
protected LessIOSecurityManager(final LessIOContext context)
{
checkNotNull(context, "context is null");
this.classpathHolder.set(getCurrentClassPath());
for (final Class> whitelistedClass : context.getWhitelistedClasses()) {
whitelistCache.put(whitelistedClass, Boolean.TRUE);
}
for (final Class> testrunnerClass : context.getTestrunnerClasses()) {
testrunnerCache.put(testrunnerClass, Boolean.TRUE);
}
this.networkDelegate = new LessIONetworkDelegate(context);
this.filesystemDelegate = new LessIOFilesystemDelegate(context, classpathHolder);
this.processDelegate = new LessIOProcessDelegate(context);
}
//
// Network
//
@Override
public void checkAccept(final String host, final int port) throws LessIOException
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, networkDelegate.getAcceptPredicate(host, port));
}
@Override
public void checkConnect(final String host, final int port, final Object context) throws LessIOException
{
checkConnect(host, port);
}
@Override
public void checkConnect(final String host, final int port) throws LessIOException
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, networkDelegate.getConnectPredicate(host, port));
}
@Override
public void checkListen(final int port) throws LessIOException
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, networkDelegate.getListenPredicate(port));
}
@Override
public void checkMulticast(final InetAddress maddr, final byte ttl) throws LessIOException
{
checkMulticast(maddr);
}
@Override
public void checkMulticast(final InetAddress maddr) throws LessIOException
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, networkDelegate.getMulticastPredicate(maddr));
}
//
// File system
//
@Override
public void checkRead(final String file, final Object context)
{
checkRead(file);
}
@Override
public void checkRead(final String fileName)
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkNotNull(fileName, "fileName is null");
final Path path = Paths.get(fileName);
try {
if (filesystemDelegate.checkFilesystemAccess(path)) {
return;
}
}
catch (final Exception e) {
throw new LessIOException(e, "Exception while accessing %s for read.", fileName);
}
checkPredicate(classContext, filesystemDelegate.getFileAccessPredicate(path, "read"));
}
@Override
public void checkRead(final FileDescriptor fd)
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, filesystemDelegate.getFileDescriptorPredicate(fd, "read"));
}
@Override
public void checkWrite(final FileDescriptor fd)
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, filesystemDelegate.getFileDescriptorPredicate(fd, "write"));
}
@Override
public void checkWrite(final String fileName)
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkNotNull(fileName, "fileName is null");
final Path path = Paths.get(fileName);
try {
if (filesystemDelegate.checkFilesystemAccess(path)) {
return;
}
}
catch (final Exception e) {
throw new LessIOException(e, "Exception while accessing %s for write.", fileName);
}
checkPredicate(classContext, filesystemDelegate.getFileAccessPredicate(path, "write"));
}
@Override
public void checkDelete(final String fileName)
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkNotNull(fileName, "fileName is null");
final Path path = Paths.get(fileName);
try {
if (filesystemDelegate.checkFilesystemAccess(path)) {
return;
}
}
catch (final Exception e) {
throw new LessIOException(e, "Exception while accessing %s for delete.", fileName);
}
checkPredicate(classContext, filesystemDelegate.getFileAccessPredicate(path, "delete"));
}
//
// Command execution
//
@Override
public void checkExec(final String cmd) throws LessIOException
{
final Class>[] classContext = getClassContext();
if (checkImplicitPermissions(classContext)) {
return;
}
checkPredicate(classContext, processDelegate.getExecuteProcessPredicate(cmd));
}
//
// Class Loader management
//
@Override
public void checkCreateClassLoader()
{
// Reset classpath refernce on classloader creation in case the classpath has changed.
// In particular, Maven's Surefire booter changes the classpath after the security
// manager has been initialized.
classpathHolder.set(getCurrentClassPath());
}
//
// Overrides from SecurityManager
//
@Override
public void checkAccess(final Thread t)
{
}
@Override
public void checkAccess(final ThreadGroup g)
{
}
@Override
public void checkMemberAccess(final Class> clazz, final int which)
{
}
@Override
public void checkPackageAccess(final String pkg)
{
}
@Override
public void checkPackageDefinition(final String pkg)
{
}
@Override
public void checkSetFactory()
{
}
@Override
public void checkPropertiesAccess()
{
}
@Override
public void checkPropertyAccess(final String key)
{
}
@Override
public void checkSecurityAccess(final String target)
{
}
@Override
public void checkPermission(final Permission perm, final Object context)
{
}
@Override
public void checkPermission(final Permission perm)
{
}
private boolean checkImplicitPermissions(final Class>[] classContext)
{
// all tests are skipped.
if (SKIP_CHECKS) {
return true;
}
// Check all classes in the class context.
for (final Class> clazz : classContext) {
// Any whitelisted class is accepted.
if (isWhitelistedClass(clazz)) {
return true;
}
// Any testrunner class that contains the @AllowAll annotation is
// also accepted
if (isTestrunnerClass(clazz) && hasAnnotations(clazz, AllowAll.class)) {
return true;
}
}
return false;
}
private boolean isWhitelistedClass(final Class> clazz)
{
return matchesWhitelistCache(clazz, whitelistCache);
}
private boolean isTestrunnerClass(final Class> clazz)
{
return matchesWhitelistCache(clazz, testrunnerCache);
}
private void checkPredicate(final Class>[] classContext,
final LessIOPredicate predicate) throws LessIOException
{
// Only check permissions when we're running in the context of a JUnit test.
boolean encounteredTestMethodRunner = false;
for (final Class> clazz : classContext) {
// Check whether any of the classes on the stack is one of the
// test runner classes.
if (isTestrunnerClass(clazz)) {
encounteredTestMethodRunner = true;
}
else if (hasAnnotations(clazz, AllowAll.class)) {
throw new LessIOException("Found @AllowAll on a non-testrunner class (%s), refusing to run test!", clazz.getName());
}
// Look whether any class in the stack is properly authorized to run the
// operation.
try {
if (predicate.check(clazz)) {
return;
}
}
catch (final Exception e) {
throw new LessIOException(e, "Exception while processing %s", predicate);
}
}
if (!encounteredTestMethodRunner) {
return;
}
// No class on the stack trace is properly authorized, throw an exception.
throw new LessIOException("No class in the class context satisfies %s", predicate);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy