You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
10/10 — Expert. Estimated effort: 4–6 days for a senior engineer.
Context
The README.md states "all communication runs over wss:// in production" (line 34) and the architecture diagram shows wss:// between clients and the gateway. However, the codebase has zero TLS support: src/server.js creates a plain http.createServer() (line 26) and new WebSocketServer({ server }) (line 46–49). The Dockerfile and docker-compose.yml expose port 8080 with no TLS termination. There is no certificate loading, no HTTPS server, no mTLS, and no integration with certificate managers (Let's Encrypt, cert-manager, HashiCorp Vault).
In a production fleet-tracking deployment, the gateway must:
Terminate TLS for wss:// connections (server certificate).
Optionally validate client certificates (mTLS) for device authentication — stronger than JWT.
Support multiple tenants on the same gateway with different certificates (SNI).
Rotate certificates automatically without downtime (Let's Encrypt 90-day expiry, or internal PKI 24h expiry).
Support certificate revocation checking (CRL/OCSP) for mTLS.
Problem statement
Implement a production-grade TLS layer that:
HTTPS/WSS server: Replace http.createServer() with https.createServer({ cert, key, ca, ... }) in src/server.js. The WebSocket server upgrades on top of HTTPS.
Certificate management:
Load certificates from: filesystem (TLS_CERT_PATH, TLS_KEY_PATH, TLS_CA_PATH), environment variables (TLS_CERT_PEM, TLS_KEY_PEM), or a certificate manager plugin interface.
Support multiple certificates with SNI callback: server.addContext(hostname, { cert, key, ca }) for multi-tenant deployments.
Automated rotation: watch certificate files for changes (fs.watch) or poll certificate manager API. On cert change, call server.setSecureContext(newContext) — Node.js 18+ supports this for live rotation without restart.
mTLS client certificate validation:
When TLS_REQUEST_CERT=true and TLS_CA_PEM/TLS_CA_PATH set, request client cert: requestCert: true, rejectUnauthorized: false (we validate manually for custom logic).
On connection, extract socket.getPeerCertificate(true) — verify chain against CA, check expiry, check revocation (CRL/OCSP), extract subject CN/SAN as deviceId.
Map deviceId → clientId for auth. If mTLS succeeds, JWT token becomes optional (configurable: TLS_MTLS_REQUIRES_JWT=false).
Revocation: load CRL from TLS_CRL_PATH or TLS_CRL_URL (periodic fetch). OCSP: use node:cryptoverify with OCSP stapling or external OCSP responder.
Certificate transparency and monitoring:
Expose /admin/v1/tls/certificates (issue 14) listing all loaded certs with { subject, issuer, validFrom, validTo, san, ocspStatus }.
Alert when any cert expires within 30 days (log warning, admin event).
Integration with auth system (issue 7): mTLS device identity can be used as the primary auth factor. JWT becomes a secondary factor or is omitted entirely for mTLS-authenticated connections.
Graceful certificate rotation: When a new cert is loaded, existing TLS connections continue with old cert. New connections use new cert. No connection drops.
Current behavior
src/server.js: plain HTTP server, no TLS.
src/auth.js: only JWT, no certificate validation.
No certificate files, no https module usage, no SNI handling.
package.json: no TLS-related dependencies.
Required behavior
New module src/tls-manager.js exporting TLSManager class.
tlsManager.watchAndRotate() starts file watchers / polling. On change, calls server.setSecureContext(newContext) and emits certificate_rotated event.
src/server.js uses TLSManager to create HTTPS server. Connection handler calls tlsManager.getPeerIdentity(ws._socket) and passes result to verifyConnection() (which now accepts { token?, mtlsIdentity? }).
verifyConnection (issue 7) extended: if mtlsIdentity.verified && !mtlsIdentity.revoked, accept with clientId = mtlsIdentity.deviceId (or JWT sub if both present).
Admin API (issue 14) endpoints for cert inspection and manual rotation trigger.
Constraints
Do not modify validator.js, rate-limiter.js, conn-rate-limiter.js, logger.js, errors.js, room-manager.js.
Do not modify existing test files. New test files required.
Use only Node.js built-in tls, https, crypto, fs modules. No new npm dependencies for core TLS (certificate manager plugins can be separate packages).
Certificate rotation must not drop existing connections — server.setSecureContext() achieves this in Node 18+.
mTLS validation must be fast: <5ms per connection for cert chain verification + CRL check (cache CRL in memory, refresh every 5 min).
SNI callback must be synchronous and fast — cert lookup from in-memory Map.
Support both PEM (string) and file paths for cert/key/CA/CRL.
The WebSocket server creation in server.js must be refactored to accept a pre-created https.Server instance (or TLSManager creates it).
Acceptance criteria
TLSManager loads cert/key from file paths and creates SecureContext
HTTPS server starts on TLS_PORT (default 8443), WebSocket upgrades work over wss://
SNI: two certs loaded for tenant1.example.com and tenant2.example.com — correct cert served based on ServerName
mTLS: client with valid cert signed by CA connects → getPeerIdentity returns verified identity, JWT optional
mTLS: client with expired cert → rejected with close code 4003 "Client certificate expired"
mTLS: client with revoked cert (in CRL) → rejected with close code 4004 "Client certificate revoked"
Certificate rotation: modify cert file, TLSManager detects change, calls server.setSecureContext(), new connections use new cert, old connections unaffected
server.setSecureContext(context) — available in Node 18+. For older Node, you must recreate the server (not required — assume Node 18+).
Certificate chain verification: crypto.X509Certificate (Node 15+) for parsing, crypto.verify for signature verification. Or use openssl CLI via child_process for complex validation (CRL, OCSP).
CRL parsing: crypto.X509Certificate doesn't parse CRL. Use openssl crl -inform PEM -text -noout and parse output, or implement minimal CRL parsing (it's just a list of serial numbers).
For mTLS deviceId extraction: cert.subject contains CN=device-123 or subjectaltname has DNS:device-123. Parse with regex or x509 library.
ws._socket is the underlying tls.TLSSocket — ws._socket.getPeerCertificate(true) returns full chain.
Difficulty
10/10 — Expert. Estimated effort: 4–6 days for a senior engineer.
Context
The
README.mdstates "all communication runs overwss://in production" (line 34) and the architecture diagram showswss://between clients and the gateway. However, the codebase has zero TLS support:src/server.jscreates a plainhttp.createServer()(line 26) andnew WebSocketServer({ server })(line 46–49). TheDockerfileanddocker-compose.ymlexpose port 8080 with no TLS termination. There is no certificate loading, no HTTPS server, no mTLS, and no integration with certificate managers (Let's Encrypt, cert-manager, HashiCorp Vault).In a production fleet-tracking deployment, the gateway must:
wss://connections (server certificate).Problem statement
Implement a production-grade TLS layer that:
HTTPS/WSS server: Replace
http.createServer()withhttps.createServer({ cert, key, ca, ... })insrc/server.js. The WebSocket server upgrades on top of HTTPS.Certificate management:
TLS_CERT_PATH,TLS_KEY_PATH,TLS_CA_PATH), environment variables (TLS_CERT_PEM,TLS_KEY_PEM), or a certificate manager plugin interface.server.addContext(hostname, { cert, key, ca })for multi-tenant deployments.server.setSecureContext(newContext)— Node.js 18+ supports this for live rotation without restart.mTLS client certificate validation:
TLS_REQUEST_CERT=trueandTLS_CA_PEM/TLS_CA_PATHset, request client cert:requestCert: true, rejectUnauthorized: false(we validate manually for custom logic).socket.getPeerCertificate(true)— verify chain against CA, check expiry, check revocation (CRL/OCSP), extract subject CN/SAN asdeviceId.deviceId→clientIdfor auth. If mTLS succeeds, JWT token becomes optional (configurable:TLS_MTLS_REQUIRES_JWT=false).TLS_CRL_PATHorTLS_CRL_URL(periodic fetch). OCSP: usenode:cryptoverifywith OCSP stapling or external OCSP responder.Certificate transparency and monitoring:
/admin/v1/tls/certificates(issue 14) listing all loaded certs with{ subject, issuer, validFrom, validTo, san, ocspStatus }.tls_cert_expiry_days{subject="..."},tls_mtls_connections_total{result="success|revoked|expired|invalid"}.Integration with auth system (issue 7): mTLS device identity can be used as the primary auth factor. JWT becomes a secondary factor or is omitted entirely for mTLS-authenticated connections.
Graceful certificate rotation: When a new cert is loaded, existing TLS connections continue with old cert. New connections use new cert. No connection drops.
Current behavior
src/server.js: plain HTTP server, no TLS.src/auth.js: only JWT, no certificate validation.httpsmodule usage, no SNI handling.package.json: no TLS-related dependencies.Required behavior
src/tls-manager.jsexportingTLSManagerclass.TLSManagerconstructor:{ certSources: [{ type: "file"|"env"|"plugin", config }], caSources: [...], crlSources: [...], requestCert: boolean, rejectUnauthorized: false, sniCallback: (hostname) => SecureContext }.tlsManager.createSecureContext()returnstls.SecureContextforhttps.createServer().tlsManager.getPeerIdentity(socket)returns{ deviceId, subject, issuer, validFrom, validTo, verified: boolean, revocationChecked: boolean }or throws.tlsManager.watchAndRotate()starts file watchers / polling. On change, callsserver.setSecureContext(newContext)and emitscertificate_rotatedevent.src/server.jsusesTLSManagerto create HTTPS server. Connection handler callstlsManager.getPeerIdentity(ws._socket)and passes result toverifyConnection()(which now accepts{ token?, mtlsIdentity? }).verifyConnection(issue 7) extended: ifmtlsIdentity.verified && !mtlsIdentity.revoked, accept withclientId = mtlsIdentity.deviceId(or JWTsubif both present).Constraints
validator.js,rate-limiter.js,conn-rate-limiter.js,logger.js,errors.js,room-manager.js.tls,https,crypto,fsmodules. No new npm dependencies for core TLS (certificate manager plugins can be separate packages).server.setSecureContext()achieves this in Node 18+.server.jsmust be refactored to accept a pre-createdhttps.Serverinstance (orTLSManagercreates it).Acceptance criteria
TLSManagerloads cert/key from file paths and createsSecureContextTLS_PORT(default 8443), WebSocket upgrades work overwss://tenant1.example.comandtenant2.example.com— correct cert served based onServerNamegetPeerIdentityreturns verified identity, JWT optionalTLSManagerdetects change, callsserver.setSecureContext(), new connections use new cert, old connections unaffectedtls_cert_expiry_daysgauge,tls_mtls_connections_totalcounterGET /admin/v1/tls/certificatesreturns cert detailsnpm run lintpassesTLS_CERT_PATHnot set)tests/tls-manager.test.jswith unit tests for cert loading, SNI, mTLS validation, rotationtests/tls-integration.test.js(requires cert generation) — skipped ifopensslnot availableOut of scope
Hints and references
https.createServer({ SNICallback: (hostname, cb) => { cb(null, tlsManager.getContextForHostname(hostname)); } }).server.setSecureContext(context)— available in Node 18+. For older Node, you must recreate the server (not required — assume Node 18+).crypto.X509Certificate(Node 15+) for parsing,crypto.verifyfor signature verification. Or useopensslCLI viachild_processfor complex validation (CRL, OCSP).crypto.X509Certificatedoesn't parse CRL. Useopenssl crl -inform PEM -text -nooutand parse output, or implement minimal CRL parsing (it's just a list of serial numbers).cert.subjectcontainsCN=device-123orsubjectaltnamehasDNS:device-123. Parse with regex orx509library.ws._socketis the underlyingtls.TLSSocket—ws._socket.getPeerCertificate(true)returns full chain.server.js:opensslin test setup. Usemkcertif available for trusted certs.