HTTPS/CORS improvements: Enable CORS for player connections, secure session cookies, add certificate endpoint, nginx CORS headers

This commit is contained in:
Deployment System
2026-01-16 22:29:49 +02:00
parent cf44843418
commit c4e43ce69b
15 changed files with 3497 additions and 0 deletions

View File

@@ -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 │
└──────────────────┴──────────────────────┴─────────────────────┘
```