This is an automated email from the ASF dual-hosted git repository.

mblow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 42345fc836 [ASTERIXDB-3514][EXT]: Add error codes for missing 
invalid/missing creds to assume role
42345fc836 is described below

commit 42345fc83643b9e1e05959da0e704ce22649bb65
Author: Hussain Towaileb <[email protected]>
AuthorDate: Mon Jan 20 02:35:32 2025 +0300

    [ASTERIXDB-3514][EXT]: Add error codes for missing invalid/missing creds to 
assume role
    
    - user model changes: no
    - storage format changes: no
    - interface changes: yes
    
    Details:
    - Add error codes for missing/temporary credentials, need
      long-lived credentials to assume the role.
    - Unify getting name to update/delete cache.
    
    Ext-ref: MB-63505
    Change-Id: I3ea48cc83d4c0b92d66e518f7e8108050f0e553a
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/19344
    Reviewed-by: Murtadha Hubail <[email protected]>
    Reviewed-by: Hussain Towaileb <[email protected]>
    Integration-Tests: Jenkins <[email protected]>
    Tested-by: Jenkins <[email protected]>
---
 .../app/external/ExternalCredentialsCache.java     | 91 ++++++++++++++++------
 .../message/UpdateAwsCredentialsCacheRequest.java  | 14 +++-
 .../asterix/app/translator/QueryTranslator.java    |  5 +-
 .../asterix/common/exceptions/ErrorCode.java       |  2 +
 .../common/external/IExternalCredentialsCache.java | 40 ++++++++--
 .../common/metadata/DatasetFullyQualifiedName.java |  2 +-
 .../common/metadata/IFullyQualifiedName.java       | 22 ++++++
 .../src/main/resources/asx_errormsg/en.properties  |  2 +
 8 files changed, 144 insertions(+), 34 deletions(-)

diff --git 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCache.java
 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCache.java
index 66fbdecbc9..8cb3a47511 100644
--- 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCache.java
+++ 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCache.java
@@ -24,7 +24,13 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.asterix.common.api.IApplicationContext;
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.external.IExternalCredentialsCache;
+import org.apache.asterix.common.metadata.DatasetFullyQualifiedName;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.common.metadata.IFullyQualifiedName;
 import org.apache.asterix.common.metadata.MetadataConstants;
 import org.apache.asterix.external.util.ExternalDataConstants;
 import org.apache.asterix.external.util.aws.s3.S3Constants;
@@ -48,51 +54,70 @@ public class ExternalCredentialsCache implements 
IExternalCredentialsCache {
     }
 
     @Override
