You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2021/05/14 17:27:41 UTC

[nifi] branch main updated: NIFI-8445: Implemented HashiCorpVaultCommunicationService in nifi-vault-utils

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 ed591e0  NIFI-8445: Implemented HashiCorpVaultCommunicationService in nifi-vault-utils
ed591e0 is described below

commit ed591e0f222cab83ca0c4bff830274ae8f48bcea
Author: Joe Gresock <jg...@gmail.com>
AuthorDate: Tue Apr 27 11:48:44 2021 -0400

    NIFI-8445: Implemented HashiCorpVaultCommunicationService in nifi-vault-utils
    
    This closes #5034
    
    Signed-off-by: David Handermann <ex...@apache.org>
---
 nifi-commons/nifi-vault-utils/pom.xml              |  60 +++++
 .../HashiCorpVaultCommunicationService.java        |  46 ++++
 .../HashiCorpVaultConfigurationException.java      |  33 +++
 ...StandardHashiCorpVaultCommunicationService.java |  94 ++++++++
 .../config/HashiCorpVaultApplicationContext.java   |  39 ++++
 .../config/HashiCorpVaultConfiguration.java        |  46 ++++
 .../hashicorp/config/HashiCorpVaultProperties.java | 246 +++++++++++++++++++++
 .../hashicorp/config/HashiCorpVaultProperty.java   |  31 +++
 .../config/HashiCorpVaultPropertySource.java       |  60 +++++
 .../config/HashiCorpVaultSslProperties.java        |  81 +++++++
 .../config/lookup/BeanPropertyLookup.java          |  81 +++++++
 .../hashicorp/config/lookup/PropertyLookup.java    |  50 +++++
 .../config/lookup/ValuePropertyLookup.java         |  46 ++++
 ...andardHashiCorpVaultCommunicationServiceIT.java |  67 ++++++
 .../hashicorp/TestHashiCorpVaultConfiguration.java | 195 ++++++++++++++++
 ...StandardHashiCorpVaultCommunicationService.java | 125 +++++++++++
 nifi-commons/pom.xml                               |   1 +
 17 files changed, 1301 insertions(+)

diff --git a/nifi-commons/nifi-vault-utils/pom.xml b/nifi-commons/nifi-vault-utils/pom.xml
new file mode 100644
index 0000000..1903889
--- /dev/null
+++ b/nifi-commons/nifi-vault-utils/pom.xml
@@ -0,0 +1,60 @@
+<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.14.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-vault-utils</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.vault</groupId>
+            <artifactId>spring-vault-core</artifactId>
+            <version>2.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>5.3.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-utils</artifactId>
+            <version>1.14.0-SNAPSHOT</version>
+        </dependency>
+        <!-- Runtime dependency to enable TLS client configuration -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.9.1</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-utils-api</artifactId>
+            <version>1.14.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-utils</artifactId>
+            <version>1.14.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
+
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 0000000..977b369
--- /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 <a href="https://www.vaultproject.io/">https://www.vaultproject.io/</a>
+ */
+public interface HashiCorpVaultCommunicationService {
+
+    /**
+     * Encrypts the given plaintext using Vault's Transit Secrets Engine.
+     *
+     * @see <a href="https://www.vaultproject.io/api-docs/secret/transit">https://www.vaultproject.io/api-docs/secret/transit</a>
+     * @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 <a href="https://www.vaultproject.io/api-docs/secret/transit">https://www.vaultproject.io/api-docs/secret/transit</a>
+     * @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 0000000..8948d43
--- /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 0000000..8f92fb3
--- /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<String> configuredReadTimeout = vaultProperties.getReadTimeout();
+        if (configuredReadTimeout.isPresent()) {
+            readTimeoutDuration = getDuration(configuredReadTimeout.get());
+        }
+        final Optional<String> 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 0000000..1d92f1c
--- /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 0000000..44ad4e6
--- /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 0000000..867ca0c
--- /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 <a href="https://docs.spring.io/spring-vault/docs/2.3.1/reference/html/#vault.core.environment-vault-configuration">
+ *     https://docs.spring.io/spring-vault/docs/2.3.1/reference/html/#vault.core.environment-vault-configuration</a>
+ */
+public class HashiCorpVaultProperties {
+    public static final String HTTPS = "https";
+    private final String uri;
+    private final String authPropertiesFilename;
+    private final HashiCorpVaultSslProperties ssl;
+    private final Optional<String> connectionTimeout;
+    private final Optional<String> 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<String> getConnectionTimeout() {
+        return connectionTimeout;
+    }
+
+    public Optional<String> 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 <a href="https://docs.spring.io/spring-vault/docs/2.3.1/reference/html/#vault.core.environment-vault-configuration">
+         *     https://docs.spring.io/spring-vault/docs/2.3.1/reference/html/#vault.core.environment-vault-configuration</a>
+         * @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 0000000..81bd87e
--- /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 0000000..446efc1
--- /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<HashiCorpVaultProperties> {
+    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 0000000..99d63d5
--- /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 0000000..2ad1ac1
--- /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<String, PropertyLookup> 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<String, PropertyLookup> 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 0000000..a2cd801
--- /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 0000000..4a1e744
--- /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 0000000..60d64a9
--- /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 0000000..38cb2d9
--- /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<String, String> 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<String, String> properties = new HashMap<>();
+        properties.put(VAULT_AUTHENTICATION, TOKEN_VALUE);
+        properties.put(VAULT_TOKEN, TEST_TOKEN_VALUE);
+        return writeVaultAuthProperties(properties);
+    }
+
+    public static void writeProperties(Map<String, String> props, File authProps) throws IOException {
+        Properties properties = new Properties();
+
+        for (Map.Entry<String, String> 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<String, String> 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<String, String> 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 0000000..f9b102a
--- /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 7bda15b..5ef5bb4 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -48,6 +48,7 @@
         <module>nifi-socket-utils</module>
         <module>nifi-utils</module>
         <module>nifi-uuid5</module>
+        <module>nifi-vault-utils</module>
         <module>nifi-web-utils</module>
         <module>nifi-write-ahead-log</module>
     </modules>