distributed-state-network 0.0.4__tar.gz → 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/PKG-INFO +1 -1
  2. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/documentation/ds-node-config.md +6 -0
  3. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/pyproject.toml +1 -1
  4. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/dsnode.py +9 -5
  5. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/handler.py +9 -8
  6. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/objects/config.py +10 -1
  7. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/objects/hello_packet.py +4 -3
  8. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/util/ecdsa.py +1 -1
  9. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/util/https.py +5 -8
  10. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/util/key_manager.py +2 -2
  11. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/tests/connections.py +10 -2
  12. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/.github/workflows/publish.yml +0 -0
  13. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/.gitignore +0 -0
  14. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/LICENSE +0 -0
  15. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/README.md +0 -0
  16. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/coverage.sh +0 -0
  17. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/documentation/ds-node-server.md +0 -0
  18. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/documentation/ds-node.md +0 -0
  19. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/documentation/protocol.md +0 -0
  20. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/documentation/usage.md +0 -0
  21. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/__init__.py +0 -0
  22. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/create_key.py +0 -0
  23. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/objects/endpoint.py +0 -0
  24. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/objects/peers_packet.py +0 -0
  25. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/objects/signed_packet.py +0 -0
  26. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/objects/state_packet.py +0 -0
  27. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/util/__init__.py +0 -0
  28. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/util/aes.py +0 -0
  29. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/src/distributed_state_network/util/byte_helper.py +0 -0
  30. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/technical.md +0 -0
  31. {distributed_state_network-0.0.4 → distributed_state_network-0.1.0}/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: distributed-state-network
3
- Version: 0.0.4
3
+ Version: 0.1.0
4
4
  Summary: A tool to distribute the state of a network device to other devices on the network
5
5
  Project-URL: Homepage, https://github.com/erinclemmer/distributed_state_network
6
6
  Project-URL: Issues, https://github.com/erinclemmer/distributed_state_network/issues
@@ -12,6 +12,8 @@ from distributed_state_network import DSNodeConfig
12
12
  class DSNodeConfig:
13
13
  node_id: str
14
14
  port: int
15
+ https: bool
16
+ network_ip: str
15
17
  aes_key_file: str
16
18
  bootstrap_nodes: List[Endpoint]
