Merge branch 'cassandra-3.11' into trunk

This commit is contained in:
David Capwell 2020-11-05 14:46:12 -08:00
commit 001767de2d
31 changed files with 1298 additions and 212 deletions

View File

@ -3,6 +3,7 @@
* Fix SSTableloader issue when restoring a table named backups (CASSANDRA-16235)
* Invalid serialized size for responses caused by increasing message time by 1ms which caused extra bytes in size calculation (CASSANDRA-16103)
* Throw BufferOverflowException from DataOutputBuffer for better visibility (CASSANDRA-16214)
* TLS connections to the storage port on a node without server encryption configured causes java.io.IOException accessing missing keystore (CASSANDRA-16144)
Merged from 3.11:
Merged from 3.0:
* Prevent invoking enable/disable gossip when not in NORMAL (CASSANDRA-16146)

View File

@ -760,11 +760,16 @@ public class DatabaseDescriptor
throw new ConfigurationException("commitlog_segment_size_in_mb must be at least twice the size of max_mutation_size_in_kb / 1024", false);
// native transport encryption options
if (conf.native_transport_port_ssl != null
&& conf.native_transport_port_ssl != conf.native_transport_port
&& !conf.client_encryption_options.isEnabled())
if (conf.client_encryption_options != null)
{
throw new ConfigurationException("Encryption must be enabled in client_encryption_options for native_transport_port_ssl", false);
conf.client_encryption_options.applyConfig();
if (conf.native_transport_port_ssl != null
&& conf.native_transport_port_ssl != conf.native_transport_port
&& conf.client_encryption_options.tlsEncryptionPolicy() == EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
throw new ConfigurationException("Encryption must be enabled in client_encryption_options for native_transport_port_ssl", false);
}
}
if (conf.max_value_size_in_mb <= 0)
@ -789,6 +794,16 @@ public class DatabaseDescriptor
if (conf.otc_coalescing_enough_coalesced_messages <= 0)
throw new ConfigurationException("otc_coalescing_enough_coalesced_messages must be positive", false);
if (conf.server_encryption_options != null)
{
conf.server_encryption_options.applyConfig();
if (conf.server_encryption_options.enable_legacy_ssl_storage_port &&
conf.server_encryption_options.tlsEncryptionPolicy() == EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
throw new ConfigurationException("enable_legacy_ssl_storage_port is true (enabled) with internode encryption disabled (none). Enable encryption or disable the legacy ssl storage port.");
}
}
Integer maxMessageSize = conf.internode_max_message_size_in_bytes;
if (maxMessageSize != null)
{

View File

@ -17,16 +17,39 @@
*/
package org.apache.cassandra.config;
import java.io.File;
import java.util.List;
import java.util.Objects;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.locator.IEndpointSnitch;
import org.apache.cassandra.locator.InetAddressAndPort;
public class EncryptionOptions
{
Logger logger = LoggerFactory.getLogger(EncryptionOptions.class);
public enum TlsEncryptionPolicy
{
UNENCRYPTED("unencrypted"), OPTIONAL("optionally encrypted"), ENCRYPTED("encrypted");
private final String description;
TlsEncryptionPolicy(String description)
{
this.description = description;
}
public String description()
{
return description;
}
}
public final String keystore;
public final String keystore_password;
public final String truststore;
@ -43,8 +66,12 @@ public class EncryptionOptions
// Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
// classes so we can choose appropriate configuration for each.
// See CASSANDRA-15262 and CASSANDRA-15146
private boolean enabled;
public final Boolean optional;
protected Boolean enabled;
protected Boolean optional;
// Calculated by calling applyConfig() after populating/parsing
protected Boolean isEnabled = null;
protected Boolean isOptional = null;
public EncryptionOptions()
{
@ -58,11 +85,11 @@ public class EncryptionOptions
store_type = "JKS";
require_client_auth = false;
require_endpoint_verification = false;
enabled = false;
optional = true;
enabled = null;
optional = null;
}
public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, boolean enabled, Boolean optional)
public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean enabled, Boolean optional)
{
this.keystore = keystore;
this.keystore_password = keystore_password;
@ -75,14 +102,7 @@ public class EncryptionOptions
this.require_client_auth = require_client_auth;
this.require_endpoint_verification = require_endpoint_verification;
this.enabled = enabled;
if (optional != null) {
this.optional = optional;
} else {
// If someone is asking for an _insecure_ connection and not explicitly telling us to refuse
// encrypted connections we assume they would like to be able to transition to encrypted connections
// in the future.
this.optional = !enabled;
}
this.optional = optional;
}
public EncryptionOptions(EncryptionOptions options)
@ -98,14 +118,48 @@ public class EncryptionOptions
require_client_auth = options.require_client_auth;
require_endpoint_verification = options.require_endpoint_verification;
enabled = options.enabled;
if (options.optional != null) {
optional = options.optional;
} else {
// If someone is asking for an _insecure_ connection and not explicitly telling us to refuse
// encrypted connections we assume they would like to be able to transition to encrypted connections
// in the future.
optional = !enabled;
this.optional = options.optional;
}
/* Computes enabled and optional before use. Because the configuration can be loaded
* through pluggable mechanisms this is the only safe way to make sure that
* enabled and optional are set correctly.
*/
public EncryptionOptions applyConfig()
{
ensureConfigNotApplied();
isEnabled = this.enabled != null && enabled;
if (optional != null)
{
isOptional = optional;
}
// If someone is asking for an _insecure_ connection and not explicitly telling us to refuse
// encrypted connections AND they have a keystore file, we assume they would like to be able
// to transition to encrypted connections in the future.
else if (new File(keystore).exists())
{
isOptional = !isEnabled;
}
else
{
// Otherwise if there's no keystore, not possible to establish an optional secure connection
isOptional = false;
}
return this;
}
private void ensureConfigApplied()
{
if (isEnabled == null || isOptional == null)
throw new IllegalStateException("EncryptionOptions.applyConfig must be called first");
}
private void ensureConfigNotApplied()
{
if (isEnabled != null || isOptional != null)
throw new IllegalStateException("EncryptionOptions cannot be changed after configuration applied");
}
/**
@ -113,109 +167,153 @@ public class EncryptionOptions
*
* @return if the channel should be encrypted
*/
public boolean isEnabled() {
return this.enabled;
public Boolean isEnabled() {
ensureConfigApplied();
return isEnabled;
}
/**
* Sets if encryption should be enabled for this channel. Note that this should only be called by
* the configuration parser or tests. It is public only for that purpose, mutating enabled state
* is probably a bad idea.
* @param enabled
* @param enabled value to set
*/
public void setEnabled(boolean enabled) {
public void setEnabled(Boolean enabled) {
ensureConfigNotApplied();
this.enabled = enabled;
}
/**
* Indicates if the channel may be encrypted (but is not required to be).
* Explicitly providing a value in the configuration take precedent.
* If no optional value is set and !isEnabled(), then optional connections are allowed
* if a keystore exists. Without it, it would be impossible to establish the connections.
*
* Return type is Boolean even though it can never be null so that snakeyaml can find it
* @return if the channel may be encrypted
*/
public Boolean isOptional()
{
ensureConfigApplied();
return isOptional;
}
/**
* Sets if encryption should be optional for this channel. Note that this should only be called by
* the configuration parser or tests. It is public only for that purpose, mutating enabled state
* is probably a bad idea.
* @param optional value to set
*/
public void setOptional(boolean optional) {
ensureConfigNotApplied();
this.optional = optional;
}
public TlsEncryptionPolicy tlsEncryptionPolicy()
{
if (isOptional())
{
return TlsEncryptionPolicy.OPTIONAL;
}
else if (isEnabled())
{
return TlsEncryptionPolicy.ENCRYPTED;
}
else
{
return TlsEncryptionPolicy.UNENCRYPTED;
}
}
public EncryptionOptions withKeyStore(String keystore)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withKeyStorePassword(String keystore_password)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withTrustStore(String truststore)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withTrustStorePassword(String truststore_password)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withCipherSuites(List<String> cipher_suites)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withCipherSuites(String ... cipher_suites)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, ImmutableList.copyOf(cipher_suites),
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withProtocol(String protocol)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withAlgorithm(String algorithm)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withStoreType(String store_type)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withRequireClientAuth(boolean require_client_auth)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withEnabled(boolean enabled)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
public EncryptionOptions withOptional(boolean optional)
public EncryptionOptions withOptional(Boolean optional)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional);
enabled, optional).applyConfig();
}
/**
@ -260,8 +358,8 @@ public class EncryptionOptions
result += 31 * (protocol == null ? 0 : protocol.hashCode());
result += 31 * (algorithm == null ? 0 : algorithm.hashCode());
result += 31 * (store_type == null ? 0 : store_type.hashCode());
result += 31 * Boolean.hashCode(enabled);
result += 31 * Boolean.hashCode(optional);
result += 31 * (enabled == null ? 0 : Boolean.hashCode(enabled));
result += 31 * (optional == null ? 0 : Boolean.hashCode(optional));
result += 31 * (cipher_suites == null ? 0 : cipher_suites.hashCode());
result += 31 * Boolean.hashCode(require_client_auth);
result += 31 * Boolean.hashCode(require_endpoint_verification);
@ -286,7 +384,7 @@ public class EncryptionOptions
public ServerEncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean optional, InternodeEncryption internode_encryption, boolean enable_legacy_ssl_storage_port)
{
super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, internode_encryption != InternodeEncryption.none, optional);
super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, null, optional);
this.internode_encryption = internode_encryption;
this.enable_legacy_ssl_storage_port = enable_legacy_ssl_storage_port;
}
@ -298,8 +396,30 @@ public class EncryptionOptions
this.enable_legacy_ssl_storage_port = options.enable_legacy_ssl_storage_port;
}
public boolean isEnabled() {
return this.internode_encryption != InternodeEncryption.none;
@Override
public EncryptionOptions applyConfig()
{
return applyConfigInternal();
}
private ServerEncryptionOptions applyConfigInternal()
{
super.applyConfig();
isEnabled = this.internode_encryption != InternodeEncryption.none;
if (this.enabled != null && this.enabled && !isEnabled)
{
logger.warn("Setting server_encryption_options.enabled has no effect, use internode_encryption");
}
// regardless of the optional flag, if the internode encryption is set to rack or dc
// it must be optional so that unencrypted connections within the rack or dc can be established.
isOptional = super.isOptional || internode_encryption == InternodeEncryption.rack || internode_encryption == InternodeEncryption.dc;
return this;
}
public boolean shouldEncrypt(InetAddressAndPort endpoint)
@ -330,98 +450,98 @@ public class EncryptionOptions
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withKeyStorePassword(String keystore_password)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withTrustStore(String truststore)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withTrustStorePassword(String truststore_password)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withCipherSuites(List<String> cipher_suites)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withCipherSuites(String ... cipher_suites)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, ImmutableList.copyOf(cipher_suites),
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withProtocol(String protocol)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withAlgorithm(String algorithm)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withStoreType(String store_type)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withRequireClientAuth(boolean require_client_auth)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withOptional(boolean optional)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withInternodeEncryption(InternodeEncryption internode_encryption)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withLegacySslStoragePort(boolean enable_legacy_ssl_storage_port)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port);
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
}

View File

@ -153,7 +153,9 @@ public class YamlConfigurationLoader implements ConfigurationLoader
return node;
}
});
return (T) constructor.getSingleData(klass);
T value = (T) constructor.getSingleData(klass);
propertiesChecker.check();
return value;
}
static class CustomConstructor extends CustomClassLoaderConstructor

View File

@ -166,7 +166,7 @@ final class SettingsTable extends AbstractVirtualTable
result.row(f.getName() + "_cipher_suites").column(VALUE, value.cipher_suites.toString());
result.row(f.getName() + "_client_auth").column(VALUE, Boolean.toString(value.require_client_auth));
result.row(f.getName() + "_endpoint_verification").column(VALUE, Boolean.toString(value.require_endpoint_verification));
result.row(f.getName() + "_optional").column(VALUE, Boolean.toString(value.optional));
result.row(f.getName() + "_optional").column(VALUE, Boolean.toString(value.isOptional()));
if (value instanceof EncryptionOptions.ServerEncryptionOptions)
{

View File

@ -24,8 +24,6 @@ import java.util.List;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import javax.net.ssl.SSLSession;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -61,7 +59,6 @@ import static org.apache.cassandra.net.MessagingService.VERSION_40;
import static org.apache.cassandra.net.MessagingService.current_version;
import static org.apache.cassandra.net.MessagingService.minimum_version;
import static org.apache.cassandra.net.SocketFactory.WIRETRACE;
import static org.apache.cassandra.net.SocketFactory.encryptionLogStatement;
import static org.apache.cassandra.net.SocketFactory.newSslHandler;
public class InboundConnectionInitiator
@ -98,17 +95,19 @@ public class InboundConnectionInitiator
// order of handlers: ssl -> logger -> handshakeHandler
// For either unencrypted or transitional modes, allow Ssl optionally.
if (settings.encryption.optional)
switch(settings.encryption.tlsEncryptionPolicy())
{
pipeline.addFirst("ssl", new OptionalSslHandler(settings.encryption));
}
else
{
SslContext sslContext = SSLFactory.getOrCreateSslContext(settings.encryption, true, SSLFactory.SocketType.SERVER);
InetSocketAddress peer = settings.encryption.require_endpoint_verification ? channel.remoteAddress() : null;
SslHandler sslHandler = newSslHandler(channel, sslContext, peer);
logger.trace("creating inbound netty SslContext: context={}, engine={}", sslContext.getClass().getName(), sslHandler.engine().getClass().getName());
pipeline.addFirst("ssl", sslHandler);
case UNENCRYPTED:
// Handler checks for SSL connection attempts and cleanly rejects them if encryption is disabled
pipeline.addFirst("rejectssl", new RejectSslHandler());
break;
case OPTIONAL:
pipeline.addFirst("ssl", new OptionalSslHandler(settings.encryption));
break;
case ENCRYPTED:
SslHandler sslHandler = getSslHandler("creating", channel, settings.encryption);
pipeline.addFirst("ssl", sslHandler);
break;
}
if (WIRETRACE)
@ -214,7 +213,6 @@ public class InboundConnectionInitiator
failHandshake(ctx);
}, HandshakeProtocol.TIMEOUT_MILLIS, MILLISECONDS);
logSsl(ctx);
authenticate(ctx.channel().remoteAddress());
}
@ -231,17 +229,6 @@ public class InboundConnectionInitiator
throw new IOException("Authentication failure for inbound connection from peer " + addr);
}
private void logSsl(ChannelHandlerContext ctx)
{
SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
if (sslHandler != null)
{
SSLSession session = sslHandler.engine().getSession();
logger.info("connection from peer {} to {}, protocol = {}",
ctx.channel().remoteAddress(), ctx.channel().localAddress(), session.getProtocol());
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception
{
@ -401,7 +388,7 @@ public class InboundConnectionInitiator
channel.id().asShortText()),
current_version,
initiate.framing,
pipeline.get("ssl") != null ? encryptionLogStatement(pipeline.channel(), settings.encryption) : "disabled");
SocketFactory.encryptionConnectionSummary(pipeline.channel()));
}
@VisibleForTesting
@ -460,7 +447,7 @@ public class InboundConnectionInitiator
handler.id(true),
useMessagingVersion,
initiate.framing,
pipeline.get("ssl") != null ? encryptionLogStatement(pipeline.channel(), settings.encryption) : "disabled");
SocketFactory.encryptionConnectionSummary(pipeline.channel()));
pipeline.addLast("deserialize", handler);
@ -468,6 +455,16 @@ public class InboundConnectionInitiator
}
}
private static SslHandler getSslHandler(String description, Channel channel, EncryptionOptions.ServerEncryptionOptions encryptionOptions) throws IOException
{
final boolean buildTrustStore = true;
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, buildTrustStore, SSLFactory.SocketType.SERVER);
InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? (InetSocketAddress) channel.remoteAddress() : null;
SslHandler sslHandler = newSslHandler(channel, sslContext, peer);
logger.trace("{} inbound netty SslContext: context={}, engine={}", description, sslContext.getClass().getName(), sslHandler.engine().getClass().getName());
return sslHandler;
}
private static class OptionalSslHandler extends ByteToMessageDecoder
{
private final EncryptionOptions.ServerEncryptionOptions encryptionOptions;
@ -489,10 +486,7 @@ public class InboundConnectionInitiator
if (SslHandler.isEncrypted(in))
{
// Connection uses SSL/TLS, replace the detection handler with a SslHandler and so use encryption.
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, true, SSLFactory.SocketType.SERVER);
Channel channel = ctx.channel();
InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? (InetSocketAddress) channel.remoteAddress() : null;
SslHandler sslHandler = newSslHandler(channel, sslContext, peer);
SslHandler sslHandler = getSslHandler("replacing optional", ctx.channel(), encryptionOptions);
ctx.pipeline().replace(this, "ssl", sslHandler);
}
else
@ -503,4 +497,31 @@ public class InboundConnectionInitiator
}
}
}
private static class RejectSslHandler extends ByteToMessageDecoder
{
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
{
if (in.readableBytes() < 5)
{
// To detect if SSL must be used we need to have at least 5 bytes, so return here and try again
// once more bytes a ready.
return;
}
if (SslHandler.isEncrypted(in))
{
logger.info("Rejected incoming TLS connection before negotiating from {} to {}. TLS is explicitly disabled by configuration.",
ctx.channel().remoteAddress(), ctx.channel().localAddress());
in.readBytes(in.readableBytes()); // discard the readable bytes so not called again
ctx.close();
}
else
{
// Incoming connection did not attempt TLS/SSL encryption, just remove the detection handler and continue without
// SslHandler in the pipeline.
ctx.pipeline().remove(this);
}
}
}
}

View File

@ -84,7 +84,7 @@ public class InboundConnectionSettings
public String toString()
{
return format("address: (%s), nic: %s, encryption: %s",
bindAddress, FBUtilities.getNetworkInterface(bindAddress.address), SocketFactory.encryptionLogStatement(null, encryption));
bindAddress, FBUtilities.getNetworkInterface(bindAddress.address), SocketFactory.encryptionOptionsSummary(encryption));
}
public InboundConnectionSettings withAuthenticator(IInternodeAuthenticator authenticator)
@ -152,12 +152,12 @@ public class InboundConnectionSettings
acceptMessaging, acceptStreaming, socketFactory, handlers);
}
public InboundConnectionSettings withLegacyDefaults()
public InboundConnectionSettings withLegacySslStoragePortDefaults()
{
ServerEncryptionOptions encryption = this.encryption;
if (encryption == null)
encryption = DatabaseDescriptor.getInternodeMessagingEncyptionOptions();
encryption = encryption.withOptional(false);
encryption = encryption.withOptional(false).withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all);
return this.withBindAddress(bindAddress.withPort(DatabaseDescriptor.getSSLStoragePort()))
.withEncryption(encryption)

View File

@ -200,7 +200,7 @@ class InboundSockets
private static void addBindings(InboundConnectionSettings template, ImmutableList.Builder<InboundSocket> out)
{
InboundConnectionSettings settings = template.withDefaults();
InboundConnectionSettings legacySettings = template.withLegacyDefaults();
InboundConnectionSettings legacySettings = template.withLegacySslStoragePortDefaults();
if (settings.encryption.enable_legacy_ssl_storage_port)
{
@ -266,4 +266,4 @@ class InboundSockets
{
return sockets;
}
}
}

View File

@ -30,7 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.stream.Stream;
import javax.annotation.Nullable;
@ -1152,7 +1151,7 @@ public class OutboundConnection
id(true),
success.messagingVersion,
settings.framing,
encryptionLogStatement(channel, settings.encryption));
encryptionConnectionSummary(channel));
break;
case RETRY:

View File

@ -35,7 +35,6 @@ import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.config.DatabaseDescriptor.getEndpointSnitch;
import static org.apache.cassandra.net.MessagingService.VERSION_40;
import static org.apache.cassandra.net.MessagingService.instance;
import static org.apache.cassandra.net.SocketFactory.encryptionLogStatement;
import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
/**
@ -171,7 +170,7 @@ public class OutboundConnectionSettings
public String toString()
{
return String.format("peer: (%s, %s), framing: %s, encryption: %s",
to, connectTo, framing, encryptionLogStatement(encryption));
to, connectTo, framing, SocketFactory.encryptionOptionsSummary(encryption));
}
public OutboundConnectionSettings withAuthenticator(IInternodeAuthenticator authenticator)

View File

@ -228,39 +228,41 @@ public final class SocketFactory
return sslHandler;
}
static String encryptionLogStatement(EncryptionOptions options)
/**
* Summarizes the intended encryption options, suitable for logging. Once a connection is established, use
* {@link SocketFactory#encryptionConnectionSummary} below.
* @param options options to summarize
* @return description of encryption options
*/
static String encryptionOptionsSummary(EncryptionOptions options)
{
if (options == null)
return "disabled";
if (options == null || options.tlsEncryptionPolicy() == EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
return EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED.description();
String encryptionType = SSLFactory.openSslIsAvailable() ? "openssl" : "jdk";
return "enabled (" + encryptionType + ')';
return options.tlsEncryptionPolicy().description() + '(' + encryptionType + ')';
}
static String encryptionLogStatement(Channel channel, EncryptionOptions options)
/**
* Summarizes the encryption status of a channel, suitable for logging.
* @return description of channel encryption
*/
static String encryptionConnectionSummary(Channel channel)
{
if (options == null || !options.isEnabled())
return "disabled";
StringBuilder sb = new StringBuilder(64);
if (options.optional)
sb.append("optional (factory=");
else
sb.append("enabled (factory=");
sb.append(SSLFactory.openSslIsAvailable() ? "openssl" : "jdk");
final SslHandler sslHandler = channel == null ? null : channel.pipeline().get(SslHandler.class);
if (sslHandler != null)
final SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
if (sslHandler == null)
{
SSLSession session = sslHandler.engine().getSession();
sb.append(";protocol=")
.append(session.getProtocol())
.append(";cipher=")
.append(session.getCipherSuite());
return EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED.description();
}
SSLSession session = sslHandler.engine().getSession();
sb.append(')');
return sb.toString();
return "encrypted(factory=" +
(SSLFactory.openSslIsAvailable() ? "openssl" : "jdk") +
";protocol=" +
(session != null ? session.getProtocol() : "MISSING SESSION") +
";cipher=" +
(session != null ? session.getCipherSuite() : "MISSING SESSION") +
')';
}
EventLoopGroup defaultGroup()

View File

@ -229,7 +229,7 @@ public final class SSLFactory
}
catch (Exception e)
{
throw new IOException("failed to build trust manager store for secure connections", e);
throw new IOException("failed to build key manager store for secure connections", e);
}
}
@ -370,13 +370,13 @@ public final class SSLFactory
List<HotReloadableFile> fileList = new ArrayList<>();
if (serverOpts != null && serverOpts.isEnabled())
if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
fileList.add(new HotReloadableFile(serverOpts.keystore));
fileList.add(new HotReloadableFile(serverOpts.truststore));
}
if (clientOpts != null && clientOpts.isEnabled())
if (clientOpts != null && clientOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
fileList.add(new HotReloadableFile(clientOpts.keystore));
fileList.add(new HotReloadableFile(clientOpts.truststore));
@ -405,8 +405,8 @@ public final class SSLFactory
{
try
{
// Ensure we're able to create both server & client SslContexts
if (serverOpts != null && serverOpts.isEnabled())
// Ensure we're able to create both server & client SslContexts if they might ever be needed
if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
createNettySslContext(serverOpts, true, SocketType.SERVER, openSslIsAvailable());
createNettySslContext(serverOpts, true, SocketType.CLIENT, openSslIsAvailable());
@ -419,8 +419,8 @@ public final class SSLFactory
try
{
// Ensure we're able to create both server & client SslContexts
if (clientOpts != null && clientOpts.isEnabled())
// Ensure we're able to create both server & client SslContexts if they might ever be needed
if (clientOpts != null && clientOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.SERVER, openSslIsAvailable());
createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.CLIENT, openSslIsAvailable());

View File

@ -32,7 +32,9 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.Version;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.metrics.ClientMetrics;
import org.apache.cassandra.transport.Message;
import org.apache.cassandra.transport.Server;
@ -79,27 +81,40 @@ public class NativeTransportService
.withEventLoopGroup(workerGroup)
.withHost(nativeAddr);
if (!DatabaseDescriptor.getNativeProtocolEncryptionOptions().isEnabled())
EncryptionOptions.TlsEncryptionPolicy encryptionPolicy = DatabaseDescriptor.getNativeProtocolEncryptionOptions().tlsEncryptionPolicy();
Server regularPortServer;
Server tlsPortServer = null;
// If an SSL port is separately supplied for the native transport, listen for unencrypted connections on the
// regular port, and encryption / optionally encrypted connections on the ssl port.
if (nativePort != nativePortSSL)
{
servers = Collections.singleton(builder.withSSL(false).withPort(nativePort).build());
regularPortServer = builder.withTlsEncryptionPolicy(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED).withPort(nativePort).build();
switch(encryptionPolicy)
{
case OPTIONAL: // FALLTHRU - encryption is optional on the regular port, but encrypted on the tls port.
case ENCRYPTED:
tlsPortServer = builder.withTlsEncryptionPolicy(encryptionPolicy).withPort(nativePortSSL).build();
break;
case UNENCRYPTED: // Should have been caught by DatabaseDescriptor.applySimpleConfig
throw new IllegalStateException("Encryption must be enabled in client_encryption_options for native_transport_port_ssl");
default:
throw new IllegalStateException("Unrecognized TLS encryption policy: " + encryptionPolicy);
}
}
// Otherwise, if only the regular port is supplied, listen as the encryption policy specifies
else
{
regularPortServer = builder.withTlsEncryptionPolicy(encryptionPolicy).withPort(nativePort).build();
}
if (tlsPortServer == null)
{
servers = Collections.singleton(regularPortServer);
}
else
{
if (nativePort != nativePortSSL)
{
// user asked for dedicated ssl port for supporting both non-ssl and ssl connections
servers = Collections.unmodifiableList(
Arrays.asList(
builder.withSSL(false).withPort(nativePort).build(),
builder.withSSL(true).withPort(nativePortSSL).build()
)
);
}
else
{
// ssl only mode using configured native port
servers = Collections.singleton(builder.withSSL(true).withPort(nativePort).build());
}
servers = Collections.unmodifiableList(Arrays.asList(regularPortServer, tlsPortServer));
}
ClientMetrics.instance.init(servers);
@ -112,6 +127,7 @@ public class NativeTransportService
*/
public void start()
{
logger.info("Using Netty Version: {}", Version.identify().entrySet());
initialize();
servers.forEach(Server::start);
}

View File

@ -439,8 +439,10 @@ public class LoaderOptions
else
sslStoragePort = config.ssl_storage_port;
throttle = config.stream_throughput_outbound_megabits_per_sec;
clientEncOptions = config.client_encryption_options;
// Copy the encryption options and apply the config so that argument parsing can accesss isEnabled.
clientEncOptions = config.client_encryption_options.applyConfig();
serverEncOptions = config.server_encryption_options;
serverEncOptions.applyConfig();
if (cmd.hasOption(THROTTLE_MBITS))
{
@ -473,11 +475,13 @@ public class LoaderOptions
if (cmd.hasOption(SSL_TRUSTSTORE))
{
clientEncOptions = clientEncOptions.withTrustStore(cmd.getOptionValue(SSL_TRUSTSTORE));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_TRUSTSTORE_PW))
{
clientEncOptions = clientEncOptions.withTrustStorePassword(cmd.getOptionValue(SSL_TRUSTSTORE_PW));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_KEYSTORE))
@ -485,31 +489,37 @@ public class LoaderOptions
// if a keystore was provided, lets assume we'll need to use
clientEncOptions = clientEncOptions.withKeyStore(cmd.getOptionValue(SSL_KEYSTORE))
.withRequireClientAuth(true);
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_KEYSTORE_PW))
{
clientEncOptions = clientEncOptions.withKeyStorePassword(cmd.getOptionValue(SSL_KEYSTORE_PW));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_PROTOCOL))
{
clientEncOptions = clientEncOptions.withProtocol(cmd.getOptionValue(SSL_PROTOCOL));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_ALGORITHM))
{
clientEncOptions = clientEncOptions.withAlgorithm(cmd.getOptionValue(SSL_ALGORITHM));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_STORE_TYPE))
{
clientEncOptions = clientEncOptions.withStoreType(cmd.getOptionValue(SSL_STORE_TYPE));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(SSL_CIPHER_SUITES))
{
clientEncOptions = clientEncOptions.withCipherSuites(cmd.getOptionValue(SSL_CIPHER_SUITES).split(","));
clientEncOptions.applyConfig();
}
if (cmd.hasOption(TARGET_KEYSPACE))

