Skip to content

[fix](catalog) Probe an http or https catalog endpoint that names no port at its default port - #70364

Open
morningman wants to merge 1 commit into
apache:masterfrom
morningman:b2m-t38e-ssrf-default-ports
Open

morningman wants to merge 1 commit into
apache:masterfrom
morningman:b2m-t38e-ssrf-default-ports

Conversation

@morningman

Copy link
Copy Markdown
Contributor

Background

When an operator configures security_checker_class_name, CREATE CATALOG and ALTER CATALOG ask that checker whether the FE may connect to each endpoint the catalog's connector declares through ConnectorSecuritySurface.ofSsrfEndpoints, and refuse an intranet target (#69588). PluginDrivenExternalCatalog.parseEndpoint turns each declared value into the host and port to probe: scheme://host:port, or a bare host:port. A value it cannot turn into a concrete host and port is not probed.

What problem does this PR solve?

Issue Number: None
Related PR: #69588 (added the gate)
PR Type: Bugfix

Problem Summary:

An http or https endpoint written without a port, such as http://10.0.0.1/ns or https://10.0.0.1, passes the gate unprobed, although an HTTP client connects to port 80 or 443 for such a URI. Today only the Hive connector declares endpoints, and its thrift URIs carry their port, so no connector on master reaches this yet; the first http endpoint will be the Lance REST catalog's lance.rest.uri, and an https service URL usually names no port. The gate now probes such an endpoint at its scheme's default port.

Details

  • Frequency: Not observed; found by code review while declaring the Lance REST catalog's endpoint. Triggers when an SSRF checker is configured and a declared endpoint is an http or https URL without a port.
  • Root cause: parseEndpoint returned null for a scheme:// value whose URI.getPort() is -1, and checkEndpointsAgainstSsrf skips a null as nothing to probe.
  • Why existing tests missed it: Unit tests: PluginDrivenExternalCatalogSsrfGateTest covers thrift URIs, bare host:port, IPv6 and URI suffixes, and asserts that a thrift URI without a port is not probed; no case used an http or https URL. Regression: no suite reaches the gate, since it needs the operator's security checker SDK, which CI does not have; the unit tests use a fake.
  • Fix: For an http or https URI that names no port, parseEndpoint takes 80 or 443, the ports RFC 9110 assigns to the scheme; an explicit port still wins. A URI of another scheme without a port is still not probed: its default port, if it has one, belongs to the client, and the engine knows no connector's client. The fix is in the engine because the port follows from the scheme alone.
  • Other paths and branches: CREATE (checkWhenCreating) and both ALTER paths go through parseEndpoint, so all are fixed. branch-4.1 and branch-4.2 have the same rule in HMSExternalCatalog.parseEndpoint, but gate only Hive metastore thrift URIs, so they are not affected. Introduced by [feature](fe) Gate catalog SSRF endpoints and driver_url via a connector security surface #69588.

Testing

Local (macOS), on master f1bc8b3690c plus this change:

  • PluginDrivenExternalCatalogSsrfGateTest: the new parseEndpointUsesTheDefaultPortOfAWebScheme (seven URIs: with and without a path, an upper-case scheme, user-info, query and fragment, IPv6, an explicit port) and alterRejectsAnIntranetWebEndpointWithoutAPort (an http and an https endpoint through the real ALTER validation, with 10.0.0.1 flagged as intranet). On the code before this change 8 of them fail: the six URIs without a port parse to null, and both ALTER cases accept the endpoint ("Expected DdlException to be thrown, but nothing was thrown"). With it, all 44 cases of the class pass, as do the 18 of PluginDrivenExternalCatalogDriverUrlGateTest; fe-core checkstyle passes.

Notes for Reviewers

Release note

None

Check List (For Author)

  • Behavior changed: No
  • Does this need documentation: No
  • Pre-submission status: Ready

