morningman commented on code in PR #69588:
URL: https://github.com/apache/doris/pull/69588#discussion_r4376193126


##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorProvider.java:
##########
@@ -80,4 +81,11 @@ public void validatePropertiesForUpdate(
         candidate.putAll(updatedProperties);
         
HiveCatalogProperties.of(candidate).checkCreateTimeOnlyRules(updatedProperties);
     }
+
+    @Override
+    public ConnectorSecuritySurface securitySurface() {
+        // hive.metastore.uris (documented alias: uri) is the user-supplied 
thrift endpoint the FE connects
+        // to; the engine's SSRF gate rejects an intranet target there at 
CREATE / ALTER CATALOG.
+        return 
ConnectorSecuritySurface.ofSsrfEndpoints(HiveCatalogProperties.HIVE_METASTORE_URIS,
 "uri");

Review Comment:
   Fixed in a36c8b1f3f2. `HiveConnectorProvider.securitySurface()` now declares 
`metastore.thrift.uris` alongside `hive.metastore.uris` and `uri` (constant 
`HiveCatalogProperties.METASTORE_THRIFT_URIS`). An ALTER that touches only the 
modern key now triggers the probe, and once any endpoint key is touched the 
gate probes the values of all three keys, which covers whichever one 
`MetastoreConf.getVar` returns. A CREATE that carries both keys with 
`test_connection=false` therefore probes both values.
   
   The declaration is pinned by `HiveConnectorProviderSecuritySurfaceTest`, 
which fails against the previous provider; 
`PluginDrivenExternalCatalogSsrfGateTest` adds 
`alterTouchingOnlyModernMetastoreKeyIsProbed` and 
`createProbesModernMetastoreKeyBesideLegacyKey`. I kept the raw passthrough 
rather than having the connector override `metastore.thrift.uris`, because a 
`hive.conf.resources` hive-site.xml may set it legitimately. The 
`ofSsrfEndpoints` javadoc now requires a connector to declare every key its 
client may read the destination from.
   



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalog.java:
##########
@@ -345,41 +352,102 @@ private void 
checkDriverUrlsAgainstOperatorGate(Map<String, String> candidate,
         }
     }
 
-    /** One row of the driver-jar key table: which properties name the jar, 
live under which flavor key. */
-    private static final class DriverUrlKeys {
-        /** The properties whose value the connector hands to a class loader, 
documented aliases included. */
-        final List<String> urlKeys;
-        /** The key whose candidate value must be "jdbc" for the urlKeys to be 
live; null = always live. */
-        final String flavorKey;
+    /**
+     * The connector's declared security-sensitive property surface, looked up 
by catalog type WITHOUT
+     * initializing a connector (so neither gate forces a catalog to come to 
life). A missing provider
+     * cannot validate newly submitted properties: accepting them would bypass 
the gates when its plugin
+     * is restored, since replay and lazy reconstruction deliberately skip 
statement-time validation.
+     * Only an available provider may declare {@link 
ConnectorSecuritySurface#EMPTY}.
+     */
+    private ConnectorSecuritySurface securitySurface(Map<String, String> 
properties) throws DdlException {
+        return ConnectorFactory.findProvider(getType(), properties)
+                .map(ConnectorProvider::securitySurface)
+                .orElseThrow(() -> new DdlException("No ConnectorProvider 
found for catalog '" + name
+                        + "', type '" + getType() + "'. Install the connector 
plugin "
+                        + "before altering catalog properties."));
+    }
 
-        DriverUrlKeys(String flavorKey, String... urlKeys) {
-            this.flavorKey = flavorKey;
-            this.urlKeys = Arrays.asList(urlKeys);
+    /**
+     * Applies the operator's SSRF policy (the third-party {@code 
SecurityChecker} SDK) to every
+     * user-supplied network endpoint these properties would make the FE 
connect to, rejecting an intranet
+     * target. It mirrors {@link #checkDriverUrlsAgainstOperatorGate}: the 
connector declares the endpoint
+     * keys through {@link ConnectorSecuritySurface#ssrfEndpointKeys()}, it 
runs on CREATE
+     * (through {@code checkWhenCreating}) and on the {@code !isReplay} ALTER 
path (here and in
+     * {@code validatePropertiesBeforeUpdate}) but never on replay, and it is 
a no-op unless the operator
+     * installed an SSRF checker.
+     *
+     * <p>The judgment itself is delegated to the SDK — see {@link 
SecurityChecker#checkEndpointForSsrf}.
+     * This is a point-in-time check of the endpoint supplied at CREATE/ALTER; 
it does not guard a later
+     * runtime connection (DNS rebinding, lazy metadata access) — that is a 
deliberate scope limit.
+     *
+     * <p>A connector whose property names a host to connect to declares it in 
its
+     * {@code securitySurface()} and nowhere else.
+     */
+    private void checkEndpointsAgainstSsrf(Map<String, String> candidate, 
Map<String, String> updatedProperties)
+            throws DdlException {
+        SecurityChecker checker = SecurityChecker.getInstance();
+        if (!checker.isEnabled()) {
+            return;
+        }
+        List<String> endpointKeys = 
securitySurface(candidate).ssrfEndpointKeys();
+        if (endpointKeys.isEmpty()) {
+            return;
+        }
+        // Only an ALTER that touches an endpoint key can repoint the catalog 
at a new host; a stored value
+        // was gated at its own CREATE/ALTER time. On CREATE the full property 
map is passed as both
+        // arguments, so every supplied endpoint counts as touched and is 
checked.
+        boolean touched = 
endpointKeys.stream().anyMatch(updatedProperties::containsKey);
+        if (!touched) {
+            return;
+        }
+        for (String key : endpointKeys) {
+            String rawValue = candidate.get(key);
+            if (rawValue == null || rawValue.trim().isEmpty()) {
+                continue;
+            }
+            // A value may list several comma-separated endpoints (e.g. HA 
hive.metastore.uris).
+            for (String endpoint : rawValue.split(",")) {
+                HostAndPort hostAndPort = parseEndpoint(endpoint.trim());

Review Comment:
   Fixed in a36c8b1f3f2 with the second option. While a checker is configured, 
`checkEndpointsAgainstSsrf` now rejects any declared endpoint value that 
contains `${`. The check runs before the touched-key shortcut, so it covers 
CREATE, an ALTER that writes a placeholder, and an ALTER that changes only the 
referenced property (`hms.target` in your example); a catalog stored with a 
placeholder has to be given a literal endpoint before any other property 
change. It also covers the bare form `${x}:9083`, which `HostAndPort` would 
have parsed as a host literally named `${x}` whose DNS failure the probe 
ignores, and `${env.X}` / system-property references.
   
   I did not resolve through the connector's configuration layer: that needs a 
new SPI hook that builds a `HiveConf` at DDL time (plugin loader, 
`hive.conf.resources`, environment, system properties) and diffs the old and 
new effective endpoints, while no real HMS URI needs a placeholder. Tests: 
`createRejectsPlaceholderEndpointWithConnectionTestDisabled` (4 forms), 
`alterRejectsPlaceholderEndpointWithoutProbing`, 
`alterOfThePropertyAStoredPlaceholderRefersToIsRejected` and 
`alterAcceptsPlaceholderWhenNoCheckerInstalled`; against the previous 
implementation the suite fails 7 of 35 (every placeholder case).
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to