Add remote address tracking for JDBC/Avatica SQL queries#19231
Conversation
17dd93d to
5aac04a
Compare
test: verify remote address capture in Avatica SQL queries test: Replace forbidden API calls in DruidAvaticaHandlerTest test: Update test expectations to include remoteAddress in query context & Update Quidem test files for remote address tracking test: Fix checkstyle violations
1563f92 to
fc1d96f
Compare
| public boolean handle(Request request, Response response, Callback callback) throws Exception | ||
| { | ||
| String requestURI = request.getHttpURI().getPath(); | ||
| String remoteAddr = Request.getRemoteAddr(request); |
There was a problem hiding this comment.
[P2] Remote address is still missing for protobuf Avatica clients
This only captures the remote address in the JSON handler, but Druid also exposes the protobuf Avatica endpoint and DruidAvaticaProtobufHandlerTest runs the same JDBC coverage with serialization=protobuf. Protobuf requests never set THREAD_LOCAL_REMOTE_ADDRESS, so openDruidConnection leaves remoteAddress absent and JDBC/protobuf queries still emit null remoteAddress, leaving half of the Avatica API unfixed.
There was a problem hiding this comment.
Fixed in 655299d — added remoteAddress collection for the protobuf Avatica endpoint as well.
| { | ||
| String remoteAddress = THREAD_LOCAL_REMOTE_ADDRESS.get(); | ||
| if (remoteAddress != null) { | ||
| context.put("remoteAddress", remoteAddress); |
There was a problem hiding this comment.
[P2] Internal remoteAddress is treated as a user context key
This adds remoteAddress to the JDBC session context before SqlQueryPlus.withContext(..., sessionContext) derives authContextKeys from sessionContext.keySet(). In clusters with query context authorization enabled, remoteAddress is not in AuthConfig.ALLOWED_CONTEXT_KEYS, so ordinary Avatica queries can now require WRITE permission on QUERY_CONTEXT remoteAddress even though the server, not the user, injected it. Pass this value outside the user context or mark it as an allowed internal key.
e49e8ea to
655299d
Compare
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
Reviewed 12 of 12 changed files.
This is an automated review by Codex GPT-5.5
| { | ||
| String remoteAddress = THREAD_LOCAL_REMOTE_ADDRESS.get(); | ||
| if (remoteAddress != null) { | ||
| context.put("remoteAddress", remoteAddress); |
There was a problem hiding this comment.
[P1] Allow the injected remoteAddress key through context auth
This stores the server-injected remoteAddress in the JDBC session context, and later SqlQueryPlus.withContext treats every sessionContext key as an authContextKey. In clusters with authorizeQueryContextParams enabled, remoteAddress is not in AuthConfig.ALLOWED_CONTEXT_KEYS, so every Avatica query opened through this path can require WRITE permission on QUERY_CONTEXT:remoteAddress and fail authorization even though the user did not provide that key. Please either keep remoteAddress out of the user/session query context used for auth, or add this server-injected key to the allowed-context list.
There was a problem hiding this comment.
aefdd64
Thanks for the review. I've addressed this by passing remoteAddress as a dedicated parameter through the call chain instead of injecting it into the session context map.
Before:
- DruidMeta.openConnection() puts remoteAddress into the session context map
- SqlQueryPlus.withContext() picks it up as an authContextKey
- With authorizeQueryContextParams enabled, this triggers a WRITE permission check on QUERY_CONTEXT:remoteAddress
After:
- remoteAddress is removed from the session context map entirely
- Passed as a separate constructor/method parameter: DruidMeta → DruidConnection → DruidJdbcStatement / PreparedStatement → SqlStatementFactory → DirectStatement
- No longer treated as a user-supplied context key, so no unintended auth checks
This keeps it out of authContextKey entirely while still being available for metrics/logging. Verified by building only the druid-sql jar and swapping it into a running
cluster — works as expected.
(test version: 37.0.0)
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 0 |
| P2 | 1 |
| P3 | 0 |
| Total | 1 |
Reviewed 10 of 10 changed files. The earlier reply follow-ups look resolved: remoteAddress is no longer injected into session context, and the protobuf handler now sets the thread-local remote address. I found one remaining issue below.
This is an automated review by Codex GPT-5.5
| final String remoteAddress | ||
| ) | ||
| { | ||
| super(lifecycleToolbox, queryPlus, null); |
There was a problem hiding this comment.
[P2] Pass remoteAddress into the PreparedStatement reporter
The Avatica path now passes remoteAddress into the PreparedStatement constructor, but the constructor still calls super(lifecycleToolbox, queryPlus, null), so the PreparedStatement own SqlExecutionReporter is created without the address. DruidJdbcPreparedStatement.close() later calls sqlStatement.close(), which emits the prepare-phase SQL log and metrics, so prepared-statement prepare logs still contain an empty remoteAddress even though execution logs have it. Pass remoteAddress to super here and tighten the prepared-statement test to assert the relevant log entries all carry a remote address.
There was a problem hiding this comment.
problem
- super(..., null) meant the prepare-phase SqlExecutionReporter was built without the address,
- DruidJdbcPreparedStatement.close() → sqlStatement.close() emitted prepare-phase logs/metrics with an empty remoteAddress.
Fixed by forwarding remoteAddress to super() in PreparedStatement's three-arg constructor
90b6a85
… logs aren't empty
Fixes #19230.
Description
remoteAddress is always
nullfor JDBC/Avatica queries, even though it's a documented dimension forsqlQuery/time,sqlQuery/bytes, and sqlQuery/planningTimeMs. This PR fixes that.Problem
See #19230. In short, remoteAddress was hardcoded as
nullinPreparedStatement.javaandSqlStatementFactory.javafor the Avatica path, whileSqlResource.java(the HTTP path) correctly passeshttpRequest.getRemoteAddr().
Solution
remoteAddrfrom the HTTP request and stores it in aThreadLocal. Cleans up infinallyto prevent memory leaks.ThreadLocalinopenConnection()and injectsremoteAddressinto the connection context map.remoteAddressfrom context and passes it toDirectStatement.remoteAddressas a dimension in metrics and includes it in request logs.No changes to
DruidConnectionor any existing data structures —remoteAddresspiggybacks on the context map that's already there.Testing
Added three test methods to DruidAvaticaHandlerTest:
Release note
sqlQuery/time,sqlQuery/bytes, and sqlQuery/planningTimeMs metrics now includeremoteAddressfor JDBC/Avatica queries, consistent with the HTTP SQL API.Key changed/added classes in this PR
DruidAvaticaJsonHandler— ThreadLocal-based remote address captureDruidMeta— connection context enrichment with remote addressSqlStatementFactory— remote address extraction and propagationPreparedStatement— remote address handling for prepared statementsSqlExecutionReporter— remote address emission in metrics and logsThis PR has: