diff --git a/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java index bcda62c654c6..7b10615f5c33 100644 --- a/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java +++ b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/StandardHashiCorpVaultCommunicationService.java @@ -107,6 +107,8 @@ public StandardHashiCorpVaultCommunicationService(final PropertySource... pro final VaultEndpoint vaultEndpoint = vaultConfiguration.vaultEndpoint(); final RestTemplate restTemplate = VaultClients.createRestTemplate(vaultEndpoint, clientHttpRequestFactory); + vaultConfiguration.setClientHttpRequestFactory(clientHttpRequestFactory); + final VaultClient.Builder vaultClientBuilder = VaultClient.builder(restTemplate).endpoint(vaultEndpoint); final String namespace = vaultConfiguration.getNamespace(); if (namespace != null && !namespace.isEmpty()) { diff --git a/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java index 012f0fb83e34..190684aafd7a 100644 --- a/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java +++ b/nifi-commons/nifi-hashicorp-vault/src/main/java/org/apache/nifi/vault/hashicorp/config/HashiCorpVaultConfiguration.java @@ -23,6 +23,9 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.support.ResourcePropertySource; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.vault.client.RestTemplateFactory; +import org.springframework.vault.config.AbstractVaultConfiguration; import org.springframework.vault.config.EnvironmentVaultConfiguration; import org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend; import org.springframework.vault.support.ClientOptions; @@ -65,9 +68,14 @@ public String getKey() { private static final String HTTPS = "https"; + private static final String CLIENT_HTTP_REQUEST_FACTORY_WRAPPER_BEAN = "clientHttpRequestFactoryWrapper"; + private final SslConfiguration sslConfiguration; private final ClientOptions clientOptions; private final KeyValueBackend keyValueBackend; + private final HashiCorpVaultApplicationContext applicationContext; + + private AbstractVaultConfiguration.ClientFactoryWrapper clientFactoryWrapper; /** * Creates a HashiCorpVaultConfiguration from property sources, in increasing precedence. @@ -118,7 +126,8 @@ public HashiCorpVaultConfiguration(final ConfigurableEnvironment env, final Prop } this.keyValueBackend = keyValueBackend; - this.setApplicationContext(new HashiCorpVaultApplicationContext(env)); + this.applicationContext = new HashiCorpVaultApplicationContext(env); + this.setApplicationContext(applicationContext); sslConfiguration = env.getProperty(VaultConfigurationKey.URI.key).contains(HTTPS) ? super.sslConfiguration() : SslConfiguration.unconfigured(); @@ -130,6 +139,23 @@ public KeyValueBackend getKeyValueBackend() { return keyValueBackend; } + /** + * Registers the given request factory as the {@code clientHttpRequestFactoryWrapper} bean and uses it + * to build the {@link RestTemplateFactory}. Spring Vault authentication methods other than token + * authentication build their own Vault client from these, so this must be called before + * {@link #clientAuthentication()} to supply NiFi's configured request factory. + * @param clientHttpRequestFactory The request factory used for Vault communication + */ + public void setClientHttpRequestFactory(final ClientHttpRequestFactory clientHttpRequestFactory) { + this.clientFactoryWrapper = new AbstractVaultConfiguration.ClientFactoryWrapper(clientHttpRequestFactory); + applicationContext.getBeanFactory().registerSingleton(CLIENT_HTTP_REQUEST_FACTORY_WRAPPER_BEAN, clientFactoryWrapper); + } + + @Override + protected RestTemplateFactory getRestTemplateFactory() { + return clientFactoryWrapper == null ? super.getRestTemplateFactory() : restTemplateFactory(clientFactoryWrapper); + } + /** * A convenience method to create a PropertySource from a file on disk. * @param filename The properties filename. diff --git a/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java index 41bf16b3b38c..1dbec16c053e 100644 --- a/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java +++ b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestHashiCorpVaultConfiguration.java @@ -23,8 +23,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.vault.authentication.ClientAuthentication; import org.springframework.vault.client.VaultEndpoint; import org.springframework.vault.core.VaultKeyValueOperationsSupport; @@ -148,6 +150,44 @@ public void testBasicProperties() { this.runTest("http"); } + @Test + public void testKubernetesClientAuthentication(@TempDir Path tempDir) throws IOException { + final Path tokenFile = Files.createTempFile(tempDir, "vault-k8s-token", ".jwt"); + Files.writeString(tokenFile, "test-jwt-token"); + + final Map props = new HashMap<>(); + props.put(VAULT_AUTHENTICATION, "KUBERNETES"); + props.put("vault.kubernetes.role", "test-role"); + props.put("vault.kubernetes.service-account-token-file", tokenFile.toFile().getAbsolutePath()); + final File k8sAuthProps = Files.createTempFile(tempDir, "vault-", ".properties").toFile(); + writeProperties(props, k8sAuthProps); + propertiesBuilder.setAuthPropertiesFilename(k8sAuthProps.getAbsolutePath()); + + config = new HashiCorpVaultConfiguration( + createIsolatedEnvironment(), new HashiCorpVaultPropertySource(propertiesBuilder.build())); + config.setClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); + + final ClientAuthentication clientAuthentication = config.clientAuthentication(); + assertNotNull(clientAuthentication); + } + + @Test + public void testAwsEc2ClientAuthentication(@TempDir Path tempDir) throws IOException { + final Map props = new HashMap<>(); + props.put(VAULT_AUTHENTICATION, "AWS_EC2"); + props.put("vault.aws-ec2.role", "test-role"); + final File awsAuthProps = Files.createTempFile(tempDir, "vault-", ".properties").toFile(); + writeProperties(props, awsAuthProps); + propertiesBuilder.setAuthPropertiesFilename(awsAuthProps.getAbsolutePath()); + + config = new HashiCorpVaultConfiguration( + createIsolatedEnvironment(), new HashiCorpVaultPropertySource(propertiesBuilder.build())); + config.setClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); + + final ClientAuthentication clientAuthentication = config.clientAuthentication(); + assertNotNull(clientAuthentication); + } + @Test public void testKvVersion() { config = new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build()));