Pārlūkot izejas kodu

Introduce secure security manager to project

This commit migrates SecureSM, our secure security manager
implementation, from its own repository to being a sub-project of
Elasticsearch.
Jason Tedor 7 gadi atpakaļ
vecāks
revīzija
1b3d529bef

+ 2 - 1
build.gradle

@@ -145,7 +145,7 @@ task verifyVersions {
  * after the backport of the backcompat code is complete.
  */
 allprojects {
-  ext.bwc_tests_enabled = false
+  ext.bwc_tests_enabled = true
 }
 
 task verifyBwcTestsEnabled {
@@ -185,6 +185,7 @@ subprojects {
     "org.elasticsearch:elasticsearch-cli:${version}": ':server:cli',
     "org.elasticsearch:elasticsearch-core:${version}": ':libs:elasticsearch-core',
     "org.elasticsearch:elasticsearch-nio:${version}": ':libs:elasticsearch-nio',
+    "org.elasticsearch:elasticsearch-secure-sm:${version}": ':libs:secure-sm',
     "org.elasticsearch.client:elasticsearch-rest-client:${version}": ':client:rest',
     "org.elasticsearch.client:elasticsearch-rest-client-sniffer:${version}": ':client:sniffer',
     "org.elasticsearch.client:elasticsearch-rest-high-level-client:${version}": ':client:rest-high-level',

+ 72 - 0
libs/secure-sm/build.gradle

@@ -0,0 +1,72 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+import org.elasticsearch.gradle.precommit.PrecommitTasks
+
+apply plugin: 'elasticsearch.build'
+apply plugin: 'nebula.maven-base-publish'
+apply plugin: 'nebula.maven-scm'
+
+archivesBaseName = 'elasticsearch-secure-sm'
+
+publishing {
+    publications {
+        nebula {
+            artifactId = archivesBaseName
+        }
+    }
+}
+
+dependencies {
+    // do not add non-test compile dependencies to secure-sm without a good reason to do so
+
+    testCompile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
+    testCompile "junit:junit:${versions.junit}"
+    testCompile "org.hamcrest:hamcrest-all:${versions.hamcrest}"
+
+    if (isEclipse == false || project.path == ":libs:secure-sm-tests") {
+        testCompile("org.elasticsearch.test:framework:${version}") {
+            exclude group: 'org.elasticsearch', module: 'secure-sm'
+        }
+    }
+}
+
+forbiddenApisMain {
+    signaturesURLs = [PrecommitTasks.getResource('/forbidden/jdk-signatures.txt')]
+}
+
+if (isEclipse) {
+    // in Eclipse the project is under a fake root so we need to change around the source sets
+    sourceSets {
+        if (project.path == ":libs:secure-sm") {
+            main.java.srcDirs = ['java']
+            main.resources.srcDirs = ['resources']
+        } else {
+            test.java.srcDirs = ['java']
+            test.resources.srcDirs = ['resources']
+        }
+    }
+}
+
+// JAR hell is part of core which we do not want to add as a dependency
+jarHell.enabled = false
+
+namingConventions {
+    testClass = 'junit.framework.TestCase'
+}

+ 265 - 0
libs/secure-sm/src/main/java/org/elasticsearch/secure_sm/SecureSM.java

@@ -0,0 +1,265 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.secure_sm;
+
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.util.Objects;
+
+/**
+ * Extension of SecurityManager that works around a few design flaws in Java Security.
+ * <p>
+ * There are a few major problems that require custom {@code SecurityManager} logic to fix:
+ * <ul>
+ *   <li>{@code exitVM} permission is implicitly granted to all code by the default
+ *       Policy implementation. For a server app, this is not wanted. </li>
+ *   <li>ThreadGroups are not enforced by default, instead only system threads are
+ *       protected out of box by {@code modifyThread/modifyThreadGroup}. Applications
+ *       are encouraged to override the logic here to implement a stricter policy.
+ *   <li>System threads are not even really protected, because if the system uses
+ *       ThreadPools, {@code modifyThread} is abused by its {@code shutdown} checks. This means
+ *       a thread must have {@code modifyThread} to even terminate its own pool, leaving
+ *       system threads unprotected.
+ * </ul>
+ * This class throws exception on {@code exitVM} calls, and provides a whitelist where calls
+ * from exit are allowed.
+ * <p>
+ * Additionally it enforces threadgroup security with the following rules:
+ * <ul>
+ *   <li>{@code modifyThread} and {@code modifyThreadGroup} are required for any thread access
+ *       checks: with these permissions, access is granted as long as the thread group is
+ *       the same or an ancestor ({@code sourceGroup.parentOf(targetGroup) == true}). 
+ *   <li>code without these permissions can do very little, except to interrupt itself. It may
+ *       not even create new threads.
+ *   <li>very special cases (like test runners) that have {@link ThreadPermission} can violate 
+ *       threadgroup security rules.
+ * </ul>
+ * <p>
+ * If java security debugging ({@code java.security.debug}) is enabled, and this SecurityManager
+ * is installed, it will emit additional debugging information when threadgroup access checks fail.
+ *  
+ * @see SecurityManager#checkAccess(Thread)
+ * @see SecurityManager#checkAccess(ThreadGroup)
+ * @see <a href="http://cs.oswego.edu/pipermail/concurrency-interest/2009-August/006508.html">
+ *         http://cs.oswego.edu/pipermail/concurrency-interest/2009-August/006508.html</a>
+ */
+public class SecureSM extends SecurityManager {
+
+    private final String[] classesThatCanExit;
+
+    /**
+     * Creates a new security manager where no packages can exit nor halt the virtual machine.
+     */
+    public SecureSM() {
+        this(new String[0]);
+    }
+
+    /**
+     * Creates a new security manager with the specified list of regular expressions as the those that class names will be tested against to
+     * check whether or not a class can exit or halt the virtual machine.
+     *
+     * @param classesThatCanExit the list of classes that can exit or halt the virtual machine
+     */
+    public SecureSM(final String[] classesThatCanExit) {
+        this.classesThatCanExit = classesThatCanExit;
+    }
+
+    /**
+     * Creates a new security manager with a standard set of test packages being the only packages that can exit or halt the virtual
+     * machine. The packages that can exit are:
+     * <ul>
+     *    <li><code>org.apache.maven.surefire.booter.</code></li>
+     *    <li><code>com.carrotsearch.ant.tasks.junit4.</code></li>
+     *    <li><code>org.eclipse.internal.junit.runner.</code></li>
+     *    <li><code>com.intellij.rt.execution.junit.</code></li>
+     * </ul>
+     *
+     * @return an instance of SecureSM where test packages can halt or exit the virtual machine
+     */
+    public static SecureSM createTestSecureSM() {
+        return new SecureSM(TEST_RUNNER_PACKAGES);
+    }
+
+    static final String[] TEST_RUNNER_PACKAGES = new String[] {
+        // surefire test runner
+        "org\\.apache\\.maven\\.surefire\\.booter\\..*",
+        // junit4 test runner
+        "com\\.carrotsearch\\.ant\\.tasks\\.junit4\\.slave\\..*",
+        // eclipse test runner
+        "org\\.eclipse.jdt\\.internal\\.junit\\.runner\\..*",
+        // intellij test runner
+        "com\\.intellij\\.rt\\.execution\\.junit\\..*"
+    };
+
+    // java.security.debug support
+    private static final boolean DEBUG = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+        @Override
+        public Boolean run() {
+            try {
+                String v = System.getProperty("java.security.debug");
+                // simple check that they are trying to debug
+                return v != null && v.length() > 0;
+            } catch (SecurityException e) {
+                return false;
+            }
+        }
+    });
+    
+    @Override
+    @SuppressForbidden(reason = "java.security.debug messages go to standard error")
+    public void checkAccess(Thread t) {
+        try {
+            checkThreadAccess(t);
+        } catch (SecurityException e) {
+            if (DEBUG) {
+                System.err.println("access: caller thread=" + Thread.currentThread());
+                System.err.println("access: target thread=" + t);
+                debugThreadGroups(Thread.currentThread().getThreadGroup(), t.getThreadGroup());
+            }
+            throw e;
+        }
+    }
+    
+    @Override
+    @SuppressForbidden(reason = "java.security.debug messages go to standard error")
+    public void checkAccess(ThreadGroup g) {
+        try {
+            checkThreadGroupAccess(g);
+        } catch (SecurityException e) {
+            if (DEBUG) {
+                System.err.println("access: caller thread=" + Thread.currentThread());
+                debugThreadGroups(Thread.currentThread().getThreadGroup(), g);
+            }
+            throw e;
+        }
+    }
+
+    @SuppressForbidden(reason = "java.security.debug messages go to standard error")
+    private void debugThreadGroups(final ThreadGroup caller, final ThreadGroup target) {
+        System.err.println("access: caller group=" + caller);
+        System.err.println("access: target group=" + target);
+    }
+    
+    // thread permission logic
+
+    private static final Permission MODIFY_THREAD_PERMISSION = new RuntimePermission("modifyThread");
+    private static final Permission MODIFY_ARBITRARY_THREAD_PERMISSION = new ThreadPermission("modifyArbitraryThread");
+
+    protected void checkThreadAccess(Thread t) {
+        Objects.requireNonNull(t);
+
+        // first, check if we can modify threads at all.
+        checkPermission(MODIFY_THREAD_PERMISSION);
+        
+        // check the threadgroup, if its our thread group or an ancestor, its fine.
+        final ThreadGroup source = Thread.currentThread().getThreadGroup();
+        final ThreadGroup target = t.getThreadGroup();
+        
+        if (target == null) {
+            return;    // its a dead thread, do nothing.
+        } else if (source.parentOf(target) == false) {
+            checkPermission(MODIFY_ARBITRARY_THREAD_PERMISSION);
+        }
+    }
+    
+    private static final Permission MODIFY_THREADGROUP_PERMISSION = new RuntimePermission("modifyThreadGroup");
+    private static final Permission MODIFY_ARBITRARY_THREADGROUP_PERMISSION = new ThreadPermission("modifyArbitraryThreadGroup");
+    
+    protected void checkThreadGroupAccess(ThreadGroup g) {
+        Objects.requireNonNull(g);
+
+        // first, check if we can modify thread groups at all.
+        checkPermission(MODIFY_THREADGROUP_PERMISSION);
+        
+        // check the threadgroup, if its our thread group or an ancestor, its fine.
+        final ThreadGroup source = Thread.currentThread().getThreadGroup();
+        final ThreadGroup target = g;
+        
+        if (source == null) {
+            return; // we are a dead thread, do nothing
+        } else if (source.parentOf(target) == false) {
+            checkPermission(MODIFY_ARBITRARY_THREADGROUP_PERMISSION);
+        }
+    }
+
+    // exit permission logic
+    @Override
+    public void checkExit(int status) {
+        innerCheckExit(status);
+    }
+    
+    /**
+     * The "Uwe Schindler" algorithm.
+     *
+     * @param status the exit status
+     */
+    protected void innerCheckExit(final int status) {
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                final String systemClassName = System.class.getName(),
+                        runtimeClassName = Runtime.class.getName();
+                String exitMethodHit = null;
+                for (final StackTraceElement se : Thread.currentThread().getStackTrace()) {
+                    final String className = se.getClassName(), methodName = se.getMethodName();
+                    if (
+                        ("exit".equals(methodName) || "halt".equals(methodName)) &&
+                        (systemClassName.equals(className) || runtimeClassName.equals(className))
+                    ) {
+                        exitMethodHit = className + '#' + methodName + '(' + status + ')';
+                        continue;
+                    }
+                    
+                    if (exitMethodHit != null) {
+                        if (classesThatCanExit == null) {
+                            break;
+                        }
+                        if (classCanExit(className, classesThatCanExit)) {
+                            // this exit point is allowed, we return normally from closure:
+                            return null;
+                        }
+                        // anything else in stack trace is not allowed, break and throw SecurityException below:
+                        break;
+                    }
+                }
+                
+                if (exitMethodHit == null) {
+                    // should never happen, only if JVM hides stack trace - replace by generic:
+                    exitMethodHit = "JVM exit method";
+                }
+                throw new SecurityException(exitMethodHit + " calls are not allowed");
+            }
+        });
+        
+        // we passed the stack check, delegate to super, so default policy can still deny permission:
+        super.checkExit(status);
+    }
+
+    static boolean classCanExit(final String className, final String[] classesThatCanExit) {
+        for (final String classThatCanExit : classesThatCanExit) {
+            if (className.matches(classThatCanExit)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 34 - 0
libs/secure-sm/src/main/java/org/elasticsearch/secure_sm/SuppressForbidden.java

@@ -0,0 +1,34 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.secure_sm;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to suppress forbidden-apis errors inside a whole class, a method, or a field.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
+@interface SuppressForbidden {
+    String reason();
+}

+ 60 - 0
libs/secure-sm/src/main/java/org/elasticsearch/secure_sm/ThreadPermission.java

@@ -0,0 +1,60 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.secure_sm;
+
+import java.security.BasicPermission;
+
+/**
+ * Permission to modify threads or thread groups normally not accessible
+ * to the current thread.
+ * <p>
+ * {@link SecureSM} enforces ThreadGroup security: threads with
+ * {@code RuntimePermission("modifyThread")} or {@code RuntimePermission("modifyThreadGroup")}
+ * are only allowed to modify their current thread group or an ancestor of that group.
+ * <p>
+ * In some cases (e.g. test runners), code needs to manipulate arbitrary threads,
+ * so this Permission provides for that: the targets {@code modifyArbitraryThread} and
+ * {@code modifyArbitraryThreadGroup} allow a thread blanket access to any group.
+ *
+ * @see ThreadGroup
+ * @see SecureSM
+ */
+public final class ThreadPermission extends BasicPermission {
+
+    /**
+     * Creates a new ThreadPermission object.
+     *
+     * @param name target name
+     */
+    public ThreadPermission(String name) {
+        super(name);
+    }
+
+    /**
+     * Creates a new ThreadPermission object.
+     * This constructor exists for use by the {@code Policy} object to instantiate new Permission objects.
+     *
+     * @param name target name
+     * @param actions ignored
+     */
+    public ThreadPermission(String name, String actions) {
+        super(name, actions);
+    }
+}

+ 141 - 0
libs/secure-sm/src/test/java/org/elasticsearch/secure_sm/SecureSMTests.java

@@ -0,0 +1,141 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.secure_sm;
+
+import junit.framework.TestCase;
+
+import java.security.Permission;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Simple tests for SecureSM */
+public class SecureSMTests extends TestCase {
+    static {
+        // install a mock security policy:
+        // AllPermission to source code
+        // ThreadPermission not granted anywhere else
+        final ProtectionDomain sourceCode = SecureSM.class.getProtectionDomain();
+        Policy.setPolicy(new Policy() {
+            @Override
+            public boolean implies(ProtectionDomain domain, Permission permission) {
+                if (domain == sourceCode) {
+                    return true;
+                } else if (permission instanceof ThreadPermission) {
+                    return false;
+                }
+                return true;
+            }
+        });
+        System.setSecurityManager(SecureSM.createTestSecureSM());
+    }
+
+    @SuppressForbidden(reason = "testing that System#exit is blocked")
+    public void testTryToExit() {
+        try {
+            System.exit(1);
+            fail("did not hit expected exception");
+        } catch (SecurityException expected) {}
+    }
+
+    public void testClassCanExit() {
+        assertTrue(SecureSM.classCanExit("org.apache.maven.surefire.booter.CommandReader", SecureSM.TEST_RUNNER_PACKAGES));
+        assertTrue(SecureSM.classCanExit("com.carrotsearch.ant.tasks.junit4.slave.JvmExit", SecureSM.TEST_RUNNER_PACKAGES));
+        assertTrue(SecureSM.classCanExit("org.eclipse.jdt.internal.junit.runner.RemoteTestRunner", SecureSM.TEST_RUNNER_PACKAGES));
+        assertTrue(SecureSM.classCanExit("com.intellij.rt.execution.junit.JUnitStarter", SecureSM.TEST_RUNNER_PACKAGES));
+        assertTrue(SecureSM.classCanExit("org.elasticsearch.Foo", new String[]{"org.elasticsearch.Foo"}));
+        assertFalse(SecureSM.classCanExit("org.elasticsearch.Foo", new String[]{"org.elasticsearch.Bar"}));
+    }
+
+    public void testCreateThread() throws Exception {
+        Thread t = new Thread();
+        t.start();
+        t.join();
+        // no exception
+    }
+
+    public void testCreateThreadGroup() throws Exception {
+        Thread t = new Thread(new ThreadGroup("childgroup"), "child");
+        t.start();
+        t.join();
+        // no exception
+    }
+
+    public void testModifyChild() throws Exception {
+        final AtomicBoolean interrupted = new AtomicBoolean(false);
+        Thread t = new Thread(new ThreadGroup("childgroup"), "child") {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(Long.MAX_VALUE);
+                } catch (InterruptedException expected) {
+                    interrupted.set(true);
+                }
+            }
+        };
+        t.start();
+        t.interrupt();
+        t.join();
+        // no exception
+        assertTrue(interrupted.get());
+    }
+
+    public void testNoModifySibling() throws Exception {
+        final AtomicBoolean interrupted1 = new AtomicBoolean(false);
+        final AtomicBoolean interrupted2 = new AtomicBoolean(false);
+
+        final Thread t1 = new Thread(new ThreadGroup("childgroup"), "child") {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(Long.MAX_VALUE);
+                } catch (InterruptedException expected) {
+                    interrupted1.set(true);
+                }
+            }
+        };
+        t1.start();
+
+        Thread t2 = new Thread(new ThreadGroup("anothergroup"), "another child") {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(Long.MAX_VALUE);
+                } catch (InterruptedException expected) {
+                    interrupted2.set(true);
+                    try {
+                        t1.interrupt(); // try to bogusly interrupt our sibling
+                        fail("did not hit expected exception");
+                    } catch (SecurityException expected2) {}
+                }
+            }
+        };
+        t2.start();
+        t2.interrupt();
+        t2.join();
+        // sibling attempted to but was not able to muck with its other sibling
+        assertTrue(interrupted2.get());
+        assertFalse(interrupted1.get());
+        // but we are the parent and can terminate
+        t1.interrupt();
+        t1.join();
+        assertTrue(interrupted1.get());
+    }
+}

