parthchandra commented on code in PR #6023:
URL: https://github.com/apache/datafusion-comet/pull/6023#discussion_r4065824295


##########
native/core/src/parquet/objectstore/s3.rs:
##########
@@ -334,6 +337,23 @@ fn lookup_provider_class<'a>(
     get_config_trimmed(configs, bucket, PROVIDER_CLASS_PROPERTY).filter(|s| 
!s.is_empty())
 }
 
+/// Suffixes of `fs.s3a.*` keys that carry static-credential secrets. These 
are deliberately not
+/// forwarded to the SPI: the adapters exist for the case where static keys 
are not used, and
+/// forwarding secrets would widen the blast radius and put them in the 
dispatcher cache-key hash.
+const SECRET_KEY_SUFFIXES: [&str; 3] = [".access.key", ".secret.key", 
".session.token"];

Review Comment:
   Fixed. `forward_catalog_properties` now forwards the full `fs.s3a.*` subset 
with nothing stripped,
   so a chain like `SimpleAWSCredentialsProvider,custom` (and assumed-role) 
resolves the same
   principal as Spark instead of silently falling through to a different entry.
   
   You were right that the blast-radius argument didn't hold: these keys 
already cross JNI for the
   non-adapter path (`build_credential_provider`), and the Iceberg path already 
forwards its full bag.
   
   I also addressed the retention point you raised rather than deferring it: 
the dispatcher's
   `InstanceKey` now stores a SHA-256 digest of the property bag instead of the 
raw values, so the
   static JVM-lifetime cache map holds no secrets. `initialize()` still gets 
the full map, and a
   changed secret value changes the digest so the instance is rebuilt 
correctly. The dispatcher's
   isolation/multi-tenant tests still pass.



##########
native/core/src/parquet/objectstore/s3.rs:
##########
@@ -1023,6 +1043,40 @@ mod tests {
         assert_eq!(session_token, Some("test_session_token"));
     }
 