AI Disclosure

  • Tool: Claude Code (Claude Opus 5.5)
  • Generated: the fix, the tests, and this description
  • Human review: The author reviewed part of the changes.
  • Prompts: While porting the Lance REST catalog, which declares lance.rest.uri as an endpoint, the author learned that the gate skips a URL without a port, and asked for a separate engine PR that probes http and https at their default ports.

…port at its default port

### Background

When an operator configures `security_checker_class_name`, CREATE CATALOG and ALTER CATALOG ask that checker whether the FE may connect to each endpoint the catalog's connector declares through `ConnectorSecuritySurface.ofSsrfEndpoints`, and refuse an intranet target (apache#69588). `PluginDrivenExternalCatalog.parseEndpoint` turns each declared value into the host and port to probe: `scheme://host:port`, or a bare `host:port`. A value it cannot turn into a concrete host and port is not probed.

### What problem does this PR solve?

Issue Number: None
Related PR: apache#69588 (added the gate)
PR Type: Bugfix

Problem Summary:

An http or https endpoint written without a port, such as `http://10.0.0.1/ns` or `https://10.0.0.1`, passes the gate unprobed, although an HTTP client connects to port 80 or 443 for such a URI. Today only the Hive connector declares endpoints, and its thrift URIs carry their port, so no connector on master reaches this yet; the first http endpoint will be the Lance REST catalog's `lance.rest.uri`, and an https service URL usually names no port. The gate now probes such an endpoint at its scheme's default port.

### Details

- **Frequency:** Not observed; found by code review while declaring the Lance REST catalog's endpoint. Triggers when an SSRF checker is configured and a declared endpoint is an http or https URL without a port.
- **Root cause:** `parseEndpoint` returned null for a `scheme://` value whose `URI.getPort()` is -1, and `checkEndpointsAgainstSsrf` skips a null as nothing to probe.
- **Why existing tests missed it:** Unit tests: `PluginDrivenExternalCatalogSsrfGateTest` covers thrift URIs, bare `host:port`, IPv6 and URI suffixes, and asserts that a thrift URI without a port is not probed; no case used an http or https URL. Regression: no suite reaches the gate, since it needs the operator's security checker SDK, which CI does not have; the unit tests use a fake.
- **Fix:** For an http or https URI that names no port, `parseEndpoint` takes 80 or 443, the ports RFC 9110 assigns to the scheme; an explicit port still wins. A URI of another scheme without a port is still not probed: its default port, if it has one, belongs to the client, and the engine knows no connector's client. The fix is in the engine because the port follows from the scheme alone.
- **Other paths and branches:** CREATE (`checkWhenCreating`) and both ALTER paths go through `parseEndpoint`, so all are fixed. branch-4.1 and branch-4.2 have the same rule in `HMSExternalCatalog.parseEndpoint`, but gate only Hive metastore thrift URIs, so they are not affected. Introduced by apache#69588.

### Testing

Local (macOS), on master `f1bc8b3690c` plus this change:

- `PluginDrivenExternalCatalogSsrfGateTest`: the new `parseEndpointUsesTheDefaultPortOfAWebScheme` (seven URIs: with and without a path, an upper-case scheme, user-info, query and fragment, IPv6, an explicit port) and `alterRejectsAnIntranetWebEndpointWithoutAPort` (an http and an https endpoint through the real ALTER validation, with `10.0.0.1` flagged as intranet). On the code before this change 8 of them fail: the six URIs without a port parse to null, and both ALTER cases accept the endpoint ("Expected DdlException to be thrown, but nothing was thrown"). With it, all 44 cases of the class pass, as do the 18 of `PluginDrivenExternalCatalogDriverUrlGateTest`; fe-core checkstyle passes.

### Notes for Reviewers

- The Javadoc of `ConnectorSecuritySurface.ofSsrfEndpoints` still describes the value as a `host:port`, optionally scheme-prefixed. It is left alone here because apache#70338, still open, edits that file.

### Release note

None

