Copilot commented on code in PR #4699:
URL: https://github.com/apache/cassandra/pull/4699#discussion_r3024444314


##########
src/java/org/apache/cassandra/db/guardrails/Guardrails.java:
##########
@@ -1853,6 +1864,60 @@ public boolean getUnsetTrainingMinFrequencyEnabled()
         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);
+        }
+    }
+
+    @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);
+        }
+    }
+
+    @Override
+    public void setMinimumClientDriverVersionsWarned(String value)
+    {
+        try
+        {
+            DEFAULT_CONFIG.setMinimumClientDriverVersionsWarned(
+                    JsonUtils.JSON_OBJECT_MAPPER.readValue(value, new 
TypeReference<Map<String, String>>() {}));
+        }
+        catch (Throwable t)
+        {
+            throw new RuntimeException(t);
+        }
+    }
+
+    @Override
+    public void setMinimumClientDriverVersionsDisallowed(String value)
+    {
+        try
+        {
+            DEFAULT_CONFIG.setMinimumClientDriverVersionsDisallowed(
+                    JsonUtils.JSON_OBJECT_MAPPER.readValue(value, new 
TypeReference<Map<String, String>>() {}));
+        }
+        catch (Throwable t)
+        {
+            throw new RuntimeException(t);
+        }
+    }

Review Comment:
   The MBean setters catch `Throwable` and rethrow `new RuntimeException(t)` 
without context, which makes operator debugging difficult and may also mask 
serious `Error`s. Prefer catching `Exception` (or the specific Jackson 
exceptions) and throwing a runtime exception with a clear message including 
which property failed to parse (e.g., 
`minimum_client_driver_versions_warned/disallowed`) and the original cause.



