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,583 @@
# Kiwy-Signage Player - HTTPS/SSL Certificate Analysis
## Executive Summary
The Kiwy-Signage player is a Python-based digital signage application built with Kivy that communicates with the DigiServer v2 backend. **The player currently has NO custom SSL certificate verification mechanism and relies entirely on Python's `requests` library default behavior.**
This means:
- ✅ HTTPS connections to production servers work because they have valid CA-signed certificates
- ❌ Self-signed certificates or custom certificate authorities will **FAIL** without code modifications
- ❌ No `verify` parameter is passed to any requests calls (uses default `verify=True`)
- ❌ No support for custom CA certificates or certificate bundles
---
## 1. HTTP Client Library & Dependencies
### Library Used
- **requests** (version 2.32.4) - Python HTTP library with SSL verification enabled by default
- **aiohttp** (version 3.9.1) - Not currently used for player authentication/API calls
### Dependency Chain
```
requirements.txt:
- kivy>=2.3.0
- ffpyplayer
- requests==2.32.4 ← Used for ALL HTTPS requests
- bcrypt==4.2.1
- aiohttp==3.9.1
- asyncio==3.4.3
```
---
## 2. Main Connection Files & Locations
### Core Authentication Module
**File:** [src/player_auth.py](../../tmp/Kiwy-Signage/src/player_auth.py)
**Lines:** 352 lines total
**Responsibility:** Handles all server authentication and API communication
### Playlist Management
**File:** [src/get_playlists_v2.py](../../tmp/Kiwy-Signage/src/get_playlists_v2.py)
**Lines:** 352 lines total
**Responsibility:** Fetches and manages playlists, uses PlayerAuth for communication
### Network Monitoring
**File:** [src/network_monitor.py](../../tmp/Kiwy-Signage/src/network_monitor.py)
**Lines:** 235 lines total
**Responsibility:** Monitors connectivity using ping (not HTTPS), manages WiFi restarts
### Main GUI Application
**File:** [src/main.py](../../tmp/Kiwy-Signage/src/main.py)
**Lines:** 1,826 lines total
**Responsibility:** Kivy GUI, server connection settings, calls PlayerAuth for authentication
### Configuration File
**File:** [config/app_config.json](../../tmp/Kiwy-Signage/config/app_config.json)
**Responsibility:** Stores server IP, port, player credentials, and settings
---
## 3. HTTPS Connection Architecture
### Authentication Flow
```
1. Player Configuration (config/app_config.json)
├─ server_ip: "digi-signage.moto-adv.com"
├─ port: "443"
├─ screen_name: "player-name"
└─ quickconnect_key: "QUICK123"
2. URL Construction (src/main.py, lines 696-703)
├─ If server_ip has http:// or https:// prefix, use as-is
├─ Otherwise: protocol = "https" if port == "443" else "http"
└─ server_url = f"{protocol}://{server_ip}:{port}"
3. Authentication Request (src/player_auth.py, lines 95-98)
├─ POST /api/auth/player
├─ Payload: {hostname, password, quickconnect_code}
└─ Returns: {auth_code, player_id, player_name, playlist_id, ...}
4. Authenticated API Calls (src/player_auth.py, lines 159-163, etc.)
├─ Headers: Authorization: Bearer {auth_code}
└─ GET/POST to various /api/... endpoints
```
### All HTTPS Request Points in Code
#### 1. **Authentication** (src/player_auth.py)
**Location:** [Line 95](../../tmp/Kiwy-Signage/src/player_auth.py#L95)
```python
response = requests.post(auth_url, json=payload, timeout=timeout)
```
- **URL:** `{server_url}/api/auth/player`
- **Method:** POST
- **Auth:** None (initial auth)
- **SSL Verify:** DEFAULT (True, no custom handling)
**Location:** [Line 157](../../tmp/Kiwy-Signage/src/player_auth.py#L157)
```python
response = requests.post(verify_url, json=payload, timeout=timeout)
```
- **URL:** `{server_url}/api/auth/verify`
- **Method:** POST
- **Auth:** None
- **SSL Verify:** DEFAULT (True)
#### 2. **Playlist Fetching** (src/player_auth.py)
**Location:** [Line 178](../../tmp/Kiwy-Signage/src/player_auth.py#L178)
```python
response = requests.get(playlist_url, headers=headers, timeout=timeout)
```
- **URL:** `{server_url}/api/playlists/{player_id}`
- **Method:** GET
- **Auth:** Bearer token in Authorization header
- **Headers:** `Authorization: Bearer {auth_code}`
- **SSL Verify:** DEFAULT (True, **NO `verify=` parameter**)
#### 3. **Heartbeat/Status** (src/player_auth.py)
**Location:** [Line 227](../../tmp/Kiwy-Signage/src/player_auth.py#L227)
```python
response = requests.post(heartbeat_url, headers=headers, json=payload, timeout=timeout)
```
- **URL:** `{server_url}/api/players/{player_id}/heartbeat`
- **Method:** POST
- **Auth:** Bearer token
- **SSL Verify:** DEFAULT (True, **NO `verify=` parameter**)
#### 4. **Player Feedback** (src/player_auth.py)
**Location:** [Line 254](../../tmp/Kiwy-Signage/src/player_auth.py#L254)
```python
response = requests.post(feedback_url, headers=headers, json=payload, timeout=timeout)
```
- **URL:** `{server_url}/api/player-feedback`
- **Method:** POST
- **Auth:** Bearer token
- **SSL Verify:** DEFAULT (True, **NO `verify=` parameter**)
#### 5. **Media Download** (src/get_playlists_v2.py)
**Location:** [Line 159](../../tmp/Kiwy-Signage/src/get_playlists_v2.py#L159)
```python
response = requests.get(file_url, timeout=30)
```
- **URL:** Direct to media file URLs from playlist
- **Method:** GET
- **Auth:** None (public download URLs)
- **SSL Verify:** DEFAULT (True, **NO `verify=` parameter**)
---
## 4. Certificate Verification Current Configuration
### Current SSL/Certificate Behavior
**Summary:** Relies entirely on Python's `requests` library defaults.
**Default requests behavior:**
- `verify=True` (implicitly used when not specified)
- Uses system CA certificate store
- Validates certificate chain, hostname, and expiration
- Rejects self-signed certificates with error
### Hardcoded Certificate Settings
🔴 **NONE** - No hardcoded SSL certificate settings exist in the codebase.
### Certificate Verification Code Locations
**Search Results for "verify", "ssl", "cert", "certificate":**
Only `verify_auth()` method found (authenticates with server, not certificate verification):
- [src/player_auth.py, Line 137](../../tmp/Kiwy-Signage/src/player_auth.py#L137) - `def verify_auth(self, timeout: int = 10)`
- [src/player_auth.py, Line 153](../../tmp/Kiwy-Signage/src/player_auth.py#L153) - `verify_url = f"{server_url}/api/auth/verify"`
**No SSL/certificate configuration found in:**
- ❌ requests library verify parameter
- ❌ Custom CA bundle paths
- ❌ SSL context configuration
- ❌ Certificate pinning
- ❌ urllib3 certificate settings
---
## 5. Self-Signed Certificate Support
### Current State: ❌ NOT SUPPORTED
When connecting to a server with a self-signed certificate:
```python
# Current code (player_auth.py, Line 95):
response = requests.post(auth_url, json=payload, timeout=timeout)
# Will raise:
# requests.exceptions.SSLError:
# ("certificate verify failed: self signed certificate (_ssl.c:...)
```
### Exception Handling
The code catches exceptions but doesn't differentiate SSL errors:
```python
# player_auth.py, lines 111-127
except requests.exceptions.ConnectionError:
error_msg = "Cannot connect to server"
except requests.exceptions.Timeout:
error_msg = "Connection timeout"
except Exception as e:
error_msg = f"Authentication error: {str(e)}"
# Will catch SSL errors here but label them as generic "Authentication error"
```
---
## 6. Required Changes for Self-Signed Certificate Support
### Option 1: Disable Certificate Verification (⚠️ INSECURE - Development Only)
**Not Recommended for Production**
Add to each `requests` call:
```python
verify=False # Disables SSL certificate verification
```
**Example modification:**
```python
# OLD (player_auth.py, Line 95):
response = requests.post(auth_url, json=payload, timeout=timeout)
# NEW:
response = requests.post(auth_url, json=payload, timeout=timeout, verify=False)
```
**Locations requiring modification (5 places):**
1. [src/player_auth.py, Line 95](../../tmp/Kiwy-Signage/src/player_auth.py#L95) - authenticate() method
2. [src/player_auth.py, Line 157](../../tmp/Kiwy-Signage/src/player_auth.py#L157) - verify_auth() method
3. [src/player_auth.py, Line 178](../../tmp/Kiwy-Signage/src/player_auth.py#L178) - get_playlist() method
4. [src/player_auth.py, Line 227](../../tmp/Kiwy-Signage/src/player_auth.py#L227) - send_heartbeat() method
5. [src/player_auth.py, Line 254](../../tmp/Kiwy-Signage/src/player_auth.py#L254) - send_feedback() method
6. [src/get_playlists_v2.py, Line 159](../../tmp/Kiwy-Signage/src/get_playlists_v2.py#L159) - download_media_files() method
---
### Option 2: Custom CA Certificate Bundle (✅ RECOMMENDED)
**Production-Ready Approach**
#### Step 1: Create certificate configuration
```python
# New file: src/ssl_config.py
import os
import requests
class SSLConfig:
"""Manage SSL certificate verification for self-signed certs"""
@staticmethod
def get_ca_bundle():
"""Get path to CA certificate bundle
Returns:
str: Path to CA bundle or True for default system certs
"""
# Priority order:
# 1. Custom CA bundle in config directory
# 2. CA bundle path from environment variable
# 3. System default CA bundle (requests uses certifi)
custom_ca = 'config/ca_bundle.crt'
if os.path.exists(custom_ca):
return custom_ca
env_ca = os.environ.get('REQUESTS_CA_BUNDLE')
if env_ca and os.path.exists(env_ca):
return env_ca
return True # Use system/certifi default
@staticmethod
def get_verify_setting():
"""Get SSL verification setting
Returns:
bool or str: Path to CA bundle or True/False
"""
return SSLConfig.get_ca_bundle()
```
#### Step 2: Modify PlayerAuth to use custom certificates
```python
# player_auth.py modifications:
from ssl_config import SSLConfig # Add import
class PlayerAuth:
def __init__(self, config_file='player_auth.json'):
self.config_file = config_file
self.auth_data = self._load_auth_data()
self.verify_ssl = SSLConfig.get_verify_setting() # Add this
def authenticate(self, ...):
# Add verify parameter to requests call:
response = requests.post(
auth_url,
json=payload,
timeout=timeout,
verify=self.verify_ssl # ADD THIS
)
def verify_auth(self, ...):
response = requests.post(
verify_url,
json=payload,
timeout=timeout,
verify=self.verify_ssl # ADD THIS
)
def get_playlist(self, ...):
response = requests.get(
playlist_url,
headers=headers,
timeout=timeout,
verify=self.verify_ssl # ADD THIS
)
def send_heartbeat(self, ...):
response = requests.post(
heartbeat_url,
headers=headers,
json=payload,
timeout=timeout,
verify=self.verify_ssl # ADD THIS
)
def send_feedback(self, ...):
response = requests.post(
feedback_url,
headers=headers,
json=payload,
timeout=timeout,
verify=self.verify_ssl # ADD THIS
)
```
#### Step 3: Handle media downloads
```python
# get_playlists_v2.py modifications:
from ssl_config import SSLConfig
def download_media_files(playlist, media_dir):
verify_ssl = SSLConfig.get_verify_setting() # Add this
for media in playlist:
...
response = requests.get(
file_url,
timeout=30,
verify=verify_ssl # ADD THIS
)
...
```
#### Step 4: Prepare CA certificate
1. **Export certificate from self-signed server:**
```bash
openssl s_client -connect server.local:443 -showcerts < /dev/null | \
openssl x509 -outform PEM > ca_bundle.crt
```
2. **Place in player config:**
```bash
cp ca_bundle.crt /path/to/Kiwy-Signage/config/ca_bundle.crt
```
3. **Or set environment variable:**
```bash
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt
```
---
### Option 3: Certificate Pinning (⚠️ Advanced)
For maximum security when using self-signed certificates:
```python
import ssl
import certifi
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
class SSLPinningAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ctx = create_urllib3_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
# Or use specific certificate:
# ctx.load_verify_locations('config/server_cert.pem')
kwargs['ssl_context'] = ctx
return super().init_poolmanager(*args, **kwargs)
# Usage in PlayerAuth:
session = requests.Session()
session.mount('https://', SSLPinningAdapter())
response = session.post(auth_url, json=payload, timeout=timeout)
```
---
## 7. Testing Self-Signed Certificate Connections
### Before Modification (Current Behavior)
Test connection to self-signed server:
```bash
cd /tmp/Kiwy-Signage
python3 -c "
import requests
url = 'https://your-self-signed-server:443/api/health'
try:
response = requests.get(url)
print('Connection successful')
except requests.exceptions.SSLError as e:
print(f'SSL Error: {e}')
"
# Output: SSL Error: certificate verify failed
```
### After Modification (With Custom CA)
```bash
cd /tmp/Kiwy-Signage
# Place ca_bundle.crt in config/
python3 -c "
import requests
url = 'https://your-self-signed-server:443/api/health'
response = requests.get(url, verify='config/ca_bundle.crt')
print(f'Connection successful: {response.status_code}')
"
# Output: Connection successful: 200
```
---
## 8. Summary Table
| Aspect | Current State | Support Level |
|--------|---------------|----------------|
| **HTTP Client** | requests 2.32.4 | ✅ Production-ready |
| **HTTPS Support** | Yes (standard URLs) | ✅ Full |
| **Self-Signed Certs** | ❌ NO | ❌ NOT SUPPORTED |
| **Custom CA Bundle** | ❌ NO | ❌ NOT SUPPORTED |
| **Certificate Pinning** | ❌ NO | ❌ NOT SUPPORTED |
| **SSL Verify Parameter** | Default (True) | ⚠️ All requests use default |
| **Hardcoded Settings** | None | - |
| **Environment Variables** | Not checked | ⚠️ Could be added |
| **Configuration File** | app_config.json (no SSL options) | ⚠️ Could be extended |
---
## 9. Integration with DigiServer v2
### Current Communication Protocol
The player communicates with DigiServer v2 using:
1. **Initial Authentication (HTTP/HTTPS)**
- Endpoint: `POST /api/auth/player`
- Payload: `{hostname, password, quickconnect_code}`
- Response: `{auth_code, player_id, player_name, ...}`
2. **All Subsequent Requests (HTTP/HTTPS)**
- Header: `Authorization: Bearer {auth_code}`
- Endpoints:
- `GET /api/playlists/{player_id}`
- `POST /api/players/{player_id}/heartbeat`
- `POST /api/player-feedback`
3. **Media Downloads (HTTP/HTTPS)**
- Direct URLs from playlist: `{server_url}/uploads/...`
### Server Configuration (config/app_config.json)
```json
{
"server_ip": "digi-signage.moto-adv.com",
"port": "443",
"screen_name": "tv-terasa",
"quickconnect_key": "8887779",
"orientation": "Landscape",
"touch": "True",
"max_resolution": "1920x1080",
"edit_feature_enabled": true
}
```
### ⚠️ NOTE: No SSL/certificate options in config
The application accepts server_ip, port, hostname, and credentials, but:
- ❌ No way to specify CA certificate path
- ❌ No way to disable SSL verification
- ❌ No way to enable certificate pinning
---
## 10. Recommended Implementation Plan
### For Self-Signed Certificate Support:
**Step 1: Add SSL Configuration Module** (5-10 min)
- Create `src/ssl_config.py` with SSLConfig class
- Support for custom CA bundle path
**Step 2: Modify PlayerAuth** (10-15 min)
- Add `verify_ssl` parameter to `__init__`
- Update all 5 `requests` calls to include `verify=self.verify_ssl`
- Improve SSL error handling/reporting
**Step 3: Update Configuration** (5 min)
- Extend `config/app_config.json` to include optional `ca_bundle_path`
- Or use environment variable `REQUESTS_CA_BUNDLE`
**Step 4: Documentation** (5 min)
- Add README section on SSL certificate configuration
- Document how to export and place CA certificates
**Step 5: Testing** (10-15 min)
- Test with self-signed certificate
- Verify backward compatibility with valid CA certs
**Total Time Estimate:** 35-50 minutes for complete implementation
---
## 11. Code References
### All requests calls in codebase:
```
src/player_auth.py:
Line 95: requests.post(auth_url, json=payload, timeout=timeout)
Line 157: requests.post(verify_url, json=payload, timeout=timeout)
Line 178: requests.get(playlist_url, headers=headers, timeout=timeout)
Line 227: requests.post(heartbeat_url, headers=headers, json=payload, timeout=timeout)
Line 254: requests.post(feedback_url, headers=headers, json=payload, timeout=timeout)
src/get_playlists_v2.py:
Line 159: requests.get(file_url, timeout=30)
working_files/test_direct_api.py:
Line 32: requests.get(url, headers=headers, timeout=10)
working_files/get_playlists.py:
Line 101: requests.post(feedback_url, json=feedback_data, timeout=10)
Line 131: requests.get(server_url, params=params)
Line 139: requests.get(file_url, timeout=10)
```
All calls use default `verify=True` (implicit).
---
## Conclusion
The Kiwy-Signage player is a well-structured Python application that properly uses the `requests` library for HTTPS communication. However, it currently **does not support self-signed certificates or custom certificate authorities** without code modifications.
To support self-signed certificates, implementing Option 2 (Custom CA Certificate Bundle) is recommended as it:
- ✅ Maintains security for production deployments
- ✅ Allows flexibility for self-signed/internal CAs
- ✅ Requires minimal code changes (5-6 request calls)
- ✅ Follows Python best practices
- ✅ Is backward compatible with existing deployments