diff --git a/nifi-commons/nifi-vault-utils/pom.xml b/nifi-commons/nifi-vault-utils/pom.xml
new file mode 100644
index 0000000000..190388908c
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ org.apache.nifi
+ nifi-commons
+ 1.14.0-SNAPSHOT
+
+ nifi-vault-utils
+
+
+ org.springframework.vault
+ spring-vault-core
+ 2.3.2
+
+
+ org.springframework
+ spring-core
+ 5.3.6
+
+
+ org.apache.nifi
+ nifi-utils
+ 1.14.0-SNAPSHOT
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.9.1
+ runtime
+
+
+ org.apache.nifi
+ nifi-security-utils-api
+ 1.14.0-SNAPSHOT
+ test
+
+
+ org.apache.nifi
+ nifi-security-utils
+ 1.14.0-SNAPSHOT
+ test
+
+
+
+
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
new file mode 100644
index 0000000000..977b369e54
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultCommunicationService.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * A service to handle all communication with an instance of HashiCorp Vault.
+ * @see https://www.vaultproject.io/
+ */
+public interface HashiCorpVaultCommunicationService {
+
+ /**
+ * Encrypts the given plaintext using Vault's Transit Secrets Engine.
+ *
+ * @see https://www.vaultproject.io/api-docs/secret/transit
+ * @param transitKey A named encryption key used in the Transit Secrets Engine. The key is expected to have
+ * already been configured in the Vault instance.
+ * @param plainText The plaintext to encrypt
+ * @return The cipher text
+ */
+ String encrypt(String transitKey, byte[] plainText);
+
+ /**
+ * Decrypts the given cipher text using Vault's Transit Secrets Engine.
+ *
+ * @see https://www.vaultproject.io/api-docs/secret/transit
+ * @param transitKey A named encryption key used in the Transit Secrets Engine. The key is expected to have
+ * already been configured in the Vault instance.
+ * @param cipherText The cipher text to decrypt
+ * @return The decrypted plaintext
+ */
+ byte[] decrypt(String transitKey, String cipherText);
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
new file mode 100644
index 0000000000..8948d433e6
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/HashiCorpVaultConfigurationException.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * 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);
+ }
+}
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
new file mode 100644
index 0000000000..8f92fb30cc
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java
@@ -0,0 +1,94 @@
+/*
+ * 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.util.FormatUtils;
+import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultConfiguration;
+import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultProperties;
+import org.springframework.vault.authentication.SimpleSessionManager;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.core.VaultTransitOperations;
+import org.springframework.vault.support.Ciphertext;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.Plaintext;
+import org.springframework.vault.support.SslConfiguration;
+
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implements the VaultCommunicationService using Spring Vault
+ */
+public class StandardHashiCorpVaultCommunicationService implements HashiCorpVaultCommunicationService {
+ private static final String HTTPS = "https";
+
+ private final HashiCorpVaultConfiguration vaultConfiguration;
+ private final VaultTemplate vaultTemplate;
+ private final VaultTransitOperations transitOperations;
+
+ /**
+ * Creates a VaultCommunicationService that uses Spring Vault.
+ * @param vaultProperties Properties to configure the service
+ * @throws HashiCorpVaultConfigurationException If the configuration was invalid
+ */
+ public StandardHashiCorpVaultCommunicationService(final HashiCorpVaultProperties vaultProperties) throws HashiCorpVaultConfigurationException {
+ this.vaultConfiguration = new HashiCorpVaultConfiguration(vaultProperties);
+
+ final SslConfiguration sslConfiguration = vaultProperties.getUri().contains(HTTPS)
+ ? vaultConfiguration.sslConfiguration() : SslConfiguration.unconfigured();
+
+ final ClientOptions clientOptions = getClientOptions(vaultProperties);
+
+ vaultTemplate = new VaultTemplate(vaultConfiguration.vaultEndpoint(),
+ ClientHttpRequestFactoryFactory.create(clientOptions, sslConfiguration),
+ new SimpleSessionManager(vaultConfiguration.clientAuthentication()));
+
+ transitOperations = vaultTemplate.opsForTransit();
+ }
+
+ private static ClientOptions getClientOptions(HashiCorpVaultProperties vaultProperties) {
+ final ClientOptions clientOptions = new ClientOptions();
+ Duration readTimeoutDuration = clientOptions.getReadTimeout();
+ Duration connectionTimeoutDuration = clientOptions.getConnectionTimeout();
+ final Optional configuredReadTimeout = vaultProperties.getReadTimeout();
+ if (configuredReadTimeout.isPresent()) {
+ readTimeoutDuration = getDuration(configuredReadTimeout.get());
+ }
+ final Optional configuredConnectionTimeout = vaultProperties.getConnectionTimeout();
+ if (configuredConnectionTimeout.isPresent()) {
+ connectionTimeoutDuration = getDuration(configuredConnectionTimeout.get());
+ }
+ return new ClientOptions(connectionTimeoutDuration, readTimeoutDuration);
+ }
+
+ private static Duration getDuration(String formattedDuration) {
+ final double duration = FormatUtils.getPreciseTimeDuration(formattedDuration, TimeUnit.MILLISECONDS);
+ return Duration.ofMillis(Double.valueOf(duration).longValue());
+ }
+
+ @Override
+ public String encrypt(final String transitKey, final byte[] plainText) {
+ return transitOperations.encrypt(transitKey, Plaintext.of(plainText)).getCiphertext();
+ }
+
+ @Override
+ public byte[] decrypt(final String transitKey, final String cipherText) {
+ return transitOperations.decrypt(transitKey, Ciphertext.of(cipherText)).getPlaintext();
+ }
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java
new file mode 100644
index 0000000000..1d92f1c380
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultApplicationContext.java
@@ -0,0 +1,39 @@
+/*
+ * 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.config;
+
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+
+import java.nio.file.Paths;
+
+/**
+ * Basic ApplicationContext that defines resources as FileSystemResource objects.
+ */
+public class HashiCorpVaultApplicationContext extends StaticApplicationContext {
+
+ public HashiCorpVaultApplicationContext(ConfigurableEnvironment env) {
+ this.setEnvironment(env);
+ }
+
+ @Override
+ public Resource getResource(String location) {
+ return new FileSystemResource(Paths.get(location));
+ }
+}
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
new file mode 100644
index 0000000000..44ad4e6ef7
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * 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.config;
+
+import org.apache.nifi.vault.hashicorp.HashiCorpVaultConfigurationException;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.support.ResourcePropertySource;
+import org.springframework.vault.config.EnvironmentVaultConfiguration;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+/**
+ * A Vault configuration that uses the NiFiVaultEnvironment.
+ */
+public class HashiCorpVaultConfiguration extends EnvironmentVaultConfiguration {
+
+ public HashiCorpVaultConfiguration(final HashiCorpVaultProperties vaultProperties) throws HashiCorpVaultConfigurationException {
+ final ConfigurableEnvironment env = new StandardEnvironment();
+
+ try {
+ env.getPropertySources().addFirst(new ResourcePropertySource(new FileSystemResource(Paths.get(vaultProperties.getAuthPropertiesFilename()))));
+ } catch (IOException e) {
+ throw new HashiCorpVaultConfigurationException("Could not load auth properties", e);
+ }
+ env.getPropertySources().addFirst(new HashiCorpVaultPropertySource(vaultProperties));
+
+ this.setApplicationContext(new HashiCorpVaultApplicationContext(env));
+ }
+}
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
new file mode 100644
index 0000000000..867ca0c494
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperties.java
@@ -0,0 +1,246 @@
+/*
+ * 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.config;
+
+import org.apache.nifi.vault.hashicorp.HashiCorpVaultConfigurationException;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Properties to configure the HashiCorpVaultCommunicationService. The only properties considered mandatory are uri and
+ * authPropertiesFilename. See the following link for valid vault authentication properties (default is
+ * vault.authentication=TOKEN, expecting a vault.token property to be supplied).
+ *
+ * @see
+ * https://docs.spring.io/spring-vault/docs/2.3.1/reference/html/#vault.core.environment-vault-configuration
+ */
+public class HashiCorpVaultProperties {
+ public static final String HTTPS = "https";
+ private final String uri;
+ private final String authPropertiesFilename;
+ private final HashiCorpVaultSslProperties ssl;
+ private final Optional connectionTimeout;
+ private final Optional readTimeout;
+
+ 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);
+
+ 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");
+ }
+ validateAuthProperties();
+ }
+
+ private void validateAuthProperties() throws HashiCorpVaultConfigurationException {
+ final File authPropertiesFile = Paths.get(authPropertiesFilename).toFile();
+ if (!authPropertiesFile.exists()) {
+ throw new HashiCorpVaultConfigurationException(String.format("Auth properties file [%s] does not exist", authPropertiesFilename));
+ }
+ }
+
+ @HashiCorpVaultProperty
+ public String getUri() {
+ return uri;
+ }
+
+ @HashiCorpVaultProperty
+ public HashiCorpVaultSslProperties getSsl() {
+ return ssl;
+ }
+
+ public String getAuthPropertiesFilename() {
+ return authPropertiesFilename;
+ }
+
+ public Optional getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ public Optional getReadTimeout() {
+ return readTimeout;
+ }
+
+ /**
+ * Builder for HashiCorpVaultProperties. The only properties that are considered mandatory are uri and authPropertiesFilename.
+ */
+ public static class HashiCorpVaultPropertiesBuilder {
+ private String uri;
+ private String keyStore;
+ private String keyStoreType;
+ private String keyStorePassword;
+ private String trustStore;
+ private String trustStoreType;
+ private String trustStorePassword;
+ private String authPropertiesFilename;
+ private String enabledTlsCipherSuites;
+ private String enabledTlsProtocols;
+ private String connectionTimeout;
+ private String readTimeout;
+
+ /**
+ * Set the Vault URI (e.g., http://localhost:8200). If using https protocol, the KeyStore and TrustStore
+ * properties are expected to also be set.
+ * @param uri Vault's URI
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setUri(String uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ /**
+ * Sets the path to the keyStore.
+ * @param keyStore Path to the keyStore
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setKeyStore(String keyStore) {
+ this.keyStore = keyStore;
+ return this;
+ }
+
+ /**
+ * Sets keyStore type (e.g., JKS, PKCS12).
+ * @param keyStoreType KeyStore type
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setKeyStoreType(String keyStoreType) {
+ this.keyStoreType = keyStoreType;
+ return this;
+ }
+
+ /**
+ * Sets the keyStore password.
+ * @param keyStorePassword KeyStore password
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ return this;
+ }
+
+ /**
+ * Sets the path to the trustStore.
+ * @param trustStore Path to the trustStore
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setTrustStore(String trustStore) {
+ this.trustStore = trustStore;
+ return this;
+ }
+
+ /**
+ * Sets the trustStore type (e.g., JKS, PKCS12).
+ * @param trustStoreType TrustStore type
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setTrustStoreType(String trustStoreType) {
+ this.trustStoreType = trustStoreType;
+ return this;
+ }
+
+ /**
+ * Sets the trustStore passsword.
+ * @param trustStorePassword TrustStore password
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setTrustStorePassword(String trustStorePassword) {
+ this.trustStorePassword = trustStorePassword;
+ return this;
+ }
+
+ /**
+ * Sets the path to the vault authentication properties file. See the following link for valid
+ * vault authentication properties (default is vault.authentication=TOKEN, expecting a vault.token property
+ * to be supplied).
+ * @see
+ * https://docs.spring.io/spring-vault/docs/2.3.1/reference/html/#vault.core.environment-vault-configuration
+ * @param authPropertiesFilename The filename of a properties file containing Spring Vault authentication
+ * properties
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setAuthPropertiesFilename(String authPropertiesFilename) {
+ this.authPropertiesFilename = authPropertiesFilename;
+ return this;
+ }
+
+ /**
+ * Sets an optional comma-separated list of enabled TLS cipher suites.
+ * @param enabledTlsCipherSuites Enabled TLS cipher suites (only these will be enabled)
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setEnabledTlsCipherSuites(String enabledTlsCipherSuites) {
+ this.enabledTlsCipherSuites = enabledTlsCipherSuites;
+ return this;
+ }
+
+ /**
+ * Sets an optional comma-separated list of enabled TLS protocols.
+ * @param enabledTlsProtocols Enabled TLS protocols (only these will be enabled)
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setEnabledTlsProtocols(String enabledTlsProtocols) {
+ this.enabledTlsProtocols = enabledTlsProtocols;
+ return this;
+ }
+
+ /**
+ * Sets the connection timeout for the HTTP client, using the standard NiFi duration format (e.g., 5 secs)
+ * @param connectionTimeout Connection timeout (default is 5 secs)
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setConnectionTimeout(String connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ return this;
+ }
+
+ /**
+ * Sets the read timeout for the HTTP client, using the standard NiFi duration format (e.g., 15 secs).
+ * @param readTimeout Read timeout (default is 15 secs)
+ * @return
+ */
+ public HashiCorpVaultPropertiesBuilder setReadTimeout(String readTimeout) {
+ this.readTimeout = readTimeout;
+ return this;
+ }
+
+ /**
+ * Build the VaultProperties.
+ * @return
+ */
+ public HashiCorpVaultProperties build() {
+ return new HashiCorpVaultProperties(uri, keyStore, keyStoreType, keyStorePassword, trustStore, trustStoreType,
+ trustStorePassword, authPropertiesFilename, enabledTlsCipherSuites, enabledTlsProtocols, connectionTimeout, readTimeout);
+ }
+ }
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java
new file mode 100644
index 0000000000..81bd87ec99
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultProperty.java
@@ -0,0 +1,31 @@
+/*
+ * 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.config;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a vault property that should be mapped to a Spring Vault property key.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HashiCorpVaultProperty {
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java
new file mode 100644
index 0000000000..446efc121c
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultPropertySource.java
@@ -0,0 +1,60 @@
+/*
+ * 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.config;
+
+import org.apache.nifi.vault.hashicorp.config.lookup.BeanPropertyLookup;
+import org.apache.nifi.vault.hashicorp.config.lookup.PropertyLookup;
+import org.springframework.core.env.PropertySource;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HashiCorpVaultPropertySource extends PropertySource {
+ private static final String PREFIX = "vault";
+ private static final Pattern DASH_LETTER_PATTERN = Pattern.compile("-[a-z]");
+
+ private PropertyLookup propertyLookup;
+
+ public HashiCorpVaultPropertySource(HashiCorpVaultProperties source) {
+ super(HashiCorpVaultPropertySource.class.getName(), source);
+
+ propertyLookup = new BeanPropertyLookup(PREFIX, HashiCorpVaultProperties.class, HashiCorpVaultProperty.class);
+ }
+
+ @Override
+ public Object getProperty(final String key) {
+ Objects.requireNonNull(key, "Property key cannot be null");
+
+ return propertyLookup.getPropertyValue(getPropertyKey(key), getSource());
+ }
+
+ /**
+ * Converts key names from format test-value to testValue
+ * @param springPropertyKey A Spring Vault property key
+ * @return
+ */
+ private String getPropertyKey(String springPropertyKey) {
+ final Matcher m = DASH_LETTER_PATTERN.matcher(springPropertyKey);
+ final StringBuffer result = new StringBuffer();
+ while (m.find()) {
+ m.appendReplacement(result, m.group(0).substring(1).toUpperCase());
+ }
+ m.appendTail(result);
+ return result.toString();
+ }
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java
new file mode 100644
index 0000000000..99d63d53b1
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultSslProperties.java
@@ -0,0 +1,81 @@
+/*
+ * 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.config;
+
+public class HashiCorpVaultSslProperties {
+ private final String keyStore;
+ private final String keyStoreType;
+ private final String keyStorePassword;
+ private final String trustStore;
+ private final String trustStoreType;
+ private final String trustStorePassword;
+ private final String enabledCipherSuites;
+ private final String enabledProtocols;
+
+ public HashiCorpVaultSslProperties(String keyStore, String keyStoreType, String keyStorePassword, String trustStore,
+ String trustStoreType, String trustStorePassword,
+ String enabledCipherSuites, String enabledProtocols) {
+ this.keyStore = keyStore;
+ this.keyStoreType = keyStoreType;
+ this.keyStorePassword = keyStorePassword;
+ this.trustStore = trustStore;
+ this.trustStoreType = trustStoreType;
+ this.trustStorePassword = trustStorePassword;
+ this.enabledCipherSuites = enabledCipherSuites;
+ this.enabledProtocols = enabledProtocols;
+ }
+
+ @HashiCorpVaultProperty
+ public String getKeyStore() {
+ return keyStore;
+ }
+
+ @HashiCorpVaultProperty
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ @HashiCorpVaultProperty
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ @HashiCorpVaultProperty
+ public String getTrustStore() {
+ return trustStore;
+ }
+
+ @HashiCorpVaultProperty
+ public String getTrustStoreType() {
+ return trustStoreType;
+ }
+
+ @HashiCorpVaultProperty
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ @HashiCorpVaultProperty
+ public String getEnabledCipherSuites() {
+ return enabledCipherSuites;
+ }
+
+ @HashiCorpVaultProperty
+ public String getEnabledProtocols() {
+ return enabledProtocols;
+ }
+}
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
new file mode 100644
index 0000000000..2ad1ac101c
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/BeanPropertyLookup.java
@@ -0,0 +1,81 @@
+/*
+ * 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.config.lookup;
+
+import org.apache.nifi.vault.hashicorp.HashiCorpVaultConfigurationException;
+import org.springframework.beans.BeanUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * A property lookup that indexes the properties of a Java bean.
+ */
+public class BeanPropertyLookup extends PropertyLookup {
+ private static final String SEPARATOR = ".";
+
+ private final Map propertyLookupMap;
+
+ public BeanPropertyLookup(final String prefix, final Class> beanClass, final Class extends Annotation> propertyFilter) {
+ this(prefix, beanClass, propertyFilter, null);
+ }
+
+ private BeanPropertyLookup(final String prefix, final Class> beanClass, final Class extends Annotation> propertyFilter,
+ final PropertyDescriptor propertyDescriptor) {
+ super(propertyDescriptor);
+ propertyLookupMap = Arrays.stream(BeanUtils.getPropertyDescriptors(beanClass))
+ .filter(pd -> pd.getReadMethod().getAnnotation(propertyFilter) != null)
+ .collect(Collectors.toMap(
+ pd -> getPropertyKey(prefix, pd),
+ pd -> pd.getReadMethod().getReturnType().equals(String.class) ? new ValuePropertyLookup(pd)
+ : new BeanPropertyLookup(getPropertyKey(prefix, pd), pd.getReadMethod().getReturnType(), propertyFilter, pd)
+ ));
+ }
+
+ private static String getPropertyKey(final String prefix, final PropertyDescriptor propertyDescriptor) {
+ return prefix == null ? propertyDescriptor.getDisplayName() : String.join(SEPARATOR, prefix, propertyDescriptor.getDisplayName());
+ }
+
+ @Override
+ public Object getPropertyValue(final String propertyKey, final Object obj) {
+ if (propertyLookupMap.containsKey(propertyKey)) {
+ final PropertyLookup propertyLookup = propertyLookupMap.get(propertyKey);
+ return propertyLookup.getPropertyValue(propertyKey, propertyLookup.getEnclosingObject(obj));
+ }
+ for(final Map.Entry entry : propertyLookupMap.entrySet()) {
+ final String key = entry.getKey();
+ if (propertyKey.startsWith(key + SEPARATOR)) {
+ final PropertyLookup propertyLookup = entry.getValue();
+ return propertyLookup.getPropertyValue(propertyKey, propertyLookup.getEnclosingObject(obj));
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object getEnclosingObject(Object obj) {
+ try {
+ return getPropertyDescriptor().getReadMethod().invoke(obj);
+ } catch (final IllegalAccessException | InvocationTargetException e) {
+ throw new HashiCorpVaultConfigurationException("Could not invoke " + getPropertyDescriptor().getDisplayName());
+ }
+ }
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java
new file mode 100644
index 0000000000..a2cd8013e6
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/PropertyLookup.java
@@ -0,0 +1,50 @@
+/*
+ * 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.config.lookup;
+
+import java.beans.PropertyDescriptor;
+
+/**
+ * Provides a method of looking up property values.
+ */
+public abstract class PropertyLookup {
+
+ private final PropertyDescriptor propertyDescriptor;
+
+ protected PropertyLookup(final PropertyDescriptor propertyDescriptor) {
+ this.propertyDescriptor = propertyDescriptor;
+ }
+
+ /**
+ * Returns the value of a property.
+ * @param propertyKey The property key (e.g., object.child.propertyValue)
+ * @param obj The object from which to retrieve the property.
+ * @return The property value
+ */
+ public abstract Object getPropertyValue(final String propertyKey, final Object obj);
+
+ /**
+ * Returns the enclosing object of the property.
+ * @param obj The top level object
+ * @return The appropriate object that contains the property
+ */
+ public abstract Object getEnclosingObject(final Object obj);
+
+ protected PropertyDescriptor getPropertyDescriptor() {
+ return propertyDescriptor;
+ }
+}
diff --git a/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java
new file mode 100644
index 0000000000..4a1e744061
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/main/java/org/apache/nifi/vault/hashicorp/config/lookup/ValuePropertyLookup.java
@@ -0,0 +1,46 @@
+/*
+ * 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.config.lookup;
+
+import org.apache.nifi.vault.hashicorp.HashiCorpVaultConfigurationException;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A simple property lookup that invokes the property descriptor to retrieve the value of the property.
+ */
+public class ValuePropertyLookup extends PropertyLookup {
+
+ public ValuePropertyLookup(final PropertyDescriptor propertyDescriptor) {
+ super(propertyDescriptor);
+ }
+
+ @Override
+ public Object getPropertyValue(final String propertyKey, final Object obj) {
+ try {
+ return getPropertyDescriptor().getReadMethod().invoke(obj);
+ } catch (final IllegalAccessException | InvocationTargetException e) {
+ throw new HashiCorpVaultConfigurationException("Could not get the value of " + propertyKey);
+ }
+ }
+
+ @Override
+ public Object getEnclosingObject(Object obj) {
+ return obj;
+ }
+}
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
new file mode 100644
index 0000000000..60d64a9168
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationServiceIT.java
@@ -0,0 +1,67 @@
+/*
+ * 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.vault.hashicorp.config.HashiCorpVaultProperties;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The simplest way to run this test is by installing Vault locally, then running:
+ *
+ * vault server -dev
+ * vault secrets enable transit
+ * vault write -f transit/keys/nifi
+ *
+ * Make note of the Root Token and create a properties file with the contents:
+ * vault.token=[Root Token]
+ *
+ * Then, set the system property -Dvault.auth.properties to the file path of the above properties file when
+ * running the integration test.
+ */
+public class StandardHashiCorpVaultCommunicationServiceIT {
+
+ private static final String TRANSIT_KEY = "nifi";
+
+ private HashiCorpVaultCommunicationService vcs;
+
+ @Before
+ public void init() {
+ vcs = new StandardHashiCorpVaultCommunicationService(new HashiCorpVaultProperties.HashiCorpVaultPropertiesBuilder()
+ .setAuthPropertiesFilename(System.getProperty("vault.auth.properties"))
+ .setUri("http://127.0.0.1:8200")
+ .build());
+ }
+
+ @Test
+ public void testEncryptDecrypt() {
+ this.runEncryptDecryptTest();
+ }
+
+ public void runEncryptDecryptTest() {
+ String plaintext = "this is the plaintext";
+
+ String ciphertext = vcs.encrypt(TRANSIT_KEY, plaintext.getBytes(StandardCharsets.UTF_8));
+
+ byte[] decrypted = vcs.decrypt(TRANSIT_KEY, ciphertext);
+
+ Assert.assertEquals(plaintext, new String(decrypted, StandardCharsets.UTF_8));
+ }
+}
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
new file mode 100644
index 0000000000..38cb2d9484
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java
@@ -0,0 +1,195 @@
+/*
+ * 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.vault.hashicorp.config.HashiCorpVaultConfiguration;
+import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultProperties;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.support.SslConfiguration;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class TestHashiCorpVaultConfiguration {
+ public static final String VAULT_AUTHENTICATION = "vault.authentication";
+ public static final String VAULT_TOKEN = "vault.token";
+
+ private static final String TEST_TOKEN_VALUE = "test-token";
+ private static final String TOKEN_VALUE = "TOKEN";
+ private static final String URI_VALUE = "http://localhost:8200";
+ private static final String KEYSTORE_PASSWORD_VALUE = "keystorePassword";
+ private static final String KEYSTORE_TYPE_VALUE = "keystoreType";
+ private static final String TRUSTSTORE_PASSWORD_VALUE = "truststorePassword";
+ private static final String TRUSTSTORE_TYPE_VALUE = "truststoreType";
+ public static final String TLS_V_1_3_VALUE = "TLSv1.3";
+ public static final String TEST_CIPHER_SUITE_VALUE = "Test cipher suite";
+
+ private static Path keystoreFile;
+ private static Path truststoreFile;
+
+ private HashiCorpVaultProperties.HashiCorpVaultPropertiesBuilder propertiesBuilder;
+ private static File authProps;
+
+ private HashiCorpVaultConfiguration config;
+
+ @BeforeClass
+ public static void initClass() throws IOException {
+ keystoreFile = Files.createTempFile("test", ".jks");
+ truststoreFile = Files.createTempFile("test", ".jks");
+ authProps = writeBasicVaultAuthProperties();
+ }
+
+ @AfterClass
+ public static void cleanUpClass() throws IOException {
+ Files.deleteIfExists(keystoreFile);
+ Files.deleteIfExists(truststoreFile);
+ Files.deleteIfExists(authProps.toPath());
+ }
+
+ @Before
+ public void init() throws IOException {
+ propertiesBuilder = new HashiCorpVaultProperties.HashiCorpVaultPropertiesBuilder()
+ .setUri(URI_VALUE)
+ .setAuthPropertiesFilename(authProps.getAbsolutePath());
+
+ }
+
+ public static File writeVaultAuthProperties(final Map properties) throws IOException {
+ File authProps = File.createTempFile("vault-", ".properties");
+ writeProperties(properties, authProps);
+ return authProps;
+ }
+
+ /**
+ * Writes a new temp vault authentication properties file with the following properties:
+ * vault.authentication=TOKEN
+ * vault.token=test-token
+ * @return The created temp file
+ * @throws IOException If the file could not be written
+ */
+ public static File writeBasicVaultAuthProperties() throws IOException {
+ Map properties = new HashMap<>();
+ properties.put(VAULT_AUTHENTICATION, TOKEN_VALUE);
+ properties.put(VAULT_TOKEN, TEST_TOKEN_VALUE);
+ return writeVaultAuthProperties(properties);
+ }
+
+ public static void writeProperties(Map props, File authProps) throws IOException {
+ Properties properties = new Properties();
+
+ for (Map.Entry entry : props.entrySet()) {
+ properties.put(entry.getKey(), entry.getValue());
+ }
+ try (Writer writer = new FileWriter(authProps)) {
+ properties.store(writer, "Vault test authentication properties");
+ }
+ }
+
+ public void runTest() {
+ config = new HashiCorpVaultConfiguration(propertiesBuilder.build());
+
+ VaultEndpoint endpoint = config.vaultEndpoint();
+ Assert.assertEquals("localhost", endpoint.getHost());
+ Assert.assertEquals(8200, endpoint.getPort());
+ Assert.assertEquals("http", endpoint.getScheme());
+
+ ClientAuthentication clientAuthentication = config.clientAuthentication();
+ Assert.assertNotNull(clientAuthentication);
+ }
+
+ @Test
+ public void testBasicProperties() {
+ this.runTest();
+ }
+
+ @Test
+ public void testTlsProperties() throws IOException {
+ propertiesBuilder.setKeyStore(keystoreFile.toFile().getAbsolutePath());
+ propertiesBuilder.setKeyStorePassword(KEYSTORE_PASSWORD_VALUE);
+ propertiesBuilder.setKeyStoreType(KEYSTORE_TYPE_VALUE);
+ propertiesBuilder.setTrustStore(truststoreFile.toFile().getAbsolutePath());
+ propertiesBuilder.setTrustStorePassword(TRUSTSTORE_PASSWORD_VALUE);
+ propertiesBuilder.setTrustStoreType(TRUSTSTORE_TYPE_VALUE);
+ propertiesBuilder.setEnabledTlsProtocols(TLS_V_1_3_VALUE);
+ propertiesBuilder.setEnabledTlsCipherSuites(TEST_CIPHER_SUITE_VALUE);
+
+ this.runTest();
+
+ SslConfiguration sslConfiguration = config.sslConfiguration();
+ Assert.assertEquals(keystoreFile.toFile().getAbsolutePath(), sslConfiguration.getKeyStoreConfiguration().getResource().getFile().getAbsolutePath());
+ Assert.assertEquals(KEYSTORE_PASSWORD_VALUE, new String(sslConfiguration.getKeyStoreConfiguration().getStorePassword()));
+ Assert.assertEquals(KEYSTORE_TYPE_VALUE, sslConfiguration.getKeyStoreConfiguration().getStoreType());
+ Assert.assertEquals(truststoreFile.toFile().getAbsolutePath(), sslConfiguration.getTrustStoreConfiguration().getResource().getFile().getAbsolutePath());
+ Assert.assertEquals(TRUSTSTORE_PASSWORD_VALUE, new String(sslConfiguration.getTrustStoreConfiguration().getStorePassword()));
+ Assert.assertEquals(TRUSTSTORE_TYPE_VALUE, sslConfiguration.getTrustStoreConfiguration().getStoreType());
+ Assert.assertEquals(Arrays.asList(TLS_V_1_3_VALUE), sslConfiguration.getEnabledProtocols());
+ Assert.assertEquals(Arrays.asList(TEST_CIPHER_SUITE_VALUE), sslConfiguration.getEnabledCipherSuites());
+ }
+
+ @Test
+ public void testInvalidTLS() {
+ propertiesBuilder.setUri(URI_VALUE.replace("http", "https"));
+ Assert.assertThrows(NullPointerException.class, () -> this.runTest());
+ }
+
+ @Test
+ public void testMissingAuthToken() throws IOException {
+ File authProperties = null;
+ try {
+ final Map props = new HashMap<>();
+ props.put(VAULT_AUTHENTICATION, TOKEN_VALUE);
+ authProperties = writeVaultAuthProperties(props);
+ propertiesBuilder.setAuthPropertiesFilename(authProperties.getAbsolutePath());
+
+ Assert.assertThrows(IllegalArgumentException.class, () -> this.runTest());
+ } finally {
+ if (authProperties != null) {
+ Files.deleteIfExists(authProperties.toPath());
+ }
+ }
+ }
+
+ @Test
+ public void testMissingAuthType() throws IOException {
+ File authProperties = null;
+ try {
+ final Map props = new HashMap<>();
+ authProperties = writeVaultAuthProperties(props);
+ propertiesBuilder.setAuthPropertiesFilename(authProperties.getAbsolutePath());
+
+ Assert.assertThrows(IllegalArgumentException.class, () -> this.runTest());
+ } finally {
+ if (authProperties != null) {
+ Files.deleteIfExists(authProperties.toPath());
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000000..f9b102a81a
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java
@@ -0,0 +1,125 @@
+/*
+ * 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.security.util.KeyStoreUtils;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultProperties;
+import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultSslProperties;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+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";
+
+ private HashiCorpVaultProperties properties;
+ private HashiCorpVaultSslProperties sslProperties;
+ private File authProps;
+
+ @Before
+ public void init() throws IOException {
+ authProps = TestHashiCorpVaultConfiguration.writeBasicVaultAuthProperties();
+
+ 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);
+ }
+
+ @After
+ public void cleanUp() throws IOException {
+ Files.deleteIfExists(authProps.toPath());
+ }
+
+ private HashiCorpVaultCommunicationService configureService() {
+ return new StandardHashiCorpVaultCommunicationService(properties);
+ }
+
+ @Test
+ 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 each to check if they are configured
+ Mockito.verify(properties, Mockito.times(1)).getConnectionTimeout();
+ Mockito.verify(properties, Mockito.times(1)).getReadTimeout();
+
+ // These should not be called because TLS is not configured
+ this.ensureTlsPropertiesAccessed(0);
+ }
+
+ private void ensureTlsPropertiesAccessed(int numberOfTimes) {
+ Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getKeyStore();
+ Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getKeyStoreType();
+ Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getKeyStorePassword();
+ 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
+ public void testTimeouts() {
+ Mockito.when(properties.getConnectionTimeout()).thenReturn(Optional.of("20 secs"));
+ Mockito.when(properties.getReadTimeout()).thenReturn(Optional.of("40 secs"));
+ this.configureService();
+
+ Mockito.verify(properties, Mockito.times(1)).getConnectionTimeout();
+ Mockito.verify(properties, Mockito.times(1)).getReadTimeout();
+ }
+
+ @Test
+ public void testTLS() throws GeneralSecurityException, IOException {
+ TlsConfiguration tlsConfiguration = KeyStoreUtils.createTlsConfigAndNewKeystoreTruststore();
+ try {
+ 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())
+ .collect(Collectors.joining(",")));
+ Mockito.when(sslProperties.getEnabledCipherSuites()).thenReturn(CIPHER_SUITE_VALUE);
+
+ Mockito.when(properties.getUri()).thenReturn(URI_VALUE.replace("http", "https"));
+ this.configureService();
+
+ this.ensureTlsPropertiesAccessed(1);
+ } finally {
+ Files.deleteIfExists(Paths.get(tlsConfiguration.getKeystorePath()));
+ Files.deleteIfExists(Paths.get(tlsConfiguration.getTruststorePath()));
+ }
+ }
+}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 7bda15b889..5ef5bb49c4 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -48,6 +48,7 @@
nifi-socket-utils
nifi-utils
nifi-uuid5
+ nifi-vault-utils
nifi-web-utils
nifi-write-ahead-log