This is an automated email from the ASF dual-hosted git repository.

frankgh pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b8ddad05 CASSSIDECAR-407: Add new settings under driver_parameters to 
support pluggable withCredentials provider. Add two possible implementations, 
ConfigProvider (which is the current implementation) and a new one, 
FileProvider which reads username and password from the disk.
b8ddad05 is described below

commit b8ddad057c4db7b6ad061ba04323d9cc10622d96
Author: Michael Burman <[email protected]>
AuthorDate: Fri Feb 13 12:20:43 2026 +0200

    CASSSIDECAR-407: Add new settings under driver_parameters to support 
pluggable withCredentials provider. Add two possible implementations, 
ConfigProvider (which is the current implementation) and a new one, 
FileProvider which reads username and password from the disk.
    
    Patch by Michael Burman; reviewed by Francisco Guerrero, Yifan Cai for 
CASSSIDECAR-407
---
 CHANGES.txt                                        |   1 +
 conf/sidecar.yaml                                  |   7 +-
 docs/src/user.adoc                                 |  17 +-
 .../resources/config/sidecar.yaml.template         |  13 +-
 .../sidecar/cluster/CQLSessionProviderImpl.java    |  26 ++-
 .../sidecar/cluster/auth/ConfigProvider.java       |  64 +++++++
 .../sidecar/cluster/auth/CqlAuthProvider.java      |  35 ++++
 .../sidecar/cluster/auth/FileProvider.java         |  94 ++++++++++
 .../sidecar/config/DriverConfiguration.java        |  11 ++
 .../config/yaml/DriverConfigurationImpl.java       | 192 +++++++++++++++++++--
 .../sidecar/modules/ConfigurationModule.java       |  44 +++++
 .../sidecar/testing/IntegrationTestModule.java     |  15 +-
 .../sidecar/cluster/auth/ConfigProviderTest.java   |  48 ++++++
 .../sidecar/cluster/auth/FileProviderTest.java     |  75 ++++++++
 .../sidecar/config/SidecarConfigurationTest.java   |  16 ++
 .../sidecar_driver_params_config_provider.yaml     |  23 +++
 16 files changed, 646 insertions(+), 35 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 589d6547..3ef59747 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 0.4.0
 -----
+ * Ability to load Cassandra connection secrets from filesystem 
(CASSSIDECAR-407)
  * Sidecar should wake up immediately on the instance receiving a phase signal 
instead of waiting for the discovery loop (CASSSIDECAR-454)
  * Fix schema initialization race, CI test reporting, and build stability 
(CASSSIDECAR-468)
  * Add FileBasedConfigurationProvider for file-based overlay persistence 
(CASSSIDECAR-426)
diff --git a/conf/sidecar.yaml b/conf/sidecar.yaml
index 2a2e6d9c..3fc9d457 100644
--- a/conf/sidecar.yaml
+++ b/conf/sidecar.yaml
@@ -325,8 +325,11 @@ access_control:
 driver_parameters:
   contact_points:
     - "127.0.0.1:9042"
-  username: cassandra
-  password: cassandra
+  auth_provider:
+    class_name: org.apache.cassandra.sidecar.cluster.auth.ConfigProvider
+    parameters:
+      username: cassandra
+      password: cassandra
   ssl:
     enabled:  false
     keystore:
diff --git a/docs/src/user.adoc b/docs/src/user.adoc
index d8ef90a7..ec6f6596 100644
--- a/docs/src/user.adoc
+++ b/docs/src/user.adoc
@@ -99,8 +99,16 @@ driver_parameters:
   contact_points:
     - "<host from cassandra_instances>:<port from cassandra_instances>"
     - "<host of additional cassandra instances in your cluster>:<port of 
instance>"
-  username: <A user which has been configured on the local Cassandra cluster>
-  password: <Password associated with the above user>
+  auth_provider:
+    class_name: org.apache.cassandra.sidecar.cluster.auth.ConfigProvider
+    parameters:
+      username: <A user configured on the local Cassandra cluster>
+      password: <Password associated with the above user>
+    # Alternative option is to read username and password from a file
+    # class_name: org.apache.cassandra.sidecar.cluster.auth.FileProvider
+    # parameters:
+    #   username_path: <Path to file containing Cassandra username>
+    #   password_path: <Path to file containing Cassandra password>
   ssl:
     enabled: <true if you have enabled encryption for CQL, false if otherwise>
     keystore:
@@ -338,8 +346,9 @@ This section of the `sidecar.yaml` file configures how 
requests are authenticate
 The `driver_parameters` section of the `sidecar.yaml` file is used to 
