You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sa...@apache.org on 2025/01/17 09:14:42 UTC

(cassandra) branch trunk updated (6e8d0cde9e -> a397a53891)

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

samt pushed a change to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


    from 6e8d0cde9e Make JsonUtils serialize Instant always with the same format
     new f33267c8fa Tighten up permissions on system keyspaces
     new 80e84fd722 Merge branch 'cassandra-3.0' into cassandra-3.11
     new 1848efaf53 Merge branch 'cassandra-3.11' into cassandra-4.0
     new d14d16926e Merge branch 'cassandra-4.0' into cassandra-4.1
     new 217490bdd8 Merge branch 'cassandra-4.1' into cassandra-5.0
     new a397a53891 Merge branch 'cassandra-5.0' into trunk

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.txt                                        |   3 +
 src/java/org/apache/cassandra/auth/Permission.java |   7 ++
 src/java/org/apache/cassandra/auth/Resources.java  |  28 ++++-
 .../cql3/statements/GrantPermissionsStatement.java |  21 ++++
 .../apache/cassandra/schema/SchemaConstants.java   |  10 ++
 .../org/apache/cassandra/service/ClientState.java  |  60 +++++++--
 .../apache/cassandra/auth/GrantAndRevokeTest.java  | 138 +++++++++++++++++++--
 test/unit/org/apache/cassandra/cql3/CQLTester.java |   8 ++
 .../apache/cassandra/service/ClientStateTest.java  |   2 +
 .../apache/cassandra/transport/TlsTestUtils.java   |   2 +-
 10 files changed, 253 insertions(+), 26 deletions(-)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org


(cassandra) 01/01: Merge branch 'cassandra-5.0' into trunk

Posted by sa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a397a538919206506df0e960c839e457e316d7f1
Merge: 6e8d0cde9e 217490bdd8
Author: Sam Tunnicliffe <sa...@apache.org>
AuthorDate: Fri Jan 17 09:11:22 2025 +0000

    Merge branch 'cassandra-5.0' into trunk

 CHANGES.txt                                        |   3 +
 src/java/org/apache/cassandra/auth/Permission.java |   7 ++
 src/java/org/apache/cassandra/auth/Resources.java  |  28 ++++-
 .../cql3/statements/GrantPermissionsStatement.java |  21 ++++
 .../apache/cassandra/schema/SchemaConstants.java   |  10 ++
 .../org/apache/cassandra/service/ClientState.java  |  60 +++++++--
 .../apache/cassandra/auth/GrantAndRevokeTest.java  | 138 +++++++++++++++++++--
 test/unit/org/apache/cassandra/cql3/CQLTester.java |   8 ++
 .../apache/cassandra/service/ClientStateTest.java  |   2 +
 .../apache/cassandra/transport/TlsTestUtils.java   |   2 +-
 10 files changed, 253 insertions(+), 26 deletions(-)

diff --cc CHANGES.txt
index 23a694b0bd,feae546032..0b12096ef1
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -167,6 -60,6 +167,9 @@@ Merged from 4.0
   * Fix rendering UNSET collection types in query tracing (CASSANDRA-19880)
   * Fix latency reported by ideal consistency level monitoring (CASSANDRA-19651)
   * Do not spam log with SSLExceptions (CASSANDRA-18839)
++Merged from 3.0
++  * Tighten up permissions on system keyspaces (CASSANDRA-20090)
++  * Fix incorrect column identifier bytes problem when renaming a column (CASSANDRA-18956)
  
  5.0.0
  5.0-rc2
diff --cc src/java/org/apache/cassandra/schema/SchemaConstants.java
index 9a5e8844b2,70fcef6410..2323893c4b
--- a/src/java/org/apache/cassandra/schema/SchemaConstants.java
+++ b/src/java/org/apache/cassandra/schema/SchemaConstants.java
@@@ -125,6 -121,16 +125,16 @@@ public final class SchemaConstant
                  || isReplicatedSystemKeyspace(keyspaceName);
      }
  