View File

@ -50,7 +50,7 @@ public class Client extends SimpleClient
public Client(String host, int port, ProtocolVersion version, EncryptionOptions encryptionOptions)
{
super(host, port, version, version.isBeta(), encryptionOptions);
super(host, port, version, version.isBeta(), new EncryptionOptions(encryptionOptions).applyConfig());
setEventHandler(eventHandler);
}
@ -294,7 +294,7 @@ public class Client extends SimpleClient
int port = Integer.parseInt(args[1]);
ProtocolVersion version = args.length == 3 ? ProtocolVersion.decode(Integer.parseInt(args[2]), DatabaseDescriptor.getNativeTransportAllowOlderProtocols()) : ProtocolVersion.CURRENT;
EncryptionOptions encryptionOptions = new EncryptionOptions();
EncryptionOptions encryptionOptions = new EncryptionOptions().applyConfig();
System.out.println("CQL binary protocol console " + host + "@" + port + " using native protocol version " + version);
new Client(host, port, version, encryptionOptions).run();

View File

@ -48,7 +48,6 @@ import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.Version;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -88,7 +87,7 @@ public class Server implements CassandraDaemon.Server
};
public final InetSocketAddress socket;
public boolean useSSL = false;
public final EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy;
private final AtomicBoolean isRunning = new AtomicBoolean(false);
private EventLoopGroup workerGroup;
@ -96,7 +95,7 @@ public class Server implements CassandraDaemon.Server
private Server (Builder builder)
{
this.socket = builder.getSocket();
this.useSSL = builder.useSSL;
this.tlsEncryptionPolicy = builder.tlsEncryptionPolicy;
if (builder.workerGroup != null)
{
workerGroup = builder.workerGroup;
@ -141,29 +140,27 @@ public class Server implements CassandraDaemon.Server
if (workerGroup != null)
bootstrap = bootstrap.group(workerGroup);
if (this.useSSL)
{
final EncryptionOptions clientEnc = DatabaseDescriptor.getNativeProtocolEncryptionOptions();
final EncryptionOptions clientEnc = DatabaseDescriptor.getNativeProtocolEncryptionOptions();
if (clientEnc.optional)
{
logger.info("Enabling optionally encrypted CQL connections between client and server");
bootstrap.childHandler(new OptionalSecureInitializer(this, clientEnc));
}
else
{
logger.info("Enabling encrypted CQL connections between client and server");
bootstrap.childHandler(new SecureInitializer(this, clientEnc));
}
}
else
switch (this.tlsEncryptionPolicy)
{
bootstrap.childHandler(new Initializer(this));
case UNENCRYPTED:
bootstrap.childHandler(new Initializer(this));
break;
case OPTIONAL:
logger.debug("Enabling optionally encrypted CQL connections between client and server");
bootstrap.childHandler(new OptionalSecureInitializer(this, clientEnc));
break;
case ENCRYPTED:
logger.debug("Enabling encrypted CQL connections between client and server");
bootstrap.childHandler(new SecureInitializer(this, clientEnc));
break;
default:
throw new IllegalStateException("Unrecognized TLS encryption policy: " + this.tlsEncryptionPolicy);
}
// Bind and start to accept incoming connections.
logger.info("Using Netty Version: {}", Version.identify().entrySet());
logger.info("Starting listening for CQL clients on {} ({})...", socket, this.useSSL ? "encrypted" : "unencrypted");
logger.info("Starting listening for CQL clients on {} ({})...", socket, clientEnc.tlsEncryptionPolicy().description());
ChannelFuture bindFuture = bootstrap.bind(socket);
if (!bindFuture.awaitUninterruptibly().isSuccess())
@ -219,14 +216,14 @@ public class Server implements CassandraDaemon.Server
{
private EventLoopGroup workerGroup;
private EventExecutor eventExecutorGroup;
private boolean useSSL = false;
private EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy = EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED;
private InetAddress hostAddr;
private int port = -1;
private InetSocketAddress socket;
public Builder withSSL(boolean useSSL)
public Builder withTlsEncryptionPolicy(EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy)
{
this.useSSL = useSSL;
this.tlsEncryptionPolicy = tlsEncryptionPolicy;
return this;
}

View File

@ -114,7 +114,7 @@ public class SimpleClient implements Closeable
throw new IllegalArgumentException(String.format("Beta version of server used (%s), but USE_BETA flag is not set", version));
this.version = version;
this.encryptionOptions = encryptionOptions;
this.encryptionOptions = new EncryptionOptions(encryptionOptions).applyConfig();
}
public SimpleClient(String host, int port)

View File

@ -57,6 +57,7 @@ public class InstanceConfig implements IInstanceConfig
public final UUID hostId;
public UUID hostId() { return hostId; }
private final Map<String, Object> params = new TreeMap<>();
private final Map<String, Object> dtestParams = new TreeMap<>();
private final EnumSet featureFlags;
@ -125,6 +126,7 @@ public class InstanceConfig implements IInstanceConfig
this.num = copy.num;
this.networkTopology = new NetworkTopology(copy.networkTopology);
this.params.putAll(copy.params);
this.dtestParams.putAll(copy.dtestParams);
this.hostId = copy.hostId;
this.featureFlags = copy.featureFlags;
this.broadcastAddressAndPort = copy.broadcastAddressAndPort;
@ -190,8 +192,7 @@ public class InstanceConfig implements IInstanceConfig
{
if (value == null)
value = NULL;
params.put(fieldName, value);
getParams(fieldName).put(fieldName, value);
return this;
}
@ -201,10 +202,18 @@ public class InstanceConfig implements IInstanceConfig
value = NULL;
// test value
params.put(fieldName, value);
getParams(fieldName).put(fieldName, value);
return this;
}
private Map<String, Object> getParams(String fieldName)
{
Map<String, Object> map = params;
if (fieldName.startsWith("dtest"))
map = dtestParams;
return map;
}
public void propagate(Object writeToConfig, Map<Class<?>, Function<Object, Object>> mapping)
{
throw new IllegalStateException("In-JVM dtests no longer support propagate");
@ -218,17 +227,17 @@ public class InstanceConfig implements IInstanceConfig
public Object get(String name)
{
return params.get(name);
return getParams(name).get(name);
}
public int getInt(String name)
{
return (Integer)params.get(name);
return (Integer) get(name);
}
public String getString(String name)
{
return (String)params.get(name);
return (String) get(name);
}
public Map<String, Object> getParams()

View File

@ -0,0 +1,295 @@
/*
* 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.distributed.test;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.FutureListener;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.utils.concurrent.SimpleCondition;
public class AbstractEncryptionOptionsImpl extends TestBaseImpl
{
Logger logger = LoggerFactory.getLogger(EncryptionOptions.class);
final static String validKeyStorePath = "test/conf/cassandra_ssl_test.keystore";
final static String validKeyStorePassword = "cassandra";
final static String validTrustStorePath = "test/conf/cassandra_ssl_test.truststore";
final static String validTrustStorePassword = "cassandra";
final static Map<String,Object> validKeystore = ImmutableMap.of("keystore", validKeyStorePath,
"keystore_password", validKeyStorePassword,
"truststore", validTrustStorePath,
"truststore_password", validTrustStorePassword);
// Result of a TlsConnection.connect call. The result is updated as the TLS connection
// sequence takes place. The nextOnFailure/nextOnSuccess allows the discard handler
// to correctly update state if an unexpected exception is thrown.
public enum ConnectResult {
UNINITIALIZED,
FAILED_TO_NEGOTIATE,
NEVER_CONNECTED,
NEGOTIATED,
CONNECTED_AND_ABOUT_TO_NEGOTIATE(FAILED_TO_NEGOTIATE, NEGOTIATED),
CONNECTING(NEVER_CONNECTED, CONNECTED_AND_ABOUT_TO_NEGOTIATE);
public final ConnectResult nextOnFailure;
public final ConnectResult nextOnSuccess;
ConnectResult()
{
nextOnFailure = null;
nextOnSuccess = null;
}
ConnectResult(ConnectResult nextOnFailure, ConnectResult nextOnSuccess)
{
this.nextOnFailure = nextOnFailure;
this.nextOnSuccess = nextOnSuccess;
}
}
public class TlsConnection
{
final String host;
final int port;
final EncryptionOptions encryptionOptions = new EncryptionOptions()
.withEnabled(true)
.withKeyStore(validKeyStorePath).withKeyStorePassword(validKeyStorePassword)
.withTrustStore(validTrustStorePath).withTrustStorePassword(validTrustStorePassword);
private Throwable lastThrowable;
public TlsConnection(String host, int port)
{
this.host = host;
this.port = port;
}
public synchronized Throwable lastThrowable()
{
return lastThrowable;
}
private synchronized void setLastThrowable(Throwable cause)
{
lastThrowable = cause;
}
final AtomicReference<ConnectResult> result = new AtomicReference<>(ConnectResult.UNINITIALIZED);
void setResult(String why, ConnectResult expected, ConnectResult newResult)
{
if (newResult == null)
return;
logger.debug("Setting progress from {} to {}", expected, expected.nextOnSuccess);
result.getAndUpdate(v -> {
if (v == expected)
return newResult;
else
throw new IllegalStateException(
String.format("CAS attempt on %s failed from %s to %s but %s did not match expected value",
why, expected, newResult, v));
});
}
void successProgress()
{
ConnectResult current = result.get();
setResult("success", current, current.nextOnSuccess);
}
void failure()
{
ConnectResult current = result.get();
setResult("failure", current, current.nextOnFailure);
}
ConnectResult connect() throws Throwable
{
AtomicInteger connectAttempts = new AtomicInteger(0);
result.set(ConnectResult.UNINITIALIZED);
setLastThrowable(null);
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, true,
SSLFactory.SocketType.CLIENT);
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
SimpleCondition attemptCompleted = new SimpleCondition();
// Listener on the SSL handshake makes sure that the test completes immediately as
// the server waits to receive a message over the TLS connection, so the discardHandler.decode
// will likely never be called. The lambda has to handle it's own exceptions as it's a listener,
// not in the request pipeline to pass them on to discardHandler.
FutureListener<Channel> handshakeResult = channelFuture -> {
try
{
logger.debug("handshakeFuture() listener called");
channelFuture.get();
successProgress();
}
catch (Throwable cause)
{
logger.info("handshakeFuture() threw", cause);
failure();
setLastThrowable(cause);
}
attemptCompleted.signalAll();
};
ChannelHandler connectHandler = new ByteToMessageDecoder()
{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
logger.debug("connectHandler.channelActive");
int count = connectAttempts.incrementAndGet();
if (count > 1)
{
logger.info("connectHandler.channelActive called more than once - {}", count);
}
successProgress();
// Add the handler after the connection is established to make sure the connection
// progress is recorded
final SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
sslHandler.handshakeFuture().addListener(handshakeResult);
super.channelActive(ctx);
}
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
{
logger.debug("connectHandler.decode - readable bytes {}", in.readableBytes());
ctx.pipeline().remove(this);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
logger.debug("connectHandler.exceptionCaught", cause);
setLastThrowable(cause);
failure();
attemptCompleted.signalAll();
}
};
ChannelHandler discardHandler = new ByteToMessageDecoder()
{
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
{
logger.info("discardHandler.decode - {} readable bytes made it past SSL negotiation, discarding.",
in.readableBytes());
in.readBytes(in.readableBytes());
attemptCompleted.signalAll();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
logger.debug("discardHandler.exceptionCaught", cause);
setLastThrowable(cause);
failure();
attemptCompleted.signalAll();
}
};
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.handler(new ChannelInitializer<Channel>()
{
@Override
protected void initChannel(Channel channel)
{
SslHandler sslHandler = sslContext.newHandler(channel.alloc());
channel.pipeline().addFirst(connectHandler, sslHandler, discardHandler);
}
});
result.set(ConnectResult.CONNECTING);
ChannelFuture f = b.connect(host, port);
try
{
f.sync();
attemptCompleted.await(15, TimeUnit.SECONDS);
}
finally
{
f.channel().close();
}
return result.get();
}
void assertCannotConnect() throws Throwable
{
try
{
connect();
}
catch (java.net.ConnectException ex)
{
// verify it was not possible to connect before starting the server
}
}
}
/* Provde the cluster cannot start with the configured options */
void assertCannotStartDueToConfigurationException(Cluster cluster)
{
Throwable tr = null;
try
{
cluster.startup();
}
catch (Throwable maybeConfigException)
{
tr = maybeConfigException;
}
if (tr == null)
{
Assert.fail("Expected a ConfigurationException");
}
else
{
Assert.assertEquals(ConfigurationException.class.getName(), tr.getClass().getName());
}
}
}

View File

@ -49,8 +49,7 @@ public class IncRepairTruncationTest extends TestBaseImpl
{
ExecutorService es = Executors.newFixedThreadPool(3);
try(Cluster cluster = init(Cluster.build(2)
.withConfig(config -> config.set("disable_incremental_repair", false)
.with(GOSSIP)
.withConfig(config -> config.with(GOSSIP)
.with(NETWORK))
.start()))
{

View File

@ -0,0 +1,218 @@
/*
* 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.distributed.test;
import java.net.InetAddress;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImpl
{
@Test
public void nodeWillNotStartWithBadKeystoreTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options",
ImmutableMap.of("optional", true,
"keystore", "/path/to/bad/keystore/that/should/not/exist",
"truststore", "/path/to/bad/truststore/that/should/not/exist"));
}).createWithoutStarting())
{
assertCannotStartDueToConfigurationException(cluster);
}
}
@Test
public void legacySslPortProvidedWithEncryptionNoneWillNotStartTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("ssl_storage_port", 7013);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("internode_encryption", "none")
.put("optional", false)
.put("enable_legacy_ssl_storage_port", "true")
.build());
}).createWithoutStarting())
{
assertCannotStartDueToConfigurationException(cluster);
}
}
@Test
public void optionalTlsConnectionDisabledWithoutKeystoreTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> c.with(Feature.NETWORK)).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int port = cluster.get(1).config().broadcastAddress().getPort();
TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port);
tlsConnection.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS connection should not be possible without keystore",
ConnectResult.FAILED_TO_NEGOTIATE, tlsConnection.connect());
}
}
@Test
public void optionalTlsConnectionAllowedWithKeystoreTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options", validKeystore);
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int port = cluster.get(1).config().broadcastAddress().getPort();
TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port);
tlsConnection.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS connection should be possible with keystore by default",
ConnectResult.NEGOTIATED, tlsConnection.connect());
}
}
@Test
public void optionalTlsConnectionAllowedToStoragePortTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("storage_port", 7012);
c.set("ssl_storage_port", 7013);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("internode_encryption", "none")
.put("optional", true)
.put("enable_legacy_ssl_storage_port", "true")
.build());
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int regular_port = (int) cluster.get(1).config().get("storage_port");
int ssl_port = (int) cluster.get(1).config().get("ssl_storage_port");
// Create the connections and prove they cannot connect before server start
TlsConnection connectToRegularPort = new TlsConnection(address.getHostAddress(), regular_port);
connectToRegularPort.assertCannotConnect();
TlsConnection connectToSslStoragePort = new TlsConnection(address.getHostAddress(), ssl_port);
connectToSslStoragePort.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS native connection should be possible to ssl_storage_port",
ConnectResult.NEGOTIATED, connectToSslStoragePort.connect());
Assert.assertEquals("TLS native connection should be possible with valid keystore by default",
ConnectResult.NEGOTIATED, connectToRegularPort.connect());
}
}
@Test
public void legacySslStoragePortEnabledWithSameRegularAndSslPortTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("storage_port", 7012); // must match in-jvm dtest assigned ports
c.set("ssl_storage_port", 7012);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("internode_encryption", "none")
.put("optional", true)
.put("enable_legacy_ssl_storage_port", "true")
.build());
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int ssl_port = (int) cluster.get(1).config().get("ssl_storage_port");
// Create the connections and prove they cannot connect before server start
TlsConnection connectToSslStoragePort = new TlsConnection(address.getHostAddress(), ssl_port);
connectToSslStoragePort.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS native connection should be possible to ssl_storage_port",
ConnectResult.NEGOTIATED, connectToSslStoragePort.connect());
}
}
@Test
public void tlsConnectionRejectedWhenUnencrypted() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("internode_encryption", "none")
.put("optional", false)
.build());
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int regular_port = (int) cluster.get(1).config().get("storage_port");
// Create the connections and prove they cannot connect before server start
TlsConnection connection = new TlsConnection(address.getHostAddress(), regular_port);
connection.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS native connection should be possible with valid keystore by default",
ConnectResult.FAILED_TO_NEGOTIATE, connection.connect());
}
}
@Test
public void allInternodeEncryptionEstablishedTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(2).withConfig(c -> {
c.with(Feature.NETWORK)
.with(Feature.GOSSIP) // To make sure AllMembersAliveMonitor checks gossip (which uses internode conns)
.with(Feature.NATIVE_PROTOCOL); // For virtual tables
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("internode_encryption", "all")
.build());
}).start())
{
// Just check startup - cluster should not be able to establish internode connections xwithout encrypted connections
for (int i = 1; i <= cluster.size(); i++)
{
Object[][] result = cluster.get(i).executeInternal("SELECT successful_connection_attempts, address, port FROM system_views.internode_outbound");
Assert.assertEquals(1, result.length);
long successfulConnectionAttempts = (long) result[0][0];
Assert.assertTrue("At least one connection: " + successfulConnectionAttempts, successfulConnectionAttempts > 0);
}
}
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.distributed.test;
import java.net.InetAddress;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOptionsImpl
{
@Test
public void nodeWillNotStartWithBadKeystore() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.of("enabled", true,
"optional", true,
"keystore", "/path/to/bad/keystore/that/should/not/exist",
"truststore", "/path/to/bad/truststore/that/should/not/exist"));
}).createWithoutStarting())
{
assertCannotStartDueToConfigurationException(cluster);
}
}
@Test
public void optionalTlsConnectionDisabledWithoutKeystoreTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> c.with(Feature.NATIVE_PROTOCOL)).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int port = (int) cluster.get(1).config().get("native_transport_port");
TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port);
tlsConnection.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS connection should not be possible without keystore",
ConnectResult.FAILED_TO_NEGOTIATE, tlsConnection.connect());
}
}
@Test
public void optionalTlsConnectionAllowedWithKeystoreTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options", validKeystore);
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int port = (int) cluster.get(1).config().get("native_transport_port");
TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port);
tlsConnection.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS native connection should be possible with keystore by default",
ConnectResult.NEGOTIATED, tlsConnection.connect());
}
}
@Test
public void optionalTlsConnectionAllowedToRegularPortTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("native_transport_port_ssl", 9043);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("enabled", false)
.put("optional", true)
.build());
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
int unencrypted_port = (int) cluster.get(1).config().get("native_transport_port");
int ssl_port = (int) cluster.get(1).config().get("native_transport_port_ssl");
// Create the connections and prove they cannot connect before server start
TlsConnection connectionToUnencryptedPort = new TlsConnection(address.getHostAddress(), unencrypted_port);
connectionToUnencryptedPort.assertCannotConnect();
TlsConnection connectionToEncryptedPort = new TlsConnection(address.getHostAddress(), ssl_port);
connectionToEncryptedPort.assertCannotConnect();
cluster.startup();
Assert.assertEquals("TLS native connection should be possible to native_transport_port_ssl",
ConnectResult.NEGOTIATED, connectionToEncryptedPort.connect());
Assert.assertEquals("TLS native connection should not be possible on the regular port if an SSL port is specified",
ConnectResult.FAILED_TO_NEGOTIATE, connectionToUnencryptedPort.connect()); // but did connect
}
}
@Test
public void unencryptedNativeConnectionNotlisteningOnTlsPortTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("native_transport_port_ssl", 9043);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
.put("enabled", false)
.put("optional", false)
.build());
}).createWithoutStarting())
{
assertCannotStartDueToConfigurationException(cluster);
}
}
}

View File

@ -179,8 +179,7 @@ public class PreviewRepairTest extends TestBaseImpl
public void testConcurrentIncRepairDuringPreview() throws IOException, InterruptedException, ExecutionException
{
try (Cluster cluster = init(Cluster.build(2).withConfig(config ->
config.set("disable_incremental_repair", false)
.with(GOSSIP)
config.with(GOSSIP)
.with(NETWORK)).start()))
{
cluster.schemaChange("create table " + KEYSPACE + ".tbl (id int primary key, t int)");

View File

@ -0,0 +1,178 @@
/*
* 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.config;
import java.io.File;
import java.util.Collections;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.assertj.core.api.Assertions;
import static org.apache.cassandra.config.EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED;
import static org.apache.cassandra.config.EncryptionOptions.TlsEncryptionPolicy.OPTIONAL;
import static org.apache.cassandra.config.EncryptionOptions.TlsEncryptionPolicy.ENCRYPTED;
import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.all;
import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.dc;
import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.none;
import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.rack;
import static org.junit.Assert.*;
public class EncryptionOptionsTest
{
static class EncryptionOptionsTestCase
{
final EncryptionOptions encryptionOptions;
final EncryptionOptions.TlsEncryptionPolicy expected;
final String description;
public EncryptionOptionsTestCase(EncryptionOptions encryptionOptions, EncryptionOptions.TlsEncryptionPolicy expected, String description)
{
this.encryptionOptions = encryptionOptions;
this.expected = expected;
this.description = description;
}
public static EncryptionOptionsTestCase of(Boolean optional, String keystorePath, Boolean enabled, EncryptionOptions.TlsEncryptionPolicy expected)
{
return new EncryptionOptionsTestCase(new EncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass",
Collections.emptyList(), "TLS", null, "JKS", false, false, enabled, optional)
.applyConfig(),
expected,
String.format("optional=%s keystore=%s enabled=%s", optional, keystorePath, enabled));
}
}
static String absentKeystore = "test/conf/missing-keystore-is-not-here";
static String presentKeystore = "test/conf/keystore.jks";
EncryptionOptionsTestCase[] encryptionOptionTestCases = {
// Optional Keystore Enabled Expected
EncryptionOptionsTestCase.of(null, absentKeystore, false, UNENCRYPTED),
EncryptionOptionsTestCase.of(null, absentKeystore, true, ENCRYPTED),
EncryptionOptionsTestCase.of(null, presentKeystore, false, OPTIONAL),
EncryptionOptionsTestCase.of(null, presentKeystore, true, ENCRYPTED),
EncryptionOptionsTestCase.of(false, absentKeystore, false, UNENCRYPTED),
EncryptionOptionsTestCase.of(false, absentKeystore, true, ENCRYPTED),
EncryptionOptionsTestCase.of(true, presentKeystore, false, OPTIONAL),
EncryptionOptionsTestCase.of(true, presentKeystore, true, OPTIONAL)
};
@Test
public void testEncryptionOptionPolicy()
{
assertTrue(new File(presentKeystore).exists());
assertFalse(new File(absentKeystore).exists());
for (EncryptionOptionsTestCase testCase : encryptionOptionTestCases)
{
Assert.assertSame(testCase.description, testCase.expected, testCase.encryptionOptions.tlsEncryptionPolicy());
}
}
static class ServerEncryptionOptionsTestCase
{
final EncryptionOptions encryptionOptions;
final EncryptionOptions.TlsEncryptionPolicy expected;
final String description;
public ServerEncryptionOptionsTestCase(EncryptionOptions encryptionOptions, EncryptionOptions.TlsEncryptionPolicy expected, String description)
{
this.encryptionOptions = encryptionOptions;
this.expected = expected;
this.description = description;
}
public static ServerEncryptionOptionsTestCase of(Boolean optional, String keystorePath,
EncryptionOptions.ServerEncryptionOptions.InternodeEncryption internodeEncryption,
EncryptionOptions.TlsEncryptionPolicy expected)
{
return new ServerEncryptionOptionsTestCase(new EncryptionOptions.ServerEncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass",
Collections.emptyList(), "TLS", null, "JKS", false, false, optional, internodeEncryption, false)
.applyConfig(),
expected,
String.format("optional=%s keystore=%s internode=%s", optional, keystorePath, internodeEncryption));
}
}
@Test
public void isEnabledServer()
{
Map<String, Object> yaml = ImmutableMap.of(
"server_encryption_options", ImmutableMap.of(
"isEnabled", false
)
);
Assertions.assertThatThrownBy(() -> YamlConfigurationLoader.fromMap(yaml, Config.class))
.isInstanceOf(ConfigurationException.class)
.hasMessage("Invalid yaml. Please remove properties [isEnabled] from your cassandra.yaml");
}
@Test
public void isOptionalServer()
{
Map<String, Object> yaml = ImmutableMap.of(
"server_encryption_options", ImmutableMap.of(
"isOptional", false
)
);
Assertions.assertThatThrownBy(() -> YamlConfigurationLoader.fromMap(yaml, Config.class))
.isInstanceOf(ConfigurationException.class)
.hasMessage("Invalid yaml. Please remove properties [isOptional] from your cassandra.yaml");
}
ServerEncryptionOptionsTestCase[] serverEncryptionOptionTestCases = {
// Optional Keystore Internode Expected
ServerEncryptionOptionsTestCase.of(null, absentKeystore, none, UNENCRYPTED),
ServerEncryptionOptionsTestCase.of(null, absentKeystore, rack, OPTIONAL),
ServerEncryptionOptionsTestCase.of(null, absentKeystore, dc, OPTIONAL),
ServerEncryptionOptionsTestCase.of(null, absentKeystore, all, ENCRYPTED),
ServerEncryptionOptionsTestCase.of(null, presentKeystore, none, OPTIONAL),
ServerEncryptionOptionsTestCase.of(null, presentKeystore, rack, OPTIONAL),
ServerEncryptionOptionsTestCase.of(null, absentKeystore, dc, OPTIONAL),
ServerEncryptionOptionsTestCase.of(null, absentKeystore, all, ENCRYPTED),
ServerEncryptionOptionsTestCase.of(false, absentKeystore, none, UNENCRYPTED),
ServerEncryptionOptionsTestCase.of(false, absentKeystore, rack, OPTIONAL),
ServerEncryptionOptionsTestCase.of(false, absentKeystore, dc, OPTIONAL),
ServerEncryptionOptionsTestCase.of(false, absentKeystore, all, ENCRYPTED),
ServerEncryptionOptionsTestCase.of(true, presentKeystore, none, OPTIONAL),
ServerEncryptionOptionsTestCase.of(true, presentKeystore, rack, OPTIONAL),
ServerEncryptionOptionsTestCase.of(true, absentKeystore, dc, OPTIONAL),
ServerEncryptionOptionsTestCase.of(true, absentKeystore, all, OPTIONAL),
};
@Test
public void testServerEncryptionOptionPolicy()
{
assertTrue(new File(presentKeystore).exists());
assertFalse(new File(absentKeystore).exists());
for (ServerEncryptionOptionsTestCase testCase : serverEncryptionOptionTestCases)
{
Assert.assertSame(testCase.description, testCase.expected, testCase.encryptionOptions.tlsEncryptionPolicy());
}
}
}

View File

@ -35,18 +35,19 @@ public class YamlConfigurationLoaderTest
int storagePort = 123;
Config.CommitLogSync commitLogSync = Config.CommitLogSync.batch;
ParameterizedClass seedProvider = new ParameterizedClass("org.apache.cassandra.locator.SimpleSeedProvider", Collections.emptyMap());
EncryptionOptions encryptionOptions = new EncryptionOptions()
.withKeyStore("myNewKeystore")
.withCipherSuites("SomeCipher")
.withOptional(false);
Map<String,Object> encryptionOptions = ImmutableMap.of("cipher_suites", Collections.singletonList("FakeCipher"),
"optional", false,
"enabled", true);
Map<String,Object> map = ImmutableMap.of("storage_port", storagePort,
"commitlog_sync", commitLogSync,
"seed_provider", seedProvider,
"client_encryption_options", encryptionOptions);
Config config = YamlConfigurationLoader.fromMap(map, Config.class);
assertEquals(storagePort, config.storage_port); // Check a simple integer
assertEquals(commitLogSync, config.commitlog_sync); // Check an enum
assertEquals(seedProvider, config.seed_provider); // Check a parameterized class
assertEquals(encryptionOptions, config.client_encryption_options); // Check a nested object
assertEquals(false, config.client_encryption_options.optional); // Check a nested object
assertEquals(true, config.client_encryption_options.enabled); // Check a nested object
}
}

View File

@ -1045,7 +1045,7 @@ public abstract class CQLTester
protected SimpleClient newSimpleClient(ProtocolVersion version, boolean compression, boolean checksums, boolean isOverloadedException) throws IOException
{
return new SimpleClient(nativeAddr.getHostAddress(), nativePort, version, version.isBeta(), new EncryptionOptions()).connect(compression, checksums, isOverloadedException);
return new SimpleClient(nativeAddr.getHostAddress(), nativePort, version, version.isBeta(), new EncryptionOptions().applyConfig()).connect(compression, checksums, isOverloadedException);
}
protected SimpleClient newSimpleClient(ProtocolVersion version, boolean compression, boolean checksums) throws IOException

View File

@ -54,6 +54,8 @@ public class SettingsTableTest extends CQLTester
public void config()
{
config = new Config();
config.client_encryption_options.applyConfig();
config.server_encryption_options.applyConfig();
table = new SettingsTable(KS_NAME, config);
VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table)));
}
@ -151,9 +153,9 @@ public class SettingsTableTest extends CQLTester
config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv5");
check(pre + "protocol", "TLSv5");
check(pre + "optional", "true");
config.server_encryption_options = config.server_encryption_options.withOptional(false);
check(pre + "optional", "false");
config.server_encryption_options = config.server_encryption_options.withOptional(true);
check(pre + "optional", "true");
check(pre + "client_auth", "false");
config.server_encryption_options = config.server_encryption_options.withRequireClientAuth(true);

View File

@ -25,7 +25,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -35,7 +34,6 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.*;
import java.util.regex.Matcher;
import com.google.common.collect.Iterables;
import com.google.common.net.InetAddresses;
import com.codahale.metrics.Timer;
@ -366,11 +364,11 @@ public class MessagingServiceTest
for (InboundSockets.InboundSocket socket : connections.sockets())
{
Assert.assertEquals(serverEncryptionOptions.isEnabled(), socket.settings.encryption.isEnabled());
Assert.assertEquals(serverEncryptionOptions.optional, socket.settings.encryption.optional);
Assert.assertEquals(serverEncryptionOptions.isOptional(), socket.settings.encryption.isOptional());
if (!serverEncryptionOptions.isEnabled())
Assert.assertFalse(legacySslPort == socket.settings.bindAddress.port);
if (legacySslPort == socket.settings.bindAddress.port)
Assert.assertFalse(socket.settings.encryption.optional);
Assert.assertFalse(socket.settings.encryption.isOptional());
Assert.assertTrue(socket.settings.bindAddress.toString(), expect.remove(socket.settings.bindAddress));
}
}

View File

@ -23,12 +23,15 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.xml.crypto.Data;
import com.google.common.collect.Sets;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.Pair;
@ -38,16 +41,19 @@ import static org.junit.Assert.assertTrue;
public class NativeTransportServiceTest
{
static EncryptionOptions defaultOptions;
@BeforeClass
public static void setupDD()
{
DatabaseDescriptor.daemonInitialization();
defaultOptions = DatabaseDescriptor.getNativeProtocolEncryptionOptions();
}
@After
public void resetConfig()
{
DatabaseDescriptor.updateNativeProtocolEncryptionOptions(options -> options.withEnabled(false));
DatabaseDescriptor.updateNativeProtocolEncryptionOptions(update -> new EncryptionOptions(defaultOptions).applyConfig());
DatabaseDescriptor.setNativeTransportPortSSL(null);
}
@ -118,7 +124,7 @@ public class NativeTransportServiceTest
{
assertEquals(1, service.getServers().size());
Server server = service.getServers().iterator().next();
assertFalse(server.useSSL);
assertEquals(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED, server.tlsEncryptionPolicy);
assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort());
});
}
@ -135,7 +141,7 @@ public class NativeTransportServiceTest
service.initialize();
assertEquals(1, service.getServers().size());
Server server = service.getServers().iterator().next();
assertTrue(server.useSSL);
assertEquals(EncryptionOptions.TlsEncryptionPolicy.ENCRYPTED, server.tlsEncryptionPolicy);
assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort());
}, false, 1);
}
@ -152,16 +158,19 @@ public class NativeTransportServiceTest
service.initialize();
assertEquals(1, service.getServers().size());
Server server = service.getServers().iterator().next();
assertTrue(server.useSSL);
assertEquals(EncryptionOptions.TlsEncryptionPolicy.OPTIONAL, server.tlsEncryptionPolicy);
assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort());
}, false, 1);
}
@Test
public void testSSLWithNonSSL()
public void testSSLPortWithOptionalEncryption()
{
// ssl+non-ssl settings: client encryption enabled and additional ssl port specified
DatabaseDescriptor.updateNativeProtocolEncryptionOptions(options -> options.withEnabled(true));
DatabaseDescriptor.updateNativeProtocolEncryptionOptions(
options -> options.withEnabled(true)
.withOptional(true)
.withKeyStore("test/conf/cassandra_ssl_test.keystore"));
DatabaseDescriptor.setNativeTransportPortSSL(8432);
withService((NativeTransportService service) ->
@ -170,12 +179,71 @@ public class NativeTransportServiceTest
assertEquals(2, service.getServers().size());
assertEquals(
Sets.newHashSet(Arrays.asList(
Pair.create(true, DatabaseDescriptor.getNativeTransportPortSSL()),
Pair.create(false, DatabaseDescriptor.getNativeTransportPort())
Pair.create(EncryptionOptions.TlsEncryptionPolicy.OPTIONAL,
DatabaseDescriptor.getNativeTransportPortSSL()),
Pair.create(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED,
DatabaseDescriptor.getNativeTransportPort())
)
),
service.getServers().stream().map((Server s) ->
Pair.create(s.useSSL, s.socket.getPort())).collect(Collectors.toSet())
Pair.create(s.tlsEncryptionPolicy,
s.socket.getPort())).collect(Collectors.toSet())
);
}, false, 1);
}
@Test(expected=java.lang.IllegalStateException.class)
public void testSSLPortWithDisabledEncryption()
{
// ssl+non-ssl settings: client encryption disabled and additional ssl port specified
// should get an illegal state exception
DatabaseDescriptor.updateNativeProtocolEncryptionOptions(
options -> options.withEnabled(false));
DatabaseDescriptor.setNativeTransportPortSSL(8432);
withService((NativeTransportService service) ->
{
service.initialize();
assertEquals(1, service.getServers().size());
assertEquals(
Sets.newHashSet(Arrays.asList(
Pair.create(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED,
DatabaseDescriptor.getNativeTransportPort())
)
),
service.getServers().stream().map((Server s) ->
Pair.create(s.tlsEncryptionPolicy,
s.socket.getPort())).collect(Collectors.toSet())
);
}, false, 1);
}
@Test
public void testSSLPortWithEnabledSSL()
{
// ssl+non-ssl settings: client encryption enabled and additional ssl port specified
// encryption is enabled and not optional, so listen on both ports requiring encryption
DatabaseDescriptor.updateNativeProtocolEncryptionOptions(
options -> options.withEnabled(true)
.withOptional(false)
.withKeyStore("test/conf/cassandra_ssl_test.keystore"));
DatabaseDescriptor.setNativeTransportPortSSL(8432);
withService((NativeTransportService service) ->
{
service.initialize();
assertEquals(2, service.getServers().size());
assertEquals(
Sets.newHashSet(Arrays.asList(
Pair.create(EncryptionOptions.TlsEncryptionPolicy.ENCRYPTED,
DatabaseDescriptor.getNativeTransportPortSSL()),
Pair.create(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED,
DatabaseDescriptor.getNativeTransportPort())
)
),
service.getServers().stream().map((Server s) ->
Pair.create(s.tlsEncryptionPolicy,
s.socket.getPort())).collect(Collectors.toSet())
);
}, false, 1);
}

View File

@ -40,7 +40,7 @@ public class SettingsTransport implements Serializable
public EncryptionOptions getEncryptionOptions()
{
EncryptionOptions encOptions = new EncryptionOptions();
EncryptionOptions encOptions = new EncryptionOptions().applyConfig();
if (options.trustStore.present())
{
encOptions = encOptions

View File

@ -70,7 +70,7 @@ public class JavaDriverClient
this.username = settings.mode.username;
this.password = settings.mode.password;
this.authProvider = settings.mode.authProvider;
this.encryptionOptions = encryptionOptions;
this.encryptionOptions = new EncryptionOptions(encryptionOptions).applyConfig();
this.loadBalancingPolicy = loadBalancingPolicy(settings);
this.connectionsPerHost = settings.mode.connectionsPerHost == null ? 8 : settings.mode.connectionsPerHost;