Skip to content

branch-4.1: [feature](fe) Gate HMS catalog SSRF endpoints at CREATE / ALTER CATALOG #69588 - #70092

Merged
yiguolei merged 1 commit into
apache:branch-4.1from
morningman:pick/branch-4.1/69588
Nov 4, 2026
Merged

yiguolei merged 1 commit into
apache:branch-4.1from
morningman:pick/branch-4.1/69588

Conversation

@morningman

@morningman morningman commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #69588 (Same backport for branch-4.2: #70091)

Problem Summary:

Backport of #69588 to branch-4.1. The master change cannot be cherry-picked: it is built on the fe-connector plugin SPI (ConnectorProvider, ConnectorSecuritySurface, PluginDrivenExternalCatalog), and none of those exist on 4.1. This PR ports the security fix itself onto the 4.1 catalog classes, with the same rules and the same error messages.

In short

With the SSRF security SDK configured (security_checker_class_name), CREATE / ALTER CATALOG on 4.1 never checked an HMS catalog's metastore endpoint, so a user with CREATE_PRIV on a catalog (no ADMIN) could make the FE's HMS client connect to an intranet address. HMSExternalCatalog now runs the same CREATE / ALTER gate master got in #69588 (on ALTER, before CatalogMgr takes its write lock), and the SDK binding no longer leaks its net-hook onto pooled FE threads. Without a checker configured (the open-source default), the new gate does nothing.

Background

  • security_checker_class_name names a third-party SDK (Aliyun com.aliyun.securitysdk.SecurityUtil), loaded by reflection through SecurityChecker / UrlSecurityChecker in fe-common. Its net-hook sits at the JDK socket layer: install it on the current thread, connect, and the connect throws SSRFUnsafeConnectionException (a SecurityException) if the resolved address is intranet. The SDK has no "is this host intranet" string API, so it can only judge a real connect. Today only HTTP callers use it (HTTP TVF, S3, ES, small-file download, stage creation).
  • An HMS catalog takes its endpoint from hive.metastore.uris or its alias uri. HMSBaseProperties sets that value, plus every other hive.-prefixed catalog property, into the HiveConf of the FE's HMS client. HiveMetaStoreClient reads the endpoint through MetastoreConf.getVar, which looks at metastore.thrift.uris first, and through Hadoop's Configuration.get, which expands ${...} placeholders (other properties, ${env.X}, JVM system properties).

1. The problem, and what it cost

  • CREATE CATALOG (CREATE_PRIV) and ALTER CATALOG (ALTER privilege on the catalog) accepted any metastore endpoint, so the FE opened a thrift connection to a host inside its own network on behalf of a non-admin user.
  • UrlSecurityChecker bound the SDK's stop method as stopSSRFNetHookChecking(String) but invoked it with no argument, so stop never ran and the net-hook stayed on the pooled FE thread after every bracketed HTTP request.
  • Only the http form of the SDK's start was bound, so nothing could hook a non-http connection such as HMS thrift.

