From bb7fab6b6d177bef7849d74945309f3e72576be1 Mon Sep 17 00:00:00 2001 From: exceptionfactory Date: Mon, 14 Oct 2024 23:42:41 -0500 Subject: [PATCH] NIFI-13874 Refactored KeyStore and SSLContext Creation for Tests - Added EphemeralKeyStoreBuilder to nifi-security-ssl - Removed nifi-security-utils - Moved StandardTlsConfiguration to nifi-ssl-context-service - Refactored tests to use EphemeralKeyStoreBuilder and nifi-security-cert-builder for TLS --- minifi/minifi-bootstrap/pom.xml | 11 - .../ingestors/RestChangeIngestorSSLTest.java | 121 ------- .../minifi-framework-bundle/pom.xml | 5 - nifi-code-coverage/pom.xml | 5 - nifi-commons/nifi-hashicorp-vault/pom.xml | 6 - ...ardHashiCorpVaultCommunicationService.java | 43 --- .../ssl/EphemeralKeyStoreBuilder.java | 131 ++++++++ .../ssl/EphemeralKeyStoreBuilderTest.java | 118 +++++++ .../nifi/security/util/SslContextFactory.java | 230 ------------- .../util/TemporaryKeyStoreBuilder.java | 210 ------------ .../security/util/SslSocketFactoryTest.java | 106 ------ .../util/StandardTlsConfigurationTest.java | 205 ------------ nifi-commons/pom.xml | 1 - .../nifi-amqp-processors/pom.xml | 11 +- .../nifi-email-processors/pom.xml | 6 - .../nifi-event-transport/pom.xml | 8 +- .../StringNettyEventSenderFactoryTest.java | 24 +- .../pom.xml | 6 - .../nifi-kafka-3-service/pom.xml | 12 +- .../Kafka3ConnectionServiceBaseIT.java | 82 +++-- .../service/Kafka3ConnectionServiceSSLIT.java | 28 +- .../nifi-redis-extensions/pom.xml | 2 +- .../nifi-standard-processors/pom.xml | 17 +- .../nifi/processors/standard/InvokeHTTP.java | 6 +- .../processors/standard/InvokeHTTPTest.java | 81 +++-- .../processors/standard/TestListenHTTP.java | 189 ++++------- .../processors/standard/TestListenTCP.java | 31 +- .../nifi/processors/standard/TestPutTCP.java | 31 +- .../nifi/web/util/ssl/SslContextUtils.java | 110 ------- .../nifi-distributed-cache-server/pom.xml | 8 +- .../map/DistributedMapCacheTlsTest.java | 42 ++- .../nifi-ssl-context-service/pom.xml | 10 +- .../nifi/ssl/StandardSSLContextService.java | 83 ++++- .../nifi/ssl}/StandardTlsConfiguration.java | 85 +---- .../nifi/ssl/SSLContextServiceTest.java | 256 --------------- ...andardRestrictedSSLContextServiceTest.java | 55 +++- .../ssl/StandardSSLContextServiceTest.java | 302 ++++++++++++++++++ .../nifi-web-client-provider-service/pom.xml | 8 +- .../StandardKeyManagerProviderTest.java | 49 ++- .../StandardWebClientServiceProviderTest.java | 39 ++- .../nifi-ldap-iaa-providers/pom.xml | 4 +- .../org/apache/nifi/ldap/LdapProvider.java | 110 +++++-- .../apache/nifi/ldap/ProviderProperty.java | 43 +++ .../ldap/tenants/LdapUserGroupProvider.java | 108 ++++++- .../nifi-framework-nar-bom/pom.xml | 10 - .../nifi-framework-nar/pom.xml | 5 - .../nifi-framework-cluster-protocol/pom.xml | 5 - .../nifi-framework-cluster/pom.xml | 5 - .../nifi-framework-components/pom.xml | 5 - .../nifi-framework-core/pom.xml | 11 +- .../queue/clustered/LoadBalancedQueueIT.java | 28 +- .../client/async/nio/TestPeerChannel.java | 27 +- .../ConnectionLoadBalanceServerTest.java | 29 +- ...estZooKeeperStateServerConfigurations.java | 190 ----------- .../pom.xml | 5 - .../nifi-framework-nar-loading-utils/pom.xml | 5 - .../nifi-web/nifi-jetty/pom.xml | 3 +- .../FrameworkServerConnectorFactoryTest.java | 74 +++-- .../nifi-web/nifi-web-api/pom.xml | 5 - .../nifi-web/nifi-web-security/pom.xml | 17 +- ...andardRegistrationBuilderProviderTest.java | 73 ++--- ...elyingPartyRegistrationRepositoryTest.java | 69 ++-- .../nifi-registry-jetty/pom.xml | 2 +- ...ApplicationServerConnectorFactoryTest.java | 56 +++- .../nifi-system-test-suite/pom.xml | 5 - 65 files changed, 1546 insertions(+), 2121 deletions(-) delete mode 100644 minifi/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/configuration/ingestors/RestChangeIngestorSSLTest.java create mode 100644 nifi-commons/nifi-security-ssl/src/main/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilder.java create mode 100644 nifi-commons/nifi-security-ssl/src/test/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilderTest.java delete mode 100644 nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java delete mode 100644 nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java delete mode 100644 nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/SslSocketFactoryTest.java delete mode 100644 nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/StandardTlsConfigurationTest.java delete mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/web/util/ssl/SslContextUtils.java rename {nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util => nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl}/StandardTlsConfiguration.java (78%) delete mode 100644 nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java create mode 100644 nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardSSLContextServiceTest.java create mode 100644 nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ProviderProperty.java delete mode 100644 nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServerConfigurations.java diff --git a/minifi/minifi-bootstrap/pom.xml b/minifi/minifi-bootstrap/pom.xml index 7de184370888..4fb66e29a4ea 100644 --- a/minifi/minifi-bootstrap/pom.xml +++ b/minifi/minifi-bootstrap/pom.xml @@ -51,11 +51,6 @@ limitations under the License. nifi-utils 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-ssl - 2.0.0-SNAPSHOT - org.apache.nifi c2-protocol-api @@ -125,12 +120,6 @@ limitations under the License. com.fasterxml.jackson.core jackson-annotations - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - test - org.bouncycastle bcprov-jdk18on diff --git a/minifi/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/configuration/ingestors/RestChangeIngestorSSLTest.java b/minifi/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/configuration/ingestors/RestChangeIngestorSSLTest.java deleted file mode 100644 index 8597da3168e2..000000000000 --- a/minifi/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/configuration/ingestors/RestChangeIngestorSSLTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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.minifi.bootstrap.configuration.ingestors; - -import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY; -import static org.mockito.Mockito.when; - -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.security.KeyStore; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509TrustManager; -import okhttp3.OkHttpClient; -import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; -import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener; -import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; -import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult; -import org.apache.nifi.minifi.properties.BootstrapProperties; -import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; -import org.apache.nifi.security.ssl.StandardSslContextBuilder; -import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.mockito.Mockito; - -public class RestChangeIngestorSSLTest extends RestChangeIngestorCommonTest { - - @BeforeAll - public static void setUpHttps() throws IOException, InterruptedException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().trustStoreType("JKS").build(); - - Map bootstrapProperties = new HashMap<>(); - bootstrapProperties.put(RestChangeIngestor.TRUSTSTORE_LOCATION_KEY, tlsConfiguration.getTruststorePath()); - bootstrapProperties.put(RestChangeIngestor.TRUSTSTORE_PASSWORD_KEY, tlsConfiguration.getTruststorePassword()); - bootstrapProperties.put(RestChangeIngestor.TRUSTSTORE_TYPE_KEY, tlsConfiguration.getTruststoreType().getType()); - bootstrapProperties.put(RestChangeIngestor.KEYSTORE_LOCATION_KEY, tlsConfiguration.getKeystorePath()); - bootstrapProperties.put(RestChangeIngestor.KEYSTORE_PASSWORD_KEY, tlsConfiguration.getKeystorePassword()); - bootstrapProperties.put(RestChangeIngestor.KEYSTORE_TYPE_KEY, tlsConfiguration.getKeystoreType().getType()); - bootstrapProperties.put(RestChangeIngestor.NEED_CLIENT_AUTH_KEY, "false"); - bootstrapProperties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); - bootstrapProperties.put(PULL_HTTP_BASE_KEY + ".override.core", "true"); - BootstrapProperties properties = new BootstrapProperties(bootstrapProperties); - - restChangeIngestor = new RestChangeIngestor(); - - testNotifier = Mockito.mock(ConfigurationChangeNotifier.class); - ConfigurationChangeListener testListener = Mockito.mock(ConfigurationChangeListener.class); - when(testListener.getDescriptor()).thenReturn("MockChangeListener"); - when(testNotifier.notifyListeners(Mockito.any())).thenReturn(Collections.singleton(new ListenerHandleResult(testListener))); - - ConfigurationFileHolder configurationFileHolder = Mockito.mock(ConfigurationFileHolder.class); - when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(ByteBuffer.wrap(new byte[0]))); - - restChangeIngestor.initialize(properties, configurationFileHolder, testNotifier); - restChangeIngestor.setDifferentiator(mockDifferentiator); - restChangeIngestor.start(); - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - - final KeyStore keyStore; - try (final FileInputStream keyStoreStream = new FileInputStream(tlsConfiguration.getKeystorePath())) { - keyStore = new StandardKeyStoreBuilder() - .type(tlsConfiguration.getKeystoreType().getType()) - .inputStream(keyStoreStream) - .password(tlsConfiguration.getKeystorePassword().toCharArray()) - .build(); - } - - final KeyStore truststore; - try (final FileInputStream trustStoreStream = new FileInputStream(tlsConfiguration.getTruststorePath())) { - truststore = new StandardKeyStoreBuilder() - .type(tlsConfiguration.getTruststoreType().getType()) - .inputStream(trustStoreStream) - .password(tlsConfiguration.getTruststorePassword().toCharArray()) - .build(); - } - - final X509TrustManager trustManager = new StandardTrustManagerBuilder().trustStore(truststore).build(); - final SSLContext sslContext = new StandardSslContextBuilder() - .keyStore(keyStore) - .keyPassword(tlsConfiguration.getKeyPassword().toCharArray()) - .trustStore(truststore) - .build(); - final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - - clientBuilder.sslSocketFactory(sslSocketFactory, trustManager); - - Thread.sleep(1000); - url = restChangeIngestor.getURI().toURL().toString(); - client = clientBuilder.build(); - } - - @AfterAll - public static void stop() throws Exception { - restChangeIngestor.close(); - client = null; - } -} diff --git a/minifi/minifi-nar-bundles/minifi-framework-bundle/pom.xml b/minifi/minifi-nar-bundles/minifi-framework-bundle/pom.xml index fffe2da4c04e..3beeffd45d44 100644 --- a/minifi/minifi-nar-bundles/minifi-framework-bundle/pom.xml +++ b/minifi/minifi-nar-bundles/minifi-framework-bundle/pom.xml @@ -59,11 +59,6 @@ limitations under the License. nifi-framework-nar-loading-utils 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-utils-api diff --git a/nifi-code-coverage/pom.xml b/nifi-code-coverage/pom.xml index 15a08150e159..6a3ffbfaa8b7 100644 --- a/nifi-code-coverage/pom.xml +++ b/nifi-code-coverage/pom.xml @@ -352,11 +352,6 @@ nifi-security-ssl 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-utils-api diff --git a/nifi-commons/nifi-hashicorp-vault/pom.xml b/nifi-commons/nifi-hashicorp-vault/pom.xml index f7af44ae2eff..e405c1fda561 100644 --- a/nifi-commons/nifi-hashicorp-vault/pom.xml +++ b/nifi-commons/nifi-hashicorp-vault/pom.xml @@ -65,12 +65,6 @@ 2.0.0-SNAPSHOT test - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - test - diff --git a/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java index d4e931758c5d..6ae727a750de 100644 --- a/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java +++ b/nifi-commons/nifi-hashicorp-vault/src/test/java/org/apache/nifi/vault/hashicorp/TestStandardHashiCorpVaultCommunicationService.java @@ -16,10 +16,7 @@ */ package org.apache.nifi.vault.hashicorp; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultProperties; -import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultSslProperties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,18 +25,14 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.Arrays; import java.util.Optional; -import java.util.stream.Collectors; import static org.mockito.Mockito.when; public class TestStandardHashiCorpVaultCommunicationService { public static final String URI_VALUE = "http://127.0.0.1:8200"; - public static final String CIPHER_SUITE_VALUE = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; private HashiCorpVaultProperties properties; - private HashiCorpVaultSslProperties sslProperties; private File authProps; @BeforeEach @@ -47,11 +40,9 @@ public void init() throws IOException { authProps = TestHashiCorpVaultConfiguration.writeBasicVaultAuthProperties(); properties = Mockito.mock(HashiCorpVaultProperties.class); - sslProperties = Mockito.mock(HashiCorpVaultSslProperties.class); when(properties.getUri()).thenReturn(URI_VALUE); when(properties.getAuthPropertiesFilename()).thenReturn(authProps.getAbsolutePath()); - when(properties.getSsl()).thenReturn(sslProperties); when(properties.getKvVersion()).thenReturn(1); } @@ -73,18 +64,6 @@ public void testBasicConfiguration() { // Once to check if the property is set, and once to retrieve the value Mockito.verify(properties, Mockito.times(2)).getAuthPropertiesFilename(); - - // These should not be called because TLS is not configured - this.ensureTlsPropertiesAccessed(0); - } - - private void ensureTlsPropertiesAccessed(int numberOfTimes) { - Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getKeyStore(); - Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getKeyStoreType(); - Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getKeyStorePassword(); - Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getTrustStore(); - Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getTrustStoreType(); - Mockito.verify(sslProperties, Mockito.times(numberOfTimes)).getTrustStorePassword(); } @Test @@ -93,26 +72,4 @@ public void testTimeouts() { when(properties.getReadTimeout()).thenReturn(Optional.of("40 secs")); this.configureService(); } - - @Test - public void testTLS() { - TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - - when(sslProperties.getKeyStore()).thenReturn(tlsConfiguration.getKeystorePath()); - when(sslProperties.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword()); - when(sslProperties.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType()); - when(sslProperties.getTrustStore()).thenReturn(tlsConfiguration.getTruststorePath()); - when(sslProperties.getTrustStorePassword()).thenReturn(tlsConfiguration.getTruststorePassword()); - when(sslProperties.getTrustStoreType()).thenReturn(tlsConfiguration.getTruststoreType().getType()); - when(sslProperties.getEnabledProtocols()).thenReturn(Arrays.stream(tlsConfiguration.getEnabledProtocols()) - .collect(Collectors.joining(","))); - when(sslProperties.getEnabledCipherSuites()).thenReturn(CIPHER_SUITE_VALUE); - - when(properties.getUri()).thenReturn(URI_VALUE.replace("http", "https")); - this.configureService(); - - this.ensureTlsPropertiesAccessed(2); - Mockito.verify(sslProperties, Mockito.times(1)).getEnabledProtocols(); - Mockito.verify(sslProperties, Mockito.times(1)).getEnabledCipherSuites(); - } } diff --git a/nifi-commons/nifi-security-ssl/src/main/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilder.java b/nifi-commons/nifi-security-ssl/src/main/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilder.java new file mode 100644 index 000000000000..f6525b108dc2 --- /dev/null +++ b/nifi-commons/nifi-security-ssl/src/main/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilder.java @@ -0,0 +1,131 @@ +/* + * 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.security.ssl; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; + +/** + * Ephemeral implementation of KeyStore Builder that creates a KeyStore in memory without persistent entries + */ +public class EphemeralKeyStoreBuilder implements KeyStoreBuilder { + + private static final String CERTIFICATE_ALIAS = "certificate-%d"; + + private static final String PRIVATE_KEY_ALIAS = "private-key-%d"; + + private final List certificates = new ArrayList<>(); + + private final List privateKeyEntries = new ArrayList<>(); + + private char[] keyPassword = null; + + /** + * Add X.509 Certificate to list of trusted certificates + * + * @param certificate Certificate to be added as a trusted entry + * @return Builder + */ + public EphemeralKeyStoreBuilder addCertificate(final X509Certificate certificate) { + Objects.requireNonNull(certificate, "Certificate required"); + certificates.add(certificate); + return this; + } + + /** + * Add Private Key Entry containing one or more associated X.509 Certificates + * + * @param privateKeyEntry Private Key Entry to be added + * @return Builder + */ + public EphemeralKeyStoreBuilder addPrivateKeyEntry(final KeyStore.PrivateKeyEntry privateKeyEntry) { + Objects.requireNonNull(privateKeyEntry, "Private Key Entry required"); + privateKeyEntries.add(privateKeyEntry); + return this; + } + + /** + * Set Key Password for Private Key Entries + * + * @param keyPassword Key password array of characters + * @return Builder + */ + public EphemeralKeyStoreBuilder keyPassword(final char[] keyPassword) { + this.keyPassword = Objects.requireNonNull(keyPassword, "Key Password required").clone(); + return this; + } + + /** + * Build Key Store with provided Certificates and Private Key Entries + * + * @return Key Store + */ + @Override + public KeyStore build() { + final KeyStore keyStore = getInitializedKeyStore(); + + final ListIterator certificateEntries = certificates.listIterator(); + while (certificateEntries.hasNext()) { + final String alias = CERTIFICATE_ALIAS.formatted(certificateEntries.nextIndex()); + final X509Certificate certificate = certificateEntries.next(); + + try { + keyStore.setCertificateEntry(alias, certificate); + } catch (final KeyStoreException e) { + final String message = String.format("Set certificate entry [%s] failed", alias); + throw new BuilderConfigurationException(message, e); + } + } + + final ListIterator privateKeys = privateKeyEntries.listIterator(); + while (privateKeys.hasNext()) { + final String alias = PRIVATE_KEY_ALIAS.formatted(privateKeys.nextIndex()); + final KeyStore.PrivateKeyEntry privateKeyEntry = privateKeys.next(); + + final PrivateKey privateKey = privateKeyEntry.getPrivateKey(); + final Certificate[] certificateChain = privateKeyEntry.getCertificateChain(); + + try { + keyStore.setKeyEntry(alias, privateKey, keyPassword, certificateChain); + } catch (final KeyStoreException e) { + final String message = String.format("Set key entry [%s] failed", alias); + throw new BuilderConfigurationException(message, e); + } + } + + return keyStore; + } + + private KeyStore getInitializedKeyStore() { + final String keyStoreType = KeyStore.getDefaultType(); + try { + final KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null); + return keyStore; + } catch (final Exception e) { + final String message = String.format("Key Store Type [%s] initialization failed", keyStoreType); + throw new BuilderConfigurationException(message, e); + } + } +} diff --git a/nifi-commons/nifi-security-ssl/src/test/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilderTest.java b/nifi-commons/nifi-security-ssl/src/test/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilderTest.java new file mode 100644 index 000000000000..1515d304d53c --- /dev/null +++ b/nifi-commons/nifi-security-ssl/src/test/java/org/apache/nifi/security/ssl/EphemeralKeyStoreBuilderTest.java @@ -0,0 +1,118 @@ +/* + * 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.security.ssl; + +import org.junit.jupiter.api.BeforeAll; +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.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class EphemeralKeyStoreBuilderTest { + + private static final String KEY_ALGORITHM = "RSA"; + + private static final char[] KEY_PASSWORD = EphemeralKeyStoreBuilderTest.class.getSimpleName().toCharArray();; + + private static KeyPair keyPair; + + @Mock + private X509Certificate certificate; + + @BeforeAll + static void setKeyPair() throws NoSuchAlgorithmException { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + keyPair = keyPairGenerator.generateKeyPair(); + } + + @Test + void testBuild() { + final EphemeralKeyStoreBuilder builder = new EphemeralKeyStoreBuilder(); + + final KeyStore keyStore = builder.build(); + + assertNotNull(keyStore); + + final String expectedKeyStoreType = KeyStore.getDefaultType(); + assertEquals(expectedKeyStoreType, keyStore.getType()); + } + + @Test + void testAddCertificateBuild() throws KeyStoreException { + final EphemeralKeyStoreBuilder builder = new EphemeralKeyStoreBuilder(); + + final KeyStore keyStore = builder.addCertificate(certificate).build(); + + assertNotNull(keyStore); + + final String expectedKeyStoreType = KeyStore.getDefaultType(); + assertEquals(expectedKeyStoreType, keyStore.getType()); + + final Enumeration aliases = keyStore.aliases(); + assertTrue(aliases.hasMoreElements()); + final String alias = aliases.nextElement(); + assertNotNull(alias); + assertFalse(aliases.hasMoreElements()); + } + + @Test + void testAddPrivateKeyEntryBuild() throws Exception { + final EphemeralKeyStoreBuilder builder = new EphemeralKeyStoreBuilder(); + + final PrivateKey privateKey = keyPair.getPrivate(); + final PublicKey publicKey = keyPair.getPublic(); + when(certificate.getPublicKey()).thenReturn(publicKey); + + final KeyStore.PrivateKeyEntry privateKeyEntry = new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{certificate}); + + final KeyStore keyStore = builder.addPrivateKeyEntry(privateKeyEntry).keyPassword(KEY_PASSWORD).build(); + + assertNotNull(keyStore); + + final String expectedKeyStoreType = KeyStore.getDefaultType(); + assertEquals(expectedKeyStoreType, keyStore.getType()); + + final Enumeration aliases = keyStore.aliases(); + assertTrue(aliases.hasMoreElements()); + final String alias = aliases.nextElement(); + assertNotNull(alias); + + final Key key = keyStore.getKey(alias, KEY_PASSWORD); + assertEquals(privateKey, key); + + assertFalse(aliases.hasMoreElements()); + } +} diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java deleted file mode 100644 index 36ad093f6c61..000000000000 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.security.util; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.nifi.security.ssl.StandardKeyManagerFactoryBuilder; -import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; -import org.apache.nifi.security.ssl.StandardTrustManagerFactoryBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A factory for creating SSL contexts using the application's security properties. By requiring callers to bundle - * the properties in a {@link TlsConfiguration} container object, much better validation and property matching can - * occur. The {@code public} methods are designed for easy use, while the {@code protected} methods provide more - * granular (but less common) access to intermediate objects if required. - */ -public final class SslContextFactory { - private static final Logger logger = LoggerFactory.getLogger(SslContextFactory.class); - - /** - * Create and initialize a {@link SSLContext} from the provided TLS configuration. - * - * @param tlsConfiguration the TLS configuration container object - * @return {@link SSLContext} initialized from TLS Configuration or null when TLS Configuration is empty - * @throws TlsException if there is a problem configuring the SSLContext - */ - public static SSLContext createSslContext(final TlsConfiguration tlsConfiguration) throws TlsException { - if (TlsConfiguration.isEmpty(tlsConfiguration)) { - logger.debug("Cannot create SSLContext from empty TLS configuration; returning null"); - return null; - } - - // If the keystore properties are present, truststore properties are required to be present as well - if (tlsConfiguration.isKeystorePopulated() && !tlsConfiguration.isTruststorePopulated()) { - logger.error("The TLS config keystore properties were populated but the truststore properties were not"); - if (logger.isDebugEnabled()) { - logger.debug("Provided TLS config: {}", tlsConfiguration); - } - throw new TlsException("Truststore properties are required if keystore properties are present"); - } - final TrustManager[] trustManagers = getTrustManagers(tlsConfiguration); - - return createSslContext(tlsConfiguration, trustManagers); - } - - /** - * Create and initialize a {@link SSLContext} from the provided TLS configuration and Trust Managers. - * - * @param tlsConfiguration the TLS configuration container object - * @param trustManagers Trust Managers can be null to use platform default Trust Managers - * @return {@link SSLContext} initialized from TLS Configuration or null when TLS Configuration is empty - * @throws TlsException if there is a problem configuring the SSLContext - */ - public static SSLContext createSslContext(final TlsConfiguration tlsConfiguration, final TrustManager[] trustManagers) throws TlsException { - if (TlsConfiguration.isEmpty(tlsConfiguration)) { - logger.debug("Cannot create SSLContext from empty TLS configuration; returning null"); - return null; - } - - final KeyManager[] keyManagers = getKeyManagers(tlsConfiguration); - return initializeSSLContext(tlsConfiguration, keyManagers, trustManagers); - } - - /** - * Returns a configured {@link X509TrustManager} for the provided configuration. Useful for - * constructing HTTP clients which require their own trust management rather than an - * {@link SSLContext}. Filters and removes any trust managers that are not - * {@link javax.net.ssl.X509TrustManager} implementations, and returns the first - * X.509 trust manager. - * - * @param tlsConfiguration the TLS configuration container object - * @return an X.509 TrustManager (can be {@code null}) - * @throws TlsException if there is a problem reading the truststore to create the trust managers - */ - public static X509TrustManager getX509TrustManager(TlsConfiguration tlsConfiguration) throws TlsException { - TrustManager[] trustManagers = getTrustManagers(tlsConfiguration); - if (trustManagers == null) { - return null; - } - Optional x509TrustManager = Arrays.stream(trustManagers) - .filter(tm -> tm instanceof X509TrustManager) - .map(tm -> (X509TrustManager) tm) - .findFirst(); - return x509TrustManager.orElse(null); - } - - /** - * Convenience method to return the {@link SSLSocketFactory} from the created {@link SSLContext} - * - * @param tlsConfiguration the TLS configuration container object - * @return the configured SSLSocketFactory (can be {@code null}) - * @throws TlsException if there is a problem creating the SSLContext or SSLSocketFactory - */ - public static SSLSocketFactory createSSLSocketFactory(final TlsConfiguration tlsConfiguration) throws TlsException { - SSLContext sslContext = createSslContext(tlsConfiguration); - if (sslContext == null) { - // Only display an error in the log if the provided config wasn't empty - if (!TlsConfiguration.isEmpty(tlsConfiguration)) { - logger.error("The SSLContext could not be formed from the provided TLS configuration. Check the provided keystore and truststore properties"); - } - return null; - } - return sslContext.getSocketFactory(); - } - - /** - * Returns an array of {@link KeyManager}s for the provided configuration. Useful for constructing - * HTTP clients which require their own key management rather than an {@link SSLContext}. The result can be - * {@code null} or empty. If an empty configuration is provided, {@code null} is returned. However, if a partially-populated - * but invalid configuration is provided, a {@link TlsException} is thrown. - * - * @param tlsConfiguration the TLS configuration container object with keystore properties - * @return an array of KeyManagers (can be {@code null}) - * @throws TlsException if there is a problem reading the keystore to create the key managers - */ - protected static KeyManager[] getKeyManagers(final TlsConfiguration tlsConfiguration) throws TlsException { - KeyManager[] keyManagers = null; - - final String keystorePath = tlsConfiguration.getKeystorePath(); - final KeystoreType keystoreType = tlsConfiguration.getKeystoreType(); - - if (tlsConfiguration.isKeystorePopulated()) { - try (InputStream inputStream = new FileInputStream(keystorePath)) { - final KeyStore keyStore = new StandardKeyStoreBuilder() - .inputStream(inputStream) - .type(keystoreType.getType()) - .password(tlsConfiguration.getKeystorePassword().toCharArray()) - .build(); - - keyManagers = new StandardKeyManagerFactoryBuilder() - .keyStore(keyStore) - .keyPassword(tlsConfiguration.getFunctionalKeyPassword().toCharArray()) - .build() - .getKeyManagers(); - } catch (final IOException e) { - final String message = "Key Store [%s] Type [%s] loading failed".formatted(keystorePath, keystoreType); - throw new TlsException(message, e); - } - } else if (tlsConfiguration.isAnyKeystorePopulated()) { - throw new TlsException("Key Store properties missing: Set Path [%s] Type [%s]".formatted(keystorePath, keystoreType)); - } - - return keyManagers; - } - - /** - * Returns an array of {@link TrustManager} implementations based on the provided truststore configurations. The result can be - * {@code null} or empty. If an empty configuration is provided, {@code null} is returned. However, if a partially-populated - * but invalid configuration is provided, a {@link TlsException} is thrown. - *

- * - * @param tlsConfiguration the TLS configuration container object with truststore properties - * @return the loaded trust managers - * @throws TlsException if there is a problem reading from the truststore - */ - public static TrustManager[] getTrustManagers(final TlsConfiguration tlsConfiguration) throws TlsException { - Objects.requireNonNull(tlsConfiguration, "TLS Configuration required"); - - final String truststorePath = tlsConfiguration.getTruststorePath(); - final KeystoreType truststoreType = tlsConfiguration.getTruststoreType(); - - TrustManager[] trustManagers = null; - if (tlsConfiguration.isTruststorePopulated()) { - try (InputStream inputStream = new FileInputStream(truststorePath)) { - final StandardKeyStoreBuilder keyStoreBuilder = new StandardKeyStoreBuilder() - .inputStream(inputStream) - .type(truststoreType.getType()); - - final String truststorePassword = tlsConfiguration.getTruststorePassword(); - if (truststorePassword != null) { - keyStoreBuilder.password(truststorePassword.toCharArray()); - } - final KeyStore trustStore = keyStoreBuilder.build(); - - trustManagers = new StandardTrustManagerFactoryBuilder() - .trustStore(trustStore) - .build() - .getTrustManagers(); - } catch (final IOException e) { - final String message = "Trust Store [%s] Type [%s] loading failed".formatted(truststorePath, truststoreType); - throw new TlsException(message, e); - } - } else if (tlsConfiguration.isAnyTruststorePopulated()) { - throw new TlsException("Trust Store properties missing: Set Path [%s] Type [%s]".formatted(truststorePath, truststoreType)); - } - - return trustManagers; - } - - private static SSLContext initializeSSLContext(final TlsConfiguration tlsConfiguration, final KeyManager[] keyManagers, final TrustManager[] trustManagers) throws TlsException { - try { - final SSLContext sslContext = SSLContext.getInstance(tlsConfiguration.getProtocol()); - sslContext.init(keyManagers, trustManagers, new SecureRandom()); - return sslContext; - } catch (final NoSuchAlgorithmException | KeyManagementException e) { - logger.error("Encountered an error creating SSLContext from TLS configuration ({}): {}", tlsConfiguration, e.getLocalizedMessage()); - throw new TlsException("Error creating SSL context", e); - } - } -} diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java deleted file mode 100644 index a2218e37b0b4..000000000000 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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.security.util; - -import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; - -import javax.security.auth.x500.X500Principal; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.SecureRandom; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.time.Duration; -import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - * KeyStore Factory for creating temporary files primarily used for testing - */ -public class TemporaryKeyStoreBuilder { - private static final String KEY_PAIR_ALGORITHM = "RSA"; - - private static final int KEY_SIZE = 2048; - - private static final int RANDOM_BYTES_LENGTH = 16; - - private static final Base64.Encoder ENCODER = Base64.getEncoder().withoutPadding(); - - private static final String DISTINGUISHED_NAME_FORMAT = "CN=%s"; - - private static final int CERTIFICATE_VALID_DAYS = 1; - - private static final KeystoreType KEYSTORE_TYPE = KeystoreType.PKCS12; - - private static final String KEY_STORE_EXTENSION = ".p12"; - - private static final String KEY_STORE_PREFIX = "TemporaryKeyStore-"; - - private static final String DEFAULT_HOSTNAME = "localhost"; - - private String hostname = DEFAULT_HOSTNAME; - - private String trustStorePassword = generateSecureRandomPassword(); - - private String trustStoreType = KEYSTORE_TYPE.getType(); - - /** - * Set Hostname used for Certificate Common Name and DNS Subject Alternative Names - * - * @param hostname Hostname is required - * @return Builder - */ - public TemporaryKeyStoreBuilder hostname(final String hostname) { - this.hostname = Objects.requireNonNull(hostname, "Hostname required"); - return this; - } - - /** - * Set Trust Store Password used for protected generated Trust Store file - * - * @param trustStorePassword Trust Store Password is required - * @return Builder - */ - public TemporaryKeyStoreBuilder trustStorePassword(final String trustStorePassword) { - this.trustStorePassword = Objects.requireNonNull(trustStorePassword, "TrustStore Password required"); - return this; - } - - /** - * Set Trust Store Type used for storing Trust Store files - * - * @param trustStoreType Trust Store type must be a supported value for KeyStore.getInstance() - * @return Builder - */ - public TemporaryKeyStoreBuilder trustStoreType(final String trustStoreType) { - this.trustStoreType = Objects.requireNonNull(trustStoreType, "TrustStore Type required"); - return this; - } - - /** - * Build Temporary KeyStore and TrustStore with configured values and set files with File.deleteOnExit() - * - * @return TLS Configuration with KeyStore and TrustStore properties - */ - public TlsConfiguration build() { - final KeyPair keyPair = generateKeyPair(); - final X509Certificate certificate = generateCertificate(hostname, keyPair); - - final KeyStoreConfiguration keyStoreConfiguration = setKeyStore(keyPair.getPrivate(), certificate); - final KeyStoreConfiguration trustStoreConfiguration = setTrustStore(certificate); - - return new StandardTlsConfiguration( - keyStoreConfiguration.location(), - keyStoreConfiguration.password(), - keyStoreConfiguration.password(), - keyStoreConfiguration.keyStoreType(), - trustStoreConfiguration.location(), - trustStoreConfiguration.password(), - trustStoreConfiguration.keyStoreType(), - TlsPlatform.getLatestProtocol() - ); - } - - private KeyStoreConfiguration setKeyStore(final PrivateKey privateKey, final X509Certificate certificate) { - final KeyStore keyStore = getNewKeyStore(KEYSTORE_TYPE.getType()); - - final String password = generateSecureRandomPassword(); - final String alias = UUID.randomUUID().toString(); - try { - keyStore.setKeyEntry(alias, privateKey, password.toCharArray(), new Certificate[]{certificate}); - } catch (final KeyStoreException e) { - throw new RuntimeException("Set Key Entry Failed", e); - } - - final File keyStoreFile = storeKeyStore(keyStore, password.toCharArray()); - return new KeyStoreConfiguration(keyStoreFile.getAbsolutePath(), password, KEYSTORE_TYPE.getType()); - } - - private KeyStoreConfiguration setTrustStore(final X509Certificate certificate) { - final KeyStore keyStore = getNewKeyStore(trustStoreType); - - final String alias = UUID.randomUUID().toString(); - try { - keyStore.setCertificateEntry(alias, certificate); - } catch (final KeyStoreException e) { - throw new RuntimeException("Set Certificate Entry Failed", e); - } - - final File trustStoreFile = storeKeyStore(keyStore, trustStorePassword.toCharArray()); - return new KeyStoreConfiguration(trustStoreFile.getAbsolutePath(), trustStorePassword, trustStoreType); - } - - private File storeKeyStore(final KeyStore keyStore, final char[] password) { - try { - final File keyStoreFile = File.createTempFile(KEY_STORE_PREFIX, KEY_STORE_EXTENSION); - keyStoreFile.deleteOnExit(); - try (final OutputStream outputStream = new FileOutputStream(keyStoreFile)) { - keyStore.store(outputStream, password); - } - return keyStoreFile; - } catch (final IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) { - throw new RuntimeException("Store KeyStore Failed", e); - } - } - - private KeyStore getNewKeyStore(final String newKeyStoreType) { - try { - final KeyStore keyStore = KeyStore.getInstance(newKeyStoreType); - keyStore.load(null); - return keyStore; - } catch (final KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { - throw new RuntimeException(String.format("Create KeyStore [%s] Failed", KEYSTORE_TYPE), e); - } - } - - private X509Certificate generateCertificate(final String hostname, final KeyPair keyPair) { - final X500Principal distinguishedName = new X500Principal(String.format(DISTINGUISHED_NAME_FORMAT, hostname)); - final List dnsNames = Collections.singletonList(hostname); - return new StandardCertificateBuilder(keyPair, distinguishedName, Duration.ofDays(CERTIFICATE_VALID_DAYS)) - .setDnsSubjectAlternativeNames(dnsNames) - .build(); - } - - private KeyPair generateKeyPair() { - try { - final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM); - keyPairGenerator.initialize(KEY_SIZE); - return keyPairGenerator.generateKeyPair(); - } catch (final NoSuchAlgorithmException e) { - throw new IllegalArgumentException(String.format("[%s] Algorithm not found", KEY_PAIR_ALGORITHM), e); - } - } - - private String generateSecureRandomPassword() { - final SecureRandom secureRandom = new SecureRandom(); - final byte[] randomBytes = new byte[RANDOM_BYTES_LENGTH]; - secureRandom.nextBytes(randomBytes); - return ENCODER.encodeToString(randomBytes); - } - - private record KeyStoreConfiguration(String location, String password, String keyStoreType) { - - } -} diff --git a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/SslSocketFactoryTest.java b/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/SslSocketFactoryTest.java deleted file mode 100644 index 87b39696cb93..000000000000 --- a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/SslSocketFactoryTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.security.util; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -public class SslSocketFactoryTest { - private static final String EMPTY = ""; - - private static TlsConfiguration tlsConfiguration; - - @BeforeAll - public static void setTlsConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - } - - @Test - public void testCreateSslContextNullTlsConfiguration() throws TlsException { - final SSLContext sslContext = SslContextFactory.createSslContext(null); - assertNull(sslContext); - } - - @Test - public void testCreateSslContextEmptyTlsConfiguration() throws TlsException { - final SSLContext sslContext = SslContextFactory.createSslContext(new StandardTlsConfiguration()); - assertNull(sslContext); - } - - @Test - public void testCreateSslContextEmptyKeyPassword() throws TlsException { - final TlsConfiguration customTlsConfiguration = new StandardTlsConfiguration( - tlsConfiguration.getKeystorePath(), - tlsConfiguration.getKeystorePassword(), - EMPTY, - tlsConfiguration.getKeystoreType(), - tlsConfiguration.getTruststorePath(), - tlsConfiguration.getTruststorePassword(), - tlsConfiguration.getTruststoreType(), - tlsConfiguration.getProtocol() - ); - final SSLContext sslContext = SslContextFactory.createSslContext(customTlsConfiguration); - assertNotNull(sslContext); - assertEquals(customTlsConfiguration.getProtocol(), sslContext.getProtocol()); - } - - @Test - public void testCreateSslContextEmptyTrustStorePasswordJks() throws TlsException { - final TlsConfiguration customTlsConfiguration = new TemporaryKeyStoreBuilder() - .trustStorePassword(EMPTY) - .trustStoreType(KeystoreType.JKS.getType()) - .build(); - final SSLContext sslContext = SslContextFactory.createSslContext(customTlsConfiguration); - assertNotNull(sslContext); - assertEquals(customTlsConfiguration.getProtocol(), sslContext.getProtocol()); - } - - @Test - public void testCreateSslContext() throws TlsException { - final SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration); - assertNotNull(sslContext); - assertEquals(tlsConfiguration.getProtocol(), sslContext.getProtocol()); - } - - @Test - public void testGetTrustManager() throws TlsException { - final X509TrustManager trustManager = SslContextFactory.getX509TrustManager(tlsConfiguration); - assertNotNull(trustManager); - assertEquals(1, trustManager.getAcceptedIssuers().length); - } - - @Test - public void testGetTrustManagers() throws TlsException { - final TrustManager[] trustManagers = SslContextFactory.getTrustManagers(tlsConfiguration); - assertNotNull(trustManagers); - assertEquals(1, trustManagers.length); - } - - @Test - public void testGetTrustManagersEmptyTlsConfiguration() throws TlsException { - final TrustManager[] trustManagers = SslContextFactory.getTrustManagers(new StandardTlsConfiguration()); - assertNull(trustManagers); - } -} diff --git a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/StandardTlsConfigurationTest.java b/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/StandardTlsConfigurationTest.java deleted file mode 100644 index 345e2ea4dac7..000000000000 --- a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/StandardTlsConfigurationTest.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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.security.util; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class StandardTlsConfigurationTest { - private static final String KEY_STORE_PATH = UUID.randomUUID().toString(); - - private static final String KEY_STORE_PASSWORD = UUID.randomUUID().toString(); - - private static final String KEY_PASSWORD = UUID.randomUUID().toString(); - - private static final String KEY_STORE_TYPE = KeystoreType.PKCS12.getType(); - - private static final String TRUST_STORE_PATH = UUID.randomUUID().toString(); - - private static final String TRUST_STORE_PASSWORD = UUID.randomUUID().toString(); - - private static final String TRUST_STORE_TYPE = KeystoreType.JKS.getType(); - - private static final String PROTOCOL = TlsPlatform.getLatestProtocol(); - - private static TlsConfiguration tlsConfiguration; - - @BeforeAll - public static void setTlsConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - } - - @Test - public void testIsKeyStoreValid() { - assertTrue(tlsConfiguration.isKeystoreValid()); - } - - @Test - public void testIsTrustStoreValid() { - assertTrue(tlsConfiguration.isTruststoreValid()); - } - - @Test - public void testEqualsNullProperties() { - final StandardTlsConfiguration configuration = new StandardTlsConfiguration(); - assertEquals(new StandardTlsConfiguration(), configuration); - } - - @Test - public void testHashCodeNullProperties() { - final StandardTlsConfiguration configuration = new StandardTlsConfiguration(); - assertEquals(new StandardTlsConfiguration().hashCode(), configuration.hashCode()); - } - - @Test - public void testFunctionalKeyPasswordFromKeyStorePassword() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - null, - KEY_STORE_TYPE, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - TRUST_STORE_TYPE, - PROTOCOL - ); - assertEquals(KEY_STORE_PASSWORD, configuration.getFunctionalKeyPassword()); - } - - @Test - public void testIsKeyStorePopulated() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - KEY_PASSWORD, - KEY_STORE_TYPE, - null, - null, - null, - PROTOCOL - ); - assertTrue(configuration.isKeystorePopulated()); - assertTrue(configuration.isAnyKeystorePopulated()); - } - - @Test - public void testIsKeyStorePopulatedMissingProperties() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - null, - null, - null, - null, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - TRUST_STORE_TYPE, - PROTOCOL - ); - assertFalse(configuration.isKeystorePopulated()); - assertFalse(configuration.isAnyKeystorePopulated()); - } - - @Test - public void testIsTrustStorePopulated() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - null, - null, - null, - null, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - TRUST_STORE_TYPE, - PROTOCOL - ); - assertTrue(configuration.isTruststorePopulated()); - assertTrue(configuration.isAnyTruststorePopulated()); - } - - @Test - public void testIsTrustStorePopulatedMissingProperties() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - KEY_PASSWORD, - KEY_STORE_TYPE, - null, - null, - null, - PROTOCOL - ); - assertFalse(configuration.isTruststorePopulated()); - assertFalse(configuration.isAnyTruststorePopulated()); - } - - @Test - public void testGetEnabledProtocolsVersionMatched() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - KEY_PASSWORD, - KEY_STORE_TYPE, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - TRUST_STORE_TYPE, - PROTOCOL - ); - - final String[] enabledProtocols = configuration.getEnabledProtocols(); - assertArrayEquals(new String[]{PROTOCOL}, enabledProtocols); - } - - @Test - public void testGetEnabledProtocolsTlsProtocol() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - KEY_PASSWORD, - KEY_STORE_TYPE, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - TRUST_STORE_TYPE, - StandardTlsConfiguration.TLS_PROTOCOL - ); - - final String[] enabledProtocols = configuration.getEnabledProtocols(); - assertArrayEquals(TlsPlatform.getPreferredProtocols().toArray(new String[0]), enabledProtocols); - } - - @Test - public void testGetEnabledProtocolsSslProtocol() { - final TlsConfiguration configuration = new StandardTlsConfiguration( - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - KEY_PASSWORD, - KEY_STORE_TYPE, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - TRUST_STORE_TYPE, - StandardTlsConfiguration.SSL_PROTOCOL - ); - - final String[] enabledProtocols = configuration.getEnabledProtocols(); - - assertArrayEquals(TlsPlatform.getSupportedProtocols().toArray(new String[0]), enabledProtocols); - } -} diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml index 971642bfe67f..cb61c61a6dd7 100644 --- a/nifi-commons/pom.xml +++ b/nifi-commons/pom.xml @@ -53,7 +53,6 @@ nifi-security-kerberos nifi-security-ssl nifi-security-utils-api - nifi-security-utils nifi-single-user-utils nifi-site-to-site-client nifi-swagger-integration diff --git a/nifi-extension-bundles/nifi-amqp-bundle/nifi-amqp-processors/pom.xml b/nifi-extension-bundles/nifi-amqp-bundle/nifi-amqp-processors/pom.xml index 27dfa57d6a8d..3a0260181e0b 100644 --- a/nifi-extension-bundles/nifi-amqp-bundle/nifi-amqp-processors/pom.xml +++ b/nifi-extension-bundles/nifi-amqp-bundle/nifi-amqp-processors/pom.xml @@ -34,12 +34,6 @@ language governing permissions and limitations under the License. --> org.apache.nifi nifi-ssl-context-service-api - - org.apache.nifi - nifi-ssl-context-service - 2.0.0-SNAPSHOT - test - org.apache.nifi nifi-utils @@ -56,6 +50,11 @@ language governing permissions and limitations under the License. --> com.fasterxml.jackson.core jackson-databind + + org.apache.nifi + nifi-security-utils-api + test + diff --git a/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml b/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml index 5ff805484477..0b73cee5c2f8 100644 --- a/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml +++ b/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml @@ -96,12 +96,6 @@ - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - test - com.icegreen greenmail diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/pom.xml b/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/pom.xml index a6b03ed763f4..1c67b4c81fdb 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/pom.xml +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/pom.xml @@ -51,7 +51,13 @@ org.apache.nifi - nifi-security-utils + nifi-security-ssl + 2.0.0-SNAPSHOT + test + + + org.apache.nifi + nifi-security-cert-builder 2.0.0-SNAPSHOT test diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/src/test/java/org/apache/nifi/event/transport/netty/StringNettyEventSenderFactoryTest.java b/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/src/test/java/org/apache/nifi/event/transport/netty/StringNettyEventSenderFactoryTest.java index cfbb93436966..c7e1e7c0e954 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/src/test/java/org/apache/nifi/event/transport/netty/StringNettyEventSenderFactoryTest.java +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-event-transport/src/test/java/org/apache/nifi/event/transport/netty/StringNettyEventSenderFactoryTest.java @@ -26,10 +26,10 @@ import org.apache.nifi.event.transport.configuration.TransportProtocol; import org.apache.nifi.event.transport.message.ByteArrayMessage; import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.util.ClientAuth; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -38,12 +38,18 @@ import org.mockito.junit.jupiter.MockitoExtension; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; import java.net.InetAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -199,7 +205,15 @@ private NettyEventServerFactory getEventServerFactory(final int port, final Bloc } private SSLContext getSslContext() throws GeneralSecurityException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - return SslContextFactory.createSslContext(tlsConfiguration); + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + return new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(new char[]{}) + .build(); } } diff --git a/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/pom.xml b/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/pom.xml index a5504285da87..f26a23bafd5a 100644 --- a/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/pom.xml +++ b/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/pom.xml @@ -22,12 +22,6 @@ nifi-flow-registry-client-services jar - - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-utils-api diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/pom.xml b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/pom.xml index af054dff6b31..ddbbdcabcfe5 100644 --- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/pom.xml +++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/pom.xml @@ -36,6 +36,10 @@ org.apache.nifi nifi-ssl-context-service-api + + org.apache.nifi + nifi-security-utils-api + org.apache.nifi nifi-kerberos-user-service-api @@ -68,7 +72,13 @@ org.apache.nifi - nifi-ssl-context-service + nifi-security-cert-builder + 2.0.0-SNAPSHOT + test + + + org.apache.nifi + nifi-security-ssl 2.0.0-SNAPSHOT test diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceBaseIT.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceBaseIT.java index d635c50aca99..a5b9311fce65 100644 --- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceBaseIT.java +++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceBaseIT.java @@ -35,10 +35,9 @@ import org.apache.nifi.kafka.service.api.record.ByteRecord; import org.apache.nifi.kafka.service.api.record.KafkaRecord; import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; import org.apache.nifi.ssl.SSLContextService; -import org.apache.nifi.ssl.StandardRestrictedSSLContextService; import org.apache.nifi.util.MockConfigurationContext; import org.apache.nifi.util.NoOpProcessor; import org.apache.nifi.util.TestRunner; @@ -50,11 +49,22 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.io.TempDir; import org.testcontainers.containers.KafkaContainer; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; +import javax.security.auth.x500.X500Principal; +import java.io.File; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.Collections; import java.util.Iterator; @@ -74,6 +84,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @TestInstance(Lifecycle.PER_CLASS) public class Kafka3ConnectionServiceBaseIT { @@ -100,13 +112,25 @@ public class Kafka3ConnectionServiceBaseIT { private static final int POLLING_ATTEMPTS = 3; - private static final Set fileLocationNames = Set.of( - "KAFKA_SSL_KEYSTORE_LOCATION", "KAFKA_SSL_TRUSTSTORE_LOCATION"); + private static final Set fileLocationNames = Set.of("KAFKA_SSL_KEYSTORE_LOCATION", "KAFKA_SSL_TRUSTSTORE_LOCATION"); protected static final String TEST_USERNAME = "nifi"; protected static final String TEST_PASSWORD = UUID.randomUUID().toString(); - protected TlsConfiguration tlsConfiguration; + private static final String KEY_STORE_EXTENSION = ".p12"; + + protected static final String KEY_PASSWORD = Kafka3ConnectionServiceBaseIT.class.getSimpleName(); + + protected static final String KEY_STORE_PASSWORD = KEY_PASSWORD; + + @TempDir + private static Path keyStoreDirectory; + + protected static Path keyStorePath; + + protected static String keyStoreType; + + protected static Path trustStorePath; protected TestRunner runner; @@ -115,8 +139,28 @@ public class Kafka3ConnectionServiceBaseIT { private Kafka3ConnectionService service; @BeforeAll - void startContainer() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); + void startContainer() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .keyPassword(KEY_PASSWORD.toCharArray()) + .build(); + + keyStorePath = File.createTempFile("keyStore", KEY_STORE_EXTENSION, keyStoreDirectory.toFile()).toPath(); + try (OutputStream outputStream = Files.newOutputStream(keyStorePath)) { + keyStore.store(outputStream, KEY_STORE_PASSWORD.toCharArray()); + } + keyStoreType = keyStore.getType().toUpperCase(); + + final KeyStore trustStore = new EphemeralKeyStoreBuilder() + .addCertificate(certificate) + .build(); + trustStorePath = File.createTempFile("trustStore", KEY_STORE_EXTENSION, keyStoreDirectory.toFile()).toPath(); + try (OutputStream outputStream = Files.newOutputStream(trustStorePath)) { + trustStore.store(outputStream, KEY_STORE_PASSWORD.toCharArray()); + } + kafkaContainer = new KafkaContainer(DockerImageName.parse(IMAGE_NAME)); initializeContainer(); kafkaContainer.start(); @@ -238,7 +282,7 @@ void testProduceConsumeRecord() { } @Test - void testVerifySuccessful() throws InitializationException { + void testVerifySuccessful() { final Map properties = new LinkedHashMap<>(); properties.put(Kafka3ConnectionService.BOOTSTRAP_SERVERS, kafkaContainer.getBootstrapServers()); final MockConfigurationContext configurationContext = new MockConfigurationContext(properties, null, null); @@ -311,18 +355,18 @@ private Iterator poll(final KafkaConsumerService consumerService) { protected String addSSLContextService(final TestRunner runner) throws InitializationException { final String identifier = SSLContextService.class.getSimpleName(); - final SSLContextService service = new StandardRestrictedSSLContextService(); + final SSLContextService service = mock(SSLContextService.class); + when(service.getIdentifier()).thenReturn(identifier); runner.addControllerService(identifier, service); - // TemporaryKeyStoreBuilder sets keystorePassword and keyPassword to the same value. - // The SSL Context Service uses specified Keystore Password as the Key Password. - //runner.setProperty(service, StandardRestrictedSSLContextService.KEY_PASSWORD, tlsConfiguration.getKeyPassword()); - runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE, tlsConfiguration.getKeystorePath()); - runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE_PASSWORD, tlsConfiguration.getKeystorePassword()); - runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE, tlsConfiguration.getTruststorePath()); - runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE_PASSWORD, tlsConfiguration.getTruststorePassword()); - runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); + when(service.isKeyStoreConfigured()).thenReturn(true); + when(service.getKeyStoreFile()).thenReturn(keyStorePath.toString()); + when(service.getKeyStoreType()).thenReturn(keyStoreType); + when(service.getKeyStorePassword()).thenReturn(KEY_STORE_PASSWORD); + when(service.isTrustStoreConfigured()).thenReturn(true); + when(service.getTrustStoreFile()).thenReturn(trustStorePath.toString()); + when(service.getTrustStoreType()).thenReturn(keyStoreType); + when(service.getTrustStorePassword()).thenReturn(KEY_STORE_PASSWORD); runner.enableControllerService(service); return identifier; diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceSSLIT.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceSSLIT.java index ce08ba03a84d..403e93b7a193 100644 --- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceSSLIT.java +++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-3-service/src/test/java/org/apache/nifi/kafka/service/Kafka3ConnectionServiceSSLIT.java @@ -29,13 +29,13 @@ public class Kafka3ConnectionServiceSSLIT extends Kafka3ConnectionServiceBaseIT protected Map getKafkaContainerConfigProperties() { final Map properties = new LinkedHashMap<>(super.getKafkaContainerConfigProperties()); properties.put("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "BROKER:SSL,PLAINTEXT:SSL"); - properties.put("KAFKA_SSL_KEYSTORE_LOCATION", tlsConfiguration.getKeystorePath()); - properties.put("KAFKA_SSL_KEYSTORE_TYPE", tlsConfiguration.getKeystoreType().getType()); - properties.put("KAFKA_SSL_KEYSTORE_PASSWORD", tlsConfiguration.getKeystorePassword()); - properties.put("KAFKA_SSL_KEY_PASSWORD", tlsConfiguration.getKeyPassword()); - properties.put("KAFKA_SSL_TRUSTSTORE_LOCATION", tlsConfiguration.getTruststorePath()); - properties.put("KAFKA_SSL_TRUSTSTORE_TYPE", tlsConfiguration.getTruststoreType().getType()); - properties.put("KAFKA_SSL_TRUSTSTORE_PASSWORD", tlsConfiguration.getTruststorePassword()); + properties.put("KAFKA_SSL_KEYSTORE_LOCATION", keyStorePath.toString()); + properties.put("KAFKA_SSL_KEYSTORE_TYPE", keyStoreType); + properties.put("KAFKA_SSL_KEYSTORE_PASSWORD", KEY_STORE_PASSWORD); + properties.put("KAFKA_SSL_KEY_PASSWORD", KEY_PASSWORD); + properties.put("KAFKA_SSL_TRUSTSTORE_LOCATION", trustStorePath.toString()); + properties.put("KAFKA_SSL_TRUSTSTORE_TYPE", keyStoreType); + properties.put("KAFKA_SSL_TRUSTSTORE_PASSWORD", KEY_STORE_PASSWORD); properties.put("KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND", "false"); properties.put("KAFKA_SSL_CLIENT_AUTH", "required"); properties.put("KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM", " "); @@ -54,13 +54,13 @@ protected Map getKafkaServiceConfigProperties() throws Initializ protected Map getAdminClientConfigProperties() { final Map properties = new LinkedHashMap<>(super.getAdminClientConfigProperties()); properties.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SSL.name()); - properties.put(KafkaClientProperty.SSL_KEY_PASSWORD.getProperty(), tlsConfiguration.getKeystorePassword()); - properties.put(KafkaClientProperty.SSL_KEYSTORE_LOCATION.getProperty(), tlsConfiguration.getKeystorePath()); - properties.put(KafkaClientProperty.SSL_KEYSTORE_TYPE.getProperty(), tlsConfiguration.getKeystoreType().getType()); - properties.put(KafkaClientProperty.SSL_KEYSTORE_PASSWORD.getProperty(), tlsConfiguration.getKeystorePassword()); - properties.put(KafkaClientProperty.SSL_TRUSTSTORE_LOCATION.getProperty(), tlsConfiguration.getTruststorePath()); - properties.put(KafkaClientProperty.SSL_TRUSTSTORE_TYPE.getProperty(), tlsConfiguration.getTruststoreType().getType()); - properties.put(KafkaClientProperty.SSL_TRUSTSTORE_PASSWORD.getProperty(), tlsConfiguration.getTruststorePassword()); + properties.put(KafkaClientProperty.SSL_KEY_PASSWORD.getProperty(), KEY_PASSWORD); + properties.put(KafkaClientProperty.SSL_KEYSTORE_LOCATION.getProperty(), keyStorePath.toString()); + properties.put(KafkaClientProperty.SSL_KEYSTORE_TYPE.getProperty(), keyStoreType); + properties.put(KafkaClientProperty.SSL_KEYSTORE_PASSWORD.getProperty(), KEY_STORE_PASSWORD); + properties.put(KafkaClientProperty.SSL_TRUSTSTORE_LOCATION.getProperty(), trustStorePath.toString()); + properties.put(KafkaClientProperty.SSL_TRUSTSTORE_TYPE.getProperty(), keyStoreType); + properties.put(KafkaClientProperty.SSL_TRUSTSTORE_PASSWORD.getProperty(), KEY_STORE_PASSWORD); return properties; } } diff --git a/nifi-extension-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml b/nifi-extension-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml index 3bbf8902c3ba..27b4c474b315 100644 --- a/nifi-extension-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml +++ b/nifi-extension-bundles/nifi-redis-bundle/nifi-redis-extensions/pom.xml @@ -91,7 +91,7 @@ org.apache.nifi - nifi-ssl-context-service + nifi-security-utils-api 2.0.0-SNAPSHOT test diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml index 2b4a7889e6ee..d9576939d29a 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml @@ -291,11 +291,6 @@ commons-codec ${org.apache.commons.codec.version} - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-cert @@ -542,6 +537,18 @@ 2.0.0-SNAPSHOT test + + org.apache.nifi + nifi-security-ssl + 2.0.0-SNAPSHOT + test + + + org.apache.nifi + nifi-security-cert-builder + 2.0.0-SNAPSHOT + test + com.squareup.okhttp3 mockwebserver diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java index 4855a8843891..43dcaac5b33e 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java @@ -81,8 +81,6 @@ import org.apache.nifi.processors.standard.util.SoftLimitBoundedByteArrayOutputStream; import org.apache.nifi.proxy.ProxyConfiguration; import org.apache.nifi.proxy.ProxySpec; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TlsConfiguration; import org.apache.nifi.security.util.TlsException; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.stream.io.StreamUtils; @@ -114,7 +112,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -770,8 +767,7 @@ public void setUpClient(final ProcessContext context) throws TlsException, IOExc if (sslService != null) { final SSLContext sslContext = sslService.createContext(); final SSLSocketFactory socketFactory = sslContext.getSocketFactory(); - final TlsConfiguration tlsConfiguration = sslService.createTlsConfiguration(); - final X509TrustManager trustManager = Objects.requireNonNull(SslContextFactory.getX509TrustManager(tlsConfiguration), "Trust Manager not found"); + final X509TrustManager trustManager = sslService.createTrustManager();; okHttpClientBuilder.sslSocketFactory(socketFactory, trustManager); } diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/InvokeHTTPTest.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/InvokeHTTPTest.java index 3699c842df6f..9f6e617886f7 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/InvokeHTTPTest.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/InvokeHTTPTest.java @@ -36,16 +36,15 @@ import org.apache.nifi.proxy.ProxyConfiguration; import org.apache.nifi.proxy.ProxyConfigurationService; import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.security.util.StandardTlsConfiguration; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; +import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.util.LogMessage; import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; -import org.apache.nifi.web.util.ssl.SslContextUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -57,11 +56,18 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.net.Proxy; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; @@ -137,25 +143,36 @@ public class InvokeHTTPTest { private static final String TLS_CONNECTION_TIMEOUT = "60 s"; - private static TlsConfiguration generatedTlsConfiguration; + private static SSLContext sslContext; - private static TlsConfiguration truststoreTlsConfiguration; + private static SSLContext trustStoreSslContext; + + private static X509ExtendedTrustManager trustManager; private MockWebServer mockWebServer; private TestRunner runner; @BeforeAll - public static void setStores() { - generatedTlsConfiguration = new TemporaryKeyStoreBuilder().build(); - truststoreTlsConfiguration = new StandardTlsConfiguration( - null, - null, - null, - generatedTlsConfiguration.getTruststorePath(), - generatedTlsConfiguration.getTruststorePassword(), - generatedTlsConfiguration.getTruststoreType() - ); + public static void setStores() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final char[] protectionParameter = new char[]{}; + + sslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); + + trustStoreSslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .build(); + + trustManager = new StandardTrustManagerBuilder().trustStore(keyStore).build(); } @BeforeEach @@ -511,19 +528,19 @@ public void testRunGetHttp200SuccessDigestAuthentication() throws InterruptedExc } @Test - public void testRunGetHttp200SuccessSslContextServiceServerTrusted() throws InitializationException, GeneralSecurityException { - assertResponseSuccessSslContextConfigured(generatedTlsConfiguration, truststoreTlsConfiguration); + public void testRunGetHttp200SuccessSslContextServiceServerTrusted() throws InitializationException { + assertResponseSuccessSslContextConfigured(trustStoreSslContext); } @Test - public void testRunGetHttp200SuccessSslContextServiceMutualTrusted() throws InitializationException, GeneralSecurityException { - assertResponseSuccessSslContextConfigured(generatedTlsConfiguration, generatedTlsConfiguration); + public void testRunGetHttp200SuccessSslContextServiceMutualTrusted() throws InitializationException { + assertResponseSuccessSslContextConfigured(sslContext); } @Test - public void testRunGetSslContextServiceMutualTrustedClientCertificateMissing() throws InitializationException, GeneralSecurityException { + public void testRunGetSslContextServiceMutualTrustedClientCertificateMissing() throws InitializationException { runner.setProperty(InvokeHTTP.HTTP2_DISABLED, StringUtils.capitalize(Boolean.TRUE.toString())); - setSslContextConfiguration(generatedTlsConfiguration, truststoreTlsConfiguration); + setSslContextConfiguration(trustStoreSslContext); mockWebServer.requireClientAuth(); setUrlProperty(); @@ -992,8 +1009,8 @@ private void assertResponseSuccessRelationships() { runner.assertTransferCount(InvokeHTTP.FAILURE, 0); } - private void assertResponseSuccessSslContextConfigured(final TlsConfiguration serverTlsConfiguration, final TlsConfiguration clientTlsConfiguration) throws InitializationException, TlsException { - setSslContextConfiguration(serverTlsConfiguration, clientTlsConfiguration); + private void assertResponseSuccessSslContextConfigured(final SSLContext clientSslContext) throws InitializationException { + setSslContextConfiguration(clientSslContext); enqueueResponseCodeAndRun(HTTP_OK); assertResponseSuccessRelationships(); @@ -1003,14 +1020,12 @@ private void assertResponseSuccessSslContextConfigured(final TlsConfiguration se flowFile.assertAttributeExists(InvokeHTTP.REMOTE_DN); } - private void setSslContextConfiguration(final TlsConfiguration serverTlsConfiguration, final TlsConfiguration clientTlsConfiguration) throws InitializationException, TlsException { - final SSLContextService sslContextService = setSslContextService(); - final SSLContext serverSslContext = SslContextUtils.createSslContext(serverTlsConfiguration); - setMockWebServerSslSocketFactory(serverSslContext); + private void setSslContextConfiguration(final SSLContext clientSslContext) throws InitializationException { + setMockWebServerSslSocketFactory(); - final SSLContext clientSslContext = SslContextUtils.createSslContext(clientTlsConfiguration); + final SSLContextService sslContextService = setSslContextService(); when(sslContextService.createContext()).thenReturn(clientSslContext); - when(sslContextService.createTlsConfiguration()).thenReturn(clientTlsConfiguration); + when(sslContextService.createTrustManager()).thenReturn(trustManager); } private SSLContextService setSslContextService() throws InitializationException { @@ -1026,7 +1041,7 @@ private SSLContextService setSslContextService() throws InitializationException return sslContextService; } - private void setMockWebServerSslSocketFactory(final SSLContext sslContext) { + private void setMockWebServerSslSocketFactory() { final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); if (sslSocketFactory == null) { throw new IllegalArgumentException("Socket Factory not found"); diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java index d0143099bf42..5276d3959f87 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java @@ -28,9 +28,10 @@ import org.apache.nifi.processors.standard.http.ContentEncodingStrategy; import org.apache.nifi.processors.standard.http.HttpProtocolStrategy; import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.StandardTlsConfiguration; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; +import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; import org.apache.nifi.security.util.TlsConfiguration; import org.apache.nifi.security.util.TlsPlatform; import org.apache.nifi.serialization.record.MockRecordParser; @@ -41,7 +42,6 @@ import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; -import org.apache.nifi.web.util.ssl.SslContextUtils; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.thread.ThreadPool; @@ -49,16 +49,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; -import org.mockito.Mockito; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + import jakarta.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.File; @@ -70,6 +68,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -82,6 +85,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TestListenHTTP { @@ -97,8 +102,6 @@ public class TestListenHTTP { private final static String HTTP_SERVER_BASEPATH_EL = "${" + BASEPATH_VARIABLE + "}"; private static final String MULTIPART_ATTRIBUTE = "http.multipart.name"; - private static final String TLS_1_3 = "TLSv1.3"; - private static final String TLS_1_2 = "TLSv1.2"; private static final String LOCALHOST = "localhost"; private static final int SOCKET_CONNECT_TIMEOUT = 100; @@ -111,10 +114,6 @@ public class TestListenHTTP { private static final Duration CLIENT_CALL_TIMEOUT = Duration.ofSeconds(10); private static final String LOCALHOST_DN = "CN=localhost"; - private static TlsConfiguration serverConfiguration; - private static TlsConfiguration serverTls_1_3_Configuration; - private static TlsConfiguration serverNoTruststoreConfiguration; - private static SSLContext serverKeyStoreSslContext; private static SSLContext serverKeyStoreNoTrustStoreSslContext; private static SSLContext keyStoreSslContext; private static SSLContext trustStoreSslContext; @@ -123,67 +122,29 @@ public class TestListenHTTP { private ListenHTTP proc; private TestRunner runner; - - static boolean isTls13Supported() { - return TLS_1_3.equals(TlsPlatform.getLatestProtocol()); - } - @BeforeAll public static void setUpSuite() throws GeneralSecurityException { - // generate new keystore and truststore - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - - serverConfiguration = new StandardTlsConfiguration( - tlsConfiguration.getKeystorePath(), - tlsConfiguration.getKeystorePassword(), - tlsConfiguration.getKeyPassword(), - tlsConfiguration.getKeystoreType(), - tlsConfiguration.getTruststorePath(), - tlsConfiguration.getTruststorePassword(), - tlsConfiguration.getTruststoreType(), - TLS_1_2 - ); - serverTls_1_3_Configuration = new StandardTlsConfiguration( - tlsConfiguration.getKeystorePath(), - tlsConfiguration.getKeystorePassword(), - tlsConfiguration.getKeyPassword(), - tlsConfiguration.getKeystoreType(), - tlsConfiguration.getTruststorePath(), - tlsConfiguration.getTruststorePassword(), - tlsConfiguration.getTruststoreType(), - TLS_1_3 - ); - serverNoTruststoreConfiguration = new StandardTlsConfiguration( - tlsConfiguration.getKeystorePath(), - tlsConfiguration.getKeystorePassword(), - tlsConfiguration.getKeyPassword(), - tlsConfiguration.getKeystoreType(), - null, - null, - null, - TLS_1_2 - ); - - serverKeyStoreSslContext = SslContextUtils.createSslContext(serverConfiguration); - trustManager = SslContextFactory.getX509TrustManager(serverConfiguration); - serverKeyStoreNoTrustStoreSslContext = SslContextFactory.createSslContext(serverNoTruststoreConfiguration, new TrustManager[]{trustManager}); - - keyStoreSslContext = SslContextUtils.createSslContext(new StandardTlsConfiguration( - tlsConfiguration.getKeystorePath(), - tlsConfiguration.getKeystorePassword(), - tlsConfiguration.getKeystoreType(), - tlsConfiguration.getTruststorePath(), - tlsConfiguration.getTruststorePassword(), - tlsConfiguration.getTruststoreType()) - ); - trustStoreSslContext = SslContextUtils.createSslContext(new StandardTlsConfiguration( - null, - null, - null, - tlsConfiguration.getTruststorePath(), - tlsConfiguration.getTruststorePassword(), - tlsConfiguration.getTruststoreType()) - ); + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final char[] protectionParameter = new char[]{}; + + trustManager = new StandardTrustManagerBuilder().trustStore(keyStore).build(); + serverKeyStoreNoTrustStoreSslContext = new StandardSslContextBuilder() + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); + + keyStoreSslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); + trustStoreSslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .build(); } @BeforeEach @@ -238,7 +199,7 @@ public void testPOSTRequestsReturnCodeReceivedWithEL() throws Exception { @Test public void testSecurePOSTRequestsReceivedWithoutELHttp2AndHttp1() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); runner.setProperty(ListenHTTP.HTTP_PROTOCOL_STRATEGY, HttpProtocolStrategy.H2_HTTP_1_1); @@ -249,7 +210,7 @@ public void testSecurePOSTRequestsReceivedWithoutELHttp2AndHttp1() throws Except @Test public void testSecurePOSTRequestsReturnCodeReceivedWithoutELHttp2() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); @@ -261,7 +222,7 @@ public void testSecurePOSTRequestsReturnCodeReceivedWithoutELHttp2() throws Exce @Test public void testSecurePOSTRequestsReceivedWithEL() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO); runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL); @@ -272,7 +233,7 @@ public void testSecurePOSTRequestsReceivedWithEL() throws Exception { @Test public void testSecurePOSTRequestsReturnCodeReceivedWithEL() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); @@ -283,7 +244,7 @@ public void testSecurePOSTRequestsReturnCodeReceivedWithEL() throws Exception { @Test public void testSecureTwoWaySslPOSTRequestsReceivedWithoutEL() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); runner.assertValid(); @@ -293,7 +254,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithoutEL() throws Exception @Test public void testSecureTwoWaySslPOSTRequestsReceivedWithUnauthorizedSubjectDn() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.AUTHORIZED_DN_PATTERN, "CN=other"); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); @@ -304,7 +265,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithUnauthorizedSubjectDn() t @Test public void testSecureTwoWaySslPOSTRequestsReceivedWithAuthorizedIssuerDn() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.AUTHORIZED_DN_PATTERN, LOCALHOST_DN); runner.setProperty(ListenHTTP.AUTHORIZED_ISSUER_DN_PATTERN, LOCALHOST_DN); @@ -316,7 +277,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithAuthorizedIssuerDn() thro @Test public void testSecureTwoWaySslPOSTRequestsReceivedWithUnauthorizedIssuerDn() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.AUTHORIZED_DN_PATTERN, LOCALHOST_DN); // Although subject is authorized, issuer is not runner.setProperty(ListenHTTP.AUTHORIZED_ISSUER_DN_PATTERN, "CN=other"); @@ -328,7 +289,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithUnauthorizedIssuerDn() th @Test public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithoutEL() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); @@ -339,7 +300,7 @@ public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithoutEL() throws @Test public void testSecureTwoWaySslPOSTRequestsReceivedWithEL() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL); @@ -350,7 +311,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithEL() throws Exception { @Test public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithEL() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); @@ -361,12 +322,12 @@ public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithEL() throws Exc @Test public void testSecureServerSupportsCurrentTlsProtocolVersion() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO); final int listeningPort = startSecureServer(); final SSLSocketFactory sslSocketFactory = trustStoreSslContext.getSocketFactory(); try (final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(LOCALHOST, listeningPort)) { - final String currentProtocol = serverNoTruststoreConfiguration.getProtocol(); + final String currentProtocol = TlsPlatform.getLatestProtocol(); sslSocket.setEnabledProtocols(new String[]{currentProtocol}); sslSocket.startHandshake(); @@ -377,37 +338,19 @@ public void testSecureServerSupportsCurrentTlsProtocolVersion() throws Exception @Test public void testSecureServerTrustStoreConfiguredClientAuthenticationRequired() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED, serverConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED); final int port = startSecureServer(); assertThrows(IOException.class, () -> sendMessage(null, true, port, false, HTTP_POST)); } @Test public void testSecureServerTrustStoreNotConfiguredClientAuthenticationNotRequired() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverNoTruststoreConfiguration); + configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO); final int port = startSecureServer(); final int responseCode = sendMessage(null, true, port, true, HTTP_POST); assertEquals(HttpServletResponse.SC_NO_CONTENT, responseCode); } - @EnabledIf(value = "isTls13Supported", disabledReason = "TLSv1.3 is not supported") - @Test - public void testSecureServerRejectsUnsupportedTlsProtocolVersion() throws Exception { - configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO, serverTls_1_3_Configuration); - - runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); - runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - final int listeningPort = startWebServer(); - final SSLSocketFactory sslSocketFactory = trustStoreSslContext.getSocketFactory(); - try (final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(LOCALHOST, listeningPort)) { - sslSocket.setEnabledProtocols(new String[]{TLS_1_2}); - - assertThrows(SSLHandshakeException.class, sslSocket::startHandshake); - } - } - @Test public void testMaxThreadPoolSizeTooLow() { runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); @@ -471,16 +414,18 @@ public void testPOSTRequestsReceivedWithRecordReader() throws Exception { } final String expectedMessage = - "\"1\",\"rec1\",\"101\"\n" + - "\"2\",\"rec2\",\"102\"\n" + - "\"3\",\"rec3\",\"103\"\n" + - "\"4\",\"rec4\",\"104\"\n"; + """ + "1","rec1","101" + "2","rec2","102" + "3","rec3","103" + "4","rec4","104" + """; startWebServerAndSendMessages(Collections.singletonList(""), HttpServletResponse.SC_OK, false, false, HTTP_POST); List mockFlowFiles = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS); runner.assertTransferCount(RELATIONSHIP_SUCCESS, 1); - mockFlowFiles.get(0).assertContentEquals(expectedMessage); + mockFlowFiles.getFirst().assertContentEquals(expectedMessage); } @Test @@ -533,7 +478,7 @@ public void testPostContentEncodingGzipAccepted() throws IOException { assertTrue(response.isSuccessful()); runner.assertTransferCount(RELATIONSHIP_SUCCESS, 1); - final MockFlowFile flowFile = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).iterator().next(); + final MockFlowFile flowFile = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).getFirst(); flowFile.assertContentEquals(message); } } @@ -631,8 +576,8 @@ private void testPOSTRequestsReceived(int returnCode, boolean secure, boolean tw mockFlowFiles.get(3).assertContentEquals("payload 2"); if (twoWaySsl) { - mockFlowFiles.get(0).assertAttributeEquals("restlistener.remote.user.dn", LOCALHOST_DN); - mockFlowFiles.get(0).assertAttributeEquals("restlistener.remote.issuer.dn", LOCALHOST_DN); + mockFlowFiles.getFirst().assertAttributeEquals("restlistener.remote.user.dn", LOCALHOST_DN); + mockFlowFiles.getFirst().assertAttributeEquals("restlistener.remote.issuer.dn", LOCALHOST_DN); } } } @@ -678,16 +623,18 @@ private void startWebServerAndSendMessages(final List messages, final in } } - private void configureProcessorSslContextService(final ListenHTTP.ClientAuthentication clientAuthentication, - final TlsConfiguration tlsConfiguration) throws InitializationException { - final RestrictedSSLContextService sslContextService = Mockito.mock(RestrictedSSLContextService.class); - Mockito.when(sslContextService.getIdentifier()).thenReturn(SSL_CONTEXT_SERVICE_IDENTIFIER); - Mockito.when(sslContextService.createTlsConfiguration()).thenReturn(tlsConfiguration); + private void configureProcessorSslContextService(final ListenHTTP.ClientAuthentication clientAuthentication) throws InitializationException { + final RestrictedSSLContextService sslContextService = mock(RestrictedSSLContextService.class); + when(sslContextService.getIdentifier()).thenReturn(SSL_CONTEXT_SERVICE_IDENTIFIER); + + final TlsConfiguration tlsConfiguration = mock(TlsConfiguration.class); + when(tlsConfiguration.getEnabledProtocols()).thenReturn(TlsPlatform.getPreferredProtocols().toArray(new String[0])); + when(sslContextService.createTlsConfiguration()).thenReturn(tlsConfiguration); if (ListenHTTP.ClientAuthentication.REQUIRED.equals(clientAuthentication)) { - Mockito.when(sslContextService.createContext()).thenReturn(serverKeyStoreSslContext); + when(sslContextService.createContext()).thenReturn(keyStoreSslContext); } else { - Mockito.when(sslContextService.createContext()).thenReturn(serverKeyStoreNoTrustStoreSslContext); + when(sslContextService.createContext()).thenReturn(serverKeyStoreNoTrustStoreSslContext); } runner.addControllerService(SSL_CONTEXT_SERVICE_IDENTIFIER, sslContextService); diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenTCP.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenTCP.java index 8845b0be1cc5..45f0f7f9fe6f 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenTCP.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenTCP.java @@ -24,21 +24,28 @@ import org.apache.nifi.event.transport.netty.ByteArrayNettyEventSenderFactory; import org.apache.nifi.processor.util.listen.ListenerProperties; import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.util.ClientAuth; -import org.apache.nifi.security.util.TlsException; import org.apache.nifi.ssl.RestrictedSSLContextService; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; -import org.apache.nifi.web.util.ssl.SslContextUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -58,9 +65,23 @@ public class TestListenTCP { private TestRunner runner; @BeforeAll - public static void configureServices() throws TlsException { - keyStoreSslContext = SslContextUtils.createKeyStoreSslContext(); - trustStoreSslContext = SslContextUtils.createTrustStoreSslContext(); + public static void configureServices() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + + keyStoreSslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(new char[]{}) + .build(); + + trustStoreSslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyPassword(new char[]{}) + .build(); } @BeforeEach diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutTCP.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutTCP.java index f20db10aeabb..f040e2ccd4c4 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutTCP.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutTCP.java @@ -27,8 +27,9 @@ import org.apache.nifi.processors.standard.property.TransmissionStrategy; import org.apache.nifi.provenance.ProvenanceEventRecord; import org.apache.nifi.provenance.ProvenanceEventType; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.serialization.RecordReader; import org.apache.nifi.serialization.RecordReaderFactory; import org.apache.nifi.serialization.RecordSetWriter; @@ -40,7 +41,6 @@ import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; -import org.apache.nifi.web.util.ssl.SslContextUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,11 +52,18 @@ import org.mockito.stubbing.Answer; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.Arrays; import java.util.Collections; @@ -138,10 +145,7 @@ public void testRunSuccess() throws Exception { @Test public void testRunSuccessSslContextService() throws Exception { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - - final SSLContext sslContext = SslContextUtils.createSslContext(tlsConfiguration); - assertNotNull(sslContext, "SSLContext not found"); + final SSLContext sslContext = getSslContext(); final String identifier = SSLContextService.class.getName(); final SSLContextService sslContextService = Mockito.mock(SSLContextService.class); Mockito.when(sslContextService.getIdentifier()).thenReturn(identifier); @@ -367,6 +371,19 @@ private String[] createContent(final int size) { return new String[] {new String(content)}; } + private SSLContext getSslContext() throws GeneralSecurityException { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + return new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(new char[]{}) + .build(); + } + private static class MockRecordSetWriter implements RecordSetWriter { private final OutputStream outputStream; diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/web/util/ssl/SslContextUtils.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/web/util/ssl/SslContextUtils.java deleted file mode 100644 index 8935b3b0e372..000000000000 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/web/util/ssl/SslContextUtils.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.web.util.ssl; - -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.StandardTlsConfiguration; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; - -import javax.net.ssl.SSLContext; -import java.io.File; - -public class SslContextUtils { - private static final TlsConfiguration TLS_CONFIGURATION; - - private static final TlsConfiguration KEYSTORE_TLS_CONFIGURATION; - - private static final TlsConfiguration TRUSTSTORE_TLS_CONFIGURATION; - - static { - try { - TLS_CONFIGURATION = new TemporaryKeyStoreBuilder().build(); - - KEYSTORE_TLS_CONFIGURATION = new StandardTlsConfiguration( - TLS_CONFIGURATION.getKeystorePath(), - TLS_CONFIGURATION.getKeystorePassword(), - TLS_CONFIGURATION.getKeyPassword(), - TLS_CONFIGURATION.getKeystoreType().getType(), - TLS_CONFIGURATION.getTruststorePath(), - TLS_CONFIGURATION.getTruststorePassword(), - TLS_CONFIGURATION.getTruststoreType().getType() - ); - - TRUSTSTORE_TLS_CONFIGURATION = new StandardTlsConfiguration( - null, - null, - null, - null, - TLS_CONFIGURATION.getTruststorePath(), - TLS_CONFIGURATION.getTruststorePassword(), - TLS_CONFIGURATION.getTruststoreType().getType() - ); - } catch (final Exception e) { - throw new IllegalStateException("Failed to create TLS configuration for testing", e); - } - } - - /** - * Create SSLContext with Key Store and Trust Store configured - * - * @return SSLContext configured with Key Store and Trust Store - * @throws TlsException Thrown on SslContextFactory.createSslContext() - */ - public static SSLContext createKeyStoreSslContext() throws TlsException { - return SslContextFactory.createSslContext(KEYSTORE_TLS_CONFIGURATION); - } - - /** - * Create SSLContext with Trust Store configured - * - * @return SSLContext configured with Trust Store - * @throws TlsException Thrown on SslContextFactory.createSslContext() - */ - public static SSLContext createTrustStoreSslContext() throws TlsException { - return SslContextFactory.createSslContext(TRUSTSTORE_TLS_CONFIGURATION); - } - - /** - * Create SSLContext using Keystore and Truststore with deleteOnExit() for files - * - * @param tlsConfiguration TLS Configuration - * @return SSLContext configured with generated Keystore and Truststore - * @throws TlsException Thrown on SslContextFactory.createSslContext() - */ - public static SSLContext createSslContext(final TlsConfiguration tlsConfiguration) throws TlsException { - final String keystorePath = tlsConfiguration.getKeystorePath(); - if (StringUtils.isNotBlank(keystorePath)) { - final File keystoreFile = new File(keystorePath); - keystoreFile.deleteOnExit(); - } - - final String truststorePath = tlsConfiguration.getTruststorePath(); - if (StringUtils.isNotBlank(truststorePath)) { - final File truststoreFile = new File(truststorePath); - truststoreFile.deleteOnExit(); - } - - final SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration); - if (sslContext == null) { - throw new TlsException(String.format("Failed to create SSLContext from Configuration %s", tlsConfiguration)); - } - return sslContext; - } -} diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/pom.xml b/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/pom.xml index 42051bf93be9..6790224eddfa 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/pom.xml +++ b/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/pom.xml @@ -42,7 +42,13 @@ org.apache.nifi - nifi-security-utils + nifi-security-ssl + 2.0.0-SNAPSHOT + test + + + org.apache.nifi + nifi-security-cert-builder 2.0.0-SNAPSHOT test diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/map/DistributedMapCacheTlsTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/map/DistributedMapCacheTlsTest.java index 8482e7dd1122..bcad46f581b2 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/map/DistributedMapCacheTlsTest.java +++ b/nifi-extension-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/map/DistributedMapCacheTlsTest.java @@ -22,9 +22,9 @@ import org.apache.nifi.distributed.cache.client.Serializer; import org.apache.nifi.distributed.cache.client.exception.DeserializationException; import org.apache.nifi.processor.Processor; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; @@ -34,14 +34,22 @@ import org.mockito.Mockito; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; public class DistributedMapCacheTlsTest { @@ -98,19 +106,21 @@ public void testMapPut() throws IOException { assertFalse(client.containsKey(key, serializer)); } - /** - * Create a fresh {@link SSLContext} in order to test mutual TLS authentication aspect of the - * distributed cache protocol. - * - * @return a NiFi {@link SSLContextService}, to be used to secure the distributed cache comms - * @throws GeneralSecurityException on SSLContext generation failure - */ - private static SSLContextService createSslContextService() throws GeneralSecurityException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - final SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration); + private static SSLContextService createSslContextService() throws NoSuchAlgorithmException { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final SSLContext sslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(new char[]{}) + .build(); + final SSLContextService sslContextService = Mockito.mock(SSLContextService.class); - Mockito.when(sslContextService.getIdentifier()).thenReturn(sslContextService.getClass().getName()); - Mockito.when(sslContextService.createContext()).thenReturn(sslContext); + when(sslContextService.getIdentifier()).thenReturn(sslContextService.getClass().getName()); + when(sslContextService.createContext()).thenReturn(sslContext); return sslContextService; } diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/pom.xml b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/pom.xml index 24c2380f3934..c4ecb99723e9 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/pom.xml +++ b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/pom.xml @@ -28,12 +28,18 @@ org.apache.nifi - nifi-security-utils + nifi-security-utils-api + + + org.apache.nifi + nifi-security-ssl 2.0.0-SNAPSHOT org.apache.nifi - nifi-security-utils-api + nifi-security-cert-builder + 2.0.0-SNAPSHOT + test diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java index 94b963d76d7a..23b6f9137c57 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java +++ b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java @@ -34,11 +34,11 @@ import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; +import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; import org.apache.nifi.security.util.KeystoreType; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.StandardTlsConfiguration; import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; import org.apache.nifi.security.util.TlsPlatform; import org.apache.nifi.util.StringUtils; @@ -49,6 +49,9 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.util.ArrayList; @@ -242,16 +245,46 @@ public TlsConfiguration createTlsConfiguration() { */ @Override public SSLContext createContext() { - final TlsConfiguration tlsConfiguration = createTlsConfiguration(); - if (!tlsConfiguration.isTruststorePopulated()) { - getLogger().warn("Trust Store properties not found: using platform default Certificate Authorities"); - } - try { - final TrustManager[] trustManagers = SslContextFactory.getTrustManagers(tlsConfiguration); - return SslContextFactory.createSslContext(tlsConfiguration, trustManagers); - } catch (final TlsException e) { - getLogger().error("Unable to create SSLContext: {}", e.getLocalizedMessage()); + final String protocol = getSslAlgorithm(); + final StandardSslContextBuilder sslContextBuilder = new StandardSslContextBuilder().protocol(protocol); + + final TrustManager trustManager; + final String trustStoreFile = getKeyStoreFile(); + if (trustStoreFile == null || trustStoreFile.isBlank()) { + getLogger().debug("Trust Store File not configured"); + } else { + trustManager = createTrustManager(); + sslContextBuilder.trustManager(trustManager); + } + + final String keyStoreFile = getKeyStoreFile(); + if (keyStoreFile == null || keyStoreFile.isBlank()) { + getLogger().debug("Key Store File not configured"); + } else { + final StandardKeyStoreBuilder keyStoreBuilder = new StandardKeyStoreBuilder(); + keyStoreBuilder.type(getKeyStoreType()); + keyStoreBuilder.password(getKeyStorePassword().toCharArray()); + + final Path keyStorePath = Paths.get(keyStoreFile); + try (InputStream keyStoreInputStream = Files.newInputStream(keyStorePath)) { + keyStoreBuilder.inputStream(keyStoreInputStream); + final KeyStore keyStore = keyStoreBuilder.build(); + sslContextBuilder.keyStore(keyStore); + } + + final char[] keyProtectionPassword; + final String keyPassword = getKeyPassword(); + if (keyPassword == null) { + keyProtectionPassword = getKeyStorePassword().toCharArray(); + } else { + keyProtectionPassword = keyPassword.toCharArray(); + } + sslContextBuilder.keyPassword(keyProtectionPassword); + } + + return sslContextBuilder.build(); + } catch (final Exception e) { throw new ProcessException("Unable to create SSLContext", e); } } @@ -264,12 +297,28 @@ public SSLContext createContext() { @Override public X509TrustManager createTrustManager() { try { - final X509TrustManager trustManager = SslContextFactory.getX509TrustManager(createTlsConfiguration()); - if (trustManager == null) { - throw new ProcessException("X.509 Trust Manager not found using configured properties"); + final char[] password; + final String trustStorePassword = getTrustStorePassword(); + if (trustStorePassword == null || trustStorePassword.isBlank()) { + password = null; + } else { + password = trustStorePassword.toCharArray(); + } + + final StandardKeyStoreBuilder builder = new StandardKeyStoreBuilder().type(getTrustStoreType()).password(password); + + final String trustStoreFile = getTrustStoreFile(); + if (trustStoreFile == null || trustStoreFile.isBlank()) { + throw new ProcessException("Trust Store File not specified"); } - return trustManager; - } catch (final TlsException e) { + + final Path trustStorePath = Paths.get(trustStoreFile); + try (InputStream trustStoreInputStream = Files.newInputStream(trustStorePath)) { + builder.inputStream(trustStoreInputStream); + final KeyStore trustStore = builder.build(); + return new StandardTrustManagerBuilder().trustStore(trustStore).build(); + } + } catch (final Exception e) { throw new ProcessException("Unable to create X.509 Trust Manager", e); } } diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardTlsConfiguration.java similarity index 78% rename from nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java rename to nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardTlsConfiguration.java index c9ac19c95b37..8d8d9c9489d1 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java +++ b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardTlsConfiguration.java @@ -14,9 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.nifi.security.util; +package org.apache.nifi.ssl; import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; +import org.apache.nifi.security.util.KeystoreType; +import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.util.TlsPlatform; import java.io.FileInputStream; import java.io.InputStream; @@ -25,15 +28,12 @@ import java.util.Objects; /** - * This class serves as a concrete immutable domain object (acting as an internal DTO) - * for the various keystore and truststore configuration settings necessary for - * building {@link javax.net.ssl.SSLContext}s. + * Standard implementation of TLS Configuration for SSL Context Services */ -public class StandardTlsConfiguration implements TlsConfiguration { +class StandardTlsConfiguration implements TlsConfiguration { protected static final String SSL_PROTOCOL = "SSL"; protected static final String TLS_PROTOCOL = "TLS"; - private static final String TLS_PROTOCOL_VERSION = TlsPlatform.getLatestProtocol(); private static final String MASKED_PASSWORD_LOG = "********"; private static final String NULL_LOG = "null"; @@ -48,63 +48,6 @@ public class StandardTlsConfiguration implements TlsConfiguration { private final String protocol; - /** - * Default constructor present for testing and completeness. - */ - public StandardTlsConfiguration() { - this(null, null, null, "", null, null, "", null); - } - - /** - * Instantiates a container object with the given configuration values. - * - * @param keystorePath the keystore path - * @param keystorePassword the keystore password - * @param keystoreType the keystore type - * @param truststorePath the truststore path - * @param truststorePassword the truststore password - * @param truststoreType the truststore type - */ - public StandardTlsConfiguration(String keystorePath, String keystorePassword, KeystoreType keystoreType, String truststorePath, String truststorePassword, KeystoreType truststoreType) { - this(keystorePath, keystorePassword, keystorePassword, keystoreType, truststorePath, truststorePassword, truststoreType, TLS_PROTOCOL_VERSION); - } - - /** - * Instantiates a container object with the given configuration values. - * - * @param keystorePath the keystore path - * @param keystorePassword the keystore password - * @param keyPassword the key password - * @param keystoreType the keystore type - * @param truststorePath the truststore path - * @param truststorePassword the truststore password - * @param truststoreType the truststore type - */ - public StandardTlsConfiguration(String keystorePath, String keystorePassword, String keyPassword, - KeystoreType keystoreType, String truststorePath, String truststorePassword, KeystoreType truststoreType) { - this(keystorePath, keystorePassword, keyPassword, keystoreType, truststorePath, truststorePassword, truststoreType, TLS_PROTOCOL_VERSION); - } - - /** - * Instantiates a container object with the given configuration values. - * - * @param keystorePath the keystore path - * @param keystorePassword the keystore password - * @param keyPassword the key password - * @param keystoreType the keystore type as a String - * @param truststorePath the truststore path - * @param truststorePassword the truststore password - * @param truststoreType the truststore type as a String - */ - public StandardTlsConfiguration(String keystorePath, String keystorePassword, String keyPassword, - String keystoreType, String truststorePath, String truststorePassword, String truststoreType) { - this(keystorePath, keystorePassword, keyPassword, - (KeystoreType.isValidKeystoreType(keystoreType) ? KeystoreType.valueOf(keystoreType.toUpperCase()) : null), - truststorePath, truststorePassword, - (KeystoreType.isValidKeystoreType(truststoreType) ? KeystoreType.valueOf(truststoreType.toUpperCase()) : null), - TLS_PROTOCOL_VERSION); - } - /** * Instantiates a container object with the given configuration values. * @@ -150,22 +93,6 @@ public StandardTlsConfiguration(String keystorePath, String keystorePassword, St this.protocol = protocol; } - /** - * Instantiates a container object with a deep copy of the given configuration values. - * - * @param other the configuration to copy - */ - public StandardTlsConfiguration(TlsConfiguration other) { - this.keystorePath = other.getKeystorePath(); - this.keystorePassword = other.getKeystorePassword(); - this.keyPassword = other.getKeyPassword(); - this.keystoreType = other.getKeystoreType(); - this.truststorePath = other.getTruststorePath(); - this.truststorePassword = other.getTruststorePassword(); - this.truststoreType = other.getTruststoreType(); - this.protocol = other.getProtocol(); - } - @Override public String getKeystorePath() { return keystorePath; diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java deleted file mode 100644 index 4a0b5c65c7c4..000000000000 --- a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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.ssl; - -import org.apache.nifi.components.ValidationContext; -import org.apache.nifi.components.ValidationResult; -import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.util.MockProcessContext; -import org.apache.nifi.util.MockValidationContext; -import org.apache.nifi.util.TestRunner; -import org.apache.nifi.util.TestRunners; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class SSLContextServiceTest { - private static TlsConfiguration tlsConfiguration; - - @BeforeAll - public static void setTlsConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - } - - @Test - public void testShouldFailToAddControllerServiceWithNoProperties() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - final SSLContextService service = new StandardSSLContextService(); - final Map properties = new HashMap<>(); - runner.addControllerService("test-no-properties", service, properties); - runner.assertNotValid(service); - } - - @Test - public void testShouldFailToAddControllerServiceWithoutKeystoreType() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - final SSLContextService service = new StandardSSLContextService(); - final Map properties = new HashMap<>(); - properties.put(StandardSSLContextService.KEYSTORE.getName(), tlsConfiguration.getKeystorePath()); - properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getKeystorePassword()); - runner.addControllerService("test-no-keystore-type", service, properties); - runner.assertNotValid(service); - } - - @Test - public void testShouldFailToAddControllerServiceWithOnlyTruststorePath() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - final SSLContextService service = new StandardSSLContextService(); - final Map properties = new HashMap<>(); - properties.put(StandardSSLContextService.KEYSTORE.getName(), tlsConfiguration.getKeystorePath()); - properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getKeystorePassword()); - properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - properties.put(StandardSSLContextService.TRUSTSTORE.getName(), tlsConfiguration.getTruststorePath()); - runner.addControllerService("test-no-truststore-password-or-type", service, properties); - runner.assertNotValid(service); - } - - @Test - public void testShouldFailToAddControllerServiceWithWrongPasswords() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - final SSLContextService service = new StandardSSLContextService(); - final Map properties = new HashMap<>(); - properties.put(StandardSSLContextService.KEYSTORE.getName(), tlsConfiguration.getKeystorePath()); - properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), String.class.getSimpleName()); - properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - properties.put(StandardSSLContextService.TRUSTSTORE.getName(), tlsConfiguration.getTruststorePath()); - properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), String.class.getSimpleName()); - properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.addControllerService("test-wrong-passwords", service, properties); - - runner.assertNotValid(service); - } - - @Test - public void testShouldFailToAddControllerServiceWithNonExistentFiles() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - final SSLContextService service = new StandardSSLContextService(); - final Map properties = new HashMap<>(); - properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/DOES-NOT-EXIST.jks"); - properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - properties.put(StandardSSLContextService.TRUSTSTORE.getName(), tlsConfiguration.getTruststorePath()); - properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.addControllerService("test-keystore-file-does-not-exist", service, properties); - runner.assertNotValid(service); - } - - @Test - public void testGood() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - SSLContextService service = new StandardSSLContextService(); - runner.addControllerService("test-good1", service); - runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), tlsConfiguration.getKeystorePath()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getKeystorePassword()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), tlsConfiguration.getTruststorePath()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.enableControllerService(service); - - runner.setProperty("SSL Context Svc ID", "test-good1"); - runner.assertValid(service); - service = (SSLContextService) runner.getProcessContext().getControllerServiceLookup().getControllerService("test-good1"); - assertNotNull(service); - SSLContextService sslService = service; - sslService.createContext(); - } - - @Test - public void testGoodWithEL() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - SSLContextService service = new StandardSSLContextService(); - runner.addControllerService("test-good1", service); - runner.setEnvironmentVariableValue("keystore", tlsConfiguration.getKeystorePath()); - runner.setEnvironmentVariableValue("truststore", tlsConfiguration.getTruststorePath()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), "${keystore}"); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getKeystorePassword()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), "${truststore}"); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.enableControllerService(service); - - runner.setProperty("SSL Context Svc ID", "test-good1"); - runner.assertValid(service); - service = (SSLContextService) runner.getProcessContext().getControllerServiceLookup().getControllerService("test-good1"); - assertNotNull(service); - SSLContextService sslService = service; - sslService.createContext(); - } - - @Test - public void testWithChanges() throws InitializationException { - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - SSLContextService service = new StandardSSLContextService(); - runner.addControllerService("test-good1", service); - runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), tlsConfiguration.getKeystorePath()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getKeystorePassword()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), tlsConfiguration.getTruststorePath()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.enableControllerService(service); - - runner.setProperty("SSL Context Svc ID", "test-good1"); - runner.assertValid(service); - - runner.disableControllerService(service); - runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/DOES-NOT-EXIST.jks"); - runner.assertNotValid(service); - - runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), tlsConfiguration.getKeystorePath()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), String.class.getSimpleName()); - runner.assertNotValid(service); - - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - runner.enableControllerService(service); - runner.assertValid(service); - } - - @Test - public void testValidationResultsCacheShouldExpire() throws InitializationException, IOException { - // Copy the keystore and truststore to a tmp directory so the originals are not modified - File originalKeystore = new File(tlsConfiguration.getKeystorePath()); - File originalTruststore = new File(tlsConfiguration.getTruststorePath()); - - File tmpKeystore = File.createTempFile(getClass().getSimpleName(), ".keystore.p12"); - File tmpTruststore = File.createTempFile(getClass().getSimpleName(), ".truststore.p12"); - - Files.copy(originalKeystore.toPath(), tmpKeystore.toPath(), StandardCopyOption.REPLACE_EXISTING); - Files.copy(originalTruststore.toPath(), tmpTruststore.toPath(), StandardCopyOption.REPLACE_EXISTING); - - final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - StandardSSLContextService service = new StandardSSLContextService(); - final String serviceIdentifier = "test-should-expire"; - runner.addControllerService(serviceIdentifier, service); - runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), tmpKeystore.getAbsolutePath()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), tlsConfiguration.getKeystorePassword()); - runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), tlsConfiguration.getKeystoreType().getType()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), tmpTruststore.getAbsolutePath()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.enableControllerService(service); - - runner.setProperty("SSL Context Svc ID", serviceIdentifier); - runner.assertValid(service); - - // Act - assertTrue(tmpKeystore.delete()); - assertFalse(tmpKeystore.exists()); - - // Manually validate the service (expecting cached result to be returned) - final MockProcessContext processContext = (MockProcessContext) runner.getProcessContext(); - // This service does not use the state manager - final ValidationContext validationContext = new MockValidationContext(processContext, null); - - // Even though the keystore file is no longer present, because no property changed, the cached result is still valid - Collection validationResults = service.customValidate(validationContext); - assertTrue(validationResults.isEmpty(), "validation results is not empty"); - - // Assert - - // Have to exhaust the cached result by checking n-1 more times - for (int i = 2; i < service.getValidationCacheExpiration(); i++) { - validationResults = service.customValidate(validationContext); - assertTrue(validationResults.isEmpty(), "validation results is not empty"); - } - - validationResults = service.customValidate(validationContext); - assertFalse(validationResults.isEmpty(), "validation results is empty"); - } - - @Test - public void testGoodTrustOnly() throws InitializationException { - TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); - SSLContextService service = new StandardSSLContextService(); - HashMap properties = new HashMap<>(); - properties.put(StandardSSLContextService.TRUSTSTORE.getName(), tlsConfiguration.getTruststorePath()); - properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), tlsConfiguration.getTruststorePassword()); - properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), tlsConfiguration.getTruststoreType().getType()); - runner.addControllerService("test-good2", service, properties); - runner.enableControllerService(service); - - runner.setProperty("SSL Context Svc ID", "test-good2"); - runner.assertValid(); - assertNotNull(service); - service.createContext(); - } -} diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardRestrictedSSLContextServiceTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardRestrictedSSLContextServiceTest.java index 8454f0fc0dc8..73b4de774867 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardRestrictedSSLContextServiceTest.java +++ b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardRestrictedSSLContextServiceTest.java @@ -18,8 +18,8 @@ import org.apache.nifi.processor.Processor; import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; import org.apache.nifi.security.util.TlsPlatform; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; @@ -27,19 +27,42 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(MockitoExtension.class) public class StandardRestrictedSSLContextServiceTest { + private static final String ALIAS = "entry-0"; + private static final String SERVICE_ID = StandardRestrictedSSLContextService.class.getSimpleName(); - private static TlsConfiguration tlsConfiguration; + private static final String KEY_STORE_EXTENSION = ".p12"; + + private static final String KEY_STORE_PASS = StandardRestrictedSSLContextServiceTest.class.getName(); + + @TempDir + private static Path keyStoreDirectory; + + private static Path keyStorePath; + + private static String keyStoreType; @Mock private Processor processor; @@ -49,8 +72,18 @@ public class StandardRestrictedSSLContextServiceTest { private TestRunner runner; @BeforeAll - public static void setConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); + public static void setConfiguration() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder().build(); + keyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), KEY_STORE_PASS.toCharArray(), new Certificate[]{certificate}); + + keyStorePath = Files.createTempFile(keyStoreDirectory, StandardRestrictedSSLContextServiceTest.class.getSimpleName(), KEY_STORE_EXTENSION); + try (OutputStream outputStream = Files.newOutputStream(keyStorePath)) { + keyStore.store(outputStream, KEY_STORE_PASS.toCharArray()); + } + + keyStoreType = keyStore.getType().toUpperCase(); } @BeforeEach @@ -95,11 +128,11 @@ public void testPreferredProtocolsCreateContext() throws InitializationException } private void setMinimumProperties() { - runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE, tlsConfiguration.getKeystorePath()); - runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE_PASSWORD, tlsConfiguration.getKeystorePassword()); - runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE, tlsConfiguration.getTruststorePath()); - runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE_PASSWORD, tlsConfiguration.getTruststorePassword()); - runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); + runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE, keyStorePath.toString()); + runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE_PASSWORD, KEY_STORE_PASS); + runner.setProperty(service, StandardRestrictedSSLContextService.KEYSTORE_TYPE, keyStoreType); + runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE, keyStorePath.toString()); + runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE_PASSWORD, KEY_STORE_PASS); + runner.setProperty(service, StandardRestrictedSSLContextService.TRUSTSTORE_TYPE, keyStoreType); } } diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardSSLContextServiceTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardSSLContextServiceTest.java new file mode 100644 index 000000000000..e4dcc45cae30 --- /dev/null +++ b/nifi-extension-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/StandardSSLContextServiceTest.java @@ -0,0 +1,302 @@ +/* + * 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.ssl; + +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.util.MockProcessContext; +import org.apache.nifi.util.MockValidationContext; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +public class StandardSSLContextServiceTest { + private static final String SERVICE_PROPERTY = "SSL Context Svc ID"; + + private static final String SERVICE_ID = StandardSSLContextService.class.getSimpleName(); + + private static final String ALIAS = "entry-0"; + + private static final String KEY_STORE_EXTENSION = ".p12"; + + private static final String KEY_STORE_PASS = UUID.randomUUID().toString(); + + private static final String TRUST_STORE_PASS = UUID.randomUUID().toString(); + + @TempDir + private static Path keyStoreDirectory; + + private static String keyStoreType; + + private static Path keyStorePath; + + private static Path trustStorePath; + + @BeforeAll + public static void setConfiguration() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder().build(); + keyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), KEY_STORE_PASS.toCharArray(), new Certificate[]{certificate}); + + keyStorePath = Files.createTempFile(keyStoreDirectory, "keyStore", KEY_STORE_EXTENSION); + try (OutputStream outputStream = Files.newOutputStream(keyStorePath)) { + keyStore.store(outputStream, KEY_STORE_PASS.toCharArray()); + } + + keyStoreType = keyStore.getType().toUpperCase(); + + final KeyStore trustStore = new EphemeralKeyStoreBuilder().addCertificate(certificate).build(); + trustStorePath = Files.createTempFile(keyStoreDirectory, "trustStore", KEY_STORE_EXTENSION); + try (OutputStream outputStream = Files.newOutputStream(trustStorePath)) { + trustStore.store(outputStream, TRUST_STORE_PASS.toCharArray()); + } + } + + @Test + public void testNotValidMissingProperties() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + + runner.addControllerService(SERVICE_ID, service, Map.of()); + runner.assertNotValid(service); + } + + @Test + public void testNotValidMissingKeyStoreType() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap<>(); + + properties.put(StandardSSLContextService.KEYSTORE.getName(), keyStorePath.toString()); + properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + runner.addControllerService(SERVICE_ID, service, properties); + runner.assertNotValid(service); + } + + @Test + public void testNotValidMissingTrustStoreType() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap<>(); + + properties.put(StandardSSLContextService.KEYSTORE.getName(), keyStorePath.toString()); + properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + properties.put(StandardSSLContextService.TRUSTSTORE.getName(), trustStorePath.toString()); + runner.addControllerService(SERVICE_ID, service, properties); + runner.assertNotValid(service); + } + + @Test + public void testNotValidIncorrectPassword() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap<>(); + + runner.addControllerService(SERVICE_ID, service, properties); + + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), keyStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), String.class.getSimpleName()); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), keyStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), String.class.getSimpleName()); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + + runner.assertNotValid(service); + } + + @Test + public void testShouldFailToAddControllerServiceWithNonExistentFiles() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap<>(); + + properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/DOES-NOT-EXIST.jks"); + properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + properties.put(StandardSSLContextService.TRUSTSTORE.getName(), keyStorePath.toString()); + properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), keyStorePath.toString()); + properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + runner.addControllerService(SERVICE_ID, service, properties); + + runner.assertNotValid(service); + } + + @Test + public void testCreateContext() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + + runner.addControllerService(SERVICE_ID, service); + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), keyStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), trustStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUST_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + runner.enableControllerService(service); + + runner.setProperty(SERVICE_PROPERTY, SERVICE_ID); + runner.assertValid(service); + + final SSLContext sslContext = service.createContext(); + assertNotNull(sslContext); + } + + @Test + public void testCreateContextExpressionLanguageProperties() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + + runner.addControllerService(SERVICE_ID, service); + runner.setEnvironmentVariableValue("keystore", keyStorePath.toString()); + runner.setEnvironmentVariableValue("truststore", trustStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), "${keystore}"); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), "${truststore}"); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUST_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + runner.enableControllerService(service); + + runner.setProperty(SERVICE_PROPERTY, SERVICE_ID); + runner.assertValid(service); + + final SSLContext sslContext = service.createContext(); + assertNotNull(sslContext); + } + + @Test + public void testValidPropertiesChanged() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + + runner.addControllerService(SERVICE_ID, service); + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), keyStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.KEY_PASSWORD.getName(), KEY_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), trustStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUST_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + runner.enableControllerService(service); + + runner.setProperty(SERVICE_PROPERTY, SERVICE_ID); + runner.assertValid(service); + + runner.disableControllerService(service); + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/DOES-NOT-EXIST.jks"); + runner.assertNotValid(service); + + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), keyStorePath.toString()); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), String.class.getSimpleName()); + + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUST_STORE_PASS); + runner.enableControllerService(service); + runner.assertValid(service); + } + + @Test + public void testValidPropertiesChangedValidationExpired(@TempDir final Path tempDir) throws InitializationException, IOException { + final Path tempKeyStore = tempDir.resolve("keyStore.p12"); + final Path tempTrustStore = tempDir.resolve("trustStore.p12"); + + Files.copy(keyStorePath, tempKeyStore, StandardCopyOption.REPLACE_EXISTING); + Files.copy(trustStorePath, tempTrustStore, StandardCopyOption.REPLACE_EXISTING); + + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + runner.addControllerService(SERVICE_ID, service); + runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), tempKeyStore.toString()); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEY_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), keyStoreType); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), tempTrustStore.toString()); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUST_STORE_PASS); + runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + runner.enableControllerService(service); + + runner.setProperty(SERVICE_PROPERTY, SERVICE_ID); + runner.assertValid(service); + + final MockProcessContext processContext = (MockProcessContext) runner.getProcessContext(); + // This service does not use the state manager + final ValidationContext validationContext = new MockValidationContext(processContext, null); + + // Even though the keystore file is no longer present, because no property changed, the cached result is still valid + Collection validationResults = service.customValidate(validationContext); + assertTrue(validationResults.isEmpty(), "validation results is not empty"); + + // Have to exhaust the cached result by checking n-1 more times + for (int i = 2; i < service.getValidationCacheExpiration(); i++) { + validationResults = service.customValidate(validationContext); + assertTrue(validationResults.isEmpty(), "validation results is not empty"); + } + + validationResults = service.customValidate(validationContext); + assertFalse(validationResults.isEmpty(), "validation results is empty"); + } + + @Test + public void testCreateContextTrustStoreWithoutKeyStore() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final StandardSSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap<>(); + + properties.put(StandardSSLContextService.TRUSTSTORE.getName(), trustStorePath.toString()); + properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUST_STORE_PASS); + properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), keyStoreType); + runner.addControllerService(SERVICE_ID, service, properties); + runner.enableControllerService(service); + + runner.setProperty(SERVICE_PROPERTY, SERVICE_ID); + runner.assertValid(); + + final SSLContext sslContext = service.createContext(); + assertNotNull(sslContext); + } +} diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/pom.xml b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/pom.xml index 70c3fcbf2105..47b9b4caeb72 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/pom.xml +++ b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/pom.xml @@ -49,7 +49,13 @@ org.apache.nifi - nifi-security-utils + nifi-security-ssl + 2.0.0-SNAPSHOT + test + + + org.apache.nifi + nifi-security-cert-builder 2.0.0-SNAPSHOT test diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardKeyManagerProviderTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardKeyManagerProviderTest.java index ccb660126358..ef5e9e7cb2f7 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardKeyManagerProviderTest.java +++ b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardKeyManagerProviderTest.java @@ -16,18 +16,29 @@ */ package org.apache.nifi.web.client.provider.service; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; import org.apache.nifi.ssl.SSLContextService; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import javax.net.ssl.X509KeyManager; +import javax.security.auth.x500.X500Principal; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -36,7 +47,16 @@ @ExtendWith(MockitoExtension.class) class StandardKeyManagerProviderTest { - static TlsConfiguration tlsConfiguration; + @TempDir + static Path keyStoreDirectory; + + static Path keyStorePath; + + static String keyStoreType; + + static String keyStorePass; + + private static final String KEY_STORE_EXTENSION = ".p12"; @Mock SSLContextService sslContextService; @@ -44,8 +64,21 @@ class StandardKeyManagerProviderTest { StandardKeyManagerProvider provider; @BeforeAll - static void setTlsConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); + static void setKeyStore() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final char[] protectionParameter = new char[]{}; + + keyStorePath = Files.createTempFile(keyStoreDirectory, StandardKeyManagerProviderTest.class.getSimpleName(), KEY_STORE_EXTENSION); + try (OutputStream outputStream = Files.newOutputStream(keyStorePath)) { + keyStore.store(outputStream, protectionParameter); + } + + keyStoreType = keyStore.getType(); + keyStorePass = new String(protectionParameter); } @BeforeEach @@ -65,9 +98,9 @@ void testGetKeyManagerNotConfigured() { @Test void testGetKeyManager() { when(sslContextService.isKeyStoreConfigured()).thenReturn(true); - when(sslContextService.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType()); - when(sslContextService.getKeyStoreFile()).thenReturn(tlsConfiguration.getKeystorePath()); - when(sslContextService.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword()); + when(sslContextService.getKeyStoreType()).thenReturn(keyStoreType); + when(sslContextService.getKeyStoreFile()).thenReturn(keyStorePath.toString()); + when(sslContextService.getKeyStorePassword()).thenReturn(keyStorePass); final Optional keyManager = provider.getKeyManager(sslContextService); diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProviderTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProviderTest.java index 2c74b10e4458..5132923fd190 100644 --- a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProviderTest.java +++ b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/test/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProviderTest.java @@ -24,10 +24,11 @@ import org.apache.nifi.proxy.ProxyConfiguration; import org.apache.nifi.proxy.ProxyConfigurationService; import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; +import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; +import org.apache.nifi.security.util.TlsPlatform; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.util.NoOpProcessor; import org.apache.nifi.util.TestRunner; @@ -47,10 +48,17 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URI; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -88,8 +96,6 @@ class StandardWebClientServiceProviderTest { private static final boolean TUNNEL_PROXY_DISABLED = false; - static TlsConfiguration tlsConfiguration; - static SSLContext sslContext; static X509TrustManager trustManager; @@ -107,10 +113,21 @@ class StandardWebClientServiceProviderTest { StandardWebClientServiceProvider provider; @BeforeAll - static void setTlsConfiguration() throws TlsException { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - sslContext = SslContextFactory.createSslContext(tlsConfiguration); - trustManager = SslContextFactory.getX509TrustManager(tlsConfiguration); + static void setTlsConfiguration() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final char[] protectionParameter = new char[]{}; + + sslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); + + trustManager = new StandardTrustManagerBuilder().trustStore(keyStore).build(); } @BeforeEach @@ -163,7 +180,7 @@ void testGetWebServiceClientGetUri() throws InterruptedException { @Test void testGetWebServiceClientSslContextServiceConfiguredGetUri() throws InitializationException, InterruptedException { when(sslContextService.getIdentifier()).thenReturn(SSL_CONTEXT_SERVICE_ID); - when(sslContextService.getSslAlgorithm()).thenReturn(tlsConfiguration.getProtocol()); + when(sslContextService.getSslAlgorithm()).thenReturn(TlsPlatform.getLatestProtocol()); when(sslContextService.createTrustManager()).thenReturn(trustManager); runner.addControllerService(SSL_CONTEXT_SERVICE_ID, sslContextService); diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml index 0cae978126d2..6ca1ba5a4a0a 100644 --- a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml +++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml @@ -35,12 +35,12 @@ org.apache.nifi - nifi-security-utils + nifi-security-identity 2.0.0-SNAPSHOT org.apache.nifi - nifi-security-identity + nifi-security-ssl 2.0.0-SNAPSHOT diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java index e60acc8dd13f..4adb64f3ef88 100644 --- a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java +++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java @@ -16,6 +16,12 @@ */ package org.apache.nifi.ldap; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -32,10 +38,8 @@ import org.apache.nifi.authentication.exception.ProviderCreationException; import org.apache.nifi.authentication.exception.ProviderDestructionException; import org.apache.nifi.configuration.NonComponentConfigurationContext; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.StandardTlsConfiguration; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; +import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.util.FormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -244,24 +248,93 @@ private void setTimeout(final LoginIdentityProviderConfigurationContext configur } } - public static SSLContext getConfiguredSslContext(final NonComponentConfigurationContext configurationContext) { - final String rawKeystore = configurationContext.getProperty("TLS - Keystore"); - final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password"); - // TODO: Should support different key password - final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type"); - final String rawTruststore = configurationContext.getProperty("TLS - Truststore"); - final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password"); - final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type"); - final String rawProtocol = configurationContext.getProperty("TLS - Protocol"); + private static SSLContext getConfiguredSslContext(final NonComponentConfigurationContext configurationContext) { + final String rawProtocol = configurationContext.getProperty(ProviderProperty.TLS_PROTOCOL.getProperty()); + SSLContext sslContext = null; try { - TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(rawKeystore, rawKeystorePassword, null, rawKeystoreType, - rawTruststore, rawTruststorePassword, rawTruststoreType, rawProtocol); - return SslContextFactory.createSslContext(tlsConfiguration); - } catch (TlsException e) { + final KeyStore trustStore = getTrustStore(configurationContext); + if (trustStore == null) { + logger.debug("Truststore not configured"); + } else { + final StandardSslContextBuilder sslContextBuilder = new StandardSslContextBuilder(); + sslContextBuilder.protocol(rawProtocol); + sslContextBuilder.trustStore(trustStore); + + final KeyStore keyStore = getKeyStore(configurationContext); + if (keyStore == null) { + logger.debug("Keystore not configured"); + } else { + final String keyStorePassword = configurationContext.getProperty(ProviderProperty.KEYSTORE_PASSWORD.getProperty()); + final char[] keyPassword = keyStorePassword.toCharArray(); + + sslContextBuilder.keyStore(keyStore); + sslContextBuilder.keyPassword(keyPassword); + sslContext = sslContextBuilder.build(); + } + } + } catch (final Exception e) { logger.error("Encountered an error configuring TLS for LDAP identity provider: {}", e.getLocalizedMessage()); throw new ProviderCreationException("Error configuring TLS for LDAP identity provider", e); } + + return sslContext; + } + + private static KeyStore getKeyStore(final NonComponentConfigurationContext configurationContext) throws IOException { + final String rawKeystore = configurationContext.getProperty(ProviderProperty.KEYSTORE.getProperty()); + final String rawKeystorePassword = configurationContext.getProperty(ProviderProperty.KEYSTORE_PASSWORD.getProperty()); + final String rawKeystoreType = configurationContext.getProperty(ProviderProperty.KEYSTORE_TYPE.getProperty()); + + final KeyStore keyStore; + + if (rawKeystore == null || rawKeystore.isBlank()) { + keyStore = null; + } else if (rawKeystorePassword == null) { + throw new ProviderCreationException("Keystore Password not configured"); + } else { + final StandardKeyStoreBuilder builder = new StandardKeyStoreBuilder(); + builder.type(rawKeystoreType); + + final char[] keyStorePassword = rawKeystorePassword.toCharArray(); + builder.password(keyStorePassword); + + final Path trustStorePath = Paths.get(rawKeystore); + try (InputStream trustStoreStream = Files.newInputStream(trustStorePath)) { + builder.inputStream(trustStoreStream); + keyStore = builder.build(); + } + } + + return keyStore; + } + + private static KeyStore getTrustStore(final NonComponentConfigurationContext configurationContext) throws IOException { + final String rawTruststore = configurationContext.getProperty(ProviderProperty.TRUSTSTORE.getProperty()); + final String rawTruststorePassword = configurationContext.getProperty(ProviderProperty.TRUSTSTORE_PASSWORD.getProperty()); + final String rawTruststoreType = configurationContext.getProperty(ProviderProperty.TRUSTSTORE_TYPE.getProperty()); + + final KeyStore trustStore; + + if (rawTruststore == null || rawTruststore.isBlank()) { + trustStore = null; + } else if (rawTruststorePassword == null) { + throw new ProviderCreationException("Truststore Password not configured"); + } else { + final StandardKeyStoreBuilder builder = new StandardKeyStoreBuilder(); + builder.type(rawTruststoreType); + + final char[] trustStorePassword = rawTruststorePassword.toCharArray(); + builder.password(trustStorePassword); + + final Path trustStorePath = Paths.get(rawTruststore); + try (InputStream trustStoreStream = Files.newInputStream(trustStorePath)) { + builder.inputStream(trustStoreStream); + trustStore = builder.build(); + } + } + + return trustStore; } @Override @@ -278,8 +351,7 @@ public final AuthenticationResponse authenticate(final LoginCredentials credenti // use dn if configured if (IdentityStrategy.USE_DN.equals(identityStrategy)) { // attempt to get the ldap user details to get the DN - if (authentication.getPrincipal() instanceof LdapUserDetails) { - final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal(); + if (authentication.getPrincipal() instanceof LdapUserDetails userDetails) { return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration, issuer); } else { logger.warn("Unable to determine user DN for {}, using username.", authentication.getName()); diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ProviderProperty.java b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ProviderProperty.java new file mode 100644 index 000000000000..8c486b9a3cd6 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ProviderProperty.java @@ -0,0 +1,43 @@ +/* + * 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.ldap; + +public enum ProviderProperty { + KEYSTORE("TLS - Keystore"), + + KEYSTORE_PASSWORD("TLS - Keystore Password"), + + KEYSTORE_TYPE("TLS - Keystore Type"), + + TRUSTSTORE("TLS - Truststore"), + + TRUSTSTORE_PASSWORD("TLS - Truststore Password"), + + TRUSTSTORE_TYPE("TLS - Truststore Type"), + + TLS_PROTOCOL("TLS - Protocol"); + + private final String property; + + ProviderProperty(final String property) { + this.property = property; + } + + public String getProperty() { + return property; + } +} diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java index 8f35fc9599bc..05866789c578 100644 --- a/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java +++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java @@ -34,11 +34,10 @@ import org.apache.nifi.components.PropertyValue; import org.apache.nifi.ldap.LdapAuthenticationStrategy; import org.apache.nifi.ldap.LdapsSocketFactory; +import org.apache.nifi.ldap.ProviderProperty; import org.apache.nifi.ldap.ReferralStrategy; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.StandardTlsConfiguration; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; +import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.util.FormatUtils; import org.apache.nifi.util.NiFiProperties; import org.slf4j.Logger; @@ -66,6 +65,12 @@ import javax.naming.directory.Attribute; import javax.naming.directory.SearchControls; import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -494,7 +499,7 @@ private void load(final ContextSource contextSource) { } do { - userList.addAll(ldapTemplate.search(userSearchBase, userFilter.encode(), userControls, new AbstractContextMapper() { + userList.addAll(ldapTemplate.search(userSearchBase, userFilter.encode(), userControls, new AbstractContextMapper<>() { @Override protected User doMapFromContext(DirContextOperations ctx) { // get the user @@ -567,7 +572,7 @@ protected User doMapFromContext(DirContextOperations ctx) { } do { - groupList.addAll(ldapTemplate.search(groupSearchBase, groupFilter.encode(), groupControls, new AbstractContextMapper() { + groupList.addAll(ldapTemplate.search(groupSearchBase, groupFilter.encode(), groupControls, new AbstractContextMapper<>() { @Override protected Group doMapFromContext(DirContextOperations ctx) { // get the group identity @@ -830,22 +835,91 @@ private void setTimeout(final AuthorizerConfigurationContext configurationContex } private SSLContext getConfiguredSslContext(final AuthorizerConfigurationContext configurationContext) { - final String rawKeystore = configurationContext.getProperty("TLS - Keystore").getValue(); - final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password").getValue(); - final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type").getValue(); - final String rawTruststore = configurationContext.getProperty("TLS - Truststore").getValue(); - final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password").getValue(); - final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type").getValue(); - final String rawProtocol = configurationContext.getProperty("TLS - Protocol").getValue(); + final String rawProtocol = configurationContext.getProperty(ProviderProperty.TLS_PROTOCOL.getProperty()).getValue(); + SSLContext sslContext = null; try { - TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(rawKeystore, rawKeystorePassword, null, rawKeystoreType, - rawTruststore, rawTruststorePassword, rawTruststoreType, rawProtocol); - return SslContextFactory.createSslContext(tlsConfiguration); - } catch (TlsException e) { + final KeyStore trustStore = getTrustStore(configurationContext); + if (trustStore == null) { + logger.debug("Truststore not configured"); + } else { + final StandardSslContextBuilder sslContextBuilder = new StandardSslContextBuilder(); + sslContextBuilder.protocol(rawProtocol); + sslContextBuilder.trustStore(trustStore); + + final KeyStore keyStore = getKeyStore(configurationContext); + if (keyStore == null) { + logger.debug("Keystore not configured"); + } else { + final String keyStorePassword = configurationContext.getProperty(ProviderProperty.KEYSTORE_PASSWORD.getProperty()).getValue(); + final char[] keyPassword = keyStorePassword.toCharArray(); + + sslContextBuilder.keyStore(keyStore); + sslContextBuilder.keyPassword(keyPassword); + sslContext = sslContextBuilder.build(); + } + } + } catch (final Exception e) { logger.error("Encountered an error configuring TLS for LDAP user group provider: {}", e.getLocalizedMessage()); throw new ProviderCreationException("Error configuring TLS for LDAP user group provider", e); } + + return sslContext; } + private static KeyStore getKeyStore(final AuthorizerConfigurationContext configurationContext) throws IOException { + final String rawKeystore = configurationContext.getProperty(ProviderProperty.KEYSTORE.getProperty()).getValue(); + final String rawKeystorePassword = configurationContext.getProperty(ProviderProperty.KEYSTORE_PASSWORD.getProperty()).getValue(); + final String rawKeystoreType = configurationContext.getProperty(ProviderProperty.KEYSTORE_TYPE.getProperty()).getValue(); + + final KeyStore keyStore; + + if (rawKeystore == null || rawKeystore.isBlank()) { + keyStore = null; + } else if (rawKeystorePassword == null) { + throw new ProviderCreationException("Keystore Password not configured"); + } else { + final StandardKeyStoreBuilder builder = new StandardKeyStoreBuilder(); + builder.type(rawKeystoreType); + + final char[] keyStorePassword = rawKeystorePassword.toCharArray(); + builder.password(keyStorePassword); + + final Path trustStorePath = Paths.get(rawKeystore); + try (InputStream trustStoreStream = Files.newInputStream(trustStorePath)) { + builder.inputStream(trustStoreStream); + keyStore = builder.build(); + } + } + + return keyStore; + } + + private static KeyStore getTrustStore(final AuthorizerConfigurationContext configurationContext) throws IOException { + final String rawTruststore = configurationContext.getProperty(ProviderProperty.TRUSTSTORE.getProperty()).getValue(); + final String rawTruststorePassword = configurationContext.getProperty(ProviderProperty.TRUSTSTORE_PASSWORD.getProperty()).getValue(); + final String rawTruststoreType = configurationContext.getProperty(ProviderProperty.TRUSTSTORE_TYPE.getProperty()).getValue(); + + final KeyStore trustStore; + + if (rawTruststore == null || rawTruststore.isBlank()) { + trustStore = null; + } else if (rawTruststorePassword == null) { + throw new ProviderCreationException("Truststore Password not configured"); + } else { + final StandardKeyStoreBuilder builder = new StandardKeyStoreBuilder(); + builder.type(rawTruststoreType); + + final char[] trustStorePassword = rawTruststorePassword.toCharArray(); + builder.password(trustStorePassword); + + final Path trustStorePath = Paths.get(rawTruststore); + try (InputStream trustStoreStream = Files.newInputStream(trustStorePath)) { + builder.inputStream(trustStoreStream); + trustStore = builder.build(); + } + } + + return trustStore; + } } diff --git a/nifi-framework-bundle/nifi-framework-nar-bom/pom.xml b/nifi-framework-bundle/nifi-framework-nar-bom/pom.xml index 771871833c36..2f310f537662 100644 --- a/nifi-framework-bundle/nifi-framework-nar-bom/pom.xml +++ b/nifi-framework-bundle/nifi-framework-nar-bom/pom.xml @@ -87,12 +87,6 @@ 2.0.0-SNAPSHOT provided - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - provided - org.apache.nifi nifi-utils @@ -264,10 +258,6 @@ org.apache.nifi nifi-security-utils-api - - org.apache.nifi - nifi-security-utils - org.apache.nifi nifi-utils diff --git a/nifi-framework-bundle/nifi-framework-nar/pom.xml b/nifi-framework-bundle/nifi-framework-nar/pom.xml index 593898903922..2c3aa807d4e2 100644 --- a/nifi-framework-bundle/nifi-framework-nar/pom.xml +++ b/nifi-framework-bundle/nifi-framework-nar/pom.xml @@ -75,11 +75,6 @@ nifi-security-utils-api compile - - org.apache.nifi - nifi-security-utils - compile - org.apache.nifi nifi-utils diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml index c1e1f31e8f51..c0befa3c55f4 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml @@ -56,11 +56,6 @@ nifi-framework-core-api 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-cert diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/pom.xml index 41ad7c6040b2..43b72cef9ebb 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/pom.xml @@ -151,11 +151,6 @@ nifi-framework-cluster-protocol 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-site-to-site-client diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml index a4d7ff7f672b..f7b5c2cd7f0e 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml @@ -127,11 +127,6 @@ nifi-property-encryptor 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-xml-processing diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml index 7b0487d7fa6d..943c7742575d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml @@ -81,11 +81,6 @@ nifi-security-utils-api 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-framework-nar-utils @@ -340,6 +335,12 @@ 2.0.0-SNAPSHOT test + + org.apache.nifi + nifi-security-cert-builder + 2.0.0-SNAPSHOT + test + io.netty netty-handler diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/LoadBalancedQueueIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/LoadBalancedQueueIT.java index aa79a3b770a8..d87ba06aba5b 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/LoadBalancedQueueIT.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/LoadBalancedQueueIT.java @@ -53,9 +53,9 @@ import org.apache.nifi.controller.repository.claim.ResourceClaim; import org.apache.nifi.events.EventReporter; import org.apache.nifi.provenance.ProvenanceRepository; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -64,6 +64,7 @@ import org.mockito.stubbing.Answer; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -71,10 +72,16 @@ import java.io.OutputStream; import java.security.GeneralSecurityException; import java.security.KeyManagementException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -185,10 +192,21 @@ public Object answer(final InvocationOnMock invocation) { clientRepoRecords = Collections.synchronizedList(new ArrayList<>()); clientFlowFileRepo = createFlowFileRepository(clientRepoRecords); - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - sslContext = SslContextFactory.createSslContext(tlsConfiguration); + sslContext = getSslContext(); } + private SSLContext getSslContext() throws GeneralSecurityException { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + return new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(new char[]{}) + .build(); + } private ContentClaim createContentClaim(final byte[] bytes) { final ResourceClaim resourceClaim = mock(ResourceClaim.class); diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/client/async/nio/TestPeerChannel.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/client/async/nio/TestPeerChannel.java index 615f089642b4..2819bf62cd4e 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/client/async/nio/TestPeerChannel.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/client/async/nio/TestPeerChannel.java @@ -29,9 +29,9 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.ssl.SslHandler; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.util.TlsPlatform; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -40,6 +40,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.UncheckedIOException; import java.net.InetSocketAddress; @@ -49,6 +50,12 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.OptionalInt; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -92,8 +99,18 @@ public static boolean isTls13Supported() { @BeforeAll public static void setConfiguration() throws GeneralSecurityException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - sslContext = SslContextFactory.createSslContext(tlsConfiguration); + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final char[] protectionParameter = new char[]{}; + + sslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); } @Test diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServerTest.java index af3941af796a..c125c3eeb9f2 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServerTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServerTest.java @@ -17,10 +17,9 @@ package org.apache.nifi.controller.queue.clustered.server; import org.apache.nifi.events.EventReporter; -import org.apache.nifi.security.util.SslContextFactory; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,11 +30,17 @@ import javax.net.ssl.SSLException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; @@ -60,9 +65,19 @@ class ConnectionLoadBalanceServerTest { EventReporter eventReporter; @BeforeAll - static void setSslContext() throws TlsException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - sslContext = SslContextFactory.createSslContext(tlsConfiguration); + static void setSslContext() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); + final char[] protectionParameter = new char[]{}; + + sslContext = new StandardSslContextBuilder() + .trustStore(keyStore) + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); } @Test diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServerConfigurations.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServerConfigurations.java deleted file mode 100644 index 2f54b1a4cbef..000000000000 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServerConfigurations.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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.controller.state.server; - -import org.apache.commons.io.FileUtils; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.util.NiFiProperties; -import org.apache.zookeeper.server.ServerCnxnFactory; -import org.apache.zookeeper.server.quorum.QuorumPeerConfig; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -// This class tests the behaviors involved with the ZooKeeperStateServer::create method. The servers are not started, -// and TLS connections are not used. -public class TestZooKeeperStateServerConfigurations { - private static final String INSECURE_ZOOKEEPER_PROPS = getPath("insecure.zookeeper.properties"); - private static final String SECURE_ZOOKEEPER_PROPS = getPath("secure.zookeeper.properties"); - private static final String ZOOKEEPER_PROPERTIES_FILE_KEY = "nifi.state.management.embedded.zookeeper.properties"; - private static final String ZOOKEEPER_CNXN_FACTORY = "org.apache.zookeeper.server.NettyServerCnxnFactory"; - - private static final Map INSECURE_PROPS = new HashMap() {{ - put(ZOOKEEPER_PROPERTIES_FILE_KEY, INSECURE_ZOOKEEPER_PROPS); - }}; - - private static final Map SECURE_NIFI_PROPS = new HashMap<>(); - - private static final Map INSECURE_NIFI_PROPS = new HashMap() {{ - putAll(INSECURE_PROPS); - put(NiFiProperties.WEB_HTTP_PORT, "8080"); - put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "false"); - }}; - - private NiFiProperties secureNiFiProps; - private NiFiProperties insecureNiFiProps; - private QuorumPeerConfig secureQuorumPeerConfig; - private QuorumPeerConfig insecureQuorumPeerConfig; - private Properties secureZooKeeperProps; - private Properties insecureZooKeeperProps; - - private static TlsConfiguration tlsConfiguration; - - @BeforeAll - public static void setTlsConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - - SECURE_NIFI_PROPS.put(NiFiProperties.STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES, SECURE_ZOOKEEPER_PROPS); - SECURE_NIFI_PROPS.put(NiFiProperties.WEB_HTTPS_PORT, "8443"); - SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath()); - SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword()); - SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath()); - SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); - SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword()); - SECURE_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "true"); - } - - @BeforeEach - public void setupWithValidProperties() throws IOException, QuorumPeerConfig.ConfigException { - - // Secure properties setup - secureNiFiProps = NiFiProperties.createBasicNiFiProperties(null, SECURE_NIFI_PROPS); - assertNotNull(secureNiFiProps); - - // This shows that a ZooKeeper server is created from valid NiFi properties: - final ZooKeeperStateServer secureZooKeeperStateServer = ZooKeeperStateServer.create(secureNiFiProps); - assertNotNull(secureZooKeeperStateServer); - - secureQuorumPeerConfig = secureZooKeeperStateServer.getQuorumPeerConfig(); - assertNotNull(secureQuorumPeerConfig); - - secureZooKeeperProps = new Properties(); - secureZooKeeperProps.load( FileUtils.openInputStream(new File(SECURE_ZOOKEEPER_PROPS))); - assertNotNull(secureZooKeeperProps); - - // Insecure properties setup - insecureNiFiProps = NiFiProperties.createBasicNiFiProperties(null, INSECURE_NIFI_PROPS); - assertNotNull(insecureNiFiProps); - - // This shows that a ZooKeeper server is created from valid NiFi properties: - final ZooKeeperStateServer insecureZooKeeperStateServer = ZooKeeperStateServer.create(insecureNiFiProps); - assertNotNull(insecureZooKeeperStateServer); - - insecureQuorumPeerConfig = insecureZooKeeperStateServer.getQuorumPeerConfig(); - assertNotNull(insecureQuorumPeerConfig); - - insecureZooKeeperProps = new Properties(); - insecureZooKeeperProps.load(FileUtils.openInputStream(new File(INSECURE_ZOOKEEPER_PROPS))); - assertNotNull(insecureZooKeeperProps); - } - - @AfterEach - public void clearConnectionProperties() { - Collections.unmodifiableSet(System.getProperties().stringPropertyNames()).stream() - .filter(name -> name.startsWith("zookeeper.")) - .forEach(System::clearProperty); - } - - // This test shows that a ZooKeeperStateServer cannot be created from empty NiFi properties. - @Test - public void testCreateFromEmptyNiFiProperties() throws IOException, QuorumPeerConfig.ConfigException { - final NiFiProperties emptyProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<>()); - - assertNotNull(emptyProps); - assertNull(ZooKeeperStateServer.create(emptyProps)); - } - - // This test shows that a ZooKeeperStateServer can be created from insecure NiFi properties. - @Test - public void testCreateFromValidInsecureNiFiProperties() throws IOException, QuorumPeerConfig.ConfigException { - final NiFiProperties insecureProps = NiFiProperties.createBasicNiFiProperties(null, INSECURE_PROPS); - final ZooKeeperStateServer server = ZooKeeperStateServer.create(insecureProps); - - assertNotNull(server); - assertNotNull(server.getQuorumPeerConfig().getClientPortAddress()); - } - - // This test shows that the client can specify a secure port and that port is used: - @Test - public void testCreateWithSpecifiedSecureClientPort() throws IOException, QuorumPeerConfig.ConfigException { - final NiFiProperties secureProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap() {{ - putAll(SECURE_NIFI_PROPS); - put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS); - }}); - - final ZooKeeperStateServer server = ZooKeeperStateServer.create(secureProps); - assertNotNull(server); - - final QuorumPeerConfig config = server.getQuorumPeerConfig(); - assertEquals(secureZooKeeperProps.getProperty("secureClientPort"), String.valueOf(config.getSecureClientPortAddress().getPort())); - } - - // This shows that a secure NiFi with an secure ZooKeeper will not have an insecure client address or port: - @Test - public void testCreateRemovesInsecureClientPort() { - assertNotNull(secureZooKeeperProps.getProperty("secureClientPort")); - assertNotEquals("", secureZooKeeperProps.getProperty("clientPort")); - assertNull(secureQuorumPeerConfig.getClientPortAddress()); - } - - // This test shows that a connection class is set when none is specified (QuorumPeerConfig::parseProperties sets the System property): - @Test - public void testCreateWithUnspecifiedConnectionClass() { - assertEquals(org.apache.zookeeper.server.NettyServerCnxnFactory.class.getName(), System.getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY)); - } - - // This test shows that a specified connection class is honored (QuorumPeerConfig::parseProperties sets the System property): - @Test - public void testCreateWithSpecifiedConnectionClass() throws IOException, QuorumPeerConfig.ConfigException { - final NiFiProperties secureProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap() {{ - putAll(SECURE_NIFI_PROPS); - put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS); - }}); - - assertNotNull(ZooKeeperStateServer.create(secureProps)); - assertEquals(ZOOKEEPER_CNXN_FACTORY, System.getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY)); - } - - private static String getPath(String path) { - return new File("src/test/resources/TestZooKeeperStateServerConfigurations/" + path).getAbsolutePath(); - } -} diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-external-resource-utils/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-external-resource-utils/pom.xml index 0c1e3ddf832c..c0f6370d4545 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-external-resource-utils/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-external-resource-utils/pom.xml @@ -32,11 +32,6 @@ nifi-security-utils-api 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-framework-nar-utils diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/pom.xml index 7c2be9bda100..cc6d8f0a882a 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/pom.xml @@ -26,11 +26,6 @@ org.apache.nifi nifi-nar-utils - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-utils-api diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml index 2357c0eb84af..39d6f9beb209 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml @@ -120,8 +120,9 @@ org.apache.nifi - nifi-security-utils + nifi-security-cert-builder 2.0.0-SNAPSHOT + test diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java index 6c0cd96ddb2d..3b443b08a107 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java @@ -16,9 +16,9 @@ */ package org.apache.nifi.web.server.connector; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; -import org.apache.nifi.security.util.TlsException; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; +import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.util.NiFiProperties; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; @@ -29,8 +29,19 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.Properties; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -52,12 +63,40 @@ class FrameworkServerConnectorFactoryTest { private static final String INCLUDED_CIPHER_SUITE_PATTERN = ".*AES_256_GCM.*"; - private static TlsConfiguration tlsConfiguration; + private static final String ALIAS = "entry-0"; + + private static final String KEY_STORE_EXTENSION = ".p12"; + + private static final String KEY_STORE_PASS = FrameworkServerConnectorFactoryTest.class.getName(); + + @TempDir + private static Path keyStoreDirectory; + + private static String keyStoreType; + + private static Path keyStorePath; + + private static SSLContext sslContext; @BeforeAll - static void setTlsConfiguration() { - final TemporaryKeyStoreBuilder builder = new TemporaryKeyStoreBuilder(); - tlsConfiguration = builder.build(); + static void setConfiguration() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder().build(); + keyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), KEY_STORE_PASS.toCharArray(), new Certificate[]{certificate}); + + keyStorePath = Files.createTempFile(keyStoreDirectory, FrameworkServerConnectorFactoryTest.class.getSimpleName(), KEY_STORE_EXTENSION); + try (OutputStream outputStream = Files.newOutputStream(keyStorePath)) { + keyStore.store(outputStream, KEY_STORE_PASS.toCharArray()); + } + + keyStoreType = keyStore.getType().toUpperCase(); + + sslContext = new StandardSslContextBuilder() + .keyStore(keyStore) + .trustStore(keyStore) + .keyPassword(KEY_STORE_PASS.toCharArray()) + .build(); } @Test @@ -140,13 +179,13 @@ void testGetServerConnectorHttpsHttp2AndHttp11() { private Properties getHttpsProperties() { final Properties serverProperties = new Properties(); serverProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, Integer.toString(HTTPS_PORT)); - serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath()); - serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword()); - serverProperties.setProperty(NiFiProperties.SECURITY_KEY_PASSWD, tlsConfiguration.getKeyPassword()); - serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath()); - serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); - serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword()); + serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE, keyStorePath.toString()); + serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE, keyStoreType); + serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD, KEY_STORE_PASS); + serverProperties.setProperty(NiFiProperties.SECURITY_KEY_PASSWD, KEY_STORE_PASS); + serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, keyStorePath.toString()); + serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, keyStoreType); + serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, KEY_STORE_PASS); return serverProperties; } @@ -154,12 +193,7 @@ private FrameworkServerConnectorFactory getHttpsConnectorFactory(final Propertie final NiFiProperties properties = getProperties(serverProperties); final Server server = new Server(); final FrameworkServerConnectorFactory factory = new FrameworkServerConnectorFactory(server, properties); - try { - final SSLContext sslContext = org.apache.nifi.security.util.SslContextFactory.createSslContext(tlsConfiguration); - factory.setSslContext(sslContext); - } catch (final TlsException e) { - throw new IllegalStateException("Failed to create SSL Context", e); - } + factory.setSslContext(sslContext); return factory; } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml index 53912e6feb66..5fe91fb1b53d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml @@ -443,11 +443,6 @@ nifi-framework-core provided - - org.apache.nifi - nifi-security-utils - test - org.apache.nifi nifi-utils diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml index 92f4994bfff8..e093f8e54aee 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml @@ -101,11 +101,6 @@ nifi-web-utils 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-security-identity @@ -317,6 +312,18 @@ mockwebserver test + + org.apache.nifi + nifi-security-cert-builder + 2.0.0-SNAPSHOT + test + + + org.apache.nifi + nifi-security-ssl + 2.0.0-SNAPSHOT + test + org.apache.nifi nifi-python-framework-api diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRegistrationBuilderProviderTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRegistrationBuilderProviderTest.java index f3602e275dd7..bf12ae313f71 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRegistrationBuilderProviderTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRegistrationBuilderProviderTest.java @@ -20,12 +20,11 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.apache.commons.io.IOUtils; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardKeyManagerBuilder; -import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.saml2.SamlConfigurationException; import org.junit.jupiter.api.AfterEach; @@ -36,16 +35,20 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import java.io.FileInputStream; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.Objects; import java.util.Properties; @@ -109,13 +112,18 @@ void testGetRegistrationBuilderHttpUrlNotFound() { } @Test - void testGetRegistrationBuilderHttpsUrl() throws IOException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - final X509KeyManager keyManager = getKeyManager(tlsConfiguration); - final X509TrustManager trustManager = getTrustManager(tlsConfiguration); + void testGetRegistrationBuilderHttpsUrl() throws Exception { + final KeyStore keyStore = getKeyStore(); + final char[] protectionParameter = new char[]{}; + + final X509KeyManager keyManager = new StandardKeyManagerBuilder() + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); + final X509TrustManager trustManager = new StandardTrustManagerBuilder().trustStore(keyStore).build(); final SSLContext sslContext = new StandardSslContextBuilder() .keyManager(keyManager) - .keyPassword(tlsConfiguration.getKeyPassword().toCharArray()) + .keyPassword(protectionParameter) .trustManager(trustManager) .build(); @@ -127,7 +135,7 @@ void testGetRegistrationBuilderHttpsUrl() throws IOException { mockWebServer.enqueue(response); final String metadataUrl = getMetadataUrl(); - final NiFiProperties properties = getProperties(metadataUrl, tlsConfiguration); + final NiFiProperties properties = getPropertiesTrustStoreStrategy(metadataUrl); assertRegistrationFound(properties, keyManager, trustManager); } @@ -145,31 +153,12 @@ private void assertRegistrationFound(final NiFiProperties properties, final X509 assertEquals(Saml2MessageBinding.POST, registration.getAssertionConsumerServiceBinding()); } - private X509ExtendedKeyManager getKeyManager(final TlsConfiguration tlsConfiguration) throws IOException { - try (InputStream inputStream = new FileInputStream(tlsConfiguration.getKeystorePath())) { - final KeyStore keyStore = new StandardKeyStoreBuilder() - .inputStream(inputStream) - .password(tlsConfiguration.getKeystorePassword().toCharArray()) - .type(tlsConfiguration.getKeystoreType().getType()) - .build(); - - return new StandardKeyManagerBuilder() - .keyStore(keyStore) - .keyPassword(tlsConfiguration.getFunctionalKeyPassword().toCharArray()) - .build(); - } - } - - private X509ExtendedTrustManager getTrustManager(final TlsConfiguration tlsConfiguration) throws IOException { - try (InputStream inputStream = new FileInputStream(tlsConfiguration.getTruststorePath())) { - final KeyStore trustStore = new StandardKeyStoreBuilder() - .inputStream(inputStream) - .password(tlsConfiguration.getTruststorePassword().toCharArray()) - .type(tlsConfiguration.getTruststoreType().getType()) - .build(); - - return new StandardTrustManagerBuilder().trustStore(trustStore).build(); - } + private KeyStore getKeyStore() throws GeneralSecurityException { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + return new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); } private NiFiProperties getProperties(final String metadataUrl) { @@ -178,19 +167,11 @@ private NiFiProperties getProperties(final String metadataUrl) { return NiFiProperties.createBasicNiFiProperties(null, properties); } - private NiFiProperties getProperties(final String metadataUrl, final TlsConfiguration tlsConfiguration) { + private NiFiProperties getPropertiesTrustStoreStrategy(final String metadataUrl) { final Properties properties = new Properties(); properties.setProperty(NiFiProperties.SECURITY_USER_SAML_IDP_METADATA_URL, metadataUrl); properties.setProperty(NiFiProperties.SECURITY_USER_SAML_HTTP_CLIENT_TRUSTSTORE_STRATEGY, StandardRegistrationBuilderProvider.NIFI_TRUST_STORE_STRATEGY); - properties.setProperty(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath()); - properties.setProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - properties.setProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword()); - properties.setProperty(NiFiProperties.SECURITY_KEY_PASSWD, tlsConfiguration.getKeyPassword()); - properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath()); - properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); - properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword()); - return NiFiProperties.createBasicNiFiProperties(null, properties); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRelyingPartyRegistrationRepositoryTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRelyingPartyRegistrationRepositoryTest.java index 3bc3cb8f1fa2..632077922058 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRelyingPartyRegistrationRepositoryTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/registration/StandardRelyingPartyRegistrationRepositoryTest.java @@ -16,11 +16,10 @@ */ package org.apache.nifi.web.security.saml2.registration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardKeyManagerBuilder; -import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; import org.apache.nifi.util.NiFiProperties; import org.junit.jupiter.api.Test; import org.opensaml.xmlsec.signature.support.SignatureConstants; @@ -30,12 +29,14 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; import javax.security.auth.x500.X500Principal; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.Collection; import java.util.Objects; import java.util.Optional; @@ -75,12 +76,17 @@ void testFindByRegistrationId() { } @Test - void testFindByRegistrationIdSingleLogoutEnabled() throws IOException { - final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build(); - final X509ExtendedKeyManager keyManager = getKeyManager(tlsConfiguration); - final X509ExtendedTrustManager trustManager = getTrustManager(tlsConfiguration); + void testFindByRegistrationIdSingleLogoutEnabled() throws Exception { + final KeyStore keyStore = getKeyStore(); + final char[] protectionParameter = new char[]{}; - final NiFiProperties properties = getSingleLogoutProperties(tlsConfiguration); + final X509ExtendedKeyManager keyManager = new StandardKeyManagerBuilder() + .keyStore(keyStore) + .keyPassword(protectionParameter) + .build(); + final X509ExtendedTrustManager trustManager = new StandardTrustManagerBuilder().trustStore(keyStore).build(); + + final NiFiProperties properties = getSingleLogoutProperties(); final StandardRelyingPartyRegistrationRepository repository = new StandardRelyingPartyRegistrationRepository(properties, keyManager, trustManager); final RelyingPartyRegistration registration = repository.findByRegistrationId(Saml2RegistrationProperty.REGISTRATION_ID.getProperty()); @@ -128,19 +134,11 @@ private NiFiProperties getProperties() { return NiFiProperties.createBasicNiFiProperties(null, properties); } - private NiFiProperties getSingleLogoutProperties(final TlsConfiguration tlsConfiguration) { + private NiFiProperties getSingleLogoutProperties() { final Properties properties = getStandardProperties(); properties.setProperty(NiFiProperties.SECURITY_USER_SAML_SINGLE_LOGOUT_ENABLED, Boolean.TRUE.toString()); properties.setProperty(NiFiProperties.SECURITY_USER_SAML_SIGNATURE_ALGORITHM, SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512); - properties.setProperty(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath()); - properties.setProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - properties.setProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword()); - properties.setProperty(NiFiProperties.SECURITY_KEY_PASSWD, tlsConfiguration.getKeyPassword()); - properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath()); - properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); - properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword()); - return NiFiProperties.createBasicNiFiProperties(null, properties); } @@ -157,30 +155,11 @@ private String getFileMetadataUrl() { return resource.toString(); } - private X509ExtendedKeyManager getKeyManager(final TlsConfiguration tlsConfiguration) throws IOException { - try (InputStream inputStream = new FileInputStream(tlsConfiguration.getKeystorePath())) { - final KeyStore keyStore = new StandardKeyStoreBuilder() - .inputStream(inputStream) - .password(tlsConfiguration.getKeystorePassword().toCharArray()) - .type(tlsConfiguration.getKeystoreType().getType()) - .build(); - - return new StandardKeyManagerBuilder() - .keyStore(keyStore) - .keyPassword(tlsConfiguration.getFunctionalKeyPassword().toCharArray()) - .build(); - } - } - - private X509ExtendedTrustManager getTrustManager(final TlsConfiguration tlsConfiguration) throws IOException { - try (InputStream inputStream = new FileInputStream(tlsConfiguration.getTruststorePath())) { - final KeyStore trustStore = new StandardKeyStoreBuilder() - .inputStream(inputStream) - .password(tlsConfiguration.getTruststorePassword().toCharArray()) - .type(tlsConfiguration.getTruststoreType().getType()) - .build(); - - return new StandardTrustManagerBuilder().trustStore(trustStore).build(); - } + private KeyStore getKeyStore() throws GeneralSecurityException { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + return new EphemeralKeyStoreBuilder() + .addPrivateKeyEntry(new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{certificate})) + .build(); } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-jetty/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-jetty/pom.xml index e94b3d0dbd82..91a5d9defc1a 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-jetty/pom.xml +++ b/nifi-registry/nifi-registry-core/nifi-registry-jetty/pom.xml @@ -90,7 +90,7 @@ org.apache.nifi - nifi-security-utils + nifi-security-cert-builder 2.0.0-SNAPSHOT test diff --git a/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/test/java/org/apache/nifi/registry/jetty/connector/ApplicationServerConnectorFactoryTest.java b/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/test/java/org/apache/nifi/registry/jetty/connector/ApplicationServerConnectorFactoryTest.java index 1e09332b96f4..11a4ac6e2940 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/test/java/org/apache/nifi/registry/jetty/connector/ApplicationServerConnectorFactoryTest.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/test/java/org/apache/nifi/registry/jetty/connector/ApplicationServerConnectorFactoryTest.java @@ -17,15 +17,26 @@ package org.apache.nifi.registry.jetty.connector; import org.apache.nifi.registry.properties.NiFiRegistryProperties; -import org.apache.nifi.security.util.TemporaryKeyStoreBuilder; -import org.apache.nifi.security.util.TlsConfiguration; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; +import org.apache.nifi.security.ssl.EphemeralKeyStoreBuilder; import org.apache.nifi.util.StringUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; 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 javax.security.auth.x500.X500Principal; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.List; import java.util.Properties; @@ -46,15 +57,34 @@ class ApplicationServerConnectorFactoryTest { private static final String LOCALHOST = "127.0.0.1"; - private static final String PROPRIETARY_TRUST_STORE_TYPE = "JKS"; + private static final String ALIAS = "entry-0"; + + private static final String KEY_STORE_EXTENSION = ".p12"; + + private static final String KEY_STORE_PASS = ApplicationServerConnectorFactoryTest.class.getName(); + + @TempDir + private static Path keyStoreDirectory; + + private static String keyStoreType; - static TlsConfiguration tlsConfiguration; + private static Path keyStorePath; Server server; @BeforeAll - static void setTlsConfiguration() { - tlsConfiguration = new TemporaryKeyStoreBuilder().trustStoreType(PROPRIETARY_TRUST_STORE_TYPE).build(); + static void setConfiguration() throws Exception { + final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + final X509Certificate certificate = new StandardCertificateBuilder(keyPair, new X500Principal("CN=localhost"), Duration.ofHours(1)).build(); + final KeyStore keyStore = new EphemeralKeyStoreBuilder().build(); + keyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), KEY_STORE_PASS.toCharArray(), new Certificate[]{certificate}); + + keyStorePath = Files.createTempFile(keyStoreDirectory, ApplicationServerConnectorFactoryTest.class.getSimpleName(), KEY_STORE_EXTENSION); + try (OutputStream outputStream = Files.newOutputStream(keyStorePath)) { + keyStore.store(outputStream, KEY_STORE_PASS.toCharArray()); + } + + keyStoreType = keyStore.getType().toUpperCase(); } @BeforeEach @@ -144,12 +174,12 @@ void testGetServerConnectorHttp2Properties() { private Properties getSecurityProperties() { final Properties securityProperties = new Properties(); - securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath()); - securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType()); - securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword()); - securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath()); - securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType()); - securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword()); + securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE, keyStorePath.toString()); + securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, keyStoreType); + securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, KEY_STORE_PASS); + securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE, keyStorePath.toString()); + securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, keyStoreType); + securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, KEY_STORE_PASS); return securityProperties; } diff --git a/nifi-system-tests/nifi-system-test-suite/pom.xml b/nifi-system-tests/nifi-system-test-suite/pom.xml index 568514331c25..3b840366801a 100644 --- a/nifi-system-tests/nifi-system-test-suite/pom.xml +++ b/nifi-system-tests/nifi-system-test-suite/pom.xml @@ -284,11 +284,6 @@ nifi-bootstrap 2.0.0-SNAPSHOT - - org.apache.nifi - nifi-security-utils - 2.0.0-SNAPSHOT - org.apache.nifi nifi-xml-processing