+     /**
+      * @return whether or not the keyspace is a non-virtual, system keyspace
+      */
+     public static boolean isNonVirtualSystemKeyspace(String keyspaceName)
+     {
 -        final String lowercaseKeyspaceName = keyspaceName.toLowerCase();
++        final String lowercaseKeyspaceName = toLowerCaseLocalized(keyspaceName);
+         return LOCAL_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName)
+                || REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName);
+     }
+ 
      /**
       * Returns the set of all system keyspaces
       * @return all system keyspaces
diff --cc test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java
index 2ac0232139,69f988653f..5ddd544615
--- a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java
+++ b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java
@@@ -387,95 -404,110 +403,201 @@@ public class GrantAndRevokeTest extend
          assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted MODIFY on <keyspace revoke_yeah>");
      }
  
 +    @Test
 +    public void testCreateTableLikeAuthorize() throws Throwable
 +    {
 +        useSuperUser();
 +
 +        // two keyspaces
 +        executeNet("CREATE KEYSPACE ks1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
 +        executeNet("CREATE KEYSPACE ks2 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
 +        executeNet("CREATE TABLE ks1.sourcetb (id int PRIMARY KEY, val text)");
 +        executeNet("CREATE USER '" + user + "' WITH PASSWORD '" + pass + "'");
 +
 +        // same keyspace
 +        // have no select permission on source table
 +        ResultSet res = executeNet("REVOKE SELECT ON TABLE ks1.sourcetb FROM " + user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted SELECT on <table ks1.sourcetb>");
 +
 +        useUser(user, pass);
 +        // Spin assert for effective auth changes.
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no SELECT permission on <table ks1.sourcetb> or any of its parents",
-                         formatQuery("SELECT * FROM ks1.sourcetb LIMIT 1"));
++                                        formatQuery("SELECT * FROM ks1.sourcetb LIMIT 1"));
 +            }
-             catch(Throwable e)
++            catch (Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table ks1.sourcetb> or any of its parents",
 +                                "CREATE TABLE ks1.targetTb LIKE ks1.sourcetb");
 +
 +        // have select permission on source table and do not have create permission on target keyspace
 +        useSuperUser();
 +        executeNet("GRANT SELECT ON TABLE ks1.sourcetb TO " + user);
 +        res = executeNet("REVOKE CREATE ON KEYSPACE ks1 FROM " + user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted CREATE on <keyspace ks1>");
 +
 +        useUser(user, pass);
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no CREATE permission on <all tables in ks1> or any of its parents",
-                         formatQuery("CREATE TABLE ks1.targetTb LIKE ks1.sourcetb"));
++                                        formatQuery("CREATE TABLE ks1.targetTb LIKE ks1.sourcetb"));
 +            }
-             catch(Throwable e)
++            catch (Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +
 +        assertUnauthorizedQuery("User user has no CREATE permission on <all tables in ks1> or any of its parents",
 +                                "CREATE TABLE ks1.targetTb LIKE ks1.sourcetb");
 +
 +        // different keyspaces
 +        // have select permission on source table and do not have create permission on target keyspace
 +        useSuperUser();
 +        executeNet("GRANT SELECT ON TABLE ks1.sourcetb TO " + user);
 +        res = executeNet("REVOKE CREATE ON KEYSPACE ks2 FROM " + user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted CREATE on <keyspace ks2>");
 +
 +        useUser(user, pass);
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no CREATE permission on <all tables in ks2> or any of its parents",
-                         formatQuery("CREATE TABLE ks2.targetTb LIKE ks1.sourcetb"));
++                                        formatQuery("CREATE TABLE ks2.targetTb LIKE ks1.sourcetb"));
 +            }
-             catch(Throwable e)
++            catch (Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +
 +        assertUnauthorizedQuery("User user has no CREATE permission on <all tables in ks2> or any of its parents",
 +                                "CREATE TABLE ks2.targetTb LIKE ks1.sourcetb");
 +
 +        // source keyspace and table do not exist
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table ks1.tbnotexist> or any of its parents",
 +                                "CREATE TABLE ks2.targetTb LIKE ks1.tbnotexist");
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table ksnotexists.sourcetb> or any of its parents",
 +                                "CREATE TABLE ks2.targetTb LIKE ksnotexists.sourcetb");
 +        // target keyspace does not exist
 +        assertUnauthorizedQuery("User user has no CREATE permission on <all tables in ksnotexists> or any of its parents",
 +                                "CREATE TABLE ksnotexists.targetTb LIKE ks1.sourcetb");
++    }
++
+     @Test
+     public void testSpecificGrantsOnSystemKeyspaces() throws Throwable
+     {
+         // Granting specific permissions on system keyspaces should not be allowed if those permissions include any from
+         // the denylist Permission.INVALID_FOR_SYSTEM_KEYSPACES. By this definition, GRANT ALL on any system keyspace,
+         // or a table within one, should be rejected.
+         useSuperUser();
+         executeNet("CREATE ROLE '" + user + "'");
+         String responseMsg = "Granting permissions on system keyspaces is strictly limited, this operation is not permitted";
+         for (String keyspace : Iterables.concat(LOCAL_SYSTEM_KEYSPACE_NAMES, REPLICATED_SYSTEM_KEYSPACE_NAMES))
+         {
+             assertUnauthorizedQuery(responseMsg, format("GRANT ALL PERMISSIONS ON KEYSPACE %s TO %s", keyspace, user));
+             DataResource keyspaceResource = DataResource.keyspace(keyspace);
+             for (Permission p : keyspaceResource.applicablePermissions())
+                 maybeRejectGrant(p, responseMsg, format("GRANT %s ON KEYSPACE %s TO %s", p.name(), keyspace, user));
+ 
+             assertUnauthorizedQuery(responseMsg, format("GRANT ALL PERMISSIONS ON ALL TABLES IN KEYSPACE %s TO %s", keyspace, user));
+             for (TableMetadata table : Schema.instance.getKeyspaceMetadata(keyspace).tables)
+             {
+                 DataResource tableResource = DataResource.table(keyspace, table.name);
+                 assertUnauthorizedQuery(responseMsg, format("GRANT ALL PERMISSIONS ON %s TO %s", table, user));
+                 for (Permission p : tableResource.applicablePermissions())
+                     maybeRejectGrant(p, responseMsg, format("GRANT %s ON %s TO %s", p.name(), table, user));
+             }
+         }
+     }
  
+     @Test
+     public void testGrantOnAllKeyspaces() throws Throwable
+     {
+         // Granting either specific or ALL permissions on ALL KEYSPACES is allowed, however these permissions are
+         // effective for non-system keyspaces only. If for any reason it is necessary to modify permissions on
+         // on a system keyspace, it must be done using keyspace specific grant statements.
+         useSuperUser();
+         executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND password='%s'", user, pass));
+         executeNet(String.format("ALTER KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", SchemaConstants.TRACE_KEYSPACE_NAME));
+         executeNet("CREATE KEYSPACE user_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
+         executeNet("CREATE TABLE user_keyspace.t1 (k int PRIMARY KEY)");
+         useUser(user, pass);
+ 
+         assertUnauthorizedQuery("User user has no MODIFY permission on <table user_keyspace.t1> or any of its parents",
+                                 "INSERT INTO user_keyspace.t1 (k) VALUES (0)");
+         assertUnauthorizedQuery("User user has no MODIFY permission on <table system.local> or any of its parents",
+                                 "INSERT INTO system.local(key) VALUES ('invalid')");
+ 
+         useSuperUser();
+         executeNet(ProtocolVersion.CURRENT, format("GRANT MODIFY ON ALL KEYSPACES TO %s", user));
+ 
+         useUser(user, pass);
+         // User now has write permission on non-system keyspaces only
+         executeNet(ProtocolVersion.CURRENT, "INSERT INTO user_keyspace.t1 (k) VALUES (0)");
+         assertUnauthorizedQuery("User user has no MODIFY permission on <table system.local> or any of its parents",
+                                 "INSERT INTO system.local(key) VALUES ('invalid')");
+ 
+         // A non-superuser only has read access to a pre-defined set of system tables and all system_schema/traces
+         // tables and granting ALL permissions on ALL keyspaces also does not affect this.
+         maybeReadSystemTables(false);
+         useSuperUser();
+         executeNet(ProtocolVersion.CURRENT, format("GRANT ALL PERMISSIONS ON ALL KEYSPACES TO %s", user));
+         maybeReadSystemTables(false);
+ 
+         // A superuser can still read system tables
+         useSuperUser();
+         maybeReadSystemTables(true);
+         // and also write to them, though this is still strongly discouraged
+         executeNet(ProtocolVersion.CURRENT, "INSERT INTO system.peers_v2(peer, peer_port, data_center) VALUES ('127.0.100.100', 7012, 'invalid_dc')");
+     }
+ 
+     private void maybeReadSystemTables(boolean superuser) throws Throwable
+     {
+         if (superuser)
+             useSuperUser();
+         else
+             useUser(user, pass);
+ 
+         Set<String> readableKeyspaces = new HashSet<>(Arrays.asList(SCHEMA_KEYSPACE_NAME, TRACE_KEYSPACE_NAME));
+         Set<String> readableSystemTables = new HashSet<>(Arrays.asList(SystemKeyspace.LOCAL,
+                                                                        SystemKeyspace.PEERS_V2,
+                                                                        SystemKeyspace.LEGACY_PEERS,
+                                                                        SystemKeyspace.LEGACY_SIZE_ESTIMATES,
+                                                                        SystemKeyspace.TABLE_ESTIMATES));
+ 
+         for (String keyspace : Iterables.concat(LOCAL_SYSTEM_KEYSPACE_NAMES, REPLICATED_SYSTEM_KEYSPACE_NAMES))
+         {
+             for (TableMetadata table : Schema.instance.getKeyspaceMetadata(keyspace).tables)
+             {
+                 if (superuser || (readableKeyspaces.contains(keyspace) || (keyspace.equals(SYSTEM_KEYSPACE_NAME) && readableSystemTables.contains(table.name))))
+                 {
+                     executeNet(ProtocolVersion.CURRENT, ConsistencyLevel.ONE, format("SELECT * FROM %s LIMIT 1", table));
+                 }
+                 else
+                 {
+                     assertUnauthorizedQuery(format("User %s has no SELECT permission on %s or any of its parents", user, table.resource),
+                                             format("SELECT * FROM %s LIMIT 1", table));
+                 }
+             }
+         }
+     }
+ 
+     private void maybeRejectGrant(Permission p, String errorResponse, String grant) throws Throwable
+     {
+         if (Permission.INVALID_FOR_SYSTEM_KEYSPACES.contains(p))
+             assertUnauthorizedQuery(errorResponse, grant);
+         else
+             executeNet(ProtocolVersion.CURRENT, grant);
      }
  }
diff --cc test/unit/org/apache/cassandra/service/ClientStateTest.java
index f037eb2c35,b6df9c7cba..349b649ed1
--- a/test/unit/org/apache/cassandra/service/ClientStateTest.java
+++ b/test/unit/org/apache/cassandra/service/ClientStateTest.java
@@@ -50,6 -55,12 +50,8 @@@ public class ClientStateTes
          properties = new WithProperties().set(ORG_APACHE_CASSANDRA_DISABLE_MBEAN_REGISTRATION, true);
          SchemaLoader.prepareServer();
          DatabaseDescriptor.setAuthFromRoot(true);
 -        // create the system_auth keyspace so the IRoleManager can function as normal
 -        SchemaLoader.createKeyspace(SchemaConstants.AUTH_KEYSPACE_NAME,
 -                                    KeyspaceParams.simple(1),
 -                                    Iterables.toArray(AuthKeyspace.metadata().tables, TableMetadata.class));
+         DatabaseDescriptor.setRoleManager(new AuthTestUtils.LocalCassandraRoleManager());
+         DatabaseDescriptor.getRoleManager().setup();
          Roles.init();
          AuthCacheService.initializeAndRegisterCaches();
      }
diff --cc test/unit/org/apache/cassandra/transport/TlsTestUtils.java
index 24f3124f86,0000000000..f3994e7613
mode 100644,000000..100644
--- a/test/unit/org/apache/cassandra/transport/TlsTestUtils.java
+++ b/test/unit/org/apache/cassandra/transport/TlsTestUtils.java
@@@ -1,222 -1,0 +1,222 @@@
 +/*
 + * 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.transport;
 +
 +import java.io.File;
 +import java.net.InetAddress;
 +import java.net.InetSocketAddress;
 +import java.nio.file.Path;
 +import java.time.Instant;
 +import java.time.temporal.ChronoUnit;
 +import java.util.Collections;
 +import java.util.Map;
 +import java.util.function.Consumer;
 +import java.util.function.Function;
 +import javax.net.ssl.SSLException;
 +
 +import com.google.common.collect.ImmutableMap;
 +
 +import com.datastax.driver.core.PlainTextAuthProvider;
 +import com.datastax.driver.core.RemoteEndpointAwareJdkSSLOptions;
 +import com.datastax.driver.core.SSLOptions;
 +import com.datastax.driver.core.Session;
 +import com.datastax.driver.core.policies.LoadBalancingPolicy;
 +import org.apache.cassandra.config.Config;
 +import org.apache.cassandra.config.EncryptionOptions;
 +import org.apache.cassandra.config.ParameterizedClass;
 +import org.apache.cassandra.distributed.api.ICluster;
 +import org.apache.cassandra.distributed.api.IInvokableInstance;
 +import org.apache.cassandra.distributed.shared.ClusterUtils;
 +import org.apache.cassandra.distributed.util.Auth;
 +import org.apache.cassandra.distributed.util.SingleHostLoadBalancingPolicy;
 +import org.apache.cassandra.security.ISslContextFactory;
 +import org.apache.cassandra.utils.tls.CertificateBuilder;
 +import org.apache.cassandra.utils.tls.CertificateBundle;
 +
 +import static org.apache.cassandra.auth.CassandraRoleManager.DEFAULT_SUPERUSER_NAME;
 +import static org.apache.cassandra.auth.CassandraRoleManager.DEFAULT_SUPERUSER_PASSWORD;
 +
 +public class TlsTestUtils
 +{
 +    public static String SERVER_KEYSTORE_PATH = "test/conf/cassandra_ssl_test.keystore";
 +    public static String SERVER_KEYSTORE_PATH_PEM = "test/conf/cassandra_ssl_test.keystore.pem";
 +    public static String SERVER_KEYSTORE_PATH_UNENCRYPTED_PEM = "test/conf/cassandra_ssl_test.unencrypted_keystore.pem";
 +    public static String SERVER_KEYSTORE_PASSWORD = "cassandra";
 +
 +    public static String SERVER_KEYSTORE_ENDPOINT_VERIFY_PATH = "test/conf/cassandra_ssl_test_endpoint_verify.keystore";
 +    public static String SERVER_KEYSTORE_ENDPOINT_VERIFY_PASSWORD = "cassandra";
 +
 +    public static String SERVER_OUTBOUND_KEYSTORE_PATH = "test/conf/cassandra_ssl_test_outbound.keystore";
 +    public static String SERVER_OUTBOUND_KEYSTORE_PASSWORD = "cassandra";
 +
 +    public static String SERVER_TRUSTSTORE_PATH = "test/conf/cassandra_ssl_test.truststore";
 +    public static String SERVER_TRUSTSTORE_PEM_PATH = "test/conf/cassandra_ssl_test.truststore.pem";
 +    public static String SERVER_TRUSTSTORE_PASSWORD = "cassandra";
 +
 +    // To regenerate:
 +    // 1. generate keystore
 +    //    keytool -genkeypair -keystore test/conf/cassandra_ssl_test_spiffe.keystore -validity 100000 -keyalg RSA -dname "CN=Apache Cassandra, OU=ssl_test, O=Unknown, L=Unknown, ST=Unknown, C=Unknown" -keypass cassandra -storepass cassandra -alias spiffecert -ext SAN=URI:spiffe://test.cassandra.apache.org/unitTest/mtls -storetype jks
 +    // 2. export cert
 +    //    keytool -export -alias spiffecert -file spiffecert.cer -keystore test/conf/cassandra_ssl_test_spiffe.keystore
 +    // 3. import cert into truststore
 +    //    keytool -import -v -trustcacerts -alias spiffecert -file spiffecert.cer -keystore test/conf/cassandra_ssl_test.truststore
 +    public static String CLIENT_SPIFFE_KEYSTORE_PATH = "test/conf/cassandra_ssl_test_spiffe.keystore";
 +    public static String CLIENT_SPIFFE_KEYSTORE_PASSWORD = "cassandra";
 +    public static String CLIENT_SPIFFE_IDENTITY = "spiffe://test.cassandra.apache.org/unitTest/mtls";
 +
 +    public static String CLIENT_TRUSTSTORE_PATH = "test/conf/cassandra_ssl_test.truststore";
 +    public static String CLIENT_TRUSTSTORE_PASSWORD = "cassandra";
 +
 +    public static EncryptionOptions getClientEncryptionOptions()
 +    {
 +        return new EncryptionOptions(new EncryptionOptions()
 +                              .withEnabled(true)
 +                              .withRequireClientAuth(EncryptionOptions.ClientAuth.OPTIONAL)
 +                              .withOptional(true)
 +                              .withKeyStore(SERVER_KEYSTORE_PATH)
 +                              .withKeyStorePassword(SERVER_KEYSTORE_PASSWORD)
 +                              .withTrustStore(SERVER_TRUSTSTORE_PATH)
 +                              .withTrustStorePassword(SERVER_TRUSTSTORE_PASSWORD)
 +                              .withRequireEndpointVerification(false));
 +    }
 +
 +    public static void configureWithMutualTlsWithPasswordFallbackAuthenticator(Config config)
 +    {
 +        // Configure an authenticator that supports multiple authentication mechanisms.
 +        Map<String, String> parameters = Collections.singletonMap("validator_class_name", "org.apache.cassandra.auth.SpiffeCertificateValidator");
 +        config.authenticator = new ParameterizedClass("MutualTlsWithPasswordFallbackAuthenticator", parameters);
 +        // Configure client encryption such that we can optionally connect with SSL.
 +        config.client_encryption_options = TlsTestUtils.getClientEncryptionOptions();
 +        config.role_manager = new ParameterizedClass("CassandraRoleManager");
 +        config.authorizer = new ParameterizedClass("CassandraAuthorizer");
 +    }
 +
 +    public static ISslContextFactory getClientSslContextFactory(boolean provideClientCert)
 +    {
 +        ImmutableMap.Builder<String, Object> params = ImmutableMap.<String, Object>builder()
 +                                                                  .put("truststore", CLIENT_TRUSTSTORE_PATH)
 +                                                                  .put("truststore_password", CLIENT_TRUSTSTORE_PASSWORD);
 +
 +        if (provideClientCert)
 +        {
 +            params.put("keystore", CLIENT_SPIFFE_KEYSTORE_PATH)
 +                  .put("keystore_password", CLIENT_SPIFFE_KEYSTORE_PASSWORD);
 +        }
 +
 +        return new SimpleClientSslContextFactory(params.build());
 +    }
 +
 +    public static SSLOptions getSSLOptions(boolean provideClientCert) throws SSLException
 +    {
 +        return RemoteEndpointAwareJdkSSLOptions.builder()
 +                                               .withSSLContext(getClientSslContextFactory(provideClientCert)
 +                                                               .createJSSESslContext(EncryptionOptions.ClientAuth.OPTIONAL))
 +                                               .build();
 +    }
 +
 +    public static SSLOptions getSSLOptions(Path keystorePath, Path truststorePath) throws RuntimeException
 +    {
 +        try
 +        {
 +            return RemoteEndpointAwareJdkSSLOptions.builder()
 +                                                   .withSSLContext(getClientSslContextFactory(keystorePath, truststorePath)
 +                                                                   .createJSSESslContext(EncryptionOptions.ClientAuth.OPTIONAL))
 +                                                   .build();
 +        }
 +        catch (SSLException e)
 +        {
 +            throw new RuntimeException(e);
 +        }
 +    }
 +
 +    private static ISslContextFactory getClientSslContextFactory(Path keystorePath, Path truststorePath)
 +    {
 +        ImmutableMap.Builder<String, Object> params = ImmutableMap.<String, Object>builder()
 +                                                                  .put("truststore", truststorePath.toString())
 +                                                                  .put("truststore_password", CLIENT_TRUSTSTORE_PASSWORD);
 +
 +        if (keystorePath != null)
 +        {
 +            params.put("keystore", keystorePath.toString())
 +                  .put("keystore_password", "cassandra");
 +        }
 +
 +        return new SimpleClientSslContextFactory(params.build());
 +    }
 +
 +    public static void configureIdentity(ICluster<IInvokableInstance> cluster, SSLOptions sslOptions)
 +    {
 +        withAuthenticatedSession(cluster.get(1), DEFAULT_SUPERUSER_NAME, DEFAULT_SUPERUSER_PASSWORD, session -> {
 +            session.execute("CREATE ROLE cassandra_ssl_test WITH LOGIN = true");
 +            session.execute(String.format("ADD IDENTITY '%s' TO ROLE 'cassandra_ssl_test'", CLIENT_SPIFFE_IDENTITY));
 +            // GRANT select to cassandra_ssl_test to be able to query the system_views.clients virtual table
-             session.execute("GRANT SELECT ON ALL KEYSPACES to cassandra_ssl_test");
++            session.execute("GRANT SELECT ON system_views.clients to cassandra_ssl_test");
 +        }, sslOptions);
 +    }
 +
 +    public static Path generateSelfSignedCertificate(Function<CertificateBuilder, CertificateBuilder> customizeCertificate, File targetDirectory) throws Exception
 +    {
 +        return generateClientCertificate(customizeCertificate, targetDirectory, null);
 +    }
 +
 +    public static Path generateClientCertificate(Function<CertificateBuilder, CertificateBuilder> customizeCertificate, File targetDirectory, CertificateBundle ca) throws Exception
 +    {
 +        CertificateBuilder builder = new CertificateBuilder().subject("CN=Apache Cassandra, OU=ssl_test, O=Unknown, L=Unknown, ST=Unknown, C=Unknown")
 +                                                             .notBefore(Instant.now().minus(1, ChronoUnit.DAYS))
 +                                                             .notAfter(Instant.now().plus(1, ChronoUnit.DAYS))
 +                                                             .alias("spiffecert")
 +                                                             .addSanUriName(CLIENT_SPIFFE_IDENTITY)
 +                                                             .rsa2048Algorithm();
 +        if (customizeCertificate != null)
 +        {
 +            builder = customizeCertificate.apply(builder);
 +        }
 +        CertificateBundle ssc = ca != null
 +                                ? builder.buildIssuedBy(ca)
 +                                : builder.buildSelfSigned();
 +        return ssc.toTempKeyStorePath(targetDirectory.toPath(), SERVER_KEYSTORE_PASSWORD.toCharArray(), SERVER_KEYSTORE_PASSWORD.toCharArray());
 +    }
 +
 +    public static void withAuthenticatedSession(IInvokableInstance instance,
 +                                         String username,
 +                                         String password,
 +                                         Consumer<Session> consumer,
 +                                         SSLOptions sslOptions)
 +    {
 +        // wait for existing roles
 +        Auth.waitForExistingRoles(instance);
 +
 +        InetSocketAddress nativeInetSocketAddress = ClusterUtils.getNativeInetSocketAddress(instance);
 +        InetAddress address = nativeInetSocketAddress.getAddress();
 +        LoadBalancingPolicy lbc = new SingleHostLoadBalancingPolicy(address);
 +
 +        com.datastax.driver.core.Cluster.Builder builder = com.datastax.driver.core.Cluster.builder()
 +                                                                                           .withLoadBalancingPolicy(lbc)
 +                                                                                           .withSSL(sslOptions)
 +                                                                                           .withAuthProvider(new PlainTextAuthProvider(username, password))
 +                                                                                           .addContactPoint(address.getHostAddress())
 +                                                                                           .withPort(nativeInetSocketAddress.getPort());
 +
 +        try (com.datastax.driver.core.Cluster c = builder.build(); Session session = c.connect())
 +        {
 +            consumer.accept(session);
 +        }
 +    }
 +
 +}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org