mirror of https://github.com/apache/cassandra
Merge branch 'cassandra-3.11' into trunk
This commit is contained in:
commit
001767de2d
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)");
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue