This is an automated email from the ASF dual-hosted git repository.
exceptionfactory 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 3987d39cdc NIFI-9401 Added HashiCorpVaultParameterProvider
3987d39cdc is described below
commit 3987d39cdc9a9b6890df441edd134a5649a85548
Author: Joe Gresock <[email protected]>
AuthorDate: Fri Sep 9 14:19:46 2022 -0400
NIFI-9401 Added HashiCorpVaultParameterProvider
- Refactored nifi-vault-utils to nifi-hashicorp-vault-api and
nifi-hashcorp-vault modules
- Added HashiCorpVaultClientService and Standard implementation
This closes #6304
Signed-off-by: David Handermann <[email protected]>
---
nifi-assembly/pom.xml | 6 +
nifi-commons/nifi-hashicorp-vault-api/pom.xml | 24 +++
.../HashiCorpVaultCommunicationService.java | 12 ++
.../pom.xml | 8 +-
.../HashiCorpVaultConfigurationException.java | 0
...StandardHashiCorpVaultCommunicationService.java | 13 ++
.../config/HashiCorpVaultApplicationContext.java | 0
.../config/HashiCorpVaultConfiguration.java | 35 +++-
.../hashicorp/config/HashiCorpVaultProperties.java | 0
.../hashicorp/config/HashiCorpVaultProperty.java | 0
.../config/HashiCorpVaultPropertySource.java | 0
.../config/HashiCorpVaultSslProperties.java | 0
.../config/lookup/BeanPropertyLookup.java | 0
.../hashicorp/config/lookup/PropertyLookup.java | 0
.../config/lookup/ValuePropertyLookup.java | 0
...andardHashiCorpVaultCommunicationServiceIT.java | 0
.../hashicorp/TestHashiCorpVaultConfiguration.java | 0
...StandardHashiCorpVaultCommunicationService.java | 10 +-
.../nifi-property-protection-hashicorp/pom.xml | 7 +-
nifi-commons/pom.xml | 3 +-
.../pom.xml | 11 +-
.../pom.xml | 20 +-
.../hashicorp/HashiCorpVaultClientService.java | 115 +++++++++++
.../vault/hashicorp/VaultAuthenticationMethod.java | 23 +--
.../pom.xml | 25 ++-
.../StandardHashiCorpVaultClientService.java | 209 +++++++++++++++++++
.../org.apache.nifi.controller.ControllerService | 15 ++
.../additionalDetails.html | 133 ++++++++++++
.../TestStandardHashiCorpVaultClientService.java | 225 +++++++++++++++++++++
.../nifi-hashicorp-vault-nar/pom.xml | 22 ++
.../pom.xml | 23 ++-
.../hashicorp/HashiCorpVaultParameterProvider.java | 163 +++++++++++++++
.../org.apache.nifi.parameter.ParameterProvider | 15 ++
.../TestHashiCorpVaultParameterProvider.java | 162 +++++++++++++++
.../pom.xml | 8 +-
.../nifi-hashicorp-vault-bundle/pom.xml | 4 +
36 files changed, 1241 insertions(+), 50 deletions(-)
diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index c07683a50e..0baa9445d5 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -838,6 +838,12 @@ language governing permissions and limitations under the
License. -->
<version>1.18.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+
<artifactId>nifi-hashicorp-vault-client-service-api-nar</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-stateless-processor-nar</artifactId>
diff --git a/nifi-commons/nifi-hashicorp-vault-api/pom.xml
b/nifi-commons/nifi-hashicorp-vault-api/pom.xml
new file mode 100644
index 0000000000..6cbec0d5a1
--- /dev/null
+++ b/nifi-commons/nifi-hashicorp-vault-api/pom.xml
@@ -0,0 +1,24 @@
+<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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <!--
+ 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.
+ -->
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
+</project>
+
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
b/nifi-commons/nifi-hashicorp-vault-api/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
similarity index 91%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
rename to
nifi-commons/nifi-hashicorp-vault-api/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
index 840db72ffa..3d06270f7b 100644
---
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
+++
b/nifi-commons/nifi-hashicorp-vault-api/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.vault.hashicorp;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -24,6 +25,10 @@ import java.util.Optional;
* @see <a href="https://www.vaultproject.io/">https://www.vaultproject.io/</a>
*/
public interface HashiCorpVaultCommunicationService {
+ /**
+ * @return The HashiCorp Vault server version
+ */
+ String getServerVersion();
/**
* Encrypts the given plaintext using Vault's Transit Secrets Engine.
@@ -83,4 +88,11 @@ public interface HashiCorpVaultCommunicationService {
* @return A map from key to value from the secret key/values, or an empty
map if not found
*/
Map<String, String> readKeyValueSecretMap(String keyValuePath, String
secretKey);
+
+ /**
+ * Lists the secrets at the given Key/Value Secrets Engine path.
+ * @param keyValuePath The Vault path to list
+ * @return The list of secret names
+ */
+ List<String> listKeyValueSecrets(String keyValuePath);
}
diff --git a/nifi-commons/nifi-vault-utils/pom.xml
b/nifi-commons/nifi-hashicorp-vault/pom.xml
similarity index 91%
rename from nifi-commons/nifi-vault-utils/pom.xml
rename to nifi-commons/nifi-hashicorp-vault/pom.xml
index 11d39ba9a7..a012e2d3e8 100644
--- a/nifi-commons/nifi-vault-utils/pom.xml
+++ b/nifi-commons/nifi-hashicorp-vault/pom.xml
@@ -19,7 +19,7 @@
<artifactId>nifi-commons</artifactId>
<version>1.18.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-vault-utils</artifactId>
+ <artifactId>nifi-hashicorp-vault</artifactId>
<properties>
<spring.vault.version>2.3.2</spring.vault.version>
</properties>
@@ -45,6 +45,12 @@
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
similarity index 100%
copy from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
copy to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
similarity index 94%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
index a407b85eed..38a6e5dfc6 100644
---
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
+++
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
@@ -34,6 +34,7 @@ import org.springframework.vault.support.VaultResponseSupport;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -64,6 +65,11 @@ public class StandardHashiCorpVaultCommunicationService
implements HashiCorpVaul
keyValueOperationsMap = new HashMap<>();
}
+ @Override
+ public String getServerVersion() {
+ return vaultTemplate.opsForSys().health().getVersion();
+ }
+
/**
* Creates a VaultCommunicationService that uses Spring Vault.
* @param vaultProperties Properties to configure the service
@@ -136,6 +142,13 @@ public class StandardHashiCorpVaultCommunicationService
implements HashiCorpVaul
return response == null ? Collections.emptyMap() : (Map<String,
String>) response.getRequiredData();
}
+ @Override
+ public List<String> listKeyValueSecrets(final String keyValuePath) {
+ final VaultKeyValueOperations keyValueOperations =
keyValueOperationsMap
+ .computeIfAbsent(keyValuePath, path ->
vaultTemplate.opsForKeyValue(path, KeyValueBackend.KV_1));
+ return keyValueOperations.list("/");
+ }
+
private static class SecretData {
private final String value;
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
similarity index 81%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
index 873477c097..f20335eeb4 100644
---
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
+++
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
@@ -32,6 +32,7 @@ import org.springframework.vault.support.SslConfiguration;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.Duration;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
@@ -70,13 +71,14 @@ public class HashiCorpVaultConfiguration extends
EnvironmentVaultConfiguration {
private final KeyValueBackend keyValueBackend;
/**
- * Creates a HashiCorpVaultConfiguration from property sources
- * @param propertySources A series of Spring PropertySource objects
+ * Creates a HashiCorpVaultConfiguration from property sources, in
increasing precedence.
+ * @param propertySources A series of Spring PropertySource objects (the
last in the list take precedence over
+ * sources earlier in the list)
* @throws HashiCorpVaultConfigurationException If the authentication
properties file could not be read
*/
public HashiCorpVaultConfiguration(final PropertySource<?>...
propertySources) {
final ConfigurableEnvironment env = new StandardEnvironment();
- for(final PropertySource<?> propertySource : propertySources) {
+ for (final PropertySource<?> propertySource : propertySources) {
env.getPropertySources().addFirst(propertySource);
}
@@ -105,6 +107,7 @@ public class HashiCorpVaultConfiguration extends
EnvironmentVaultConfiguration {
}
}
this.keyValueBackend = keyValueBackend;
+ validateProperties(env);
this.setApplicationContext(new HashiCorpVaultApplicationContext(env));
@@ -114,6 +117,30 @@ public class HashiCorpVaultConfiguration extends
EnvironmentVaultConfiguration {
clientOptions = getClientOptions();
}
+ private void validateProperties(final ConfigurableEnvironment environment)
{
+ try {
+ final String vaultUri =
Objects.requireNonNull(environment.getProperty(VaultConfigurationKey.URI.key),
+ "Missing required property " +
VaultConfigurationKey.URI.key);
+ if (vaultUri.startsWith(HTTPS)) {
+ requireSslProperty("vault.ssl.key-store", environment);
+ requireSslProperty("vault.ssl.key-store-password",
environment);
+ requireSslProperty("vault.ssl.key-store-type", environment);
+ requireSslProperty("vault.ssl.trust-store", environment);
+ requireSslProperty("vault.ssl.trust-store-password",
environment);
+ requireSslProperty("vault.ssl.trust-store-type", environment);
+ }
+ } catch (final NullPointerException e) {
+ // Rethrow as IllegalArgumentException
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+
+ }
+
+ private void requireSslProperty(final String propertyName, final
ConfigurableEnvironment environment) {
+ Objects.requireNonNull(environment.getProperty(propertyName),
propertyName + " is required with an https URI");
+ }
+
+
public KeyValueBackend getKeyValueBackend() {
return keyValueBackend;
}
@@ -158,7 +185,7 @@ public class HashiCorpVaultConfiguration extends
EnvironmentVaultConfiguration {
return new ClientOptions(connectionTimeoutDuration,
readTimeoutDuration);
}
- private static Duration getDuration(String formattedDuration) {
+ private static Duration getDuration(final String formattedDuration) {
final double duration =
FormatUtils.getPreciseTimeDuration(formattedDuration, TimeUnit.MILLISECONDS);
return Duration.ofMillis(Double.valueOf(duration).longValue());
}
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java
b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java
rename to
nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java
diff --git
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
rename to
nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
diff --git
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
similarity index 100%
rename from
nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
rename to
nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
diff --git
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
similarity index 93%
rename from
nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
rename to
nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
index a24f13b28d..d4e931758c 100644
---
a/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
+++
b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
@@ -68,8 +68,8 @@ public class TestStandardHashiCorpVaultCommunicationService {
public void testBasicConfiguration() {
this.configureService();
- // Once to check if the URI is https, and once by VaultTemplate
- Mockito.verify(properties, Mockito.times(2)).getUri();
+ // Once to check if the URI is https, once by VaultTemplate, and once
to validate
+ Mockito.verify(properties, Mockito.times(3)).getUri();
// Once to check if the property is set, and once to retrieve the value
Mockito.verify(properties,
Mockito.times(2)).getAuthPropertiesFilename();
@@ -85,8 +85,6 @@ public class TestStandardHashiCorpVaultCommunicationService {
Mockito.verify(sslProperties,
Mockito.times(numberOfTimes)).getTrustStore();
Mockito.verify(sslProperties,
Mockito.times(numberOfTimes)).getTrustStoreType();
Mockito.verify(sslProperties,
Mockito.times(numberOfTimes)).getTrustStorePassword();
- Mockito.verify(sslProperties,
Mockito.times(numberOfTimes)).getEnabledProtocols();
- Mockito.verify(sslProperties,
Mockito.times(numberOfTimes)).getEnabledCipherSuites();
}
@Test
@@ -113,6 +111,8 @@ public class TestStandardHashiCorpVaultCommunicationService
{
when(properties.getUri()).thenReturn(URI_VALUE.replace("http",
"https"));
this.configureService();
- this.ensureTlsPropertiesAccessed(1);
+ this.ensureTlsPropertiesAccessed(2);
+ Mockito.verify(sslProperties, Mockito.times(1)).getEnabledProtocols();
+ Mockito.verify(sslProperties,
Mockito.times(1)).getEnabledCipherSuites();
}
}
diff --git a/nifi-commons/nifi-property-protection-hashicorp/pom.xml
b/nifi-commons/nifi-property-protection-hashicorp/pom.xml
index f6be9da744..9d9509c55e 100644
--- a/nifi-commons/nifi-property-protection-hashicorp/pom.xml
+++ b/nifi-commons/nifi-property-protection-hashicorp/pom.xml
@@ -29,7 +29,12 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-vault-utils</artifactId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
<dependency>
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 3dcdf77ef1..c62949b580 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -64,7 +64,8 @@
<module>nifi-socket-utils</module>
<module>nifi-utils</module>
<module>nifi-uuid5</module>
- <module>nifi-vault-utils</module>
+ <module>nifi-hashicorp-vault</module>
+ <module>nifi-hashicorp-vault-api</module>
<module>nifi-web-client</module>
<module>nifi-web-client-api</module>
<module>nifi-web-utils</module>
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api-nar/pom.xml
similarity index 80%
copy from
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
copy to
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api-nar/pom.xml
index 511f1a008a..f855199967 100644
---
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api-nar/pom.xml
@@ -22,7 +22,7 @@
<version>1.18.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-hashicorp-vault-nar</artifactId>
+ <artifactId>nifi-hashicorp-vault-client-service-api-nar</artifactId>
<packaging>nar</packaging>
<properties>
<maven.javadoc.skip>true</maven.javadoc.skip>
@@ -32,8 +32,15 @@
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
-
<artifactId>nifi-hashicorp-vault-parameter-value-provider</artifactId>
+ <artifactId>nifi-standard-services-api-nar</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault-client-service-api</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
</dependencies>
+
</project>
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/pom.xml
similarity index 81%
copy from
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
copy to
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/pom.xml
index b9cee8e038..7245b6c861 100755
---
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/pom.xml
@@ -20,29 +20,33 @@
<artifactId>nifi-hashicorp-vault-bundle</artifactId>
<version>1.18.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-hashicorp-vault-parameter-value-provider</artifactId>
+ <artifactId>nifi-hashicorp-vault-client-service-api</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-stateless-api</artifactId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-vault-utils</artifactId>
+ <artifactId>nifi-ssl-context-service-api</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
- <!-- test dependencies -->
<dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jcl-over-slf4j</artifactId>
- <scope>test</scope>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-expression-language</artifactId>
+ <artifactId>nifi-utils</artifactId>
<version>1.18.0-SNAPSHOT</version>
+ </dependency>
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultClientService.java
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultClientService.java
new file mode 100644
index 0000000000..4a7376a04d
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultClientService.java
@@ -0,0 +1,115 @@
+/*
+ * 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.nifi.vault.hashicorp;
+
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.VerifiableControllerService;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.ssl.SSLContextService;
+
+/**
+ * Provides a HashiCorpVaultCommunicationService.
+ */
+public interface HashiCorpVaultClientService extends ControllerService,
VerifiableControllerService {
+
+ AllowableValue DIRECT_PROPERTIES = new AllowableValue("direct-properties",
"Direct Properties",
+ "Use properties, including dynamic properties, configured directly
in the Controller Service to configure the client");
+ AllowableValue PROPERTIES_FILES = new AllowableValue("properties-files",
"Properties Files",
+ "Use one or more '.properties' files to configure the client");
+
+ PropertyDescriptor CONFIGURATION_STRATEGY = new
PropertyDescriptor.Builder()
+ .displayName("Configuration Strategy")
+ .name("configuration-strategy")
+ .required(true)
+ .allowableValues(DIRECT_PROPERTIES, PROPERTIES_FILES)
+ .defaultValue(DIRECT_PROPERTIES.getValue())
+ .description("Specifies the source of the configuration
properties.")
+ .build();
+
+ PropertyDescriptor VAULT_URI = new PropertyDescriptor.Builder()
+ .name("vault.uri")
+ .displayName("Vault URI")
+ .description("The URI of the HashiCorp Vault server (e.g.,
http://localhost:8200). Required if not specified in the " +
+ "Bootstrap HashiCorp Vault Configuration File.")
+ .required(true)
+
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+ .addValidator(StandardValidators.URI_VALIDATOR)
+ .dependsOn(CONFIGURATION_STRATEGY, DIRECT_PROPERTIES)
+ .build();
+
+ PropertyDescriptor VAULT_AUTHENTICATION = new PropertyDescriptor.Builder()
+ .name("vault.authentication")
+ .displayName("Vault Authentication")
+ .description("Vault authentication method, as described in the
Spring Vault Environment Configuration documentation " +
+
"(https://docs.spring.io/spring-vault/docs/2.3.x/reference/html/#vault.core.environment-vault-configuration).")
+ .required(true)
+ .allowableValues(VaultAuthenticationMethod.values())
+ .defaultValue(VaultAuthenticationMethod.TOKEN.name())
+ .dependsOn(CONFIGURATION_STRATEGY, DIRECT_PROPERTIES)
+ .build();
+
+ PropertyDescriptor SSL_CONTEXT_SERVICE = new PropertyDescriptor.Builder()
+ .name("vault.ssl.context.service")
+ .displayName("SSL Context Service")
+ .description("The SSL Context Service used to provide client
certificate information for TLS/SSL connections to the " +
+ "HashiCorp Vault server.")
+ .required(false)
+ .identifiesControllerService(SSLContextService.class)
+ .dependsOn(CONFIGURATION_STRATEGY, DIRECT_PROPERTIES)
+ .build();
+
+ PropertyDescriptor VAULT_PROPERTIES_FILES = new
PropertyDescriptor.Builder()
+ .name("vault.properties.files")
+ .displayName("Vault Properties Files")
+ .description("A comma-separated list of files containing HashiCorp
Vault configuration properties, as described in the Spring Vault " +
+ "Environment Configuration documentation
(https://docs.spring.io/spring-vault/docs/2.3.x/reference/html/#vault.core.environment-vault-configuration).
" +
+ "All of the Spring property keys and
authentication-specific property keys are supported.")
+ .required(true)
+ .dependsOn(CONFIGURATION_STRATEGY, PROPERTIES_FILES)
+ .identifiesExternalResource(ResourceCardinality.MULTIPLE,
ResourceType.FILE)
+ .build();
+
+ PropertyDescriptor CONNECTION_TIMEOUT = new PropertyDescriptor.Builder()
+ .name("vault.connection.timeout")
+ .displayName("Connection Timeout")
+ .description("The connection timeout for the HashiCorp Vault
client")
+ .required(true)
+ .defaultValue("5 sec")
+ .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
+ .build();
+
+ PropertyDescriptor READ_TIMEOUT = new PropertyDescriptor.Builder()
+ .name("vault.read.timeout")
+ .displayName("Read Timeout")
+ .description("The read timeout for the HashiCorp Vault client")
+ .required(true)
+ .defaultValue("15 sec")
+ .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
+ .build();
+
+ /**
+ *
+ * @return A service for communicating with HashiCorp Vault.
+ */
+ HashiCorpVaultCommunicationService getHashiCorpVaultCommunicationService();
+
+}
diff --git
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/src/main/java/org/apache/nifi/vault/hashicorp/VaultAuthenticationMethod.java
similarity index 67%
rename from
nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
rename to
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/src/main/java/org/apache/nifi/vault/hashicorp/VaultAuthenticationMethod.java
index 8948d433e6..075f0f99b5 100644
---
a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service-api/src/main/java/org/apache/nifi/vault/hashicorp/VaultAuthenticationMethod.java
@@ -16,18 +16,13 @@
*/
package org.apache.nifi.vault.hashicorp;
-/**
- * Indicates a misconfiguration of the Vault client.
- */
-public class HashiCorpVaultConfigurationException extends RuntimeException {
- public HashiCorpVaultConfigurationException() {
- }
-
- public HashiCorpVaultConfigurationException(String message) {
- super(message);
- }
-
- public HashiCorpVaultConfigurationException(String message, Throwable
cause) {
- super(message, cause);
- }
+public enum VaultAuthenticationMethod {
+ TOKEN,
+ APPID,
+ APPROLE,
+ AWS_EC2,
+ AZURE,
+ CERT,
+ CUBBYHOLE,
+ KUBERNETES
}
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/pom.xml
similarity index 71%
copy from
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
copy to
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/pom.xml
index b9cee8e038..6ed1f61677 100755
---
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/pom.xml
@@ -15,24 +15,40 @@
-->
<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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
+
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-hashicorp-vault-bundle</artifactId>
<version>1.18.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-hashicorp-vault-parameter-value-provider</artifactId>
+
+ <artifactId>nifi-hashicorp-vault-client-service</artifactId>
<packaging>jar</packaging>
+
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-stateless-api</artifactId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
<version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-vault-utils</artifactId>
+ <artifactId>nifi-hashicorp-vault</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-ssl-context-service-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault-client-service-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
@@ -41,9 +57,10 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-expression-language</artifactId>
+ <artifactId>nifi-security-utils</artifactId>
<version>1.18.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
+
</project>
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultClientService.java
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultClientService.java
new file mode 100644
index 0000000000..f242d8658d
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultClientService.java
@@ -0,0 +1,209 @@
+/*
+ * 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.nifi.vault.hashicorp;
+
+import org.apache.nifi.annotation.behavior.DynamicProperties;
+import org.apache.nifi.annotation.behavior.DynamicProperty;
+import org.apache.nifi.annotation.behavior.SupportsSensitiveDynamicProperties;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnDisabled;
+import org.apache.nifi.annotation.lifecycle.OnEnabled;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.resource.ResourceReference;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.ssl.SSLContextService;
+import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultConfiguration;
+import org.springframework.core.env.PropertySource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+@Tags({"hashicorp", "vault", "client"})
+@CapabilityDescription("A controller service for interacting with HashiCorp
Vault.")
+@SupportsSensitiveDynamicProperties
+@DynamicProperties(
+ @DynamicProperty(name = "A Spring Vault configuration property name",
+ value = "The property value",
+ description = "Allows any Spring Vault property keys to be
specified, as described in " +
+
"(https://docs.spring.io/spring-vault/docs/2.3.x/reference/html/#vault.core.environment-vault-configuration).
" +
+ "See Additional Details for more information.",
+ expressionLanguageScope =
ExpressionLanguageScope.VARIABLE_REGISTRY
+ )
+)
+public class StandardHashiCorpVaultClientService extends
AbstractControllerService implements HashiCorpVaultClientService {
+
+ private static List<PropertyDescriptor> PROPERTIES =
Collections.unmodifiableList(Arrays.asList(
+ CONFIGURATION_STRATEGY,
+ VAULT_URI,
+ VAULT_AUTHENTICATION,
+ SSL_CONTEXT_SERVICE,
+ VAULT_PROPERTIES_FILES,
+ CONNECTION_TIMEOUT,
+ READ_TIMEOUT
+ ));
+
+ private HashiCorpVaultCommunicationService communicationService;
+
+ @Override
+ protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final
String propertyDescriptorName) {
+ return new PropertyDescriptor.Builder()
+ .name(propertyDescriptorName)
+ .displayName(propertyDescriptorName)
+ .dynamic(true)
+
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+ }
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return PROPERTIES;
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext
context, final ComponentLog verificationLogger,
+ final Map<String, String>
variables) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+ HashiCorpVaultCommunicationService service = null;
+ try {
+ service = createCommunicationService(context);
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.SUCCESSFUL)
+ .verificationStepName("Configure HashiCorp Vault Client")
+ .explanation("Successfully configured HashiCorp Vault
Client")
+ .build());
+ } catch (final Exception e) {
+ verificationLogger.error("Failed to configure HashiCorp Vault
Client", e);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.FAILED)
+ .verificationStepName("Configure HashiCorp Vault Client")
+ .explanation("Failed to configure HashiCorp Vault Client:
" + e.getMessage())
+ .build());
+ }
+ if (service != null) {
+ try {
+ service.getServerVersion();
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.SUCCESSFUL)
+ .verificationStepName("Connect to HashiCorp Vault
Server")
+ .explanation("Successfully connected to HashiCorp
Vault Server")
+ .build());
+ } catch (final Exception e) {
+ verificationLogger.error("Failed to connect to HashiCorp Vault
Server", e);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.FAILED)
+ .verificationStepName("Connect to HashiCorp Vault
Server")
+ .explanation("Failed to connect to HashiCorp Vault
Server: " + e.getMessage())
+ .build());
+ }
+ }
+
+ return results;
+ }
+
+ @OnEnabled
+ public void onEnabled(final ConfigurationContext context) throws
InitializationException {
+ try {
+ communicationService = createCommunicationService(context);
+ } catch (final Exception e) {
+ throw new InitializationException("Failed to initialize HashiCorp
Vault client", e);
+ }
+ }
+
+ @OnDisabled
+ public void onDisabled() {
+ communicationService = null;
+ }
+
+ @Override
+ public HashiCorpVaultCommunicationService
getHashiCorpVaultCommunicationService() {
+ return communicationService;
+ }
+
+ private HashiCorpVaultCommunicationService
createCommunicationService(final ConfigurationContext context) throws
IOException {
+ final List<PropertySource<?>> propertySources = new ArrayList<>();
+
+ final String configurationStrategy =
context.getProperty(CONFIGURATION_STRATEGY).getValue();
+ if (DIRECT_PROPERTIES.getValue().equals(configurationStrategy)) {
+ final PropertySource<?> configurationPropertySource = new
DirectPropertySource("Direct Properties", context);
+ propertySources.add(configurationPropertySource);
+ } else {
+ for (final ResourceReference resourceReference :
context.getProperty(VAULT_PROPERTIES_FILES).asResources().asList()) {
+ final String propertiesFile = resourceReference.getLocation();
+
propertySources.add(HashiCorpVaultConfiguration.createPropertiesFileSource(propertiesFile));
+ }
+ }
+
+ return new
StandardHashiCorpVaultCommunicationService(propertySources.toArray(new
PropertySource[0]));
+ }
+
+ static class DirectPropertySource extends
PropertySource<ConfigurationContext> {
+
+ private static final String VAULT_SSL_KEY_PATTERN =
"vault.ssl.(key.*|trust.*|enabledProtocols)";
+
+ public DirectPropertySource(final String name, final
ConfigurationContext source) {
+ super(name, source);
+ }
+
+ @Override
+ public Object getProperty(final String name) {
+ if (name.matches(VAULT_SSL_KEY_PATTERN)) {
+ return getSslProperty(name);
+ }
+
+ return getSource().getAllProperties().get(name);
+ }
+
+ private String getSslProperty(final String name) {
+ if (getSource().getProperty(SSL_CONTEXT_SERVICE).isSet()) {
+ final SSLContextService sslContextService =
getSource().getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
+ switch (name) {
+ case "vault.ssl.key-store":
+ return sslContextService.getKeyStoreFile();
+ case "vault.ssl.key-store-password":
+ return sslContextService.getKeyStorePassword();
+ case "vault.ssl.key-store-type":
+ return sslContextService.getKeyStoreType();
+ case "vault.ssl.trust-store":
+ return sslContextService.getTrustStoreFile();
+ case "vault.ssl.trust-store-password":
+ return sslContextService.getTrustStorePassword();
+ case "vault.ssl.trust-store-type":
+ return sslContextService.getTrustStoreType();
+ case "vault.ssl.enabledProtocols":
+ return sslContextService.getSslAlgorithm();
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
new file mode 100644
index 0000000000..d20ace77f8
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -0,0 +1,15 @@
+# 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.nifi.vault.hashicorp.StandardHashiCorpVaultClientService
\ No newline at end of file
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/resources/docs/org.apache.nifi.vault.hashicorp.StandardHashiCorpVaultClientService/additionalDetails.html
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/resources/docs/org.apache.nifi.vault.hashicorp.StandardHashiCorpVaultClientService/additionalDetails.html
new file mode 100644
index 0000000000..337ee65164
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/main/resources/docs/org.apache.nifi.vault.hashicorp.StandardHashiCorpVaultClientService/additionalDetails.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/html">
+<!--
+ 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.
+ -->
+
+<head>
+ <meta charset="utf-8"/>
+ <title>StandardHashiCorpVaultClientService</title>
+ <link rel="stylesheet" href="../../../../../css/component-usage.css"
type="text/css"/>
+</head>
+<body>
+
+<h1>Configuring the Bootstrap HashiCorp Vault Configuration File</h1>
+
+<p>
+ The ./conf/bootstrap-hashicorp-vault.conf file that comes with Apache NiFi
is a convenient way to configure this
+ controller service in a manner consistent with the HashiCorpVault
sensitive properties provider. Since this file is already used for configuring
+ the Vault client for protecting sensitive properties in the NiFi
configuration files
+ (see the <a
href="../../../../../html/administration-guide.html#hashicorp-vault-providers">Administrator's
Guide</a>),
+ it's a natural starting point for configuring the controller service as
well.
+</p>
+
+<p>
+ An example configuration of this properties file is as follows:
+</p>
+
+<code>
+ <pre>
+# HTTP or HTTPS URI for HashiCorp Vault is required to enable the Sensitive
Properties Provider
+vault.uri=https://127.0.0.1:8200
+
+# Optional file supports authentication properties described in the Spring
Vault Environment Configuration
+#
https://docs.spring.io/spring-vault/docs/2.3.x/reference/html/#vault.core.environment-vault-configuration
+#
+# All authentication properties must be included in
bootstrap-hashicorp-vault.conf when this property is not specified.
+# Properties in bootstrap-hashicorp-vault.conf take precedence when the same
values are defined in both files.
+# Token Authentication is the default when the 'vault.authentication' property
is not specified.
+vault.authentication.properties.file=[full/path/to/vault-auth.properties]
+
+# Optional Timeout properties
+vault.connection.timeout=5 secs
+vault.read.timeout=15 secs
+
+# Optional TLS properties
+vault.ssl.enabledCipherSuites=
+vault.ssl.enabledProtocols=TLSv1.3
+vault.ssl.key-store=[path/to/keystore.p12]
+vault.ssl.key-store-type=PKCS12
+vault.ssl.key-store-password=[keystore password]
+vault.ssl.trust-store=[path/to/truststore.p12]
+vault.ssl.trust-store-type=PKCS12
+vault.ssl.trust-store-password=[truststore password]
+ </pre>
+</code>
+
+<p>
+ In order to use this file in the StandardHashiCorpVaultClientService,
specify the following properties:
+ <ul>
+ <li><b>Configuration Strategy</b> - Properties Files</li>
+ <li><b>Vault Properties Files</b> -
./conf/bootstrap-hashicorp-vault.conf</li>
+ </ul>
+</p>
+
+<p>
+ If your bootstrap configuration includes the
vault.authentication.properties.file containing additional authentication
properties, this
+ file will also need to be added to the Vault Properties Files property as
a comma-separated value.
+</p>
+
+<h3>Configuring the Client using Direct Properties</h3>
+
+<p>
+ However, if you want to specify or override properties directly in the
controller service, you may do this by specifying a Configuration Strategy
+ of 'Direct Properties'. This can be useful if you are reusing an
SSLContextService or want to parameterize the Vault configuration properties.
+ Authentication-related properties can also be added as sensitive dynamic
properties, as seen in the examples below.
+</p>
+
+<h3>Vault Authentication</h3>
+<p>
+ Under the hood, the controller service uses Spring Vault, and directly
supports the property keys specified in
+ <a
href="https://docs.spring.io/spring-vault/docs/2.3.x/reference/html/#vault.core.environment-vault-configuration">Spring
Vault's documentation</a>.
+ Following are some common examples of authentication with Vault.
+</p>
+
+<h4>Token Authentication</h4>
+<p>
+ The simplest authentication scheme uses a rotating token, which is enabled
by default in Vault. To specify this mechanism, select "TOKEN" from the
+ "Vault Authentication" property (the default). However, since the token
should rotate by nature, it is a best practice to use the 'Properties Files'
+ Configuration Strategy, and keep the token value in an external properties
file, indicating this filename in the 'Vault Properties Files' property.
+ Then an external process can rotate the token in the file without updating
NiFi configuration. In order to pick up the changed token, the controller
+ service must be disabled and re-enabled.
+</p>
+
+<p>
+ For testing purposes, however, it may be more convenient to specify the
token directly in the controller service. To do so, add a new Sensitive
property named
+ 'vault.token' and enter the token as the value.
+</p>
+
+<h4>Certificate Authentication</h4>
+<p>
+ Certificate authentication must be enabled in the Vault server before it
can be used from NiFi, but it uses the same TLS settings as the actual
+ client connection, so no additional authentication properties are
required. While these TLS settings can be provided in an external properties
file,
+ we will demonstrate configuring an SSLContextService instead.
+</p>
+
+<p>
+ First, create an SSLContextService controller service and configure the
Filename, Password, and Type for both the Keystore and Truststore.
+ Enable it, and assign it as the SSL Context Service in the Vault
controller service. Then, simply specify "CERT" as the "Vault Authentication"
+ property value.
+</p>
+
+<h4>Other Authentication Methods</h4>
+<p>
+ To configure the other authentication methods, see the Spring Vault
documentation linked above. All relevant properties should be added either
+ to the external properties files referenced in the "Vault Properties
Files" property if using the 'Properties Files' Configuration Strategy,
+ or added as custom properties with the same name if using the 'Direct
Properties' Configuration Strategy.
+ For example, for the Azure authentication mechanism, properties will have
to be added for 'vault.azure-msi.azure-path',
+ 'vault.azure-msi.role', and 'vault.azure-msi.identity-token-service'.
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultClientService.java
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultClientService.java
new file mode 100644
index 0000000000..cbcd583b76
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-client-service/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultClientService.java
@@ -0,0 +1,225 @@
+/*
+ * 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.nifi.vault.hashicorp;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.components.resource.ResourceReference;
+import org.apache.nifi.components.resource.ResourceReferences;
+import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.security.util.KeyStoreUtils;
+import org.apache.nifi.security.util.StandardTlsConfiguration;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.ssl.SSLContextService;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestStandardHashiCorpVaultClientService {
+
+ private static Path bootstrapHashiCorpVaultConf;
+ private static TlsConfiguration tlsConfiguration;
+
+ private StandardHashiCorpVaultClientService clientService;
+
+ private ConfigurationContext mockContext(final Map<PropertyDescriptor,
String> properties, final boolean includeSSL) {
+ final ConfigurationContext context = mock(ConfigurationContext.class);
+ properties.entrySet().forEach(entry -> mockProperty(context,
entry.getKey(), entry.getValue()));
+ if
(properties.containsKey(HashiCorpVaultClientService.VAULT_PROPERTIES_FILES)) {
+ final PropertyValue propertiesFilesProperty =
mock(PropertyValue.class);
+ final ResourceReferences resources =
mock(ResourceReferences.class);
+
when(resources.asList()).thenReturn(Arrays.asList(properties.get(HashiCorpVaultClientService.VAULT_PROPERTIES_FILES).split(","))
+ .stream().map(SimpleResourceReference::new)
+ .collect(Collectors.toList()));
+ when(propertiesFilesProperty.asResources()).thenReturn(resources);
+
when(context.getProperty(HashiCorpVaultClientService.VAULT_PROPERTIES_FILES)).thenReturn(propertiesFilesProperty);
+ }
+ if (includeSSL) {
+ final SSLContextService sslContextService =
mock(SSLContextService.class);
+
when(sslContextService.getKeyStoreFile()).thenReturn(tlsConfiguration.getKeystorePath());
+
when(sslContextService.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType());
+
when(sslContextService.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword());
+
when(sslContextService.getTrustStoreFile()).thenReturn(tlsConfiguration.getTruststorePath());
+
when(sslContextService.getTrustStoreType()).thenReturn(tlsConfiguration.getTruststoreType().getType());
+
when(sslContextService.getTrustStorePassword()).thenReturn(tlsConfiguration.getTruststorePassword());
+
when(sslContextService.getSslAlgorithm()).thenReturn(tlsConfiguration.getProtocol());
+
+ final PropertyValue sslContextServicePropertyValue =
mock(PropertyValue.class);
+ when(sslContextServicePropertyValue.isSet()).thenReturn(true);
+
when(sslContextServicePropertyValue.asControllerService(SSLContextService.class)).thenReturn(sslContextService);
+
when(context.getProperty(HashiCorpVaultClientService.SSL_CONTEXT_SERVICE)).thenReturn(sslContextServicePropertyValue);
+ } else {
+ final PropertyValue sslContextServicePropertyValue =
mock(PropertyValue.class);
+ when(sslContextServicePropertyValue.isSet()).thenReturn(false);
+
when(context.getProperty(HashiCorpVaultClientService.SSL_CONTEXT_SERVICE)).thenReturn(sslContextServicePropertyValue);
+ }
+
when(context.getAllProperties()).thenReturn(properties.entrySet().stream()
+ .collect(Collectors.toMap(e -> e.getKey().getName(),
Map.Entry::getValue)));
+ return context;
+ }
+
+ private void mockProperty(final ConfigurationContext context, final
PropertyDescriptor descriptor, final String value) {
+ final PropertyValue propertyValue = mock(PropertyValue.class);
+ when(propertyValue.getValue()).thenReturn(value);
+ when(context.getProperty(descriptor)).thenReturn(propertyValue);
+ }
+
+ @BeforeAll
+ public static void initOnce() throws IOException, GeneralSecurityException
{
+ bootstrapHashiCorpVaultConf =
Files.createTempFile("bootstrap-hashicorp-vault", "conf");
+ final File keyStoreFile =
File.createTempFile(TestStandardHashiCorpVaultClientService.class.getSimpleName(),
".keystore.p12");
+ final File trustStoreFile =
File.createTempFile(TestStandardHashiCorpVaultClientService.class.getSimpleName(),
".truststore.p12");
+ final TlsConfiguration requestedTlsConfig = new
StandardTlsConfiguration();
+ tlsConfiguration =
KeyStoreUtils.createTlsConfigAndNewKeystoreTruststore(requestedTlsConfig, 7,
null);
+ // This should be overridden by any explicit properties
+ Files.write(bootstrapHashiCorpVaultConf,
(String.format("vault.uri=https://localhost:8200\n" +
+ "vault.authentication=TOKEN\n" +
+ "vault.token=my-token\n" +
+ "vault.ssl.key-store=%s\n" +
+ "vault.ssl.key-store-password=%s\n" +
+ "vault.ssl.key-store-type=%s\n" +
+ "vault.ssl.trust-store=%s\n" +
+ "vault.ssl.trust-store-password=%s\n" +
+ "vault.ssl.trust-store-type=%s\n",
+ tlsConfiguration.getKeystorePath().replace("\\", "\\\\"),
+ tlsConfiguration.getKeystorePassword(),
+ tlsConfiguration.getKeystoreType().getType(),
+ tlsConfiguration.getTruststorePath().replace("\\", "\\\\"),
+ tlsConfiguration.getTruststorePassword(),
+
tlsConfiguration.getTruststoreType().getType())).getBytes(StandardCharsets.UTF_8));
+ }
+
+ @AfterAll
+ public static void tearDownOnce() throws IOException {
+ Files.deleteIfExists(bootstrapHashiCorpVaultConf);
+ Files.deleteIfExists(Paths.get(tlsConfiguration.getKeystorePath()));
+ Files.deleteIfExists(Paths.get(tlsConfiguration.getTruststorePath()));
+ }
+
+ @BeforeEach
+ public void init() {
+ clientService = new StandardHashiCorpVaultClientService() {
+ @Override
+ protected ComponentLog getLogger() {
+ return mock(ComponentLog.class);
+ }
+ };
+ }
+
+ @Test
+ public void onEnabledHttpDirect() throws InitializationException {
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ final PropertyDescriptor vaultToken = new
PropertyDescriptor.Builder().name("vault.token").build();
+ properties.put(HashiCorpVaultClientService.CONFIGURATION_STRATEGY,
HashiCorpVaultClientService.DIRECT_PROPERTIES.getValue());
+ properties.put(HashiCorpVaultClientService.VAULT_URI,
"http://localhost:8200");
+ properties.put(vaultToken, "myToken");
+ properties.put(HashiCorpVaultClientService.VAULT_AUTHENTICATION,
"TOKEN");
+ final ConfigurationContext context = mockContext(properties, false);
+ clientService.onEnabled(context);
+ assertNotNull(clientService.getHashiCorpVaultCommunicationService());
+ clientService.onDisabled();
+ assertNull(clientService.getHashiCorpVaultCommunicationService());
+ }
+
+ @Test
+ public void onEnabledHttpsDirect() throws InitializationException {
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ final PropertyDescriptor vaultToken = new
PropertyDescriptor.Builder().name("vault.token").build();
+ properties.put(HashiCorpVaultClientService.CONFIGURATION_STRATEGY,
HashiCorpVaultClientService.DIRECT_PROPERTIES.getValue());
+ properties.put(HashiCorpVaultClientService.VAULT_URI,
"https://localhost:8200");
+ properties.put(vaultToken, "myToken");
+ properties.put(HashiCorpVaultClientService.VAULT_AUTHENTICATION,
"TOKEN");
+ final ConfigurationContext context = mockContext(properties, true);
+ clientService.onEnabled(context);
+ assertNotNull(clientService.getHashiCorpVaultCommunicationService());
+ clientService.onDisabled();
+ assertNull(clientService.getHashiCorpVaultCommunicationService());
+ }
+
+ @Test
+ public void onEnabledHttpsPropertiesFiles() throws InitializationException
{
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ properties.put(HashiCorpVaultClientService.CONFIGURATION_STRATEGY,
HashiCorpVaultClientService.PROPERTIES_FILES.getValue());
+ properties.put(HashiCorpVaultClientService.VAULT_PROPERTIES_FILES,
bootstrapHashiCorpVaultConf.toString());
+ final ConfigurationContext context = mockContext(properties, true);
+ clientService.onEnabled(context);
+ assertNotNull(clientService.getHashiCorpVaultCommunicationService());
+ clientService.onDisabled();
+ assertNull(clientService.getHashiCorpVaultCommunicationService());
+ }
+
+ private class SimpleResourceReference implements ResourceReference {
+ private final String filename;
+
+ private SimpleResourceReference(String filename) {
+ this.filename = filename;
+ }
+
+ @Override
+ public File asFile() {
+ return null;
+ }
+
+ @Override
+ public URL asURL() {
+ return null;
+ }
+
+ @Override
+ public InputStream read() throws IOException {
+ return null;
+ }
+
+ @Override
+ public boolean isAccessible() {
+ return false;
+ }
+
+ @Override
+ public String getLocation() {
+ return filename;
+ }
+
+ @Override
+ public ResourceType getResourceType() {
+ return null;
+ }
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
index 511f1a008a..fd6924cc74 100644
---
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-nar/pom.xml
@@ -35,5 +35,27 @@
<artifactId>nifi-hashicorp-vault-parameter-value-provider</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+
<artifactId>nifi-hashicorp-vault-client-service-api-nar</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault-parameter-provider</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault-client-service</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ </dependency>
</dependencies>
</project>
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/pom.xml
similarity index 85%
copy from
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
copy to
nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/pom.xml
index b9cee8e038..80d227a12a 100755
---
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/pom.xml
@@ -15,22 +15,32 @@
-->
<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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
+
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-hashicorp-vault-bundle</artifactId>
<version>1.18.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-hashicorp-vault-parameter-value-provider</artifactId>
+
+ <artifactId>nifi-hashicorp-vault-parameter-provider</artifactId>
<packaging>jar</packaging>
+
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-stateless-api</artifactId>
+ <artifactId>nifi-hashicorp-vault-client-service-api</artifactId>
<version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-vault-utils</artifactId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
<!-- test dependencies -->
@@ -39,11 +49,6 @@
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.apache.nifi</groupId>
- <artifactId>nifi-expression-language</artifactId>
- <version>1.18.0-SNAPSHOT</version>
- <scope>test</scope>
- </dependency>
</dependencies>
+
</project>
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultParameterProvider.java
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultParameterProvider.java
new file mode 100644
index 0000000000..154d0c6133
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultParameterProvider.java
@@ -0,0 +1,163 @@
+/*
+ * 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.nifi.vault.hashicorp;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.Validator;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.parameter.AbstractParameterProvider;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterDescriptor;
+import org.apache.nifi.parameter.ParameterGroup;
+import org.apache.nifi.parameter.ParameterProvider;
+import org.apache.nifi.parameter.VerifiableParameterProvider;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@CapabilityDescription("Provides parameters from HashiCorp Vault Key/Value
Secrets. Each Secret represents a parameter group, " +
+ "which will map to a Parameter Context. The keys and values in the
Secret map to Parameters.")
+@Tags({"hashicorp", "vault", "secret"})
+public class HashiCorpVaultParameterProvider extends AbstractParameterProvider
implements ParameterProvider, VerifiableParameterProvider {
+
+ public static final PropertyDescriptor VAULT_CLIENT_SERVICE = new
PropertyDescriptor.Builder()
+ .name("vault-client-service")
+ .displayName("HashiCorp Vault Client Service")
+ .description("The service used to interact with HashiCorp Vault")
+ .identifiesControllerService(HashiCorpVaultClientService.class)
+ .addValidator(Validator.VALID)
+ .required(true)
+ .build();
+ public static final PropertyDescriptor KV_PATH = new
PropertyDescriptor.Builder()
+ .name("kv-path")
+ .displayName("Key/Value Path")
+ .description("The HashiCorp Vault path to the Key/Value Secrets
Engine")
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .required(true)
+ .defaultValue("kv")
+ .build();
+ public static final PropertyDescriptor SECRET_NAME_PATTERN = new
PropertyDescriptor.Builder()
+ .name("secret-name-pattern")
+ .displayName("Secret Name Pattern")
+ .description("A Regular Expression indicating which Secrets to
include as parameter groups to map to Parameter Contexts by name.")
+ .addValidator(StandardValidators.REGULAR_EXPRESSION_VALIDATOR)
+ .required(true)
+ .defaultValue(".*")
+ .build();
+
+ private static final List<PropertyDescriptor> PROPERTIES =
Collections.unmodifiableList(Arrays.asList(
+ VAULT_CLIENT_SERVICE,
+ KV_PATH,
+ SECRET_NAME_PATTERN));
+
+ private HashiCorpVaultCommunicationService vaultCommunicationService;
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return PROPERTIES;
+ }
+
+ @Override
+ public List<ParameterGroup> fetchParameters(final ConfigurationContext
context) {
+ if (vaultCommunicationService == null) {
+ vaultCommunicationService = getVaultCommunicationService(context);
+ }
+
+ final List<ParameterGroup> parameterGroups =
getParameterGroups(vaultCommunicationService, context);
+ return parameterGroups;
+ }
+
+ private List<ParameterGroup> getParameterGroups(final
HashiCorpVaultCommunicationService vaultCommunicationService,
+ final
ConfigurationContext context) {
+ final String kvPath = context.getProperty(KV_PATH).getValue();
+ final String secretIncludeRegex =
context.getProperty(SECRET_NAME_PATTERN).getValue();
+ final List<String> allSecretNames =
vaultCommunicationService.listKeyValueSecrets(kvPath);
+ final List<String> secretNames = allSecretNames.stream()
+ .filter(name -> name.matches(secretIncludeRegex))
+ .collect(Collectors.toList());
+
+ final List<ParameterGroup> parameterGroups = new ArrayList<>();
+ for (final String secretName : secretNames) {
+ final Map<String, String> keyValues =
vaultCommunicationService.readKeyValueSecretMap(kvPath, secretName);
+ final List<Parameter> parameters = new ArrayList<>();
+ keyValues.forEach( (key, value) -> {
+ final ParameterDescriptor parameterDescriptor = new
ParameterDescriptor.Builder().name(key).build();
+ parameters.add(new Parameter(parameterDescriptor, value, null,
true));
+ });
+ parameterGroups.add(new ParameterGroup(secretName, parameters));
+ }
+ final long parameterCount = parameterGroups.stream()
+ .flatMap(group -> group.getParameters().stream())
+ .count();
+ final List<String> parameterGroupNames = parameterGroups.stream()
+ .map(group -> group.getGroupName())
+ .distinct()
+ .collect(Collectors.toList());
+ getLogger().info("Fetched parameter groups {}, containing a total of
{} parameters", parameterGroupNames, parameterCount);
+ return parameterGroups;
+ }
+
+ @Override
+ public void onPropertyModified(final PropertyDescriptor descriptor, final
String oldValue, final String newValue) {
+ if (VAULT_CLIENT_SERVICE.equals(descriptor)) {
+ vaultCommunicationService = null;
+ }
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext
context, final ComponentLog verificationLogger) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+ try {
+ final HashiCorpVaultCommunicationService vaultCommunicationService
= getVaultCommunicationService(context);
+ final List<ParameterGroup> parameterGroups =
getParameterGroups(vaultCommunicationService, context);
+ final int groupCount = parameterGroups.size();
+ final long parameterCount = parameterGroups.stream()
+ .flatMap(group -> group.getParameters().stream())
+ .count();
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.SUCCESSFUL)
+ .verificationStepName("Fetch Secrets as Parameter Groups")
+ .explanation(String.format("Successfully fetched %s
secrets matching the filter as Parameter Groups, " +
+ "containing a total of %s Parameters.",
groupCount, parameterCount))
+ .build());
+ } catch (final Exception e) {
+ verificationLogger.error("Failed to fetch secrets as Parameter
Groups", e);
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.FAILED)
+ .verificationStepName("Fetch Secrets as Parameter Groups")
+ .explanation(String.format("Failed to fetch secrets as
Parameter Groups: " + e.getMessage()))
+ .build());
+ }
+
+ return results;
+ }
+
+ HashiCorpVaultCommunicationService getVaultCommunicationService(final
ConfigurationContext context) {
+ final HashiCorpVaultClientService clientService =
context.getProperty(VAULT_CLIENT_SERVICE)
+ .asControllerService(HashiCorpVaultClientService.class);
+ return clientService.getHashiCorpVaultCommunicationService();
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/main/resources/META-INF/services/org.apache.nifi.parameter.ParameterProvider
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/main/resources/META-INF/services/org.apache.nifi.parameter.ParameterProvider
new file mode 100644
index 0000000000..34f9c97d31
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/main/resources/META-INF/services/org.apache.nifi.parameter.ParameterProvider
@@ -0,0 +1,15 @@
+# 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.nifi.vault.hashicorp.HashiCorpVaultParameterProvider
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultParameterProvider.java
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultParameterProvider.java
new file mode 100644
index 0000000000..9880448c67
--- /dev/null
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-provider/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultParameterProvider.java
@@ -0,0 +1,162 @@
+/*
+ * 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.nifi.vault.hashicorp;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterDescriptor;
+import org.apache.nifi.parameter.ParameterGroup;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class TestHashiCorpVaultParameterProvider {
+
+ private HashiCorpVaultParameterProvider parameterProvider;
+
+ @Mock
+ private HashiCorpVaultCommunicationService vaultCommunicationService;
+
+ private List<ParameterGroup> mockedGroups;
+
+ @BeforeEach
+ public void init() {
+ vaultCommunicationService =
mock(HashiCorpVaultCommunicationService.class);
+
+ parameterProvider = new HashiCorpVaultParameterProvider() {
+ @Override
+ HashiCorpVaultCommunicationService
getVaultCommunicationService(final ConfigurationContext context) {
+ return vaultCommunicationService;
+ }
+
+ @Override
+ protected ComponentLog getLogger() {
+ return mock(ComponentLog.class);
+ }
+ };
+ mockedGroups = new ArrayList<>();
+ mockedGroups.add(new ParameterGroup("groupA", Arrays.asList(
+ createParameter("paramA", "valueA"),
+ createParameter("paramB", "valueB"),
+ createParameter("otherParam", "valueC"))));
+ mockedGroups.add(new ParameterGroup("groupA", Arrays.asList(
+ createParameter("paramC", "valueC"),
+ createParameter("paramD", "valueD"),
+ createParameter("otherParam2", "valueE"))));
+ mockedGroups.add(new ParameterGroup("groupB", Arrays.asList(
+ createParameter("paramC", "valueC"),
+ createParameter("paramD", "valueD"),
+ createParameter("otherParam", "valueE"))));
+ }
+
+ @Test
+ public void testFetchParameters() {
+ mockSecrets("kv2", mockedGroups);
+
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ properties.put(HashiCorpVaultParameterProvider.KV_PATH, "kv2");
+ properties.put(HashiCorpVaultParameterProvider.VAULT_CLIENT_SERVICE,
"service");
+ properties.put(HashiCorpVaultParameterProvider.SECRET_NAME_PATTERN,
".*");
+ final ConfigurationContext context = mockContext(properties);
+
+ final List<ParameterGroup> results =
parameterProvider.fetchParameters(context);
+ assertEquals(3, results.size());
+ results.forEach(group -> {
+ assertEquals(3, group.getParameters().size());
+ });
+ }
+
+ @Test
+ public void testFetchParametersSecretRegex() {
+ mockSecrets("kv2", mockedGroups);
+
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ properties.put(HashiCorpVaultParameterProvider.KV_PATH, "kv2");
+ properties.put(HashiCorpVaultParameterProvider.VAULT_CLIENT_SERVICE,
"service");
+ properties.put(HashiCorpVaultParameterProvider.SECRET_NAME_PATTERN,
".*A");
+ final ConfigurationContext context = mockContext(properties);
+
+ final List<ParameterGroup> results =
parameterProvider.fetchParameters(context);
+ assertEquals(2, results.size());
+ results.forEach(group -> {
+ assertEquals(3, group.getParameters().size());
+ });
+ }
+
+ @Test
+ public void testVerifyParameters() {
+ mockSecrets("kv2", mockedGroups);
+
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ properties.put(HashiCorpVaultParameterProvider.KV_PATH, "kv2");
+ properties.put(HashiCorpVaultParameterProvider.VAULT_CLIENT_SERVICE,
"service");
+ properties.put(HashiCorpVaultParameterProvider.SECRET_NAME_PATTERN,
".*");
+ final ConfigurationContext context = mockContext(properties);
+
+ final List<ConfigVerificationResult> results =
parameterProvider.verify(context, mock(ComponentLog.class));
+ assertEquals(1, results.size());
+ final String explanation = results.get(0).getExplanation();
+ assertTrue(explanation.contains("3 secrets"));
+ assertTrue(explanation.contains("9 Parameters"));
+ }
+
+ private ConfigurationContext mockContext(final Map<PropertyDescriptor,
String> properties) {
+ final ConfigurationContext context = mock(ConfigurationContext.class);
+ properties.entrySet().forEach(entry -> mockProperty(context,
entry.getKey(), entry.getValue()));
+ return context;
+ }
+
+ private void mockProperty(final ConfigurationContext context, final
PropertyDescriptor descriptor, final String value) {
+ final PropertyValue propertyValue = mock(PropertyValue.class);
+ lenient().when(propertyValue.getValue()).thenReturn(value);
+
lenient().when(context.getProperty(descriptor)).thenReturn(propertyValue);
+ }
+
+ private void mockSecrets(final String kvPath, final List<ParameterGroup>
parameterGroups) {
+ when(vaultCommunicationService.listKeyValueSecrets(kvPath))
+ .thenReturn(parameterGroups.stream().map(group ->
group.getGroupName()).collect(Collectors.toList()));
+ for (final ParameterGroup parameterGroup : parameterGroups) {
+ final Map<String, String> keyValues =
parameterGroup.getParameters().stream()
+ .collect(Collectors.toMap(parameter ->
parameter.getDescriptor().getName(), parameter -> parameter.getValue()));
+
lenient().when(vaultCommunicationService.readKeyValueSecretMap(kvPath,
parameterGroup.getGroupName())).thenReturn(keyValues);
+ }
+ }
+
+ private Parameter createParameter(final String name, final String value) {
+ return new Parameter(new
ParameterDescriptor.Builder().name(name).build(), value);
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
index b9cee8e038..2a686186fd 100755
---
a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
+++
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/nifi-hashicorp-vault-parameter-value-provider/pom.xml
@@ -30,7 +30,13 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-vault-utils</artifactId>
+ <artifactId>nifi-hashicorp-vault-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-hashicorp-vault</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
<!-- test dependencies -->
diff --git a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/pom.xml
b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/pom.xml
index 14c80f5080..12737a7461 100644
--- a/nifi-nar-bundles/nifi-hashicorp-vault-bundle/pom.xml
+++ b/nifi-nar-bundles/nifi-hashicorp-vault-bundle/pom.xml
@@ -25,6 +25,10 @@
<description>A bundle for reading and writing secrets from HashiCorp
Vault</description>
<modules>
<module>nifi-hashicorp-vault-parameter-value-provider</module>
+ <module>nifi-hashicorp-vault-client-service-api</module>
+ <module>nifi-hashicorp-vault-client-service-api-nar</module>
+ <module>nifi-hashicorp-vault-client-service</module>
+ <module>nifi-hashicorp-vault-parameter-provider</module>
<module>nifi-hashicorp-vault-nar</module>
</modules>
</project>