configure the Cassandra driver used by Cassandra Sidecar to connect to 
Cassandra over CQL. The following properties are defined:
 
 * `contact_points`: This is a list of IP Addresses or hostnames and ports of 
the Cassandra instances that Cassandra Sidecar can use to bootstrap its 
connection to the Cassandra cluster.
-* `username`: This is the username used to authenticate with the Cassandra 
cluster.
-* `password`: This is the password used to authenticate with the Cassandra 
cluster.
+* `auth_provider`: Provider entry used to resolve driver authentication 
details. Implementation choices are 
`org.apache.cassandra.sidecar.cluster.auth.FileProvider` and 
`org.apache.cassandra.sidecar.cluster.auth.ConfigProvider`. If both 
`auth_provider` and `username`/`password` are configured, Sidecar uses 
`auth_provider` and uses `username`/`password` as fallback when no provider is 
configured.
+* `username`: This is the username used to authenticate with the Cassandra 
cluster. This is a legacy fallback and is deprecated in favor of 
`auth_provider` with `ConfigProvider`.
+* `password`: This is the password used to authenticate with the Cassandra 
cluster. This is a legacy fallback and is deprecated in favor of 
`auth_provider` with `ConfigProvider`.
 * `ssl`: This defines how the Cassandra driver can connect to Cassandra over 
SSL. See the above <<ssl, SSL/TLS Configuration>> section for more information.
 * `num_connections`: This defines the number of connections that the Cassandra 
driver will open to each Cassandra instance. By default, 2.
 * `local_dc`: This configures the local data center of the Cassandra driver. 
Ensure that this is the same value as the data center of the Cassandra nodes 
which this Cassandra Sidecar manages.
diff --git 
a/integration-tests/src/integrationTest/resources/config/sidecar.yaml.template 
b/integration-tests/src/integrationTest/resources/config/sidecar.yaml.template
index 79613d40..cea582f7 100644
--- 
a/integration-tests/src/integrationTest/resources/config/sidecar.yaml.template
+++ 
b/integration-tests/src/integrationTest/resources/config/sidecar.yaml.template
@@ -267,8 +267,17 @@ access_control:
 driver_parameters:
   contact_points:
     - "127.0.0.1:9042"
-  username: cassandra
-  password: cassandra
+  auth_provider:
+  # Authentication information, implementing 
org.apache.cassandra.sidecar.cluster.auth.CqlAuthProvider;
+  # used to provide username/password to connect to the Cassandra cluster.
+  #   class_name: org.apache.cassandra.sidecar.cluster.auth.FileProvider
+  #   parameters:
+  #     username_path: /opt/sidecar/superuser/username
+  #     password_path: /opt/sidecar/superuser/password
+    class_name: org.apache.cassandra.sidecar.cluster.auth.ConfigProvider
+    parameters:
+      username: cassandra
+      password: cassandra
   ssl:
     enabled:  false
     keystore:
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/CQLSessionProviderImpl.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/CQLSessionProviderImpl.java
index 491ec362..7130a92d 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/CQLSessionProviderImpl.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/CQLSessionProviderImpl.java
@@ -27,7 +27,9 @@ import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -51,6 +53,8 @@ import com.datastax.driver.core.policies.LoadBalancingPolicy;
 import com.datastax.driver.core.policies.ReconnectionPolicy;
 import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
+import org.apache.cassandra.sidecar.cluster.auth.ConfigProvider;
+import org.apache.cassandra.sidecar.cluster.auth.CqlAuthProvider;
 import org.apache.cassandra.sidecar.cluster.driver.SidecarLoadBalancingPolicy;
 import org.apache.cassandra.sidecar.common.server.CQLSessionProvider;
 import org.apache.cassandra.sidecar.common.server.utils.DriverUtils;
@@ -80,8 +84,7 @@ public class CQLSessionProviderImpl implements 
CQLSessionProvider
     private final NettyOptions nettyOptions;
     private final ReconnectionPolicy reconnectionPolicy;
     private final List<InetSocketAddress> localInstances;
-    private final String username;
-    private final String password;
+    private final CqlAuthProvider authProvider;
     private final DriverUtils driverUtils;
     private volatile Session session;
 
@@ -119,8 +122,10 @@ public class CQLSessionProviderImpl implements 
CQLSessionProvider
         this.localInstances = localInstances;
         this.localDc = localDc;
         this.numAdditionalConnections = numAdditionalConnections;
