feat(proxy): add NO_PROXY support and use request URL for proxy agent selection

- Parse NO_PROXY / no_proxy in Probe Config into a trimmed list
- Wire NO_PROXY into UI docs, Helm chart values, and probe Docker/compose examples
- Add NO_PROXY env var to Helm probe template when provided
- Pass target URL to ProxyConfig.getRequestProxyAgents / getHttpProxyAgent / getHttpsProxyAgent so proxy selection is per-request
- Update probe calls (Alive, Metrics, FetchList, FetchMonitorTest, Register, Monitor ingest/reporting, Api/Website/Ssl monitors) to use local URL variables and supply them to proxy helpers
- Minor refactors to avoid inline URL construction where reused
This commit is contained in:
Nawaz Dhandala
2025-10-24 15:02:36 +01:00
parent 35f9b7f5c4
commit f403c6a9e9
15 changed files with 97 additions and 28 deletions

View File

@@ -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
`}
/>

View File

@@ -27,6 +27,7 @@ docker run --name oneuptime-probe --network host \
-e PROBE_ID=<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=<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

View File

@@ -105,6 +105,7 @@ The following table lists the configurable parameters of the OneUptime chart and
| `probes.<key>.customCodeMonitorScriptTimeoutInMs` | Timeout for custom code monitor script | `60000` | |
| `probes.<key>.proxy.httpProxyUrl` | HTTP proxy URL for HTTP requests made by the probe (optional) | `nil` | |
| `probes.<key>.proxy.httpsProxyUrl` | HTTPS proxy URL for HTTPS requests made by the probe (optional) | `nil` | |
| `probes.<key>.proxy.noProxy` | Comma-separated hosts that should bypass the proxy (optional) | `nil` | |
| `probes.<key>.additionalContainers` | Additional containers to add to the probe pod | `nil` | |
| `probes.<key>.resources` | Pod resources (limits, requests) | `nil` | |
| `statusPage.cnameRecord` | CNAME record for the status page | `nil` | |

View File

@@ -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 }}

View File

@@ -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

View File

@@ -43,7 +43,7 @@ router.get(
url: queueSizeUrl,
data: requestBody,
headers: {},
options: { ...ProxyConfig.getRequestProxyAgents() },
options: { ...ProxyConfig.getRequestProxyAgents(queueSizeUrl) },
});
if (result instanceof HTTPErrorResponse) {

View File

@@ -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<string> = rawNoProxy
? rawNoProxy
.split(",")
.map((value: string) => value.trim())
.reduce<Array<string>>((accumulator: Array<string>, current: string) => {
if (!current) {
return accumulator;
}
const parts: Array<string> = current
.split(/\s+/)
.map((item: string) => item.trim())
.filter((item: string) => item.length > 0);
return accumulator.concat(parts);
}, [])
: [];

View File

@@ -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<JSONObject> = 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()) {

View File

@@ -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");

View File

@@ -65,7 +65,7 @@ class FetchMonitorTestAndProbe {
limit: 100,
},
headers: {},
options: { ...ProxyConfig.getRequestProxyAgents() },
options: { ...ProxyConfig.getRequestProxyAgents(monitorListUrl) },
});
logger.debug("MONITOR TEST: Fetched monitor test list");

View File

@@ -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<JSONObject>({
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);

View File

@@ -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<JSONObject>({
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<JSONObject>({
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),
},
});
}

View File

@@ -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),
},
};

View File

@@ -279,9 +279,9 @@ export default class SSLMonitor {
// Use proxy agent if proxy is configured
if (ProxyConfig.isProxyConfigured()) {
const httpsProxyAgent: HttpsProxyAgent<string> | null =
ProxyConfig.getHttpsProxyAgent();
ProxyConfig.getHttpsProxyAgent(url);
const httpProxyAgent: HttpProxyAgent<string> | null =
ProxyConfig.getHttpProxyAgent();
ProxyConfig.getHttpProxyAgent(url);
// Prefer HTTPS proxy agent, fall back to HTTP proxy agent
const proxyAgent:

View File

@@ -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),
});
}