HTTPS/SSL Support and Password Obfuscation Control#13
HTTPS/SSL Support and Password Obfuscation Control#13gierwialo wants to merge 3 commits intopuddly:devfrom
Conversation
puddly
left a comment
There was a problem hiding this comment.
Thanks! I've left a few comments about the code.
Is the password obfuscation setting something we can autodetect? Possibly from the API version (if that's exposed unauthenticated) or just by trying both approaches?
nanokvm/client.py
Outdated
| Set to False to disable verification for self-signed certificates. | ||
| ssl_ca_cert: Path to custom CA certificate bundle file for SSL verification. | ||
| Useful for self-signed certificates or private CAs. | ||
| use_password_obfuscation: Use password obfuscation/encryption (default: True). |
There was a problem hiding this comment.
Can this be autodetected if the URL is https://? Or can we automatically try both obfuscated and unobfuscated?
There was a problem hiding this comment.
Hah, good question! I looked into this but there's no unauthenticated endpoint that exposes the API/firmware
version - /application/version and /vm/info both require a token, so we can't detect the version before login.
Your suggestion: autodetect based on URL scheme is simple, but not 100% reliable. The use_password_obfuscation parameter would still be available as an explicit override.
Other approach would be try both fallback ie. try plain text first, if INVALID_USERNAME_OR_PASSWORD retry with obfuscated (or vice versa). It should work reliably regardless of configuration, but doubles the requests on one path and also doubles them when the user provides genuinely wrong credentials.
Which approach would you prefer? Or should we just keep it as an explicit parameter with a default value?
PS. I agree with your other review comments, I'll push the fixes by the end of the week ;-)
tests/test_ssl.py
Outdated
| from nanokvm.client import NanoKVMClient, NanoKVMSSLError | ||
|
|
||
|
|
||
| class TestSSLConfiguration: |
There was a problem hiding this comment.
For consistency with other unit tests, can you make these functional? We don't really use classes for test method organization.
nanokvm/client.py
Outdated
| try: | ||
| ca_path = Path(self._ssl_ca_cert) | ||
| if not ca_path.is_file(): | ||
| raise NanoKVMSSLError( |
There was a problem hiding this comment.
Should we instead allow these normal exceptions to propagate without being re-raised as a NanoKVMSSLError. ssl.create_default_context raises a FileNotFoundError if the CA certificate is missing, ssl.SSLError otherwise.
nanokvm/client.py
Outdated
| """Async context manager entry.""" | ||
| self._session = ClientSession() | ||
|
|
||
| ssl_config = self._create_ssl_context() |
There was a problem hiding this comment.
Creating a SSL context isn't async-safe (it performs file IO), this should be offloaded to the event loop executor.
I recently bought a NanoKVM Pro PCI-E and wanted to connect to it over HTTPS from Python. Turns out the library didn't support SSL/TLS at all, so I added it. While testing, I ran into a weird authentication issue that led me down a rabbit hole...
"The Authentication Mystery"
When I tried connecting to my NanoKVM Pro (running Application v1.2.12, Image v1.0.13) over HTTPS, authentication kept failing with "Invalid username or password" - even though I was using the correct credentials!
After SSH'ing into the device and monitoring logs, I discovered the reason: the library was sending AES-256-CBC encrypted passwords, but my device expected plain text over HTTPS (it does bcrypt hashing server-side). The web UI was working fine because it sends passwords in plain text.
Turns out newer NanoKVM versions (Pro and Standard ≥2.1.6) switched to accepting plain text passwords over HTTPS, while older versions still need the client-side obfuscation.
Note: NanoKVM Pro uses different version numbering (1.x.x) than standard NanoKVM (2.x.x), which initially confused me - I thought v1.2.12 was ancient!
What I Added
1. SSL/TLS Support
Three new optional parameters for
NanoKVMClient.__init__():verify_ssl: bool = True- Enable/disable SSL certificate verificationFalsefor self-signed certs (testing only)ssl_ca_cert: str | None = None- Path to custom CA certificateImplementation details:
aiohttp.TCPConnectorwith SSL configNanoKVMSSLErrorexception2. Password Obfuscation Control
One new parameter to handle the authentication differences:
use_password_obfuscation: bool = True- Control password encryptionTrue(backward compatible with older devices)Falsefor modern NanoKVM with HTTPSTrue: Uses AES-256-CBC encryption (legacy behavior)False: Sends plain text (relies on HTTPS transport security)Backward Compatibility
Everything is 100% backward compatible. Existing code works without changes:
All new parameters have sensible defaults:
verify_ssl=True- Secure by defaultuse_password_obfuscation=True- Works with older devicesTesting
I tested everything with my actual NanoKVM Pro over HTTPS:
use_password_obfuscation=False)Usage Examples
NanoKVM with HTTPS (Recommended)
Legacy NanoKVM with HTTP
Self-Signed Certificates
Technical Details
SSL Implementation
ssl.create_default_context()for secure defaultsPassword Obfuscation
EncryptSecretKeyFiles Changed
nanokvm/client.py- Main implementation (~150 lines added/modified)tests/test_ssl.py- New comprehensive SSL tests (~200 lines)README.md- Updated docs with HTTPS examplesVersion Compatibility Guide
Based on my testing and research:
Modern devices (use plain text password):
→ Set
use_password_obfuscation=FalseLegacy devices (need obfuscation):
EncryptSecretKey→ Keep
use_password_obfuscation=True(default)Not sure which you have?
use_password_obfuscation=FalseMy Test Setup
Device: NanoKVM Pro PCI-E
Software: Application v1.2.12, Image v1.0.13 (latest for Pro line)
Certificate: Let's Encrypt
Protocol: HTTPS with TLS 1.3
Authentication: Plain text password (
use_password_obfuscation=False)Result: Everything works perfectly for me ;-)
Feel free to comment and review what I've done :)
Review changes
Auto-Detection of Password Obfuscation Mode
Based on suggestion, the client now automatically detects whether the device needs obfuscated or plain text passwords — no manual configuration needed.
use_password_obfuscation: bool | None = None- Changed frombool = TruetoOptional[bool]None(default): Auto-detect — tries obfuscated first, falls back to plain textTrue: Force obfuscation (legacy behavior)False: Force plain textHow auto-detect works:
NanoKVMAuthenticationFailure→ retry with plain textThis means existing code works without changes and without the extra parameter:
Other Review Fixes
NanoKVMSSLError— SSL exceptions (FileNotFoundError,ssl.SSLError) now propagate naturally fromssl.create_default_context()instead of being wrapped_create_ssl_context()is offloaded viaasyncio.to_thread()since it performs file IOasync def test_*functions, no classes, consistent with the rest of the test suite