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

bbende pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 48de70a568 NIFI-10071: Adding support for HashiCorp Vault K/V version 
2 Secrets Engine (#6087)
48de70a568 is described below

commit 48de70a56883c69a7bc51545c7cd410563b995ec
Author: Joe Gresock <jgres...@gmail.com>
AuthorDate: Wed Jun 1 15:09:01 2022 -0400

    NIFI-10071: Adding support for HashiCorp Vault K/V version 2 Secrets Engine 
(#6087)
---
 .../HashiCorpVaultCommunicationService.java        | 24 +++++-----
 ...StandardHashiCorpVaultCommunicationService.java | 13 +++---
 .../config/HashiCorpVaultConfiguration.java        | 26 +++++++++++
 .../hashicorp/config/HashiCorpVaultProperties.java | 54 ++++++++++++++--------
 .../config/lookup/BeanPropertyLookup.java          |  7 ++-
 ...andardHashiCorpVaultCommunicationServiceIT.java | 26 +++++++++--
 .../hashicorp/TestHashiCorpVaultConfiguration.java | 24 ++++++++++
 ...StandardHashiCorpVaultCommunicationService.java | 31 +++++++------
 .../src/main/asciidoc/administration-guide.adoc    |  1 +
 .../resources/conf/bootstrap-hashicorp-vault.conf  |  2 +
 .../resources/conf/bootstrap-hashicorp-vault.conf  |  2 +
 11 files changed, 155 insertions(+), 55 deletions(-)

diff --git 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
index 8e9f8c5594..840db72ffa 100644
--- 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
+++ 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
@@ -46,39 +46,39 @@ public interface HashiCorpVaultCommunicationService {
     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>
-     * @param keyValuePath The Vault path to use for the configured Key/Value 
v1 Secrets Engine
+     * @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 
Secrets Engine
      * @param secretKey The secret key
      * @param value The secret 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>
-     * @param keyValuePath The Vault path to use for the configured Key/Value 
v1 Secrets Engine
+     * @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 
Secrets Engine
      * @param secretKey The secret key
      * @return The secret value, or empty if not found
      */
     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>
-     * @param keyValuePath The Vault path to use for the configured Key/Value 
v1 Secrets Engine
+     * @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 
Secrets Engine
      * @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);
 
     /**
-     * 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>
-     * @param keyValuePath The Vault path to use for the configured Key/Value 
v1 Secrets Engine
+     * @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 
Secrets Engine
      * @param secretKey The secret key
      * @return A map from key to value from the secret key/values, or an empty 
map if not found
      */
diff --git 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
index 34508436d3..a407b85eed 100644
--- 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
+++ 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
@@ -25,6 +25,7 @@ import org.springframework.core.env.PropertySource;
 import org.springframework.vault.authentication.SimpleSessionManager;
 import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
 import org.springframework.vault.core.VaultKeyValueOperations;
+import 
org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend;
 import org.springframework.vault.core.VaultTemplate;
 import org.springframework.vault.core.VaultTransitOperations;
 import org.springframework.vault.support.Ciphertext;
@@ -37,8 +38,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 
-import static 
org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend.KV_1;
-
 /**
  * Implements the VaultCommunicationService using Spring Vault
  */
@@ -46,6 +45,7 @@ public class StandardHashiCorpVaultCommunicationService 
implements HashiCorpVaul
     private final VaultTemplate vaultTemplate;
     private final VaultTransitOperations transitOperations;
     private final Map<String, VaultKeyValueOperations> keyValueOperationsMap;
+    private final KeyValueBackend keyValueBackend;
 
     /**
      * Creates a VaultCommunicationService that uses Spring Vault.
@@ -60,6 +60,7 @@ public class StandardHashiCorpVaultCommunicationService 
implements HashiCorpVaul
                 new 
SimpleSessionManager(vaultConfiguration.clientAuthentication()));
 
         transitOperations = vaultTemplate.opsForTransit();
+        keyValueBackend = vaultConfiguration.getKeyValueBackend();
         keyValueOperationsMap = new HashMap<>();
     }
 
@@ -94,7 +95,7 @@ public class StandardHashiCorpVaultCommunicationService 
implements HashiCorpVaul
         Objects.requireNonNull(secretKey, "Secret secretKey must be 
specified");
         Objects.requireNonNull(value, "Secret value must be specified");
         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));
     }
 
@@ -109,7 +110,7 @@ public class StandardHashiCorpVaultCommunicationService 
implements HashiCorpVaul
         Objects.requireNonNull(keyValuePath, "Vault K/V path must be 
specified");
         Objects.requireNonNull(secretKey, "Secret secretKey must be 
specified");
         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);
         return response == null ? Optional.empty() : 
Optional.ofNullable(response.getRequiredData().getValue());
     }
@@ -123,14 +124,14 @@ public class StandardHashiCorpVaultCommunicationService 
implements HashiCorpVaul
             return;
         }
         final VaultKeyValueOperations keyValueOperations = 
keyValueOperationsMap
-                .computeIfAbsent(keyValuePath, path -> 
vaultTemplate.opsForKeyValue(path, KV_1));
+                .computeIfAbsent(keyValuePath, path -> 
vaultTemplate.opsForKeyValue(path, keyValueBackend));
         keyValueOperations.put(secretKey, keyValues);
     }
 
     @Override
     public Map<String, String> readKeyValueSecretMap(final String 
keyValuePath, final String key) {
         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);
         return response == null ? Collections.emptyMap() : (Map<String, 
String>) response.getRequiredData();
     }
diff --git 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
index 34ce6c68a9..873477c097 100644
--- 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
+++ 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
@@ -25,6 +25,7 @@ import org.springframework.core.io.FileSystemResource;
 import org.springframework.core.io.support.ResourcePropertySource;
 import org.springframework.vault.client.RestTemplateFactory;
 import org.springframework.vault.config.EnvironmentVaultConfiguration;
+import 
org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend;
 import org.springframework.vault.support.ClientOptions;
 import org.springframework.vault.support.SslConfiguration;
 
@@ -37,10 +38,14 @@ import java.util.concurrent.TimeUnit;
  * A Vault configuration that uses the NiFiVaultEnvironment.
  */
 public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration 
{
+    private static final int KV_V1 = 1;
+    private static final int KV_V2 = 2;
+
     public enum VaultConfigurationKey {
         AUTHENTICATION_PROPERTIES_FILE("vault.authentication.properties.file"),
         READ_TIMEOUT("vault.read.timeout"),
         CONNECTION_TIMEOUT("vault.connection.timeout"),
+        KV_VERSION("vault.kv.version"),
         URI("vault.uri");
 
         private final String key;
@@ -62,6 +67,7 @@ public class HashiCorpVaultConfiguration extends 
EnvironmentVaultConfiguration {
 
     private final SslConfiguration sslConfiguration;
     private final ClientOptions clientOptions;
+    private final KeyValueBackend keyValueBackend;
 
     /**
      * 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));
 
         sslConfiguration = 
env.getProperty(VaultConfigurationKey.URI.key).contains(HTTPS)
@@ -92,6 +114,10 @@ public class HashiCorpVaultConfiguration extends 
EnvironmentVaultConfiguration {
         clientOptions = getClientOptions();
     }
 
+    public KeyValueBackend getKeyValueBackend() {
+        return keyValueBackend;
+    }
+
     /**
      * A convenience method to create a PropertySource from a file on disk.
      * @param filename The properties filename.
diff --git 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
index 99b6ff13a9..5042dd3593 100644
--- 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
+++ 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
@@ -38,26 +38,27 @@ public class HashiCorpVaultProperties {
     private final HashiCorpVaultSslProperties ssl;
     private final Optional<String> connectionTimeout;
     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,
-                                     final String trustStoreType, final String 
trustStorePassword, final String authPropertiesFilename,
-                                     final String enabledTlsCipherSuites, 
final String enabledTlsProtocols, final String connectionTimeout, final String 
readTimeout) {
-        Objects.requireNonNull(uri, "Vault URI is required");
-        Objects.requireNonNull(authPropertiesFilename, "Vault auth properties 
filename is required");
-        this.uri = uri;
-        this.authPropertiesFilename = authPropertiesFilename;
-        this.ssl = new HashiCorpVaultSslProperties(keyStore, keyStoreType, 
keyStorePassword, trustStore, trustStoreType, trustStorePassword,
-                enabledTlsCipherSuites, enabledTlsProtocols);
-        this.connectionTimeout = connectionTimeout == null ? Optional.empty() 
: Optional.of(connectionTimeout);
-        this.readTimeout = readTimeout == null ? Optional.empty() : 
Optional.of(readTimeout);
+    private HashiCorpVaultProperties(final HashiCorpVaultPropertiesBuilder 
builder) {
+        this.uri =  Objects.requireNonNull(builder.uri, "Vault URI is 
required");;
+        this.authPropertiesFilename = 
Objects.requireNonNull(builder.authPropertiesFilename, "Vault auth properties 
filename is required");
+        this.ssl = new HashiCorpVaultSslProperties(builder.keyStore, 
builder.keyStoreType, builder.keyStorePassword,
+                builder.trustStore, builder.trustStoreType, 
builder.trustStorePassword,builder.enabledTlsCipherSuites, 
builder.enabledTlsProtocols);
+        this.connectionTimeout = builder.connectionTimeout == null ? 
Optional.empty() : Optional.of(builder.connectionTimeout);
+        this.readTimeout = builder.readTimeout == null ? Optional.empty() : 
Optional.of(builder.readTimeout);
+        this.kvVersion = builder.kvVersion;
+        if (kvVersion != 1 && kvVersion != 2) {
+            throw new HashiCorpVaultConfigurationException("Key/Value version 
" + kvVersion + " is not supported");
+        }
 
         if (uri.startsWith(HTTPS)) {
-            Objects.requireNonNull(keyStore, "KeyStore is required with an 
https URI");
-            Objects.requireNonNull(keyStorePassword, "KeyStore password is 
required with an https URI");
-            Objects.requireNonNull(keyStoreType, "KeyStore type is required 
with an https URI");
-            Objects.requireNonNull(trustStore, "TrustStore is required with an 
https URI");
-            Objects.requireNonNull(trustStorePassword, "TrustStore password is 
required with an https URI");
-            Objects.requireNonNull(trustStoreType, "TrustStore type is 
required with an https URI");
+            Objects.requireNonNull(builder.keyStore, "KeyStore is required 
with an https URI");
+            Objects.requireNonNull(builder.keyStorePassword, "KeyStore 
password is required with an https URI");
+            Objects.requireNonNull(builder.keyStoreType, "KeyStore type is 
required with an https URI");
+            Objects.requireNonNull(builder.trustStore, "TrustStore is required 
with an https URI");
+            Objects.requireNonNull(builder.trustStorePassword, "TrustStore 
password is required with an https URI");
+            Objects.requireNonNull(builder.trustStoreType, "TrustStore type is 
required with an https URI");
         }
         validateAuthProperties();
     }
@@ -79,6 +80,11 @@ public class HashiCorpVaultProperties {
         return ssl;
     }
 
+    @HashiCorpVaultProperty(key = "kv.version")
+    public int getKvVersion() {
+        return kvVersion;
+    }
+
     @HashiCorpVaultProperty(key = "authentication.properties.file")
     public String getAuthPropertiesFilename() {
         return authPropertiesFilename;
@@ -108,6 +114,7 @@ public class HashiCorpVaultProperties {
         private String enabledTlsProtocols;
         private String connectionTimeout;
         private String readTimeout;
+        private int kvVersion = 1;
 
         /**
          * 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;
         }
 
+        /**
+         * 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.
          * @param keyStore Path to the keyStore
@@ -240,8 +257,7 @@ public class HashiCorpVaultProperties {
          * @return Builder
          */
         public HashiCorpVaultProperties build() {
-            return new HashiCorpVaultProperties(uri, keyStore, keyStoreType, 
keyStorePassword, trustStore, trustStoreType,
-                    trustStorePassword, authPropertiesFilename, 
enabledTlsCipherSuites, enabledTlsProtocols, connectionTimeout, readTimeout);
+            return new HashiCorpVaultProperties(this);
         }
     }
 }
diff --git 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
index da873afa24..1b7bab2a33 100644
--- 
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
+++ 
b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
@@ -44,11 +44,16 @@ public class BeanPropertyLookup extends PropertyLookup {
                 .filter(pd -> 
pd.getReadMethod().getAnnotation(HashiCorpVaultProperty.class) != null)
                 .collect(Collectors.toMap(
                         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)
                 ));
     }
 
+    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) {
         final HashiCorpVaultProperty propertyAnnotation = 
propertyDescriptor.getReadMethod().getAnnotation(HashiCorpVaultProperty.class);
         final String unqualifiedPropertyKey = 
!propertyAnnotation.key().isEmpty() ? propertyAnnotation.key() : 
propertyDescriptor.getDisplayName();
diff --git 
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
 
b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
index 6261376dcd..2080e147a8 100644
--- 
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
+++ 
b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
@@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
  * vault server -dev
  * vault secrets enable transit
  * vault secrets enable kv
+ * vault secrets enable kv-v2
  * vault write -f transit/keys/nifi
  *
  * Make note of the Root Token and create a properties file with the contents:
@@ -48,10 +49,13 @@ public class StandardHashiCorpVaultCommunicationServiceIT {
 
     @BeforeEach
     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"))
-                .setUri("http://127.0.0.1:8200";)
-                .build());
+                .setUri("http://127.0.0.1:8200";);
     }
 
     @Test
@@ -83,6 +87,22 @@ public class StandardHashiCorpVaultCommunicationServiceIT {
         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
      */
diff --git 
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
 
b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
index 9dd75f9b6f..190b9aa6ce 100644
--- 
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
+++ 
b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.vault.authentication.ClientAuthentication;
 import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.core.VaultKeyValueOperationsSupport;
 import org.springframework.vault.support.SslConfiguration;
 
 import java.io.File;
@@ -134,6 +135,29 @@ public class TestHashiCorpVaultConfiguration {
         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
     public void testTlsProperties() throws IOException {
         propertiesBuilder.setKeyStore(keystoreFile.toFile().getAbsolutePath());
diff --git 
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
 
b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
index 4eb14eb79d..a24f13b28d 100644
--- 
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
+++ 
b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
@@ -32,6 +32,8 @@ import java.util.Arrays;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
+import static org.mockito.Mockito.when;
+
 public class TestStandardHashiCorpVaultCommunicationService {
     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";
@@ -47,9 +49,10 @@ public class TestStandardHashiCorpVaultCommunicationService {
         properties = Mockito.mock(HashiCorpVaultProperties.class);
         sslProperties = Mockito.mock(HashiCorpVaultSslProperties.class);
 
-        Mockito.when(properties.getUri()).thenReturn(URI_VALUE);
-        
Mockito.when(properties.getAuthPropertiesFilename()).thenReturn(authProps.getAbsolutePath());
-        Mockito.when(properties.getSsl()).thenReturn(sslProperties);
+        when(properties.getUri()).thenReturn(URI_VALUE);
+        
when(properties.getAuthPropertiesFilename()).thenReturn(authProps.getAbsolutePath());
+        when(properties.getSsl()).thenReturn(sslProperties);
+        when(properties.getKvVersion()).thenReturn(1);
     }
 
     @AfterEach
@@ -88,8 +91,8 @@ public class TestStandardHashiCorpVaultCommunicationService {
 
     @Test
     public void testTimeouts() {
-        
Mockito.when(properties.getConnectionTimeout()).thenReturn(Optional.of("20 
secs"));
-        Mockito.when(properties.getReadTimeout()).thenReturn(Optional.of("40 
secs"));
+        when(properties.getConnectionTimeout()).thenReturn(Optional.of("20 
secs"));
+        when(properties.getReadTimeout()).thenReturn(Optional.of("40 secs"));
         this.configureService();
     }
 
@@ -97,17 +100,17 @@ public class 
TestStandardHashiCorpVaultCommunicationService {
     public void testTLS() {
         TlsConfiguration tlsConfiguration = new 
TemporaryKeyStoreBuilder().build();
 
-        
Mockito.when(sslProperties.getKeyStore()).thenReturn(tlsConfiguration.getKeystorePath());
-        
Mockito.when(sslProperties.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword());
-        
Mockito.when(sslProperties.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType());
-        
Mockito.when(sslProperties.getTrustStore()).thenReturn(tlsConfiguration.getTruststorePath());
-        
Mockito.when(sslProperties.getTrustStorePassword()).thenReturn(tlsConfiguration.getTruststorePassword());
-        
Mockito.when(sslProperties.getTrustStoreType()).thenReturn(tlsConfiguration.getTruststoreType().getType());
-        
Mockito.when(sslProperties.getEnabledProtocols()).thenReturn(Arrays.stream(tlsConfiguration.getEnabledProtocols())
+        
when(sslProperties.getKeyStore()).thenReturn(tlsConfiguration.getKeystorePath());
+        
when(sslProperties.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword());
+        
when(sslProperties.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType());
+        
when(sslProperties.getTrustStore()).thenReturn(tlsConfiguration.getTruststorePath());
+        
when(sslProperties.getTrustStorePassword()).thenReturn(tlsConfiguration.getTruststorePassword());
+        
when(sslProperties.getTrustStoreType()).thenReturn(tlsConfiguration.getTruststoreType().getType());
+        
when(sslProperties.getEnabledProtocols()).thenReturn(Arrays.stream(tlsConfiguration.getEnabledProtocols())
                 .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.ensureTlsPropertiesAccessed(1);
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc 
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index dda5ef3e84..d61b2134ef 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1852,6 +1852,7 @@ Following are the configuration properties available 
inside the `bootstrap-hashi
 [options="header,footer"]
 |===
 |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.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_
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
index bbf54f57cf..bf6975b962 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
@@ -23,6 +23,8 @@ vault.transit.path=
 
 # Key/Value Path is required to enable the Sensitive Properties Provider 
Protection Scheme 'hashicorp/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
 # vault.authentication=TOKEN
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
 
b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
index bbf54f57cf..bf6975b962 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-hashicorp-vault.conf
@@ -23,6 +23,8 @@ vault.transit.path=
 
 # Key/Value Path is required to enable the Sensitive Properties Provider 
Protection Scheme 'hashicorp/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
 # vault.authentication=TOKEN

Reply via email to