Skip to content

[fix][client] Apply connect and request timeouts to the OAuth2 HTTP requests - #609

Closed
david-streamlio wants to merge 1 commit into
apache:mainfrom
david-streamlio:fix/oauth2-http-timeouts
Closed

[fix][client] Apply connect and request timeouts to the OAuth2 HTTP requests#609
david-streamlio wants to merge 1 commit into
apache:mainfrom
david-streamlio:fix/oauth2-http-timeouts

Conversation

@david-streamlio

Copy link
Copy Markdown

Fixes #608

Motivation

The OAuth2 authentication flows issue two HTTP requests — the OpenID discovery request to
<issuer_url>/.well-known/openid-configuration and the token request to the token endpoint —
and neither had any timeout configured.

CurlWrapper::Options::timeoutInSeconds defaults to 0, which libcurl interprets as "no
limit" for CURLOPT_TIMEOUT, and CURLOPT_CONNECTTIMEOUT was never set at all. Both OAuth2
call sites left the default in place: fetchTokenEndpoint() passed {} and
fetchOauth2Token() only set postFields.

As a result, an issuer that accepts the TCP connection but never replies blocks
Authentication::getAuthData() — and therefore client creation and every subsequent
reconnection — forever, with no way for the application to recover.

Modifications

  • CurlWrapper::Options gains connectTimeoutInSeconds, applied as CURLOPT_CONNECTTIMEOUT
    when greater than 0. The existing timeoutInSeconds{0} default is unchanged, so the other
    callers of CurlWrapper::get() keep their current behaviour.

  • Both timeouts are now set at the two OAuth2 call sites.

  • The values are configurable through the OAuth2 ParamMap, alongside issuer_url,
    audience and scope:

    • connect_timeout_seconds, default 10
    • request_timeout_seconds, default 30

    A missing key, or a value that is not a non-negative integer, falls back to the default and
    logs a warning. 0 is accepted and falls back to libcurl's own defaults. Both keys work for
    client_secret_post and tls_client_auth, and are documented in
    include/pulsar/Authentication.h.

  • Drive-by: CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT are now passed as long.
    curl_easy_setopt is variadic and reads a long for these options, so passing an int was
    undefined behaviour.

Verifying this change

This change added tests and can be verified as follows:

  • AuthPluginTest.testOauth2UnresponsiveIssuer points an OAuth2 flow at a local socket that
    is bound and listening but never accepted — the kernel completes the TCP handshake from the
    listen backlog, so the client connects and then waits for a response that never arrives. The
    test configures request_timeout_seconds = 2 and asserts that getAuthData() returns
    ResultAuthenticationError within a bounded time instead of hanging. It completes in ~2s;
    with the timeout removed the same test hangs indefinitely.
  • AuthPluginTest.testOauth2TimeoutSettings covers the defaults, explicit values, 0, and
    invalid values falling back to the defaults.

Ran locally with --gtest_filter=*Oauth2*: 11 of 12 pass. The remaining failure,
testOauth2Failure, requires a broker on localhost:6650 and fails identically on main.

Documentation

  • doc-required
    (The two new connect_timeout_seconds / request_timeout_seconds parameters should be added
    to the OAuth2 section of the C++ client docs on the website. The Doxygen comments in
    include/pulsar/Authentication.h are updated in this PR.)

🤖 Generated with Claude Code

…equests

The OAuth2 flows fetched the OpenID discovery document and the token with no
timeout: CurlWrapper::Options::timeoutInSeconds defaults to 0, which libcurl
reads as "no limit", and CURLOPT_CONNECTTIMEOUT was never set. An issuer that
accepts the connection but never replies therefore blocked getAuthData()
forever.

Add connectTimeoutInSeconds to CurlWrapper::Options (applied as
CURLOPT_CONNECTTIMEOUT only when set, so other callers are unaffected) and set
both timeouts at the two OAuth2 call sites. The values are configurable through
the OAuth2 ParamMap via connect_timeout_seconds (default 10) and
request_timeout_seconds (default 30).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@david-streamlio

Copy link
Copy Markdown
Author

Closing this as a duplicate of #606, which covers the same fix and predates this PR.

Both add connect_timeout_seconds / request_timeout_seconds to the OAuth2 ParamMap with the same 10s / 30s defaults, add a connect-timeout option to CurlWrapper, and apply both timeouts at the discovery and token call sites for both OAuth2 flows.

Two small deltas from this PR that might be worth folding into #606:

  1. CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT should be passed as long. curl_easy_setopt is variadic and reads a long for these options, so passing an int is undefined behaviour.
  2. Accepting 0 as "fall back to libcurl's own defaults" rather than rejecting it, so users can opt out of the application-level deadline without an out-of-band mechanism.

/cc @RobertIndie#608 stays open until #606 lands.

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.

[Bug] OAuth2 authentication makes HTTP requests with no timeout, hanging the client indefinitely

1 participant