mirror of https://github.com/apache/nifi.git
NIFI-8445: Implemented HashiCorpVaultCommunicationService in nifi-vault-utils
This closes #5034 Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
parent
51aae5bcf6
commit
ed591e0f22
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue