diff --git a/Dashboard/src/Components/Probe/CustomProbeDocumentation.tsx b/Dashboard/src/Components/Probe/CustomProbeDocumentation.tsx index c30e95d259..a793b4990f 100644 --- a/Dashboard/src/Components/Probe/CustomProbeDocumentation.tsx +++ b/Dashboard/src/Components/Probe/CustomProbeDocumentation.tsx @@ -41,6 +41,7 @@ docker run --name oneuptime-probe --network host \\ -e ONEUPTIME_URL=${host.toString()} \\ -e HTTP_PROXY_URL=http://proxy.example.com:8080 \\ -e HTTPS_PROXY_URL=http://proxy.example.com:8080 \\ + -e NO_PROXY=localhost,.internal.example.com \ -d oneuptime/probe:release # With proxy authentication @@ -50,6 +51,7 @@ docker run --name oneuptime-probe --network host \\ -e ONEUPTIME_URL=${host.toString()} \\ -e HTTP_PROXY_URL=http://username:password@proxy.example.com:8080 \\ -e HTTPS_PROXY_URL=http://username:password@proxy.example.com:8080 \\ + -e NO_PROXY=localhost,.internal.example.com \ -d oneuptime/probe:release `} /> diff --git a/Docs/Content/probe/custom-probe.md b/Docs/Content/probe/custom-probe.md index 1d57631ce5..4960aff4e5 100644 --- a/Docs/Content/probe/custom-probe.md +++ b/Docs/Content/probe/custom-probe.md @@ -27,6 +27,7 @@ docker run --name oneuptime-probe --network host \ -e PROBE_ID= \ -e ONEUPTIME_URL=https://oneuptime.com \ -e HTTP_PROXY_URL=http://proxy.example.com:8080 \ + -e NO_PROXY=localhost,.internal.example.com \ -d oneuptime/probe:release # For HTTPS proxy @@ -35,6 +36,7 @@ docker run --name oneuptime-probe --network host \ -e PROBE_ID= \ -e ONEUPTIME_URL=https://oneuptime.com \ -e HTTPS_PROXY_URL=http://proxy.example.com:8080 \ + -e NO_PROXY=localhost,.internal.example.com \ -d oneuptime/probe:release # With proxy authentication @@ -44,6 +46,7 @@ docker run --name oneuptime-probe --network host \ -e ONEUPTIME_URL=https://oneuptime.com \ -e HTTP_PROXY_URL=http://username:password@proxy.example.com:8080 \ -e HTTPS_PROXY_URL=http://username:password@proxy.example.com:8080 \ + -e NO_PROXY=localhost,.internal.example.com \ -d oneuptime/probe:release ``` @@ -84,9 +87,11 @@ services: # Proxy configuration (optional) - HTTP_PROXY_URL=http://proxy.example.com:8080 - HTTPS_PROXY_URL=http://proxy.example.com:8080 + - NO_PROXY=localhost,.internal.example.com # For proxy with authentication: # - HTTP_PROXY_URL=http://username:password@proxy.example.com:8080 # - HTTPS_PROXY_URL=http://username:password@proxy.example.com:8080 + # - NO_PROXY=localhost,.internal.example.com network_mode: host restart: always ``` @@ -162,11 +167,15 @@ spec: value: "http://proxy.example.com:8080" - name: HTTPS_PROXY_URL value: "http://proxy.example.com:8080" + - name: NO_PROXY + value: "localhost,.internal.example.com" # For proxy with authentication, use: # - name: HTTP_PROXY_URL # value: "http://username:password@proxy.example.com:8080" # - name: HTTPS_PROXY_URL # value: "http://username:password@proxy.example.com:8080" + # - name: NO_PROXY + # value: "localhost,.internal.example.com" ``` Then run the following command: @@ -189,6 +198,7 @@ The probe supports the following environment variables: #### Optional Variables - `HTTP_PROXY_URL` - HTTP proxy server URL for HTTP requests - `HTTPS_PROXY_URL` - HTTP proxy server URL for HTTPS requests +- `NO_PROXY` - Comma-separated hosts or domains that should bypass the proxy - `PROBE_NAME` - Custom name for the probe - `PROBE_DESCRIPTION` - Description for the probe - `PROBE_MONITORING_WORKERS` - Number of monitoring workers (default: 1) @@ -199,7 +209,7 @@ The probe supports the following environment variables: #### Proxy Configuration -The probe supports both HTTP and HTTPS proxy servers. When configured, the probe will route all monitoring traffic through the specified proxy servers. +The probe supports both HTTP and HTTPS proxy servers. When configured, the probe will route all monitoring traffic through the specified proxy servers. You can also provide a comma-separated `NO_PROXY` list to bypass the proxy for internal hosts or networks. **Proxy URL Format:** ``` @@ -214,9 +224,10 @@ http://[username:password@]proxy.server.com:port - HTTP and HTTPS proxy support - Proxy authentication (username/password) - Automatic fallback between HTTP and HTTPS proxies +- Selective proxy bypass using `NO_PROXY` - Works with all monitor types (Website, API, SSL, Synthetic, etc.) -**Note:** Both standard environment variables (`HTTP_PROXY_URL`, `HTTPS_PROXY_URL`) and lowercase variants (`http_proxy`, `https_proxy`) are supported for compatibility. +**Note:** Both standard environment variables (`HTTP_PROXY_URL`, `HTTPS_PROXY_URL`, `NO_PROXY`) and lowercase variants (`http_proxy`, `https_proxy`, `no_proxy`) are supported for compatibility. ### Verify diff --git a/HelmChart/Public/oneuptime/README.md b/HelmChart/Public/oneuptime/README.md index 6617dfbcbb..79527554c9 100644 --- a/HelmChart/Public/oneuptime/README.md +++ b/HelmChart/Public/oneuptime/README.md @@ -105,6 +105,7 @@ The following table lists the configurable parameters of the OneUptime chart and | `probes..customCodeMonitorScriptTimeoutInMs` | Timeout for custom code monitor script | `60000` | | | `probes..proxy.httpProxyUrl` | HTTP proxy URL for HTTP requests made by the probe (optional) | `nil` | | | `probes..proxy.httpsProxyUrl` | HTTPS proxy URL for HTTPS requests made by the probe (optional) | `nil` | | +| `probes..proxy.noProxy` | Comma-separated hosts that should bypass the proxy (optional) | `nil` | | | `probes..additionalContainers` | Additional containers to add to the probe pod | `nil` | | | `probes..resources` | Pod resources (limits, requests) | `nil` | | | `statusPage.cnameRecord` | CNAME record for the status page | `nil` | | diff --git a/HelmChart/Public/oneuptime/templates/probe.yaml b/HelmChart/Public/oneuptime/templates/probe.yaml index 82da7953a6..1ba3e1a16a 100644 --- a/HelmChart/Public/oneuptime/templates/probe.yaml +++ b/HelmChart/Public/oneuptime/templates/probe.yaml @@ -126,6 +126,10 @@ spec: - name: HTTPS_PROXY_URL value: {{ $val.proxy.httpsProxyUrl | squote }} {{- end }} + {{- if and $val.proxy $val.proxy.noProxy }} + - name: NO_PROXY + value: {{ $val.proxy.noProxy | squote }} + {{- end }} {{- include "oneuptime.env.oneuptimeSecret" $ | nindent 12 }} ports: - containerPort: {{ if and $val.ports $val.ports.http }}{{ $val.ports.http }}{{ else }}3874{{ end }} diff --git a/HelmChart/Public/oneuptime/values.yaml b/HelmChart/Public/oneuptime/values.yaml index fa83363fc2..11b0cc0b01 100644 --- a/HelmChart/Public/oneuptime/values.yaml +++ b/HelmChart/Public/oneuptime/values.yaml @@ -302,6 +302,9 @@ probes: # Example: http://proxy.example.com:8080 # Example with auth: http://username:password@proxy.example.com:8080 httpsProxyUrl: + # Comma-separated list of hosts that should bypass the proxy (optional) + # Example: localhost,.internal.example.com,10.0.0.0/8 + noProxy: # KEDA autoscaling configuration based on monitor queue metrics keda: enabled: false @@ -337,6 +340,8 @@ probes: # httpProxyUrl: # # HTTPS proxy URL for HTTPS requests (optional) # httpsProxyUrl: +# # Hosts that should bypass the proxy (optional) +# noProxy: # resources: # additionalContainers: # KEDA autoscaling configuration based on monitor queue metrics diff --git a/Probe/API/Metrics.ts b/Probe/API/Metrics.ts index 393fbac815..9864276077 100644 --- a/Probe/API/Metrics.ts +++ b/Probe/API/Metrics.ts @@ -43,7 +43,7 @@ router.get( url: queueSizeUrl, data: requestBody, headers: {}, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { ...ProxyConfig.getRequestProxyAgents(queueSizeUrl) }, }); if (result instanceof HTTPErrorResponse) { diff --git a/Probe/Config.ts b/Probe/Config.ts index 325e8958e8..6d2a6dce15 100644 --- a/Probe/Config.ts +++ b/Probe/Config.ts @@ -103,3 +103,28 @@ export const HTTP_PROXY_URL: string | null = */ export const HTTPS_PROXY_URL: string | null = process.env["HTTPS_PROXY_URL"] || process.env["https_proxy"] || null; + +/* + * NO_PROXY: Comma-separated list of hosts that should bypass the configured proxy. + * Hosts can include optional ports (example.com:8080) or leading dots for subdomains (.example.com). + */ +const rawNoProxy: string | undefined = + process.env["NO_PROXY"] || process.env["no_proxy"] || undefined; + +export const NO_PROXY: Array = rawNoProxy + ? rawNoProxy + .split(",") + .map((value: string) => value.trim()) + .reduce>((accumulator: Array, current: string) => { + if (!current) { + return accumulator; + } + + const parts: Array = current + .split(/\s+/) + .map((item: string) => item.trim()) + .filter((item: string) => item.length > 0); + + return accumulator.concat(parts); + }, []) + : []; diff --git a/Probe/Jobs/Alive.ts b/Probe/Jobs/Alive.ts index b20f989dbb..acef3ad72b 100644 --- a/Probe/Jobs/Alive.ts +++ b/Probe/Jobs/Alive.ts @@ -36,10 +36,14 @@ const InitJob: VoidFunction = (): void => { logger.debug("Probe ID: " + probeId.toString()); + const aliveUrl: URL = URL.fromString(PROBE_INGEST_URL.toString()).addRoute( + "/alive", + ); + const result: HTTPResponse = await API.post({ - url: URL.fromString(PROBE_INGEST_URL.toString()).addRoute("/alive"), + url: aliveUrl, data: ProbeAPIRequest.getDefaultRequestBody(), - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { ...ProxyConfig.getRequestProxyAgents(aliveUrl) }, }); if (result.isSuccess()) { diff --git a/Probe/Jobs/Monitor/FetchList.ts b/Probe/Jobs/Monitor/FetchList.ts index 1a30f19413..c55bd1e871 100644 --- a/Probe/Jobs/Monitor/FetchList.ts +++ b/Probe/Jobs/Monitor/FetchList.ts @@ -101,7 +101,7 @@ class FetchListAndProbe { limit: PROBE_MONITOR_FETCH_LIMIT || 100, }, headers: {}, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { ...ProxyConfig.getRequestProxyAgents(monitorListUrl) }, }); logger.debug("Fetched monitor list"); diff --git a/Probe/Jobs/Monitor/FetchMonitorTest.ts b/Probe/Jobs/Monitor/FetchMonitorTest.ts index 337a5a773f..5a347125a1 100644 --- a/Probe/Jobs/Monitor/FetchMonitorTest.ts +++ b/Probe/Jobs/Monitor/FetchMonitorTest.ts @@ -65,7 +65,7 @@ class FetchMonitorTestAndProbe { limit: 100, }, headers: {}, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { ...ProxyConfig.getRequestProxyAgents(monitorListUrl) }, }); logger.debug("MONITOR TEST: Fetched monitor test list"); diff --git a/Probe/Services/Register.ts b/Probe/Services/Register.ts index 7b7a71c12a..d09adfe844 100644 --- a/Probe/Services/Register.ts +++ b/Probe/Services/Register.ts @@ -73,17 +73,19 @@ export default class Register { hostname: HOSTNAME, }; + const statusReportUrl: URL = URL.fromString( + PROBE_INGEST_URL.toString(), + ).addRoute("/probe/status-report/offline"); + await API.fetch({ method: HTTPMethod.POST, - url: URL.fromString(PROBE_INGEST_URL.toString()).addRoute( - "/probe/status-report/offline", - ), + url: statusReportUrl, data: { ...ProbeAPIRequest.getDefaultRequestBody(), statusReport: stausReport as any, }, headers: {}, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { ...ProxyConfig.getRequestProxyAgents(statusReportUrl) }, }); } } @@ -131,7 +133,9 @@ export default class Register { probeDescription: PROBE_DESCRIPTION, clusterKey: ClusterKeyAuthorization.getClusterKey(), }, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { + ...ProxyConfig.getRequestProxyAgents(probeRegistrationUrl), + }, }); if (result.isSuccess()) { @@ -149,13 +153,17 @@ export default class Register { return process.exit(); } + const aliveUrl: URL = URL.fromString(PROBE_INGEST_URL.toString()).addRoute( + "/alive", + ); + await API.post({ - url: URL.fromString(PROBE_INGEST_URL.toString()).addRoute("/alive"), + url: aliveUrl, data: { probeKey: PROBE_KEY.toString(), probeId: PROBE_ID.toString(), }, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { ...ProxyConfig.getRequestProxyAgents(aliveUrl) }, }); LocalCache.setString("PROBE", "PROBE_ID", PROBE_ID.toString() as string); diff --git a/Probe/Utils/Monitors/Monitor.ts b/Probe/Utils/Monitors/Monitor.ts index 91fbdb1185..db8ada92c9 100644 --- a/Probe/Utils/Monitors/Monitor.ts +++ b/Probe/Utils/Monitors/Monitor.ts @@ -63,18 +63,24 @@ export default class MonitorUtil { if (result) { // report this back to Probe API. + const monitorTestIngestUrl: URL = URL.fromString( + PROBE_INGEST_URL.toString(), + ).addRoute( + "/probe/response/monitor-test-ingest/" + + monitorTest.id?.toString(), + ); await API.fetch({ method: HTTPMethod.POST, - url: URL.fromString(PROBE_INGEST_URL.toString()).addRoute( - "/probe/response/monitor-test-ingest/" + monitorTest.id?.toString(), - ), + url: monitorTestIngestUrl, data: { ...ProbeAPIRequest.getDefaultRequestBody(), probeMonitorResponse: result as any, }, headers: {}, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { + ...ProxyConfig.getRequestProxyAgents(monitorTestIngestUrl), + }, }); } @@ -112,18 +118,21 @@ export default class MonitorUtil { if (result) { // report this back to Probe API. + const monitorIngestUrl: URL = URL.fromString( + PROBE_INGEST_URL.toString(), + ).addRoute("/probe/response/ingest"); await API.fetch({ method: HTTPMethod.POST, - url: URL.fromString(PROBE_INGEST_URL.toString()).addRoute( - "/probe/response/ingest", - ), + url: monitorIngestUrl, data: { ...ProbeAPIRequest.getDefaultRequestBody(), probeMonitorResponse: result as any, }, headers: {}, - options: { ...ProxyConfig.getRequestProxyAgents() }, + options: { + ...ProxyConfig.getRequestProxyAgents(monitorIngestUrl), + }, }); } diff --git a/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts index 9fa829079f..43b51655b0 100644 --- a/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts @@ -67,7 +67,7 @@ export default class ApiMonitor { options: { timeout: options.timeout?.toNumber() || 5000, doNotFollowRedirects: options.doNotFollowRedirects || false, - ...ProxyConfig.getRequestProxyAgents(), + ...ProxyConfig.getRequestProxyAgents(url), }, }; @@ -91,7 +91,7 @@ export default class ApiMonitor { options: { timeout: options.timeout?.toNumber() || 5000, doNotFollowRedirects: options.doNotFollowRedirects || false, - ...ProxyConfig.getRequestProxyAgents(), + ...ProxyConfig.getRequestProxyAgents(url), }, }; diff --git a/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts index 9a4654cf57..f58ac7f8d9 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts @@ -279,9 +279,9 @@ export default class SSLMonitor { // Use proxy agent if proxy is configured if (ProxyConfig.isProxyConfigured()) { const httpsProxyAgent: HttpsProxyAgent | null = - ProxyConfig.getHttpsProxyAgent(); + ProxyConfig.getHttpsProxyAgent(url); const httpProxyAgent: HttpProxyAgent | null = - ProxyConfig.getHttpProxyAgent(); + ProxyConfig.getHttpProxyAgent(url); // Prefer HTTPS proxy agent, fall back to HTTP proxy agent const proxyAgent: diff --git a/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts index ae9f91b316..4765ad167c 100644 --- a/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts @@ -65,7 +65,7 @@ export default class WebsiteMonitor { isHeadRequest: options.isHeadRequest, timeout: options.timeout?.toNumber() || 5000, doNotFollowRedirects: options.doNotFollowRedirects || false, - ...ProxyConfig.getRequestProxyAgents(), + ...ProxyConfig.getRequestProxyAgents(url), }); if ( @@ -78,7 +78,7 @@ export default class WebsiteMonitor { isHeadRequest: false, timeout: options.timeout?.toNumber() || 5000, doNotFollowRedirects: options.doNotFollowRedirects || false, - ...ProxyConfig.getRequestProxyAgents(), + ...ProxyConfig.getRequestProxyAgents(url), }); }