github-actions[bot] commented on code in PR #68540:
URL: https://github.com/apache/doris/pull/68540#discussion_r4217149855


##########
fe/fe-filesystem/fe-filesystem-gcs/src/main/java/org/apache/doris/filesystem/gcs/GcsFileSystemProperties.java:
##########
@@ -134,10 +153,63 @@ public Map<String, String> toMap() {
         return Collections.unmodifiableMap(kv);
     }
 
+    private static Map<String, String> withGcpProvider(Map<String, String> 
properties) {
+        Map<String, String> selected = new HashMap<>(properties);
+        selected.put("provider", "GCP");
+        return selected;
+    }
+
+    public GcsAuth getAuth() {
+        return auth;
+    }
+
+    @Override
+    public Map<String, String> matchedProperties() {
+        Map<String, String> matched = new HashMap<>(super.matchedProperties());
+        if (auth.getMode() != GcsAuth.Mode.HMAC) {
+            matched.put(GcpCredential.CREDENTIAL_PROVIDER_TYPE, 
auth.isAnonymous() ? "ANONYMOUS"
+                    : 
auth.getNativeCredential().orElseThrow().getCredentialProviderType().name());
+            matched.put(GcpCredential.IMPERSONATION_SERVICE_ACCOUNT,
+                    
auth.getNativeCredential().map(GcpCredential::getImpersonationServiceAccount).orElse(""));
+        }
+        return Collections.unmodifiableMap(matched);
+    }
+
+    @Override
+    protected void customizeS3CompatibleKv(Map<String, String> kv) {
+        if (auth.getNativeCredential().isPresent()) {
+            GcpCredential credential = auth.getNativeCredential().get();
+            kv.remove("AWS_CREDENTIALS_PROVIDER_TYPE");
+            kv.put(GcpCredential.CREDENTIAL_PROVIDER_TYPE, 
credential.getCredentialProviderType().name());
+            putIfNotBlank(kv, GcpCredential.IMPERSONATION_SERVICE_ACCOUNT,
+                    credential.getImpersonationServiceAccount());
+        }
+    }
+
+    @Override
+    public Map<String, String> toHadoopConfigurationMap() {
+        if (auth.getNativeCredential().isEmpty()) {
+            return super.toHadoopConfigurationMap();
+        }
+        GcpCredential credential = auth.getNativeCredential().get();
+        Map<String, String> cfg = new HashMap<>();
+        cfg.put("fs.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem");
+        String storageRoot = getEndpoint();
+        if (!storageRoot.contains("://")) {
+            storageRoot = "https://"; + storageRoot;
+        }
+        cfg.put("fs.gs.storage.root.url", storageRoot.endsWith("/") ? 
storageRoot : storageRoot + "/");

Review Comment:
   [P1] Validate the effective Hadoop GCS endpoint before attaching OAuth 
credentials. This sets `fs.gs.storage.root.url` from the checked `gs.endpoint`, 
but Iceberg's `buildHadoopConfiguration()` applies raw catalog `fs.*` 
properties afterward; `fs.gs.storage.root.url=https://attacker.example/` 
replaces it for a native `gs://` Hadoop warehouse. The pinned GCS connector 
builds its JSON client with that root URL and a credential-bearing request 
initializer, so catalog filesystem calls send the FE ADC/Compute Engine token 
to the caller-selected host. Hudi and Paimon have the same raw override. Reject 
or revalidate endpoint-related `fs.gs.*` overrides when native auth is active. 
[Connector 
source](https://github.com/GoogleCloudDataproc/hadoop-connectors/blob/v3.1.18/gcsio/src/main/java/com/google/cloud/hadoop/gcsio/GoogleCloudStorageImpl.java).



##########
fe/fe-filesystem/fe-filesystem-gcs/src/main/java/org/apache/doris/filesystem/gcs/GcsFileSystemProperties.java:
##########
@@ -134,10 +153,63 @@ public Map<String, String> toMap() {
         return Collections.unmodifiableMap(kv);
     }
 
+    private static Map<String, String> withGcpProvider(Map<String, String> 
properties) {
+        Map<String, String> selected = new HashMap<>(properties);
+        selected.put("provider", "GCP");
+        return selected;
+    }
+
+    public GcsAuth getAuth() {
+        return auth;
+    }
+
+    @Override
+    public Map<String, String> matchedProperties() {
+        Map<String, String> matched = new HashMap<>(super.matchedProperties());
+        if (auth.getMode() != GcsAuth.Mode.HMAC) {
+            matched.put(GcpCredential.CREDENTIAL_PROVIDER_TYPE, 
auth.isAnonymous() ? "ANONYMOUS"
+                    : 
auth.getNativeCredential().orElseThrow().getCredentialProviderType().name());
+            matched.put(GcpCredential.IMPERSONATION_SERVICE_ACCOUNT,
+                    
auth.getNativeCredential().map(GcpCredential::getImpersonationServiceAccount).orElse(""));
+        }
+        return Collections.unmodifiableMap(matched);
+    }
+
+    @Override
+    protected void customizeS3CompatibleKv(Map<String, String> kv) {
+        if (auth.getNativeCredential().isPresent()) {
+            GcpCredential credential = auth.getNativeCredential().get();
+            kv.remove("AWS_CREDENTIALS_PROVIDER_TYPE");
+            kv.put(GcpCredential.CREDENTIAL_PROVIDER_TYPE, 
credential.getCredentialProviderType().name());
+            putIfNotBlank(kv, GcpCredential.IMPERSONATION_SERVICE_ACCOUNT,
+                    credential.getImpersonationServiceAccount());
+        }
+    }
+
+    @Override
+    public Map<String, String> toHadoopConfigurationMap() {
+        if (auth.getNativeCredential().isEmpty()) {
+            return super.toHadoopConfigurationMap();
+        }
+        GcpCredential credential = auth.getNativeCredential().get();
+        Map<String, String> cfg = new HashMap<>();
+        cfg.put("fs.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem");

Review Comment:
   [P2] Preserve Hadoop access to GCS-backed Hudi `s3://` locations. Native GCP 
auth returns only `fs.gs.*` here, but this class still advertises `s3` and 
`s3a`, and Hudi passes the HMS table location unchanged to 
`HoodieTableMetaClient` with this Hadoop configuration. A GCS Hudi table stored 
as `s3://bucket/table` therefore selects S3A without the old GCS 
endpoint/credentials (or the default S3 implementation), so its `.hoodie` 
metadata cannot be read under native auth. Rewrite the Hadoop-facing Hudi path 
to `gs://` or provide equivalent OAuth-capable mappings for these aliases.



##########
fe/fe-core/src/main/java/org/apache/doris/catalog/S3Resource.java:
##########
@@ -218,72 +237,109 @@ protected static void pingS3(String bucketName, String 
rootPath, Map<String, Str
         LOG.info("success to ping s3");
     }
 
+    private static void normalizeProperties(Map<String, String> properties, 
String provider) {
+        // Validate the raw aliases before a preferred value can hide 
conflicting credentials.
+        GcsAuthResolver.resolve(properties);
+        if (provider == null) {
+            return;
+        }
+        switch (provider.toUpperCase(Locale.ROOT)) {
+            case "GCP":
+                // Normalize before validation, policy checks and persistence, 
so FE connector
+                // binding and Resource/Vault protocol builders consume the 
same values.
+                GCS_PROPERTY_ALIASES.forEach((alias, key) -> {
+                    String value = properties.remove(alias);
+                    // Match connector binding: nonblank gs.* values take 
precedence.
+                    if (StringUtils.isNotBlank(value)) {
+                        properties.put(key, value);
+                    }
+                });
+                break;
+            default:
+                break;
+        }
+    }
+
     @Override
-    public void modifyProperties(Map<String, String> properties) throws 
DdlException {
+    public synchronized void modifyProperties(Map<String, String> 
newProperties) throws DdlException {
+        // Serialize the snapshot, validation and publication. A lock only 
around publication
+        // would allow a concurrent ALTER to replace a successful update with 
an older snapshot.
+        Map<String, String> properties = new HashMap<>(newProperties);
+        String provider = 
StringUtils.defaultIfEmpty(properties.get("provider"),
+                this.properties.get("provider"));
+        // Preserve AWS_* ALTER compatibility before merging with stored 
canonical properties.
+        S3ResourceCompat.convertToStdProperties(properties);
+        // Resolve aliases separately so this ALTER wins over persisted values 
regardless
+        // of their spelling. Within each map, nonblank gs.* values still take 
precedence.
+        normalizeProperties(properties, provider);
+        Map<String, String> effectiveProperties = new 
HashMap<>(this.properties);
+        normalizeProperties(effectiveProperties, provider);
+        S3ResourceCompat.convertToStdProperties(effectiveProperties);
+        for (Map.Entry<String, String> update : properties.entrySet()) {
+            // Match persistence: empty updates are ignored, except when 
clearing a session token.
+            replaceIfEffectiveValue(effectiveProperties, update.getKey(), 
update.getValue());
+            if (S3ResourceCompat.SESSION_TOKEN.equals(update.getKey())
+                    || S3ResourceCompat.Env.TOKEN.equals(update.getKey())) {
+                effectiveProperties.put(update.getKey(), update.getValue());
+            }
+        }
         if (references.containsValue(ReferenceType.POLICY)) {
             // can't change, because remote fs use it info to find data.
             List<String> cantChangeProperties = 
Arrays.asList(S3ResourceCompat.ENDPOINT, S3ResourceCompat.REGION,
                     S3ResourceCompat.ROOT_PATH, S3ResourceCompat.BUCKET, 
S3ResourceCompat.Env.ENDPOINT,
                     S3ResourceCompat.Env.REGION,
                     S3ResourceCompat.Env.ROOT_PATH, 
S3ResourceCompat.Env.BUCKET);
-            Optional<String> any = 
cantChangeProperties.stream().filter(properties::containsKey).findAny();
+            Optional<String> any = cantChangeProperties.stream()
+                    .filter(key -> properties.containsKey(key)
+                            || !Objects.equals(this.properties.get(key), 
effectiveProperties.get(key)))
+                    .findAny();
             if (any.isPresent()) {
                 throw new DdlException("current not support modify property : 
" + any.get());
             }
         }
-        // compatible with old version, Need convert if modified properties 
map uses old properties.
-        S3ResourceCompat.convertToStdProperties(properties);
-        if (!Strings.isNullOrEmpty(properties.get(S3ResourceCompat.ENDPOINT))) 
{
-            properties.put(S3ResourceCompat.Env.ENDPOINT, 
properties.get(S3ResourceCompat.ENDPOINT));
+        if 
(!Strings.isNullOrEmpty(effectiveProperties.get(S3ResourceCompat.ENDPOINT))) {
+            effectiveProperties.put(S3ResourceCompat.Env.ENDPOINT, 
effectiveProperties.get(S3ResourceCompat.ENDPOINT));
         }
-        boolean needCheck = isNeedCheck(properties);
+        for (Map.Entry<String, String> kv : properties.entrySet()) {
+            if (kv.getKey().equalsIgnoreCase(S3ResourceCompat.ROLE_ARN)
+                    && !Strings.isNullOrEmpty(kv.getValue())) {
+                effectiveProperties.remove(S3ResourceCompat.ACCESS_KEY);
+                effectiveProperties.remove(S3ResourceCompat.Env.ACCESS_KEY);
+                effectiveProperties.remove(S3ResourceCompat.SECRET_KEY);
+                effectiveProperties.remove(S3ResourceCompat.Env.SECRET_KEY);
+            }
+            if (kv.getKey().equalsIgnoreCase(S3ResourceCompat.ACCESS_KEY)
+                    && !Strings.isNullOrEmpty(kv.getValue())) {
+                effectiveProperties.remove(S3ResourceCompat.ROLE_ARN);
+                effectiveProperties.remove(S3ResourceCompat.Env.ROLE_ARN);
+                effectiveProperties.remove(S3ResourceCompat.EXTERNAL_ID);
+                effectiveProperties.remove(S3ResourceCompat.Env.EXTERNAL_ID);
+            }
+        }
+        GcsAuthResolver.resolve(effectiveProperties);

Review Comment:
   [P2] Allow an existing GCP resource to change authentication mode. This 
validates a merged map that still contains the previous credentials: adding 
`gs.credential_provider_type=DEFAULT` to an HMAC resource leaves its stored 
AK/SK, and blank AK/SK updates are ignored. The resolver rejects that mix; the 
reverse change retains the native selector too. This prevents in-place 
migration of a resource used by a storage policy. Clear mutually exclusive 
credentials when ALTER explicitly selects the other mode, then validate the 
resulting map.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/storage/CloudObjectStoreAdapter.java:
##########
@@ -115,6 +123,8 @@ public static Cloud.ObjectStoreInfoPB.Builder 
getObjStoreInfoPB(Map<String, Stri
             }
         }
 
+        ObjCredentialFactory.fromProperties(properties, gcsAuth)

Review Comment:
   [P2] Include native GCP identity in SHOW CREATE STORAGE VAULT. This writes 
the selected provider type and impersonation account into 
`ObjectStoreInfoPB.credential`, but 
`ShowCreateStorageVaultCommand.getObjectCreateStmt()` reconstructs DDL using 
only legacy fields plus `provider=GCP`. Recreating a COMPUTE_ENGINE or 
impersonated vault from that DDL therefore selects DEFAULT ADC and can lose 
access to its bucket. Emit `gs.credential_provider_type` and 
`gs.impersonation_service_account` from the stored credential and cover a SHOW 
CREATE round trip.



##########
cloud/src/meta-service/meta_service_resource.cpp:
##########
@@ -1162,28 +1201,72 @@ static int alter_s3_storage_vault_by_id(InstanceInfoPB& 
instance, std::unique_pt
         new_vault.mutable_obj_info()->clear_role_arn();
         new_vault.mutable_obj_info()->clear_external_id();
         new_vault.mutable_obj_info()->clear_cred_provider_type();
+        new_vault.mutable_obj_info()->clear_credential();

Review Comment:
   [P2] Keep native vault auth when an AK/SK replacement is empty. `ALTER 
STORAGE VAULT` accepts `s3.access_key=""` and `s3.secret_key=""`; FE sets both 
fields, so the pair-presence check and encryption succeed here, then this line 
erases the working GCP credential. The resulting empty keys pass 
`validate_obj_authentication()` and are persisted, while recycler skips the 
empty pair and has no OAuth credential to use. Require both replacement values 
to be nonempty before clearing the current credential, and test empty and 
half-empty replacements.



-- 
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