-        this.username = username;
-        this.password = password;
+        Map<String, String> namedParameters = new HashMap<>();
+        namedParameters.put("username", username);
+        namedParameters.put("password", password);
+        this.authProvider = new ConfigProvider(namedParameters);
         this.sslConfiguration = sslConfiguration;
         this.nettyOptions = options;
         this.reconnectionPolicy = new ExponentialReconnectionPolicy(500, 
healthCheckFrequencyMillis);
@@ -129,6 +134,7 @@ public class CQLSessionProviderImpl implements 
CQLSessionProvider
 
     public CQLSessionProviderImpl(SidecarConfiguration configuration,
                                   NettyOptions options,
+                                  CqlAuthProvider authProvider,
                                   DriverUtils driverUtils)
     {
         this.driverUtils = driverUtils;
@@ -139,11 +145,10 @@ public class CQLSessionProviderImpl implements 
CQLSessionProvider
                                            .map(i -> new 
InetSocketAddress(i.host(), i.port()))
                                            .collect(Collectors.toList());
         this.localDc = driverConfiguration.localDc();
-        this.username = driverConfiguration.username();
-        this.password = driverConfiguration.password();
         this.sslConfiguration = driverConfiguration.sslConfiguration();
         this.numAdditionalConnections = driverConfiguration.numConnections();
         this.nettyOptions = options;
+        this.authProvider = authProvider;
         long maxDelayMs = 
configuration.healthCheckConfiguration().executeInterval().toMillis();
         this.reconnectionPolicy = new ExponentialReconnectionPolicy(500, 
maxDelayMs);
     }
@@ -208,9 +213,14 @@ public class CQLSessionProviderImpl implements 
CQLSessionProvider
                 builder.withSSL(sslOptions);
             }
 
-            if (username != null && password != null)
+            if (authProvider != null)
             {
-                builder.withCredentials(username, password);
+                String username = authProvider.username();
+                String password = authProvider.password();
+                if (username != null && password != null)
+                {
+                    builder.withCredentials(username, password);
+                }
             }
             // During mTLS connections, when client sends in keystore, we 
should have an AuthProvider passed along.
             // hence we pass empty username and password in 
PlainTextAuthProvider here, in case user hasn't already
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/ConfigProvider.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/ConfigProvider.java
new file mode 100644
index 00000000..10419f8d
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/ConfigProvider.java
@@ -0,0 +1,64 @@
+/*
+ * 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.sidecar.cluster.auth;
+
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+
+/**
+ * Loads CQL username/password directly from auth_provider parameters.
+ */
+public class ConfigProvider implements CqlAuthProvider
+{
+    public static final String USERNAME_PARAM = "username";
+    public static final String PASSWORD_PARAM = "password";
+
+    private final String username;
+    private final String password;
+
+    public ConfigProvider(Map<String, String> parameters)
+    {
+        Objects.requireNonNull(parameters, "parameters must not be null");
+        this.username = requiredParameter(parameters, USERNAME_PARAM);
+        this.password = requiredParameter(parameters, PASSWORD_PARAM);
+    }
+
+    private static String requiredParameter(Map<String, String> parameters, 
String key)
+    {
+        if (!parameters.containsKey(key))
+        {
+            throw new ConfigurationException("Missing required auth_provider 
parameter \"" + key + "\"");
+        }
+        return parameters.get(key);
+    }
+
+  @Override
+  public String username()
+  {
+        return username;
+  }
+
+  @Override
+  public String password()
+  {
+        return password;
+  }
+}
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/CqlAuthProvider.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/CqlAuthProvider.java
new file mode 100644
index 00000000..941f331c
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/CqlAuthProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.sidecar.cluster.auth;
+
+/**
+ * Provides authentication details used by Sidecar's internal Cassandra CQL 
client.
+ */
+public interface CqlAuthProvider
+{
+    /**
+     * @return username for the CQL connection
+     */
+    String username();
+
+    /**
+     * @return password for the CQL connection
+     */
+    String password();
+}
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/FileProvider.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/FileProvider.java
new file mode 100644
index 00000000..b7b03145
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/auth/FileProvider.java
@@ -0,0 +1,94 @@
+/*
+ * 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.sidecar.cluster.auth;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+
+/**
+ * Loads CQL username/password from files.
+ */
+public class FileProvider implements CqlAuthProvider
+{
+    static final String USERNAME_PATH_PARAM = "username_path";
+    static final String PASSWORD_PATH_PARAM = "password_path";
+
+    private final Path usernamePath;
+    private final Path passwordPath;
+
+    public FileProvider(Map<String, String> parameters)
+    {
+        Objects.requireNonNull(parameters, "parameters must not be null");
+        this.usernamePath = resolvePath(parameters, USERNAME_PATH_PARAM);
+        this.passwordPath = resolvePath(parameters, PASSWORD_PATH_PARAM);
+    }
+
+    private static Path resolvePath(Map<String, String> parameters, String key)
+    {
+        if (!parameters.containsKey(key))
+        {
+            throw new ConfigurationException("Missing required auth_provider 
parameter \"" + key + "\"");
+        }
+
+        try
+        {
+            return Paths.get(parameters.get(key));
+        }
+        catch (InvalidPathException e)
+        {
+            throw new ConfigurationException("Invalid path in auth_provider 
parameter \"" + key + "\"", e);
+        }
+    }
+
+    private static String readSecret(Path path, String key)
+    {
+        try
+        {
+            String secret = Files.readString(path).trim();
+            if (secret.isEmpty())
+            {
+                throw new ConfigurationException("Empty content in 
auth_provider file for parameter \"" + key + "\"");
+            }
+            return secret;
+        }
+        catch (IOException e)
+        {
+            throw new ConfigurationException("Unable to read auth_provider 
file for parameter \"" + key + "\"", e);
+        }
+    }
+
+  @Override
+  public String username()
+  {
+        return readSecret(usernamePath, USERNAME_PATH_PARAM);
+  }
+
+  @Override
+  public String password()
+  {
+        return readSecret(passwordPath, PASSWORD_PATH_PARAM);
+  }
+}
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/config/DriverConfiguration.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/config/DriverConfiguration.java
index 412a82c1..0ffea4cd 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/config/DriverConfiguration.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/config/DriverConfiguration.java
@@ -49,15 +49,26 @@ public interface DriverConfiguration
     String localDc();
 
     /**
+     * @deprecated use {@link #authProvider()} with
+     * {@code org.apache.cassandra.sidecar.cluster.auth.ConfigProvider} 
instead.
      * @return the username used for connecting to the Cassandra instance
      */
