From 6b5e37a201e839cc78baf57eb2318ca7d4e131de Mon Sep 17 00:00:00 2001 From: Pierre Villard Date: Mon, 6 Jul 2026 16:22:14 +0200 Subject: [PATCH 1/2] NIFI-16078 - Fix StandardHashiCorpVaultClientService for non-token authentication --- ...ardHashiCorpVaultCommunicationService.java | 2 + .../config/HashiCorpVaultConfiguration.java | 28 +++++++++- .../TestHashiCorpVaultConfiguration.java | 55 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) 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..72d6b9a83700 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 @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; 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 +149,60 @@ public void testBasicProperties() { this.runTest("http"); } + @Test + public void testKubernetesClientAuthentication() throws IOException { + File k8sAuthProps = null; + Path tokenFile = null; + try { + tokenFile = Files.createTempFile("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()); + k8sAuthProps = writeVaultAuthProperties(props); + propertiesBuilder.setAuthPropertiesFilename(k8sAuthProps.getAbsolutePath()); + + config = new HashiCorpVaultConfiguration( + createIsolatedEnvironment(), new HashiCorpVaultPropertySource(propertiesBuilder.build())); + config.setClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); + + final ClientAuthentication clientAuthentication = config.clientAuthentication(); + assertNotNull(clientAuthentication); + } finally { + if (k8sAuthProps != null) { + Files.deleteIfExists(k8sAuthProps.toPath()); + } + if (tokenFile != null) { + Files.deleteIfExists(tokenFile); + } + } + } + + @Test + public void testAwsEc2ClientAuthentication() throws IOException { + File awsAuthProps = null; + try { + final Map props = new HashMap<>(); + props.put(VAULT_AUTHENTICATION, "AWS_EC2"); + props.put("vault.aws-ec2.role", "test-role"); + awsAuthProps = writeVaultAuthProperties(props); + propertiesBuilder.setAuthPropertiesFilename(awsAuthProps.getAbsolutePath()); + + config = new HashiCorpVaultConfiguration( + createIsolatedEnvironment(), new HashiCorpVaultPropertySource(propertiesBuilder.build())); + config.setClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); + + final ClientAuthentication clientAuthentication = config.clientAuthentication(); + assertNotNull(clientAuthentication); + } finally { + if (awsAuthProps != null) { + Files.deleteIfExists(awsAuthProps.toPath()); + } + } + } + @Test public void testKvVersion() { config = new HashiCorpVaultConfiguration(new HashiCorpVaultPropertySource(propertiesBuilder.build())); From b2a98892486cbc7ff0099c1c4f5eb3dc2f41fead Mon Sep 17 00:00:00 2001 From: Pierre Villard Date: Thu, 9 Jul 2026 18:03:43 +0200 Subject: [PATCH 2/2] NIFI-16078 - Use JUnit 5 @TempDir in Vault auth tests --- .../TestHashiCorpVaultConfiguration.java | 77 ++++++++----------- 1 file changed, 31 insertions(+), 46 deletions(-) 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 72d6b9a83700..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,6 +23,7 @@ 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; @@ -150,57 +151,41 @@ public void testBasicProperties() { } @Test - public void testKubernetesClientAuthentication() throws IOException { - File k8sAuthProps = null; - Path tokenFile = null; - try { - tokenFile = Files.createTempFile("vault-k8s-token", ".jwt"); - Files.writeString(tokenFile, "test-jwt-token"); + 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 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()); - k8sAuthProps = writeVaultAuthProperties(props); - propertiesBuilder.setAuthPropertiesFilename(k8sAuthProps.getAbsolutePath()); - - config = new HashiCorpVaultConfiguration( - createIsolatedEnvironment(), new HashiCorpVaultPropertySource(propertiesBuilder.build())); - config.setClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); - - final ClientAuthentication clientAuthentication = config.clientAuthentication(); - assertNotNull(clientAuthentication); - } finally { - if (k8sAuthProps != null) { - Files.deleteIfExists(k8sAuthProps.toPath()); - } - if (tokenFile != null) { - Files.deleteIfExists(tokenFile); - } - } + final ClientAuthentication clientAuthentication = config.clientAuthentication(); + assertNotNull(clientAuthentication); } @Test - public void testAwsEc2ClientAuthentication() throws IOException { - File awsAuthProps = null; - try { - final Map props = new HashMap<>(); - props.put(VAULT_AUTHENTICATION, "AWS_EC2"); - props.put("vault.aws-ec2.role", "test-role"); - awsAuthProps = writeVaultAuthProperties(props); - propertiesBuilder.setAuthPropertiesFilename(awsAuthProps.getAbsolutePath()); + 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()); - config = new HashiCorpVaultConfiguration( - createIsolatedEnvironment(), new HashiCorpVaultPropertySource(propertiesBuilder.build())); - config.setClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); - - final ClientAuthentication clientAuthentication = config.clientAuthentication(); - assertNotNull(clientAuthentication); - } finally { - if (awsAuthProps != null) { - Files.deleteIfExists(awsAuthProps.toPath()); - } - } + final ClientAuthentication clientAuthentication = config.clientAuthentication(); + assertNotNull(clientAuthentication); } @Test