From 396fb1c4752b2b93ef0ec9d0c9c6be05aa403eef Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Fri, 17 Jul 2026 15:57:00 +0200 Subject: [PATCH] feat(otel-tracing-proxy): trust internal endpoints with untrusted CAs (#10139) * [ee] feat(otel-tracing-proxy): trust internal endpoints with untrusted CAs Add `insecure_upstream_hosts` and `upstream_ca_certs` to the HTTP Request Tracing settings so the OTEL tracing proxy can reach internal endpoints with untrusted or private-CA certificates while keeping them traced. Wires the two settings through the worker config and live reload, adds the inputs to the instance settings UI, and pulls in the rustls upstream-client deps (hyper-rustls/tokio-rustls/rustls/ rustls-native-certs/rustls-pemfile; hyper-http-proxy switched to its rustls feature). The proxy-side implementation lives in the companion EE PR. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: bump ee-repo-ref to otel_ca companion commit Co-Authored-By: Claude Opus 4.8 (1M context) * fix(otel-tracing-proxy): expose new fields in declarative config; bump ee-ref Addresses code-review findings: - Add `insecure_upstream_hosts` and `upstream_ca_certs` to the declarative `OtelTracingProxySettings` in instance_config.rs so operator/GitOps-managed installs can set them and reconciliation no longer drops values saved via the UI. - Restore the trailing newline on ee-repo-ref.txt and bump it to the companion EE commit carrying the strict host-matching / port-ordering fixes. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: update ee-repo-ref to 51e50629f48dbc4f5520a787b4bdfb76f4cd38d3 This commit updates the EE repository reference after PR #665 was merged in windmill-ee-private. Previous ee-repo-ref: 49f458e4446395e98915c220baa757ab3b2ed2d8 New ee-repo-ref: 51e50629f48dbc4f5520a787b4bdfb76f4cd38d3 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: windmill-internal-app[bot] --- backend/Cargo.lock | 9 ++-- backend/Cargo.toml | 5 ++- backend/ee-repo-ref.txt | 2 +- backend/src/monitor.rs | 6 ++- .../windmill-common/src/instance_config.rs | 9 ++++ backend/windmill-worker/Cargo.toml | 8 +++- backend/windmill-worker/src/worker.rs | 11 +++++ .../src/lib/components/InstanceSetting.svelte | 45 +++++++++++++++++++ 8 files changed, 86 insertions(+), 9 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 64427a820c..a278882d38 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -5814,12 +5814,9 @@ dependencies = [ "http 1.4.2", "hyper 1.10.1", "hyper-rustls 0.27.9", - "hyper-tls", "hyper-util", - "native-tls", "pin-project-lite", "tokio", - "tokio-native-tls", "tokio-rustls 0.26.4", "tower-service", ] @@ -15747,7 +15744,7 @@ dependencies = [ "hmac", "hudsucker", "hyper-http-proxy", - "hyper-tls", + "hyper-rustls 0.27.9", "hyper-util", "itertools 0.14.0", "jsonwebtoken 8.3.0", @@ -15777,6 +15774,9 @@ dependencies = [ "reqwest-middleware", "rsa", "rust_decimal", + "rustls 0.23.35", + "rustls-native-certs 0.8.4", + "rustls-pemfile 2.2.0", "serde", "serde_json", "sha2 0.10.9", @@ -15786,6 +15786,7 @@ dependencies = [ "tiberius", "tokio", "tokio-postgres", + "tokio-rustls 0.26.4", "tokio-stream", "tokio-util", "tracing", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 9aa5717bb0..b8277d852a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -681,5 +681,8 @@ rumqttc = { version = "0.24.0", features = ["use-native-tls"]} strum = { version = "0.27", features = ["derive"] } strum_macros = "0.27" hudsucker = { version = "0.22", features = ["rcgen-ca", "native-tls-client"] } -hyper-http-proxy = { version = "1", default-features = false, features = ["native-tls"] } +hyper-http-proxy = { version = "1", default-features = false, features = ["rustls-tls-native-roots"] } +hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "ring", "tls12"] } +tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } +rustls-native-certs = "0.8" rcgen = "0.13" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 25c70cdf3c..adf0644740 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -e19948fa2974a7d89bec12957fc6d9fa0a421da8 \ No newline at end of file +51e50629f48dbc4f5520a787b4bdfb76f4cd38d3 diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 14acebd13b..0392e128b3 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -1061,12 +1061,16 @@ pub async fn reload_otel_tracing_proxy_setting(conn: &Connection) { if current.enabled != new_settings.enabled || current.enabled_languages != new_settings.enabled_languages || current.no_proxy_hosts != new_settings.no_proxy_hosts + || current.insecure_upstream_hosts != new_settings.insecure_upstream_hosts + || current.upstream_ca_certs != new_settings.upstream_ca_certs { tracing::info!( - "OTEL tracing proxy settings changed: enabled={}, languages={:?}, no_proxy_hosts={:?}", + "OTEL tracing proxy settings changed: enabled={}, languages={:?}, no_proxy_hosts={:?}, insecure_upstream_hosts={:?}, upstream_ca_certs={}", new_settings.enabled, new_settings.enabled_languages, new_settings.no_proxy_hosts, + new_settings.insecure_upstream_hosts, + if new_settings.upstream_ca_certs.as_deref().unwrap_or("").trim().is_empty() { "unset" } else { "set" }, ); *current = new_settings; } diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index bb56d5d0cc..74b0647d82 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -641,6 +641,15 @@ pub struct OtelTracingProxySettings { /// pin their own CA (kubectl, helm, terraform providers, aws cli for EKS, etc.). #[serde(default, skip_serializing_if = "Option::is_none")] pub no_proxy_hosts: Option, + /// Comma-separated host/IP patterns for which the MITM proxy skips upstream TLS + /// verification. Unlike `no_proxy_hosts` the hosts stay traced — only the proxy's own + /// upstream certificate check is disabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub insecure_upstream_hosts: Option, + /// Extra CA certificates (PEM bundle) added to the MITM proxy's upstream trust store, + /// on top of the system roots, so internal endpoints signed by a private CA verify. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub upstream_ca_certs: Option, } /// Script language identifier (for instance config use). diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index 5b1047a153..bdc6ec49d6 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -13,7 +13,7 @@ default = [] private = ["windmill-worker-volumes/private", "windmill-queue/private", "windmill-common/private", "windmill-dep-map/private", "windmill-runtime-nativets?/private"] mcp = ["windmill-ai/mcp", "dep:windmill-mcp"] prometheus = ["dep:prometheus", "windmill-common/prometheus"] -enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker-volumes/enterprise", "windmill-runtime-nativets?/enterprise", "dep:pem", "dep:rsa", "dep:tokio-util", "dep:opentelemetry-proto", "dep:prost", "dep:hudsucker", "dep:rcgen", "dep:hyper-http-proxy", "dep:hyper-tls", "dep:hyper-util"] +enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker-volumes/enterprise", "windmill-runtime-nativets?/enterprise", "dep:pem", "dep:rsa", "dep:tokio-util", "dep:opentelemetry-proto", "dep:prost", "dep:hudsucker", "dep:rcgen", "dep:hyper-http-proxy", "dep:hyper-rustls", "dep:tokio-rustls", "dep:rustls", "dep:rustls-native-certs", "dep:rustls-pemfile", "dep:hyper-util"] mssql = ["dep:tiberius"] mssql-kerberos = ["mssql", "tiberius/integrated-auth-gssapi"] # Linux/Unix integrated auth mssql-winauth = ["mssql", "tiberius/winauth"] # Windows integrated auth @@ -144,7 +144,11 @@ bollard = { workspace = true, optional = true } oracle = { workspace = true, optional = true } hudsucker = { workspace = true, optional = true } hyper-http-proxy = { workspace = true, optional = true } -hyper-tls = { workspace = true, optional = true } +hyper-rustls = { workspace = true, optional = true } +tokio-rustls = { workspace = true, optional = true } +rustls = { workspace = true, optional = true } +rustls-native-certs = { workspace = true, optional = true } +rustls-pemfile = { workspace = true, optional = true } hyper-util = { workspace = true, optional = true } rcgen = { workspace = true, optional = true } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 2a4541b095..ece7b03ee7 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -283,6 +283,17 @@ pub struct OtelTracingProxySettings { pub enabled_languages: HashSet, #[serde(default)] pub no_proxy_hosts: Option, + /// Comma-separated host/IP patterns for which the MITM proxy skips upstream TLS + /// verification. Unlike `no_proxy_hosts` (which bypasses the proxy entirely, so the + /// request goes untraced), these hosts stay traced — only the proxy's own upstream + /// certificate check is disabled. Same suffix-matching semantics as `no_proxy_hosts`. + #[serde(default)] + pub insecure_upstream_hosts: Option, + /// Extra CA certificates (PEM bundle) added to the MITM proxy's upstream trust store, + /// on top of the system roots. Lets the proxy verify internal endpoints signed by a + /// private CA without disabling verification. + #[serde(default)] + pub upstream_ca_certs: Option, } #[cfg(feature = "prometheus")] diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index 10e117bb42..95040a7b1f 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -753,6 +753,51 @@ upstream relay (e.g. through a corporate proxy).

+
+ + +

+ Comma-separated host/IP patterns the proxy still traces but for which it skips + upstream TLS certificate verification. Use for internal endpoints with + self-signed or otherwise untrusted certificates — unlike NO_PROXY above, these + requests stay traced. Same matching as NO_PROXY (example.com matches + subdomains; .example.com matches subdomains only). +

+
+
+ + +

+ Extra CA certificates added to the proxy's upstream trust store, on top of the + system roots. Use this to trace internal endpoints signed by a private CA while + keeping certificate verification enabled — preferred over the insecure list above + when you have the CA. +

+
{/if} {:else if setting.fieldType == 'object_store_config'}