### Check List (For Author)

- Behavior changed: No
- Does this need documentation: No
- Pre-submission status: Ready

### AI Disclosure

- Tool: Claude Code (Claude Opus 5.5)
- Generated: the fix, the tests, and this description
- Human review: The author reviewed part of the changes.
- Prompts: While porting the Lance REST catalog, which declares `lance.rest.uri` as an endpoint, the author learned that the gate skips a URL without a port, and asked for a separate engine PR that probes http and https at their default ports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review of PR #70364 at head 809f183 against base f1bc8b3 found no new blocking or non-blocking findings. The change correctly maps an omitted port on case-insensitive http/https URIs to 80/443, preserves explicit ports and existing unknown-scheme behavior, and feeds the resulting destination through the existing SecurityChecker gate. CREATE and detached non-replay ALTER validation use the merged candidate, while touched-key, placeholder, no-checker, multi-endpoint, and replay semantics remain intact. Concurrency, lifecycle, persistence, FE/BE compatibility, and error propagation introduce no new issue in this patch; no thrift/protobuf or configuration changes are involved. The new tests cover paths, userinfo, query/fragment suffixes, uppercase schemes, IPv6, explicit ports, and ALTER rejection at the web defaults. Validation was static only: the authoritative diff and related call paths were inspected and git diff --check was clean; builds and tests were not executed under the review contract. There is no additional user-provided focus. Both independent review agents returned NO_NEW_VALUABLE_FINDINGS, so this review converged after round 1. Future connector-specific URI semantics are outside this head and do not leave a current coverage gap.

Review converged after 1 round(s).

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28543 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 809f1833429194d534da2680347db3280afebe8f, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17594	5173	5044	5044
q2	2092	338	245	245
q3	10222	1364	810	810
q4	4672	460	333	333
q5	7438	792	522	522
q6	182	187	150	150
q7	730	784	595	595
q8	9243	1335	1011	1011
q9	5474	4187	4186	4186
q10	6841	1291	1018	1018
q11	419	241	221	221
q12	636	398	274	274
q13	18096	3502	2976	2976
q14	266	268	254	254
q15	q16	719	708	666	666
q17	1196	764	598	598
q18	6975	6183	6105	6105
q19	1100	948	591	591
q20	381	336	225	225
q21	5124	2602	2446	2446
q22	346	321	273	273
Total cold run time: 99746 ms
Total hot run time: 28543 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5944	5921	5939	5921
q2	712	621	584	584
q3	5525	5677	5288	5288
q4	2232	2342	1545	1545
q5	5828	5693	5698	5693
q6	234	191	141	141
q7	2293	1995	1783	1783
q8	2951	2518	2450	2450
q9	7519	7471	7456	7456
q10	3630	3555	3112	3112
q11	580	399	375	375
q12	667	649	492	492
q13	3270	3625	3028	3028
q14	283	281	268	268
q15	q16	667	665	605	605
q17	7688	6938	6821	6821
q18	12971	12171	12838	12171
q19	919	780	777	777
q20	2128	2139	1937	1937
q21	5591	4646	4712	4646
q22	496	426	401	401
Total cold run time: 72128 ms
Total hot run time: 65494 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 154062 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 809f1833429194d534da2680347db3280afebe8f, data reload: false

