def federated_ursulas(ursula_federated_test_config): if MOCK_KNOWN_URSULAS_CACHE: raise RuntimeError( "Ursulas cache was unclear at fixture loading time. " "Did you use one of the ursula maker functions without cleaning up?" ) # MOCK_KNOWN_URSULAS_CACHE.clear() _ursulas = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK) # Since we mutate this list in some tests, it's not enough to remember and remove the Ursulas; we have to remember them by port. # The same is true of blockchain_ursulas below. _ports_to_remove = [ursula.rest_interface.port for ursula in _ursulas] yield _ursulas for port in _ports_to_remove: if port in MOCK_KNOWN_URSULAS_CACHE: test_logger.debug( f"Removing {port} ({MOCK_KNOWN_URSULAS_CACHE[port]}).") del MOCK_KNOWN_URSULAS_CACHE[port] for u in _ursulas: u.stop() u._finalize() # Pytest will hold on to this object, need to clear it manually. # See https://github.com/pytest-dev/pytest/issues/5642 _ursulas.clear()
def test_hendrix_handles_content_length_validation(ursula_federated_test_config): node = make_federated_ursulas(ursula_config=ursula_federated_test_config, quantity=1).pop() node_deployer = node.get_deployer() node_deployer.addServices() node_deployer.catalogServers(node_deployer.hendrix) node_deployer.start() def check_node_rejects_large_posts(node): too_much_data = os.urandom(100 * 1024) response = requests.post( "https://{}/consider_arrangement".format(node.rest_url()), data=too_much_data, verify=False) assert response.status_code > 400 assert response.reason == "Request Entity Too Large" return node def check_node_accepts_normal_posts(node): a_normal_arrangement = os.urandom(49 * 1024) # 49K, the limit is 50K response = requests.post( "https://{}/consider_arrangement".format(node.rest_url()), data=a_normal_arrangement, verify=False) assert response.status_code >= 500 # it still fails because we are sending random bytes assert response.reason != "Request Entity Too Large" # but now we are running nucypher code return node yield threads.deferToThread(check_node_rejects_large_posts, node) yield threads.deferToThread(check_node_accepts_normal_posts, node)
def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request): # good_serials = _determine_good_serials(10000, 50000) try: quantity = request.param except AttributeError: quantity = 5000 # Bigass fleet by default; that's kinda the point. with GlobalLoggerSettings.pause_all_logging_while(): with mock_secret_source(): with mock_cert_storage, mock_cert_loading, mock_rest_app_creation, mock_cert_generation, mock_remember_node, mock_message_verification: _ursulas = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=quantity, know_each_other=False) all_ursulas = {u.checksum_address: u for u in _ursulas} for ursula in _ursulas: # FIXME #2588: FleetSensor should not own fully-functional Ursulas. # It only needs to see whatever public info we can normally get via REST. # Also sharing mutable Ursulas like that can lead to unpredictable results. ursula.known_nodes.current_state._nodes = all_ursulas ursula.known_nodes.current_state.checksum = b"This is a fleet state checksum..".hex( ) yield _ursulas for ursula in _ursulas: del MOCK_KNOWN_URSULAS_CACHE[ursula.rest_interface.port]
def test_federated_nodes_connect_via_tls_and_verify( ursula_federated_test_config): node = make_federated_ursulas(ursula_config=ursula_federated_test_config, quantity=1).pop() node_deployer = node.get_deployer() node_deployer.addServices() node_deployer.catalogServers(node_deployer.hendrix) node_deployer.start() cert = node_deployer.cert.to_cryptography() cert_bytes = cert.public_bytes(serialization.Encoding.PEM) def check_node_with_cert(node, cert_file): response = requests.get("https://{}/public_information".format( node.rest_url()), verify=cert_file) ursula = Ursula.from_bytes(response.content) assert ursula == node try: with open("test-cert", "wb") as f: f.write(cert_bytes) yield threads.deferToThread(check_node_with_cert, node, "test-cert") finally: os.remove("test-cert")
def test_one_node_stores_a_bunch_of_others(federated_ursulas, ursula_federated_test_config): the_chosen_seednode = list(federated_ursulas)[2] # ...neo? seed_node = the_chosen_seednode.seed_node_metadata() newcomer = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=1, know_each_other=False, save_metadata=True, seed_nodes=[seed_node]).pop() assert not newcomer.known_nodes newcomer.start_learning_loop(now=True) def start_lonely_learning_loop(): newcomer.start_learning_loop() start = maya.now() # Loop until the_chosen_seednode is in storage. while the_chosen_seednode not in newcomer.node_storage.all(federated_only=True): passed = maya.now() - start if passed.seconds > 2: pytest.fail("Didn't find the seed node.") yield deferToThread(start_lonely_learning_loop) assert list(newcomer.known_nodes) assert len(list(newcomer.known_nodes)) == len(list(newcomer.node_storage.all(True))) assert set(list(newcomer.known_nodes)) == set(list(newcomer.node_storage.all(True)))
def test_ursula_serves_statics(ursula_federated_test_config): with tempfile.TemporaryDirectory() as STATICS_DIR: os.environ['NUCYPHER_STATIC_FILES_ROOT'] = STATICS_DIR node = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=1).pop() node_deployer = node.get_deployer() node_deployer.addServices() node_deployer.catalogServers(node_deployer.hendrix) node_deployer.start() cert = node_deployer.cert.to_cryptography() cert_bytes = cert.public_bytes(serialization.Encoding.PEM) def check_static_service(node, cert_file): response = requests.get( "https://{}/statics/test-never-make-a-file-with-this-name.js". format(node.rest_url()), verify=cert_file) assert response.status_code == 200 assert "I am Javascript" in response.text assert response.headers['Content-Type'] == 'application/javascript' return node def check_static_file_not_there(node, cert_file): response = requests.get( "https://{}/statics/no-file-by-this-name.js".format( node.rest_url()), verify=cert_file) assert response.status_code == 404 return node try: with open("test-cert", "wb") as f: f.write(cert_bytes) Path(STATICS_DIR).mkdir(exist_ok=True) with open( Path(STATICS_DIR, 'test-never-make-a-file-with-this-name.js'), 'w+') as fout: fout.write("console.log('I am Javascript')\n") fout.close() yield threads.deferToThread(check_static_service, node, "test-cert") yield threads.deferToThread(check_static_file_not_there, node, "test-cert") finally: Path("test-cert").unlink()
def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request): try: quantity = request.param except AttributeError: quantity = 5000 # Bigass fleet by default; that's kinda the point. with GlobalLoggerSettings.pause_all_logging_while(): with mock_secret_source(): with mock_cert_storage, mock_cert_loading, mock_rest_app_creation, mock_cert_generation, mock_remember_node, mock_message_verification: _ursulas = make_federated_ursulas(ursula_config=ursula_federated_test_config, quantity=quantity, know_each_other=False) all_ursulas = {u.checksum_address: u for u in _ursulas} for ursula in _ursulas: ursula.known_nodes._nodes = all_ursulas ursula.known_nodes.checksum = b"This is a fleet state checksum..".hex() return _ursulas
def federated_ursulas(ursula_federated_test_config): if MOCK_KNOWN_URSULAS_CACHE: raise RuntimeError( "Ursulas cache was unclear at fixture loading time. Did you use one of the ursula maker functions without cleaning up?" ) _ursulas = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK) # Since we mutate this list in some tests, it's not enough to remember and remove the Ursulas; we have to remember them by port. # The same is true of blockchain_ursulas below. _ports_to_remove = [ursula.rest_interface.port for ursula in _ursulas] yield _ursulas for port in _ports_to_remove: test_logger.debug( f"Removing {port} ({MOCK_KNOWN_URSULAS_CACHE[port]}).") del MOCK_KNOWN_URSULAS_CACHE[port] for u in _ursulas: u.stop()
def test_new_federated_ursula_announces_herself(ursula_federated_test_config): ursula_in_a_house, ursula_with_a_mouse = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=2, know_each_other=False, network_middleware=MockRestMiddleware()) # Neither Ursula knows about the other. assert ursula_in_a_house.known_nodes == ursula_with_a_mouse.known_nodes ursula_in_a_house.remember_node(ursula_with_a_mouse) # OK, now, ursula_in_a_house knows about ursula_with_a_mouse, but not vice-versa. assert ursula_with_a_mouse in ursula_in_a_house.known_nodes assert ursula_in_a_house not in ursula_with_a_mouse.known_nodes # But as ursula_in_a_house learns, she'll announce herself to ursula_with_a_mouse. ursula_in_a_house.learn_from_teacher_node() assert ursula_with_a_mouse in ursula_in_a_house.known_nodes assert ursula_in_a_house in ursula_with_a_mouse.known_nodes
def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request): # good_serials = _determine_good_serials(10000, 50000) try: quantity = request.param except AttributeError: quantity = 5000 # Bigass fleet by default; that's kinda the point. with GlobalLoggerSettings.pause_all_logging_while(): with mock_secret_source(): with mock_cert_storage, mock_cert_loading, mock_rest_app_creation, mock_cert_generation, mock_remember_node, mock_message_verification: _ursulas = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=quantity, know_each_other=False) all_ursulas = {u.checksum_address: u for u in _ursulas} for ursula in _ursulas: ursula.known_nodes.current_state._nodes = all_ursulas ursula.known_nodes.current_state.checksum = b"This is a fleet state checksum..".hex( ) yield _ursulas for ursula in _ursulas: del MOCK_KNOWN_URSULAS_CACHE[ursula.rest_interface.port]
def federated_ursulas(ursula_federated_test_config): _ursulas = make_federated_ursulas( ursula_config=ursula_federated_test_config, quantity=NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK) yield _ursulas
def test_emit_warning_upon_new_version(ursula_federated_test_config, caplog): nodes = make_federated_ursulas(ursula_config=ursula_federated_test_config, quantity=3, know_each_other=False) teacher, learner, new_node = nodes learner.remember_node(teacher) teacher.remember_node(learner) teacher.remember_node(new_node) new_node.TEACHER_VERSION = learner.LEARNER_VERSION + 1 warnings = [] def warning_trapper(event): if event['log_level'] == LogLevel.warn: warnings.append(event) globalLogPublisher.addObserver(warning_trapper) learner.learn_from_teacher_node() assert len(warnings) == 1 assert warnings[0]['log_format'] == learner.unknown_version_message.format( new_node, new_node.TEACHER_VERSION, learner.LEARNER_VERSION) # Now let's go a little further: make the version totally unrecognizable. # First, there's enough garbage to at least scrape a potential checksum address fleet_snapshot = os.urandom(32 + 4) random_bytes = os.urandom(50) # lots of garbage in here future_version = learner.LEARNER_VERSION + 42 version_bytes = future_version.to_bytes(2, byteorder="big") crazy_bytes = fleet_snapshot + VariableLengthBytestring(version_bytes + random_bytes) signed_crazy_bytes = bytes(teacher.stamp(crazy_bytes)) Response = namedtuple("MockResponse", ("content", "status_code")) response = Response(content=signed_crazy_bytes + crazy_bytes, status_code=200) learner._current_teacher_node = teacher learner.network_middleware.get_nodes_via_rest = lambda *args, **kwargs: response learner.learn_from_teacher_node() # If you really try, you can read a node representation from the garbage accidental_checksum = to_checksum_address(random_bytes[:20]) accidental_nickname = nickname_from_seed(accidental_checksum)[0] accidental_node_repr = Character._display_name_template.format( "Ursula", accidental_nickname, accidental_checksum) assert len(warnings) == 2 assert warnings[1]['log_format'] == learner.unknown_version_message.format( accidental_node_repr, future_version, learner.LEARNER_VERSION) # This time, however, there's not enough garbage to assume there's a checksum address... random_bytes = os.urandom(2) crazy_bytes = fleet_snapshot + VariableLengthBytestring(version_bytes + random_bytes) signed_crazy_bytes = bytes(teacher.stamp(crazy_bytes)) response = Response(content=signed_crazy_bytes + crazy_bytes, status_code=200) learner._current_teacher_node = teacher learner.learn_from_teacher_node() assert len(warnings) == 3 # ...so this time we get a "really unknown version message" assert warnings[2][ 'log_format'] == learner.really_unknown_version_message.format( future_version, learner.LEARNER_VERSION) globalLogPublisher.removeObserver(warning_trapper)