-    public synchronized Object getCredentials(Map<String, String> 
configuration) {
-        String name = getName(configuration);
-        if (cache.containsKey(name) && 
!needsRefresh(cache.get(name).getLeft())) {
-            return cache.get(name).getRight();
-        }
-        return null;
+    public synchronized Object getCredentials(Map<String, String> 
configuration) throws CompilationException {
+        IFullyQualifiedName fqn = 
getFullyQualifiedNameFromConfiguration(configuration);
+        return getCredentials(fqn);
     }
 
     @Override
-    public synchronized void updateCache(Map<String, String> configuration, 
Map<String, String> credentials) {
-        String type = configuration.get(ExternalDataConstants.KEY_READER);
-        if 
(ExternalDataConstants.KEY_ADAPTER_NAME_AWS_S3.equalsIgnoreCase(type)) {
-            updateAwsCache(configuration, credentials);
+    public synchronized Object getCredentials(IFullyQualifiedName fqn) {
+        String name = getName(fqn);
+        if (cache.containsKey(name) && 
!needsRefresh(cache.get(name).getLeft())) {
+            return cache.get(name).getRight();
         }
+        return null;
     }
 
     @Override
-    public void deleteCredentials(String name) {
-        cache.remove(name);
+    public synchronized void updateCache(Map<String, String> configuration, 
Map<String, String> credentials)
+            throws CompilationException {
+        IFullyQualifiedName fqn = 
getFullyQualifiedNameFromConfiguration(configuration);
+        String type = 
configuration.get(ExternalDataConstants.KEY_EXTERNAL_SOURCE_TYPE);
+        updateCache(fqn, type, credentials);
     }
 
     @Override
-    public String getName(Map<String, String> configuration) {
-        String database = 
configuration.get(ExternalDataConstants.KEY_DATASET_DATABASE);
-        if (database == null) {
-            database = MetadataConstants.DEFAULT_DATABASE;
+    public synchronized void updateCache(IFullyQualifiedName fqn, String type, 
Map<String, String> credentials) {
+        String name = getName(fqn);
+        if 
(ExternalDataConstants.KEY_ADAPTER_NAME_AWS_S3.equalsIgnoreCase(type)) {
+            updateAwsCache(name, credentials);
         }
-        String dataverse = 
configuration.get(ExternalDataConstants.KEY_DATASET_DATAVERSE);
-        String dataset = configuration.get(ExternalDataConstants.KEY_DATASET);
-        return String.join(".", database, dataverse, dataset);
     }
 
-    private void updateAwsCache(Map<String, String> configuration, Map<String, 
String> credentials) {
+    private void updateAwsCache(String name, Map<String, String> credentials) {
         String accessKeyId = 
credentials.get(S3Constants.ACCESS_KEY_ID_FIELD_NAME);
         String secretAccessKey = 
credentials.get(S3Constants.SECRET_ACCESS_KEY_FIELD_NAME);
         String sessionToken = 
credentials.get(S3Constants.SESSION_TOKEN_FIELD_NAME);
-        doUpdateAwsCache(configuration, 
AwsSessionCredentials.create(accessKeyId, secretAccessKey, sessionToken));
+        doUpdateAwsCache(name, AwsSessionCredentials.create(accessKeyId, 
secretAccessKey, sessionToken));
     }
 
-    private void doUpdateAwsCache(Map<String, String> configuration, 
AwsSessionCredentials credentials) {
-        String name = getName(configuration);
+    private void doUpdateAwsCache(String name, AwsSessionCredentials 
credentials) {
         cache.put(name, Pair.of(Span.start(awsAssumeRoleDuration, 
TimeUnit.SECONDS), credentials));
         LOGGER.info("Received and cached new credentials for {}", name);
     }
 
+    @Override
+    public void deleteCredentials(IFullyQualifiedName fqn) {
+        String name = getName(fqn);
+        Object removed = cache.remove(name);
+        if (removed != null) {
+            LOGGER.info("Removed cached credentials for {}", name);
+        } else {
+            LOGGER.info("No cached credentials found for {}, nothing to 
remove", name);
+        }
+    }
+
+    @Override
+    public String getName(Map<String, String> configuration) throws 
CompilationException {
+        IFullyQualifiedName fqn = 
getFullyQualifiedNameFromConfiguration(configuration);
+        return getName(fqn);
+    }
+
+    @Override
+    public String getName(IFullyQualifiedName fqn) {
+        return fqn.toString();
+    }
+
     /**
      * Refresh if the remaining time is less than the configured refresh 
percentage
      *
@@ -103,4 +128,24 @@ public class ExternalCredentialsCache implements 
IExternalCredentialsCache {
         return (double) span.remaining(TimeUnit.SECONDS)
                 / span.getSpan(TimeUnit.SECONDS) < 
refreshAwsAssumeRoleThreshold;
     }
+
+    protected IFullyQualifiedName 
getFullyQualifiedNameFromConfiguration(Map<String, String> configuration)
+            throws CompilationException {
+        String database = 
configuration.get(ExternalDataConstants.KEY_DATASET_DATABASE);
+        if (database == null) {
+            database = MetadataConstants.DEFAULT_DATABASE;
+        }
+        String stringDataverse = 
configuration.get(ExternalDataConstants.KEY_DATASET_DATAVERSE);
+        DataverseName dataverse = getDataverseName(stringDataverse);
+        String dataset = configuration.get(ExternalDataConstants.KEY_DATASET);
+        return new DatasetFullyQualifiedName(database, dataverse, dataset);
+    }
+
+    protected DataverseName getDataverseName(String dataverse) throws 
CompilationException {
+        try {
+            return DataverseName.createSinglePartName(dataverse);
+        } catch (AsterixException ex) {
+            throw new 
CompilationException(ErrorCode.INVALID_DATABASE_OBJECT_NAME, dataverse);
+        }
+    }
 }
diff --git 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/UpdateAwsCredentialsCacheRequest.java
 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/UpdateAwsCredentialsCacheRequest.java
index 44d4c21305..d1a9ebfeac 100644
--- 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/UpdateAwsCredentialsCacheRequest.java
+++ 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/UpdateAwsCredentialsCacheRequest.java
@@ -21,10 +21,15 @@ package org.apache.asterix.app.message;
 import java.util.Map;
 
 import org.apache.asterix.common.api.INcApplicationContext;
+import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.messaging.api.INcAddressedMessage;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
 public class UpdateAwsCredentialsCacheRequest implements INcAddressedMessage {
 
+    private static final Logger LOGGER = LogManager.getLogger();
     private static final long serialVersionUID = 1L;
     private final Map<String, String> configuration;
     private final Map<String, String> credentials;
@@ -35,8 +40,13 @@ public class UpdateAwsCredentialsCacheRequest implements 
INcAddressedMessage {
     }
 
     @Override
-    public void handle(INcApplicationContext appCtx) {
-        appCtx.getExternalCredentialsCache().updateCache(configuration, 
credentials);
+    public void handle(INcApplicationContext appCtx) throws 
HyracksDataException {
+        try {
+            appCtx.getExternalCredentialsCache().updateCache(configuration, 
credentials);
+        } catch (CompilationException ex) {
+            LOGGER.info("Failed to process request", ex);
+            throw HyracksDataException.create(ex);
+        }
     }
 
     @Override
diff --git 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index 4ee674e852..51371da148 100644
--- 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -2448,8 +2448,9 @@ public class QueryTranslator extends 
AbstractLangTranslator implements IStatemen
                     sourceLoc, EnumSet.of(DropOption.IF_EXISTS), 
requestParameters.isForceDropDataset());
 
             MetadataManager.INSTANCE.commitTransaction(mdTxnCtx.getValue());
-            appCtx.getExternalCredentialsCache()
-                    .deleteCredentials(String.join(".", databaseName, 
dataverseName.getCanonicalForm(), datasetName));
+            if (ds.getDatasetType().equals(DatasetType.EXTERNAL)) {
+                
appCtx.getExternalCredentialsCache().deleteCredentials(ds.getDatasetFullyQualifiedName());
+            }
             return true;
         } catch (Exception e) {
             LOGGER.error("failed to drop dataset; executing compensating 
operations", e);
diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
index afc43e20db..d06a9e5d0d 100644
--- 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
@@ -318,6 +318,8 @@ public enum ErrorCode implements IError {
     COULD_NOT_CREATE_TOKENS(1211),
     NO_AWS_VALID_PARAMS_FOUND_FOR_CROSS_ACCOUNT_TRUST_AUTHENTICATION(1212),
     FAILED_EXTERNAL_CROSS_ACCOUNT_AUTHENTICATION(1213),
+    LONG_LIVED_CREDENTIALS_NEEDED_TO_ASSUME_ROLE(1214),
+    TEMPORARY_CREDENTIALS_CANNOT_BE_USED_TO_ASSUME_ROLE(1215),
 
     // Feed errors
     DATAFLOW_ILLEGAL_STATE(3001),
diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/external/IExternalCredentialsCache.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/external/IExternalCredentialsCache.java
index c603893066..3ff444ddc1 100644
--- 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/external/IExternalCredentialsCache.java
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/external/IExternalCredentialsCache.java
@@ -20,6 +20,9 @@ package org.apache.asterix.common.external;
 
 import java.util.Map;
 
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.IFullyQualifiedName;
+
 public interface IExternalCredentialsCache {
 
     /**
@@ -28,7 +31,15 @@ public interface IExternalCredentialsCache {
      * @param configuration configuration containing external collection 
details
      * @return credentials if present, null otherwise
      */
-    Object getCredentials(Map<String, String> configuration);
+    Object getCredentials(Map<String, String> configuration) throws 
CompilationException;
+
+    /**
+     * Returns the cached credentials. Can be of any supported external 
credentials type
+     *
+     * @param fqn fully qualified name of the credentials entity
+     * @return credentials if present, null otherwise
+     */
+    Object getCredentials(IFullyQualifiedName fqn) throws CompilationException;
 
     /**
      * Updates the credentials cache with the provided credentials for the 
specified name
@@ -36,14 +47,23 @@ public interface IExternalCredentialsCache {
      * @param configuration configuration containing external collection 
details
      * @param credentials credentials to cache
      */
-    void updateCache(Map<String, String> configuration, Map<String, String> 
credentials);
+    void updateCache(Map<String, String> configuration, Map<String, String> 
credentials) throws CompilationException;
 
     /**
-     * Deletes the cache for the provided entity name
+     * Updates the credentials cache with the provided credentials for the 
specified name
      *
-     * @param name name of the entity for which the credentials are to be 
deleted
+     * @param fqn fully qualified name for the credentials entity
+     * @param type type of the entity
+     * @param credentials credentials to cache
      */
-    void deleteCredentials(String name);
+    void updateCache(IFullyQualifiedName fqn, String type, Map<String, String> 
credentials);
+
+    /**
+     * Deletes the cache for the provided enitty
+     *
+     * @param fqn fully qualified name of entity for which the credentials are 
to be deleted
+     */
+    void deleteCredentials(IFullyQualifiedName fqn);
 
     /**
      * Returns the name of the entity which the cached credentials belong to
@@ -51,5 +71,13 @@ public interface IExternalCredentialsCache {
      * @param configuration configuration containing external collection 
details
      * @return name of entity which credentials belong to
      */
-    String getName(Map<String, String> configuration);
+    String getName(Map<String, String> configuration) throws 
CompilationException;
+
+    /**
+     * Returns the name of the entity which the cached credentials belong to
+     *
+     * @param fqn fully qualified name for the credentials entity
+     * @return name of entity which credentials belong to
+     */
+    String getName(IFullyQualifiedName fqn) throws CompilationException;
 }
diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DatasetFullyQualifiedName.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DatasetFullyQualifiedName.java
index 261cddc6f6..e6b66e65e3 100644
--- 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DatasetFullyQualifiedName.java
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DatasetFullyQualifiedName.java
@@ -21,7 +21,7 @@ package org.apache.asterix.common.metadata;
 import java.io.Serializable;
 import java.util.Objects;
 
-public class DatasetFullyQualifiedName implements Serializable {
+public class DatasetFullyQualifiedName implements Serializable, 
IFullyQualifiedName {
 
     private static final long serialVersionUID = 2L;
     private final String databaseName;
diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IFullyQualifiedName.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IFullyQualifiedName.java
new file mode 100644
index 0000000000..f099902f90
--- /dev/null
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IFullyQualifiedName.java
@@ -0,0 +1,22 @@
+/*
+ * 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.asterix.common.metadata;
+
+public interface IFullyQualifiedName {
+}
diff --git 
a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties 
b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
index 6900de3391..c1ffd13627 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -320,6 +320,8 @@
 1211 = Could not create delegation tokens
 1212 = No credentials found for cross-account authentication. Expected 
instance profile or access key id & secret access key for assuming role
 1213 = Failed to perform cross-account authentication. Encountered error : 
'%1$s'
+1214 = Long-lived credentials are required to assume a role
+1215 = Temporary credentials cannot be used to assume a role
 
 # Feed Errors
 3001 = Illegal state.

Reply via email to