+    #[test]
+    fn test_forward_catalog_properties_filters_scope_and_secrets() {
+        let mut configs: HashMap<String, String> = HashMap::new();
+        configs.insert(
+            "fs.s3a.aws.credentials.provider".to_string(),
+            
"com.amazonaws.auth.DefaultAWSCredentialsProviderChain".to_string(),
+        );
+        configs.insert("fs.s3a.endpoint".to_string(), 
"s3.example.com".to_string());
+        configs.insert(
+            format!("fs.s3a.comet.{PROVIDER_CLASS_PROPERTY}"),

Review Comment:
   Fixed — was building `fs.s3a.comet.comet.credential.provider.class`. Now
   `format!("fs.s3a.{PROVIDER_CLASS_PROPERTY}")`, and the test asserts the 
activation key survives
   forwarding.



##########
native/core/src/parquet/objectstore/s3.rs:
##########
@@ -334,6 +337,23 @@ fn lookup_provider_class<'a>(
     get_config_trimmed(configs, bucket, PROVIDER_CLASS_PROPERTY).filter(|s| 
!s.is_empty())
 }
 
+/// Suffixes of `fs.s3a.*` keys that carry static-credential secrets. These 
are deliberately not
+/// forwarded to the SPI: the adapters exist for the case where static keys 
are not used, and
+/// forwarding secrets would widen the blast radius and put them in the 
dispatcher cache-key hash.
+const SECRET_KEY_SUFFIXES: [&str; 3] = [".access.key", ".secret.key", 
".session.token"];
+
+/// Builds the `catalog_properties` map forwarded to the SPI on the Parquet 
path: the `fs.s3a.*`
+/// subset with static-credential secrets removed. HashMap equality is 
order-independent, so the
+/// dispatcher instance-cache key stays stable regardless of iteration order.
+fn forward_catalog_properties(configs: &HashMap<String, String>) -> 
HashMap<String, String> {
+    configs
+        .iter()
+        .filter(|(k, _)| k.starts_with("fs.s3a."))
+        .filter(|(k, _)| !SECRET_KEY_SUFFIXES.iter().any(|suffix| 
k.ends_with(suffix)))

Review Comment:
   
    `fs.s3a.encryption.key`,`proxy.password`, etc. are now forwarded the same 
as everything else, exactly as Spark sees them. And the dispatcher no longer 
retains any of these values 



##########
dev/ci/check-suites.py:
##########
@@ -37,6 +37,7 @@ def file_to_class_name(path: Path) -> str | None:
         "org.apache.comet.parquet.ParquetReadFromFakeHadoopFsSuite", # manual 
test suite (loads libhdfs, see #5023)
         "org.apache.comet.IcebergReadFromS3Suite", # manual test suite
         "org.apache.comet.cloud.s3.CometS3CredentialBridgeSuite", # manual 
test suite
+        
"org.apache.comet.cloud.s3.HadoopS3ACredentialProviderAdapterBridgeSuite", # 
manual test suite

Review Comment:
   Ran the suite locally on the spark-4.0, and spark-4.1 profile against a 
MinIO testcontainer. Updated the PR description



##########
spark/src/main/spark-3.x/org/apache/comet/cloud/s3/SdkCredentialExtraction.java:
##########
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.comet.cloud.s3;
+
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSSessionCredentials;
+
+/**
+ * Maps AWS SDK v1 {@link AWSCredentials} onto {@link CometS3Credentials}. 
Compiled only into
+ * spark-3.4 / 3.5 builds (spark-3.x source set), directly against SDK v1.
+ */
+final class SdkCredentialExtraction {
+
+  private SdkCredentialExtraction() {}
+
+  static CometS3Credentials toCometCredentials(AWSCredentials creds) {
+    String sessionToken = null;
+    if (creds instanceof AWSSessionCredentials) {
+      sessionToken = ((AWSSessionCredentials) creds).getSessionToken();
+    }
+    // The v1 base interface exposes no expiration; report 0 (unknown). Safe: 
the Parquet path
+    // ignores expiration and the Iceberg path applies a bounded default TTL.
+    return new CometS3Credentials(creds.getAWSAccessKeyId(), 
creds.getAWSSecretKey(), sessionToken, 0L);

Review Comment:
   Fixed. Added `src/main/spark-*/**/*.java` and `src/test/spark-*/**/*.java` 
to the `<java>` spotless
   block, parallel to the existing `<scala>` includes. `make format` now 
reaches the adapter code (it
   wrapped the two >100-col lines) and alsoupdated a few pre-existing shim 
files.



##########
spark/src/main/spark-4.x/org/apache/comet/cloud/s3/HadoopS3ACredentialProviderAdapter.java:
##########
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.comet.cloud.s3;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.s3a.S3AUtils;
+import org.apache.hadoop.fs.s3a.auth.CredentialProviderListFactory;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+
+import org.apache.comet.annotation.Public;
+
+/**
+ * Delegates credential resolution to Hadoop S3A's own provider construction, 
so it accepts
+ * everything the {@code fs.s3a.aws.credentials.provider} chain accepts. This 
is the spark-4.x (AWS
+ * SDK v2) body; it calls {@link CredentialProviderListFactory} and returns v2 
credentials.
+ *
+ * <p>Enable it (leaving {@code fs.s3a.aws.credentials.provider} untouched) 
with:
+ *
+ * <pre>
+ * 
spark.hadoop.fs.s3a.comet.credential.provider.class=org.apache.comet.cloud.s3.HadoopS3ACredentialProviderAdapter
+ * </pre>
+ */
+@Public
+public class HadoopS3ACredentialProviderAdapter implements 
CometS3CredentialProvider {
+
+  private Map<String, String> properties;
+  private volatile AwsCredentialsProvider delegate;
+
+  @Override
+  public void initialize(Map<String, String> catalogProperties) {
+    this.properties = catalogProperties;
+  }
+
+  @Override
+  public CometS3Credentials getCredentialsForPath(CometS3CredentialContext 
context)
+      throws Exception {
+    AwsCredentialsProvider provider = ensureDelegate(context.getBucket());
+    return 
SdkCredentialExtraction.toCometCredentials(provider.resolveCredentials());
+  }
+
+  private AwsCredentialsProvider ensureDelegate(String bucket) throws 
Exception {
+    AwsCredentialsProvider local = delegate;
+    if (local != null) {
+      return local;
+    }
+    synchronized (this) {
+      if (delegate == null) {
+        Configuration conf =
+            
S3AUtils.propagateBucketOptions(AdapterSupport.toConfiguration(properties), 
bucket);
+        AdapterSupport.patchSecurityCredentialProviders(conf);
+        URI uri = new URI("s3a://" + bucket + "/");
+        delegate = 
CredentialProviderListFactory.createAWSCredentialProviderList(uri, conf);

Review Comment:
   
   Went with your fallback: both Hadoop adapters now catch `LinkageError` 
around the factory call and
   rethrow naming the Hadoop/SDK line the build targets, so an EMR-style 
mismatch fails legibly
   instead of a bare `NoSuchMethodError`. A true runtime probe would need both 
SDK bodies compiled in,
   which fights the per-source-set design, so I'd keep that out of this PR 
unless you feel strongly.



##########
spark/src/main/spark-4.x/org/apache/comet/cloud/s3/AwsSdkCredentialProviderAdapter.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.comet.cloud.s3;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+
+import org.apache.comet.annotation.Public;
+import org.apache.comet.util.ClassLoaders;
+
+/**
+ * Wraps a raw AWS SDK v2 {@link AwsCredentialsProvider} named via
+ * {@code fs.s3a.comet.credential.adapter.class}, for a provider not 
registered through S3A. This is
+ * the spark-4.x (SDK v2) body. Prefer {@link 
HadoopS3ACredentialProviderAdapter} unless the
+ * provider is a plain SDK class not wired through Hadoop.
+ *
+ * <pre>
+ * 
spark.hadoop.fs.s3a.comet.credential.provider.class=org.apache.comet.cloud.s3.AwsSdkCredentialProviderAdapter
+ * spark.hadoop.fs.s3a.comet.credential.adapter.class=&lt;FQCN of an 
AwsCredentialsProvider&gt;
+ * </pre>
+ */
+@Public
+public class AwsSdkCredentialProviderAdapter implements 
CometS3CredentialProvider {
+
+  static final String DELEGATE_CLASS_PROPERTY = 
"comet.credential.adapter.class";
+
+  private Map<String, String> properties;
+  private volatile AwsCredentialsProvider delegate;
+
+  @Override
+  public void initialize(Map<String, String> catalogProperties) {
+    this.properties = catalogProperties;
+  }
+
+  @Override
+  public CometS3Credentials getCredentialsForPath(CometS3CredentialContext 
context)
+      throws Exception {
+    AwsCredentialsProvider provider = ensureDelegate(context.getBucket());
+    return 
SdkCredentialExtraction.toCometCredentials(provider.resolveCredentials());
+  }
+
+  private AwsCredentialsProvider ensureDelegate(String bucket) throws 
Exception {
+    AwsCredentialsProvider local = delegate;
+    if (local != null) {
+      return local;
+    }
+    synchronized (this) {
+      if (delegate == null) {
+        delegate = instantiate(bucket);
+      }
+      return delegate;
+    }
+  }
+
+  private AwsCredentialsProvider instantiate(String bucket) throws Exception {
+    String className = AdapterSupport.lookup(properties, bucket, 
DELEGATE_CLASS_PROPERTY);
+    if (className == null) {
+      throw new IllegalStateException(
+          "AwsSdkCredentialProviderAdapter requires fs.s3a."
+              + DELEGATE_CLASS_PROPERTY
+              + " (or the per-bucket variant) to name an 
AwsCredentialsProvider");
+    }
+    Class<?> clazz = ClassLoaders.loadClass(className);
+    if (!AwsCredentialsProvider.class.isAssignableFrom(clazz)) {
+      throw new IllegalStateException(
+          className
+              + " does not implement 
software.amazon.awssdk.auth.credentials.AwsCredentialsProvider");
+    }
+    // SDK v2 instantiation conventions, in order: static create(), static 
builder().build(),
+    // public no-arg constructor.
+    Method create = AdapterSupport.staticMethod(clazz, "create");

Review Comment:
   Fixed both. There's now one shared `AdapterSupport.instantiateDelegate` used 
by v1 and v2, so the
   `@Public` contract is identical: `(URI, Configuration)` -> `(Configuration)` 
-> `create()` ->
   `builder().build()` -> `getInstance()` -> no-arg. A Hadoop-convention 
provider now works the same
   on Spark 3.5 and 4.0.
   
   The factory lookup also requires the return type to be assignable to the 
target provider (matching
   Hadoop's `getFactoryMethod`), so an unrelated `static String 
create()`/`getInstance()` is skipped
   rather than invoked and failing at the cast. Added a 
`V2UnrelatedCreateProvider` fixture and test,
   plus the private-builder-impl test for the `build()` case.



##########
docs/source/user-guide/latest/s3-credential-providers.md:
##########
@@ -36,6 +36,39 @@ You probably do, if any of these are true:
 - You have a custom Iceberg `client.factory` that injects a configured S3 
client.
 - Spark queries against your S3 paths work, but the same queries with Comet 
enabled fail with 403.
 
+## Built-in adapters
+
+If a native Parquet scan fails with `Unsupported credential provider: <class>` 
(for example `com.amazonaws.auth.DefaultAWSCredentialsProviderChain`), the 
class you named in `fs.s3a.aws.credentials.provider` is one that plain 
Spark/Hadoop accepts but Comet's native reader does not reimplement. Comet 
ships two built-in `CometS3CredentialProvider` adapters that fix this with a 
one-line config change; you leave your existing 
`fs.s3a.aws.credentials.provider` untouched.
+
+These adapters cover the Parquet native scan path only. Enabling one is 
opt-in: naming it is what activates it, and Comet's existing native provider 
handling is unchanged for everyone else.
+
+### `HadoopS3ACredentialProviderAdapter` (recommended)
+
+Delegates to Hadoop S3A's own provider construction, so it accepts everything 
the `fs.s3a.aws.credentials.provider` chain accepts (the default chain, 
web-identity, assumed-role, custom signers, per-bucket config). This is the 
general answer for the failure above.

Review Comment:
   
   Updated. -- the section now just says the chain (static keys, assumed-role, 
etc.) resolves the same way it would under Spark.



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