2. How this PR fixes it

  • fe-common, SecurityChecker: adds the no-arg startSSRFChecking() (the SDK's TCP form), isEnabled(), and checkEndpointForSsrf(host, port), which brackets a single 5-second TCP connect with the net-hook. A SecurityException from the connect is the rejection; a refused / timed-out / unknown-host connect is ignored, so a public endpoint that is down right now can still be used. UrlSecurityChecker binds the SDK's no-arg start and no-arg stop; DummySecurityChecker.isEnabled() returns false. The singleton becomes lazily initialized with a test setter.
  • fe-core, HMSExternalCatalog: checkWhenCreating() (CREATE, before test_connection) and the new validatePropertiesBeforeUpdateUnlocked() (ALTER) call the new gate on hive.metastore.uris, uri and metastore.thrift.uris. The rules match master: no-op without a checker; a value containing ${ is rejected before anything else (so an ALTER of only the property a stored placeholder refers to is rejected as well); an ALTER probes only when it touches one of these keys; every entry of a comma-separated HA list is probed; schemed values are parsed with java.net.URI host/port semantics, so a query, fragment or userinfo part cannot hide the real destination.
  • fe-core, CatalogMgr.alterCatalogProps: on 4.1 ALTER CATALOG is validated under CatalogMgr's write lock, and a probe to an unreachable endpoint can wait up to 5 seconds, so the probe must not run there. ALTER now takes a snapshot of the catalog's properties under the read lock, runs ExternalCatalog.validatePropertiesBeforeUpdateUnlocked (new, a no-op by default) on it without holding the lock, and then applies the change under the write lock only if the same catalog still has the snapshot's properties; if another DDL changed or dropped the catalog in between, it starts over from a new snapshot. The rest of the ALTER validation still runs under the lock, unchanged. CREATE already probes before CatalogMgr takes its lock.

Where the backport differs from master:

Master (#69588) This PR Why
Endpoint keys declared by the hive connector through ConnectorSecuritySurface Keys are a constant in HMSExternalCatalog 4.1 has no connector SPI; HMSExternalCatalog is already the HMS-specific class
Gate in PluginDrivenExternalCatalog Gate in HMSExternalCatalog, through checkWhenCreating (CREATE) and the new validatePropertiesBeforeUpdateUnlocked (ALTER) Neither runs on replay: CatalogFactory calls checkWhenCreating only for a new catalog, and replay goes through replayAlterCatalogProps, not alterCatalogProps
driver_url gate moved onto the security surface, driverUrlKeysOf deleted Not ported 4.1 checks driver_url in JdbcExternalCatalog / IcebergExternalCatalog / PaimonExternalCatalog.checkProperties; there is no key table to replace
ALTER rejected while the connector provider is missing Not ported 4.1 has no connector providers
checkEndpointForSsrf throws DdlException Throws Exception with the same messages; the HMS gate rethrows it as DdlException On 4.1 DdlException is in fe-core, which fe-common cannot reference
Connector plugin API 22.0 -> 23.0 No version change No connector plugin API on 4.1
All ALTER validation runs before CatalogMgr takes its write lock (#68585) Only the lock-free checks (the probe) run before the write lock, through validatePropertiesBeforeUpdateUnlocked, on a snapshot that is checked again under the lock; the rest of ALTER validation stays under the lock #68585 is not on 4.1 and also changes HDFS HA validation; this keeps the change to the probe
metastore.thrift.uris gated because the hive connector passes every property to HiveConf Gated as well HMSBaseProperties copies only hive.-prefixed properties today, so the key does not reach the client yet; gating it keeps the gate correct if that copy widens, and keeps one rule on both branches

3. Results

With an SSRF checker configured:

Statement Before After
CREATE CATALOG ... ("type"="hms", "hive.metastore.uris"="thrift://10.0.0.1:9083") Accepted Rejected: Endpoint 10.0.0.1:9083 is rejected by the SSRF policy (intranet address not allowed): ...
Same, with test_connection = false Accepted Rejected (the gate does not depend on test_connection)
ALTER CATALOG c SET PROPERTIES ("hive.metastore.uris"="thrift://10.0.0.1:9083") Accepted Rejected
thrift://user@10.0.0.1:9083?x#y, or an intranet host second in an HA list Accepted Rejected
"hive.metastore.uris"="thrift://${hive.x}:9083" with "hive.x"="127.0.0.1" Accepted; the client connects to 127.0.0.1 Rejected: ... must give its endpoint literally while an SSRF checker is configured ...
Public endpoint that is currently unreachable Accepted Accepted (a failed connect is not an SSRF signal)
ALTER of an unrelated property Accepted Accepted, no probe
ALTER that probes an unreachable public endpoint No probe Probes for up to 5 seconds without holding CatalogMgr's lock
Replay of CREATE / ALTER from the edit log No probe No probe

Without a checker configured, nothing changes. The only change outside the gate is that the SDK's net-hook is now removed after each bracketed HTTP request, as intended.

4. The classes, and how they call each other

  • SecurityChecker (fe-common): the SSRF primitive checkEndpointForSsrf, plus isEnabled() and the TCP start.
  • UrlSecurityChecker / DummySecurityChecker (fe-common): SDK binding / no-op.
  • HMSExternalCatalog (fe-core): the endpoint keys, checkEndpointsAgainstSsrf, parseEndpoint, and the two hooks.
  • ExternalCatalog (fe-core): the new validatePropertiesBeforeUpdateUnlocked hook, a no-op by default.
  • CatalogMgr (fe-core): alterCatalogProps runs that hook on a snapshot before taking its write lock and checks the snapshot again under it.
CREATE CATALOG
  CatalogFactory.createFromCommand
   └ HMSExternalCatalog.checkWhenCreating
      ├ checkEndpointsAgainstSsrf  (new)  -> SecurityChecker.checkEndpointForSsrf
      └ super.checkWhenCreating  (test_connection)

ALTER CATALOG ... SET PROPERTIES
  CatalogMgr.alterCatalogProps
   ├ read lock:  snapshot the catalog's properties
   ├ no lock:    HMSExternalCatalog.validatePropertiesBeforeUpdateUnlocked  (new)
   │              └ checkEndpointsAgainstSsrf  (new)  -> SecurityChecker.checkEndpointForSsrf
   └ write lock: same catalog, same properties? if not, start over
                  └ alterExternalCatalogPropsFenced -> validatePropertiesBeforeUpdate
                     -> checkProperties (unchanged), publish, journal

SecurityChecker.checkEndpointForSsrf(host, port)
 ├ startSSRFChecking()       -> SDK startSSRFNetHookChecking()
 ├ doConnect(host, port, 5s) -> SDK throws SecurityException for an intranet target
 └ stopSSRFChecking()        -> SDK stopSSRFNetHookChecking()

Replay: CatalogFactory.createFromLog and replayAlterCatalogProps(isReplay=true) call neither hook.

SSRF surface note (per repo policy): the probe is a new FE -> user-named-host outbound TCP connect, made only when the operator installed the SSRF SDK and only at CREATE / ALTER CATALOG (CREATE_PRIV or ADMIN for CREATE, ALTER privilege for ALTER). It exists only so the SDK can reject an intranet target. Known limits, same as master: it is a point-in-time check, so it does not guard a later runtime connection (DNS rebinding) or a client that bypasses the JDK socket layer.

Release note

When an SSRF checker is configured, CREATE / ALTER CATALOG checks HMS endpoint destinations (hive.metastore.uris, uri, and metastore.thrift.uris), including URIs containing query, fragment, or userinfo components, and rejects an HMS endpoint value that contains a ${...} placeholder.

Check List (For Author)

  • Test

    • Regression test

    • Unit Test

    • Manual test (add detailed scripts or steps below)

    • No need to test or manual test. Explain why:

      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
    • Unit Test: the new SecurityCheckerTest (6 tests) and HMSExternalCatalogSsrfGateTest (48), plus the existing HMSExternalCatalogParquetTimeZoneTest, HttpTableValuedFunctionTest, CatalogMgrTest, CatalogMgrLanceGuardTest, ExternalCatalogTest, ExternalCatalogRuntimeStateTest, CatalogVarbinaryMigrationTest, HMSPropertiesTest, HiveTableSinkTest, JdbcExternalCatalogTest and PaimonExternalCatalogDriverUrlSecurityTest: 134 tests in 13 classes passed with TZ=Asia/Shanghai mvn test -pl fe-common,fe-core -am; Checkstyle reports 0 violations in fe-common and fe-core.

    • HMSExternalCatalogSsrfGateTest also drives ALTER through a real CatalogMgr. It checks that the probe runs while the thread holds neither the read nor the write lock, that an intranet target leaves the catalog and the journal untouched, and that an ALTER whose catalog is altered or dropped during the probe checks again, or fails with No catalog found, instead of applying a stale check.

    • Mutation check: each of 21 mutants is killed by at least one new test. The mutants drop either hook call, the isEnabled() guard of the gate or of checkEndpointForSsrf, the touched-key guard, a declared key, the comma split or the URI parsing; run the placeholder rule only on touched keys or ahead of the isEnabled() guard; swallow the SDK's rejection or a failed hook install; treat a failed connect as a rejection; skip stopSSRFChecking() in finally; probe under CatalogMgr's write lock (from validatePropertiesBeforeUpdate, or by calling the lock-free hook under the lock); or drop the snapshot comparison or the catalog identity check under the lock.

    • Checked on this branch that HMSBaseProperties hands hive.metastore.uris = thrift://${hive.x}:9083 with hive.x = 127.0.0.1 to the HMS client as thrift://127.0.0.1:9083, and does not hand it a metastore.thrift.uris catalog property.

  • Behavior changed:

    • No.
    • Yes. With an SSRF checker configured, CREATE / ALTER CATALOG probes HMS metastore endpoints and rejects intranet targets and ${...} placeholders; the SDK net-hook is now removed after each bracketed request. Without a checker, nothing changes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

…OG (apache#69588)

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69588

Problem Summary:

Backport of apache#69588, adapted to the 4.x catalog classes. The master change is
built on the fe-connector plugin SPI (ConnectorProvider,
ConnectorSecuritySurface, PluginDrivenExternalCatalog), which does not exist on
this branch, so it cannot be cherry-picked.

When an operator configures the SSRF security SDK (security_checker_class_name),
CREATE / ALTER CATALOG did not check an HMS catalog's metastore endpoint against
the SSRF policy, so a user with CREATE privilege on a catalog could point the
FE's HMS client at an intranet address, and ALTER could repoint an existing one.

HMSExternalCatalog now applies the same gate as master on CREATE
(checkWhenCreating, before test_connection) and on ALTER, never on replay. It
brackets a lightweight TCP connect to every endpoint in hive.metastore.uris, uri
and metastore.thrift.uris with the SDK's net-hook, so an intranet target is
rejected; a plain connect failure is ignored. Schemed endpoints use
java.net.URI host/port semantics, and while a checker is configured an endpoint
value containing a ${...} placeholder is rejected, because the client's
HiveConf expands it only after the check. With no checker configured the gate
is a no-op.

The probe must not hold CatalogMgr's write lock, under which 4.x validates
ALTER CATALOG (master moved that validation out of the lock in apache#68585).
CatalogMgr.alterCatalogProps now snapshots the catalog's properties under the
read lock, runs the new ExternalCatalog.validatePropertiesBeforeUpdateUnlocked
hook (a no-op by default; HMSExternalCatalog runs the SSRF gate there) without
holding the lock, and applies the ALTER under the write lock only if the same
catalog still has the snapshot's properties; otherwise it checks again. All
other ALTER validation still runs under the lock, unchanged. CREATE already
probes before CatalogMgr takes its lock.

SecurityChecker gains the no-arg (TCP) start, isEnabled() and
checkEndpointForSsrf(). UrlSecurityChecker now binds the SDK's no-arg stop, so
the net-hook is no longer leaked onto the pooled FE thread after a bracketed
request. checkEndpointForSsrf throws Exception with master's messages because
DdlException lives in fe-core on this branch; the HMS gate rethrows it as
DdlException.

Not ported, because 4.x has no connector plugins: the driver_url gate migration
(4.x checks driver_url in each catalog's checkProperties), the rejection of
ALTER while a connector provider is unavailable, and the connector API version
bump.

### Release note

When an SSRF checker is configured, CREATE / ALTER CATALOG checks HMS endpoint
destinations (hive.metastore.uris, uri, and metastore.thrift.uris), including
URIs containing query, fragment, or userinfo components, and rejects an HMS
endpoint value that contains a ${...} placeholder.

### Check List (For Author)

- Test: Unit Test - SecurityCheckerTest, HMSExternalCatalogSsrfGateTest,
  HMSExternalCatalogParquetTimeZoneTest, HttpTableValuedFunctionTest,
  CatalogMgrTest, CatalogMgrLanceGuardTest and CatalogVarbinaryMigrationTest,
  with Checkstyle on fe-common and fe-core
- Behavior changed: Yes - with an SSRF checker configured, CREATE / ALTER
  CATALOG probes HMS endpoints and rejects intranet targets and placeholders;
  the SDK net-hook is now removed after each bracketed request; ALTER CATALOG
  checks again if the catalog changes while its lock-free checks run
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morningman
morningman requested a review from yiguolei as a code owner November 3, 2026 09:02
@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

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 78.31% (65/83) 🎉
Increment coverage report
Complete coverage report

yiguolei pushed a commit that referenced this pull request Nov 4, 2026
… ALTER CATALOG #69588 (#70091)

### What problem does this PR solve?

Issue Number: None

Related PR: #69588 (Same backport for branch-4.1: #70092)

Problem Summary:

Backport of #69588 to branch-4.2. The master change cannot be
cherry-picked: it is built on the fe-connector plugin SPI
(`ConnectorProvider`, `ConnectorSecuritySurface`,
`PluginDrivenExternalCatalog`), and none of those exist on 4.2. This PR
ports the security fix itself onto the 4.2 catalog classes, with the
same rules and the same error messages.

**In short**

With the SSRF security SDK configured (`security_checker_class_name`),
CREATE / ALTER CATALOG on 4.2 never checked an HMS catalog's metastore
endpoint, so a user with CREATE_PRIV on a catalog (no ADMIN) could make
the FE's HMS client connect to an intranet address. `HMSExternalCatalog`
now runs the same CREATE / ALTER gate master got in #69588 (on ALTER,
before `CatalogMgr` takes its write lock), and the SDK binding no longer
leaks its net-hook onto pooled FE threads. Without a checker configured
(the open-source default), the new gate does nothing.

**Background**

- `security_checker_class_name` names a third-party SDK (Aliyun
`com.aliyun.securitysdk.SecurityUtil`), loaded by reflection through
`SecurityChecker` / `UrlSecurityChecker` in fe-common. Its net-hook sits
at the JDK socket layer: install it on the current thread, connect, and
the connect throws `SSRFUnsafeConnectionException` (a
`SecurityException`) if the resolved address is intranet. The SDK has no
"is this host intranet" string API, so it can only judge a real connect.
Today only HTTP callers use it (HTTP TVF, S3, ES, small-file download,
stage creation).
- An HMS catalog takes its endpoint from `hive.metastore.uris` or its
alias `uri`. `HMSBaseProperties` sets that value, plus every other
`hive.`-prefixed catalog property, into the `HiveConf` of the FE's HMS
client. `HiveMetaStoreClient` reads the endpoint through
`MetastoreConf.getVar`, which looks at `metastore.thrift.uris` first,
and through Hadoop's `Configuration.get`, which expands `${...}`
placeholders (other properties, `${env.X}`, JVM system properties).

**1. The problem, and what it cost**

- CREATE CATALOG (CREATE_PRIV) and ALTER CATALOG (ALTER privilege on the
catalog) accepted any metastore endpoint, so the FE opened a thrift
connection to a host inside its own network on behalf of a non-admin
user.
- `UrlSecurityChecker` bound the SDK's stop method as
`stopSSRFNetHookChecking(String)` but invoked it with no argument, so
stop never ran and the net-hook stayed on the pooled FE thread after
every bracketed HTTP request.
- Only the http form of the SDK's start was bound, so nothing could hook
a non-http connection such as HMS thrift.

**2. How this PR fixes it**

- fe-common, `SecurityChecker`: adds the no-arg `startSSRFChecking()`
(the SDK's TCP form), `isEnabled()`, and `checkEndpointForSsrf(host,
port)`, which brackets a single 5-second TCP connect with the net-hook.
A `SecurityException` from the connect is the rejection; a refused /
timed-out / unknown-host connect is ignored, so a public endpoint that
is down right now can still be used. `UrlSecurityChecker` binds the
SDK's no-arg start and no-arg stop; `DummySecurityChecker.isEnabled()`
returns false. The singleton becomes lazily initialized with a test
setter.
- fe-core, `HMSExternalCatalog`: `checkWhenCreating()` (CREATE, before
`test_connection`) and the new
`validatePropertiesBeforeUpdateUnlocked()` (ALTER) call the new gate on
`hive.metastore.uris`, `uri` and `metastore.thrift.uris`. The rules
match master: no-op without a checker; a value containing `${` is
rejected before anything else (so an ALTER of only the property a stored
placeholder refers to is rejected as well); an ALTER probes only when it
touches one of these keys; every entry of a comma-separated HA list is
probed; schemed values are parsed with `java.net.URI` host/port
semantics, so a query, fragment or userinfo part cannot hide the real
destination.
- fe-core, `CatalogMgr.alterCatalogProps`: on 4.2 ALTER CATALOG is
validated under `CatalogMgr`'s write lock, and a probe to an unreachable
endpoint can wait up to 5 seconds, so the probe must not run there.
ALTER now takes a snapshot of the catalog's properties under the read
lock, runs `ExternalCatalog.validatePropertiesBeforeUpdateUnlocked`
(new, a no-op by default) on it without holding the lock, and then
applies the change under the write lock only if the same catalog still
has the snapshot's properties; if another DDL changed or dropped the
catalog in between, it starts over from a new snapshot. The rest of the
ALTER validation still runs under the lock, unchanged. CREATE already
probes before `CatalogMgr` takes its lock.

Where the backport differs from master:

| Master (#69588) | This PR | Why |
| --- | --- | --- |
| Endpoint keys declared by the hive connector through
`ConnectorSecuritySurface` | Keys are a constant in `HMSExternalCatalog`
| 4.2 has no connector SPI; `HMSExternalCatalog` is already the
HMS-specific class |
| Gate in `PluginDrivenExternalCatalog` | Gate in `HMSExternalCatalog`,
through `checkWhenCreating` (CREATE) and the new
`validatePropertiesBeforeUpdateUnlocked` (ALTER) | Neither runs on
replay: `CatalogFactory` calls `checkWhenCreating` only for a new
catalog, and replay goes through `replayAlterCatalogProps`, not
`alterCatalogProps` |
| `driver_url` gate moved onto the security surface, `driverUrlKeysOf`
deleted | Not ported | 4.2 checks `driver_url` in `JdbcExternalCatalog`
/ `IcebergExternalCatalog` / `PaimonExternalCatalog.checkProperties`;
there is no key table to replace |
| ALTER rejected while the connector provider is missing | Not ported |
4.2 has no connector providers |
| `checkEndpointForSsrf` throws `DdlException` | Throws `Exception` with
the same messages; the HMS gate rethrows it as `DdlException` | On 4.2
`DdlException` is in fe-core, which fe-common cannot reference |
| Connector plugin API 22.0 -> 23.0 | No version change | No connector
plugin API on 4.2 |
| All ALTER validation runs before `CatalogMgr` takes its write lock
(#68585) | Only the lock-free checks (the probe) run before the write
lock, through `validatePropertiesBeforeUpdateUnlocked`, on a snapshot
that is checked again under the lock; the rest of ALTER validation stays
under the lock | #68585 is not on 4.2 and also changes HDFS HA
validation; this keeps the change to the probe |
| `metastore.thrift.uris` gated because the hive connector passes every
property to `HiveConf` | Gated as well | `HMSBaseProperties` copies only
`hive.`-prefixed properties today, so the key does not reach the client
yet; gating it keeps the gate correct if that copy widens, and keeps one
rule on both branches |

**3. Results**

With an SSRF checker configured:

| Statement | Before | After |
| --- | --- | --- |
| `CREATE CATALOG ... ("type"="hms",
"hive.metastore.uris"="thrift://10.0.0.1:9083")` | Accepted | Rejected:
`Endpoint 10.0.0.1:9083 is rejected by the SSRF policy (intranet address
not allowed): ...` |
| Same, with `test_connection` = false | Accepted | Rejected (the gate
does not depend on `test_connection`) |
| `ALTER CATALOG c SET PROPERTIES
("hive.metastore.uris"="thrift://10.0.0.1:9083")` | Accepted | Rejected
|
| `thrift://user@10.0.0.1:9083?x#y`, or an intranet host second in an HA
list | Accepted | Rejected |
| `"hive.metastore.uris"="thrift://${hive.x}:9083"` with
`"hive.x"="127.0.0.1"` | Accepted; the client connects to 127.0.0.1 |
Rejected: `... must give its endpoint literally while an SSRF checker is
configured ...` |
| Public endpoint that is currently unreachable | Accepted | Accepted (a
failed connect is not an SSRF signal) |
| ALTER of an unrelated property | Accepted | Accepted, no probe |
| ALTER that probes an unreachable public endpoint | No probe | Probes
for up to 5 seconds without holding `CatalogMgr`'s lock |
| Replay of CREATE / ALTER from the edit log | No probe | No probe |

Without a checker configured, nothing changes. The only change outside
the gate is that the SDK's net-hook is now removed after each bracketed
HTTP request, as intended.

**4. The classes, and how they call each other**

- `SecurityChecker` (fe-common): the SSRF primitive
`checkEndpointForSsrf`, plus `isEnabled()` and the TCP start.
- `UrlSecurityChecker` / `DummySecurityChecker` (fe-common): SDK binding
/ no-op.
- `HMSExternalCatalog` (fe-core): the endpoint keys,
`checkEndpointsAgainstSsrf`, `parseEndpoint`, and the two hooks.
- `ExternalCatalog` (fe-core): the new
`validatePropertiesBeforeUpdateUnlocked` hook, a no-op by default.
- `CatalogMgr` (fe-core): `alterCatalogProps` runs that hook on a
snapshot before taking its write lock and checks the snapshot again
under it.

```
CREATE CATALOG
  CatalogFactory.createFromCommand
   └ HMSExternalCatalog.checkWhenCreating
      ├ checkEndpointsAgainstSsrf  (new)  -> SecurityChecker.checkEndpointForSsrf
      └ super.checkWhenCreating  (test_connection)

ALTER CATALOG ... SET PROPERTIES
  CatalogMgr.alterCatalogProps
   ├ read lock:  snapshot the catalog's properties
   ├ no lock:    HMSExternalCatalog.validatePropertiesBeforeUpdateUnlocked  (new)
   │              └ checkEndpointsAgainstSsrf  (new)  -> SecurityChecker.checkEndpointForSsrf
   └ write lock: same catalog, same properties? if not, start over
                  └ alterExternalCatalogPropsFenced -> validatePropertiesBeforeUpdate
                     -> checkProperties (unchanged), publish, journal

SecurityChecker.checkEndpointForSsrf(host, port)
 ├ startSSRFChecking()       -> SDK startSSRFNetHookChecking()
 ├ doConnect(host, port, 5s) -> SDK throws SecurityException for an intranet target
 └ stopSSRFChecking()        -> SDK stopSSRFNetHookChecking()

Replay: CatalogFactory.createFromLog and replayAlterCatalogProps(isReplay=true) call neither hook.
```

SSRF surface note (per repo policy): the probe is a new FE ->
user-named-host outbound TCP connect, made only when the operator
installed the SSRF SDK and only at CREATE / ALTER CATALOG (CREATE_PRIV
or ADMIN for CREATE, ALTER privilege for ALTER). It exists only so the
SDK can reject an intranet target. Known limits, same as master: it is a
point-in-time check, so it does not guard a later runtime connection
(DNS rebinding) or a client that bypasses the JDK socket layer.

### Release note

When an SSRF checker is configured, CREATE / ALTER CATALOG checks HMS
endpoint destinations (`hive.metastore.uris`, `uri`, and
`metastore.thrift.uris`), including URIs containing query, fragment, or
userinfo components, and rejects an HMS endpoint value that contains a
`${...}` placeholder.

### Check List (For Author)

- Test <!-- At least one of them must be included. -->
    - [ ] Regression test
    - [x] Unit Test
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason <!-- Add your reason?  -->

- Unit Test: the new `SecurityCheckerTest` (6 tests) and
`HMSExternalCatalogSsrfGateTest` (48), plus the existing
`HMSExternalCatalogParquetTimeZoneTest`, `HttpTableValuedFunctionTest`,
`CatalogMgrTest`, `CatalogMgrLanceGuardTest`, `ExternalCatalogTest`,
`ExternalCatalogRuntimeStateTest`, `CatalogVarbinaryMigrationTest`,
`HMSPropertiesTest`, `HiveTableSinkTest`, `JdbcExternalCatalogTest` and
`PaimonExternalCatalogDriverUrlSecurityTest`: 134 tests in 13 classes
passed with `TZ=Asia/Shanghai mvn test -pl fe-common,fe-core -am`;
Checkstyle reports 0 violations in fe-common and fe-core.
- `HMSExternalCatalogSsrfGateTest` also drives ALTER through a real
`CatalogMgr`. It checks that the probe runs while the thread holds
neither the read nor the write lock, that an intranet target leaves the
catalog and the journal untouched, and that an ALTER whose catalog is
altered or dropped during the probe checks again, or fails with `No
catalog found`, instead of applying a stale check.
- Mutation check: each of 21 mutants is killed by at least one new test.
The mutants drop either hook call, the `isEnabled()` guard of the gate
or of `checkEndpointForSsrf`, the touched-key guard, a declared key, the
comma split or the URI parsing; run the placeholder rule only on touched
keys or ahead of the `isEnabled()` guard; swallow the SDK's rejection or
a failed hook install; treat a failed connect as a rejection; skip
`stopSSRFChecking()` in `finally`; probe under `CatalogMgr`'s write lock
(from `validatePropertiesBeforeUpdate`, or by calling the lock-free hook
under the lock); or drop the snapshot comparison or the catalog identity
check under the lock.
- Checked on this branch that `HMSBaseProperties` hands
`hive.metastore.uris` = `thrift://${hive.x}:9083` with `hive.x` =
`127.0.0.1` to the HMS client as `thrift://127.0.0.1:9083`, and does not
hand it a `metastore.thrift.uris` catalog property.

- Behavior changed:
    - [ ] No.
- [x] Yes. With an SSRF checker configured, CREATE / ALTER CATALOG
probes HMS metastore endpoints and rejects intranet targets and `${...}`
placeholders; the SDK net-hook is now removed after each bracketed
request. Without a checker, nothing changes.

- Does this need documentation?
    - [x] No.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@yiguolei
yiguolei merged commit fe4248d into apache:branch-4.1 Nov 4, 2026
35 of 37 checks passed
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.

3 participants