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