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,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
|
||||
|
||||
Reference in New Issue
Block a user