mirror of
https://github.com/azaion/loader.git
synced 2026-04-22 10:06:32 +00:00
Add E2E tests, fix bugs
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# Module: api_client
|
||||
|
||||
## Purpose
|
||||
|
||||
Central API client that orchestrates authentication, encrypted resource download/upload (using a big/small binary-split scheme), and CDN integration for the Azaion resource API.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Classes
|
||||
|
||||
#### `ApiClient` (cdef class)
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-------------|-------------|------------------------------------|
|
||||
| credentials | Credentials | User email/password |
|
||||
| user | User | Authenticated user (from JWT) |
|
||||
| token | str | JWT bearer token |
|
||||
| cdn_manager | CDNManager | CDN upload/download client |
|
||||
| api_url | str | Base URL for the resource API |
|
||||
| folder | str | Declared in `.pxd` but never assigned — dead attribute |
|
||||
|
||||
#### Methods
|
||||
|
||||
| Method | Visibility | Signature | Description |
|
||||
|------------------------------|------------|-------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `__init__` | def | `(self, str api_url)` | Initialize with API base URL |
|
||||
| `set_credentials_from_dict` | cpdef | `(self, str email, str password)` | Set credentials + initialize CDN from `cdn.yaml` |
|
||||
| `set_credentials` | cdef | `(self, Credentials credentials)` | Internal: set credentials, lazy-init CDN manager |
|
||||
| `login` | cdef | `(self)` | POST `/login`, store JWT token |
|
||||
| `set_token` | cdef | `(self, str token)` | Decode JWT claims → create `User` with role mapping |
|
||||
| `get_user` | cdef | `(self) -> User` | Lazy login + return user |
|
||||
| `request` | cdef | `(self, str method, str url, object payload, bint is_stream)` | Authenticated HTTP request with auto-retry on 401/403 |
|
||||
| `list_files` | cdef | `(self, str folder, str search_file)` | GET `/resources/list/{folder}` with search param |
|
||||
| `check_resource` | cdef | `(self)` | POST `/resources/check` with hardware fingerprint |
|
||||
| `load_bytes` | cdef | `(self, str filename, str folder) -> bytes` | Download + decrypt resource using per-user+hw key |
|
||||
| `upload_file` | cdef | `(self, str filename, bytes resource, str folder)` | POST multipart upload to `/resources/{folder}` |
|
||||
| `load_big_file_cdn` | cdef | `(self, str folder, str big_part) -> bytes` | Download large file part from CDN |
|
||||
| `load_big_small_resource` | cpdef | `(self, str resource_name, str folder) -> bytes` | Reassemble resource from small (API) + big (CDN/local) parts |
|
||||
| `upload_big_small_resource` | cpdef | `(self, bytes resource, str resource_name, str folder)` | Split-encrypt and upload small part to API, big part to CDN |
|
||||
| `upload_to_cdn` | cpdef | `(self, str bucket, str filename, bytes file_bytes)` | Direct CDN upload |
|
||||
| `download_from_cdn` | cpdef | `(self, str bucket, str filename) -> bytes` | Direct CDN download |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
### Authentication Flow
|
||||
1. `set_credentials_from_dict()` → stores credentials, downloads `cdn.yaml` via `load_bytes()` (encrypted), parses YAML, initializes `CDNManager`
|
||||
2. `login()` → POST `/login` with email/password → receives JWT token → `set_token()` decodes claims (nameid, unique_name, role) → creates `User`
|
||||
3. `request()` → wraps all authenticated HTTP calls; on 401/403 auto-retries with fresh login
|
||||
|
||||
### Big/Small Resource Split (download)
|
||||
1. Downloads the "small" encrypted part via API (`load_bytes()` with per-user+hw key)
|
||||
2. Checks if "big" part exists locally (cached file)
|
||||
3. If local: concatenates small + big, decrypts with shared resource key
|
||||
4. If decrypt fails (version mismatch): falls through to CDN download
|
||||
5. If no local: downloads big part from CDN
|
||||
6. Concatenates small + big, decrypts with shared resource key
|
||||
|
||||
### Big/Small Resource Split (upload)
|
||||
1. Encrypts entire resource with shared resource key
|
||||
2. Splits: small part = `min(SMALL_SIZE_KB * 1024, 30% of encrypted)`, big part = remainder
|
||||
3. Uploads big part to CDN + saves local copy
|
||||
4. Uploads small part to API via multipart POST
|
||||
|
||||
### JWT Role Mapping
|
||||
Maps `role` claim string to `RoleEnum`: ApiAdmin, Admin, ResourceUploader, Validator, Operator, or NONE (default).
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: `constants`, `credentials`, `cdn_manager`, `hardware_service`, `security`, `user`
|
||||
- **External**: `json`, `os` (stdlib), `jwt` (pyjwt 2.10.1), `requests` (2.32.4), `yaml` (pyyaml 6.0.2)
|
||||
|
||||
## Consumers
|
||||
|
||||
- `main` — creates `ApiClient` instance; calls `set_credentials_from_dict`, `login`, `load_big_small_resource`, `upload_big_small_resource`; reads `.token`
|
||||
|
||||
## Data Models
|
||||
|
||||
Uses `Credentials`, `User`, `RoleEnum`, `CDNCredentials`, `CDNManager` from other modules.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Source | Key | Usage |
|
||||
|-------------|--------------------|-----------------------------------------|
|
||||
| `cdn.yaml` | host | CDN endpoint URL |
|
||||
| `cdn.yaml` | downloader_access_key/secret | CDN read credentials |
|
||||
| `cdn.yaml` | uploader_access_key/secret | CDN write credentials |
|
||||
|
||||
The CDN config file is itself downloaded encrypted from the API on first credential setup.
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **Azaion Resource API**: `/login`, `/resources/get/{folder}`, `/resources/{folder}` (upload), `/resources/list/{folder}`, `/resources/check`
|
||||
- **S3 CDN**: via `CDNManager` for large file parts
|
||||
|
||||
## Security
|
||||
|
||||
- JWT token stored in memory, decoded without signature verification (`options={"verify_signature": False}`)
|
||||
- Per-download encryption: resources encrypted with AES-256-CBC using a key derived from user credentials + hardware fingerprint
|
||||
- Shared resource encryption: big/small split uses a fixed shared key
|
||||
- Auto-retry on 401/403 re-authenticates transparently
|
||||
- CDN config is downloaded encrypted, decrypted locally
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Module: binary_split
|
||||
|
||||
## Purpose
|
||||
|
||||
Handles the encrypted Docker image archive workflow: downloading a key fragment from the API, decrypting an AES-256-CBC encrypted archive, loading it into Docker, and verifying expected images are present.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|------------------------|------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| `download_key_fragment`| `(resource_api_url: str, token: str) -> bytes` | GET request to `/binary-split/key-fragment` with Bearer auth |
|
||||
| `decrypt_archive` | `(encrypted_path: str, key_fragment: bytes, output_path: str) -> None` | AES-256-CBC decryption with SHA-256 derived key; strips PKCS7 padding |
|
||||
| `docker_load` | `(tar_path: str) -> None` | Runs `docker load -i <tar_path>` subprocess |
|
||||
| `check_images_loaded` | `(version: str) -> bool` | Checks all `API_SERVICES` images exist for given version tag |
|
||||
|
||||
### Module-level Constants
|
||||
|
||||
| Name | Value |
|
||||
|---------------|--------------------------------------------------------------------------------------------|
|
||||
| API_SERVICES | List of 7 Docker image names: `azaion/annotations`, `azaion/flights`, `azaion/detections`, `azaion/gps-denied-onboard`, `azaion/gps-denied-desktop`, `azaion/autopilot`, `azaion/ai-training` |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
### `decrypt_archive`
|
||||
1. Derives AES key: `SHA-256(key_fragment)` → 32-byte key
|
||||
2. Reads first 16 bytes as IV from encrypted file
|
||||
3. Decrypts remaining data in 64KB chunks using AES-256-CBC
|
||||
4. After decryption, reads last byte of output to determine PKCS7 padding length
|
||||
5. Truncates output file to remove padding
|
||||
|
||||
### `check_images_loaded`
|
||||
Iterates all 7 service image names, runs `docker image inspect <name>:<version>` for each. Returns `False` on first missing image.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: none (leaf module)
|
||||
- **External**: `hashlib`, `os`, `subprocess` (stdlib), `requests` (2.32.4), `cryptography` (44.0.2)
|
||||
|
||||
## Consumers
|
||||
|
||||
- `main` — `_run_unlock()` calls all four functions; `unlock()` endpoint calls `check_images_loaded()`
|
||||
|
||||
## Data Models
|
||||
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
|
||||
No env vars consumed directly. `API_SERVICES` list is hardcoded.
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **REST API**: GET `{resource_api_url}/binary-split/key-fragment` — downloads encryption key fragment
|
||||
- **Docker CLI**: `docker load` and `docker image inspect` via subprocess
|
||||
- **File system**: reads encrypted `.enc` archive, writes decrypted `.tar` archive
|
||||
|
||||
## Security
|
||||
|
||||
- Key derivation: SHA-256 hash of server-provided key fragment
|
||||
- Encryption: AES-256-CBC with PKCS7 padding
|
||||
- The key fragment is ephemeral — downloaded per unlock operation
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Module: cdn_manager
|
||||
|
||||
## Purpose
|
||||
|
||||
Manages upload and download operations to an S3-compatible CDN (object storage) using separate credentials for read and write access.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Classes
|
||||
|
||||
#### `CDNCredentials` (cdef class)
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|--------------------------|------|--------------------------------|
|
||||
| host | str | S3 endpoint URL |
|
||||
| downloader_access_key | str | Read-only access key |
|
||||
| downloader_access_secret | str | Read-only secret key |
|
||||
| uploader_access_key | str | Write access key |
|
||||
| uploader_access_secret | str | Write secret key |
|
||||
|
||||
#### `CDNManager` (cdef class)
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------------|--------|------------------------------------|
|
||||
| creds | CDNCredentials | Stored credentials |
|
||||
| download_client | object | boto3 S3 client (read credentials) |
|
||||
| upload_client | object | boto3 S3 client (write credentials)|
|
||||
|
||||
| Method | Signature | Returns | Description |
|
||||
|------------|--------------------------------------------------------|---------|--------------------------------------|
|
||||
| `__init__` | `(self, CDNCredentials credentials)` | — | Creates both S3 clients |
|
||||
| `upload` | `cdef (self, str bucket, str filename, bytes file_bytes)` | bool | Uploads bytes to S3 bucket/key |
|
||||
| `download` | `cdef (self, str folder, str filename)` | bool | Downloads S3 object to local `folder/filename` |
|
||||
|
||||
Note: `.pxd` declares the parameter as `str bucket` while `.pyx` uses `str folder`. Functionally identical (Cython matches by position).
|
||||
|
||||
## Internal Logic
|
||||
|
||||
### Constructor
|
||||
Creates two separate boto3 S3 clients:
|
||||
- `download_client` with `downloader_access_key` / `downloader_access_secret`
|
||||
- `upload_client` with `uploader_access_key` / `uploader_access_secret`
|
||||
|
||||
Both clients connect to the same `endpoint_url` (CDN host).
|
||||
|
||||
### `upload`
|
||||
Uses `upload_fileobj` to stream bytes to S3. Returns `True` on success, `False` on exception.
|
||||
|
||||
### `download`
|
||||
Creates local directory if needed (`os.makedirs`), then uses `download_file` to save S3 object to local path `folder/filename`. Returns `True` on success, `False` on exception.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: `constants` (for `log()`, `logerror()`)
|
||||
- **External**: `io`, `os` (stdlib), `boto3` (1.40.9)
|
||||
|
||||
## Consumers
|
||||
|
||||
- `api_client` — `load_big_file_cdn()`, `upload_big_small_resource()`, `upload_to_cdn()`, `download_from_cdn()`
|
||||
|
||||
## Data Models
|
||||
|
||||
`CDNCredentials` is the data model.
|
||||
|
||||
## Configuration
|
||||
|
||||
CDN credentials are loaded from a YAML file (`cdn.yaml`) by the `api_client` module, not by this module directly.
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **S3-compatible storage**: upload and download via boto3 S3 client with custom endpoint URL
|
||||
|
||||
## Security
|
||||
|
||||
Separate read/write credential pairs enforce least-privilege access to CDN storage.
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Module: constants
|
||||
|
||||
## Purpose
|
||||
|
||||
Centralizes shared configuration constants and provides the application-wide logging interface via Loguru.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Constants (cdef, module-level)
|
||||
|
||||
| Name | Type | Value |
|
||||
|------------------------|------|--------------------------------|
|
||||
| CONFIG_FILE | str | `"config.yaml"` |
|
||||
| QUEUE_CONFIG_FILENAME | str | `"secured-config.json"` |
|
||||
| AI_ONNX_MODEL_FILE | str | `"azaion.onnx"` |
|
||||
| CDN_CONFIG | str | `"cdn.yaml"` |
|
||||
| MODELS_FOLDER | str | `"models"` |
|
||||
| SMALL_SIZE_KB | int | `3` |
|
||||
| ALIGNMENT_WIDTH | int | `32` |
|
||||
|
||||
Note: `QUEUE_MAXSIZE`, `COMMANDS_QUEUE`, `ANNOTATIONS_QUEUE` are declared in the `.pxd` but not defined in the `.pyx` — they are unused in this codebase.
|
||||
|
||||
### Functions (cdef, Cython-only visibility)
|
||||
|
||||
| Function | Signature | Description |
|
||||
|------------------------|----------------------------|------------------------------|
|
||||
| `log` | `cdef log(str log_message)` | Logs at INFO level via Loguru |
|
||||
| `logerror` | `cdef logerror(str error)` | Logs at ERROR level via Loguru |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
Loguru is configured with three sinks:
|
||||
- **File sink**: `Logs/log_loader_{date}.txt`, INFO level, daily rotation, 30-day retention, async (enqueue=True)
|
||||
- **Stdout sink**: DEBUG level, filtered to INFO/DEBUG/SUCCESS only, colorized
|
||||
- **Stderr sink**: WARNING+ level, colorized
|
||||
|
||||
Log format: `[HH:mm:ss LEVEL] message`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: none (leaf module)
|
||||
- **External**: `loguru` (0.7.3), `sys`, `time`
|
||||
|
||||
## Consumers
|
||||
|
||||
- `hardware_service` — calls `log()`
|
||||
- `cdn_manager` — calls `log()`, `logerror()`
|
||||
- `api_client` — calls `log()`, `logerror()`, reads `CDN_CONFIG`, `SMALL_SIZE_KB`
|
||||
|
||||
## Data Models
|
||||
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
|
||||
No env vars consumed directly. Log file path is hardcoded to `Logs/log_loader_{date}.txt`.
|
||||
|
||||
## External Integrations
|
||||
|
||||
None.
|
||||
|
||||
## Security
|
||||
|
||||
None.
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Module: credentials
|
||||
|
||||
## Purpose
|
||||
|
||||
Simple data holder for user authentication credentials (email + password).
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Classes
|
||||
|
||||
#### `Credentials` (cdef class)
|
||||
|
||||
| Attribute | Type | Visibility |
|
||||
|-----------|------|------------|
|
||||
| email | str | public |
|
||||
| password | str | public |
|
||||
|
||||
| Method | Signature | Description |
|
||||
|----------------|----------------------------------------------|------------------------------------|
|
||||
| `__init__` | `(self, str email, str password)` | Constructor |
|
||||
| `__str__` | `(self) -> str` | Returns `"email: password"` format |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
No logic — pure data class.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: none (leaf module)
|
||||
- **External**: none
|
||||
|
||||
## Consumers
|
||||
|
||||
- `security` — `get_api_encryption_key` takes `Credentials` as parameter
|
||||
- `api_client` — holds a `Credentials` instance, uses `.email` and `.password` for login and key derivation
|
||||
|
||||
## Data Models
|
||||
|
||||
The `Credentials` class itself is the data model.
|
||||
|
||||
## Configuration
|
||||
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
|
||||
None.
|
||||
|
||||
## Security
|
||||
|
||||
Stores plaintext password in memory. No encryption at rest.
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Module: hardware_service
|
||||
|
||||
## Purpose
|
||||
|
||||
Collects a hardware fingerprint string from the host OS (CPU, GPU, memory, drive serial) for use in hardware-bound encryption key derivation.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Classes
|
||||
|
||||
#### `HardwareService` (cdef class)
|
||||
|
||||
| Method | Signature | Description |
|
||||
|---------------------|--------------------------------|------------------------------------------------|
|
||||
| `get_hardware_info` | `@staticmethod cdef str ()` | Returns cached hardware fingerprint string |
|
||||
|
||||
### Module-level State
|
||||
|
||||
| Name | Type | Description |
|
||||
|------------------|------|----------------------------------|
|
||||
| `_CACHED_HW_INFO`| str | Cached result (computed once) |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
### `get_hardware_info`
|
||||
|
||||
1. If cached (`_CACHED_HW_INFO is not None`), return cached value immediately
|
||||
2. Detect OS via `os.name`:
|
||||
- **Windows (`nt`)**: PowerShell command querying WMI (Win32_Processor, Win32_VideoController, Win32_OperatingSystem, Disk serial)
|
||||
- **Linux/other**: shell commands (`lscpu`, `lspci`, `free`, block device serial)
|
||||
3. Parse output lines → extract CPU, GPU, memory, drive serial
|
||||
4. Format: `"CPU: {cpu}. GPU: {gpu}. Memory: {memory}. DriveSerial: {serial}"`
|
||||
5. Cache result in `_CACHED_HW_INFO`
|
||||
|
||||
The function uses `subprocess.check_output(shell=True)` — platform-specific shell commands.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: `constants` (for `log()`)
|
||||
- **External**: `os`, `subprocess` (stdlib)
|
||||
|
||||
## Consumers
|
||||
|
||||
- `api_client` — `load_bytes()` and `check_resource()` call `HardwareService.get_hardware_info()`
|
||||
|
||||
## Data Models
|
||||
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
|
||||
None. Hardware detection commands are hardcoded per platform.
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **OS commands**: Windows PowerShell (Get-CimInstance, Get-Disk) or Linux shell (lscpu, lspci, free, /sys/block)
|
||||
|
||||
## Security
|
||||
|
||||
Produces a hardware fingerprint used to bind encryption keys to specific machines. The fingerprint includes drive serial number, which acts as a machine-unique identifier.
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Module: main
|
||||
|
||||
## Purpose
|
||||
|
||||
FastAPI application entry point providing HTTP endpoints for health checks, authentication, encrypted resource loading/uploading, and a multi-step Docker image unlock workflow.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### FastAPI Application
|
||||
|
||||
`app = FastAPI(title="Azaion.Loader")`
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Request Body | Response | Description |
|
||||
|--------|------------------|---------------------|----------------------------|----------------------------------------------------|
|
||||
| GET | `/health` | — | `{"status": "healthy"}` | Liveness probe |
|
||||
| GET | `/status` | — | `StatusResponse` | Auth status + model cache dir |
|
||||
| POST | `/login` | `LoginRequest` | `{"status": "ok"}` | Set credentials on API client |
|
||||
| POST | `/load/{filename}`| `LoadRequest` | binary (octet-stream) | Download + decrypt resource |
|
||||
| POST | `/upload/{filename}`| multipart (file + folder) | `{"status": "ok"}` | Encrypt + upload resource (big/small split) |
|
||||
| POST | `/unlock` | `LoginRequest` | `{"state": "..."}` | Start background unlock workflow |
|
||||
| GET | `/unlock/status` | — | `{"state": "...", "error": ...}` | Poll unlock progress |
|
||||
|
||||
### Pydantic Models
|
||||
|
||||
| Model | Fields |
|
||||
|-----------------|----------------------------------------------|
|
||||
| LoginRequest | email: str, password: str |
|
||||
| LoadRequest | filename: str, folder: str |
|
||||
| HealthResponse | status: str |
|
||||
| StatusResponse | status: str, authenticated: bool, modelCacheDir: str |
|
||||
|
||||
### Module-level State
|
||||
|
||||
| Name | Type | Description |
|
||||
|----------------|--------------------|------------------------------------------------|
|
||||
| api_client | ApiClient or None | Lazy-initialized singleton |
|
||||
| unlock_state | UnlockState | Current unlock workflow state |
|
||||
| unlock_error | Optional[str] | Last unlock error message |
|
||||
| unlock_lock | threading.Lock | Thread safety for unlock state mutations |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
### `get_api_client()`
|
||||
Lazy singleton pattern: creates `ApiClient(RESOURCE_API_URL)` on first call.
|
||||
|
||||
### Unlock Workflow (`_run_unlock`)
|
||||
Background task (via FastAPI BackgroundTasks) that runs these steps:
|
||||
1. Check if Docker images already loaded → if yes, set `ready`
|
||||
2. Authenticate with API (login)
|
||||
3. Download key fragment from `/binary-split/key-fragment`
|
||||
4. Decrypt archive at `IMAGES_PATH` → `.tar`
|
||||
5. `docker load` the tar file
|
||||
6. Clean up tar file
|
||||
7. Set state to `ready` (or `error` on failure)
|
||||
|
||||
State transitions are guarded by `unlock_lock` (threading.Lock).
|
||||
|
||||
### `/unlock` Endpoint
|
||||
- If already `ready` → return immediately
|
||||
- If already in progress → return current state
|
||||
- If no encrypted archive found → check if images already loaded; if not, 404
|
||||
- Otherwise, starts `_run_unlock` as a background task
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: `unlock_state` (UnlockState enum), `api_client` (lazy import), `binary_split` (lazy import)
|
||||
- **External**: `os`, `threading` (stdlib), `fastapi`, `pydantic`
|
||||
|
||||
## Consumers
|
||||
|
||||
None — this is the entry point module.
|
||||
|
||||
## Data Models
|
||||
|
||||
`LoginRequest`, `LoadRequest`, `HealthResponse`, `StatusResponse` (Pydantic models defined inline).
|
||||
|
||||
## Configuration
|
||||
|
||||
| Env Variable | Default | Description |
|
||||
|------------------|--------------------------------|--------------------------------|
|
||||
| RESOURCE_API_URL | `https://api.azaion.com` | Azaion resource API base URL |
|
||||
| IMAGES_PATH | `/opt/azaion/images.enc` | Path to encrypted Docker images |
|
||||
| API_VERSION | `latest` | Expected Docker image version tag |
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **Azaion Resource API**: via `ApiClient` (authenticated resource download/upload)
|
||||
- **Docker CLI**: via `binary_split` (docker load, image inspect)
|
||||
- **File system**: encrypted archive at `IMAGES_PATH`
|
||||
|
||||
## Security
|
||||
|
||||
- Login endpoint returns 401 on auth failure
|
||||
- All resource endpoints use authenticated API client
|
||||
- Unlock state is thread-safe via `threading.Lock`
|
||||
- Lazy imports of Cython modules (`api_client`, `binary_split`) to avoid import-time side effects
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Module: security
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides AES-256-CBC encryption/decryption and multiple key derivation strategies for API resource protection and hardware-bound access control.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Classes
|
||||
|
||||
#### `Security` (cdef class)
|
||||
|
||||
All methods are `@staticmethod cdef` — Cython-only visibility, not callable from pure Python.
|
||||
|
||||
| Method | Signature | Description |
|
||||
|-----------------------------|-----------------------------------------------------------------|----------------------------------------------------------------------|
|
||||
| `encrypt_to` | `(input_bytes, key) -> bytes` | AES-256-CBC encrypt with random IV, PKCS7 padding; returns `IV + ciphertext` |
|
||||
| `decrypt_to` | `(ciphertext_with_iv_bytes, key) -> bytes` | AES-256-CBC decrypt; first 16 bytes = IV; manual PKCS7 unpad |
|
||||
| `get_hw_hash` | `(str hardware) -> str` | Derives hardware hash: `SHA-384("Azaion_{hardware}_%$$$)0_")` → base64 |
|
||||
| `get_api_encryption_key` | `(Credentials creds, str hardware_hash) -> str` | Derives per-user+hw key: `SHA-384("{email}-{password}-{hw_hash}-#%@AzaionKey@%#---")` → base64 |
|
||||
| `get_resource_encryption_key`| `() -> str` | Returns fixed shared key: `SHA-384("-#%@AzaionKey@%#---234sdfklgvhjbnn")` → base64 |
|
||||
| `calc_hash` | `(str key) -> str` | SHA-384 hash → base64 string |
|
||||
|
||||
### Module-level Constants
|
||||
|
||||
| Name | Value | Status |
|
||||
|-------------|----------|--------|
|
||||
| BUFFER_SIZE | `65536` | Unused — declared but never referenced |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
### Encryption (`encrypt_to`)
|
||||
1. SHA-256 hash of string key → 32-byte AES key
|
||||
2. Generate random 16-byte IV
|
||||
3. PKCS7-pad plaintext to 128-bit block size
|
||||
4. AES-CBC encrypt
|
||||
5. Return `IV || ciphertext`
|
||||
|
||||
### Decryption (`decrypt_to`)
|
||||
1. SHA-256 hash of string key → 32-byte AES key
|
||||
2. Split input: first 16 bytes = IV, rest = ciphertext
|
||||
3. AES-CBC decrypt
|
||||
4. Manual PKCS7 unpadding: read last byte as padding length; strip if 1–16
|
||||
|
||||
### Key Derivation Hierarchy
|
||||
- **Hardware hash**: salted hardware fingerprint → SHA-384 → base64
|
||||
- **API encryption key**: combines user credentials + hardware hash + salt → SHA-384 → base64 (per-download key)
|
||||
- **Resource encryption key**: fixed salt string → SHA-384 → base64 (shared key for big/small resource split)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: `credentials` (for `Credentials` type in `get_api_encryption_key`)
|
||||
- **External**: `base64`, `hashlib`, `os` (stdlib), `cryptography` (44.0.2)
|
||||
|
||||
## Consumers
|
||||
|
||||
- `api_client` — calls `encrypt_to`, `decrypt_to`, `get_hw_hash`, `get_api_encryption_key`, `get_resource_encryption_key`
|
||||
|
||||
## Data Models
|
||||
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
|
||||
None.
|
||||
|
||||
## Security
|
||||
|
||||
- AES-256-CBC with PKCS7 padding for data encryption
|
||||
- SHA-384 for key derivation (with various salts)
|
||||
- SHA-256 for AES key expansion from string keys
|
||||
- `get_resource_encryption_key()` uses a hardcoded salt — the key is static and shared across all users
|
||||
- `get_api_encryption_key()` binds encryption to user credentials + hardware — per-user, per-machine keys
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Module: unlock_state
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the state machine enum for the multi-step Docker image unlock workflow.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Enums
|
||||
|
||||
#### `UnlockState` (str, Enum)
|
||||
|
||||
| Value | String Representation |
|
||||
|------------------|-----------------------|
|
||||
| idle | `"idle"` |
|
||||
| authenticating | `"authenticating"` |
|
||||
| downloading_key | `"downloading_key"` |
|
||||
| decrypting | `"decrypting"` |
|
||||
| loading_images | `"loading_images"` |
|
||||
| ready | `"ready"` |
|
||||
| error | `"error"` |
|
||||
|
||||
Inherits from `str` and `Enum`, so `.value` returns the string name directly.
|
||||
|
||||
## Internal Logic
|
||||
|
||||
No logic — pure enum definition. State transitions are managed externally by `main.py`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: none (leaf module)
|
||||
- **External**: `enum` (stdlib)
|
||||
|
||||
## Consumers
|
||||
|
||||
- `main` — uses `UnlockState` to track and report the unlock workflow progress
|
||||
|
||||
## Data Models
|
||||
|
||||
`UnlockState` is the data model.
|
||||
|
||||
## Configuration
|
||||
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
|
||||
None.
|
||||
|
||||
## Security
|
||||
|
||||
None.
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Module: user
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the authenticated user model and role enumeration for authorization decisions.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### Enums
|
||||
|
||||
#### `RoleEnum` (cdef enum)
|
||||
|
||||
| Value | Numeric |
|
||||
|------------------|---------|
|
||||
| NONE | 0 |
|
||||
| Operator | 10 |
|
||||
| Validator | 20 |
|
||||
| CompanionPC | 30 |
|
||||
| Admin | 40 |
|
||||
| ResourceUploader | 50 |
|
||||
| ApiAdmin | 1000 |
|
||||
|
||||
### Classes
|
||||
|
||||
#### `User` (cdef class)
|
||||
|
||||
| Attribute | Type | Visibility |
|
||||
|-----------|----------|------------|
|
||||
| id | str | public |
|
||||
| email | str | public |
|
||||
| role | RoleEnum | public |
|
||||
|
||||
| Method | Signature | Description |
|
||||
|------------|---------------------------------------------------|-------------|
|
||||
| `__init__` | `(self, str id, str email, RoleEnum role)` | Constructor |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
No logic — pure data class with enum.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: none (leaf module)
|
||||
- **External**: none
|
||||
|
||||
## Consumers
|
||||
|
||||
- `api_client` — creates `User` instances from JWT claims in `set_token()`, maps role strings to `RoleEnum`
|
||||
|
||||
## Data Models
|
||||
|
||||
`RoleEnum` + `User` are the data models.
|
||||
|
||||
## Configuration
|
||||
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
|
||||
None.
|
||||
|
||||
## Security
|
||||
|
||||
Role hierarchy is implicit in numeric values but no authorization enforcement logic exists here.
|
||||
|
||||
## Tests
|
||||
|
||||
No tests found.
|
||||
Reference in New Issue
Block a user