+    @Deprecated
     String username();
 
     /**
+     * @deprecated use {@link #authProvider()} with
+     * {@code org.apache.cassandra.sidecar.cluster.auth.ConfigProvider} 
instead.
      * @return the password used for connecting to the Cassandra instance
      */
+    @Deprecated
     String password();
 
+    /**
+     * @return Configured authentication provider for CQL connections.
+     */
+    ParameterizedClassConfiguration authProvider();
+
     /**
      * @return Configuration such as keystore, truststore needed for 
establishing SSL/mTLS connection with
      * Cassandra instance.
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/DriverConfigurationImpl.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/DriverConfigurationImpl.java
index b4e1aafc..fdd1f0dd 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/DriverConfigurationImpl.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/DriverConfigurationImpl.java
@@ -19,13 +19,14 @@
 package org.apache.cassandra.sidecar.config.yaml;
 
 import java.net.InetSocketAddress;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.common.DataObjectBuilder;
 import 
org.apache.cassandra.sidecar.common.server.utils.SecondBoundConfiguration;
 import org.apache.cassandra.sidecar.config.DriverConfiguration;
+import org.apache.cassandra.sidecar.config.ParameterizedClassConfiguration;
 import org.apache.cassandra.sidecar.config.SslConfiguration;
 
 /**
@@ -51,6 +52,9 @@ public class DriverConfigurationImpl implements 
DriverConfiguration
     @JsonProperty("password")
     private final String password;
 
+    @JsonProperty("auth_provider")
+    private final ParameterizedClassConfiguration authProvider;
+
     @JsonProperty("ssl")
     private final SslConfiguration sslConfiguration;
 
@@ -59,24 +63,19 @@ public class DriverConfigurationImpl implements 
DriverConfiguration
 
     public DriverConfigurationImpl()
     {
-        this(Collections.emptyList(), null, DEFAULT_NUM_CONNECTIONS, null, 
null, null, DEFAULT_UNSUPPORTED_TABLE_SCHEMA_REFRESH_TIME);
+        this(builder());
     }
 
-    public DriverConfigurationImpl(List<InetSocketAddress> contactPoints,
-                                   String localDc,
-                                   int numConnections,
-                                   String username,
-                                   String password,
-                                   SslConfiguration sslConfiguration,
-                                   SecondBoundConfiguration 
unsupportedTableSchemaRefreshTime)
+    private DriverConfigurationImpl(Builder builder)
     {
-        this.contactPoints = contactPoints;
-        this.localDc = localDc;
-        this.numConnections = numConnections;
-        this.username = username;
-        this.password = password;
-        this.sslConfiguration = sslConfiguration;
-        this.unsupportedTableSchemaRefreshTime = 
unsupportedTableSchemaRefreshTime;
+        contactPoints = builder.contactPoints;
+        localDc = builder.localDc;
+        numConnections = builder.numConnections;
+        username = builder.username;
+        password = builder.password;
+        authProvider = builder.authProvider;
+        sslConfiguration = builder.sslConfiguration;
+        unsupportedTableSchemaRefreshTime = 
builder.unsupportedTableSchemaRefreshTime;
     }
 
     /**
@@ -112,6 +111,7 @@ public class DriverConfigurationImpl implements 
DriverConfiguration
     /**
      * {@inheritDoc}
      */
