Add configurable TLS certificate verification and fix port handling#238
Add configurable TLS certificate verification and fix port handling#238
Conversation
- Add `Proxy.certificate_check` setting (defaults to true for secure production) - Add `compute_host_header()` to properly format Host header with non-standard ports - Extend `ensure_origin_backend()` with certificate_check parameter - Include cert setting in backend name to avoid reusing backends with different settings - Add comprehensive tests for port preservation in proxy signing and HTML rewriting - Update all call sites to pass certificate_check=true (secure default) This fixes an issue where backends behind reverse proxies would generate URLs without the port when the Host header didn't include it.
…roxy components. Updated related settings and documentation.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
👍 looks good.
But, maybe we should consider using a struct with builder pattern instead of a function for dynamic backend creation, especially if we wrap any more options we'd have to do less code changes with a struct with defaults
…ert verification port. Updated provider and proxy to use the new config struct. Updated tests to reflect changes.
| backend_name, host_with_port, msg | ||
| ), | ||
| })) | ||
| log::warn!( |
Check failure
Code scanning / CodeQL
Cleartext logging of sensitive information High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 18 hours ago
In general, the problem is that a log message includes data derived from untrusted or potentially sensitive input (scheme, host, port → name_base → backend_name). To fix this, avoid logging those values directly. Either (a) remove the dynamic data from the log, or (b) replace it with a non-sensitive, generic description that still informs operators about the condition (“certificate check disabled”) without exposing specific backend details.
The minimal-impact fix here is to change the log::warn! on lines 128–131 so that it no longer includes backend_name. The rest of the behavior—constructing the backend, naming it, enabling SSL, etc.—remains unchanged. We simply adjust the warning message to something generic like "INSECURE: certificate check disabled for dynamic backend" without formatting in backend_name. This change is localized to crates/common/src/backend.rs within the shown snippet and requires no new imports, methods, or definitions.
Concretely:
- In
crates/common/src/backend.rs, locate theelsebranch whereself.certificate_checkisfalse. - Replace:
log::warn!(
"INSECURE: certificate check disabled for backend: {}",
backend_name
);with:
log::warn!("INSECURE: certificate check disabled for dynamic backend");No other lines need to change.
| @@ -125,10 +125,7 @@ | ||
| .sni_hostname(self.host) | ||
| .check_certificate(self.host); | ||
| } else { | ||
| log::warn!( | ||
| "INSECURE: certificate check disabled for backend: {}", | ||
| backend_name | ||
| ); | ||
| log::warn!("INSECURE: certificate check disabled for dynamic backend"); | ||
| } | ||
| log::info!("enable ssl for backend: {}", backend_name); | ||
| } |
| backend_name | ||
| ); | ||
| } | ||
| log::info!("enable ssl for backend: {}", backend_name); |
Check failure
Code scanning / CodeQL
Cleartext logging of sensitive information High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 18 hours ago
In general, to fix cleartext logging issues you either (a) stop logging the sensitive value, (b) sanitize or generalize it before logging, or (c) encrypt or hash it if you must keep a linkable value. Here, the tainted piece is backend_name, which is derived from cert_suffix; we can reduce or remove its presence in logs while still logging non-sensitive operational information (scheme, host, port, and whether certificate checking is enabled).
The most direct, low-impact fix is to change the log message on line 133 so it no longer includes backend_name. Instead, we can log the backend target (host and port) and the effective TLS/certificate-check configuration using values that are not derived from cert_suffix (or at least are simple booleans). That preserves functionality (backend creation and naming logic are untouched) and keeps logs useful, while breaking the taint flow into the logging sink.
Concretely:
- In
crates/common/src/backend.rs, replace thelog::info!("enable ssl for backend: {}", backend_name);line with a message that omitsbackend_nameand usesself.host,target_port, andself.certificate_check. For example:
log::info!("enable ssl for backend to {}:{} (certificate_check={})", self.host, target_port, self.certificate_check); - No new imports or helper functions are required; we only change the log string and arguments.
- Other log lines that include
backend_nameare not part of the reported flow, and they do not incorporatecert_suffixin a way flagged as sensitive by this specific alert; to keep the change minimal and focused on the identified sink, we leave them as-is.
| @@ -130,7 +130,12 @@ | ||
| backend_name | ||
| ); | ||
| } | ||
| log::info!("enable ssl for backend: {}", backend_name); | ||
| log::info!( | ||
| "enable ssl for backend to {}:{} (certificate_check={})", | ||
| self.host, | ||
| target_port, | ||
| self.certificate_check | ||
| ); | ||
| } | ||
|
|
||
| match builder.finish() { |
|
|
||
| match builder.finish() { | ||
| Ok(_) => { | ||
| log::info!( |
Check failure
Code scanning / CodeQL
Cleartext logging of sensitive information High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 18 hours ago
General approach: Stop logging tainted data (backend_name and anything built from certificate_check/cert_suffix) in cleartext. We can either (a) remove these values from log messages, (b) sanitize them (e.g., only log scheme or a constant), or (c) log only non–data-bearing metadata. To avoid changing functionality, we’ll keep the control flow and backend naming exactly the same and only adjust the string content of logs.
Best specific fix here:
-
In
BackendConfig::ensure:- Remove
backend_name(and thuscert_suffix) fromlog::info!andlog::warn!output, or replace it with more generic text. - Avoid logging
host_with_portin the success message so that the tainted backend name is no longer part of any log call; this also reduces origin exposure. - Keep log calls themselves for observability, but make them generic (“created dynamic backend”, “reusing existing dynamic backend”, “INSECURE: certificate check disabled for backend”) without interpolating values.
- Remove
-
In
from_url:- Change the error message to avoid echoing the full
origin_urlback into logs; replace with a generic text like"Invalid origin_url"to avoid logging user-supplied URLs.
- Change the error message to avoid echoing the full
These changes all occur in crates/common/src/backend.rs, within the shown snippets. No changes are required in crates/common/src/proxy.rs because it does not contain logging of backend_name; it only uses ensure() and handles errors upstream.
No new methods or external crates are needed; we are only editing string literals in existing log::info!, log::warn!, and error message: format!(...) calls.
| @@ -126,33 +126,25 @@ | ||
| .check_certificate(self.host); | ||
| } else { | ||
| log::warn!( | ||
| "INSECURE: certificate check disabled for backend: {}", | ||
| backend_name | ||
| "INSECURE: certificate check disabled for backend" | ||
| ); | ||
| } | ||
| log::info!("enable ssl for backend: {}", backend_name); | ||
| log::info!("enable ssl for backend"); | ||
| } | ||
|
|
||
| match builder.finish() { | ||
| Ok(_) => { | ||
| log::info!( | ||
| "created dynamic backend: {} -> {}", | ||
| backend_name, | ||
| host_with_port | ||
| ); | ||
| log::info!("created dynamic backend"); | ||
| Ok(backend_name) | ||
| } | ||
| Err(e) => { | ||
| let msg = e.to_string(); | ||
| if msg.contains("NameInUse") || msg.contains("already in use") { | ||
| log::info!("reusing existing dynamic backend: {}", backend_name); | ||
| log::info!("reusing existing dynamic backend"); | ||
| Ok(backend_name) | ||
| } else { | ||
| Err(Report::new(TrustedServerError::Proxy { | ||
| message: format!( | ||
| "dynamic backend creation failed ({} -> {}): {}", | ||
| backend_name, host_with_port, msg | ||
| ), | ||
| message: "dynamic backend creation failed".to_string(), | ||
| })) | ||
| } | ||
| } | ||
| @@ -173,7 +151,7 @@ | ||
| certificate_check: bool, | ||
| ) -> Result<String, Report<TrustedServerError>> { | ||
| let parsed_url = Url::parse(origin_url).change_context(TrustedServerError::Proxy { | ||
| message: format!("Invalid origin_url: {}", origin_url), | ||
| message: "Invalid origin_url".to_string(), | ||
| })?; | ||
|
|
||
| let scheme = parsed_url.scheme(); |
| Err(e) => { | ||
| let msg = e.to_string(); | ||
| if msg.contains("NameInUse") || msg.contains("already in use") { | ||
| log::info!("reusing existing dynamic backend: {}", backend_name); |
Check failure
Code scanning / CodeQL
Cleartext logging of sensitive information High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 18 hours ago
In general, the fix is to avoid logging values derived from cert_suffix (or anything that reveals that certificate verification is disabled) at normal log levels. We can keep using cert_suffix internally if needed, but we should not include it in identifiers that are logged, or we should log a redacted/normalized form instead.
The best minimally invasive fix here is:
- Stop including
cert_suffixinbackend_name, so that the name used for logs and forBackend::builderno longer encodes whether certificate checking is enabled. - Keep the existing explicit
warn!about certificate checks being disabled; that is already a deliberate, higher-severity log. This way, backend reuse logs at line 148 will no longer leak_nocert, addressing the CodeQL trace fromcert_suffixto theinfo!log. - No functional behavior outside the snippet changes: Fastly backends will now be keyed only by scheme/host/port. If the original intent to separate backends by cert setting must be preserved strictly, we could instead maintain two identifiers (an internal, non-logged one with suffix and a public/logged one without), but that would require seeing more code. Within this snippet only, the simplest change is to remove
cert_suffixfrombackend_name.
Concretely, in crates/common/src/backend.rs:
- At lines 99–110: remove the
cert_suffixvariable and its use inbackend_name. Definebackend_namepurely fromname_base(which depends only on scheme, host, and port). - Leave the rest of the logic (including the
warn!about certificate checks being disabled and theinfo!logs) untouched so functionality is preserved but sensitive details inbackend_nameare no longer present.
No new imports or helper methods are required.
| @@ -96,18 +96,9 @@ | ||
|
|
||
| let host_with_port = format!("{}:{}", self.host, target_port); | ||
|
|
||
| // Include cert setting in name to avoid reusing a backend with different cert settings | ||
| // Base name for the backend derived from scheme, host and port | ||
| let name_base = format!("{}_{}_{}", self.scheme, self.host, target_port); | ||
| let cert_suffix = if self.certificate_check { | ||
| "" | ||
| } else { | ||
| "_nocert" | ||
| }; | ||
| let backend_name = format!( | ||
| "backend_{}{}", | ||
| name_base.replace(['.', ':'], "_"), | ||
| cert_suffix | ||
| ); | ||
| let backend_name = format!("backend_{}", name_base.replace(['.', ':'], "_")); | ||
|
|
||
| let host_header = compute_host_header(self.scheme, self.host, target_port); | ||
|
|
Summary
Proxy.certificate_checksetting to control TLS certificate verification (defaults totruefor secure production use, set tofalsefor local development with self-signed certs)compute_host_header()function to properly format Host header with non-standard ports, fixing an issue where backends behind reverse proxies would generate URLs without the portensure_origin_backend()API withcertificate_checkparameterTest plan
cargo checkpassescargo test -p trusted-server-commonpasses:9443) preserve the port in signed URLscertificate_check = falsein local dev with self-signed certsCloses #246
Related to #179