fix auth, decryption, api interaction

This commit is contained in:
Alex Bezdieniezhnykh
2025-01-20 10:17:35 +02:00
parent e21dd7e70f
commit ce25ef38b0
12 changed files with 146 additions and 103 deletions
+4 -5
View File
@@ -1,10 +1,9 @@
from processor_command cimport FileCommand
cdef class ApiClient: cdef class ApiClient:
cdef str email, password, token, folder, token_file, api_url cdef str email, password, token, folder, token_file, api_url
cdef get_encryption_key(self, str hardware_hash) cdef get_encryption_key(self, str hardware_hash)
cdef login(self, str email, str password, bint persist_token=*) cdef login(self, str email, str password)
cdef bytes load_file(self, str filename, bint persist_token=*) cdef load_bytes(self, str filename)
cdef bytes load_ai_model(self) cdef load_ai_model(self)
cdef load_queue_config(self)
+37 -23
View File
@@ -1,11 +1,13 @@
import io import io
import json
import os import os
from http import HTTPStatus from http import HTTPStatus
import requests import requests
cimport constants cimport constants
from hardware_service cimport HardwareService, HardwareInfo from hardware_service cimport HardwareService, HardwareInfo
from security import Security from security cimport Security
from io import BytesIO
cdef class ApiClient: cdef class ApiClient:
"""Handles API authentication and downloading of the AI model.""" """Handles API authentication and downloading of the AI model."""
@@ -20,45 +22,57 @@ cdef class ApiClient:
self.token = None self.token = None
cdef get_encryption_key(self, str hardware_hash): cdef get_encryption_key(self, str hardware_hash):
return f'{self.email}-{self.password}-{hardware_hash}-#%@AzaionKey@%#---' cdef str key = f'{self.email}-{self.password}-{hardware_hash}-#%@AzaionKey@%#---'
return Security.calc_hash(key)
cdef login(self, str email, str password, bint persist_token=False): cdef login(self, str email, str password):
response = requests.post(f"{constants.API_URL}/login", json={"email": email, "password": password}) response = requests.post(f"{constants.API_URL}/login", json={"email": email, "password": password})
response.raise_for_status() response.raise_for_status()
self.token = response.json()["token"] self.token = response.json()["token"]
print(f'')
if persist_token: with open(<str>constants.TOKEN_FILE, 'w') as file:
with open(<str>constants.TOKEN_FILE, 'w') as file: file.write(self.token)
file.write(self.token)
cdef bytes load_file(self, str filename, bint persist_token=False): cdef load_bytes(self, str filename):
hardware_service = HardwareService() hardware_service = HardwareService()
cdef HardwareInfo hardware = hardware_service.get_hardware_info() cdef HardwareInfo hardware = hardware_service.get_hardware_info()
if self.token is None: if self.token is None:
self.login(self.email, self.password, persist_token) self.login(self.email, self.password)
url = f"{constants.API_URL}/resources/get/{self.folder}" url = f"{constants.API_URL}/resources/get/{self.folder}"
headers = {"Authorization": f"Bearer {self.token}"} headers = {
payload = { "Authorization": f"Bearer {self.token}",
"password": self.password, "Content-Type": "application/json"
"hardware": hardware,
"fileName": filename
} }
response = requests.post(url, json=payload, headers=headers, stream=True) payload = json.dumps(
{
"password": self.password,
"hardware": hardware.to_json_object(),
"fileName": filename
}, indent=4)
response = requests.post(url, data=payload, headers=headers, stream=True)
if response.status_code == HTTPStatus.UNAUTHORIZED or response.status_code == HTTPStatus.FORBIDDEN: if response.status_code == HTTPStatus.UNAUTHORIZED or response.status_code == HTTPStatus.FORBIDDEN:
self.login(self.email, self.password, persist_token) self.login(self.email, self.password)
response = requests.post(url, json=payload, headers=headers, stream=True) headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
response = requests.post(url, data=payload, headers=headers, stream=True)
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
print('500!')
key = self.get_encryption_key(hardware.hash) key = self.get_encryption_key(hardware.hash)
encrypted_stream = io.BytesIO(response.content)
decrypted_stream = io.BytesIO()
Security.decrypt_to(encrypted_stream, decrypted_stream, key)
return decrypted_stream
cdef bytes load_ai_model(self): stream = BytesIO(response.raw.read())
return self.load_file(constants.AI_MODEL_FILE, <bint>True) return Security.decrypt_to(stream, key)
cdef load_ai_model(self):
return self.load_bytes(constants.AI_MODEL_FILE)
cdef load_queue_config(self):
return self.load_bytes(constants.QUEUE_CONFIG_FILENAME).decode(encoding='utf-8')
+3
View File
@@ -1,6 +1,9 @@
from io import BytesIO
import main import main
from main import ParsedArguments from main import ParsedArguments
def start_server(): def start_server():
args = ParsedArguments('admin@azaion.com', 'Az@1on1000Odm$n', 'stage', True) args = ParsedArguments('admin@azaion.com', 'Az@1on1000Odm$n', 'stage', True)
processor = main.CommandProcessor(args) processor = main.CommandProcessor(args)
+2 -3
View File
@@ -1,9 +1,8 @@
cdef class HardwareInfo: cdef class HardwareInfo:
cdef str cpu, gpu, memory, mac_address, hash cdef str cpu, gpu, memory, mac_address, hash
cdef to_json_object(self)
cdef class HardwareService: cdef class HardwareService:
cdef bint is_windows cdef bint is_windows
cdef get_mac_address(self, interface=*)
cdef HardwareInfo get_hardware_info(self) cdef HardwareInfo get_hardware_info(self)
cdef str calc_hash(self, str s)
+23 -17
View File
@@ -1,6 +1,6 @@
import base64
import subprocess import subprocess
from hashlib import sha384 from security cimport Security
import psutil
cdef class HardwareInfo: cdef class HardwareInfo:
def __init__(self, str cpu, str gpu, str memory, str mac_address, str hw_hash): def __init__(self, str cpu, str gpu, str memory, str mac_address, str hw_hash):
@@ -10,6 +10,15 @@ cdef class HardwareInfo:
self.mac_address = mac_address self.mac_address = mac_address
self.hash = hw_hash self.hash = hw_hash
cdef to_json_object(self):
return {
"CPU": self.cpu,
"GPU": self.gpu,
"MacAddress": self.mac_address,
"Memory": self.memory,
"Hash": self.hash,
}
def __str__(self): def __str__(self):
return f'CPU: {self.cpu}. GPU: {self.gpu}. Memory: {self.memory}. MAC Address: {self.mac_address}' return f'CPU: {self.cpu}. GPU: {self.gpu}. Memory: {self.memory}. MAC Address: {self.mac_address}'
@@ -27,6 +36,15 @@ cdef class HardwareService:
print('Error during os type checking') print('Error during os type checking')
self.is_windows = False self.is_windows = False
cdef get_mac_address(self, interface="Ethernet"):
addresses = psutil.net_if_addrs()
for interface_name, interface_info in addresses.items():
if interface_name == interface:
for addr in interface_info:
if addr.family == psutil.AF_LINK:
return addr.address.replace('-', '')
return None
cdef HardwareInfo get_hardware_info(self): cdef HardwareInfo get_hardware_info(self):
if self.is_windows: if self.is_windows:
os_command = ( os_command = (
@@ -49,20 +67,8 @@ cdef class HardwareService:
cdef str cpu = lines[0].replace("Name=", "").replace(" ", " ") cdef str cpu = lines[0].replace("Name=", "").replace(" ", " ")
cdef str gpu = lines[1].replace("Name=", "").replace(" ", " ") cdef str gpu = lines[1].replace("Name=", "").replace(" ", " ")
cdef str memory = lines[2].replace("TotalVisibleMemorySize=", "").replace(" ", " ") cdef str memory = lines[2].replace("TotalVisibleMemorySize=", "").replace(" ", " ")
cdef str mac_address = self.get_mac_address()
# Get MAC address
if self.is_windows:
mac_cmd = "getmac"
else:
mac_cmd = "cat /sys/class/net/*/address"
cdef str mac_address = subprocess.check_output(mac_cmd, shell=True, text=True).splitlines()[0].strip()
cdef str full_hw_str = f'Azaion_{mac_address}_{cpu}_{gpu}' cdef str full_hw_str = f'Azaion_{mac_address}_{cpu}_{gpu}'
hw_hash = self.calc_hash(full_hw_str)
return HardwareInfo(cpu, gpu, memory, mac_address, hw_hash)
cdef str calc_hash(self, str s): hw_hash = Security.calc_hash(full_hw_str)
str_bytes = s.encode('utf-8') return HardwareInfo(cpu, gpu, memory, mac_address, hw_hash)
hash_bytes = sha384(str_bytes).digest()
cdef str h = base64.b64encode(hash_bytes).decode('utf-8')
return h
+9 -11
View File
@@ -35,7 +35,14 @@ cdef class CommandProcessor:
self.running = True self.running = True
def start(self): def start(self):
threading.Thread(target=self.process_queue, daemon=True).start() while self.running:
try:
command = self.command_queue.get()
print(f'command is : {command}')
model = self.api_client.load_ai_model()
Inference(model, self.on_annotations).run_inference(command)
except Exception as e:
print(f"Error processing queue: {e}")
cdef on_message(self, FileCommand cmd): cdef on_message(self, FileCommand cmd):
try: try:
@@ -51,17 +58,8 @@ cdef class CommandProcessor:
handler.send(annotations) handler.send(annotations)
cdef process_queue(self):
while self.running:
try:
command = self.command_queue.get()
model = self.api_client.load_ai_model()
Inference(model, self.on_annotations).run_inference(command)
except Exception as e:
print(f"Error processing queue: {e}")
cdef process_load(self, FileCommand command): cdef process_load(self, FileCommand command):
response = self.api_client.load_file(command) response = self.api_client.load_bytes(command.filename)
handler = self.socket_handler if command.processor_type == ProcessorType.SOCKET else self.rabbit_handler handler = self.socket_handler if command.processor_type == ProcessorType.SOCKET else self.rabbit_handler
handler.send(response) handler.send(response)
+1 -1
View File
@@ -15,6 +15,6 @@ cdef class RabbitHandler:
cdef object annotation_producer cdef object annotation_producer
cdef object command_consumer cdef object command_consumer
cdef start(self)
cdef send(self, object message) cdef send(self, object message)
cdef start(self)
cdef close(self) cdef close(self)
+20 -15
View File
@@ -1,3 +1,4 @@
import asyncio
import json import json
import socket import socket
import struct import struct
@@ -13,23 +14,21 @@ from processor_command cimport FileCommand, ProcessorType
from annotation cimport Annotation from annotation cimport Annotation
cdef class QueueConfig: cdef class QueueConfig:
cdef str host cdef str host,
cdef int port cdef int port
cdef str producer_user cdef str producer_user, producer_pw, consumer_user, consumer_pw
cdef str producer_pw
cdef str consumer_user
cdef str consumer_pw
@staticmethod @staticmethod
cdef QueueConfig from_json(str json_string): cdef QueueConfig from_json(str json_string):
cdef dict config_dict = json.loads(<str>json_string) cdef dict config_dict = json.loads(<str>json_string)["QueueConfig"]
cdef QueueConfig config = QueueConfig() cdef QueueConfig config = QueueConfig()
config.Host = config_dict["Host"]
config.Port = config_dict["Port"] config.host = config_dict["Host"]
config.ProducerUsername = config_dict["ProducerUsername"] config.port = config_dict["Port"]
config.ProducerPassword = config_dict["ProducerPassword"] config.producer_user = config_dict["ProducerUsername"]
config.ConsumerUsername = config_dict["ConsumerUsername"] config.producer_pw = config_dict["ProducerPassword"]
config.ConsumerPassword = config_dict["ConsumerPassword"] config.consumer_user = config_dict["ConsumerUsername"]
config.consumer_pw = config_dict["ConsumerPassword"]
return config return config
cdef class SocketHandler: cdef class SocketHandler:
@@ -75,7 +74,7 @@ cdef class SocketHandler:
cdef class RabbitHandler: cdef class RabbitHandler:
def __init__(self, ApiClient api_client, object on_message): def __init__(self, ApiClient api_client, object on_message):
self.on_message = on_message self.on_message = on_message
cdef str config_str = api_client.load_file(constants.QUEUE_CONFIG_FILENAME).decode(encoding='utf-8') cdef str config_str = api_client.load_queue_config()
queue_config = QueueConfig.from_json(config_str) queue_config = QueueConfig.from_json(config_str)
self.annotation_producer = Producer( self.annotation_producer = Producer(
host=<str>queue_config.host, host=<str>queue_config.host,
@@ -91,8 +90,14 @@ cdef class RabbitHandler:
) )
cdef start(self): cdef start(self):
self.command_consumer.start() threading.Thread(target=self._run_async, daemon=True).start()
self.command_consumer.subscribe(stream=<str>constants.COMMANDS_QUEUE, callback=self.on_message_inner,
def _run_async(self):
asyncio.run(self.start_inner())
async def start_inner(self):
await self.command_consumer.start()
await self.command_consumer.subscribe(stream=<str>constants.COMMANDS_QUEUE, callback=self.on_message_inner,
offset_specification=ConsumerOffsetSpecification(OffsetType.FIRST, None)) # put real offset offset_specification=ConsumerOffsetSpecification(OffsetType.FIRST, None)) # put real offset
def on_message_inner(self, message: AMQPMessage, message_context: MessageContext): def on_message_inner(self, message: AMQPMessage, message_context: MessageContext):
+9
View File
@@ -0,0 +1,9 @@
cdef class Security:
@staticmethod
cdef encrypt_to(input_stream, key)
@staticmethod
cdef decrypt_to(input_stream, key)
@staticmethod
cdef calc_hash(str key)
+30 -22
View File
@@ -1,40 +1,48 @@
# cython: language_level=3
import hashlib import hashlib
import os import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.hashes import Hash, SHA256
from hashlib import sha384
import base64
BUFFER_SIZE = 64 * 1024 # 64 KB BUFFER_SIZE = 64 * 1024 # 64 KB
cdef class Security: cdef class Security:
@staticmethod
cdef encrypt_to(self, input_stream, output_stream, key): cdef encrypt_to(input_stream, key):
aes_key = hashlib.sha256(key.encode('utf-8')).digest() cdef bytes aes_key = hashlib.sha256(key.encode('utf-8')).digest()
iv = os.urandom(16) iv = os.urandom(16)
output_stream.write(iv) # Write IV to the output stream
cipher = Cipher(algorithms.AES(aes_key), modes.CFB(iv), backend=default_backend()) cipher = Cipher(algorithms.AES(<bytes>aes_key), modes.CFB(iv), backend=default_backend())
encryptor = cipher.encryptor() encryptor = cipher.encryptor()
# Read and encrypt in chunks cdef bytearray res = bytearray()
res.extend(iv)
while chunk := input_stream.read(BUFFER_SIZE): while chunk := input_stream.read(BUFFER_SIZE):
encrypted_data = encryptor.update(chunk) encrypted_chunk = encryptor.update(chunk)
output_stream.write(encrypted_data) res.extend(encrypted_chunk)
res.extend(encryptor.finalize())
return res
final_data = encryptor.finalize() @staticmethod
output_stream.write(final_data) cdef decrypt_to(input_stream, key):
cdef bytes aes_key = hashlib.sha256(key.encode('utf-8')).digest()
cdef bytes iv = input_stream.read(16)
cdef decrypt_to(self, input_stream, output_stream, key): cdef cipher = Cipher(algorithms.AES(<bytes>aes_key), modes.CBC(<bytes>iv), backend=default_backend())
aes_key = hashlib.sha256(key.encode('utf-8')).digest() cdef decryptor = cipher.decryptor()
iv = input_stream.read(16) # AES block size is 16 bytes
# Create cipher and decryptor
cipher = Cipher(algorithms.AES(aes_key), modes.CFB(iv), backend=default_backend())
decryptor = cipher.decryptor()
cdef bytearray res = bytearray()
while chunk := input_stream.read(BUFFER_SIZE): while chunk := input_stream.read(BUFFER_SIZE):
decrypted_data = decryptor.update(chunk) decrypted_chunk = decryptor.update(chunk)
output_stream.write(decrypted_data) res.extend(decrypted_chunk)
res.extend(decryptor.finalize())
return res
final_data = decryptor.finalize() @staticmethod
output_stream.write(final_data) cdef calc_hash(str key):
str_bytes = key.encode('utf-8')
hash_bytes = sha384(str_bytes).digest()
cdef str h = base64.b64encode(hash_bytes).decode('utf-8')
return h
+1 -1
View File
@@ -4,12 +4,12 @@ from Cython.Build import cythonize
extensions = [ extensions = [
Extension('constants', ['constants.pyx']), Extension('constants', ['constants.pyx']),
Extension('annotation', ['annotation.pyx']), Extension('annotation', ['annotation.pyx']),
Extension('security', ['security.pyx']),
Extension('hardware_service', ['hardware_service.pyx'], extra_compile_args=["-g"], extra_link_args=["-g"]), Extension('hardware_service', ['hardware_service.pyx'], extra_compile_args=["-g"], extra_link_args=["-g"]),
Extension('processor_command', ['processor_command.pyx']), Extension('processor_command', ['processor_command.pyx']),
Extension('api_client', ['api_client.pyx']), Extension('api_client', ['api_client.pyx']),
Extension('inference', ['inference.pyx']), Extension('inference', ['inference.pyx']),
Extension('remote_handlers', ['remote_handlers.pyx']), Extension('remote_handlers', ['remote_handlers.pyx']),
Extension('security', ['security.pyx']),
Extension('main', ['main.pyx']), Extension('main', ['main.pyx']),
] ]
@@ -46,17 +46,19 @@ public class HardwareService : IHardwareService
memoryStr = $"{Math.Round(memKb / 1024.0 / 1024.0)} Gb"; memoryStr = $"{Math.Round(memKb / 1024.0 / 1024.0)} Gb";
} }
var macAddress = MacAddress();
var hardwareInfo = new HardwareInfo var hardwareInfo = new HardwareInfo
{ {
Memory = memoryStr, Memory = memoryStr,
CPU = lines.Length > 1 && string.IsNullOrEmpty(lines[1]) CPU = lines.Length > 1 && string.IsNullOrEmpty(lines[1])
? "Unknown RAM" ? "Unknown CPU"
: lines[1], : lines[1].Trim(),
GPU = lines.Length > 2 && string.IsNullOrEmpty(lines[2]) GPU = lines.Length > 2 && string.IsNullOrEmpty(lines[2])
? "Unknown GPU" ? "Unknown GPU"
: lines[2] : lines[2],
MacAddress = macAddress
}; };
hardwareInfo.Hash = ToHash($"Azaion_{MacAddress()}_{hardwareInfo.CPU}_{hardwareInfo.GPU}"); hardwareInfo.Hash = ToHash($"Azaion_{macAddress}_{hardwareInfo.CPU}_{hardwareInfo.GPU}");
return hardwareInfo; return hardwareInfo;
} }
catch (Exception ex) catch (Exception ex)