+    @Deprecated
     @Override
     @JsonProperty("username")
     public String username()
@@ -122,6 +122,7 @@ public class DriverConfigurationImpl implements 
DriverConfiguration
     /**
      * {@inheritDoc}
      */
+    @Deprecated
     @Override
     @JsonProperty("password")
     public String password()
@@ -129,6 +130,16 @@ public class DriverConfigurationImpl implements 
DriverConfiguration
         return password;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @JsonProperty("auth_provider")
+    public ParameterizedClassConfiguration authProvider()
+    {
+        return authProvider;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -148,4 +159,153 @@ public class DriverConfigurationImpl implements 
DriverConfiguration
     {
         return unsupportedTableSchemaRefreshTime;
     }
+
+    public static Builder builder()
+    {
+        return new Builder();
+    }
+
+    /**
+     * {@code DriverConfigurationImpl} builder static inner class.
+     */
+    public static final class Builder implements DataObjectBuilder<Builder, 
DriverConfigurationImpl>
+    {
+        private List<InetSocketAddress> contactPoints = List.of();
+        private String localDc;
+        private int numConnections = DEFAULT_NUM_CONNECTIONS;
+        private String username;
+        private String password;
+        private ParameterizedClassConfiguration authProvider;
+        private SslConfiguration sslConfiguration;
+        private SecondBoundConfiguration unsupportedTableSchemaRefreshTime = 
DEFAULT_UNSUPPORTED_TABLE_SCHEMA_REFRESH_TIME;
+
+        private Builder()
+        {
+        }
+
+        @Override
+        public Builder self()
+        {
+            return this;
+        }
+
+        /**
+         * Sets the {@code contactPoints} and returns a reference to this 
Builder enabling method chaining.
+         *
+         * @param contactPoints the {@code contactPoints} to set
+         * @return a reference to this Builder
+         */
+        public Builder contactPoints(List<InetSocketAddress> contactPoints)
+        {
+            return update(b -> b.contactPoints = contactPoints);
+        }
+
+        /**
+         * Sets the {@code localDc} and returns a reference to this Builder 
enabling method chaining.
+         *
+         * @param localDc the {@code localDc} to set
+         * @return a reference to this Builder
+         */
+        public Builder localDc(String localDc)
+        {
+            return update(b -> b.localDc = localDc);
+        }
+
+        /**
+         * Sets the {@code numConnections} and returns a reference to this 
Builder enabling method chaining.
+         *
+         * @param numConnections the {@code numConnections} to set
+         * @return a reference to this Builder
+         */
+        public Builder numConnections(int numConnections)
+        {
+            return update(b -> b.numConnections = numConnections);
+        }
+
+        /**
+         * Sets the {@code username} and returns a reference to this Builder 
enabling method chaining.
+         *
+         * @param username the {@code username} to set
+         * @return a reference to this Builder
+         * @deprecated use {@link 
#authProvider(ParameterizedClassConfiguration)} to supply credentials instead
+         */
+        @Deprecated
+        public Builder username(String username)
+        {
+            return update(b -> b.username = username);
+        }
+
+        /**
+         * Sets the {@code password} and returns a reference to this Builder 
enabling method chaining.
+         *
+         * @param password the {@code password} to set
+         * @return a reference to this Builder
+         * @deprecated use {@link 
#authProvider(ParameterizedClassConfiguration)} to supply credentials instead
+         */
+        @Deprecated
+        public Builder password(String password)
+        {
+            return update(b -> b.password = password);
+        }
+
+        /**
+         * Sets the {@code authProvider} and returns a reference to this 
Builder enabling method chaining.
+         *
+         * <p>The auth provider is the preferred way to configure credentials, 
replacing the deprecated
+         * {@link #username(String)} and {@link #password(String)} setters. 
For example:
+         *
+         * <pre>{@code
+         * ParameterizedClassConfiguration authProvider =
+         *     new 
ParameterizedClassConfigurationImpl("org.apache.cassandra.sidecar.cluster.auth.ConfigProvider",
+         *                                             
Map.of(org.apache.cassandra.sidecar.cluster.auth.ConfigProvider.USERNAME_PARAM, 
"cassandra",
+         *                                                    
org.apache.cassandra.sidecar.cluster.auth.ConfigProvider.PASSWORD_PARAM, 
"cassandra"));
+         *
+         * DriverConfiguration driverConfiguration = 
DriverConfigurationImpl.builder()
+         *                                                                  
.authProvider(authProvider)
+         *                                                                  ...
+         *                                                                  
.build();
+         * }</pre>
+         *
+         * @param authProvider the {@code authProvider} to set
+         * @return a reference to this Builder
+         */
+        public Builder authProvider(ParameterizedClassConfiguration 
authProvider)
+        {
+            return update(b -> b.authProvider = authProvider);
+        }
+
+        /**
+         * Sets the {@code sslConfiguration} and returns a reference to this 
Builder enabling method chaining.
+         *
+         * @param sslConfiguration the {@code sslConfiguration} to set
+         * @return a reference to this Builder
+         */
+        public Builder sslConfiguration(SslConfiguration sslConfiguration)
+        {
+            return update(b -> b.sslConfiguration = sslConfiguration);
+        }
+
+        /**
+         * Sets the {@code unsupportedTableSchemaRefreshTime} and returns a 
reference to this Builder enabling
+         * method chaining.
+         *
+         * @param unsupportedTableSchemaRefreshTime the {@code 
unsupportedTableSchemaRefreshTime} to set
+         * @return a reference to this Builder
+         */
+        public Builder 
unsupportedTableSchemaRefreshTime(SecondBoundConfiguration 
unsupportedTableSchemaRefreshTime)
+        {
+            return update(b -> b.unsupportedTableSchemaRefreshTime = 
unsupportedTableSchemaRefreshTime);
+        }
+
+        /**
+         * Returns a {@code DriverConfigurationImpl} built from the parameters 
previously set.
+         *
+         * @return a {@code DriverConfigurationImpl} built with parameters of 
this {@code DriverConfigurationImpl.Builder}
+         */
+        @Override
+        public DriverConfigurationImpl build()
+        {
+            return new DriverConfigurationImpl(this);
+        }
+    }
 }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/modules/ConfigurationModule.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/modules/ConfigurationModule.java
