Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Honor http_proxy environment variables #1111

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# Changelog

## Unreleased

**Features**:

- Honor `http_proxy` environment variables. ([#1111](https://github.com/getsentry/sentry-native/pull/1111))

## 0.7.18

**Features**:

- Add support for Xbox Series X/S. ([#1100](https://github.com/getsentry/sentry-native/pull/1100))
- Add option to set debug log level. ([#1107](https://github.com/getsentry/sentry-native/pull/1107))
- Add `traces_sampler` ([#1108](https://github.com/getsentry/sentry-native/pull/1108))
- Add `traces_sampler`. ([#1108](https://github.com/getsentry/sentry-native/pull/1108))
- Provide support for C++17 compilers when using the `crashpad` backend. ([#1110](https://github.com/getsentry/sentry-native/pull/1110), [crashpad#116](https://github.com/getsentry/crashpad/pull/116), [mini_chromium#1](https://github.com/getsentry/mini_chromium/pull/1))

## 0.7.17
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ The example currently supports the following commands:
- `override-sdk-name`: Changes the SDK name via the options at runtime.
- `stack-overflow`: Provokes a stack-overflow.
- `http-proxy`: Uses a localhost `HTTP` proxy on port 8080.
- `http-proxy-auth`: Uses a localhost `HTTP` proxy on port 8080 with `user:password` as authentication.
- `socks5-proxy`: Uses a localhost `SOCKS5` proxy on port 1080.
- `socks5-proxy-auth`: Uses a localhost `SOCKS5` proxy on port 1080 with `user:password` as authentication.
- `proxy-from-env`: Reads the proxy settings from the environment variables `https_proxy` and `http_proxy` (in this order of precedence).
- `capture-transaction`: Captures a transaction.
- `traces-sampler`: Installs a traces sampler callback function when used alongside `capture-transaction`.

Expand Down
12 changes: 12 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,22 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "http-proxy")) {
sentry_options_set_proxy(options, "http://127.0.0.1:8080");
}
if (has_arg(argc, argv, "http-proxy-auth")) {
sentry_options_set_proxy(
options, "http://user:[email protected]:8080");
}

if (has_arg(argc, argv, "socks5-proxy")) {
sentry_options_set_proxy(options, "socks5://127.0.0.1:1080");
}
if (has_arg(argc, argv, "socks5-proxy-auth")) {
sentry_options_set_proxy(
options, "socks5://user:[email protected]:1080");
}

if (has_arg(argc, argv, "proxy-from-env")) {
sentry_options_set_read_proxy_from_environment(options, true);
}

sentry_init(options);

Expand Down
11 changes: 11 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,17 @@ SENTRY_API void sentry_options_set_http_proxy_n(
SENTRY_API const char *sentry_options_get_http_proxy(
const sentry_options_t *opts);

/**
* Sets whether to read the proxy settings from the environment.
*/
SENTRY_API void sentry_options_set_read_proxy_from_environment(
sentry_options_t *opts, int val);
/**
* Returns whether to read the proxy settings from the environment.
*/
SENTRY_API int sentry_options_get_read_proxy_from_environment(
const sentry_options_t *opts);

/**
* Configures the path to a file containing ssl certificates for
* verification.
Expand Down
4 changes: 4 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ sentry_init(sentry_options_t *options)
"the provided DSN \"%s\" is not valid", raw_dsn ? raw_dsn : "");
}

if (options->read_proxy_from_environment) {
sentry__set_proxy_from_environment(options);
}
Comment on lines +141 to +143
Copy link
Member Author

Choose a reason for hiding this comment

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

With this new option, I'm not sure we still need to support no_proxy; if users want to use the environment variable value in their own code, they can just not set this flag to true (which is the default anyway).

Copy link
Collaborator

@supervacuus supervacuus Jan 13, 2025

Choose a reason for hiding this comment

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

Yes and no. You always have to consider that this is a feature that the users of our users require from them.

So, for instance, if your application ignores the http_proxy env-vars, your users don't care about the topic and accept that they have to manually configure the proxy config for that application (or even have no proxy config at all).

At some point, you might provide a UI to your users that does something like the following:

(   ) direct HTTP connection.
(   ) manually specify HTTP proxy: _________ .
( * ) read proxy from the environment.

You can route this directly to our proposed options interface. However, once your users have configured the app to read from the environment, they will want to stay with it because it allows them to have a centrally managed proxy configuration rather than defining it separately in every application.

This is where no_proxy enters the picture. So, yes, resetting the proxy config of that app solves the problem, but then the users of our users are essentially back to square 1. Again, this is not a requirement for an initial implementation of that feature but a likely follow-up.


if (transport) {
if (sentry__transport_startup(transport, options) != 0) {
SENTRY_WARN("failed to initialize transport");
Expand Down
23 changes: 23 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,29 @@ sentry_options_get_http_proxy(const sentry_options_t *opts)
return sentry_options_get_proxy(opts);
}

void
sentry_options_set_read_proxy_from_environment(sentry_options_t *opts, int val)
{
opts->read_proxy_from_environment = !!val;
}

int
sentry_options_get_read_proxy_from_environment(const sentry_options_t *opts)
{
return opts->read_proxy_from_environment;
}

void
sentry__set_proxy_from_environment(sentry_options_t *opts)
{
const char *https_proxy = getenv("https_proxy");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we support upper-case HTTP_PROXY and HTTPS_PROXY too? What happens if HTTP_PROXY and http_proxy are defined?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am fine with only accepting the lower-case versions, but since this is a "thing", we should clearly document the behavior.

if (https_proxy) {
sentry_options_set_proxy(opts, https_proxy);
} else {
sentry_options_set_proxy(opts, getenv("http_proxy"));
JoshuaMoelans marked this conversation as resolved.
Show resolved Hide resolved
}
}

void
sentry_options_set_ca_certs(sentry_options_t *opts, const char *path)
{
Expand Down
5 changes: 5 additions & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ typedef struct sentry_options_s {
bool require_user_consent;
bool symbolize_stacktraces;
bool system_crash_reporter_enabled;
bool read_proxy_from_environment;

sentry_attachment_t *attachments;
sentry_run_t *run;
Expand Down Expand Up @@ -80,4 +81,8 @@ typedef struct sentry_options_s {
*/
sentry_options_t *sentry__options_incref(sentry_options_t *options);

/**
* Sets the proxy value by reading it from the environment.
*/
void sentry__set_proxy_from_environment(sentry_options_t *opts);
#endif
51 changes: 51 additions & 0 deletions src/transports/sentry_transport_winhttp.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ typedef struct {
sentry_dsn_t *dsn;
wchar_t *user_agent;
wchar_t *proxy;
wchar_t *proxy_username;
wchar_t *proxy_password;
sentry_rate_limiter_t *ratelimiter;
HINTERNET session;
HINTERNET connect;
Expand Down Expand Up @@ -51,10 +53,47 @@ sentry__winhttp_bgworker_state_free(void *_state)
sentry__dsn_decref(state->dsn);
sentry__rate_limiter_free(state->ratelimiter);
sentry_free(state->user_agent);
sentry_free(state->proxy_username);
sentry_free(state->proxy_password);
sentry_free(state->proxy);
sentry_free(state);
}

// Function to extract and set credentials TODO replace with `sentry__url_parse`
void
set_proxy_credentials(winhttp_bgworker_state_t *state, const char *proxy)
{
const char *at_sign = strchr(proxy, '@');
if (at_sign) {
const char *credentials = proxy + 7; // Skip "http://"
char *colon = strchr(credentials, ':');
if (colon && colon < at_sign) {
size_t user_len = colon - credentials;
size_t pass_len = at_sign - colon - 1;
char *user = (char *)malloc(user_len + 1);
char *pass = (char *)malloc(pass_len + 1);
strncpy(user, credentials, user_len);
user[user_len] = '\0';
strncpy(pass, colon + 1, pass_len);
pass[pass_len] = '\0';

// Convert user and pass to LPCWSTR
int user_wlen = MultiByteToWideChar(CP_UTF8, 0, user, -1, NULL, 0);
int pass_wlen = MultiByteToWideChar(CP_UTF8, 0, pass, -1, NULL, 0);
wchar_t *user_w = (wchar_t *)malloc(user_wlen * sizeof(wchar_t));
wchar_t *pass_w = (wchar_t *)malloc(pass_wlen * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, user, -1, user_w, user_wlen);
MultiByteToWideChar(CP_UTF8, 0, pass, -1, pass_w, pass_wlen);

state->proxy_username = user_w;
state->proxy_password = pass_w;

sentry_free(user);
sentry_free(pass);
}
}
}

static int
sentry__winhttp_transport_start(
const sentry_options_t *opts, void *transport_state)
Expand All @@ -71,7 +110,12 @@ sentry__winhttp_transport_start(
// ensure the proxy starts with `http://`, otherwise ignore it
if (opts->proxy && strstr(opts->proxy, "http://") == opts->proxy) {
const char *ptr = opts->proxy + 7;
Comment on lines 110 to 112
Copy link
Member Author

@JoshuaMoelans JoshuaMoelans Jan 17, 2025

Choose a reason for hiding this comment

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

Apparently our Windows transport doesn't take https:// proxies. This should be documented, since we now also read the HTTPS_PROXY environment variable, which on this platform would get ignored.

const char *at_sign = strchr(ptr, '@');
const char *slash = strchr(ptr, '/');
if (at_sign && (!slash || at_sign < slash)) {
ptr = at_sign + 1;
set_proxy_credentials(state, opts->proxy);
}
if (slash) {
char *copy = sentry__string_clone_n(ptr, slash - ptr);
state->proxy = sentry__string_to_wstr(copy);
Expand Down Expand Up @@ -103,6 +147,7 @@ sentry__winhttp_transport_start(
SENTRY_WARN("`WinHttpOpen` failed");
return 1;
}

return sentry__bgworker_start(bgworker);
}

Expand Down Expand Up @@ -209,6 +254,12 @@ sentry__winhttp_send_task(void *_envelope, void *_state)
SENTRY_DEBUGF(
"sending request using winhttp to \"%s\":\n%S", req->url, headers);

if (state->proxy_username && state->proxy_password) {
WinHttpSetCredentials(state->request, WINHTTP_AUTH_TARGET_PROXY,
WINHTTP_AUTH_SCHEME_BASIC, state->proxy_username,
state->proxy_password, 0);
}

if (WinHttpSendRequest(state->request, headers, (DWORD)-1,
(LPVOID)req->body, (DWORD)req->body_len, (DWORD)req->body_len, 0)) {
WinHttpReceiveResponse(state->request, NULL);
Expand Down
19 changes: 14 additions & 5 deletions tests/test_integration_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@ def test_capture_minidump(cmake, httpserver):
],
)
@pytest.mark.parametrize("proxy_status", [(["off"]), (["on"])])
def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
@pytest.mark.parametrize("proxy_auth", [(["off"]), (["on"])])
def test_capture_proxy(cmake, httpserver, run_args, proxy_status, proxy_auth):
if not shutil.which("mitmdump"):
pytest.skip("mitmdump is not installed")

Expand All @@ -635,12 +636,18 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
if proxy_status == ["on"]:
# start mitmdump from terminal
if run_args == ["http-proxy"]:
proxy_process = subprocess.Popen(["mitmdump"])
proxy_command = ["mitmdump"]
if proxy_auth == ["on"]:
proxy_command.append("--proxyauth=user:password")
proxy_process = subprocess.Popen(proxy_command)
time.sleep(5) # Give mitmdump some time to start
if not is_proxy_running("localhost", 8080):
pytest.fail("mitmdump (HTTP) did not start correctly")
elif run_args == ["socks5-proxy"]:
proxy_process = subprocess.Popen(["mitmdump", "--mode", "socks5"])
proxy_command = ["mitmdump", "--mode", "socks5"]
if proxy_auth == ["on"]:
proxy_command.append("--proxyauth=user:password")
proxy_process = subprocess.Popen(proxy_command)
time.sleep(5) # Give mitmdump some time to start
if not is_proxy_running("localhost", 1080):
pytest.fail("mitmdump (SOCKS5) did not start correctly")
Expand All @@ -651,12 +658,14 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)

httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")

current_run_arg = run_args[0]
if proxy_auth == ["on"]:
current_run_arg += "-auth"
run(
tmp_path,
"sentry_example",
["log", "start-session", "capture-event"]
+ run_args, # only passes if given proxy is running
+ [current_run_arg], # only passes if given proxy is running
check=True,
env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)),
)
Expand Down
Loading