nucypher/tests/network/test_failure_modes.py

187 lines
7.3 KiB
Python

import datetime
import maya
import pytest
import io
import os
import requests
from twisted.internet import threads
import pytest_twisted
from werkzeug.exceptions import RequestEntityTooLarge
from nucypher.network.nodes import Learner
from nucypher.policy.collections import TreasureMap
from nucypher.policy.policies import Policy
from nucypher.utilities.sandbox.middleware import NodeIsDownMiddleware, EvilMiddleWare
from nucypher.utilities.sandbox.ursula import make_federated_ursulas
from bytestring_splitter import BytestringSplittingError
from functools import partial
def test_bob_does_not_let_a_connection_error_stop_him(enacted_federated_policy,
federated_ursulas,
federated_bob,
federated_alice):
assert len(federated_bob.known_nodes) == 0
ursula1 = list(federated_ursulas)[0]
ursula2 = list(federated_ursulas)[1]
federated_bob.remember_node(ursula1)
federated_bob.network_middleware = NodeIsDownMiddleware()
federated_bob.network_middleware.node_is_down(ursula1)
with pytest.raises(TreasureMap.NowhereToBeFound):
federated_bob.get_treasure_map(federated_alice.stamp, enacted_federated_policy.label)
federated_bob.remember_node(ursula2)
map = federated_bob.get_treasure_map(federated_alice.stamp, enacted_federated_policy.label)
assert sorted(list(map.destinations.keys())) == sorted(
list(u.checksum_address for u in list(federated_ursulas)))
def test_alice_can_grant_even_when_the_first_nodes_she_tries_are_down(federated_alice, federated_bob, federated_ursulas):
m, n = 2, 3
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
label = b"this_is_the_path_to_which_access_is_being_granted"
federated_alice.known_nodes._nodes = {}
federated_alice.network_middleware = NodeIsDownMiddleware()
# OK, her first and only node is down.
down_node = list(federated_ursulas)[0]
federated_alice.remember_node(down_node)
federated_alice.network_middleware.node_is_down(down_node)
# Here's the command we want to run.
alice_grant_action = partial(federated_alice.grant,
federated_bob,
label,
m=m,
n=n,
expiration=policy_end_datetime,
timeout=.1)
# Try a first time, failing because no known nodes are up for Alice to even try to learn from.
with pytest.raises(down_node.NotEnoughNodes):
alice_grant_action()
# Now she learn about one node that *is* up...
reliable_node = list(federated_ursulas)[1]
federated_alice.remember_node(reliable_node)
# ...amidst a few others that are down.
more_nodes = list(federated_ursulas)[2:10]
for node in more_nodes:
federated_alice.network_middleware.node_is_down(node)
# Alice still only knows about two nodes (the one that is down and the new one).
assert len(federated_alice.known_nodes) == 2
# Now we'll have a situation where Alice knows about all 10,
# though only one is up.
# She'll try to learn about more, but there aren't any.
# Because she has successfully completed learning, but the nodes about which she learned are down,
# she'll get a different error.
for node in more_nodes:
federated_alice.remember_node(node)
with pytest.raises(Policy.Rejected):
alice_grant_action()
# Now let's let a few of them come up.
for node in more_nodes[0:4]:
federated_alice.network_middleware.node_is_up(node)
# Now the same exact action works.
# TODO: This action only succeeds here because we are forcing
# grant to accept the ursulas that just came back online (handpicked_ursulas).
# Since there are now enough Ursulas online, this action *can* succeed without forcing sample.
policy = alice_grant_action(handpicked_ursulas=more_nodes[:3])
# TODO: This is how it's actually done. How can we measure such random results?
# The below line will fail with ? probability, if more then 2 of the nodes selected
# are among those still down.
# policy = alice_grant_action()
# The number of accepted arrangements at least the number of Ursulas we're using (n)
assert len(policy._accepted_arrangements) >= n
# The number of actually enacted arrangements is exactly equal to n.
assert len(policy._enacted_arrangements) == n
def test_node_has_changed_cert(federated_alice, federated_ursulas):
federated_alice.known_nodes._nodes = {}
federated_alice.network_middleware = NodeIsDownMiddleware()
federated_alice.network_middleware.client.certs_are_broken = True
firstula = list(federated_ursulas)[0]
federated_alice.remember_node(firstula)
federated_alice.start_learning_loop(now=True)
federated_alice.learn_from_teacher_node()
# Cool - we didn't crash because of SSLError.
# TODO: Assertions and such.
def test_huge_treasure_maps_are_rejected(federated_alice, federated_ursulas):
federated_alice.network_middleware = EvilMiddleWare()
firstula = list(federated_ursulas)[0]
ok_amount = 10 * 1024 # 10k
ok_data = os.urandom(ok_amount)
with pytest.raises(BytestringSplittingError):
federated_alice.network_middleware.upload_arbitrary_data(
firstula, 'consider_arrangement', ok_data
)
"""
TODO: the following does not work because of this issue: https://github.com/pallets/werkzeug/issues/1513
it is implemented at a lower level through hendrix
but would be nice if it could be configurable through
flask as well and thus, testable here...
evil_amount = 5000 * 1024
evil_data = os.urandom(evil_amount)
with pytest.raises(RequestEntityTooLarge):
federated_alice.network_middleware.upload_arbitrary_data(
firstula, 'consider_arrangement', evil_data
)
"""
@pytest_twisted.inlineCallbacks
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)