index f6256405..2da63b21 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/modules/ConfigurationModule.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/modules/ConfigurationModule.java
@@ -18,7 +18,9 @@
 
 package org.apache.cassandra.sidecar.modules;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import com.google.common.util.concurrent.SidecarRateLimiter;
@@ -40,6 +42,9 @@ import 
org.apache.cassandra.sidecar.cluster.CQLSessionProviderImpl;
 import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.cluster.InstancesMetadata;
 import org.apache.cassandra.sidecar.cluster.InstancesMetadataImpl;
+import org.apache.cassandra.sidecar.cluster.auth.ConfigProvider;
+import org.apache.cassandra.sidecar.cluster.auth.CqlAuthProvider;
+import org.apache.cassandra.sidecar.cluster.auth.FileProvider;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
 import org.apache.cassandra.sidecar.common.server.CQLSessionProvider;
@@ -48,12 +53,15 @@ import 
org.apache.cassandra.sidecar.common.server.dns.DnsResolver;
 import org.apache.cassandra.sidecar.common.server.utils.DriverUtils;
 import org.apache.cassandra.sidecar.common.server.utils.SidecarVersionProvider;
 import 
org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.DriverConfiguration;
 import org.apache.cassandra.sidecar.config.InstanceConfiguration;
 import org.apache.cassandra.sidecar.config.JmxConfiguration;
+import org.apache.cassandra.sidecar.config.ParameterizedClassConfiguration;
 import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 import org.apache.cassandra.sidecar.config.SidecarConfiguration;
 import org.apache.cassandra.sidecar.db.DriverUnsupportedSchemaCache;
 import org.apache.cassandra.sidecar.db.schema.TableSchemaFetcher;
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
 import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory;
 import org.apache.cassandra.sidecar.metrics.instance.InstanceHealthMetrics;
 import org.apache.cassandra.sidecar.modules.multibindings.KeyClassMapKey;
