mirror of https://github.com/apache/nifi.git
NIFI-10071: Adding support for HashiCorp Vault K/V version 2 Secrets Engine (#6087)
This commit is contained in:
parent
0d45fef702
commit
48de70a568
|
@ -46,39 +46,39 @@ public interface HashiCorpVaultCommunicationService {
|
||||||
byte[] decrypt(String transitPath, String cipherText);
|
byte[] decrypt(String transitPath, String cipherText);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a single secret value using Vault's unversioned Key/Value Secrets Engine.
|
* Writes a single secret value using Vault's Key/Value Secrets Engine.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv/kv-v1">https://www.vaultproject.io/api-docs/secret/kv/kv-v1</a>
|
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv">https://www.vaultproject.io/api-docs/secret/kv</a>
|
||||||
* @param keyValuePath The Vault path to use for the configured Key/Value v1 Secrets Engine
|
* @param keyValuePath The Vault path to use for the configured Key/Value Secrets Engine
|
||||||
* @param secretKey The secret key
|
* @param secretKey The secret key
|
||||||
* @param value The secret value
|
* @param value The secret value
|
||||||
*/
|
*/
|
||||||
void writeKeyValueSecret(String keyValuePath, String secretKey, String value);
|
void writeKeyValueSecret(String keyValuePath, String secretKey, String value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a single secret value from Vault's unversioned Key/Value Secrets Engine.
|
* Reads a single secret value from Vault's Key/Value Secrets Engine.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv/kv-v1">https://www.vaultproject.io/api-docs/secret/kv/kv-v1</a>
|
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv">https://www.vaultproject.io/api-docs/secret/kv</a>
|
||||||
* @param keyValuePath The Vault path to use for the configured Key/Value v1 Secrets Engine
|
* @param keyValuePath The Vault path to use for the configured Key/Value Secrets Engine
|
||||||
* @param secretKey The secret key
|
* @param secretKey The secret key
|
||||||
* @return The secret value, or empty if not found
|
* @return The secret value, or empty if not found
|
||||||
*/
|
*/
|
||||||
Optional<String> readKeyValueSecret(String keyValuePath, String secretKey);
|
Optional<String> readKeyValueSecret(String keyValuePath, String secretKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a secret with multiple key/value pairs using Vault's unversioned Key/Value Secrets Engine.
|
* Writes a secret with multiple key/value pairs using Vault's Key/Value Secrets Engine.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv/kv-v1">https://www.vaultproject.io/api-docs/secret/kv/kv-v1</a>
|
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv">https://www.vaultproject.io/api-docs/secret/kv</a>
|
||||||
* @param keyValuePath The Vault path to use for the configured Key/Value v1 Secrets Engine
|
* @param keyValuePath The Vault path to use for the configured Key/Value Secrets Engine
|
||||||
* @param keyValues A map from key to value for keys/values that should be stored in the secret
|
* @param keyValues A map from key to value for keys/values that should be stored in the secret
|
||||||
*/
|
*/
|
||||||
void writeKeyValueSecretMap(String keyValuePath, String secretKey, Map<String, String> keyValues);
|
void writeKeyValueSecretMap(String keyValuePath, String secretKey, Map<String, String> keyValues);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a secret with multiple key/value pairs from Vault's unversioned Key/Value Secrets Engine.
|
* Reads a secret with multiple key/value pairs from Vault's Key/Value Secrets Engine.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv/kv-v1">https://www.vaultproject.io/api-docs/secret/kv/kv-v1</a>
|
* @see <a href="https://www.vaultproject.io/api-docs/secret/kv">https://www.vaultproject.io/api-docs/secret/kv</a>
|
||||||
* @param keyValuePath The Vault path to use for the configured Key/Value v1 Secrets Engine
|
* @param keyValuePath The Vault path to use for the configured Key/Value Secrets Engine
|
||||||
* @param secretKey The secret key
|
* @param secretKey The secret key
|
||||||
* @return A map from key to value from the secret key/values, or an empty map if not found
|
* @return A map from key to value from the secret key/values, or an empty map if not found
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.springframework.core.env.PropertySource;
|
||||||
import org.springframework.vault.authentication.SimpleSessionManager;
|
import org.springframework.vault.authentication.SimpleSessionManager;
|
||||||
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
|
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
|
||||||
import org.springframework.vault.core.VaultKeyValueOperations;
|
import org.springframework.vault.core.VaultKeyValueOperations;
|
||||||
|
import org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend;
|
||||||
import org.springframework.vault.core.VaultTemplate;
|
import org.springframework.vault.core.VaultTemplate;
|
||||||
import org.springframework.vault.core.VaultTransitOperations;
|
import org.springframework.vault.core.VaultTransitOperations;
|
||||||
import org.springframework.vault.support.Ciphertext;
|
import org.springframework.vault.support.Ciphertext;
|
||||||
|
@ -37,8 +38,6 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend.KV_1;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the VaultCommunicationService using Spring Vault
|
* Implements the VaultCommunicationService using Spring Vault
|
||||||
*/
|
*/
|
||||||
|
@ -46,6 +45,7 @@ public class StandardHashiCorpVaultCommunicationService implements HashiCorpVaul
|
||||||
private final VaultTemplate vaultTemplate;
|
private final VaultTemplate vaultTemplate;
|
||||||
private final VaultTransitOperations transitOperations;
|
private final VaultTransitOperations transitOperations;
|
||||||
private final Map<String, VaultKeyValueOperations> keyValueOperationsMap;
|
private final Map<String, VaultKeyValueOperations> keyValueOperationsMap;
|
||||||
|
private final KeyValueBackend keyValueBackend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a VaultCommunicationService that uses Spring Vault.
|
* Creates a VaultCommunicationService that uses Spring Vault.
|
||||||
|
@ -60,6 +60,7 @@ public class StandardHashiCorpVaultCommunicationService implements HashiCorpVaul
|
||||||
new SimpleSessionManager(vaultConfiguration.clientAuthentication()));
|
new SimpleSessionManager(vaultConfiguration.clientAuthentication()));
|
||||||
|
|
||||||
transitOperations = vaultTemplate.opsForTransit();
|
transitOperations = vaultTemplate.opsForTransit();
|
||||||
|
keyValueBackend = vaultConfiguration.getKeyValueBackend();
|
||||||
keyValueOperationsMap = new HashMap<>();
|
keyValueOperationsMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ public class StandardHashiCorpVaultCommunicationService implements HashiCorpVaul
|
||||||
Objects.requireNonNull(secretKey, "Secret secretKey must be specified");
|
Objects.requireNonNull(secretKey, "Secret secretKey must be specified");
|
||||||
Objects.requireNonNull(value, "Secret value must be specified");
|
Objects.requireNonNull(value, "Secret value must be specified");
|
||||||
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
||||||
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, KV_1));
|
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, keyValueBackend));
|
||||||
keyValueOperations.put(secretKey, new SecretData(value));
|
keyValueOperations.put(secretKey, new SecretData(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ public class StandardHashiCorpVaultCommunicationService implements HashiCorpVaul
|
||||||
Objects.requireNonNull(keyValuePath, "Vault K/V path must be specified");
|
Objects.requireNonNull(keyValuePath, "Vault K/V path must be specified");
|
||||||
Objects.requireNonNull(secretKey, "Secret secretKey must be specified");
|
Objects.requireNonNull(secretKey, "Secret secretKey must be specified");
|
||||||
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
||||||
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, KV_1));
|
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, keyValueBackend));
|
||||||
final VaultResponseSupport<SecretData> response = keyValueOperations.get(secretKey, SecretData.class);
|
final VaultResponseSupport<SecretData> response = keyValueOperations.get(secretKey, SecretData.class);
|
||||||
return response == null ? Optional.empty() : Optional.ofNullable(response.getRequiredData().getValue());
|
return response == null ? Optional.empty() : Optional.ofNullable(response.getRequiredData().getValue());
|
||||||
}
|
}
|
||||||
|
@ -123,14 +124,14 @@ public class StandardHashiCorpVaultCommunicationService implements HashiCorpVaul
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
||||||
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, KV_1));
|
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, keyValueBackend));
|
||||||
keyValueOperations.put(secretKey, keyValues);
|
keyValueOperations.put(secretKey, keyValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> readKeyValueSecretMap(final String keyValuePath, final String key) {
|
public Map<String, String> readKeyValueSecretMap(final String keyValuePath, final String key) {
|
||||||
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
final VaultKeyValueOperations keyValueOperations = keyValueOperationsMap
|
||||||
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, KV_1));
|
.computeIfAbsent(keyValuePath, path -> vaultTemplate.opsForKeyValue(path, keyValueBackend));
|
||||||
final VaultResponseSupport<Map> response = keyValueOperations.get(key, Map.class);
|
final VaultResponseSupport<Map> response = keyValueOperations.get(key, Map.class);
|
||||||
return response == null ? Collections.emptyMap() : (Map<String, String>) response.getRequiredData();
|
return response == null ? Collections.emptyMap() : (Map<String, String>) response.getRequiredData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.core.io.support.ResourcePropertySource;
|
import org.springframework.core.io.support.ResourcePropertySource;
|
||||||
import org.springframework.vault.client.RestTemplateFactory;
|
import org.springframework.vault.client.RestTemplateFactory;
|
||||||
import org.springframework.vault.config.EnvironmentVaultConfiguration;
|
import org.springframework.vault.config.EnvironmentVaultConfiguration;
|
||||||
|
import org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend;
|
||||||
import org.springframework.vault.support.ClientOptions;
|
import org.springframework.vault.support.ClientOptions;
|
||||||
import org.springframework.vault.support.SslConfiguration;
|
import org.springframework.vault.support.SslConfiguration;
|
||||||
|
|
||||||
|
@ -37,10 +38,14 @@ import java.util.concurrent.TimeUnit;
|
||||||
* A Vault configuration that uses the NiFiVaultEnvironment.
|
* A Vault configuration that uses the NiFiVaultEnvironment.
|
||||||
*/
|
*/
|
||||||
public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration {
|
public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration {
|
||||||
|
private static final int KV_V1 = 1;
|
||||||
|
private static final int KV_V2 = 2;
|
||||||
|
|
||||||
public enum VaultConfigurationKey {
|
public enum VaultConfigurationKey {
|
||||||
AUTHENTICATION_PROPERTIES_FILE("vault.authentication.properties.file"),
|
AUTHENTICATION_PROPERTIES_FILE("vault.authentication.properties.file"),
|
||||||
READ_TIMEOUT("vault.read.timeout"),
|
READ_TIMEOUT("vault.read.timeout"),
|
||||||
CONNECTION_TIMEOUT("vault.connection.timeout"),
|
CONNECTION_TIMEOUT("vault.connection.timeout"),
|
||||||
|
KV_VERSION("vault.kv.version"),
|
||||||
URI("vault.uri");
|
URI("vault.uri");
|
||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
|
@ -62,6 +67,7 @@ public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration {
|
||||||
|
|
||||||
private final SslConfiguration sslConfiguration;
|
private final SslConfiguration sslConfiguration;
|
||||||
private final ClientOptions clientOptions;
|
private final ClientOptions clientOptions;
|
||||||
|
private final KeyValueBackend keyValueBackend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a HashiCorpVaultConfiguration from property sources
|
* Creates a HashiCorpVaultConfiguration from property sources
|
||||||
|
@ -84,6 +90,22 @@ public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyValueBackend keyValueBackend = KeyValueBackend.KV_1;
|
||||||
|
if (env.containsProperty(VaultConfigurationKey.KV_VERSION.key)) {
|
||||||
|
final String kvVersion = env.getProperty(VaultConfigurationKey.KV_VERSION.key);
|
||||||
|
try {
|
||||||
|
int kvVersionNumber = Integer.parseInt(kvVersion);
|
||||||
|
if (kvVersionNumber == KV_V2) {
|
||||||
|
keyValueBackend = KeyValueBackend.KV_2;
|
||||||
|
} else if (kvVersionNumber != KV_V1) {
|
||||||
|
throw new IllegalArgumentException("K/V v" + kvVersion + " is not recognized");
|
||||||
|
}
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
throw new HashiCorpVaultConfigurationException("Unrecognized " + VaultConfigurationKey.KV_VERSION.key + ": " + kvVersion, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.keyValueBackend = keyValueBackend;
|
||||||
|
|
||||||
this.setApplicationContext(new HashiCorpVaultApplicationContext(env));
|
this.setApplicationContext(new HashiCorpVaultApplicationContext(env));
|
||||||
|
|
||||||
sslConfiguration = env.getProperty(VaultConfigurationKey.URI.key).contains(HTTPS)
|
sslConfiguration = env.getProperty(VaultConfigurationKey.URI.key).contains(HTTPS)
|
||||||
|
@ -92,6 +114,10 @@ public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration {
|
||||||
clientOptions = getClientOptions();
|
clientOptions = getClientOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyValueBackend getKeyValueBackend() {
|
||||||
|
return keyValueBackend;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convenience method to create a PropertySource from a file on disk.
|
* A convenience method to create a PropertySource from a file on disk.
|
||||||
* @param filename The properties filename.
|
* @param filename The properties filename.
|
||||||
|
|
|
@ -38,26 +38,27 @@ public class HashiCorpVaultProperties {
|
||||||
private final HashiCorpVaultSslProperties ssl;
|
private final HashiCorpVaultSslProperties ssl;
|
||||||
private final Optional<String> connectionTimeout;
|
private final Optional<String> connectionTimeout;
|
||||||
private final Optional<String> readTimeout;
|
private final Optional<String> readTimeout;
|
||||||
|
private final int kvVersion;
|
||||||
|
|
||||||
private HashiCorpVaultProperties(final String uri, String keyStore, final String keyStoreType, final String keyStorePassword, final String trustStore,
|
private HashiCorpVaultProperties(final HashiCorpVaultPropertiesBuilder builder) {
|
||||||
final String trustStoreType, final String trustStorePassword, final String authPropertiesFilename,
|
this.uri = Objects.requireNonNull(builder.uri, "Vault URI is required");;
|
||||||
final String enabledTlsCipherSuites, final String enabledTlsProtocols, final String connectionTimeout, final String readTimeout) {
|
this.authPropertiesFilename = Objects.requireNonNull(builder.authPropertiesFilename, "Vault auth properties filename is required");
|
||||||
Objects.requireNonNull(uri, "Vault URI is required");
|
this.ssl = new HashiCorpVaultSslProperties(builder.keyStore, builder.keyStoreType, builder.keyStorePassword,
|
||||||
Objects.requireNonNull(authPropertiesFilename, "Vault auth properties filename is required");
|
builder.trustStore, builder.trustStoreType, builder.trustStorePassword,builder.enabledTlsCipherSuites, builder.enabledTlsProtocols);
|
||||||
this.uri = uri;
|
this.connectionTimeout = builder.connectionTimeout == null ? Optional.empty() : Optional.of(builder.connectionTimeout);
|
||||||
this.authPropertiesFilename = authPropertiesFilename;
|
this.readTimeout = builder.readTimeout == null ? Optional.empty() : Optional.of(builder.readTimeout);
|
||||||
this.ssl = new HashiCorpVaultSslProperties(keyStore, keyStoreType, keyStorePassword, trustStore, trustStoreType, trustStorePassword,
|
this.kvVersion = builder.kvVersion;
|
||||||
enabledTlsCipherSuites, enabledTlsProtocols);
|
if (kvVersion != 1 && kvVersion != 2) {
|
||||||
this.connectionTimeout = connectionTimeout == null ? Optional.empty() : Optional.of(connectionTimeout);
|
throw new HashiCorpVaultConfigurationException("Key/Value version " + kvVersion + " is not supported");
|
||||||
this.readTimeout = readTimeout == null ? Optional.empty() : Optional.of(readTimeout);
|
}
|
||||||
|
|
||||||
if (uri.startsWith(HTTPS)) {
|
if (uri.startsWith(HTTPS)) {
|
||||||
Objects.requireNonNull(keyStore, "KeyStore is required with an https URI");
|
Objects.requireNonNull(builder.keyStore, "KeyStore is required with an https URI");
|
||||||
Objects.requireNonNull(keyStorePassword, "KeyStore password is required with an https URI");
|
Objects.requireNonNull(builder.keyStorePassword, "KeyStore password is required with an https URI");
|
||||||
Objects.requireNonNull(keyStoreType, "KeyStore type is required with an https URI");
|
Objects.requireNonNull(builder.keyStoreType, "KeyStore type is required with an https URI");
|
||||||
Objects.requireNonNull(trustStore, "TrustStore is required with an https URI");
|
Objects.requireNonNull(builder.trustStore, "TrustStore is required with an https URI");
|
||||||
Objects.requireNonNull(trustStorePassword, "TrustStore password is required with an https URI");
|
Objects.requireNonNull(builder.trustStorePassword, "TrustStore password is required with an https URI");
|
||||||
Objects.requireNonNull(trustStoreType, "TrustStore type is required with an https URI");
|
Objects.requireNonNull(builder.trustStoreType, "TrustStore type is required with an https URI");
|
||||||
}
|
}
|
||||||
validateAuthProperties();
|
validateAuthProperties();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +80,11 @@ public class HashiCorpVaultProperties {
|
||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HashiCorpVaultProperty(key = "kv.version")
|
||||||
|
public int getKvVersion() {
|
||||||
|
return kvVersion;
|
||||||
|
}
|
||||||
|
|
||||||
@HashiCorpVaultProperty(key = "authentication.properties.file")
|
@HashiCorpVaultProperty(key = "authentication.properties.file")
|
||||||
public String getAuthPropertiesFilename() {
|
public String getAuthPropertiesFilename() {
|
||||||
return authPropertiesFilename;
|
return authPropertiesFilename;
|
||||||
|
@ -108,6 +114,7 @@ public class HashiCorpVaultProperties {
|
||||||
private String enabledTlsProtocols;
|
private String enabledTlsProtocols;
|
||||||
private String connectionTimeout;
|
private String connectionTimeout;
|
||||||
private String readTimeout;
|
private String readTimeout;
|
||||||
|
private int kvVersion = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Vault URI (e.g., http://localhost:8200). If using https protocol, the KeyStore and TrustStore
|
* Set the Vault URI (e.g., http://localhost:8200). If using https protocol, the KeyStore and TrustStore
|
||||||
|
@ -120,6 +127,16 @@ public class HashiCorpVaultProperties {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Key/Value secrets engine version (1 or 2).
|
||||||
|
* @param kvVersion The Key/Value engine version
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public HashiCorpVaultPropertiesBuilder setKvVersion(int kvVersion) {
|
||||||
|
this.kvVersion = kvVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the path to the keyStore.
|
* Sets the path to the keyStore.
|
||||||
* @param keyStore Path to the keyStore
|
* @param keyStore Path to the keyStore
|
||||||
|
@ -240,8 +257,7 @@ public class HashiCorpVaultProperties {
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
public HashiCorpVaultProperties build() {
|
public HashiCorpVaultProperties build() {
|
||||||
return new HashiCorpVaultProperties(uri, keyStore, keyStoreType, keyStorePassword, trustStore, trustStoreType,
|
return new HashiCorpVaultProperties(this);
|
||||||
trustStorePassword, authPropertiesFilename, enabledTlsCipherSuites, enabledTlsProtocols, connectionTimeout, readTimeout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,11 +44,16 @@ public class BeanPropertyLookup extends PropertyLookup {
|
||||||
.filter(pd -> pd.getReadMethod().getAnnotation(HashiCorpVaultProperty.class) != null)
|
.filter(pd -> pd.getReadMethod().getAnnotation(HashiCorpVaultProperty.class) != null)
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
pd -> getPropertyKey(prefix, pd),
|
pd -> getPropertyKey(prefix, pd),
|
||||||
pd -> pd.getReadMethod().getReturnType().equals(String.class) ? new ValuePropertyLookup(pd)
|
pd -> isValueProperty(pd) ? new ValuePropertyLookup(pd)
|
||||||
: new BeanPropertyLookup(getPropertyKey(prefix, pd), pd.getReadMethod().getReturnType(), pd)
|
: new BeanPropertyLookup(getPropertyKey(prefix, pd), pd.getReadMethod().getReturnType(), pd)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isValueProperty(final PropertyDescriptor propertyDescriptor) {
|
||||||
|
final Class<?> returnType = propertyDescriptor.getReadMethod().getReturnType();
|
||||||
|
return returnType.equals(String.class) || returnType.isPrimitive();
|
||||||
|
}
|
||||||
|
|
||||||
private static String getPropertyKey(final String prefix, final PropertyDescriptor propertyDescriptor) {
|
private static String getPropertyKey(final String prefix, final PropertyDescriptor propertyDescriptor) {
|
||||||
final HashiCorpVaultProperty propertyAnnotation = propertyDescriptor.getReadMethod().getAnnotation(HashiCorpVaultProperty.class);
|
final HashiCorpVaultProperty propertyAnnotation = propertyDescriptor.getReadMethod().getAnnotation(HashiCorpVaultProperty.class);
|
||||||
final String unqualifiedPropertyKey = !propertyAnnotation.key().isEmpty() ? propertyAnnotation.key() : propertyDescriptor.getDisplayName();
|
final String unqualifiedPropertyKey = !propertyAnnotation.key().isEmpty() ? propertyAnnotation.key() : propertyDescriptor.getDisplayName();
|
||||||
|
|
|
@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
* vault server -dev
|
* vault server -dev
|
||||||
* vault secrets enable transit
|
* vault secrets enable transit
|
||||||
* vault secrets enable kv
|
* vault secrets enable kv
|
||||||
|
* vault secrets enable kv-v2
|
||||||
* vault write -f transit/keys/nifi
|
* vault write -f transit/keys/nifi
|
||||||
*
|
*
|
||||||
* Make note of the Root Token and create a properties file with the contents:
|
* Make note of the Root Token and create a properties file with the contents:
|
||||||
|
@ -48,10 +49,13 @@ public class StandardHashiCorpVaultCommunicationServiceIT {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void init() {
|
public void init() {
|
||||||
vcs = new StandardHashiCorpVaultCommunicationService(new HashiCorpVaultProperties.HashiCorpVaultPropertiesBuilder()
|
vcs = new StandardHashiCorpVaultCommunicationService(defaultServiceBuilder().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashiCorpVaultProperties.HashiCorpVaultPropertiesBuilder defaultServiceBuilder() {
|
||||||
|
return new HashiCorpVaultProperties.HashiCorpVaultPropertiesBuilder()
|
||||||
.setAuthPropertiesFilename(System.getProperty("vault.auth.properties"))
|
.setAuthPropertiesFilename(System.getProperty("vault.auth.properties"))
|
||||||
.setUri("http://127.0.0.1:8200")
|
.setUri("http://127.0.0.1:8200");
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,6 +87,22 @@ public class StandardHashiCorpVaultCommunicationServiceIT {
|
||||||
assertEquals(value, resultValue);
|
assertEquals(value, resultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run <code>vault kv get kv_v2/key</code> to see the secret
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testReadWriteSecret_kv_v2() {
|
||||||
|
final String key = "key";
|
||||||
|
final String value = "value";
|
||||||
|
|
||||||
|
vcs = new StandardHashiCorpVaultCommunicationService(defaultServiceBuilder().setKvVersion(2).build());
|
||||||
|
|
||||||
|
vcs.writeKeyValueSecret("kv-v2", key, value);
|
||||||
|
|
||||||
|
final String resultValue = vcs.readKeyValueSecret("kv-v2", key).orElseThrow(() -> new NullPointerException("Missing secret for kv/key"));
|
||||||
|
assertEquals(value, resultValue);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run <code>vault kv get kv/secret</code> to see the secret
|
* Run <code>vault kv get kv/secret</code> to see the secret
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.vault.authentication.ClientAuthentication;
|
import org.springframework.vault.authentication.ClientAuthentication;
|
||||||
import org.springframework.vault.client.VaultEndpoint;
|
import org.springframework.vault.client.VaultEndpoint;
|
||||||
|
import org.springframework.vault.core.VaultKeyValueOperationsSupport;
|
||||||
import org.springframework.vault.support.SslConfiguration;
|
import org.springframework.vault.support.SslConfiguration;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -134,6 +135,29 @@ public class TestHashiCorpVaultConfiguration {
|
||||||
this.runTest("http");
|
this.runTest("http");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKvVersion() {
|
||||||
|
config = new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build()));
|
||||||
|
assertEquals(VaultKeyValueOperationsSupport.KeyValueBackend.KV_1, config.getKeyValueBackend());
|
||||||
|
|
||||||
|
propertiesBuilder.setKvVersion(2);
|
||||||
|
config = new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build()));
|
||||||
|
assertEquals(VaultKeyValueOperationsSupport.KeyValueBackend.KV_2, config.getKeyValueBackend());
|
||||||
|
|
||||||
|
propertiesBuilder.setKvVersion(1);
|
||||||
|
config = new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build()));
|
||||||
|
assertEquals(VaultKeyValueOperationsSupport.KeyValueBackend.KV_1, config.getKeyValueBackend());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKvVersionInvalid() {
|
||||||
|
propertiesBuilder.setKvVersion(0);
|
||||||
|
assertThrows(HashiCorpVaultConfigurationException.class, () -> new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build())));
|
||||||
|
|
||||||
|
propertiesBuilder.setKvVersion(3);
|
||||||
|
assertThrows(HashiCorpVaultConfigurationException.class, () -> new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build())));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTlsProperties() throws IOException {
|
public void testTlsProperties() throws IOException {
|
||||||
propertiesBuilder.setKeyStore(keystoreFile.toFile().getAbsolutePath());
|
propertiesBuilder.setKeyStore(keystoreFile.toFile().getAbsolutePath());
|
||||||
|
|
|
@ -32,6 +32,8 @@ import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class TestStandardHashiCorpVaultCommunicationService {
|
public class TestStandardHashiCorpVaultCommunicationService {
|
||||||
public static final String URI_VALUE = "http://127.0.0.1:8200";
|
public static final String URI_VALUE = "http://127.0.0.1:8200";
|
||||||
public static final String CIPHER_SUITE_VALUE = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
|
public static final String CIPHER_SUITE_VALUE = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
|
||||||
|
@ -47,9 +49,10 @@ public class TestStandardHashiCorpVaultCommunicationService {
|
||||||
properties = Mockito.mock(HashiCorpVaultProperties.class);
|
properties = Mockito.mock(HashiCorpVaultProperties.class);
|
||||||
sslProperties = Mockito.mock(HashiCorpVaultSslProperties.class);
|
sslProperties = Mockito.mock(HashiCorpVaultSslProperties.class);
|
||||||
|
|
||||||
Mockito.when(properties.getUri()).thenReturn(URI_VALUE);
|
when(properties.getUri()).thenReturn(URI_VALUE);
|
||||||
Mockito.when(properties.getAuthPropertiesFilename()).thenReturn(authProps.getAbsolutePath());
|
when(properties.getAuthPropertiesFilename()).thenReturn(authProps.getAbsolutePath());
|
||||||
Mockito.when(properties.getSsl()).thenReturn(sslProperties);
|
when(properties.getSsl()).thenReturn(sslProperties);
|
||||||
|
when(properties.getKvVersion()).thenReturn(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
@ -88,8 +91,8 @@ public class TestStandardHashiCorpVaultCommunicationService {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTimeouts() {
|
public void testTimeouts() {
|
||||||
Mockito.when(properties.getConnectionTimeout()).thenReturn(Optional.of("20 secs"));
|
when(properties.getConnectionTimeout()).thenReturn(Optional.of("20 secs"));
|
||||||
Mockito.when(properties.getReadTimeout()).thenReturn(Optional.of("40 secs"));
|
when(properties.getReadTimeout()).thenReturn(Optional.of("40 secs"));
|
||||||
this.configureService();
|
this.configureService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,17 +100,17 @@ public class TestStandardHashiCorpVaultCommunicationService {
|
||||||
public void testTLS() {
|
public void testTLS() {
|
||||||
TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
||||||
|
|
||||||
Mockito.when(sslProperties.getKeyStore()).thenReturn(tlsConfiguration.getKeystorePath());
|
when(sslProperties.getKeyStore()).thenReturn(tlsConfiguration.getKeystorePath());
|
||||||
Mockito.when(sslProperties.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword());
|
when(sslProperties.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword());
|
||||||
Mockito.when(sslProperties.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType());
|
when(sslProperties.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType());
|
||||||
Mockito.when(sslProperties.getTrustStore()).thenReturn(tlsConfiguration.getTruststorePath());
|
when(sslProperties.getTrustStore()).thenReturn(tlsConfiguration.getTruststorePath());
|
||||||
Mockito.when(sslProperties.getTrustStorePassword()).thenReturn(tlsConfiguration.getTruststorePassword());
|
when(sslProperties.getTrustStorePassword()).thenReturn(tlsConfiguration.getTruststorePassword());
|
||||||
Mockito.when(sslProperties.getTrustStoreType()).thenReturn(tlsConfiguration.getTruststoreType().getType());
|
when(sslProperties.getTrustStoreType()).thenReturn(tlsConfiguration.getTruststoreType().getType());
|
||||||
Mockito.when(sslProperties.getEnabledProtocols()).thenReturn(Arrays.stream(tlsConfiguration.getEnabledProtocols())
|
when(sslProperties.getEnabledProtocols()).thenReturn(Arrays.stream(tlsConfiguration.getEnabledProtocols())
|
||||||
.collect(Collectors.joining(",")));
|
.collect(Collectors.joining(",")));
|
||||||
Mockito.when(sslProperties.getEnabledCipherSuites()).thenReturn(CIPHER_SUITE_VALUE);
|
when(sslProperties.getEnabledCipherSuites()).thenReturn(CIPHER_SUITE_VALUE);
|
||||||
|
|
||||||
Mockito.when(properties.getUri()).thenReturn(URI_VALUE.replace("http", "https"));
|
when(properties.getUri()).thenReturn(URI_VALUE.replace("http", "https"));
|
||||||
this.configureService();
|
this.configureService();
|
||||||
|
|
||||||
this.ensureTlsPropertiesAccessed(1);
|
this.ensureTlsPropertiesAccessed(1);
|
||||||
|
|
|
@ -1852,6 +1852,7 @@ Following are the configuration properties available inside the `bootstrap-hashi
|
||||||
[options="header,footer"]
|
[options="header,footer"]
|
||||||
|===
|
|===
|
||||||
|Property Name|Description|Default
|
|Property Name|Description|Default
|
||||||
|
|`vault.kv.version`|The Key/Value Secrets Engine version: `1` for unversioned, and `2` for versioned. This must match the versioned enabled in Vault.|`1`
|
||||||
|`vault.connection.timeout`|The connection timeout of the Vault client|`5 secs`
|
|`vault.connection.timeout`|The connection timeout of the Vault client|`5 secs`
|
||||||
|`vault.read.timeout`|The read timeout of the Vault client|`15 secs`
|
|`vault.read.timeout`|The read timeout of the Vault client|`15 secs`
|
||||||
|`vault.ssl.enabledCipherSuites`|A comma-separated list of the enabled TLS cipher suites|_none_
|
|`vault.ssl.enabledCipherSuites`|A comma-separated list of the enabled TLS cipher suites|_none_
|
||||||
|
|
|
@ -23,6 +23,8 @@ vault.transit.path=
|
||||||
|
|
||||||
# Key/Value Path is required to enable the Sensitive Properties Provider Protection Scheme 'hashicorp/vault/kv/{path}'
|
# Key/Value Path is required to enable the Sensitive Properties Provider Protection Scheme 'hashicorp/vault/kv/{path}'
|
||||||
vault.kv.path=
|
vault.kv.path=
|
||||||
|
# Key/Value Secrets Engine version may be 1 or 2, and defaults to 1
|
||||||
|
# vault.kv.version=1
|
||||||
|
|
||||||
# Token Authentication example properties
|
# Token Authentication example properties
|
||||||
# vault.authentication=TOKEN
|
# vault.authentication=TOKEN
|
||||||
|
|
|
@ -23,6 +23,8 @@ vault.transit.path=
|
||||||
|
|
||||||
# Key/Value Path is required to enable the Sensitive Properties Provider Protection Scheme 'hashicorp/vault/kv/{path}'
|
# Key/Value Path is required to enable the Sensitive Properties Provider Protection Scheme 'hashicorp/vault/kv/{path}'
|
||||||
vault.kv.path=
|
vault.kv.path=
|
||||||
|
# Key/Value Secrets Engine version may be 1 or 2, and defaults to 1
|
||||||
|
# vault.kv.version=1
|
||||||
|
|
||||||
# Token Authentication example properties
|
# Token Authentication example properties
|
||||||
# vault.authentication=TOKEN
|
# vault.authentication=TOKEN
|
||||||
|
|
Loading…
Reference in New Issue