query5	4305	504	393	393
query6	429	206	200	200
query7	4808	429	230	230
query8	315	178	155	155
query9	8793	3175	3216	3175
query10	464	314	255	255
query11	6085	3864	3570	3570
query12	144	94	93	93
query13	1261	437	350	350
query14	6126	3987	3633	3633
query14_1	3374	3417	3436	3417
query15	209	196	180	180
query16	947	442	430	430
query17	862	664	568	568
query18	2426	418	313	313
query19	203	175	151	151
query20	86	90	83	83
query21	210	137	105	105
query22	13014	12935	12758	12758
query23	13445	12969	12542	12542
query23_1	12710	12833	12684	12684
query24	7399	839	476	476
query24_1	487	488	479	479
query25	540	430	371	371
query26	1280	259	143	143
query27	2750	445	275	275
query28	4594	2068	2051	2051
query29	1586	598	471	471
query30	300	218	194	194
query31	1016	923	812	812
query32	140	95	94	94
query33	532	308	241	241
query34	885	866	522	522
query35	753	746	656	656
query36	844	807	719	719
query37	132	97	87	87
query38	1794	1739	1677	1677
query39	802	796	747	747
query39_1	742	724	744	724
query40	224	122	103	103
query41	73	71	68	68
query42	88	81	83	81
query43	349	359	336	336
query44	1317	714	736	714
query45	178	174	167	167
query46	850	970	580	580
query47	3017	2798	2754	2754
query48	304	358	216	216
query49	571	395	287	287
query50	687	276	216	216
query51	10359	10338	10481	10338
query52	78	89	72	72
query53	190	204	153	153
query54	228	197	186	186
query55	71	68	68	68
query56	221	238	215	215
query57	1663	1623	1572	1572
query58	281	272	252	252
query59	2153	2126	1961	1961
query60	277	240	223	223
query61	153	146	147	146
query62	395	334	281	281
query63	189	162	170	162
query64	2768	910	756	756
query65	3354	3269	3275	3269
query66	1753	425	295	295
query67	22596	22501	22482	22482
query68	3041	983	629	629
query69	382	289	259	259
query70	826	776	821	776
query71	304	249	211	211
query72	2699	2540	2331	2331
query73	509	526	295	295
query74	4610	4534	4342	4342
query75	2325	2318	1930	1930
query76	2321	999	595	595
query77	336	387	273	273
query78	9796	9762	9177	9177
query79	999	860	537	537
query80	558	418	352	352
query81	528	308	266	266
query82	598	136	107	107
query83	275	197	188	188
query84	309	128	107	107
query85	805	429	366	366
query86	278	240	230	230
query87	1975	1973	1807	1807
query88	3746	2786	2780	2780
query89	334	280	251	251
query90	2027	170	168	168
query91	163	162	130	130
query92	105	89	79	79
query93	999	972	580	580
query94	559	295	261	261
query95	571	320	310	310
query96	686	480	244	244
query97	2466	2455	2340	2340
query98	161	154	144	144
query99	760	773	653	653
Total cold run time: 237103 ms
Total hot run time: 154062 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 23.96 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 809f1833429194d534da2680347db3280afebe8f, data reload: false

query1	0.00	0.00	0.01
query2	0.09	0.04	0.04
query3	0.26	0.08	0.10
query4	1.61	0.10	0.10
query5	0.22	0.24	0.23
query6	1.17	0.66	0.68
query7	0.04	0.01	0.00
query8	0.06	0.04	0.04
query9	0.40	0.33	0.35
query10	0.59	0.56	0.57
query11	0.19	0.14	0.14
query12	0.19	0.15	0.14
query13	0.49	0.50	0.50
query14	1.05	1.00	1.01
query15	0.64	0.60	0.60
query16	0.35	0.36	0.35
query17	1.12	1.14	1.13
query18	0.21	0.18	0.18
query19	2.05	2.02	1.92
query20	0.02	0.01	0.01
query21	15.41	0.20	0.13
query22	4.77	0.05	0.06
query23	16.29	0.31	0.13
query24	1.06	0.29	0.17
query25	0.09	0.05	0.04
query26	0.77	0.23	0.16
query27	0.03	0.03	0.03
query28	3.79	0.84	0.36
query29	12.48	4.20	3.26
query30	0.28	0.13	0.10
query31	2.77	0.55	0.32
query32	3.22	0.59	0.50
query33	3.15	3.21	3.17
query34	15.31	3.94	3.38
query35	3.31	3.35	3.35
query36	0.56	0.44	0.45
query37	0.09	0.06	0.06
query38	0.06	0.04	0.04
query39	0.03	0.03	0.03
query40	0.18	0.14	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.03	0.04
Total cold run time: 94.58 s
Total hot run time: 23.96 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 87.50% (7/8) 🎉
Increment coverage report
Complete coverage report

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review of both changed files at 809f1833429194d534da2680347db3280afebe8f: no new actionable findings. There are no existing inline findings to carry forward and no additional user focus points.

