def test_idle_heartbeat(self): interval = 1 cluster = Cluster(protocol_version=PROTOCOL_VERSION, idle_heartbeat_interval=interval) if PROTOCOL_VERSION < 3: cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) session = cluster.connect() # This test relies on impl details of connection req id management to see if heartbeats # are being sent. May need update if impl is changed connection_request_ids = {} for h in cluster.get_connection_holders(): for c in h.get_connections(): # make sure none are idle (should have startup messages) self.assertFalse(c.is_idle) with c.lock: connection_request_ids[id(c)] = deque(c.request_ids) # copy of request ids # let two heatbeat intervals pass (first one had startup messages in it) time.sleep(2 * interval + interval/10.) connections = [c for holders in cluster.get_connection_holders() for c in holders.get_connections()] # make sure requests were sent on all connections for c in connections: expected_ids = connection_request_ids[id(c)] expected_ids.rotate(-1) with c.lock: self.assertListEqual(list(c.request_ids), list(expected_ids)) # assert idle status self.assertTrue(all(c.is_idle for c in connections)) # send messages on all connections statements_and_params = [("SELECT release_version FROM system.local", ())] * len(cluster.metadata.all_hosts()) results = execute_concurrent(session, statements_and_params) for success, result in results: self.assertTrue(success) # assert not idle status self.assertFalse(any(c.is_idle if not c.is_control_connection else False for c in connections)) # holders include session pools and cc holders = cluster.get_connection_holders() self.assertIn(cluster.control_connection, holders) self.assertEqual(len(holders), len(cluster.metadata.all_hosts()) + 1) # hosts pools, 1 for cc # include additional sessions session2 = cluster.connect() holders = cluster.get_connection_holders() self.assertIn(cluster.control_connection, holders) self.assertEqual(len(holders), 2 * len(cluster.metadata.all_hosts()) + 1) # 2 sessions' hosts pools, 1 for cc cluster._idle_heartbeat.stop() cluster._idle_heartbeat.join() assert_quiescent_pool_state(self, cluster) cluster.shutdown()
def test_idle_heartbeat_disabled(self): self.assertTrue(Cluster.idle_heartbeat_interval) # heartbeat disabled with '0' cluster = Cluster(protocol_version=PROTOCOL_VERSION, idle_heartbeat_interval=0) self.assertEqual(cluster.idle_heartbeat_interval, 0) session = cluster.connect() # let two heatbeat intervals pass (first one had startup messages in it) time.sleep(2 * Cluster.idle_heartbeat_interval) connections = [c for holders in cluster.get_connection_holders() for c in holders.get_connections()] # assert not idle status (should never get reset because there is not heartbeat) self.assertFalse(any(c.is_idle for c in connections)) cluster.shutdown()
class BasicDseAuthTest(unittest.TestCase): @classmethod def setUpClass(self): """ This will setup the necessary infrastructure to run our authentication tests. It requres the ADS_HOME environment variable and our custom embedded apache directory server jar in order to run. """ if not DSE_VERSION: return clear_kerberos_tickets() self.cluster = None # Setup variables for various keytab and other files self.conf_file_dir = os.path.join(ADS_HOME, "conf/") self.krb_conf = os.path.join(self.conf_file_dir, "krb5.conf") self.dse_keytab = os.path.join(self.conf_file_dir, "dse.keytab") self.dseuser_keytab = os.path.join(self.conf_file_dir, "dseuser.keytab") self.cassandra_keytab = os.path.join(self.conf_file_dir, "cassandra.keytab") self.bob_keytab = os.path.join(self.conf_file_dir, "bob.keytab") self.charlie_keytab = os.path.join(self.conf_file_dir, "charlie.keytab") actual_jar = os.path.join(ADS_HOME, "embedded-ads.jar") # Create configuration directories if they don't already exists if not os.path.exists(self.conf_file_dir): os.makedirs(self.conf_file_dir) if not os.path.exists(actual_jar): raise RuntimeError('could not find {}'.format(actual_jar)) log.warning("Starting adserver") # Start the ADS, this will create the keytab con configuration files listed above self.proc = subprocess.Popen([ 'java', '-jar', actual_jar, '-k', '--confdir', self.conf_file_dir ], shell=False) time.sleep(10) # TODO poll for server to come up log.warning("Starting adserver started") ccm_cluster = get_cluster() log.warning("fetching tickets") # Stop cluster if running and configure it with the correct options ccm_cluster.stop() if isinstance(ccm_cluster, DseCluster): # Setup kerberos options in cassandra.yaml config_options = { 'kerberos_options': { 'keytab': self.dse_keytab, 'service_principal': 'dse/[email protected]', 'qop': 'auth' }, 'authentication_options': { 'enabled': 'true', 'default_scheme': 'kerberos', 'scheme_permissions': 'true', 'allow_digest_with_kerberos': 'true', 'plain_text_without_ssl': 'warn', 'transitional_mode': 'disabled' }, 'authorization_options': { 'enabled': 'true' } } krb5java = "-Djava.security.krb5.conf=" + self.krb_conf # Setup dse authenticator in cassandra.yaml ccm_cluster.set_configuration_options({ 'authenticator': 'com.datastax.bdp.cassandra.auth.DseAuthenticator', 'authorizer': 'com.datastax.bdp.cassandra.auth.DseAuthorizer' }) ccm_cluster.set_dse_configuration_options(config_options) ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True, jvm_args=[krb5java]) else: log.error("Cluster is not dse cluster test will fail") @classmethod def tearDownClass(self): """ Terminates running ADS (Apache directory server). """ if not DSE_VERSION: return self.proc.terminate() def tearDown(self): """ This will clear any existing kerberos tickets by using kdestroy """ clear_kerberos_tickets() if self.cluster: self.cluster.shutdown() def refresh_kerberos_tickets(self, keytab_file, user_name, krb_conf): """ Fetches a new ticket for using the keytab file and username provided. """ self.ads_pid = subprocess.call(['kinit', '-t', keytab_file, user_name], env={'KRB5_CONFIG': krb_conf}, shell=False) def connect_and_query(self, auth_provider, query=None): """ Runs a simple system query with the auth_provided specified. """ os.environ['KRB5_CONFIG'] = self.krb_conf self.cluster = Cluster(auth_provider=auth_provider) self.session = self.cluster.connect() query = query if query else "SELECT * FROM system.local" statement = SimpleStatement(query) rs = self.session.execute(statement) return rs def test_should_not_authenticate_with_bad_user_ticket(self): """ This tests will attempt to authenticate with a user that has a valid ticket, but is not a valid dse user. @since 3.20 @jira_ticket PYTHON-457 @test_category dse auth @expected_result NoHostAvailable exception should be thrown """ self.refresh_kerberos_tickets(self.dseuser_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider) def test_should_not_athenticate_without_ticket(self): """ This tests will attempt to authenticate with a user that is valid but has no ticket @since 3.20 @jira_ticket PYTHON-457 @test_category dse auth @expected_result NoHostAvailable exception should be thrown """ auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider) def test_connect_with_kerberos(self): """ This tests will attempt to authenticate with a user that is valid and has a ticket @since 3.20 @jira_ticket PYTHON-457 @test_category dse auth @expected_result Client should be able to connect and run a basic query """ self.refresh_kerberos_tickets(self.cassandra_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider() rs = self.connect_and_query(auth_provider) self.assertIsNotNone(rs) connections = [ c for holders in self.cluster.get_connection_holders() for c in holders.get_connections() ] # Check to make sure our server_authenticator class is being set appropriate for connection in connections: self.assertTrue('DseAuthenticator' in connection.authenticator. server_authenticator_class) def test_connect_with_kerberos_and_graph(self): """ This tests will attempt to authenticate with a user and execute a graph query @since 3.20 @jira_ticket PYTHON-457 @test_category dse auth @expected_result Client should be able to connect and run a basic graph query with authentication """ self.refresh_kerberos_tickets(self.cassandra_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) rs = self.connect_and_query(auth_provider) self.assertIsNotNone(rs) reset_graph(self.session, self._testMethodName.lower()) profiles = self.cluster.profile_manager.profiles profiles[ EXEC_PROFILE_GRAPH_DEFAULT].graph_options.graph_name = self._testMethodName.lower( ) self.session.execute_graph(ClassicGraphFixtures.classic()) rs = self.session.execute_graph('g.V()') self.assertIsNotNone(rs) def test_connect_with_kerberos_host_not_resolved(self): """ This tests will attempt to authenticate with IP, this will fail on osx. The success or failure of this test is dependent on a reverse dns lookup which can be impacted by your environment if it fails don't panic. @since 3.20 @jira_ticket PYTHON-566 @test_category dse auth @expected_result Client should error when ip is used """ self.refresh_kerberos_tickets(self.cassandra_keytab, "*****@*****.**", self.krb_conf) DSEGSSAPIAuthProvider(service='dse', qops=["auth"], resolve_host_name=False) def test_connect_with_explicit_principal(self): """ This tests will attempt to authenticate using valid and invalid user principals @since 3.20 @jira_ticket PYTHON-574 @test_category dse auth @expected_result Client principals should be used by the underlying mechanism """ # Connect with valid principal self.refresh_kerberos_tickets(self.cassandra_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider( service='dse', qops=["auth"], principal="*****@*****.**") self.connect_and_query(auth_provider) connections = [ c for holders in self.cluster.get_connection_holders() for c in holders.get_connections() ] # Check to make sure our server_authenticator class is being set appropriate for connection in connections: self.assertTrue('DseAuthenticator' in connection.authenticator. server_authenticator_class) # Use invalid principal auth_provider = DSEGSSAPIAuthProvider( service='dse', qops=["auth"], principal="*****@*****.**") self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider) @greaterthanorequaldse51 def test_proxy_login_with_kerberos(self): """ Test that the proxy login works with kerberos. """ # Set up users for proxy login test self._setup_for_proxy() query = "select * from testkrbproxy.testproxy" # Try normal login with Charlie self.refresh_kerberos_tickets(self.charlie_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="*****@*****.**") self.connect_and_query(auth_provider, query=query) # Try proxy login with bob self.refresh_kerberos_tickets(self.bob_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider( service='dse', qops=["auth"], principal="*****@*****.**", authorization_id='*****@*****.**') self.connect_and_query(auth_provider, query=query) # Try logging with bob without mentioning charlie self.refresh_kerberos_tickets(self.bob_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="*****@*****.**") self.assertRaises(Unauthorized, self.connect_and_query, auth_provider, query=query) self._remove_proxy_setup() @greaterthanorequaldse51 def test_proxy_login_with_kerberos_forbidden(self): """ Test that the proxy login fail when proxy role is not granted """ # Set up users for proxy login test self._setup_for_proxy(False) query = "select * from testkrbproxy.testproxy" # Try normal login with Charlie self.refresh_kerberos_tickets(self.bob_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider( service='dse', qops=["auth"], principal="*****@*****.**", authorization_id='*****@*****.**') self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider, query=query) self.refresh_kerberos_tickets(self.bob_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="*****@*****.**") self.assertRaises(Unauthorized, self.connect_and_query, auth_provider, query=query) self._remove_proxy_setup() def _remove_proxy_setup(self): os.environ['KRB5_CONFIG'] = self.krb_conf self.refresh_kerberos_tickets(self.cassandra_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider( service='dse', qops=["auth"], principal='*****@*****.**') cluster = Cluster(auth_provider=auth_provider) session = cluster.connect() session.execute("REVOKE PROXY.LOGIN ON ROLE '{0}' FROM '{1}'".format( '*****@*****.**', '*****@*****.**')) session.execute( "DROP ROLE IF EXISTS '{0}';".format('*****@*****.**')) session.execute( "DROP ROLE IF EXISTS '{0}';".format('*****@*****.**')) # Create a keyspace and allow only charlie to query it. session.execute("DROP KEYSPACE testkrbproxy") cluster.shutdown() def _setup_for_proxy(self, grant=True): os.environ['KRB5_CONFIG'] = self.krb_conf self.refresh_kerberos_tickets(self.cassandra_keytab, "*****@*****.**", self.krb_conf) auth_provider = DSEGSSAPIAuthProvider( service='dse', qops=["auth"], principal='*****@*****.**') cluster = Cluster(auth_provider=auth_provider) session = cluster.connect() stmts = [ "CREATE ROLE IF NOT EXISTS '{0}' WITH LOGIN = TRUE;".format( '*****@*****.**'), "CREATE ROLE IF NOT EXISTS '{0}' WITH LOGIN = TRUE;".format( '*****@*****.**'), "GRANT EXECUTE ON ALL AUTHENTICATION SCHEMES to '*****@*****.**'", "CREATE ROLE IF NOT EXISTS '{0}' WITH LOGIN = TRUE;".format( '*****@*****.**'), "GRANT EXECUTE ON ALL AUTHENTICATION SCHEMES to '*****@*****.**'", # Create a keyspace and allow only charlie to query it. "CREATE KEYSPACE testkrbproxy WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", "CREATE TABLE testkrbproxy.testproxy (id int PRIMARY KEY, value text)", "GRANT ALL PERMISSIONS ON KEYSPACE testkrbproxy to '{0}'".format( '*****@*****.**'), ] if grant: stmts.append("GRANT PROXY.LOGIN ON ROLE '{0}' to '{1}'".format( '*****@*****.**', '*****@*****.**')) wait_role_manager_setup_then_execute(session, stmts) cluster.shutdown()