+ 47 - 0
libs/secure-sm/src/test/java/org/elasticsearch/secure_sm/ThreadPermissionTests.java

@@ -0,0 +1,47 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.secure_sm;
+
+import junit.framework.TestCase;
+
+import java.security.AllPermission;
+
+/**
+ * Simple tests for ThreadPermission
+ */
+public class ThreadPermissionTests extends TestCase {
+
+    public void testEquals() {
+        assertEquals(new ThreadPermission("modifyArbitraryThread"), new ThreadPermission("modifyArbitraryThread"));
+        assertFalse(new ThreadPermission("modifyArbitraryThread").equals(new AllPermission()));
+        assertFalse(new ThreadPermission("modifyArbitraryThread").equals(new ThreadPermission("modifyArbitraryThreadGroup"))); }
+
+    public void testImplies() {
+        assertTrue(new ThreadPermission("modifyArbitraryThread").implies(new ThreadPermission("modifyArbitraryThread")));
+        assertTrue(new ThreadPermission("modifyArbitraryThreadGroup").implies(new ThreadPermission("modifyArbitraryThreadGroup")));
+        assertFalse(new ThreadPermission("modifyArbitraryThread").implies(new ThreadPermission("modifyArbitraryThreadGroup")));
+        assertFalse(new ThreadPermission("modifyArbitraryThreadGroup").implies(new ThreadPermission("modifyArbitraryThread")));
+        assertFalse(new ThreadPermission("modifyArbitraryThread").implies(new AllPermission()));
+        assertFalse(new ThreadPermission("modifyArbitraryThreadGroup").implies(new AllPermission()));
+        assertTrue(new ThreadPermission("*").implies(new ThreadPermission("modifyArbitraryThread")));
+        assertTrue(new ThreadPermission("*").implies(new ThreadPermission("modifyArbitraryThreadGroup")));
+        assertFalse(new ThreadPermission("*").implies(new AllPermission()));
+    }
+}