##########
src/java/org/apache/cassandra/db/guardrails/ClientDriverVersionGuardrail.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.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.
+ */
+public class ClientDriverVersionGuardrail extends Predicates<String>
+{
+    /**
+     * Creates a new client driver version guardrail.
+     *
+     * @param warnVersions     supplier of driver name to minimum warn version 
map
+     * @param disallowVersions supplier of driver name to minimum fail version 
map
+     */
+    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_version",
+              null,
+              state -> driverId -> isBelowMinimum(driverId, 
warnVersions.apply(state)),
+              state -> driverId -> isBelowMinimum(driverId, 
disallowVersions.apply(state)),
+              // message provider is overridden below
+              (isWarning, driverId) -> "");
+        this.warnVersions = warnVersions;
+        this.disallowVersions = disallowVersions;
+    }
+
+    @Override
+    public void guard(String driverId, @Nullable ClientState state)
+    {
+        if (!enabled(state))
+            return;
+
+        Map<String, String> disallowed = disallowVersions.apply(state);
+        if (isBelowMinimum(driverId, disallowed))
+        {
+            String minimumVersion = getMinimumVersion(driverId, disallowed);
+            fail(String.format("Client driver %s is below required minimum 
version %s, connection rejected",
+                               driverId, minimumVersion), state);

Review Comment:
   `driverId` originates from client-supplied STARTUP options and is 
interpolated directly into warning/failure messages that may be logged and/or 
surfaced to clients. To reduce log-forging and nuisance risks, consider 
sanitizing the string (e.g., strip `\r`/`\n`, enforce a reasonable max length) 
before embedding it into messages.



##########
src/java/org/apache/cassandra/db/guardrails/ClientDriverVersionGuardrail.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.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.
+ */
+public class ClientDriverVersionGuardrail extends Predicates<String>
+{
+    /**
+     * Creates a new client driver version guardrail.
+     *
+     * @param warnVersions     supplier of driver name to minimum warn version 
map
+     * @param disallowVersions supplier of driver name to minimum fail version 
map
+     */
+    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_version",
+              null,
+              state -> driverId -> isBelowMinimum(driverId, 
warnVersions.apply(state)),
+              state -> driverId -> isBelowMinimum(driverId, 
disallowVersions.apply(state)),
+              // message provider is overridden below
+              (isWarning, driverId) -> "");

Review Comment:
   The guardrail name is singular (`minimum_client_driver_version`) while the 
config properties and MBean methods are plural 
(`minimum_client_driver_versions_*`). Aligning the guardrail identifier with 
the property naming improves discoverability in logs/diagnostics and reduces 
confusion when correlating enforcement behavior with configuration.



##########
src/java/org/apache/cassandra/db/guardrails/ClientDriverVersionGuardrail.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.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.
+ */
+public class ClientDriverVersionGuardrail extends Predicates<String>
+{
+    /**
+     * Creates a new client driver version guardrail.
+     *
+     * @param warnVersions     supplier of driver name to minimum warn version 
map
+     * @param disallowVersions supplier of driver name to minimum fail version 
map
+     */
+    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_version",
+              null,
+              state -> driverId -> isBelowMinimum(driverId, 
warnVersions.apply(state)),
+              state -> driverId -> isBelowMinimum(driverId, 
disallowVersions.apply(state)),
+              // message provider is overridden below
+              (isWarning, driverId) -> "");
+        this.warnVersions = warnVersions;
+        this.disallowVersions = disallowVersions;
+    }
+
+    @Override
+    public void guard(String driverId, @Nullable ClientState state)
+    {
+        if (!enabled(state))
+            return;
+
+        Map<String, String> disallowed = disallowVersions.apply(state);
+        if (isBelowMinimum(driverId, disallowed))
+        {
+            String minimumVersion = getMinimumVersion(driverId, disallowed);
+            fail(String.format("Client driver %s is below required minimum 
version %s, connection rejected",
+                               driverId, minimumVersion), state);
+            return;
+        }
+
+        Map<String, String> warned = warnVersions.apply(state);
+        if (isBelowMinimum(driverId, warned))
+        {
+            String minimumVersion = getMinimumVersion(driverId, warned);
+            warn(String.format("Client driver %s is below recommended minimum 
version %s",
+                               driverId, minimumVersion));

Review Comment:
   `driverId` originates from client-supplied STARTUP options and is 
interpolated directly into warning/failure messages that may be logged and/or 
surfaced to clients. To reduce log-forging and nuisance risks, consider 
sanitizing the string (e.g., strip `\r`/`\n`, enforce a reasonable max length) 
before embedding it into messages.



##########
src/java/org/apache/cassandra/db/guardrails/ClientDriverVersionGuardrail.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.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.
+ */
+public class ClientDriverVersionGuardrail extends Predicates<String>
+{
+    /**
+     * Creates a new client driver version guardrail.
+     *
+     * @param warnVersions     supplier of driver name to minimum warn version 
map
+     * @param disallowVersions supplier of driver name to minimum fail version 
map
+     */
+    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_version",
+              null,
+              state -> driverId -> isBelowMinimum(driverId, 
warnVersions.apply(state)),
+              state -> driverId -> isBelowMinimum(driverId, 
disallowVersions.apply(state)),
+              // message provider is overridden below
+              (isWarning, driverId) -> "");
+        this.warnVersions = warnVersions;
+        this.disallowVersions = disallowVersions;
+    }
+
+    @Override
+    public void guard(String driverId, @Nullable ClientState state)
+    {
+        if (!enabled(state))
+            return;
+
+        Map<String, String> disallowed = disallowVersions.apply(state);
+        if (isBelowMinimum(driverId, disallowed))
+        {
+            String minimumVersion = getMinimumVersion(driverId, disallowed);
+            fail(String.format("Client driver %s is below required minimum 
version %s, connection rejected",
+                               driverId, minimumVersion), state);
+            return;
+        }
+
+        Map<String, String> warned = warnVersions.apply(state);
+        if (isBelowMinimum(driverId, warned))
+        {
+            String minimumVersion = getMinimumVersion(driverId, warned);
+            warn(String.format("Client driver %s is below recommended minimum 
version %s",
+                               driverId, minimumVersion));
+        }
+    }
+
+    private static String getMinimumVersion(@Nullable String driverId, 
@Nullable Map<String, String> minimumVersions)
+    {
+        if (driverId == null || minimumVersions == null)
+            return "unknown";
+
+        int separatorIdx = driverId.lastIndexOf(':');
+        if (separatorIdx < 0)
+            return "unknown";
+
+        return minimumVersions.getOrDefault(driverId.substring(0, 
separatorIdx), "unknown");
+    }
+
+    /**
+     * Checks if the driver identified by {@code driverId} is below the 
minimum version
+     * specified in the config map.
+     * <p>
+     * A {@code null} or empty driverId, or a driverId without a version 
component,
+     * is considered valid (not below minimum). Unparseable version strings 
are also
+     * considered valid to avoid rejecting drivers with non-standard version 
formats.
+     *
+     * @param driverId        driver identifier in format 
"driverName:driverVersion"
+     * @param minimumVersions map of driver name to minimum version string
+     * @return true if the driver version is below the configured minimum
+     */
+    static boolean isBelowMinimum(@Nullable String driverId, @Nullable 
Map<String, String> minimumVersions)
+    {
+        if (minimumVersions == null || minimumVersions.isEmpty())
+            return false;
+
+        if (driverId == null || driverId.isEmpty())
+            return false;
+
+        int separatorIdx = driverId.lastIndexOf(':');
+        if (separatorIdx < 0 || separatorIdx == driverId.length() - 1)
+            return false;
+
+        String driverName = driverId.substring(0, separatorIdx);
+        String driverVersion = driverId.substring(separatorIdx + 1);
+        String minimumVersion = minimumVersions.get(driverName);
+
+        if (minimumVersion == null)
+            return false;
+
+        try
+        {
+            Semver current = new Semver(stripVPrefix(driverVersion), 
SemverType.LOOSE);
+            Semver minimum = new Semver(stripVPrefix(minimumVersion), 
SemverType.LOOSE);
+            return current.isLowerThan(minimum);
+        }
+        catch (Exception e)
+        {
+            // if version can't be parsed, don't block the connection
+            return false;
+        }
+    }
+
+    private static String stripVPrefix(String version)
+    {
+        return version.startsWith("v") || version.startsWith("V") ? 
version.substring(1) : version;
+    }

Review Comment:
   Parse failures are silently ignored (treated as “allowed”) with no logging. 
This makes misconfiguration (e.g., invalid minimum versions or unexpected 
client version formats like leading/trailing whitespace) hard to detect and can 
unintentionally disable enforcement. Consider trimming inputs before parsing 
and emitting at least a rate-limited warning (ideally including the driver name 
and the offending version string) when parsing fails.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to