The changed flow is CREATE / non-replay ALTER -> declared endpoint keys -> parseEndpoint -> SecurityChecker TCP probe. An absent HTTP/HTTPS port now becomes 80/443; an explicit port still wins. All identified source-level risks were checked and dismissed with evidence.

Critical checkpoint conclusions:

  • Goal and proof: the shared parser implements the intended default-port mapping. Seven added parser cases check exact host/port results; two added ALTER cases require both policy rejection and the exact recorded default-port probe. These tests were inspected, not executed.
  • Scope and reuse: the change is focused on one private helper, its call site, documentation and tests; it reuses the existing gate and socket-check primitive.
  • Concurrency and locking: parsing adds only local state. Ordinary ALTER validates a detached snapshot outside the catalog write lock and rechecks it before applying; the alternate non-replay path retains its existing locking. No new lock order or race was found.
  • Lifecycle and initialization: no new resource owner or mutable static state. Existing socket/hook cleanup and test singleton teardown remain intact; catalog initialization/reset and database-name mapping behavior are unchanged.
  • Configuration: no configuration item or dynamic-update behavior is added.
  • Compatibility: no public symbol, SPI, storage or wire format changes. Replay and lazy reconstruction retain their existing behavior; the stricter interactive probe is the intended change.
  • Thrift/protobuf IDs: no generated protocol definitions are changed.
  • Parallel paths: CREATE and both non-replay ALTER paths use the same parser. Disabled-checker and replay paths remain unchanged. Only Hive currently declares SSRF endpoint keys; this patch does not add an HTTP connector.
  • Conditions: explicit-port precedence and case-insensitive HTTP/HTTPS defaults are clear and documented; other schemes and bare endpoints preserve previous behavior.
  • Test coverage: new cases cover path/no path, uppercase scheme, user-info/query/fragment, IPv6, explicit port and HTTP/HTTPS rejection. Existing cases cover thrift/bare endpoints, aliases, HA lists, placeholders, disabled/untouched gates and CREATE. No new web-specific CREATE integration case is present; its shared path was traced statically.
  • Expected results: no result artifacts changed; the new assertions match the intended destinations and cannot pass merely because an unrelated validation step throws.
  • Observability and errors: existing rejection errors identify the endpoint, setup failures propagate, and connectivity-only failures retain their documented handling. No extra metrics are needed for this parser branch.
  • Persistence and transactions: no persisted state or transaction logic changes. Interactive rejection precedes catalog acceptance; replay behavior remains unchanged. Committed-data visibility, partition versions and delete bitmaps are unaffected.
  • Data writes and atomicity: detached validation does not publish a tentative candidate; existing mutation and deferred cleanup are unchanged. No storage write or new crash-recovery obligation is introduced.
  • FE/BE propagation: no new variable or cross-process value is added.
  • Performance: the fallback adds constant-time scheme checks in DDL validation; newly enabled probes use the existing bounded connect timeout. No query hot-path change was found.
  • Further review: explicit/default ports, invalid authorities, non-web schemes, provider lookup and test isolation were rechecked. No additional introduced defect or unresolved source-level candidate remains.

Coverage limits: static inspection only, with no builds, tests, benchmarks, live network checks or third-party SDK execution. The fake-provider tests do not establish end-to-end behavior of a future HTTP connector. Existing point-in-time validation limits, including later DNS changes and undeclared endpoints, are unchanged. These limits are separate from source-review convergence.

Review converged after 2 round(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants