blob: 05bc3ddb7b734f7cd7d9e8f2b8076c5a95003067 [file] [log] [blame]
/*
* 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;
import java.util.Set;
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 addAliasesForCluster(String clusterName, Map<String, String> credentials) throws AliasServiceException {
for (Map.Entry<String, String> credential : credentials.entrySet()) {
addAliasForCluster(clusterName, credential.getKey(), credential.getValue());
}
}
@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 void removeAliasesForCluster(String clusterName, Set<String> aliases) throws AliasServiceException {
for (String alias : aliases) {
removeAliasForCluster(clusterName, alias);
}
}
@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 {
}
}