Skip to content

Add configurable TLS certificate verification and fix port handling#238

Open
aram356 wants to merge 4 commits intomainfrom
feature/cert-verification-port-handling
Open

Add configurable TLS certificate verification and fix port handling#238
aram356 wants to merge 4 commits intomainfrom
feature/cert-verification-port-handling

Conversation

@aram356
Copy link
Collaborator

@aram356 aram356 commented Feb 5, 2026

Summary

  • Adds Proxy.certificate_check setting to control TLS certificate verification (defaults to true for secure production use, set to false for local development with self-signed certs)
  • Adds 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 port
  • Extends ensure_origin_backend() API with certificate_check parameter
  • Includes cert setting in backend name to avoid reusing backends with different TLS settings

Test plan

  • Verify cargo check passes
  • Verify cargo test -p trusted-server-common passes
  • Test proxy requests to origins with non-standard ports (e.g., :9443) preserve the port in signed URLs
  • Test HTML rewriting preserves non-standard ports in sub-resource URLs
  • Test with certificate_check = false in local dev with self-signed certs

Closes #246
Related to #179

- 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.
@aram356 aram356 self-assigned this Feb 5, 2026
@aram356 aram356 marked this pull request as draft February 5, 2026 18:04
@aram356 aram356 added enhancement New feature or request and removed enhancement New feature or request labels Feb 5, 2026
…roxy components. Updated related settings and documentation.
@aram356 aram356 marked this pull request as ready for review February 5, 2026 23:18
Copy link
Collaborator

@prk-Jr prk-Jr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

Copy link
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 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

This operation writes
cert_suffix
to a log file.

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, portname_basebackend_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 the else branch where self.certificate_check is false.
  • 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.

Suggested changeset 1
crates/common/src/backend.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs
--- a/crates/common/src/backend.rs
+++ b/crates/common/src/backend.rs
@@ -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);
         }
EOF
@@ -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);
}
Copilot is powered by AI and may make mistakes. Always verify output.
backend_name
);
}
log::info!("enable ssl for backend: {}", backend_name);

Check failure

Code scanning / CodeQL

Cleartext logging of sensitive information High

This operation writes
cert_suffix
to a log file.

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 the log::info!("enable ssl for backend: {}", backend_name); line with a message that omits backend_name and uses self.host, target_port, and self.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_name are not part of the reported flow, and they do not incorporate cert_suffix in 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.
Suggested changeset 1
crates/common/src/backend.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs
--- a/crates/common/src/backend.rs
+++ b/crates/common/src/backend.rs
@@ -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() {
EOF
@@ -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() {
Copilot is powered by AI and may make mistakes. Always verify output.

match builder.finish() {
Ok(_) => {
log::info!(

Check failure

Code scanning / CodeQL

Cleartext logging of sensitive information High

This operation writes
cert_suffix
to a log file.
This operation writes
... .certificate_check(...)
to a log file.
This operation writes
... .certificate_check(...)
to a log file.
This operation writes
... .certificate_check(...)
to a log file.

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 thus cert_suffix) from log::info! and log::warn! output, or replace it with more generic text.
    • Avoid logging host_with_port in 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.
  • In from_url:

    • Change the error message to avoid echoing the full origin_url back into logs; replace with a generic text like "Invalid origin_url" to avoid logging user-supplied URLs.

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.

Suggested changeset 1
crates/common/src/backend.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs
--- a/crates/common/src/backend.rs
+++ b/crates/common/src/backend.rs
@@ -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();
EOF
@@ -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();
Copilot is powered by AI and may make mistakes. Always verify output.
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

This operation writes
cert_suffix
to a log file.

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_suffix in backend_name, so that the name used for logs and for Backend::builder no 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 from cert_suffix to the info! 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_suffix from backend_name.

Concretely, in crates/common/src/backend.rs:

  • At lines 99–110: remove the cert_suffix variable and its use in backend_name. Define backend_name purely from name_base (which depends only on scheme, host, and port).
  • Leave the rest of the logic (including the warn! about certificate checks being disabled and the info! logs) untouched so functionality is preserved but sensitive details in backend_name are no longer present.

No new imports or helper methods are required.

Suggested changeset 1
crates/common/src/backend.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs
--- a/crates/common/src/backend.rs
+++ b/crates/common/src/backend.rs
@@ -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);
 
EOF
@@ -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);

Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add configurable TLS certificate verification and fix port handling

3 participants