+ 262 - 0
securesm/src/main/java/org/elasticsearch/SecureSM.java

@@ -0,0 +1,262 @@
+package org.elasticsearch;
+
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.util.Objects;
+
+/**
+ * Extension of SecurityManager that works around a few design flaws in Java Security.
+ * <p>
+ * There are a few major problems that require custom {@code SecurityManager} logic to fix:
+ * <ul>
+ *   <li>{@code exitVM} permission is implicitly granted to all code by the default
+ *       Policy implementation. For a server app, this is not wanted. </li>
+ *   <li>ThreadGroups are not enforced by default, instead only system threads are
+ *       protected out of box by {@code modifyThread/modifyThreadGroup}. Applications
+ *       are encouraged to override the logic here to implement a stricter policy.
+ *   <li>System threads are not even really protected, because if the system uses
+ *       ThreadPools, {@code modifyThread} is abused by its {@code shutdown} checks. This means
+ *       a thread must have {@code modifyThread} to even terminate its own pool, leaving
+ *       system threads unprotected.
+ * </ul>
+ * This class throws exception on {@code exitVM} calls, and provides a whitelist where calls
+ * from exit are allowed.
+ * <p>
+ * Additionally it enforces threadgroup security with the following rules:
+ * <ul>
+ *   <li>{@code modifyThread} and {@code modifyThreadGroup} are required for any thread access
+ *       checks: with these permissions, access is granted as long as the thread group is
+ *       the same or an ancestor ({@code sourceGroup.parentOf(targetGroup) == true}). 
+ *   <li>code without these permissions can do very little, except to interrupt itself. It may
+ *       not even create new threads.
+ *   <li>very special cases (like test runners) that have {@link ThreadPermission} can violate 
+ *       threadgroup security rules.
+ * </ul>
+ * <p>
+ * If java security debugging ({@code java.security.debug}) is enabled, and this SecurityManager
+ * is installed, it will emit additional debugging information when threadgroup access checks fail.
+ *  
+ * @see SecurityManager#checkAccess(Thread)
+ * @see SecurityManager#checkAccess(ThreadGroup)
+ * @see <a href="http://cs.oswego.edu/pipermail/concurrency-interest/2009-August/006508.html">
+ *         http://cs.oswego.edu/pipermail/concurrency-interest/2009-August/006508.html</a>
+ */
+public class SecureSM extends SecurityManager {
+
+  private final String[] classesThatCanExit;
+
+  /**
+   * Creates a new security manager where no packages can exit nor halt the virtual machine.
+   */
+  public SecureSM() {
+    this(new String[0]);
+  }
+
+  /**
+   * Creates a new security manager with the specified list of regular expressions as the those that class names will be tested against to
+   * check whether or not a class can exit or halt the virtual machine.
+   *
+   * @param classesThatCanExit the list of classes that can exit or halt the virtual machine
+   */
+  public SecureSM(final String[] classesThatCanExit) {
+    this.classesThatCanExit = classesThatCanExit;
+  }
+
+  /**
+   * Creates a new security manager with a standard set of test packages being the only packages that can exit or halt the virtual machine.
+   * The packages that can exit are:
+   * <ul>
+   *  <li><code>org.apache.maven.surefire.booter.</code></li>
+   *  <li><code>com.carrotsearch.ant.tasks.junit4.</code></li>
+   *  <li><code>org.eclipse.internal.junit.runner.</code></li>
+   *  <li><code>com.intellij.rt.execution.junit.</code></li>
+   * </ul>
+   *
+   * @return an instance of SecureSM where test packages can halt or exit the virtual machine
+     */
+  public static SecureSM createTestSecureSM() {
+    return new SecureSM(TEST_RUNNER_PACKAGES);
+  }
+
+  static final String[] TEST_RUNNER_PACKAGES = new String[] {
+    // surefire test runner
+    "org\\.apache\\.maven\\.surefire\\.booter\\..*",
+    // junit4 test runner
+    "com\\.carrotsearch\\.ant\\.tasks\\.junit4\\.slave\\..*",
+    // eclipse test runner
+    "org\\.eclipse.jdt\\.internal\\.junit\\.runner\\..*",
+    // intellij test runner
+    "com\\.intellij\\.rt\\.execution\\.junit\\..*"
+  };
+
+  // java.security.debug support
+  private static final boolean DEBUG = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+    @Override
+    public Boolean run() {
+      try {
+        String v = System.getProperty("java.security.debug");
+        // simple check that they are trying to debug
+        return v != null && v.length() > 0;
+      } catch (SecurityException e) {
+        return false;
+      }
+    }
+  });
+  
+  @Override
+  public void checkAccess(Thread t) {
+    try {
+      checkThreadAccess(t);
+    } catch (SecurityException e) {
+      if (DEBUG) {
+        System.out.println("access: caller thread=" + Thread.currentThread());
+        System.out.println("access: target thread=" + t);
+        debugThreadGroups(Thread.currentThread().getThreadGroup(), t.getThreadGroup());
+      }
+      throw e;
+    }
+  }
+  
+  @Override
+  public void checkAccess(ThreadGroup g) {
+    try {
+      checkThreadGroupAccess(g);
+    } catch (SecurityException e) {
+      if (DEBUG) {
+        System.out.println("access: caller thread=" + Thread.currentThread());
+        debugThreadGroups(Thread.currentThread().getThreadGroup(), g);
+      }
+      throw e;
+    }
+  }
+  
+  private void debugThreadGroups(final ThreadGroup caller, final ThreadGroup target) {
+    System.out.println("access: caller group=" + caller);
+    System.out.println("access: target group=" + target);
+  }
+  
+  // thread permission logic
+
+  private static final Permission MODIFY_THREAD_PERMISSION = new RuntimePermission("modifyThread");
+  private static final Permission MODIFY_ARBITRARY_THREAD_PERMISSION = new ThreadPermission("modifyArbitraryThread");
+
+  protected void checkThreadAccess(Thread t) {
+    Objects.requireNonNull(t);
+
+    // first, check if we can modify threads at all.
+    checkPermission(MODIFY_THREAD_PERMISSION);
+    
+    // check the threadgroup, if its our thread group or an ancestor, its fine.
+    final ThreadGroup source = Thread.currentThread().getThreadGroup();
+    final ThreadGroup target = t.getThreadGroup();
+    
+    if (target == null) {
+      return;  // its a dead thread, do nothing.
+    } else if (source.parentOf(target) == false) {
+      checkPermission(MODIFY_ARBITRARY_THREAD_PERMISSION);
+    }
+  }
+  
+  private static final Permission MODIFY_THREADGROUP_PERMISSION = new RuntimePermission("modifyThreadGroup");
+  private static final Permission MODIFY_ARBITRARY_THREADGROUP_PERMISSION = new ThreadPermission("modifyArbitraryThreadGroup");
+  
+  protected void checkThreadGroupAccess(ThreadGroup g) {
+    Objects.requireNonNull(g);
+
+    // first, check if we can modify thread groups at all.
+    checkPermission(MODIFY_THREADGROUP_PERMISSION);
+    
+    // check the threadgroup, if its our thread group or an ancestor, its fine.
+    final ThreadGroup source = Thread.currentThread().getThreadGroup();
+    final ThreadGroup target = g;
+    
+    if (source == null) {
+      return; // we are a dead thread, do nothing
+    } else if (source.parentOf(target) == false) {
+      checkPermission(MODIFY_ARBITRARY_THREADGROUP_PERMISSION);
+    }
+  }
+
+  // exit permission logic
+  @Override
+  public void checkExit(int status) {
+    innerCheckExit(status);
+  }
+  
+  /**
+   * The "Uwe Schindler" algorithm.
+   *
+   * @param status the exit status
+   */
+  protected void innerCheckExit(final int status) {
+    AccessController.doPrivileged(new PrivilegedAction<Void>() {
+      @Override
+      public Void run() {
+        final String systemClassName = System.class.getName(),
+            runtimeClassName = Runtime.class.getName();
+        String exitMethodHit = null;
+        for (final StackTraceElement se : Thread.currentThread().getStackTrace()) {
+          final String className = se.getClassName(), methodName = se.getMethodName();
+          if (
+            ("exit".equals(methodName) || "halt".equals(methodName)) &&
+            (systemClassName.equals(className) || runtimeClassName.equals(className))
+          ) {
+            exitMethodHit = className + '#' + methodName + '(' + status + ')';
+            continue;
+          }
+          
+          if (exitMethodHit != null) {
+            if (classesThatCanExit == null) {
+              break;
+            }
+            if (classCanExit(className, classesThatCanExit)) {
+              // this exit point is allowed, we return normally from closure:
+              return null;
+            }
+            // anything else in stack trace is not allowed, break and throw SecurityException below:
+            break;
+          }
+        }
+        
+        if (exitMethodHit == null) {
+          // should never happen, only if JVM hides stack trace - replace by generic:
+          exitMethodHit = "JVM exit method";
+        }
+        throw new SecurityException(exitMethodHit + " calls are not allowed");
+      }
+    });
+    
+    // we passed the stack check, delegate to super, so default policy can still deny permission:
+    super.checkExit(status);
+  }
+
+  static boolean classCanExit(final String className, final String[] classesThatCanExit) {
+    for (final String classThatCanExit : classesThatCanExit) {
+      if (className.matches(classThatCanExit)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+}

+ 62 - 0
securesm/src/main/java/org/elasticsearch/ThreadPermission.java

@@ -0,0 +1,62 @@
+package org.elasticsearch;
+
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+import java.security.BasicPermission;
+
+/**
+ * Permission to modify threads or thread groups normally not accessible
+ * to the current thread.
+ * <p>
+ * {@link SecureSM} enforces ThreadGroup security: threads with
+ * {@code RuntimePermission("modifyThread")} or {@code RuntimePermission("modifyThreadGroup")}
+ * are only allowed to modify their current thread group or an ancestor of that group.
+ * <p>
+ * In some cases (e.g. test runners), code needs to manipulate arbitrary threads,
+ * so this Permission provides for that: the targets {@code modifyArbitraryThread} and
+ * {@code modifyArbitraryThreadGroup} allow a thread blanket access to any group.
+ * 
+ * @see ThreadGroup
+ * @see SecureSM
+ */
+public final class ThreadPermission extends BasicPermission {
+
+  private static final long serialVersionUID = -3467631034676832244L;
+
+  /**
+   * Creates a new ThreadPermission object.
+   * 
+   * @param name target name
+   */
+  public ThreadPermission(String name) {
+    super(name);
+  }
+  
+  /**
+   * Creates a new ThreadPermission object.
+   * This constructor exists for use by the {@code Policy} object to instantiate new Permission objects.
+   * 
+   * @param name target name
+   * @param actions ignored
+   */
+  public ThreadPermission(String name, String actions) {
+    super(name, actions);
+  }
+}

+ 147 - 0
securesm/src/test/java/org/elasticsearch/TestSecureSM.java

@@ -0,0 +1,147 @@
+package org.elasticsearch;
+
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+import junit.framework.TestCase;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Simple tests for SecureSM */
+public class TestSecureSM extends TestCase {
+  static {
+    // install a mock security policy:
+    // AllPermission to source code
+    // ThreadPermission not granted anywhere else
+    final ProtectionDomain sourceCode = SecureSM.class.getProtectionDomain();
+    Policy.setPolicy(new Policy() {
+      @Override
+      public boolean implies(ProtectionDomain domain, Permission permission) {
+        if (domain == sourceCode) {
+          return true;
+        } else if (permission instanceof ThreadPermission) {
+          return false;
+        }
+        return true;
+      }
+    });
+    System.setSecurityManager(SecureSM.createTestSecureSM());
+  }
+  
+  @Test
+  public void testTryToExit() {
+    try {
+      System.exit(1);
+      fail("did not hit expected exception");
+    } catch (SecurityException expected) {}
+  }
+
+  @Test
+  public void testClassCanExit() {
+    assertTrue(SecureSM.classCanExit("org.apache.maven.surefire.booter.CommandReader", SecureSM.TEST_RUNNER_PACKAGES));
+    assertTrue(SecureSM.classCanExit("com.carrotsearch.ant.tasks.junit4.slave.JvmExit", SecureSM.TEST_RUNNER_PACKAGES));
+    assertTrue(SecureSM.classCanExit("org.eclipse.jdt.internal.junit.runner.RemoteTestRunner", SecureSM.TEST_RUNNER_PACKAGES));
+    assertTrue(SecureSM.classCanExit("com.intellij.rt.execution.junit.JUnitStarter", SecureSM.TEST_RUNNER_PACKAGES));
+    assertTrue(SecureSM.classCanExit("org.elasticsearch.Foo", new String[]{"org.elasticsearch.Foo"}));
+    assertFalse(SecureSM.classCanExit("org.elasticsearch.Foo", new String[]{"org.elasticsearch.Bar"}));
+  }
+  
+  @Test
+  public void testCreateThread() throws Exception {
+    Thread t = new Thread();
+    t.start();
+    t.join();
+    // no exception
+  }
+  
+  @Test
+  public void testCreateThreadGroup() throws Exception {
+    Thread t = new Thread(new ThreadGroup("childgroup"), "child");
+    t.start();
+    t.join();
+    // no exception
+  }
+  
+  @Test
+  public void testModifyChild() throws Exception {
+    final AtomicBoolean interrupted = new AtomicBoolean(false);
+    Thread t = new Thread(new ThreadGroup("childgroup"), "child") {
+      @Override
+      public void run() {
+        try {
+          Thread.sleep(Long.MAX_VALUE);
+        } catch (InterruptedException expected) {
+          interrupted.set(true);
+        }
+      }
+    };
+    t.start();
+    t.interrupt();
+    t.join();
+    // no exception
+    assertTrue(interrupted.get());
+  }
+  
+  @Test
+  public void testNoModifySibling() throws Exception {
+    final AtomicBoolean interrupted1 = new AtomicBoolean(false);
+    final AtomicBoolean interrupted2 = new AtomicBoolean(false);
+
+    final Thread t1 = new Thread(new ThreadGroup("childgroup"), "child") {
+      @Override
+      public void run() {
+        try {
+          Thread.sleep(Long.MAX_VALUE);
+        } catch (InterruptedException expected) {
+          interrupted1.set(true);
+        }
+      }
+    };
+    t1.start();
+    
+    Thread t2 = new Thread(new ThreadGroup("anothergroup"), "another child") {
+      @Override
+      public void run() {
+        try {
+          Thread.sleep(Long.MAX_VALUE);
+        } catch (InterruptedException expected) {
+          interrupted2.set(true);
+          try {
+            t1.interrupt(); // try to bogusly interrupt our sibling
+            fail("did not hit expected exception");
+          } catch (SecurityException expected2) {}
+        }
+      }
+    };
+    t2.start();
+    t2.interrupt();
+    t2.join();
+    // sibling attempted to but was not able to muck with its other sibling
+    assertTrue(interrupted2.get());
+    assertFalse(interrupted1.get());
+    // but we are the parent and can terminate
+    t1.interrupt();
+    t1.join();
+    assertTrue(interrupted1.get());
+  }
+}

+ 50 - 0
securesm/src/test/java/org/elasticsearch/TestThreadPermission.java

@@ -0,0 +1,50 @@
+package org.elasticsearch;
+
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+import java.security.AllPermission;
+
+import org.junit.Test;
+
+import junit.framework.TestCase;
+
+/** Simple tests for ThreadPermission */
+public class TestThreadPermission extends TestCase {
+  
+  @Test
+  public void testEquals() {
+    assertEquals(new ThreadPermission("modifyArbitraryThread"), new ThreadPermission("modifyArbitraryThread"));
+    assertFalse(new ThreadPermission("modifyArbitraryThread").equals(new AllPermission()));
+    assertFalse(new ThreadPermission("modifyArbitraryThread").equals(new ThreadPermission("modifyArbitraryThreadGroup")));
+  }
+  
+  @Test
+  public void testImplies() {
+    assertTrue(new ThreadPermission("modifyArbitraryThread").implies(new ThreadPermission("modifyArbitraryThread")));
+    assertTrue(new ThreadPermission("modifyArbitraryThreadGroup").implies(new ThreadPermission("modifyArbitraryThreadGroup")));
+    assertFalse(new ThreadPermission("modifyArbitraryThread").implies(new ThreadPermission("modifyArbitraryThreadGroup")));
+    assertFalse(new ThreadPermission("modifyArbitraryThreadGroup").implies(new ThreadPermission("modifyArbitraryThread")));
+    assertFalse(new ThreadPermission("modifyArbitraryThread").implies(new AllPermission()));
+    assertFalse(new ThreadPermission("modifyArbitraryThreadGroup").implies(new AllPermission()));
+    assertTrue(new ThreadPermission("*").implies(new ThreadPermission("modifyArbitraryThread")));
+    assertTrue(new ThreadPermission("*").implies(new ThreadPermission("modifyArbitraryThreadGroup")));
+    assertFalse(new ThreadPermission("*").implies(new AllPermission()));
+  }
+}

+ 1 - 2
server/build.gradle

@@ -62,6 +62,7 @@ if (!isEclipse && !isIdea) {
 dependencies {
 
   compile "org.elasticsearch:elasticsearch-core:${version}"
+  compile "org.elasticsearch:elasticsearch-secure-sm:${version}"
 
   compileOnly project(':libs:plugin-classloader')
   testRuntime project(':libs:plugin-classloader')
@@ -83,8 +84,6 @@ dependencies {
   compile "org.apache.lucene:lucene-spatial3d:${versions.lucene}"
   compile "org.apache.lucene:lucene-suggest:${versions.lucene}"
 
-  compile 'org.elasticsearch:securesm:1.2'
-
   // utilities
   compile "org.elasticsearch:elasticsearch-cli:${version}"
   compile 'com.carrotsearch:hppc:0.7.1'

+ 0 - 1
server/licenses/securesm-1.2.jar.sha1

@@ -1 +0,0 @@
-4c28f5b634497d64b727961430a516f351a099d5

+ 0 - 202
server/licenses/securesm-LICENSE.txt

@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.

+ 0 - 2
server/licenses/securesm-NOTICE.txt

@@ -1,2 +0,0 @@
-
-

+ 12 - 3
server/src/main/java/org/elasticsearch/bootstrap/Security.java

@@ -19,7 +19,6 @@
 
 package org.elasticsearch.bootstrap;
 
-import org.elasticsearch.SecureSM;
 import org.elasticsearch.cli.Command;
 import org.elasticsearch.common.SuppressForbidden;
 import org.elasticsearch.common.io.PathUtils;
@@ -28,6 +27,7 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.http.HttpTransportSettings;
 import org.elasticsearch.plugins.PluginInfo;
+import org.elasticsearch.secure_sm.SecureSM;
 import org.elasticsearch.transport.TcpTransport;
 
 import java.io.IOException;
@@ -53,8 +53,6 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import static org.elasticsearch.bootstrap.FilePermissionUtils.addDirectoryPath;
 import static org.elasticsearch.bootstrap.FilePermissionUtils.addSingleFilePath;
@@ -337,6 +335,7 @@ final class Security {
     private static void addBindPermissions(Permissions policy, Settings settings) {
         addSocketPermissionForHttp(policy, settings);
         addSocketPermissionForTransportProfiles(policy, settings);
+        addSocketPermissionForTribeNodes(policy, settings);
     }
 
     /**
@@ -382,6 +381,16 @@ final class Security {
         addSocketPermissionForPortRange(policy, transportRange);
     }
 
+    private static void addSocketPermissionForTribeNodes(final Permissions policy, final Settings settings) {
+        for (final Settings tribeNodeSettings : settings.getGroups("tribe", true).values()) {
+            // tribe nodes have HTTP disabled by default, so we check if HTTP is enabled before granting
+            if (NetworkModule.HTTP_ENABLED.exists(tribeNodeSettings) && NetworkModule.HTTP_ENABLED.get(tribeNodeSettings)) {
+                addSocketPermissionForHttp(policy, tribeNodeSettings);
+            }
+            addSocketPermissionForTransport(policy, tribeNodeSettings);
+        }
+    }
+
     /**
      * Add dynamic {@link SocketPermission} for the specified port range.
      *

+ 1 - 1
server/src/main/resources/org/elasticsearch/bootstrap/security.policy

@@ -24,7 +24,7 @@
 //// SecurityManager impl:
 //// Must have all permissions to properly perform access checks
 
-grant codeBase "${codebase.securesm}" {
+grant codeBase "${codebase.elasticsearch-secure-sm}" {
   permission java.security.AllPermission;
 };
 

+ 1 - 1
server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy

@@ -48,7 +48,7 @@ grant codeBase "${codebase.randomizedtesting-runner}" {
   // needed to fail tests on uncaught exceptions from other threads
   permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler";
   // needed for top threads handling
-  permission org.elasticsearch.ThreadPermission "modifyArbitraryThreadGroup";
+  permission org.elasticsearch.secure_sm.ThreadPermission "modifyArbitraryThreadGroup";
   // needed for TestClass creation
   permission java.lang.RuntimePermission "accessDeclaredMembers";
 };

+ 2 - 2
test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java

@@ -21,7 +21,6 @@ package org.elasticsearch.bootstrap;
 
 import com.carrotsearch.randomizedtesting.RandomizedRunner;
 import org.apache.lucene.util.LuceneTestCase;
-import org.elasticsearch.SecureSM;
 import org.elasticsearch.common.Booleans;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.SuppressForbidden;
@@ -29,9 +28,9 @@ import org.elasticsearch.common.io.FileSystemUtils;
 import org.elasticsearch.common.io.PathUtils;
 import org.elasticsearch.common.network.IfConfig;
 import org.elasticsearch.plugins.PluginInfo;
+import org.elasticsearch.secure_sm.SecureSM;
 import org.junit.Assert;
 
-import java.io.FilePermission;
 import java.io.InputStream;
 import java.net.SocketPermission;
 import java.net.URL;
@@ -136,6 +135,7 @@ public class BootstrapForTesting {
                     // intellij and eclipse don't package our internal libs, so we need to set the codebases for them manually
                     addClassCodebase(codebases,"plugin-classloader", "org.elasticsearch.plugins.ExtendedPluginsClassLoader");
                     addClassCodebase(codebases,"elasticsearch-nio", "org.elasticsearch.nio.ChannelFactory");
+                    addClassCodebase(codebases, "elasticsearch-secure-sm", "org.elasticsearch.secure_sm.SecureSM");
                 }
                 final Policy testFramework = Security.readPolicy(Bootstrap.class.getResource("test-framework.policy"), codebases);
                 final Policy esPolicy = new ESPolicy(codebases, perms, getPluginPermissions(), true);