@@ -117,15 +125,51 @@ public class ConfigurationModule extends AbstractModule
     @Singleton
     CQLSessionProvider cqlSessionProvider(Vertx vertx,
                                           SidecarConfiguration 
sidecarConfiguration,
+                                          CqlAuthProvider cqlAuthProvider,
                                           DriverUtils driverUtils)
     {
         CQLSessionProviderImpl cqlSessionProvider = new 
CQLSessionProviderImpl(sidecarConfiguration,
                                                                                
NettyOptions.DEFAULT_INSTANCE,
+                                                                               
cqlAuthProvider,
                                                                                
driverUtils);
         vertx.eventBus().localConsumer(ON_SERVER_STOP.address(), message -> 
cqlSessionProvider.close());
         return cqlSessionProvider;
     }
 
+    @Provides
+    @Singleton
+    CqlAuthProvider cqlAuthProvider(SidecarConfiguration sidecarConfiguration)
+    {
+        DriverConfiguration driverConfiguration = 
sidecarConfiguration.driverConfiguration();
+
+        ParameterizedClassConfiguration config = 
driverConfiguration.authProvider();
+        if (config == null)
+        {
+            // Fallback to the old one
+            Map<String, String> namedParameters = new HashMap<>();
+            namedParameters.put("username", driverConfiguration.username());
+            namedParameters.put("password", driverConfiguration.password());
+            return new ConfigProvider(namedParameters);
+        }
+
+        if (config.namedParameters() == null)
+        {
+            throw new ConfigurationException("Missing parameters for 
auth_provider");
+        }
+
+        Map<String, String> namedParameters = config.namedParameters();
+
+        if 
(config.className().equalsIgnoreCase(ConfigProvider.class.getName()))
+        {
+            return new ConfigProvider(namedParameters);
+        }
+        if (config.className().equalsIgnoreCase(FileProvider.class.getName()))
+        {
+            return new FileProvider(namedParameters);
+        }
+        throw new ConfigurationException("Unrecognized cql auth_provider " + 
config.className() + " set");
+    }
+
     @Provides
     @Singleton
     DriverUnsupportedSchemaCache driverUnsupportedSchemaCache(Vertx vertx,
diff --git 
a/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
 
b/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
index 918fdcdd..ce6c4590 100644
--- 
a/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
+++ 
b/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
@@ -74,6 +74,8 @@ import org.apache.cassandra.sidecar.tasks.PeriodicTask;
 import org.apache.cassandra.sidecar.tasks.ScheduleDecision;
 import org.jetbrains.annotations.NotNull;
 
+import static 
org.apache.cassandra.sidecar.cluster.auth.ConfigProvider.PASSWORD_PARAM;
+import static 
org.apache.cassandra.sidecar.cluster.auth.ConfigProvider.USERNAME_PARAM;
 import static 
org.apache.cassandra.sidecar.exceptions.CassandraUnavailableException.Service.CQL;
 import static 
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SERVER_STOP;
 
@@ -126,9 +128,16 @@ public class IntegrationTestModule extends AbstractModule
                                             
MillisecondBoundConfiguration.parse("50ms"),
                                             
MillisecondBoundConfiguration.parse("5000ms"));
 
-        DriverConfiguration driverConfiguration = new 
DriverConfigurationImpl(Collections.emptyList(), "dc1",
-                                                                              
1, "cassandra", "cassandra",
-                                                                              
null, new SecondBoundConfiguration(3, TimeUnit.SECONDS));
+        ParameterizedClassConfiguration authProvider = new 
ParameterizedClassConfigurationImpl("org.apache.cassandra.sidecar.cluster.auth.ConfigProvider",
+                                                                               
                Map.of(USERNAME_PARAM, "cassandra",
+                                                                               
                       PASSWORD_PARAM, "cassandra"));
+
+        DriverConfiguration driverConfiguration = 
DriverConfigurationImpl.builder()
+                                                                         
.localDc("dc1")
+                                                                         
.numConnections(1)
+                                                                         
.unsupportedTableSchemaRefreshTime(new SecondBoundConfiguration(3, 
TimeUnit.SECONDS))
+                                                                         
.authProvider(authProvider)
+                                                                         
.build();
 
         SslConfiguration sslConfiguration =
         SslConfigurationImpl.builder()
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/auth/ConfigProviderTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/auth/ConfigProviderTest.java
new file mode 100644
index 00000000..9d023b53
--- /dev/null
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/auth/ConfigProviderTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.sidecar.cluster.auth;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+class ConfigProviderTest
+{
+    @Test
+    void testInterfaceGetters()
+    {
+        CqlAuthProvider provider = new 
ConfigProvider(Map.of(ConfigProvider.USERNAME_PARAM, "cassandra",
+            ConfigProvider.PASSWORD_PARAM, "cassandra"));
+        assertThat(provider.username()).isEqualTo("cassandra");
+        assertThat(provider.password()).isEqualTo("cassandra");
+    }
+
+    @Test
+    void testMissingParameterThrows()
+    {
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(() -> new 
ConfigProvider(Map.of(ConfigProvider.USERNAME_PARAM, "cassandra")))
+        .withMessageContaining("Missing required auth_provider parameter");
+    }
+}
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/auth/FileProviderTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/auth/FileProviderTest.java
new file mode 100644
index 00000000..a1d0b673
--- /dev/null
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/auth/FileProviderTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.sidecar.cluster.auth;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+class FileProviderTest
+{
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void testInterfaceGetters() throws IOException
+    {
+        Path usernamePath = tempDir.resolve("username");
+        Path passwordPath = tempDir.resolve("password");
+        Files.writeString(usernamePath, "cassandra-user\n");
+        Files.writeString(passwordPath, "cassandra-pass\n");
+
+        CqlAuthProvider provider = new 
FileProvider(Map.of(FileProvider.USERNAME_PATH_PARAM, usernamePath.toString(),
+            FileProvider.PASSWORD_PATH_PARAM, passwordPath.toString()));
+        assertThat(provider.username()).isEqualTo("cassandra-user");
+        assertThat(provider.password()).isEqualTo("cassandra-pass");
+    }
+
+    @Test
+    void testMissingParameterThrows()
+    {
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(() -> new 
FileProvider(Map.of(FileProvider.USERNAME_PATH_PARAM, "/tmp/username")))
+        .withMessageContaining("Missing required auth_provider parameter");
+    }
+
+    @Test
+    void testEmptySecretThrows() throws IOException
+    {
+        Path usernamePath = tempDir.resolve("username");
+        Path passwordPath = tempDir.resolve("password");
+        Files.writeString(usernamePath, " \n");
+        Files.writeString(passwordPath, "secret");
+
+        FileProvider provider = new 
FileProvider(Map.of(FileProvider.USERNAME_PATH_PARAM, usernamePath.toString(),
+                                                                       
FileProvider.PASSWORD_PATH_PARAM, passwordPath.toString()));
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(provider::username)
+        .withMessageContaining("Empty content in auth_provider file for 
parameter");
+    }
+}
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
index e33463d9..21388278 100644
--- 
a/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
@@ -224,6 +224,22 @@ class SidecarConfigurationTest
         
assertThat(sslConfiguration.truststore().password()).isEqualTo("password");
     }
 
+    @Test
+    void testDriverParametersWithAuthProvider() throws IOException
+    {
+        Path yamlPath = 
yaml("config/sidecar_driver_params_config_provider.yaml");
+        SidecarConfiguration config = 
SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+
+        DriverConfiguration driverConfiguration = config.driverConfiguration();
+        assertThat(driverConfiguration).isNotNull();
+        assertThat(driverConfiguration.authProvider()).isNotNull();
+        ParameterizedClassConfiguration authProvider = 
driverConfiguration.authProvider();
+        
assertThat(authProvider.className()).isEqualTo("org.apache.cassandra.sidecar.cluster.auth.ConfigProvider");
+        assertThat(authProvider.namedParameters())
+        .containsExactlyInAnyOrderEntriesOf(Map.of("username", "cassandra",
+                                                   "password", "cassandra"));
+    }
+
     @Test
     void testReadCustomSchemaKeyspaceConfiguration() throws IOException
     {
diff --git 
a/server/src/test/resources/config/sidecar_driver_params_config_provider.yaml 
b/server/src/test/resources/config/sidecar_driver_params_config_provider.yaml
new file mode 100644
index 00000000..18f91892
--- /dev/null
+++ 
b/server/src/test/resources/config/sidecar_driver_params_config_provider.yaml
@@ -0,0 +1,23 @@
+driver_parameters:
+  contact_points:
+    - "127.0.0.1:9042"
+  auth_provider:
+    class_name: org.apache.cassandra.sidecar.cluster.auth.ConfigProvider
+    parameters:
+      username: cassandra
+      password: cassandra
+  ssl:
+    enabled: true
+    accepted_protocols:
+      - TLSv1.2
+      - TLSv1.3
+    keystore:
+      type: PKCS12
+      path: path/to/keystore.p12
+      password: password
+    truststore:
+      type: PKCS12
+      path: path/to/keystore.p12
+      password: password
+  num_connections: 6
+  local_dc: dc1


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


Reply via email to