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 <s...@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-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to