only allow code to bind to the user's configured port numbers/ranges

Random code shouldn't be listening on sockets elsewhere.

Today its the wild west, but we only need to grant access to what the user configured.

This means e.g. multicast plugin has to declare its intentions in its security.policy

Closes #14549
This commit is contained in:
Robert Muir 2015-11-07 12:22:12 -05:00
parent f03196193f
commit e06cae84f3
8 changed files with 99 additions and 17 deletions

View File

@ -20,12 +20,17 @@
package org.elasticsearch.bootstrap;
import org.elasticsearch.SecureSM;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.http.netty.NettyHttpServerTransport;
import org.elasticsearch.plugins.PluginInfo;
import org.elasticsearch.transport.netty.NettyTransport;
import java.io.*;
import java.net.SocketPermission;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.AccessMode;
@ -184,9 +189,18 @@ final class Security {
}
}
/** returns dynamic Permissions to configured paths */
/** returns dynamic Permissions to configured paths and bind ports */
static Permissions createPermissions(Environment environment) throws IOException {
Permissions policy = new Permissions();
addFilePermissions(policy, environment);
addBindPermissions(policy, environment.settings());
return policy;
}
/**
* Adds access to all configurable paths.
*/
static void addFilePermissions(Permissions policy, Environment environment) {
// read-only dirs
addPath(policy, "path.home", environment.binFile(), "read,readlink");
addPath(policy, "path.home", environment.libFile(), "read,readlink");
@ -212,7 +226,36 @@ final class Security {
// we just need permission to remove the file if its elsewhere.
policy.add(new FilePermission(environment.pidFile().toString(), "delete"));
}
return policy;
}
static void addBindPermissions(Permissions policy, Settings settings) throws IOException {
// http is simple
String httpRange = settings.get("http.netty.port",
settings.get("http.port",
NettyHttpServerTransport.DEFAULT_PORT_RANGE));
policy.add(new SocketPermission("localhost:" + httpRange, "listen,resolve"));
// transport is waaaay overengineered
Map<String, Settings> profiles = settings.getGroups("transport.profiles", true);
if (!profiles.containsKey(NettyTransport.DEFAULT_PROFILE)) {
profiles = new HashMap<>(profiles);
profiles.put(NettyTransport.DEFAULT_PROFILE, Settings.EMPTY);
}
// loop through all profiles and add permissions for each one, if its valid.
// (otherwise NettyTransport is lenient and ignores it)
for (Map.Entry<String, Settings> entry : profiles.entrySet()) {
Settings profileSettings = entry.getValue();
String name = entry.getKey();
String transportRange = profileSettings.get("port",
settings.get("transport.tcp.port",
NettyTransport.DEFAULT_PORT_RANGE));
// a profile is only valid if its the default profile, or if it has an actual name and specifies a port
boolean valid = NettyTransport.DEFAULT_PROFILE.equals(name) || (Strings.hasLength(name) && profileSettings.get("port") != null);
if (valid) {
policy.add(new SocketPermission("localhost:" + transportRange, "listen,resolve"));
}
}
}
/**

View File

@ -81,6 +81,7 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
public static final boolean DEFAULT_SETTING_PIPELINING = true;
public static final int DEFAULT_SETTING_PIPELINING_MAX_EVENTS = 10000;
public static final String DEFAULT_PORT_RANGE = "9200-9300";
protected final NetworkService networkService;
protected final BigArrays bigArrays;
@ -157,7 +158,7 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
this.maxCompositeBufferComponents = settings.getAsInt("http.netty.max_composite_buffer_components", -1);
this.workerCount = settings.getAsInt("http.netty.worker_count", EsExecutors.boundedNumberOfProcessors(settings) * 2);
this.blockingServer = settings.getAsBoolean("http.netty.http.blocking_server", settings.getAsBoolean(TCP_BLOCKING_SERVER, settings.getAsBoolean(TCP_BLOCKING, false)));
this.port = settings.get("http.netty.port", settings.get("http.port", "9200-9300"));
this.port = settings.get("http.netty.port", settings.get("http.port", DEFAULT_PORT_RANGE));
this.bindHosts = settings.getAsArray("http.netty.bind_host", settings.getAsArray("http.bind_host", settings.getAsArray("http.host", null)));
this.publishHosts = settings.getAsArray("http.netty.publish_host", settings.getAsArray("http.publish_host", settings.getAsArray("http.host", null)));
this.publishPort = settings.getAsInt("http.netty.publish_port", settings.getAsInt("http.publish_port", 0));

View File

@ -19,7 +19,7 @@
// Default security policy file.
// On startup, BootStrap reads environment and adds additional permissions
// for configured paths to these.
// for configured paths and network binding to these.
//// System code permissions:
//// These permissions apply to the JDK itself:
@ -53,7 +53,7 @@ grant {
permission org.elasticsearch.SpecialPermission;
// Allow connecting to the internet anywhere
permission java.net.SocketPermission "*", "accept,listen,connect,resolve";
permission java.net.SocketPermission "*", "accept,connect,resolve";
// Allow read/write to all system properties
permission java.util.PropertyPermission "*", "read,write";

View File

@ -45,7 +45,7 @@ import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadF
* A multicast channel that supports registering for receive events, and sending datagram packets. Allows
* to easily share the same multicast socket if it holds the same config.
*/
public abstract class MulticastChannel implements Closeable {
abstract class MulticastChannel implements Closeable {
/**
* Builds a channel based on the provided config, allowing to control if sharing a channel that uses

View File

@ -23,6 +23,7 @@ import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import org.apache.lucene.util.Constants;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.node.DiscoveryNode;
@ -58,6 +59,8 @@ import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -147,18 +150,21 @@ public class MulticastZenPing extends AbstractLifecycleComponent<ZenPing> implem
List<InetAddress> addresses = Arrays.asList(networkService.resolveBindHostAddresses(address == null ? null : new String[] { address }));
NetworkUtils.sortAddresses(addresses);
multicastChannel = MulticastChannel.getChannel(nodeName(), shared,
new MulticastChannel.Config(port, group, bufferSize, ttl,
addresses.get(0),
deferToInterface),
new Receiver());
final MulticastChannel.Config config = new MulticastChannel.Config(port, group, bufferSize, ttl,
addresses.get(0), deferToInterface);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
multicastChannel = AccessController.doPrivileged(new PrivilegedExceptionAction<MulticastChannel>() {
@Override
public MulticastChannel run() throws Exception {
return MulticastChannel.getChannel(nodeName(), shared, config, new Receiver());
}
});
} catch (Throwable t) {
String msg = "multicast failed to start [{}], disabling. Consider using IPv4 only (by defining env. variable `ES_USE_IPV4`)";
if (logger.isDebugEnabled()) {
logger.debug(msg, t, ExceptionsHelper.detailedMessage(t));
} else {
logger.info(msg, ExceptionsHelper.detailedMessage(t));
}
logger.info(msg, t, ExceptionsHelper.detailedMessage(t));
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.
*/
grant {
// needed to bind multicast to arbitrary port
permission java.net.SocketPermission "localhost:1024-", "listen,resolve";
};

View File

@ -161,7 +161,7 @@ public class MulticastZenPingTests extends ESTestCase {
MulticastSocket multicastSocket = null;
try {
Loggers.getLogger(MulticastZenPing.class).setLevel("TRACE");
multicastSocket = new MulticastSocket(54328);
multicastSocket = new MulticastSocket();
multicastSocket.setReceiveBufferSize(2048);
multicastSocket.setSendBufferSize(2048);
multicastSocket.setSoTimeout(60000);

View File

@ -35,6 +35,7 @@ import org.junit.Assert;
import java.io.FilePermission;
import java.io.InputStream;
import java.net.SocketPermission;
import java.net.URL;
import java.nio.file.Path;
import java.security.Permission;
@ -130,6 +131,14 @@ public class BootstrapForTesting {
perms.add(new RuntimePermission("setIO"));
}
// add bind permissions for testing
// ephemeral ports (note, on java 7 before update 51, this is a different permission)
// this should really be the only one allowed for tests, otherwise they have race conditions
perms.add(new SocketPermission("localhost:0", "listen,resolve"));
// ... but tests are messy. like file permissions, just let them live in a fantasy for now.
// TODO: cut over all tests to bind to ephemeral ports
perms.add(new SocketPermission("localhost:1024-", "listen,resolve"));
// read test-framework permissions
final Policy testFramework = Security.readPolicy(Bootstrap.class.getResource("test-framework.policy"), JarHell.parseClassPath());
final Policy esPolicy = new ESPolicy(perms, getPluginPermissions());