Hi Christopher.
On 2026-10-20 (Di.) 08:58, Christopher Faulet wrote:
Le 16/10/2026 à 9:50 PM, Aleksandar Lazic a écrit :
Hi.
I'm planning a new filter to bring HAProxy's own DNA to "cloud" or HTTP(S)
object storages, such as Azure Blob Storage, Google Cloud Storage and S3
compatible storages. The problem is that most of these storages require
authentication and/or a request signature before they deliver the data.
The filter API already covers this to a certain degree. What was missing is
a callback called once the server is known, just before the request is sent
to it. A signature depends on the target server, and the server may change
on a redispatch or a L7 retry, so the request must be finalized at that
point. This patch series adds such an "http_pre_send" callback, along with
a first shot of the cloudauth filter.
The cloudauth filter is opt-in at build time (USE_CLOUDAUTH) and marked as
experimental. For now it only stamps the request with its target server, to
show that the callback follows redispatches and L7 retries. The request
signers will be plugged in at the same place.
While working on this, I also noticed that http_reset is documented as being
called on L7 retries, but it never is. The first patch fixes the
documentation.
Of course I used AI (Claude) to help with the patches, which is noted with
a Co-Authored-By trailer in each commit.
Hi Aleks,
I quickly reviewed your patches.
First of all, about the http_reset(), I guess we can extend it to the L7
retries, especially because it was documented and never implemented... But we
must decide when exactly it should be called. Before or after the stconn
reset ? Before or after the L7 buffer swapping ?
Thanks! I agree, implementing it is better than documenting the gap.
I would call it only for the response message (&txn->rsp), after
sc_reset_endp() succeeded and before the L7 buffer swap:
- sc_reset_endp() is the last step of do_l7_retry() that may fail. Once
it succeeded the retry is certain, so a filter never resets its
context for a retry that does not happen.
- Before the swap and before b_reset(&res->buf), the failed response is
still in the buffer and txn->status is set. That matches the 1xx case,
where http_reset() is called before channel_htx_fwd_headers(), while
the interim response is still visible. The request that is sent again
is seen by the filters in the new callback anyway (see below).
- Only the response is reset from the filters' point of view: the
request callbacks (http_headers/payload/end) are not replayed on a L7
retry, the request is sent again from the L7 buffer as is.
While testing with the trace filter I noticed that
channel_start_analyze() is called a second time on the response channel
after a L7 retry (the response analyzers are set again by
back_establish()), without a channel_end_analyze() in between. With
http_reset() called just before, a filter at least gets a hint. Should
this be documented too, or do you prefer to avoid the second call?
If that's fine for you, I will replace the DOC patch with one that calls
http_reset() on L7 retries and updates the filters documentation.
About your http_pre_send_callback, I'm not a big fan of adding such code in
stconn.c, on the sending path. If the purpose is to be notified of the
selected server, I guess it could be better to add a "stream_set_target"
callback function, usable for servers and applets. It could be placed in the
stream_set_target() function, with the same behavior than "stream_set_backend"
callback function: a negative value to report an error, otherwise it is a
success. The callback would take a obj_type. Maybe the function should be
moved from the header file and placed in stream.c.
Do you think this would fit your requirement ?
OK, I will move it out of stconn.c. But I'm not sure stream_set_target()
fits a filter that must sign the request:
- It is also called during the request analysis (use-server and
force-persist in process_server_rules(), cookie and stick persistence,
RDP cookie), before AN_REQ_HTTP_INNER and the http_headers callbacks of
the other filters, so the request is not final yet at that point.
- It returns void and is called from many places, so a filter cannot
report a failure there and stop the request before it is sent. For a
signing filter that's a requirement: without valid credentials the
request must not leave HAProxy.
- It is also called with a NULL target to clear it, e.g. at the
beginning of assign_server() before the LB picks a server.
What about a callback at the beginning of connect_server() instead? The
target is known there, it's called once per connection attempt
(redispatch, retries, L7 retries), it's not on the sending path, and it
already returns SF_ERR_INTERNAL, which back_try_conn_req() handles
properly (no retry, error reported to the client, log line). A negative
return value from the filter would map to SF_ERR_INTERNAL.
It only covers servers, not applets. If filters also be able to be
notified when an applet is selected, a separate informational
stream_set_target callback could be added for that.
About the filter itself, it must be at least moved in the addons folder. But
depending on the implementation and the dependencies (technical or
functional), it could be moved into a dedicated repository, as for the
opentelemetry filter (https://github.com/haproxytech/haproxy-opentelemetry).
Sure. Apart from OpenSSL it has no external dependency, but it relies on
several internal APIs (httpclient, HTX, filters, traces, JWT), so I would
prefer to keep it in the tree under addons/cloudauth, like promex. But
if you prefer a separate repository like https://github.com/git001/haproxy-
cloudauth, that works for me too. What do you prefer?
To give an idea of what is coming: besides the core callback, the
filter currently supports Azure Blob Storage, Google Cloud Storage and
S3 compatible storages (AWS, with OVH planned next). The credentials are
either given by the administrator (environment, file at parse time, CLI)
or fetched by HAProxy itself through the httpclient from the usual
sources of each cloud (managed identity, workload identity, instance
metadata, STS), refreshed in the background and never logged. The core
part comes first, the filter itself follows once we agree on where it
should live.
Regards,
--
Christopher Faulet
Best regards
Aleks