17
19
  ```
@@ -20,6 +22,8 @@ class DSNodeConfig:
20
22
  - **node_id** (`str`): Unique identifier for the node
21
23
  - **port** (`int`): Port number for the node to listen on
22
24
  - **aes_key_file** (`str`): Path to the AES key file for encryption/decryption
25
+ - **https** (`bool`): whether to use HTTPS network requests (true) or http (false)
26
+ - **network_ip** (`str`): Must match network address of the current computer (only if https is on)
23
27
  - **bootstrap_nodes** (`List[Endpoint]`): List of initial nodes to connect to when joining the network
24
28
 
25
29
  ### Methods
@@ -39,6 +43,8 @@ config_dict = {
39
43
  "node_id": "node1",
40
44
  "port": 8000,
41
45
  "aes_key_file": "/path/to/key.aes",
46
+ "https": false,
47
+ "network_ip": "192.168.0.1",
42
48
  "bootstrap_nodes": [
43
49
  {"address": "127.0.0.1", "port": 8001}
44
50
  ]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "distributed-state-network"
7
- version = "0.0.4"
7
+ version = "0.1.0"
8
8
  authors = [{name="Erin Clemmer", email="erin.c.clemmer@gmail.com"}]
9
9
  description = "A tool to distribute the state of a network device to other devices on the network"
10
10
  keywords = ["distributed", "networking"]
@@ -38,7 +38,8 @@ class DSNode:
38
38
  self.cert_manager = CertManager(config.node_id)
39
39
  self.cred_manager = CredentialManager(config.node_id)
40
40
 
41
- self.cert_manager.generate_keys()
41
+ if self.config.https:
42
+ self.cert_manager.generate_keys(self.config.network_ip)
42
43
  self.cred_manager.generate_keys()
43
44
 
44
45
  self.node_states = {
@@ -94,8 +95,10 @@ class DSNode:
94
95
  def send_request(self, con: Endpoint, path: str, payload: bytes, verify, retries: int = 0) -> Tuple[requests.Response, bytes]:
95
96
  try:
96
97
  # Always send a ping first to throw an error if https validation does not work
97
- requests.post(f'https://{con.to_string()}/ping', data=self.encrypt_data(payload), verify=verify, timeout=2)
98
- res = requests.post(f'https://{con.to_string()}/{path}', data=self.encrypt_data(payload), verify=verify, timeout=2)
98
+ protocol = "https" if self.config.https else "http"
99
+ verify = verify if self.config.https else None
100
+ requests.post(f'{protocol}://{con.to_string()}/ping', data=self.encrypt_data(payload), verify=verify, timeout=2)
101
+ res = requests.post(f'{protocol}://{con.to_string()}/{path}', data=self.encrypt_data(payload), verify=verify, timeout=2)
99
102
  except Exception as e:
100
103
  self.logger.error(e)
101
104
  time.sleep(1)
@@ -178,7 +181,8 @@ class DSNode:
178
181
  self.logger.error(msg)
179
182
  raise Exception(505) # Version not supported
180
183
 
181
- self.cert_manager.ensure_public(pkt.node_id, pkt.https_certificate)
184
+ if self.config.https:
185
+ self.cert_manager.ensure_public(pkt.node_id, pkt.https_certificate)
182
186
  self.cred_manager.ensure_public(pkt.node_id, pkt.ecdsa_public_key)
183
187
 
184
188
  if pkt.node_id not in self.address_book:
@@ -196,7 +200,7 @@ class DSNode:
196
200
  self.my_con(),
197
201
  self.cred_manager.my_public(),
198
202
  None,
199
- self.cert_manager.my_public()
203
+ self.cert_manager.my_public() if self.config.https else None
200
204
  )
201
205
  pkt.sign(self.cred_manager.my_private())
202
206
  return pkt
@@ -73,7 +73,7 @@ class DSNodeServer(HTTPServer):
73
73
  disconnect_callback: Optional[Callable] = None,
74
74
  update_callback: Optional[Callable] = None
75
75
  ):
76
- super().__init__(("127.0.0.1", config.port), DSNodeHandler)
76
+ super().__init__(("0.0.0.0", config.port), DSNodeHandler)
77
77
  self.config = config
78
78
  self.node = DSNode(config, VERSION, disconnect_callback, update_callback)
79
79
  self.node.logger.info(f'Started DSNode on port {config.port}')
@@ -93,13 +93,14 @@ class DSNodeServer(HTTPServer):
93
93
  @staticmethod
94
94
  def start(config: DSNodeConfig, disconnect_callback: Optional[Callable] = None, update_callback: Optional[Callable] = None) -> 'NodeServer':
95
95
  n = DSNodeServer(config, disconnect_callback, update_callback)
96
- ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
97
- public_path = n.node.cert_manager.public_path(n.config.node_id)
98
- ssl_context.load_cert_chain(
99
- certfile=public_path,
100
- keyfile=public_path.replace(".crt", ".key")
101
- )
102
- n.socket = ssl_context.wrap_socket(n.socket, server_side=True)
96
+ if config.https:
97
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
98
+ public_path = n.node.cert_manager.public_path(n.config.node_id)
99
+ ssl_context.load_cert_chain(
100
+ certfile=public_path,
101
+ keyfile=public_path.replace(".crt", ".key")
102
+ )
103
+ n.socket = ssl_context.wrap_socket(n.socket, server_side=True)
103
104
  n.thread = threading.Thread(target=serve, args=(n, ))
104
105
  n.thread.start()
105
106
 
@@ -7,9 +7,18 @@ from distributed_state_network.objects.endpoint import Endpoint
7
7
  class DSNodeConfig:
8
8
  node_id: str
9
9
  port: int
10
+ https: bool
11
+ network_ip: str
10
12
  aes_key_file: str
11
13
  bootstrap_nodes: List[Endpoint]
12
14
 
13
15
  @staticmethod
14
16
  def from_dict(data: Dict) -> 'DSNodeConfig':
15
- return DSNodeConfig(data["node_id"], data["port"], data["aes_key_file"], [Endpoint.from_json(e) for e in data["bootstrap_nodes"]])
17
+ return DSNodeConfig(
18
+ data["node_id"],
19
+ data["port"],
20
+ data["https"],
21
+ data["network_ip"],
22
+ data["aes_key_file"],
23
+ [Endpoint.from_json(e) for e in data["bootstrap_nodes"]]
24
+ )
@@ -37,9 +37,10 @@ class HelloPacket(SignedPacket):
37
37
  bts.write_string(self.node_id)
38
38
  bts.write_bytes(self.connection.to_bytes())
39
39
  bts.write_bytes(self.ecdsa_public_key)
40
- bts.write_bytes(self.https_certificate)
41
40
  if include_signature:
42
41
  bts.write_bytes(self.ecdsa_signature)
42
+ if self.https_certificate is not None:
43
+ bts.write_bytes(self.https_certificate)
43
44
 
44
45
  return bts.get_bytes()
45
46
 
@@ -50,10 +51,10 @@ class HelloPacket(SignedPacket):
50
51
  node_id = bts.read_string()
51
52
  connection = Endpoint.from_bytes(bts.read_bytes())
52
53
  ecdsa_public_key = bts.read_bytes()
53
- https_certificate = bts.read_bytes()
54
54
  ecdsa_signature = bts.read_bytes()
55
+ https_certificate = bts.read_bytes() or None
55
56
 
56
- if version == '' or node_id == '' or ecdsa_public_key == b'' or https_certificate == b'':
57
+ if version == '' or node_id == '' or ecdsa_public_key == b'':
57
58
  raise Exception(406) # Not acceptable
58
59
 
59
60
  return HelloPacket(version, node_id, connection, ecdsa_public_key, ecdsa_signature, https_certificate)
@@ -2,7 +2,7 @@ import hashlib
2
2
 
3
3
  from ecdsa import SigningKey, VerifyingKey, SECP256k1
4
4
 
5
- def generate_key_pair():
5
+ def generate_key_pair(_):
6
6
  private_key = SigningKey.generate(curve=SECP256k1)
7
7
  public_key = private_key.get_verifying_key()
8
8
  return public_key.to_string(), private_key.to_string()
@@ -9,7 +9,7 @@ from cryptography.hazmat.backends import default_backend
9
9
  from cryptography.hazmat.primitives.asymmetric import rsa
10
10
  from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
11
11
 
12
- def generate_cert():
12
+ def generate_cert(network_ip: str):
13
13
  # Generate private key
14
14
  private_key = rsa.generate_private_key(
15
15
  public_exponent=65537,
@@ -19,11 +19,8 @@ def generate_cert():
19
19
 
20
20
  # Generate a self-signed certificate
21
21
  subject = issuer = x509.Name([
22
- x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
23
- x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
24
- x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
25
- x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Organization"),
26
- x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
22
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "DSN"),
23
+ x509.NameAttribute(NameOID.COMMON_NAME, "DSN"),
27
24
  ])
28
25
 
29
26
  certificate = (
@@ -36,8 +33,8 @@ def generate_cert():
36
33
  .not_valid_after(datetime.utcnow() + timedelta(days=365))
37
34
  .add_extension(
38
35
  x509.SubjectAlternativeName([
39
- x509.DNSName("localhost"),
40
- IPAddress(ipaddress.IPv4Address("127.0.0.1"))
36
+ x509.DNSName(network_ip),
37
+ IPAddress(ipaddress.IPv4Address(network_ip))
41
38
  ]),
42
39
  critical=False
43
40
  )
@@ -63,11 +63,11 @@ class KeyManager:
63
63
  with open(f'{self.folder}/{self.node_id}/{self.node_id}.{self.private_extension}', 'rb') as f:
64
64
  return f.read()
65
65
 
66
- def generate_keys(self):
66
+ def generate_keys(self, network_ip: str = None):
67
67
  if os.path.exists(f'{self.folder}/{self.node_id}/{self.node_id}.{self.private_extension}'):
68
68
  return
69
69
  logging.getLogger("DSN: " + self.node_id).info(f"Generating {self.key_type} Keys ...")
70
- cert_bytes, key_bytes = self.gen_keys()
70
+ cert_bytes, key_bytes = self.gen_keys(network_ip)
71
71
  if not os.path.exists(self.folder):
72
72
  os.mkdir(self.folder)
73
73
  if not os.path.exists(f'{self.folder}/{self.node_id}'):
@@ -17,12 +17,14 @@ cb_test = 0
17
17
 
18
18
  current_port = 5000
19
19
 
20
- def start_node(node_id: str, bootstrap_port: int = None, disconnect_cb: Optional[Callable] = None, update_cb: Optional[Callable] = None):
20
+ def start_node(node_id: str, bootstrap_port: int = None, disconnect_cb: Optional[Callable] = None, update_cb: Optional[Callable] = None, https: bool = False):
21
21
  global current_port
22
22
  args = {
23
23
  "node_id": node_id,
24
24
  "port": current_port,
25
- "aes_key_file": KEY_FILE
25
+ "aes_key_file": KEY_FILE,
26
+ "https": https,
27
+ "network_ip": "127.0.0.1"
26
28
  }
27
29
  current_port += 1
28
30
  if bootstrap_port is not None:
@@ -46,6 +48,12 @@ class ConnectionsTest(unittest.TestCase):
46
48
  self.assertEqual(["node-1", "node-2"], sorted(node1.node.peers()))
47
49
  self.assertEqual(["node-1", "node-2"], sorted(node2.node.peers()))
48
50
 
51
+ def test_https(self):
52
+ node1 = start_node("node-1", https=True)
53
+ node2 = start_node("node-2", node1.config.port, https=True)
54
+ self.assertEqual(["node-1", "node-2"], sorted(node1.node.peers()))
55
+ self.assertEqual(["node-1", "node-2"], sorted(node2.node.peers()))
56
+
49
57
  def test_many(self):
50
58
  bootstrap_node = start_node("bootstrap")
51
59
  nodes = []