An httpreq endpoint with embedded userinfo leaks its password to the log, the database and the admin API #58
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
From the v2.0.0 security audit.
src/httpreq.rs:180attachesformat!("POST {url}")as error context.url::Url'sDisplayprints the full serialization, which retainsuser:password@.cert_managerflattens the whole chain withformat!("{e:#}"), logs it at WARN, and writes it tocertificates.last_errorviarecord_certificate_error— a column returned by bothGET /api/certificatesand/ready.src/httpreq.rs:72also echoes the raw string into its parse error ({raw:?}), so a malformed value puts the password on stderr at startup.build_configneither rejects nor strips userinfo, andedge-router.example.tomldoes not warn against it.Failure scenario
An operator writes
HTTPREQ_ENDPOINT=https://er:[email protected]/dnsrather than settingHTTPREQ_USERNAME/HTTPREQ_PASSWORDseparately — a natural shortcut, and nothing forbids it. The endpoint is briefly unreachable during a renewal. The password is now at WARN in journald and persisted unredacted incertificates.last_error, where it survives log rotation and is served to every admin-API caller until the next successful order clears the column.Fix
Strip userinfo before the URL is used in any error context — or reject it at
build_configwith a message pointing atHTTPREQ_USERNAME/HTTPREQ_PASSWORD, which is probably better since silently dropping credentials an operator supplied would break their auth confusingly. Either way the parse error at:72should not echo the raw value.Worth a general look at whether anything else
Displays aUrlinto an error or log line.Fixed in
ffdd15eby rejecting, as this issue recommended — stripping would fail an operator's auth confusingly after silently discarding what they supplied.build_configrefuses an endpoint with a username or password, namingHTTPREQ_USERNAME/HTTPREQ_PASSWORDand saying where a URL-borne password ends up. The refusal does not repeat the value.:72no longer echoesraw. That site mattered most: a typo'd URL is exactly where a strayuser:password@lands, and the error reaches stderr before any validation can reject it.httpreq::redactedis now the only sanctioned way to put a URL in an error, and thePOSTcontext uses it. Keeping it as a named function rather than inlining the clear means a future call site cannot quietly reintroduce the leak.The sweep you asked for found no other site.
{url}/{raw}appears three times insrc/: the two above andHTTPREQ_HTTP_TIMEOUT's{raw:?}, which is a duration and carries nothing sensitive. Nothing elseDisplays aUrlinto an error or log.Breaking, narrowly, and in the v2.1 notes: an endpoint using the embedded-credentials shortcut now fails at startup. Nothing in the fleet does — devinfra's is
https://dns01.isohex.uk:8443with the credentials in separate variables.Documented in the README's
HTTPREQ_ENDPOINTrow and inedge-router.example.toml, which this issue correctly noted said nothing against it.373 tests pass, clippy
--all-targets -D warningsclean, fmt clean.