mirror of https://github.com/apache/cassandra
Implement a guardrail for client driver versions
patch by Stefan Miklosovic; reviewed by Andy Tolbert, Bohdan Siryk for CASSANDRA-21146
This commit is contained in:
parent
281c1dc92c
commit
e7fa71efaa
|
|
@ -1,4 +1,5 @@
|
|||
6.0-alpha2
|
||||
* Implement a guardrail for client driver versions (CASSANDRA-21146)
|
||||
* Enable IAuthenticator to declare supported and alterable role options (CASSANDRA-20834)
|
||||
* Avoid capturing lambda allocation in UnfilteredSerializer.deserializeRowBody (CASSANDRA-21289)
|
||||
* Avoid Cell iterator allocation for alive rows in MetricsRecording transformation of ReadCommand (CASSANDRA-21288)
|
||||
|
|
|
|||
|
|
@ -2682,6 +2682,27 @@ max_security_label_length: 48
|
|||
# compressed by dictionary compressor and training_min_frequency is set to 0m (the default when unset).
|
||||
#unset_training_min_frequency_enabled: true
|
||||
|
||||
# Minimum client driver versions. Connections from drivers whose version is below
|
||||
# the configured minimum will be warned or rejected. Connections that do not report
|
||||
# a driver name or version are considered valid unless 'unset' config entry is specified.
|
||||
# The map key is the driver name as reported in the native protocol STARTUP message.
|
||||
# The value is the minimum version string, which has to be a valid semver.
|
||||
# An operator can use special "unknown" and "unset" entries for both guardrails with a placeholder version.
|
||||
# A connection will fail / will be warned when a driver id is not among known entries when "unknown" entry is specified.
|
||||
# A connection will fail / will be warned when a driver id is not set at all when "unset" entry is specified.
|
||||
#minimum_client_driver_versions_warned:
|
||||
# DataStax Java Driver: "4.0.0"
|
||||
# DataStax Python Driver: "3.0.0"
|
||||
# github.com/apache/cassandra-gocql-driver: "2.0.0"
|
||||
# unknown: "0.0.0"
|
||||
# unset: "0.0.0"
|
||||
#minimum_client_driver_versions_disallowed:
|
||||
# DataStax Java Driver: "4.0.0"
|
||||
# DataStax Python Driver: "3.0.0"
|
||||
# github.com/apache/cassandra-gocql-driver: "2.0.0"
|
||||
# unknown: "0.0.0"
|
||||
# unset: "0.0.0"
|
||||
|
||||
# Startup Checks are executed as part of Cassandra startup process, not all of them
|
||||
# are configurable (so you can disable them) but these which are enumerated bellow.
|
||||
# Uncomment the startup checks and configure them appropriately to cover your needs.
|
||||
|
|
|
|||
|
|
@ -2446,6 +2446,27 @@ default_secondary_index_enabled: true
|
|||
# compressed by dictionary compressor and training_min_frequency is set to 0m (the default when unset).
|
||||
#unset_training_min_frequency_enabled: true
|
||||
|
||||
# Minimum client driver versions. Connections from drivers whose version is below
|
||||
# the configured minimum will be warned or rejected. Connections that do not report
|
||||
# a driver name or version are considered valid unless 'unset' config entry is specified.
|
||||
# The map key is the driver name as reported in the native protocol STARTUP message.
|
||||
# The value is the minimum version string, which has to be a valid semver.
|
||||
# An operator can use special "unknown" and "unset" entries for both guardrails with a placeholder version.
|
||||
# A connection will fail / will be warned when a driver id is not among known entries when "unknown" entry is specified.
|
||||
# A connection will fail / will be warned when a driver id is not set at all when "unset" entry is specified.
|
||||
#minimum_client_driver_versions_warned:
|
||||
# DataStax Java Driver: "4.0.0"
|
||||
# DataStax Python Driver: "3.0.0"
|
||||
# github.com/apache/cassandra-gocql-driver: "2.0.0"
|
||||
# unknown: "0.0.0"
|
||||
# unset: "0.0.0"
|
||||
#minimum_client_driver_versions_disallowed:
|
||||
# DataStax Java Driver: "4.0.0"
|
||||
# DataStax Python Driver: "3.0.0"
|
||||
# github.com/apache/cassandra-gocql-driver: "2.0.0"
|
||||
# unknown: "0.0.0"
|
||||
# unset: "0.0.0"
|
||||
|
||||
# Startup Checks are executed as part of Cassandra startup process, not all of them
|
||||
# are configurable (so you can disable them) but these which are enumerated bellow.
|
||||
# Uncomment the startup checks and configure them appropriately to cover your needs.
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,9 @@ public class Config
|
|||
public volatile boolean unset_training_min_frequency_warned = true;
|
||||
public volatile boolean unset_training_min_frequency_enabled = true;
|
||||
|
||||
public volatile Map<String, String> minimum_client_driver_versions_warned = Collections.emptyMap();
|
||||
public volatile Map<String, String> minimum_client_driver_versions_disallowed = Collections.emptyMap();
|
||||
|
||||
public volatile int sai_sstable_indexes_per_query_warn_threshold = 32;
|
||||
public volatile int sai_sstable_indexes_per_query_fail_threshold = -1;
|
||||
public volatile DataStorageSpec.LongBytesBound sai_string_term_size_warn_threshold = new DataStorageSpec.LongBytesBound("1KiB");
|
||||
|
|
|
|||
|
|
@ -18,15 +18,20 @@
|
|||
|
||||
package org.apache.cassandra.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -113,6 +118,8 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
false);
|
||||
validatePasswordPolicy(config.password_policy);
|
||||
validateRoleNamePolicy(config.role_name_policy);
|
||||
validateAndSanitizeClientDriverVersions(config.minimum_client_driver_versions_warned, "minimum_client_driver_versions_warned");
|
||||
validateAndSanitizeClientDriverVersions(config.minimum_client_driver_versions_disallowed, "minimum_client_driver_versions_disallowed");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -195,7 +202,7 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
{
|
||||
return config.keyspace_properties_warned;
|
||||
}
|
||||
|
||||
|
||||
public void setKeyspacePropertiesWarned(Set<String> properties)
|
||||
{
|
||||
updatePropertyWithLogging("keyspace_properties_warned",
|
||||
|
|
@ -209,7 +216,7 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
{
|
||||
return config.keyspace_properties_ignored;
|
||||
}
|
||||
|
||||
|
||||
public void setKeyspacePropertiesIgnored(Set<String> properties)
|
||||
{
|
||||
updatePropertyWithLogging("keyspace_properties_ignored",
|
||||
|
|
@ -223,7 +230,7 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
{
|
||||
return config.keyspace_properties_disallowed;
|
||||
}
|
||||
|
||||
|
||||
public void setKeyspacePropertiesDisallowed(Set<String> properties)
|
||||
{
|
||||
updatePropertyWithLogging("keyspace_properties_disallowed",
|
||||
|
|
@ -1368,6 +1375,34 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
return config.unset_training_min_frequency_enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMinimumClientDriverVersionsWarned()
|
||||
{
|
||||
return config.minimum_client_driver_versions_warned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMinimumClientDriverVersionsDisallowed()
|
||||
{
|
||||
return config.minimum_client_driver_versions_disallowed;
|
||||
}
|
||||
|
||||
public void setMinimumClientDriverVersionsWarned(Map<String, String> versions)
|
||||
{
|
||||
updatePropertyWithLogging("minimum_client_driver_versions_warned",
|
||||
versions,
|
||||
() -> config.minimum_client_driver_versions_warned,
|
||||
x -> config.minimum_client_driver_versions_warned = x);
|
||||
}
|
||||
|
||||
public void setMinimumClientDriverVersionsDisallowed(Map<String, String> versions)
|
||||
{
|
||||
updatePropertyWithLogging("minimum_client_driver_versions_disallowed",
|
||||
versions,
|
||||
() -> config.minimum_client_driver_versions_disallowed,
|
||||
x -> config.minimum_client_driver_versions_disallowed = x);
|
||||
}
|
||||
|
||||
private static <T> void updatePropertyWithLogging(String propertyName, T newValue, Supplier<T> getter, Consumer<T> setter)
|
||||
{
|
||||
T oldValue = getter.get();
|
||||
|
|
@ -1601,4 +1636,57 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
{
|
||||
ValueGenerator.getGenerator("role_name_policy", config).generate(ValueValidator.getValidator("role_name_policy", config), Map.of());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void validateAndSanitizeClientDriverVersions(Map<String, String> map, String guardrailName)
|
||||
{
|
||||
if (map == null || map.isEmpty())
|
||||
return;
|
||||
|
||||
List<String> invalidEntries = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet())
|
||||
{
|
||||
String sanitized = sanitizeVersion(entry.getValue());
|
||||
if (!isValidVersion(sanitized))
|
||||
invalidEntries.add(entry.getKey());
|
||||
}
|
||||
|
||||
if (!invalidEntries.isEmpty())
|
||||
throw new IllegalArgumentException("Invalid version entries for " + guardrailName + " guardrail, they do not follow semver: " + invalidEntries);
|
||||
|
||||
map.replaceAll((driver, version) -> sanitizeVersion(version));
|
||||
}
|
||||
|
||||
public static boolean isValidVersion(String version)
|
||||
{
|
||||
if (version == null)
|
||||
return false;
|
||||
|
||||
// try to construct it
|
||||
try
|
||||
{
|
||||
new Semver(version);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final Pattern VERSION_SANITATION_PATTERN = Pattern.compile("^[vV]");
|
||||
|
||||
public static String sanitizeVersion(String driverVersion)
|
||||
{
|
||||
String sanitizedVersionId = driverVersion == null ? null : driverVersion.trim();
|
||||
if (sanitizedVersionId != null)
|
||||
sanitizedVersionId = VERSION_SANITATION_PATTERN.matcher(sanitizedVersionId).replaceFirst("");
|
||||
|
||||
if (sanitizedVersionId == null || sanitizedVersionId.isBlank())
|
||||
return null;
|
||||
|
||||
return sanitizedVersionId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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.db.guardrails;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import com.vdurmont.semver4j.Semver.SemverType;
|
||||
|
||||
import org.apache.cassandra.config.GuardrailsOptions;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
||||
/**
|
||||
* A guardrail that warns or rejects client connections whose driver version
|
||||
* is below a configured minimum per driver name.
|
||||
* <p>
|
||||
* The guardrail is configured with maps of driver name to minimum version string.
|
||||
* Connections that do not report a driver name or version are considered valid
|
||||
* and are not subject to this guardrail.
|
||||
* <p>
|
||||
* Version comparison uses semantic versioning via {@link Semver} with loose parsing
|
||||
* to handle non-standard version strings reported by drivers.
|
||||
* <p>
|
||||
* This guadrail also reacts on "unknown: 0.0.0" and "unset: 0.0.0" entries in the configuration map.
|
||||
* When either is set, when a driver does not set its driver id or driver id is not among explicitly enumerated
|
||||
* in the configuration map, the connection will fail / will be warned respectively.
|
||||
*/
|
||||
public class ClientDriverVersionGuardrail extends Predicates<String>
|
||||
{
|
||||
private final Function<ClientState, Map<String, String>> warnVersions;
|
||||
private final Function<ClientState, Map<String, String>> disallowVersions;
|
||||
|
||||
public ClientDriverVersionGuardrail(Function<ClientState, Map<String, String>> warnVersions,
|
||||
Function<ClientState, Map<String, String>> disallowVersions)
|
||||
{
|
||||
super("minimum_client_driver_versions", null, null, null, null);
|
||||
this.warnVersions = warnVersions;
|
||||
this.disallowVersions = disallowVersions;
|
||||
}
|
||||
|
||||
public void guard(@Nullable String driverName, @Nullable String driverVersion, @Nullable ClientState state)
|
||||
{
|
||||
if (!enabled(state))
|
||||
return;
|
||||
|
||||
Map<String, String> disallowed = disallowVersions.apply(state);
|
||||
Map<String, String> warned = warnVersions.apply(state);
|
||||
|
||||
String sanitizedDriverId = driverName == null ? null : driverName.trim();
|
||||
if (sanitizedDriverId == null || sanitizedDriverId.isBlank())
|
||||
{
|
||||
logger.debug("minimum_client_driver_versions guardrail identified empty driver id to check the minimum version of");
|
||||
if (disallowed != null && disallowed.containsKey("unset"))
|
||||
{
|
||||
fail("Connections with unset driver id's are forbidden.", state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (warned != null && warned.containsKey("unset"))
|
||||
{
|
||||
warn("Connection with unset driver id was detected.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String sanitizedDriverVersion = GuardrailsOptions.sanitizeVersion(driverVersion);
|
||||
if (sanitizedDriverVersion == null)
|
||||
{
|
||||
sanitizedDriverVersion = "unset";
|
||||
logger.debug("minimum_client_driver_versions guardrail identified empty driver version to check the minimum version of");
|
||||
}
|
||||
else if (!GuardrailsOptions.isValidVersion(sanitizedDriverVersion))
|
||||
{
|
||||
logger.debug("minimum_client_driver_versions guardrail identified driver version which is not compliant semver version");
|
||||
return;
|
||||
}
|
||||
|
||||
if (disallowed != null && !disallowed.isEmpty())
|
||||
{
|
||||
String minimumVersionFail = disallowed.get(sanitizedDriverId);
|
||||
if (minimumVersionFail != null && (sanitizedDriverVersion.equals("unset") || isBelowMinimum(sanitizedDriverVersion, minimumVersionFail)))
|
||||
{
|
||||
fail(String.format("Client driver %s, version %s is below required minimum version %s, connection rejected",
|
||||
sanitizedDriverId,
|
||||
sanitizedDriverVersion,
|
||||
minimumVersionFail),
|
||||
state);
|
||||
return;
|
||||
}
|
||||
else if (minimumVersionFail == null && disallowed.containsKey("unknown"))
|
||||
{
|
||||
fail(String.format("Detected unknown driver id: %s.", sanitizedDriverId), state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (warned != null && !warned.isEmpty())
|
||||
{
|
||||
String minimumVersionWarn = warned.get(sanitizedDriverId);
|
||||
if (minimumVersionWarn != null && (sanitizedDriverVersion.equals("unset") || isBelowMinimum(sanitizedDriverVersion, minimumVersionWarn)))
|
||||
{
|
||||
warn(String.format("Client driver %s, version %s is below recommended minimum version %s",
|
||||
sanitizedDriverId,
|
||||
sanitizedDriverVersion,
|
||||
minimumVersionWarn));
|
||||
}
|
||||
else if (minimumVersionWarn == null && warned.containsKey("unknown"))
|
||||
{
|
||||
warn(String.format("Detected unknown driver id: %s", sanitizedDriverId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be used in normal circumstances as {@link #guard(String, String, ClientState)}
|
||||
* should be called directly instead.
|
||||
*
|
||||
* @param rawDriverId the value to check, driver name and version delimited by a colon.
|
||||
* @param state client state
|
||||
*/
|
||||
@Override
|
||||
public void guard(String rawDriverId, @Nullable ClientState state)
|
||||
{
|
||||
if (rawDriverId == null)
|
||||
{
|
||||
guard(null, null, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
String[] pair = rawDriverId.trim().split(":");
|
||||
if (pair.length != 2)
|
||||
return;
|
||||
|
||||
String sanitizedDriverName = pair[0].trim();
|
||||
if (sanitizedDriverName.isBlank())
|
||||
return;
|
||||
|
||||
String sanitizedDriverVersion = pair[1].trim();
|
||||
if (sanitizedDriverVersion.isBlank())
|
||||
return;
|
||||
|
||||
guard(sanitizedDriverName, sanitizedDriverVersion, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the driver version is below the minimum version
|
||||
* specified in the config map. If driver name is not in minimum versions,
|
||||
* such connection is considered to be allowed.
|
||||
* <p>
|
||||
*
|
||||
* @param driverVersion version of a driver
|
||||
* @param minimumVersion minimum allowed version
|
||||
* @return true if the driver version is lower than the configured minimum
|
||||
*/
|
||||
static boolean isBelowMinimum(String driverVersion, String minimumVersion)
|
||||
{
|
||||
Semver versionToCheck = new Semver(driverVersion, SemverType.LOOSE);
|
||||
Semver minimumVersionAllowed = new Semver(minimumVersion, SemverType.LOOSE);
|
||||
return versionToCheck.isLowerThan(minimumVersionAllowed);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,12 +19,14 @@
|
|||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
|
@ -571,6 +573,16 @@ public final class Guardrails implements GuardrailsMBean
|
|||
isWarning ? "Disk usage in keyspace datacenter exceeds warning threshold"
|
||||
: "Write request failed because disk usage exceeds failure threshold in keyspace datacenter.");
|
||||
|
||||
/**
|
||||
* Guardrail on client driver versions. Warns or rejects connections from drivers
|
||||
* whose version is below the configured minimum for that driver name.
|
||||
* Connections that do not report a driver name or version are considered valid.
|
||||
*/
|
||||
public static final ClientDriverVersionGuardrail minimumClientDriverVersion =
|
||||
new ClientDriverVersionGuardrail(
|
||||
state -> CONFIG_PROVIDER.getOrCreate(state).getMinimumClientDriverVersionsWarned(),
|
||||
state -> CONFIG_PROVIDER.getOrCreate(state).getMinimumClientDriverVersionsDisallowed());
|
||||
|
||||
/**
|
||||
* Guardrail on passwords for CREATE / ALTER ROLE statements.
|
||||
*/
|
||||
|
|
@ -1853,6 +1865,62 @@ public final class Guardrails implements GuardrailsMBean
|
|||
return DEFAULT_CONFIG.getUnsetTrainingMinFrequencyEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinimumClientDriverVersionsWarned()
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonUtils.JSON_OBJECT_MAPPER.writeValueAsString(DEFAULT_CONFIG.getMinimumClientDriverVersionsWarned());
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException("Unable to serialize minimum_client_driver_versions_warned configuration: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinimumClientDriverVersionsDisallowed()
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonUtils.JSON_OBJECT_MAPPER.writeValueAsString(DEFAULT_CONFIG.getMinimumClientDriverVersionsDisallowed());
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException("Unable to serialize minimum_client_driver_versions_disallowed configuration: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinimumClientDriverVersionsWarned(String value)
|
||||
{
|
||||
try
|
||||
{
|
||||
Map<String, String> map = JsonUtils.JSON_OBJECT_MAPPER.readValue(value, new TypeReference<>() {});
|
||||
GuardrailsOptions.validateAndSanitizeClientDriverVersions(map, "minimum_client_driver_versions_warned");
|
||||
DEFAULT_CONFIG.setMinimumClientDriverVersionsWarned(map);
|
||||
}
|
||||
catch (JsonProcessingException t)
|
||||
{
|
||||
throw new RuntimeException("Unable to deserialize payload for minimum_client_driver_versions_warned: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinimumClientDriverVersionsDisallowed(String value)
|
||||
{
|
||||
try
|
||||
{
|
||||
Map<String, String> map = JsonUtils.JSON_OBJECT_MAPPER.readValue(value, new TypeReference<>() {});
|
||||
GuardrailsOptions.validateAndSanitizeClientDriverVersions(map, "minimum_client_driver_versions_disallowed");
|
||||
DEFAULT_CONFIG.setMinimumClientDriverVersionsDisallowed(map);
|
||||
}
|
||||
catch (JsonProcessingException t)
|
||||
{
|
||||
throw new RuntimeException("Unable to deserialize minimum_client_driver_versions_disallowed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String toCSV(Set<String> values)
|
||||
{
|
||||
return values == null || values.isEmpty() ? "" : String.join(",", values);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
|
@ -682,4 +683,16 @@ public interface GuardrailsConfig
|
|||
* dictionary compressor as frequently as needed, without any limits, false otherwise.
|
||||
*/
|
||||
boolean getUnsetTrainingMinFrequencyEnabled();
|
||||
|
||||
/**
|
||||
* @return a map of driver name to minimum version that triggers a warning when the client's
|
||||
* driver version is below the specified minimum.
|
||||
*/
|
||||
Map<String, String> getMinimumClientDriverVersionsWarned();
|
||||
|
||||
/**
|
||||
* @return a map of driver name to minimum version that triggers a failure when the client's
|
||||
* driver version is below the specified minimum.
|
||||
*/
|
||||
Map<String, String> getMinimumClientDriverVersionsDisallowed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1190,4 +1190,28 @@ public interface GuardrailsMBean
|
|||
* dictionary compressor as frequently as needed, without any limits, false otherwise.
|
||||
*/
|
||||
boolean getUnsetTrainingMinFrequencyEnabled();
|
||||
|
||||
/**
|
||||
* @return JSON representation of the minimum client driver versions that trigger a warning.
|
||||
*/
|
||||
String getMinimumClientDriverVersionsWarned();
|
||||
|
||||
/**
|
||||
* @return JSON representation of the minimum client driver versions that trigger a failure.
|
||||
*/
|
||||
String getMinimumClientDriverVersionsDisallowed();
|
||||
|
||||
/**
|
||||
* Sets the minimum client driver versions that trigger a warning.
|
||||
*
|
||||
* @param value JSON representation of a map of driver name to minimum version string.
|
||||
*/
|
||||
void setMinimumClientDriverVersionsWarned(String value);
|
||||
|
||||
/**
|
||||
* Sets the minimum client driver versions that trigger a failure.
|
||||
*
|
||||
* @param value JSON representation of a map of driver name to minimum version string.
|
||||
*/
|
||||
void setMinimumClientDriverVersionsDisallowed(String value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,7 +365,9 @@ public abstract class GuardrailsConfigCommand extends AbstractCommand
|
|||
"SimpleStrategyEnabled", "simplestrategy_enabled",
|
||||
"NonPartitionRestrictedQueryEnabled", "non_partition_restricted_index_query_enabled");
|
||||
|
||||
private static final Set<String> ignored = Set.of("password_policy", "role_name_policy");
|
||||
private static final Set<String> ignored = Set.of("password_policy", "role_name_policy",
|
||||
"minimum_client_driver_versions_warned",
|
||||
"minimum_client_driver_versions_disallowed");
|
||||
|
||||
/**
|
||||
* Set of guardrails which are flags, even though their suffix would suggest they are part of "values" which have warned, ignored, and disallowed sub-categories
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.Map;
|
|||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.guardrails.Guardrails;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.transport.CBUtil;
|
||||
|
|
@ -124,12 +125,15 @@ public class StartupMessage extends Message.Request
|
|||
ClientState clientState = state.getClientState();
|
||||
clientState.setClientOptions(options);
|
||||
String driverName = options.get(DRIVER_NAME);
|
||||
String driverVersion = options.get(DRIVER_VERSION);
|
||||
if (null != driverName)
|
||||
{
|
||||
clientState.setDriverName(driverName);
|
||||
clientState.setDriverVersion(options.get(DRIVER_VERSION));
|
||||
clientState.setDriverVersion(driverVersion);
|
||||
}
|
||||
|
||||
Guardrails.minimumClientDriverVersion.guard(driverName, driverVersion, clientState);
|
||||
|
||||
IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
|
||||
if (authenticator.requireAuthentication())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* 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.db.guardrails;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.apache.cassandra.config.GuardrailsOptions.validateAndSanitizeClientDriverVersions;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GuardrailClientDriverVersionTest extends GuardrailTester
|
||||
{
|
||||
private String originalWarned;
|
||||
private String originalDisallowed;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
originalWarned = guardrails().getMinimumClientDriverVersionsWarned();
|
||||
originalDisallowed = guardrails().getMinimumClientDriverVersionsDisallowed();
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown()
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned(originalWarned != null ? originalWarned : "{}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed(originalDisallowed != null ? originalDisallowed : "{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionEqual()
|
||||
{
|
||||
assertFalse(ClientDriverVersionGuardrail.isBelowMinimum("4.18.0", "4.18.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionLessThan()
|
||||
{
|
||||
assertTrue(ClientDriverVersionGuardrail.isBelowMinimum("4.17.0", "4.18.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionGreaterThan()
|
||||
{
|
||||
assertFalse(ClientDriverVersionGuardrail.isBelowMinimum("4.19.0", "4.18.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionMajorDifference()
|
||||
{
|
||||
assertTrue(ClientDriverVersionGuardrail.isBelowMinimum("3.11.0", "4.0.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionMinorDifference()
|
||||
{
|
||||
assertTrue(ClientDriverVersionGuardrail.isBelowMinimum("4.2.0", "4.18.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionWithVPrefix() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"driver\":\"v2.0.1\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{}");
|
||||
|
||||
// above minimum — no warn
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("driver", "v2.0.2", userClientState));
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("driver", "2.0.2", userClientState));
|
||||
|
||||
// below minimum — warn
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("driver", "v2.0.0", userClientState),
|
||||
"Client driver driver, version 2.0.0 is below recommended minimum version 2.0.1");
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("driver", "2.0.0", userClientState),
|
||||
"Client driver driver, version 2.0.0 is below recommended minimum version 2.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredWhenEmpty() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{}");
|
||||
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "3.0.0", userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredWhenVersionAboveMinimum() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{}");
|
||||
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "4.20.0", userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredForNotEnumeratedDriver() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"DataStax Java Driver\":\"4.0.0\"}");
|
||||
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("gocql", "1.0.0", userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredForNullDriverName() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard(null, "4.15.0", userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredForNullDriverVersion() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", null, userClientState),
|
||||
"Client driver DataStax Java Driver, version unset is below recommended minimum version 4.18.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredForBothNullDriverAndVersion() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard(null, null, userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailNotTriggeredForEmptyDriverName() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("", "4.15.0", userClientState));
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard(" ", "4.15.0", userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailTriggeredForEmptyDriverVersion() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "", userClientState),
|
||||
"Client driver DataStax Java Driver, version unset is below recommended minimum version 4.18.0");
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", " ", userClientState),
|
||||
"Client driver DataStax Java Driver, version unset is below recommended minimum version 4.18.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailWarns() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{}");
|
||||
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "4.15.0", userClientState),
|
||||
"Client driver DataStax Java Driver, version 4.15.0 is below recommended minimum version 4.18.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailFails() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"DataStax Java Driver\":\"4.0.0\"}");
|
||||
|
||||
assertFails(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "3.11.0", userClientState),
|
||||
"Client driver DataStax Java Driver, version 3.11.0 is below required minimum version 4.0.0, connection rejected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailFailTakesPrecedenceOverWarn() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"DataStax Java Driver\":\"4.0.0\"}");
|
||||
|
||||
assertFails(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "3.11.0", userClientState),
|
||||
"Client driver DataStax Java Driver, version 3.11.0 is below required minimum version 4.0.0, connection rejected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailWarnOnly() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"DataStax Java Driver\":\"4.0.0\"}");
|
||||
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "4.15.0", userClientState),
|
||||
"Client driver DataStax Java Driver, version 4.15.0 is below recommended minimum version 4.18.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailWithVPrefixVersion() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{}");
|
||||
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", "v4.15.0", userClientState),
|
||||
"Client driver DataStax Java Driver, version 4.15.0 is below recommended minimum version 4.18.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailColonFormatWarns() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{}");
|
||||
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver:4.15.0", userClientState),
|
||||
"Client driver DataStax Java Driver, version 4.15.0 is below recommended minimum version 4.18.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailColonFormatNull() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard(null, userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardrailColonFormatNoSeparator() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
|
||||
assertValid(() -> Guardrails.minimumClientDriverVersion.guard("DataStax Java Driver", userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWarnUnsetDriverId() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"unset\":\"0.0.0\"}");
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard(null, null, userClientState),
|
||||
"Connection with unset driver id was detected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailUnsetDriverId() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"unset\":\"0.0.0\"}");
|
||||
assertFails(() -> Guardrails.minimumClientDriverVersion.guard(null, null, userClientState),
|
||||
"Connections with unset driver id's are forbidden.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWarnUnknownDriverId() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"unknown\":\"0.0.0\"}");
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("whatever driver name", "1.2.3", userClientState),
|
||||
"Detected unknown driver id: whatever driver name");
|
||||
assertWarns(() -> Guardrails.minimumClientDriverVersion.guard("whatever driver name", null, userClientState),
|
||||
"Detected unknown driver id: whatever driver name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailUnknownDriverId() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"unknown\":\"0.0.0\"}");
|
||||
assertFails(() -> Guardrails.minimumClientDriverVersion.guard("whatever driver name", "1.2.3", userClientState),
|
||||
"Detected unknown driver id: whatever driver name");
|
||||
assertFails(() -> Guardrails.minimumClientDriverVersion.guard("whatever driver name", null, userClientState),
|
||||
"Detected unknown driver id: whatever driver name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJmxGetSetWarned()
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsWarned("{\"DataStax Java Driver\":\"4.18.0\"}");
|
||||
String json = guardrails().getMinimumClientDriverVersionsWarned();
|
||||
assertTrue(json.contains("DataStax Java Driver"));
|
||||
assertTrue(json.contains("4.18.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJmxGetSetDisallowed()
|
||||
{
|
||||
guardrails().setMinimumClientDriverVersionsDisallowed("{\"DataStax Java Driver\":\"4.0.0\",\"DataStax Python Driver\":\"3.0.0\"}");
|
||||
String json = guardrails().getMinimumClientDriverVersionsDisallowed();
|
||||
assertTrue(json.contains("DataStax Java Driver"));
|
||||
assertTrue(json.contains("DataStax Python Driver"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateValidVersions()
|
||||
{
|
||||
validateAndSanitizeClientDriverVersions(new HashMap<>(Map.of("driver", "4.18.0")), "test");
|
||||
validateAndSanitizeClientDriverVersions(new HashMap<>(Map.of("driver", "v4.18.0")), "test");
|
||||
validateAndSanitizeClientDriverVersions(new HashMap<>(Map.of("driver", "V4.18.0")), "test");
|
||||
validateAndSanitizeClientDriverVersions(new HashMap<>(Map.of("a", "1.0.0", "b", "2.0.0")), "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateNullMap()
|
||||
{
|
||||
validateAndSanitizeClientDriverVersions(null, "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateEmptyMap()
|
||||
{
|
||||
validateAndSanitizeClientDriverVersions(Collections.emptyMap(), "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateInvalidVersion()
|
||||
{
|
||||
assertThatThrownBy(() -> validateAndSanitizeClientDriverVersions(Map.of("driver", "not-a-version"), "test"))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateEmptyVersion()
|
||||
{
|
||||
assertThatThrownBy(() -> validateAndSanitizeClientDriverVersions(Map.of("driver", ""), "test"))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateBlankVersion()
|
||||
{
|
||||
assertThatThrownBy(() -> validateAndSanitizeClientDriverVersions(Map.of("driver", " "), "test"))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMixedValidAndInvalid()
|
||||
{
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("good-driver", "4.18.0");
|
||||
map.put("bad-driver", "xyz");
|
||||
assertThatThrownBy(() -> validateAndSanitizeClientDriverVersions(map, "test"))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue