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

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


The following commit(s) were added to refs/heads/master by this push:
     new c6c500629a DRILL-8232: Add support for user credentials to 
VaultCredentialsProvider (#2558)
c6c500629a is described below

commit c6c500629a2633d611ee28582bb2948f4d55e214
Author: James Turton <[email protected]>
AuthorDate: Fri May 27 02:16:55 2022 +0200

    DRILL-8232: Add support for user credentials to VaultCredentialsProvider 
(#2558)
    
    * Add support for user credentials to VaultCredentialsProvider.
    
    * Change LOGGER var to lower case.
    
    * Convert VaultCredentialsProvider to AppRole authentication.
    
    BREAKING. VaultCredentialsProvider was previously configured with
    a static Vault token in a BOOT option. Now it is configured with a
    static AppRole and secret in BOOT options and those are used to obtain
    Vault tokens.
    
    * Fox logic and style.
    
    * Satisfy RAT.
---
 .../http/TestUserTranslationInHttpPlugin.java      |   2 +-
 docs/dev/PluginCredentialsProvider.md              |  11 +-
 .../exec/server/rest/PluginConfigWrapper.java      |   8 +-
 .../store/security/EnvCredentialsProvider.java     |   8 +-
 .../security/UsernamePasswordCredentials.java      |   5 +-
 .../UsernamePasswordWithProxyCredentials.java      |   2 +-
 .../security/oauth/OAuthTokenCredentials.java      |   2 +-
 .../security/vault/VaultCredentialsProvider.java   | 129 +++++++++++++++++----
 .../user/security/TestVaultUserAuthenticator.java  |   8 +-
 .../CredentialsProviderImplementationsTest.java    | 107 ++++++++++++++---
 .../storage/CredentialsProviderSerDeTest.java      |  20 +---
 .../test/resources/vault/read-vault-secrets.hcl    |  20 ++++
 .../logical/security/CredentialsProvider.java      |   2 +-
 .../logical/security/PlainCredentialsProvider.java |   5 +-
 14 files changed, 252 insertions(+), 77 deletions(-)

diff --git 
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestUserTranslationInHttpPlugin.java
 
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestUserTranslationInHttpPlugin.java
index f4d20018d3..b909b80f1d 100644
--- 
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestUserTranslationInHttpPlugin.java
+++ 
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestUserTranslationInHttpPlugin.java
@@ -172,7 +172,7 @@ public class TestUserTranslationInHttpPlugin extends 
ClusterTest {
     StoragePluginRegistry registry = cluster.storageRegistry();
     StoragePlugin plugin = registry.getPlugin("local");
     PlainCredentialsProvider credentialsProvider = (PlainCredentialsProvider) 
plugin.getConfig().getCredentialsProvider();
-    Map<String, String> credentials = 
credentialsProvider.getCredentials(TEST_USER_1);
+    Map<String, String> credentials = 
credentialsProvider.getUserCredentials(TEST_USER_1);
     assertNotNull(credentials);
     assertNull(credentials.get("username"));
     assertNull(credentials.get("password"));
diff --git a/docs/dev/PluginCredentialsProvider.md 
b/docs/dev/PluginCredentialsProvider.md
index 7702d1e083..7855f105b8 100644
--- a/docs/dev/PluginCredentialsProvider.md
+++ b/docs/dev/PluginCredentialsProvider.md
@@ -93,14 +93,17 @@ In this case, the storage `jdbc` plugin will use `user1` 
value as the `username`
 
 ## Using credentials managed by Vault
 
-`VaultCredentialsProvider` credentials provider implementation allows using 
Vault secrets as plugin credentials.
+`VaultCredentialsProvider` credentials provider implementation allows using 
Vault secrets as plugin credentials. Currently, this credentials provider 
authenticates itself to Vault using the 
[AppRole](https://www.vaultproject.io/docs/auth/approle) auth method which is 
intended for use by applications and services. In future, it may be able to use 
the Vault token of the Drill query user instead, in the event that the 
`VaultUserAuthenticator` is also in use.
 
 Before using this credential provider, the following Drill properties should 
be configured in `drill-override.conf`:
 ```
-"drill.exec.storage.vault.address" - address of the Vault server
-"drill.exec.storage.vault.token" - token used to access Vault
+"drill.exec.storage.vault.address" - host name or address of the Vault server.
+"drill.exec.storage.vault.app_role_id" - the role ID belonging to the AppRole 
Drill will use
+"drill.exec.storage.vault.secret_id" - the secret ID belonging to the AppRole 
Drill will use
 ```
 
+Note that you will generally need to create and assign a [Vault 
policy](https://www.hashicorp.com/resources/policies-vault) to grant the 
AppRole used by Drill read access to Vault secrets.
+
 Once it is set, we can configure storage plugin to use this way of obtaining 
credentials:
 ```json
 {
@@ -118,7 +121,7 @@ Once it is set, we can configure storage plugin to use this 
way of obtaining cre
 }
 ```
 
-`secretPath` property specifies the Vault key value from which to read
+`secretPath` property specifies the Vault key value from which to read. If the 
plugin's `authMode` is set to `user_translation` then the `secretPath` may 
include a variable named `$user` which will be replaced with the Drill query 
username at query execution time.
 `propertyNames` map contains keys that specify which credential will be 
obtained from the Vault secret with the secret name of the `propertyNames` 
value.
 
 For example, user may store the following secrets in the Vault:
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
index 9ae34828ee..64972c48ca 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
@@ -64,22 +64,22 @@ public class PluginConfigWrapper {
   }
 
   @JsonIgnore
-  public String getUserName(String activeUser) {
+  public String getUserName(String queryUser) {
     CredentialsProvider credentialsProvider = config.getCredentialsProvider();
     Optional<UsernamePasswordCredentials> credentials = new 
UsernamePasswordCredentials.Builder()
       .setCredentialsProvider(credentialsProvider)
-      .setQueryUser(activeUser)
+      .setQueryUser(queryUser)
       .build();
 
     return 
credentials.map(UsernamePasswordCredentials::getUsername).orElse(null);
   }
 
   @JsonIgnore
-  public String getPassword(String activeUser) {
+  public String getPassword(String queryUser) {
     CredentialsProvider credentialsProvider = config.getCredentialsProvider();
     Optional<UsernamePasswordCredentials> credentials = new 
UsernamePasswordCredentials.Builder()
       .setCredentialsProvider(credentialsProvider)
-      .setQueryUser(activeUser)
+      .setQueryUser(queryUser)
       .build();
 
     return 
credentials.map(UsernamePasswordCredentials::getPassword).orElse(null);
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/EnvCredentialsProvider.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/EnvCredentialsProvider.java
index 2325018f03..03e866c59c 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/EnvCredentialsProvider.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/EnvCredentialsProvider.java
@@ -42,8 +42,12 @@ public class EnvCredentialsProvider implements 
CredentialsProvider {
   @Override
   public Map<String, String> getCredentials() {
     Map<String, String> credentials = new HashMap<>();
-    envVariables.forEach((key, value) -> credentials.put(key, 
System.getenv(value)));
-
+    envVariables.forEach((key, value) -> {
+      String cred = System.getenv(value);
+      if (cred != null) {
+        credentials.put(key, cred);
+      }
+    });
     return credentials;
   }
 
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordCredentials.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordCredentials.java
index 379930527e..9fa663dfc6 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordCredentials.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordCredentials.java
@@ -19,15 +19,12 @@ package org.apache.drill.exec.store.security;
 
 import org.apache.drill.common.PlanStringBuilder;
 import org.apache.drill.common.logical.security.CredentialsProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 
 public class UsernamePasswordCredentials {
-  private static final Logger logger = 
LoggerFactory.getLogger(UsernamePasswordCredentials.class);
   public static final String USERNAME = "username";
   public static final String PASSWORD = "password";
 
@@ -58,7 +55,7 @@ public class UsernamePasswordCredentials {
       }
 
       Map<String, String> credentials = queryUser != null
-        ? credentialsProvider.getCredentials(queryUser)
+        ? credentialsProvider.getUserCredentials(queryUser)
         : credentialsProvider.getCredentials();
 
       if (credentials.size() == 0) {
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
index d6285a3b57..6a2d6b7a12 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
@@ -52,7 +52,7 @@ public class UsernamePasswordWithProxyCredentials extends 
UsernamePasswordCreden
       }
 
       Map<String, String> credentials = queryUser != null
-        ? credentialsProvider.getCredentials(queryUser)
+        ? credentialsProvider.getUserCredentials(queryUser)
         : credentialsProvider.getCredentials();
 
       if (credentials.size() == 0) {
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
index 66b2279bb2..08217dc6be 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
@@ -70,7 +70,7 @@ public class OAuthTokenCredentials extends 
UsernamePasswordCredentials {
       }
 
       Map<String, String> credentials = queryUser != null
-        ? credentialsProvider.getCredentials(queryUser)
+        ? credentialsProvider.getUserCredentials(queryUser)
         : credentialsProvider.getCredentials();
 
       if (credentials.size() == 0) {
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/vault/VaultCredentialsProvider.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/vault/VaultCredentialsProvider.java
index 8de62820f4..2df1e65e26 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/vault/VaultCredentialsProvider.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/vault/VaultCredentialsProvider.java
@@ -20,12 +20,17 @@ package org.apache.drill.exec.store.security.vault;
 import com.bettercloud.vault.Vault;
 import com.bettercloud.vault.VaultConfig;
 import com.bettercloud.vault.VaultException;
+import com.bettercloud.vault.response.AuthResponse;
+import com.bettercloud.vault.response.LogicalResponse;
 import com.fasterxml.jackson.annotation.JacksonInject;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.OptBoolean;
 import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.common.exceptions.UserException;
 import org.apache.drill.common.logical.security.CredentialsProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -37,15 +42,17 @@ import java.util.Objects;
  */
 public class VaultCredentialsProvider implements CredentialsProvider {
 
+  private static final Logger logger = 
LoggerFactory.getLogger(VaultCredentialsProvider.class);
   // Drill boot options used to configure a Vault credentials provider
   public static final String VAULT_ADDRESS = 
"drill.exec.storage.vault.address";
-  public static final String VAULT_TOKEN = "drill.exec.storage.vault.token";
-
-  private final String secretPath;
+  public static final String VAULT_APP_ROLE_ID = 
"drill.exec.storage.vault.app_role_id";
+  public static final String VAULT_SECRET_ID = 
"drill.exec.storage.vault.secret_id";
+  public static final String QUERY_USER_VAR = "$user";
 
+  private final String secretPath, appRoleId, secretId;
   private final Map<String, String> propertyNames;
-
-  private final Vault vault;
+  private final VaultConfig vaultConfig;
+  private Vault vault;
 
   /**
    * @param secretPath The Vault key value from which to read
@@ -58,38 +65,112 @@ public class VaultCredentialsProvider implements 
CredentialsProvider {
       @JsonProperty("secretPath") String secretPath,
       @JsonProperty("propertyNames") Map<String, String> propertyNames,
       @JacksonInject(useInput = OptBoolean.FALSE) DrillConfig config) throws 
VaultException {
+
     this.propertyNames = propertyNames;
     this.secretPath = secretPath;
-    String vaultAddress = 
Objects.requireNonNull(config.getString(VAULT_ADDRESS),
-        String.format("Vault address is not specified. Please set [%s] config 
property.", VAULT_ADDRESS));
-    String token = Objects.requireNonNull(config.getString(VAULT_TOKEN),
-        String.format("Vault token is not specified. Please set [%s] config 
property.", VAULT_TOKEN));
+    this.appRoleId = Objects.requireNonNull(
+      config.getString(VAULT_APP_ROLE_ID),
+      String.format(
+        "Vault app role id is not specified. Please set [%s] config property.",
+        VAULT_APP_ROLE_ID
+      )
+    );
+    this.secretId = Objects.requireNonNull(
+      config.getString(VAULT_SECRET_ID),
+      String.format(
+        "Vault secret id is not specified. Please set [%s] config property.",
+        VAULT_SECRET_ID
+      )
+    );
+    String vaultAddress = Objects.requireNonNull(
+      config.getString(VAULT_ADDRESS),
+      String.format(
+        "Vault address is not specified. Please set [%s] config property.",
+        VAULT_ADDRESS
+      )
+    );
 
-    VaultConfig vaultConfig = new VaultConfig()
+    this.vaultConfig = new VaultConfig()
         .address(vaultAddress)
-        .token(token)
         .build();
+    // Initial unauthenticated Vault client, needed for the first auth() call.
     this.vault = new Vault(vaultConfig);
   }
 
-  @Override
-  public Map<String, String> getCredentials() {
+  private Map<String, String> extractCredentials(Map<String, String> 
vaultSecrets) {
     Map<String, String> credentials = new HashMap<>();
-    propertyNames.forEach((key, value) -> {
-      try {
-        String credValue = vault.logical()
-            .read(secretPath)
-            .getData()
-            .get(value);
-        credentials.put(key, credValue);
-      } catch (VaultException e) {
-        throw new RuntimeException("Error while fetching credentials from 
vault", e);
+    for (Map.Entry<String, String> entry : propertyNames.entrySet()) {
+      String cred = vaultSecrets.get(entry.getValue());
+      if (cred != null) {
+        credentials.put(entry.getKey(), vaultSecrets.get(entry.getValue()));
       }
-    });
-
+    }
     return credentials;
   }
 
+  private Map<String, String> getCredentialsAt(String path) {
+    LogicalResponse resp;
+    // Obtain this thread's own reference to the current Vault object to use
+    // for deciding whether _we_ need to reauthenticate in the event of an
+    // unauthorised read, or another thread has done that already.
+    Vault threadVault = this.vault;
+
+    try {
+      logger.debug("Attempting to fetch secrets from Vault path {}.", path);
+      resp = threadVault.logical().read(path);
+
+      if (resp.getRestResponse().getStatus() == 403) {
+        logger.info("Attempt to fetch secrets received HTTP 403 from Vault.");
+        synchronized (this) {
+          if (threadVault == vault) {
+            // The Vault object has not already been replaced by another 
thread,
+            // reauthenticate and replace it.
+            logger.info("Attempting to reauthenticate.");
+            AuthResponse authResp = vault.auth().loginByAppRole(appRoleId, 
secretId);
+            vault = new 
Vault(vaultConfig.token(authResp.getAuthClientToken()));
+          } else {
+            logger.debug("Another caller has already attempted 
reauthentication.");
+          }
+        }
+        logger.debug("Reattempting to fetch secrets from Vault path {}", path);
+        resp = vault.logical().read(path);
+      }
+      return extractCredentials(resp.getData());
+
+    } catch (VaultException ex) {
+      throw UserException.systemError(ex)
+        .message("Error while fetching credentials from vault")
+        .build(logger);
+    }
+  }
+
+  @Override
+  public Map<String, String> getCredentials() {
+    Map<String, String> creds = getCredentialsAt(secretPath);
+    if (creds.isEmpty()) {
+      logger.warn(
+        "No credentials matching the configured property names were readable 
at {}",
+        secretPath
+      );
+    }
+    return creds;
+  }
+
+  @Override
+  public Map<String, String> getUserCredentials(String queryUser) {
+    // Resolve a Vault path that may contain the $user var, e.g. 
/org/dept/$user -> /org/dept/alice
+    String resolvedPath = secretPath.replace(QUERY_USER_VAR, queryUser);
+    Map<String, String> creds = getCredentialsAt(resolvedPath);
+    if (creds.isEmpty()) {
+      logger.warn(
+        "No credentials for {} matching the configured property names were 
readable at {}",
+        queryUser,
+        resolvedPath
+      );
+    }
+    return creds;
+  }
+
   public String getSecretPath() {
     return secretPath;
   }
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestVaultUserAuthenticator.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestVaultUserAuthenticator.java
index d5bf5cf1b6..ad25217a6e 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestVaultUserAuthenticator.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/rpc/user/security/TestVaultUserAuthenticator.java
@@ -42,15 +42,15 @@ import static org.junit.Assert.fail;
 @Category(SecurityTest.class)
 public class TestVaultUserAuthenticator extends ClusterTest {
 
-  private static final String ROOT_TOKEN_VALUE = "vault-token";
+  private static final String VAULT_ROOT_TOKEN = "vault-token";
 
   private static String vaultAddr;
 
   @ClassRule
   public static final VaultContainer<?> vaultContainer =
-      new VaultContainer<>(DockerImageName.parse("vault").withTag("1.1.3"))
+      new VaultContainer<>(DockerImageName.parse("vault").withTag("1.10.3"))
           .withLogLevel(VaultLogLevel.Debug)
-          .withVaultToken(ROOT_TOKEN_VALUE)
+          .withVaultToken(VAULT_ROOT_TOKEN)
           .withInitCommand(
             "auth enable userpass",
             "write auth/userpass/users/alice password=pass1 policies=admins",
@@ -93,7 +93,7 @@ public class TestVaultUserAuthenticator extends ClusterTest {
     // Use the Vault client lib to obtain Vault tokens for our test users.
     VaultConfig vaultConfig = new VaultConfig()
       .address(vaultAddr)
-      .token(ROOT_TOKEN_VALUE)
+      .token(VAULT_ROOT_TOKEN)
       .build();
 
     Vault vault = new Vault(vaultConfig);
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderImplementationsTest.java
 
b/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderImplementationsTest.java
index b8df40a04c..c49df893e9 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderImplementationsTest.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderImplementationsTest.java
@@ -17,7 +17,11 @@
  */
 package org.apache.drill.storage;
 
+import com.bettercloud.vault.Vault;
+import com.bettercloud.vault.VaultConfig;
 import com.bettercloud.vault.VaultException;
+import com.bettercloud.vault.response.LogicalResponse;
+
 import org.apache.drill.common.config.DrillConfig;
 import org.apache.drill.common.logical.security.CredentialsProvider;
 import org.apache.drill.exec.store.security.EnvCredentialsProvider;
@@ -31,6 +35,7 @@ import org.apache.hadoop.conf.Configuration;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
+import org.testcontainers.containers.BindMode;
 import org.testcontainers.utility.DockerImageName;
 import org.testcontainers.vault.VaultContainer;
 
@@ -41,24 +46,62 @@ import static org.junit.Assert.assertEquals;
 
 public class CredentialsProviderImplementationsTest extends ClusterTest {
 
-  private static final String VAULT_TOKEN_VALUE = "vault-token";
-
-  private static final String SECRET_PATH = "secret/testing";
+  private static final String VAULT_ROOT_TOKEN = "vault-token";
+  private static final String VAULT_APP_ROLE_PATH = 
"auth/approle/role/drill-role";
+  private static final String SHARED_SECRET_PATH = "secret/testing";
+  private static final String USER_SECRET_PATH = "secret/testing/$user";
+  private static final String CONTAINER_POLICY_PATH = 
"/tmp/read-vault-secrets.hcl";
 
   @ClassRule
   public static final VaultContainer<?> vaultContainer =
-      new VaultContainer<>(DockerImageName.parse("vault").withTag("1.1.3"))
-          .withVaultToken(VAULT_TOKEN_VALUE)
-          .withVaultPort(8200)
-          .withSecretInVault(SECRET_PATH,
-              "top_secret=password1",
-              "db_password=dbpassword1");
+    new VaultContainer<>(DockerImageName.parse("vault").withTag("1.10.3"))
+      .withVaultToken(VAULT_ROOT_TOKEN)
+      .withSecretInVault(SHARED_SECRET_PATH,
+          "top_secret=password1",
+          "db_password=dbpassword1")
+      
.withSecretInVault(USER_SECRET_PATH.replace(VaultCredentialsProvider.QUERY_USER_VAR,
 "alice"),
+          "top_secret=password1",
+          "db_password=dbpassword1")
+      .withClasspathResourceMapping("vault/read-vault-secrets.hcl", 
CONTAINER_POLICY_PATH, BindMode.READ_ONLY)
+      .withInitCommand(
+        "auth enable approle",
+        String.format("policy write read-secrets %s", CONTAINER_POLICY_PATH),
+        String.format("write %s policies=read-secrets", VAULT_APP_ROLE_PATH)
+      );
 
   @BeforeClass
   public static void init() throws Exception {
+    String vaultAddr = String.format(
+      "http://%s:%d";,
+      vaultContainer.getHost(),
+      vaultContainer.getFirstMappedPort()
+    );
+
+    VaultConfig vaultConfig = new VaultConfig()
+      .address(vaultAddr)
+      .token(VAULT_ROOT_TOKEN)
+      .build();
+
+    // While other Vault paths in the test container seem to work fine with KV 
engine v2,
+    // the AppRole paths produced 404s and forced the specification of version 
1 in the
+    // BetterCloud client used to perform AppRole operations.
+    Vault vault = new Vault(vaultConfig, 1);
+
+    LogicalResponse resp = vault.logical()
+      .read(String.format("%s/role-id", VAULT_APP_ROLE_PATH));
+    String appRoleId = resp.getData().get("role_id");
+
+    resp = vault.logical().write(
+      String.format("%s/secret-id", VAULT_APP_ROLE_PATH),
+      Collections.emptyMap()
+    );
+    String secretId = resp.getData().get("secret_id");
+
     startCluster(ClusterFixture.builder(dirTestWatcher)
-        .configProperty(VaultCredentialsProvider.VAULT_ADDRESS, "http://"; + 
vaultContainer.getHost() + ":" + vaultContainer.getMappedPort(8200))
-    .configProperty(VaultCredentialsProvider.VAULT_TOKEN, VAULT_TOKEN_VALUE));
+      .configProperty(VaultCredentialsProvider.VAULT_ADDRESS, vaultAddr)
+      .configProperty(VaultCredentialsProvider.VAULT_APP_ROLE_ID, appRoleId)
+      .configProperty(VaultCredentialsProvider.VAULT_SECRET_ID, secretId)
+    );
   }
 
   @Test
@@ -102,16 +145,52 @@ public class CredentialsProviderImplementationsTest 
extends ClusterTest {
   public void testVaultCredentialsProvider() throws VaultException {
     DrillConfig config = cluster.drillbit().getContext().getConfig();
 
-    CredentialsProvider envCredentialsProvider = new VaultCredentialsProvider(
-        SECRET_PATH,
+    CredentialsProvider vaultCredsProvider = new VaultCredentialsProvider(
+        SHARED_SECRET_PATH,
         ImmutableMap.of(UsernamePasswordCredentials.USERNAME, "top_secret",
             UsernamePasswordCredentials.PASSWORD, "db_password"),
         config);
 
-    Map<String, String> actualCredentials = 
envCredentialsProvider.getCredentials();
+    Map<String, String> actualCredentials = 
vaultCredsProvider.getCredentials();
 
     assertEquals(ImmutableMap.of(UsernamePasswordCredentials.USERNAME, 
"password1",
         UsernamePasswordCredentials.PASSWORD, "dbpassword1"),
         actualCredentials);
   }
+
+  @Test
+  public void testVaultUserCredentialsPresent() throws VaultException {
+    DrillConfig config = cluster.drillbit().getContext().getConfig();
+
+    CredentialsProvider vaultCredsProvider = new VaultCredentialsProvider(
+        USER_SECRET_PATH,
+        ImmutableMap.of(UsernamePasswordCredentials.USERNAME, "top_secret",
+            UsernamePasswordCredentials.PASSWORD, "db_password"),
+        config);
+
+    Map<String, String> actualCredentials = 
vaultCredsProvider.getUserCredentials("alice");
+
+    assertEquals(
+      ImmutableMap.of(
+        UsernamePasswordCredentials.USERNAME, "password1",
+        UsernamePasswordCredentials.PASSWORD, "dbpassword1"
+      ),
+      actualCredentials
+    );
+  }
+
+  @Test
+  public void testVaultUserCredentialsAbsent() throws VaultException {
+    DrillConfig config = cluster.drillbit().getContext().getConfig();
+
+    CredentialsProvider vaultCredsProvider = new VaultCredentialsProvider(
+        USER_SECRET_PATH,
+        ImmutableMap.of(UsernamePasswordCredentials.USERNAME, "top_secret",
+            UsernamePasswordCredentials.PASSWORD, "db_password"),
+        config);
+
+    Map<String, String> actualCredentials = 
vaultCredsProvider.getUserCredentials("bob");
+
+    assertEquals(Collections.<String, String>emptyMap(), actualCredentials);
+  }
 }
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderSerDeTest.java
 
b/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderSerDeTest.java
index b2549871f9..ab4946b5a3 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderSerDeTest.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/storage/CredentialsProviderSerDeTest.java
@@ -31,32 +31,22 @@ import 
org.apache.drill.shaded.guava.com.google.common.collect.ImmutableMap;
 import org.apache.drill.test.ClusterFixture;
 import org.apache.drill.test.ClusterTest;
 import org.junit.BeforeClass;
-import org.junit.ClassRule;
 import org.junit.Test;
-import org.testcontainers.utility.DockerImageName;
-import org.testcontainers.vault.VaultContainer;
 
 import static org.junit.Assert.assertEquals;
 
 public class CredentialsProviderSerDeTest extends ClusterTest {
 
-  private static final String VAULT_TOKEN_VALUE = "vault-token";
-
   private static final String SECRET_PATH = "secret/testing";
 
-  @ClassRule
-  public static final VaultContainer<?> vaultContainer =
-      new VaultContainer<>(DockerImageName.parse("vault").withTag("1.1.3"))
-          .withVaultToken(VAULT_TOKEN_VALUE)
-          .withSecretInVault(SECRET_PATH,
-              "top_secret=password1",
-              "db_password=dbpassword1");
-
   @BeforeClass
   public static void init() throws Exception {
     startCluster(ClusterFixture.builder(dirTestWatcher)
-        .configProperty(VaultCredentialsProvider.VAULT_ADDRESS, "http://"; + 
vaultContainer.getHost() + ":" + vaultContainer.getFirstMappedPort())
-        .configProperty(VaultCredentialsProvider.VAULT_TOKEN, 
VAULT_TOKEN_VALUE));
+      // Bogus Vault server values are sufficient for the tests in this class.
+      .configProperty(VaultCredentialsProvider.VAULT_ADDRESS, "foo")
+      .configProperty(VaultCredentialsProvider.VAULT_APP_ROLE_ID, "foo")
+      .configProperty(VaultCredentialsProvider.VAULT_SECRET_ID, "foo")
+    );
   }
 
   @Test
diff --git a/exec/java-exec/src/test/resources/vault/read-vault-secrets.hcl 
b/exec/java-exec/src/test/resources/vault/read-vault-secrets.hcl
new file mode 100644
index 0000000000..62bbdd3b41
--- /dev/null
+++ b/exec/java-exec/src/test/resources/vault/read-vault-secrets.hcl
@@ -0,0 +1,20 @@
+// 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.
+
+path "secret/*"
+{
+  capabilities = ["read"]
+}
diff --git 
a/logical/src/main/java/org/apache/drill/common/logical/security/CredentialsProvider.java
 
b/logical/src/main/java/org/apache/drill/common/logical/security/CredentialsProvider.java
index ba38ad0ab7..0992579401 100644
--- 
a/logical/src/main/java/org/apache/drill/common/logical/security/CredentialsProvider.java
+++ 
b/logical/src/main/java/org/apache/drill/common/logical/security/CredentialsProvider.java
@@ -46,7 +46,7 @@ public interface CredentialsProvider {
    * @return A Map of the logged in user's credentials.
    */
   @JsonIgnore
-  default Map<String, String> getCredentials(String username) {
+  default Map<String, String> getUserCredentials(String username) {
     throw UserException.unsupportedError()
       .message("%s does not support per-user credentials.", getClass())
       .build(logger);
diff --git 
a/logical/src/main/java/org/apache/drill/common/logical/security/PlainCredentialsProvider.java
 
b/logical/src/main/java/org/apache/drill/common/logical/security/PlainCredentialsProvider.java
index 5624b39276..678b086712 100644
--- 
a/logical/src/main/java/org/apache/drill/common/logical/security/PlainCredentialsProvider.java
+++ 
b/logical/src/main/java/org/apache/drill/common/logical/security/PlainCredentialsProvider.java
@@ -68,7 +68,8 @@ public class PlainCredentialsProvider implements 
CredentialsProvider {
 
   @Override
   @JsonIgnore(false)
-  @JsonProperty("credentials") public Map<String, String> getCredentials() {
+  @JsonProperty("credentials")
+  public Map<String, String> getCredentials() {
     return credentials;
   }
 
@@ -85,7 +86,7 @@ public class PlainCredentialsProvider implements 
CredentialsProvider {
    * @return A Map of the active user's credentials
    */
   @Override
-  public Map<String, String> getCredentials(String queryUser) {
+  public Map<String, String> getUserCredentials(String queryUser) {
     assert queryUser != null;
     logger.debug("Getting credentials for query user {}", queryUser);
 

Reply via email to