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