You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by kr...@apache.org on 2019/03/06 16:16:11 UTC
[knox] branch master updated: KNOX-1687 - Hashicorp Vault
RemoteAliasService provider
This is an automated email from the ASF dual-hosted git repository.
krisden pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 08dea10 KNOX-1687 - Hashicorp Vault RemoteAliasService provider
08dea10 is described below
commit 08dea106dbef920258095a9fc30325a2ea50b810
Author: Kevin Risden <kr...@apache.org>
AuthorDate: Fri Jan 11 15:58:38 2019 -0500
KNOX-1687 - Hashicorp Vault RemoteAliasService provider
Signed-off-by: Kevin Risden <kr...@apache.org>
---
.../deploy/JWTAccessTokenAssertionContributor.java | 5 +-
gateway-release/pom.xml | 4 +
.../gateway/config/impl/GatewayConfigImpl.java | 2 +-
.../security/impl/DefaultAliasService.java | 4 +-
.../security/impl/DefaultKeystoreService.java | 3 +-
.../services/security/impl/RemoteAliasService.java | 5 +-
.../security/impl/ZookeeperRemoteAliasService.java | 10 +-
.../impl/RemoteAliasServiceTestProvider.java | 4 +-
gateway-service-hashicorp-vault/pom.xml | 78 ++++++
.../vault/HashicorpVaultAliasService.java | 250 ++++++++++++++++++
.../HashicorpVaultRemoteAliasServiceProvider.java | 34 +++
...HashicorpVaultClientAuthenticationProvider.java | 34 +++
...HashicorpVaultClientAuthenticationProvider.java | 68 +++++
...HashicorpVaultClientAuthenticationProvider.java | 62 +++++
...tion.HashicorpVaultClientAuthenticationProvider | 20 ++
...nox.gateway.security.RemoteAliasServiceProvider | 19 ++
.../vault/TestHashicorpVaultAliasService.java | 282 +++++++++++++++++++++
.../src/test/resources/log4j.properties | 24 ++
.../gateway/services/security/AliasService.java | 3 +-
pom.xml | 45 ++++
20 files changed, 938 insertions(+), 18 deletions(-)
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/deploy/JWTAccessTokenAssertionContributor.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/deploy/JWTAccessTokenAssertionContributor.java
index 0a35064..d4f0c65 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/deploy/JWTAccessTokenAssertionContributor.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/deploy/JWTAccessTokenAssertionContributor.java
@@ -27,10 +27,11 @@ import org.apache.knox.gateway.services.security.CryptoService;
import org.apache.knox.gateway.topology.Provider;
import org.apache.knox.gateway.topology.Service;
+import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
+
public class JWTAccessTokenAssertionContributor extends
ProviderDeploymentContributorBase {
private static final String ENCRYPT_ACCESS_TOKENS = "encrypt_access_tokens";
- private static final String GATEWAY = "__gateway";
private static final String FILTER_CLASSNAME = "org.apache.knox.gateway.provider.federation.jwt.filter.JWTAccessTokenAssertionFilter";
private CryptoService crypto;
@@ -47,7 +48,7 @@ public class JWTAccessTokenAssertionContributor extends
@Override
public void initializeContribution(DeploymentContext context) {
super.initializeContribution(context);
- crypto.createAndStoreEncryptionKeyForCluster(GATEWAY, ENCRYPT_ACCESS_TOKENS);
+ crypto.createAndStoreEncryptionKeyForCluster(NO_CLUSTER_NAME, ENCRYPT_ACCESS_TOKENS);
}
@Override
diff --git a/gateway-release/pom.xml b/gateway-release/pom.xml
index 3902c99..0af744f 100644
--- a/gateway-release/pom.xml
+++ b/gateway-release/pom.xml
@@ -340,5 +340,9 @@
<groupId>org.apache.knox</groupId>
<artifactId>gateway-adapter</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-service-hashicorp-vault</artifactId>
+ </dependency>
</dependencies>
</project>
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index f68b536..5bb5243 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -229,7 +229,7 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
static final String DISPATCH_HOST_WHITELIST_SERVICES = DISPATCH_HOST_WHITELIST + ".services";
static final String REMOTE_ALIAS_SERVICE_CONFIG_PREFIX = GATEWAY_CONFIG_FILE_PREFIX + ".remote.alias.service.config.prefix";
- static final String REMOTE_ALIAS_SERVICE_CONFIG_PREFIX_DEFAULT = GATEWAY_CONFIG_FILE_PREFIX + ".remote.alias.service.config";
+ static final String REMOTE_ALIAS_SERVICE_CONFIG_PREFIX_DEFAULT = GATEWAY_CONFIG_FILE_PREFIX + ".remote.alias.service.config.";
private static final List<String> DEFAULT_GLOBAL_RULES_SERVICES = Arrays.asList(
"NAMENODE", "JOBTRACKER", "WEBHDFS", "WEBHCAT",
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
index 2f0dae5..7f40e3b 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
@@ -174,13 +174,13 @@ public class DefaultAliasService implements AliasService {
@Override
public char[] getPasswordFromAliasForGateway(String alias)
throws AliasServiceException {
- return getPasswordFromAliasForCluster("__gateway", alias);
+ return getPasswordFromAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
public void generateAliasForGateway(String alias)
throws AliasServiceException {
- generateAliasForCluster("__gateway", alias);
+ generateAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
index 7f42bbd..aac4129 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
@@ -54,12 +54,13 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
+
public class DefaultKeystoreService extends BaseKeystoreService implements
KeystoreService, Service {
private static final String dnTemplate = "CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
private static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
- private static final String NO_CLUSTER_NAME = "__gateway";
private static final String CERT_GEN_MODE = "hadoop.gateway.cert.gen.mode";
private static final String CERT_GEN_MODE_LOCALHOST = "localhost";
private static final String CERT_GEN_MODE_HOSTNAME = "hostname";
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
index 13f7332..5182fd1 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
@@ -47,7 +47,6 @@ import java.util.ServiceLoader;
* @since 1.1.0
*/
public class RemoteAliasService implements AliasService {
- public static final String DEFAULT_CLUSTER_NAME = "__gateway";
public static final String REMOTE_ALIAS_SERVICE_TYPE = "type";
private static final GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
@@ -173,7 +172,7 @@ public class RemoteAliasService implements AliasService {
@Override
public char[] getPasswordFromAliasForGateway(String alias)
throws AliasServiceException {
- return getPasswordFromAliasForCluster(DEFAULT_CLUSTER_NAME, alias);
+ return getPasswordFromAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
@@ -235,7 +234,7 @@ public class RemoteAliasService implements AliasService {
@Override
public void generateAliasForGateway(final String alias)
throws AliasServiceException {
- generateAliasForCluster(DEFAULT_CLUSTER_NAME, alias);
+ generateAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java
index 2758114..f2327e8 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/ZookeeperRemoteAliasService.java
@@ -40,8 +40,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
-import static org.apache.knox.gateway.services.security.impl.RemoteAliasService.DEFAULT_CLUSTER_NAME;
-
/**
* An {@link AliasService} implementation based on zookeeper remote service registry.
*/
@@ -155,7 +153,7 @@ public class ZookeeperRemoteAliasService implements AliasService {
ensureEntry(PATH_KNOX_SECURITY, remoteClient);
ensureEntry(PATH_KNOX_ALIAS_STORE_TOPOLOGY, remoteClient);
ensureEntry(
- PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + DEFAULT_CLUSTER_NAME,
+ PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + NO_CLUSTER_NAME,
remoteClient);
}
@@ -280,7 +278,7 @@ public class ZookeeperRemoteAliasService implements AliasService {
@Override
public char[] getPasswordFromAliasForGateway(String alias)
throws AliasServiceException {
- return getPasswordFromAliasForCluster(DEFAULT_CLUSTER_NAME, alias);
+ return getPasswordFromAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
@@ -305,7 +303,7 @@ public class ZookeeperRemoteAliasService implements AliasService {
@Override
public void generateAliasForGateway(final String alias) throws AliasServiceException {
- generateAliasForCluster(DEFAULT_CLUSTER_NAME, alias);
+ generateAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
@@ -411,7 +409,7 @@ public class ZookeeperRemoteAliasService implements AliasService {
ensureEntry(PATH_KNOX_SECURITY, remoteClient);
ensureEntry(PATH_KNOX_ALIAS_STORE_TOPOLOGY, remoteClient);
ensureEntry(
- PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + DEFAULT_CLUSTER_NAME,
+ PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + NO_CLUSTER_NAME,
remoteClient);
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java
index 75f73cb..523c1e2 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/RemoteAliasServiceTestProvider.java
@@ -84,7 +84,7 @@ public class RemoteAliasServiceTestProvider implements RemoteAliasServiceProvide
@Override
public char[] getPasswordFromAliasForGateway(String alias) {
- return getPasswordFromAliasForCluster(RemoteAliasService.DEFAULT_CLUSTER_NAME, alias);
+ return getPasswordFromAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
@@ -109,7 +109,7 @@ public class RemoteAliasServiceTestProvider implements RemoteAliasServiceProvide
@Override
public void generateAliasForGateway(String alias) {
- generateAliasForCluster(RemoteAliasService.DEFAULT_CLUSTER_NAME, alias);
+ generateAliasForCluster(NO_CLUSTER_NAME, alias);
}
@Override
diff --git a/gateway-service-hashicorp-vault/pom.xml b/gateway-service-hashicorp-vault/pom.xml
new file mode 100644
index 0000000..afa5435
--- /dev/null
+++ b/gateway-service-hashicorp-vault/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>gateway-service-hashicorp-vault</artifactId>
+ <name>gateway-service-hashicorp-vault</name>
+ <description>A hashicorp vault backend</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-util-common</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.vault</groupId>
+ <artifactId>spring-vault-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>vault</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-test-utils</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java
new file mode 100644
index 0000000..e9e3851
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultAliasService.java
@@ -0,0 +1,250 @@
+/*
+ * 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.knox.gateway.backend.hashicorp.vault;
+
+import org.apache.knox.gateway.backend.hashicorp.vault.authentication.HashicorpVaultClientAuthenticationProvider;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.util.PasswordUtils;
+import org.springframework.vault.VaultException;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.core.VaultVersionedKeyValueOperations;
+import org.springframework.vault.support.Versioned;
+
+import java.net.URI;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+public class HashicorpVaultAliasService implements AliasService {
+ public static final String TYPE = "hashicorp.vault";
+ public static final String VAULT_CONFIG_PREFIX = "hashicorp.vault.";
+ public static final String VAULT_ADDRESS_KEY = VAULT_CONFIG_PREFIX + "address";
+
+ private static final String KEY = "data";
+
+ static final String VAULT_SEPARATOR = "/";
+ static final String VAULT_SECRETS_ENGINE_KEY = VAULT_CONFIG_PREFIX + "secrets.engine";
+ static final String VAULT_PATH_PREFIX_KEY = VAULT_CONFIG_PREFIX + "path.prefix";
+
+ private final AliasService localAliasService;
+
+ private VaultVersionedKeyValueOperations vault;
+ private String vaultPathPrefix;
+ private GatewayConfig config;
+
+ public HashicorpVaultAliasService(AliasService localAliasService) {
+ this.localAliasService = localAliasService;
+ }
+
+ private String getPath(String clusterName) {
+ return vaultPathPrefix + clusterName;
+ }
+
+ private String getPath(String clusterName, String alias) {
+ return getPath(clusterName) + VAULT_SEPARATOR + alias;
+ }
+
+ @Override
+ public List<String> getAliasesForCluster(String clusterName) throws AliasServiceException {
+ try {
+ List<String> aliases = vault.list(getPath(clusterName));
+ if(aliases == null) {
+ return Collections.emptyList();
+ }
+
+ // Required to check if list entries are valid since versioned KV does a soft delete
+ // Keys are still listed but do not have a value
+ for (Iterator<String> iterator = aliases.iterator(); iterator.hasNext();) {
+ String alias = iterator.next();
+ if (getPasswordFromAliasForCluster(clusterName, alias) == null) {
+ // Remove the current element from the iterator and the list.
+ iterator.remove();
+ }
+ }
+
+ return aliases;
+ } catch (VaultException e) {
+ throw new AliasServiceException(e);
+ }
+ }
+
+ @Override
+ public void addAliasForCluster(String clusterName, String alias, String value) throws AliasServiceException {
+ try {
+ vault.put(getPath(clusterName, alias), Collections.singletonMap(KEY, value));
+ } catch (VaultException e) {
+ throw new AliasServiceException(e);
+ }
+ }
+
+ @Override
+ public void removeAliasForCluster(String clusterName, String alias) throws AliasServiceException {
+ // Delete is by default a soft delete with versioned KV in Vault
+ // https://learn.hashicorp.com/vault/secrets-management/sm-versioned-kv#step-6-permanently-delete-data
+ // Below is an example of how to programmatically delete all versions
+ /*
+ vaultTemplate.doWithSession(restOperations -> {
+ restOperations.delete(VAULT_SEPARATOR + vaultSecretsEngine + "/metadata/" + clusterName + VAULT_SEPARATOR + alias);
+ return null;
+ });
+ */
+ try {
+ vault.delete(getPath(clusterName, alias));
+ } catch (VaultException e) {
+ throw new AliasServiceException(e);
+ }
+ }
+
+ @Override
+ public char[] getPasswordFromAliasForCluster(String clusterName, String alias) throws AliasServiceException {
+ try {
+ Versioned<Map<String, Object>> mapVersioned = vault.get(getPath(clusterName, alias));
+ if(mapVersioned != null && mapVersioned.hasData()) {
+ Map<String, Object> data = mapVersioned.getData();
+ if(data != null && data.containsKey(KEY)) {
+ return String.valueOf(data.get(KEY)).toCharArray();
+ }
+ }
+ return null;
+ } catch (VaultException e) {
+ throw new AliasServiceException(e);
+ }
+ }
+
+ @Override
+ public char[] getPasswordFromAliasForCluster(String clusterName, String alias, boolean generate) throws AliasServiceException {
+ if(generate) {
+ getPasswordFromAliasForCluster(clusterName, alias);
+ }
+ return getPasswordFromAliasForCluster(clusterName, alias);
+ }
+
+ @Override
+ public void generateAliasForCluster(String clusterName, String alias) throws AliasServiceException {
+ addAliasForCluster(clusterName, alias, PasswordUtils.generatePassword(16));
+ }
+
+ @Override
+ public char[] getPasswordFromAliasForGateway(String alias) throws AliasServiceException {
+ return getPasswordFromAliasForCluster(NO_CLUSTER_NAME, alias);
+ }
+
+ @Override
+ public char[] getGatewayIdentityPassphrase() throws AliasServiceException {
+ return getPasswordFromAliasForGateway(config.getIdentityKeyPassphraseAlias());
+ }
+
+ @Override
+ public char[] getGatewayIdentityKeystorePassword() throws AliasServiceException {
+ return getPasswordFromAliasForGateway(config.getIdentityKeystorePasswordAlias());
+ }
+
+ @Override
+ public char[] getSigningKeyPassphrase() throws AliasServiceException {
+ return getPasswordFromAliasForGateway(config.getSigningKeyPassphraseAlias());
+ }
+
+ @Override
+ public char[] getSigningKeystorePassword() throws AliasServiceException {
+ return getPasswordFromAliasForGateway(config.getSigningKeystorePasswordAlias());
+ }
+
+ @Override
+ public void generateAliasForGateway(String alias) throws AliasServiceException {
+ generateAliasForCluster(NO_CLUSTER_NAME, alias);
+ }
+
+ @Override
+ public Certificate getCertificateForGateway(String alias) throws AliasServiceException {
+ throw new AliasServiceException(new UnsupportedOperationException());
+ }
+
+ @Override
+ public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
+ this.config = config;
+ Map<String, String> remoteAliasServiceConfiguration = config.getRemoteAliasServiceConfiguration();
+ Map<String, String> vaultConfiguration = new HashMap<>();
+ for(Map.Entry<String, String> entry : remoteAliasServiceConfiguration.entrySet()) {
+ if(entry.getKey().startsWith(VAULT_CONFIG_PREFIX)) {
+ vaultConfiguration.put(entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ String vaultAddress = vaultConfiguration.get(VAULT_ADDRESS_KEY);
+ String vaultSecretsEngine = vaultConfiguration.get(VAULT_SECRETS_ENGINE_KEY);
+ vaultPathPrefix = getVaultPathPrefix(vaultConfiguration);
+
+ VaultEndpoint vaultEndpoint;
+ try {
+ vaultEndpoint = VaultEndpoint.from(new URI(vaultAddress));
+ ClientAuthentication vaultAuthentication = getClientAuthentication(vaultConfiguration);
+ VaultTemplate vaultTemplate = new VaultTemplate(vaultEndpoint, vaultAuthentication);
+ vault = vaultTemplate.opsForVersionedKeyValue(vaultSecretsEngine);
+ } catch (Exception e) {
+ throw new ServiceLifecycleException("Failed to init", e);
+ }
+ }
+
+ private String getVaultPathPrefix(Map<String, String> properties) {
+ String vaultPathPrefix = properties.get(VAULT_PATH_PREFIX_KEY);
+ if(vaultPathPrefix == null) {
+ return "";
+ }
+ if(vaultPathPrefix.startsWith(VAULT_SEPARATOR)) {
+ vaultPathPrefix = vaultPathPrefix.replaceFirst(VAULT_SEPARATOR, "");
+ }
+ if(vaultPathPrefix.endsWith(VAULT_SEPARATOR)) {
+ return vaultPathPrefix;
+ }
+ return vaultPathPrefix + VAULT_SEPARATOR;
+ }
+
+ private ClientAuthentication getClientAuthentication(Map<String, String> properties)
+ throws Exception {
+ String authenticationType = properties.get(
+ HashicorpVaultClientAuthenticationProvider.AUTHENTICATION_TYPE_KEY);
+
+ ServiceLoader<HashicorpVaultClientAuthenticationProvider> providers =
+ ServiceLoader.load(HashicorpVaultClientAuthenticationProvider.class);
+ for (HashicorpVaultClientAuthenticationProvider provider : providers) {
+ if(authenticationType.equals(provider.getType())) {
+ return provider.newInstance(localAliasService, properties);
+ }
+ }
+
+ throw new IllegalStateException("Not able to find client authentication provider");
+ }
+
+ @Override
+ public void start() throws ServiceLifecycleException {
+ }
+
+ @Override
+ public void stop() throws ServiceLifecycleException {
+ }
+}
diff --git a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultRemoteAliasServiceProvider.java b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultRemoteAliasServiceProvider.java
new file mode 100644
index 0000000..894e510
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/HashicorpVaultRemoteAliasServiceProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.backend.hashicorp.vault;
+
+import org.apache.knox.gateway.security.RemoteAliasServiceProvider;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.MasterService;
+
+public class HashicorpVaultRemoteAliasServiceProvider implements RemoteAliasServiceProvider {
+ @Override
+ public String getType() {
+ return HashicorpVaultAliasService.TYPE;
+ }
+
+ @Override
+ public AliasService newInstance(AliasService localAliasService, MasterService ms) {
+ return new HashicorpVaultAliasService(localAliasService);
+ }
+}
diff --git a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/HashicorpVaultClientAuthenticationProvider.java b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/HashicorpVaultClientAuthenticationProvider.java
new file mode 100644
index 0000000..419d7c2
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/HashicorpVaultClientAuthenticationProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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.knox.gateway.backend.hashicorp.vault.authentication;
+
+import org.apache.knox.gateway.services.security.AliasService;
+import org.springframework.vault.authentication.ClientAuthentication;
+
+import java.util.Map;
+
+import static org.apache.knox.gateway.backend.hashicorp.vault.HashicorpVaultAliasService.VAULT_CONFIG_PREFIX;
+
+public interface HashicorpVaultClientAuthenticationProvider {
+ String AUTHENTICATION_CONFIG_PREFIX = VAULT_CONFIG_PREFIX + "authentication.";
+ String AUTHENTICATION_TYPE_KEY = AUTHENTICATION_CONFIG_PREFIX + "type";
+
+ String getType();
+ ClientAuthentication newInstance(AliasService localAliasService, Map<String, String> properties)
+ throws Exception;
+}
diff --git a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/KubernetesHashicorpVaultClientAuthenticationProvider.java b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/KubernetesHashicorpVaultClientAuthenticationProvider.java
new file mode 100644
index 0000000..11f3047
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/KubernetesHashicorpVaultClientAuthenticationProvider.java
@@ -0,0 +1,68 @@
+/*
+ * 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.knox.gateway.backend.hashicorp.vault.authentication;
+
+import org.apache.knox.gateway.backend.hashicorp.vault.HashicorpVaultAliasService;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.authentication.KubernetesAuthentication;
+import org.springframework.vault.authentication.KubernetesAuthenticationOptions;
+import org.springframework.vault.client.SimpleVaultEndpointProvider;
+import org.springframework.vault.client.VaultClients;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.client.VaultEndpointProvider;
+import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+import org.springframework.web.client.RestOperations;
+
+import java.net.URI;
+import java.util.Map;
+
+public class KubernetesHashicorpVaultClientAuthenticationProvider
+ implements HashicorpVaultClientAuthenticationProvider {
+ public static final String TYPE = "kubernetes";
+ public static final String KUBERNETES_ROLE_KEY = AUTHENTICATION_CONFIG_PREFIX + "kubernetes.role";
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public ClientAuthentication newInstance(AliasService localAliasService,
+ Map<String, String> properties) throws Exception {
+ String role = properties.get(KUBERNETES_ROLE_KEY);
+ KubernetesAuthenticationOptions kubernetesAuthenticationOptions =
+ KubernetesAuthenticationOptions.builder().role(role).build();
+ return new KubernetesAuthentication(kubernetesAuthenticationOptions,
+ getRestOperations(properties));
+ }
+
+ private RestOperations getRestOperations(Map<String, String> properties) throws Exception {
+ String vaultAddress = properties.get(HashicorpVaultAliasService.VAULT_ADDRESS_KEY);
+ VaultEndpoint vaultEndpoint = VaultEndpoint.from(new URI(vaultAddress));
+ VaultEndpointProvider vaultEndpointProvider = SimpleVaultEndpointProvider.of(vaultEndpoint);
+ ClientOptions clientOptions = new ClientOptions();
+ SslConfiguration sslConfiguration = SslConfiguration.unconfigured();
+ ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory.create(
+ clientOptions, sslConfiguration);
+ return VaultClients.createRestTemplate(vaultEndpointProvider, clientHttpRequestFactory);
+ }
+}
diff --git a/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/TokenHashicorpVaultClientAuthenticationProvider.java b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/TokenHashicorpVaultClientAuthenticationProvider.java
new file mode 100644
index 0000000..f790441
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/java/org/apache/knox/gateway/backend/hashicorp/vault/authentication/TokenHashicorpVaultClientAuthenticationProvider.java
@@ -0,0 +1,62 @@
+/*
+ * 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.knox.gateway.backend.hashicorp.vault.authentication;
+
+import org.apache.knox.gateway.services.security.AliasService;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.authentication.TokenAuthentication;
+
+import java.util.Map;
+
+public class TokenHashicorpVaultClientAuthenticationProvider
+ implements HashicorpVaultClientAuthenticationProvider {
+ public static final String TYPE = "token";
+ public static final String TOKEN_KEY = AUTHENTICATION_CONFIG_PREFIX + "token";
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public ClientAuthentication newInstance(AliasService localAliasService,
+ Map<String, String> properties) throws Exception {
+ String vaultToken = getVaultToken(localAliasService, properties);
+ return new TokenAuthentication(vaultToken);
+ }
+
+ /**
+ * Returns the Vault token from the properties and looks it up in the AliasService
+ * if it is an alias.
+ *
+ * @param localAliasService alias service to use to look up the token
+ * @param properties properties for the Hashicorp Vault remote alias service
+ * @return string of the Vault token
+ * @throws Exception exception if there is an error retrieving the Vault token
+ */
+ private String getVaultToken(AliasService localAliasService, Map<String, String> properties)
+ throws Exception {
+ String vaultToken = properties.get(TOKEN_KEY);
+ if(vaultToken.startsWith("${ALIAS=") && vaultToken.endsWith("}")) {
+ // Strip off ${ALIAS= and } from the value before looking it up
+ String vaultTokenAlias = vaultToken.substring(8, vaultToken.length()-1);
+ return new String(localAliasService.getPasswordFromAliasForGateway(vaultTokenAlias));
+ }
+ return vaultToken;
+ }
+}
diff --git a/gateway-service-hashicorp-vault/src/main/resources/META-INF/services/org.apache.knox.gateway.backend.hashicorp.vault.authentication.HashicorpVaultClientAuthenticationProvider b/gateway-service-hashicorp-vault/src/main/resources/META-INF/services/org.apache.knox.gateway.backend.hashicorp.vault.authentication.HashicorpVaultClientAuthenticationProvider
new file mode 100644
index 0000000..7d67bb9
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/resources/META-INF/services/org.apache.knox.gateway.backend.hashicorp.vault.authentication.HashicorpVaultClientAuthenticationProvider
@@ -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.
+##########################################################################
+
+org.apache.knox.gateway.backend.hashicorp.vault.authentication.KubernetesHashicorpVaultClientAuthenticationProvider
+org.apache.knox.gateway.backend.hashicorp.vault.authentication.TokenHashicorpVaultClientAuthenticationProvider
diff --git a/gateway-service-hashicorp-vault/src/main/resources/META-INF/services/org.apache.knox.gateway.security.RemoteAliasServiceProvider b/gateway-service-hashicorp-vault/src/main/resources/META-INF/services/org.apache.knox.gateway.security.RemoteAliasServiceProvider
new file mode 100644
index 0000000..da06228
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/main/resources/META-INF/services/org.apache.knox.gateway.security.RemoteAliasServiceProvider
@@ -0,0 +1,19 @@
+##########################################################################
+# 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.
+##########################################################################
+
+org.apache.knox.gateway.backend.hashicorp.vault.HashicorpVaultRemoteAliasServiceProvider
diff --git a/gateway-service-hashicorp-vault/src/test/java/org/apache/knox/gateway/backend/hashicorp/vault/TestHashicorpVaultAliasService.java b/gateway-service-hashicorp-vault/src/test/java/org/apache/knox/gateway/backend/hashicorp/vault/TestHashicorpVaultAliasService.java
new file mode 100644
index 0000000..85a0186
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/test/java/org/apache/knox/gateway/backend/hashicorp/vault/TestHashicorpVaultAliasService.java
@@ -0,0 +1,282 @@
+/*
+ * 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.knox.gateway.backend.hashicorp.vault;
+
+import org.apache.knox.gateway.backend.hashicorp.vault.authentication.HashicorpVaultClientAuthenticationProvider;
+import org.apache.knox.gateway.backend.hashicorp.vault.authentication.TokenHashicorpVaultClientAuthenticationProvider;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.test.category.VerifyTest;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.Transferable;
+import org.testcontainers.vault.VaultContainer;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+
+import static org.apache.knox.gateway.backend.hashicorp.vault.HashicorpVaultAliasService.VAULT_SEPARATOR;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeNoException;
+
+@Category(VerifyTest.class)
+public class TestHashicorpVaultAliasService {
+ private static final Logger LOG = LoggerFactory.getLogger(TestHashicorpVaultAliasService.class);
+
+ private static final Random RANDOM = new Random();
+ private static final String vaultVersion = "1.0.3";
+ private static final String vaultImage = "vault:" + vaultVersion;
+ private static final Integer vaultPort = 8200;
+ private static final String vaultToken = "myroot";
+ private String vaultSecretsEngine;
+
+ private static GenericContainer vaultContainer;
+ private static String vaultAddress;
+
+ @BeforeClass
+ public static void setUpClass() {
+ try {
+ vaultContainer = new VaultContainer(vaultImage)
+ .withVaultPort(vaultPort)
+ .withVaultToken(vaultToken)
+ .waitingFor(Wait.forHttp("/"));
+ } catch (IllegalStateException e) {
+ assumeNoException(e);
+ }
+
+ vaultContainer.start();
+ vaultAddress = String.format(Locale.ROOT,
+ "http://%s:%s",
+ vaultContainer.getContainerIpAddress(),
+ vaultContainer.getMappedPort(vaultPort));
+
+ assertTrue(vaultContainer.isRunning());
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ vaultSecretsEngine = "knox-secret-" + RANDOM.nextInt(100);
+
+ setupVaultSecretsEngine();
+ }
+
+ private void setupVaultSecretsEngine() throws Exception {
+ vaultContainer.execInContainer("vault", "secrets", "enable", "-path=" + vaultSecretsEngine,
+ "-version=2", "kv");
+ LOG.debug("created KV secrets engine %s", vaultSecretsEngine);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ cleanupVaultPolicy();
+ cleanupVaultSecretsEngine();
+
+ vaultSecretsEngine = null;
+ }
+
+ private void cleanupVaultSecretsEngine() throws Exception {
+ vaultContainer.execInContainer("vault", "secrets", "disable", vaultSecretsEngine);
+ LOG.debug("deleted KV secrets engine %s", vaultSecretsEngine);
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ if(vaultContainer != null) {
+ vaultContainer.stop();
+ }
+ }
+
+ private String getKnoxToken(boolean forceKnoxSpecifcToken) throws Exception {
+ if(forceKnoxSpecifcToken) {
+ LOG.info("Using Knox specific token");
+ Container.ExecResult tokenCreationExecResult = vaultContainer.execInContainer("vault", "token",
+ "create", "-policy=" + getVaultPolicy(), "-field=token");
+ return tokenCreationExecResult.getStdout().trim();
+ }
+ LOG.info("Using root token");
+ return vaultToken;
+ }
+
+ private String getVaultPolicy() {
+ return vaultSecretsEngine + "-policy";
+ }
+
+ @Test
+ public void testVaultIntegration() throws Exception {
+ String vaultPathPrefix = generatePathPrefix();
+ setupVaultPolicy(VAULT_SEPARATOR);
+
+ GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+
+ Map<String, String> remoteAliasServiceConfiguration = new HashMap<>();
+ remoteAliasServiceConfiguration.put(HashicorpVaultAliasService.VAULT_ADDRESS_KEY,
+ vaultAddress);
+ remoteAliasServiceConfiguration.put(HashicorpVaultAliasService.VAULT_SECRETS_ENGINE_KEY,
+ vaultSecretsEngine);
+ remoteAliasServiceConfiguration.put(HashicorpVaultAliasService.VAULT_PATH_PREFIX_KEY,
+ vaultPathPrefix);
+ remoteAliasServiceConfiguration.put(HashicorpVaultClientAuthenticationProvider.AUTHENTICATION_TYPE_KEY,
+ TokenHashicorpVaultClientAuthenticationProvider.TYPE);
+ remoteAliasServiceConfiguration.put(TokenHashicorpVaultClientAuthenticationProvider.TOKEN_KEY,
+ getKnoxToken(RANDOM.nextBoolean()));
+
+ EasyMock.expect(gatewayConfig.getRemoteAliasServiceConfiguration())
+ .andReturn(remoteAliasServiceConfiguration).anyTimes();
+ EasyMock.replay(gatewayConfig);
+
+ AliasService localAliasService = EasyMock.createNiceMock(AliasService.class);
+
+ AliasService aliasService = new HashicorpVaultAliasService(localAliasService);
+ aliasService.init(gatewayConfig, Collections.emptyMap());
+ aliasService.start();
+
+ String clusterName = "test-" + RANDOM.nextInt(100);
+ String alias = "abc-" + RANDOM.nextInt(100);
+ String aliasPassword = "def-" + RANDOM.nextInt(100);
+
+ assertEquals(0, aliasService.getAliasesForCluster(clusterName).size());
+
+ aliasService.addAliasForCluster(clusterName, alias, aliasPassword);
+
+ assertEquals(1, aliasService.getAliasesForCluster(clusterName).size());
+
+ char[] vaultAliasPassword = aliasService.getPasswordFromAliasForCluster(clusterName, alias);
+ assertEquals(aliasPassword, String.valueOf(vaultAliasPassword));
+
+ aliasService.removeAliasForCluster(clusterName, alias);
+ assertNull(aliasService.getPasswordFromAliasForCluster(clusterName, alias));
+ assertEquals(0, aliasService.getAliasesForCluster(clusterName).size());
+
+ aliasService.stop();
+ }
+
+ @Test
+ public void testVaultIntegrationPermissions() throws Exception {
+ String vaultPathPrefix = generatePathPrefix();
+ setupVaultPolicy("/invalidPrefix/");
+
+ GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+
+ Map<String, String> remoteAliasServiceConfiguration = new HashMap<>();
+ remoteAliasServiceConfiguration.put(HashicorpVaultAliasService.VAULT_ADDRESS_KEY, vaultAddress);
+ remoteAliasServiceConfiguration.put(HashicorpVaultAliasService.VAULT_SECRETS_ENGINE_KEY,
+ vaultSecretsEngine);
+ remoteAliasServiceConfiguration.put(HashicorpVaultAliasService.VAULT_PATH_PREFIX_KEY,
+ vaultPathPrefix);
+ remoteAliasServiceConfiguration.put(HashicorpVaultClientAuthenticationProvider.AUTHENTICATION_TYPE_KEY,
+ TokenHashicorpVaultClientAuthenticationProvider.TYPE);
+ remoteAliasServiceConfiguration.put(TokenHashicorpVaultClientAuthenticationProvider.TOKEN_KEY,
+ getKnoxToken(true));
+
+ EasyMock.expect(gatewayConfig.getRemoteAliasServiceConfiguration())
+ .andReturn(remoteAliasServiceConfiguration).anyTimes();
+ EasyMock.replay(gatewayConfig);
+
+ AliasService localAliasService = EasyMock.createNiceMock(AliasService.class);
+
+ AliasService aliasService = new HashicorpVaultAliasService(localAliasService);
+ aliasService.init(gatewayConfig, Collections.emptyMap());
+ aliasService.start();
+
+ String clusterName = "test-" + RANDOM.nextInt(100);
+ String alias = "abc-" + RANDOM.nextInt(100);
+ String aliasPassword = "def-" + RANDOM.nextInt(100);
+
+ try {
+ aliasService.getAliasesForCluster(clusterName);
+ fail("Should have gotten a 403");
+ } catch (AliasServiceException e) {
+ assertTrue(e.getMessage().contains("Status 403 Forbidden"));
+ }
+
+ try {
+ aliasService.addAliasForCluster(clusterName, alias, aliasPassword);
+ fail("Should have gotten a 403");
+ } catch (AliasServiceException e) {
+ assertTrue(e.getMessage().contains("Status 403 Forbidden"));
+ }
+
+ try {
+ aliasService.getPasswordFromAliasForCluster(clusterName, alias);
+ fail("Should have gotten a 403");
+ } catch (AliasServiceException e) {
+ assertTrue(e.getMessage().contains("Status 403 Forbidden"));
+ }
+
+ try {
+ aliasService.removeAliasForCluster(clusterName, alias);
+ fail("Should have gotten a 403");
+ } catch (AliasServiceException e) {
+ assertTrue(e.getMessage().contains("Status 403 Forbidden"));
+ }
+
+ aliasService.stop();
+ }
+
+ private String generatePathPrefix() {
+ StringBuilder pathPrefix = new StringBuilder();
+ int numParts = RANDOM.nextInt(10);
+ for(int i = 0; i < numParts; i++) {
+ pathPrefix.append(VAULT_SEPARATOR).append(RANDOM.nextInt(10));
+ }
+ pathPrefix.append(VAULT_SEPARATOR);
+ String result = pathPrefix.toString();
+ LOG.info("Using path prefix: '{}'", result);
+ return result;
+ }
+
+ private void setupVaultPolicy(String pathPrefix) throws Exception {
+ String policy = "path \"" + vaultSecretsEngine + pathPrefix + "*\" {\n" +
+ " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n" +
+ "}";
+ LOG.info("policy: {}", policy);
+ String policyFilePath = "/tmp/" + getVaultPolicy() + ".hcl";
+ vaultContainer.copyFileToContainer(Transferable.of(policy.getBytes(StandardCharsets.UTF_8)),
+ policyFilePath);
+ vaultContainer.execInContainer("vault", "policy", "write", getVaultPolicy(), policyFilePath);
+ LOG.debug("created policy %s", getVaultPolicy());
+ vaultContainer.execInContainer("rm", "-f", policyFilePath);
+ }
+
+ private void cleanupVaultPolicy() {
+ try {
+ vaultContainer.execInContainer("vault", "policy", "delete", getVaultPolicy());
+ LOG.debug("deleted policy %s", getVaultPolicy());
+ } catch (Exception ignore) {
+ // ignore
+ }
+ }
+}
diff --git a/gateway-service-hashicorp-vault/src/test/resources/log4j.properties b/gateway-service-hashicorp-vault/src/test/resources/log4j.properties
new file mode 100644
index 0000000..e8643cd
--- /dev/null
+++ b/gateway-service-hashicorp-vault/src/test/resources/log4j.properties
@@ -0,0 +1,24 @@
+# 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.
+
+# Root logger option
+log4j.rootLogger=INFO, stdout
+
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java
index 7a7f76d..79d69d1 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/AliasService.java
@@ -23,6 +23,7 @@ import java.util.List;
import org.apache.knox.gateway.services.Service;
public interface AliasService extends Service {
+ String NO_CLUSTER_NAME = "__gateway";
List<String> getAliasesForCluster(String clusterName)
throws AliasServiceException;
@@ -58,4 +59,4 @@ public interface AliasService extends Service {
Certificate getCertificateForGateway(String alias)
throws AliasServiceException;
-}
\ No newline at end of file
+}
diff --git a/pom.xml b/pom.xml
index c847983..ba4326c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,6 +129,7 @@
<module>gateway-test-release-utils</module>
<module>gateway-service-nifi</module>
<module>gateway-docker</module>
+ <module>gateway-service-hashicorp-vault</module>
</modules>
<properties>
@@ -220,7 +221,9 @@
<spotbugs.version>3.1.11</spotbugs.version>
<spotbugs-maven-plugin.version>3.1.11</spotbugs-maven-plugin.version>
<spring-core.version>5.1.5.RELEASE</spring-core.version>
+ <spring-vault.version>2.1.2.RELEASE</spring-vault.version>
<taglibs-standard.version>1.2.5</taglibs-standard.version>
+ <testcontainers.version>1.10.6</testcontainers.version>
<velocity.version>1.7</velocity.version>
<xmltool.version>3.3</xmltool.version>
<xml-matchers.version>0.10</xml-matchers.version>
@@ -1064,6 +1067,11 @@
<artifactId>gateway-shell-samples</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-service-hashicorp-vault</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
@@ -1918,6 +1926,30 @@
</dependency>
<dependency>
+ <groupId>org.springframework.vault</groupId>
+ <artifactId>spring-vault-dependencies</artifactId>
+ <version>${spring-vault.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <version>${spring-core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ <version>${spring-core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>${spring-core.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
<version>${forbiddenapis.version}</version>
@@ -2025,6 +2057,19 @@
<version>${curator-test.version}</version>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <version>${testcontainers.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>vault</artifactId>
+ <version>${testcontainers.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</dependencyManagement>