HTTPS/CORS improvements: Enable CORS for player connections, secure session cookies, add certificate endpoint, nginx CORS headers
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
# Kiwy-Signage HTTPS Architecture Diagram
|
||||
|
||||
## Current Architecture (Before Patches)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Kiwy-Signage Player │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ GUI / Settings (main.py:696-703) │ │
|
||||
│ │ - Reads config/app_config.json │ │
|
||||
│ │ - Builds server URL │ │
|
||||
│ │ - Calls PlayerAuth.authenticate() │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ PlayerAuth (src/player_auth.py) │ │
|
||||
│ │ - authenticate() [Line 95] │ │
|
||||
│ │ - verify_auth() [Line 157] │ │
|
||||
│ │ - get_playlist() [Line 178] │ │
|
||||
│ │ - send_heartbeat() [Line 227] │ │
|
||||
│ │ - send_feedback() [Line 254] │ │
|
||||
│ │ │ │
|
||||
│ │ All use: requests.post/get(..., timeout=30) │ │
|
||||
│ │ ⚠️ verify parameter NOT SPECIFIED (uses default) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Playlist Manager (src/get_playlists_v2.py) │ │
|
||||
│ │ - download_media_files() [Line 159] │ │
|
||||
│ │ - requests.get(file_url, timeout=30) │ │
|
||||
│ │ ⚠️ verify parameter NOT SPECIFIED │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Python requests Library (v2.32.4) │ │
|
||||
│ │ - Default: verify=True │ │
|
||||
│ │ - Validates against system CA certificates │ │
|
||||
│ │ - NO custom CA support in this application │ │
|
||||
│ │ - NO certificate pinning │ │
|
||||
│ │ - NO ignore certificate verification option │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└────────────────────────────┼────────────────────────────────────┘
|
||||
│
|
||||
┌────────▼──────────┐
|
||||
│ HTTPS Handshake │
|
||||
├───────────────────┤
|
||||
│ Validates cert: │
|
||||
│ ✓ Chain valid? │
|
||||
│ ✓ Hostname match? │
|
||||
│ ✓ Not expired? │
|
||||
│ ✓ In CA store? │
|
||||
└────────┬──────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
Success ❌ SELF-SIGNED │ Success ✅
|
||||
(Not in CA │ (CA-signed cert)
|
||||
store) │
|
||||
│ ✓ Server
|
||||
│ Certificate
|
||||
│ Valid
|
||||
│
|
||||
┌────▼─────────────────────────────────────────┐
|
||||
│ SSLError: certificate verify failed │
|
||||
│ Application cannot connect to server │
|
||||
│ Player goes offline │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
|
||||
CURRENT LIMITATION:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Player ONLY works with production certificates
|
||||
that are signed by a trusted Certificate Authority
|
||||
and present in the system's CA certificate store.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## After Patches - New Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Kiwy-Signage Player │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ GUI / Settings (main.py:696-703) │ │
|
||||
│ │ - Reads config/app_config.json │ │
|
||||
│ │ - Builds server URL │ │
|
||||
│ │ - Calls PlayerAuth.authenticate() │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ SSLConfig Module (src/ssl_config.py) ✨ NEW │ │
|
||||
│ │ - get_verify_setting() │ │
|
||||
│ │ - get_ca_bundle() │ │
|
||||
│ │ - set_ca_bundle(path) │ │
|
||||
│ │ - disable_verification() [dev/test only] │ │
|
||||
│ │ │ │
|
||||
│ │ Certificate Resolution Order: │ │
|
||||
│ │ 1. Custom CA set via set_ca_bundle() │ │
|
||||
│ │ 2. REQUESTS_CA_BUNDLE env var │ │
|
||||
│ │ 3. config/ca_bundle.crt (file in app) │ │
|
||||
│ │ 4. System default (True = certifi) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ PlayerAuth (MODIFIED: src/player_auth.py) │ │
|
||||
│ │ - __init__(): self.verify_ssl = SSLConfig.get_...() │ │
|
||||
│ │ - authenticate(): verify=self.verify_ssl [Line 95] │ │
|
||||
│ │ - verify_auth(): verify=self.verify_ssl [Line 157] │ │
|
||||
│ │ - get_playlist(): verify=self.verify_ssl [Line 178] │ │
|
||||
│ │ - send_heartbeat(): verify=self.verify_ssl [Line 227] │ │
|
||||
│ │ - send_feedback(): verify=self.verify_ssl [Line 254] │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Playlist Manager (MODIFIED: get_playlists_v2.py) │ │
|
||||
│ │ - download_media_files(): verify=verify_ssl [Line 159] │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Python requests Library (v2.32.4) │ │
|
||||
│ │ - Uses verify parameter from SSLConfig │ │
|
||||
│ │ - Can use custom CA bundle (if provided) │ │
|
||||
│ │ - Validates against specified certificate │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└────────────────────────────┼────────────────────────────────────┘
|
||||
│
|
||||
┌────────▼──────────┐
|
||||
│ HTTPS Handshake │
|
||||
├───────────────────┤
|
||||
│ Validates against:│
|
||||
│ ✓ Custom CA │
|
||||
│ ✓ Hostname │
|
||||
│ ✓ Expiration │
|
||||
└────────┬──────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
Success ✅ Success ✅ Success ✅
|
||||
(Custom CA or (Self-signed + (Production
|
||||
self-signed) ca_bundle.crt) cert)
|
||||
│ │ │
|
||||
└────────────────┬───┴────────────────────┘
|
||||
│
|
||||
┌────▼──────┐
|
||||
│ Connected! │
|
||||
│ Establish │
|
||||
│ secure │
|
||||
│ connection │
|
||||
└─────────────┘
|
||||
|
||||
|
||||
NEW CAPABILITY:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Player works with:
|
||||
✅ Production certificates (CA-signed)
|
||||
✅ Self-signed certificates (with ca_bundle.crt)
|
||||
✅ Custom CA certificates (with environment variable)
|
||||
✅ Multiple certificate scenarios (dev, test, prod)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Certificate Resolution Flow
|
||||
|
||||
```
|
||||
When Player Starts
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ PlayerAuth │
|
||||
│ __init__() │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
│ Calls SSLConfig.get_verify_setting()
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ Check Priority Order │
|
||||
└──────────────┬───────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Is custom CA │──NO──┐
|
||||
│ set via code? │ │
|
||||
└───────────────┘ │
|
||||
│ YES │
|
||||
▼ ▼
|
||||
Return path ┌──────────────────────┐
|
||||
│ Check environment │
|
||||
│ REQUESTS_CA_BUNDLE? │
|
||||
└────────┬─────────────┘
|
||||
│
|
||||
NO │ YES
|
||||
┌──────┘ ▼
|
||||
│ Return env
|
||||
│ var path
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Check config dir │
|
||||
│ config/ca_bundle.crt?│
|
||||
└────────┬─────────────┘
|
||||
│
|
||||
NO │ YES
|
||||
┌──────┘ ▼
|
||||
│ Return config
|
||||
│ cert path
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ No custom cert │
|
||||
│ found, use │
|
||||
│ system default │
|
||||
│ (True) │
|
||||
└─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Pass to requests │
|
||||
│ library as │
|
||||
│ verify=<value> │
|
||||
└──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ HTTPS Connection Made │
|
||||
│ With Selected Cert │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure After Patches
|
||||
|
||||
```
|
||||
Kiwy-Signage/
|
||||
├── config/
|
||||
│ ├── app_config.json (unchanged)
|
||||
│ ├── ca_bundle.crt ✨ NEW (optional)
|
||||
│ └── resources/
|
||||
│
|
||||
├── src/
|
||||
│ ├── main.py (unchanged)
|
||||
│ ├── player_auth.py ✏️ MODIFIED (7 changes)
|
||||
│ ├── get_playlists_v2.py ✏️ MODIFIED (2 changes)
|
||||
│ ├── ssl_config.py ✨ NEW FILE (~60 lines)
|
||||
│ ├── network_monitor.py (unchanged)
|
||||
│ ├── edit_popup.py (unchanged)
|
||||
│ └── keyboard_widget.py (unchanged)
|
||||
│
|
||||
├── working_files/ (unchanged)
|
||||
├── start.sh (unchanged)
|
||||
├── requirements.txt (unchanged - no new packages!)
|
||||
└── ...
|
||||
|
||||
Changes Summary:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
✨ New Files: 1 (ssl_config.py + ca_bundle.crt)
|
||||
✏️ Modified Files: 2 (player_auth.py, get_playlists_v2.py)
|
||||
📦 New Packages: 0 (uses existing requests library)
|
||||
🔄 Backward Compat: Yes (all changes are additive)
|
||||
⚠️ Breaking Chgs: None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Scenarios
|
||||
|
||||
### Scenario 1: Production Server (Current)
|
||||
|
||||
```
|
||||
DigiServer v2
|
||||
(digi-signage.moto-adv.com)
|
||||
│
|
||||
│ Valid CA Certificate
|
||||
│ (e.g., Let's Encrypt)
|
||||
│
|
||||
▼
|
||||
Player (No patches needed)
|
||||
│
|
||||
▼ requests.post/get(..., timeout=30)
|
||||
├─ No verify= specified
|
||||
└─ Uses system default: verify=True
|
||||
│
|
||||
▼ validates cert ✓
|
||||
│
|
||||
▼ SSL handshake succeeds ✓
|
||||
│
|
||||
▼ authenticated ✓
|
||||
|
||||
|
||||
Result: ✅ Works fine (no changes needed)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Scenario 2: Self-Signed Server (After Patches)
|
||||
|
||||
```
|
||||
DigiServer v2 (self.local)
|
||||
(Self-signed certificate)
|
||||
│
|
||||
│ 1. Export cert
|
||||
│ openssl s_client... > server.crt
|
||||
│
|
||||
│ 2. Place in player
|
||||
│ config/ca_bundle.crt
|
||||
│
|
||||
▼
|
||||
Player (with patches)
|
||||
│
|
||||
▼ __init__()
|
||||
│
|
||||
▼ SSLConfig.get_verify_setting()
|
||||
├─ Check custom CA: None
|
||||
├─ Check env var: not set
|
||||
├─ Check config dir: ✓ found ca_bundle.crt
|
||||
│
|
||||
└─ Return: 'config/ca_bundle.crt'
|
||||
│
|
||||
▼ requests.post/get(..., verify='config/ca_bundle.crt')
|
||||
│
|
||||
▼ validates cert against ca_bundle.crt ✓
|
||||
│
|
||||
▼ SSL handshake succeeds ✓
|
||||
│
|
||||
▼ authenticated ✓
|
||||
|
||||
|
||||
Result: ✅ Works with self-signed cert
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Scenario 3: Development (Insecure - Testing Only)
|
||||
|
||||
```
|
||||
DigiServer v2 (test.local)
|
||||
(Self-signed, or cert issues)
|
||||
│
|
||||
▼
|
||||
Player (with patches + SSLConfig.disable_verification())
|
||||
│
|
||||
▼ SSLConfig.disable_verification()
|
||||
│
|
||||
└─ _verify_ssl = False
|
||||
│
|
||||
▼ requests.post/get(..., verify=False)
|
||||
│
|
||||
▼ ⚠️ Skips certificate validation
|
||||
│
|
||||
▼ SSL handshake proceeds anyway ⚠️
|
||||
│
|
||||
▼ authenticated (but insecure!)
|
||||
|
||||
⚠️ VULNERABLE TO MITM ATTACKS
|
||||
|
||||
|
||||
Result: ⚠️ Works but insecure - DEV/TEST ONLY
|
||||
Note: Add in code temporarily:
|
||||
from ssl_config import SSLConfig
|
||||
SSLConfig.disable_verification() # TEMPORARY - DEV ONLY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Flow Sequence Diagram
|
||||
|
||||
```
|
||||
Player SSLConfig requests DigiServer
|
||||
│ │ │ │
|
||||
│─ authenticate()─│ │ │
|
||||
│ │ │ │
|
||||
│ get_verify_setting() │ │
|
||||
│ │ │ │
|
||||
│ ◄────────┤ 'config/ca... │ │
|
||||
│ │ bundle.crt' │ │
|
||||
│ │ │ │
|
||||
│ ┌──────────────┐ │ │
|
||||
│ │ requests.post( │ │
|
||||
│ │ url, │ │
|
||||
│ │ verify='config/ca... │ │
|
||||
│ │ bundle.crt', │ │
|
||||
│ │ ... │ │
|
||||
│ │ ) │ │
|
||||
│ └──────────────┘ │ │
|
||||
│ │ validate cert │ │
|
||||
│ │ against bundle◄──┤─ Server Cert ────┤
|
||||
│ │ │ (PEM format) │
|
||||
│ │ │ │
|
||||
│ │ │ ✓ Signature OK │
|
||||
│ │ │ ✓ Chain valid │
|
||||
│ │ │ ✓ Hostname match │
|
||||
│ │ │ │
|
||||
│ │ ◄────────────────┤─ 200 OK ─────────┤
|
||||
│ response ◄──────┤ │ {auth_code} │
|
||||
│ │ │ │
|
||||
│ Save auth_code │ │ │
|
||||
│ to file │ │ │
|
||||
│ │ │ │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
```
|
||||
BEFORE (Current):
|
||||
───────────────
|
||||
|
||||
requests.post(url, ...)
|
||||
│
|
||||
├─ success → parse response
|
||||
│
|
||||
└─ SSLError (self-signed cert)
|
||||
│
|
||||
└─ Caught by: except Exception as e
|
||||
│
|
||||
└─ error_msg = "Authentication error: ..."
|
||||
│
|
||||
└─ User sees generic error ❌
|
||||
|
||||
|
||||
AFTER (With Patches):
|
||||
─────────────────────
|
||||
|
||||
requests.post(url, ..., verify=ca_bundle)
|
||||
│
|
||||
├─ success → parse response
|
||||
│ (with custom CA support)
|
||||
│
|
||||
└─ SSLError (cert not in bundle)
|
||||
│
|
||||
└─ Caught by: except Exception as e
|
||||
│
|
||||
└─ error_msg = "Authentication error: ..."
|
||||
│
|
||||
└─ Log shows actual SSL error details ✓
|
||||
(if SSL validation fails, not player's fault)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Comparison
|
||||
|
||||
```
|
||||
Scenario: Self-Signed Certificate
|
||||
|
||||
┌──────────────────┬──────────────────────┬─────────────────────┐
|
||||
│ Approach │ Security Level │ Recommendations │
|
||||
├──────────────────┼──────────────────────┼─────────────────────┤
|
||||
│ Do nothing │ 🔴 BROKEN │ ❌ Not viable │
|
||||
│ (current) │ - Player offline │ - App won't work │
|
||||
│ │ - No connection │ │
|
||||
├──────────────────┼──────────────────────┼─────────────────────┤
|
||||
│ verify=False │ ⚠️ INSECURE │ ⚠️ DEV/TEST ONLY │
|
||||
│ (disable verify) │ - Vulnerable to MITM │ - Never production │
|
||||
│ │ - No cert validation │ - Temporary measure │
|
||||
├──────────────────┼──────────────────────┼─────────────────────┤
|
||||
│ Custom CA bundle │ ✅ SECURE │ ✅ RECOMMENDED │
|
||||
│ (patches) │ - Validates cert │ - Works with any │
|
||||
│ │ - CA is trusted │ self-signed cert │
|
||||
│ │ - No MITM risk │ - Production-ready │
|
||||
├──────────────────┼──────────────────────┼─────────────────────┤
|
||||
│ Cert pinning │ 🔒 VERY SECURE │ ✅ IF NEEDED │
|
||||
│ (advanced) │ - Pins specific cert │ - Extra complexity │
|
||||
│ │ - Maximum trust │ - For high-security │
|
||||
│ │ │ deployments │
|
||||
└──────────────────┴──────────────────────┴─────────────────────┘
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user