NIFI-13874 Refactored KeyStore and SSLContext Creation for Tests

This closes #9392

- 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

Signed-off-by: Joseph Witt <joewitt@apache.org>
This commit is contained in:
exceptionfactory 2024-10-14 23:42:41 -05:00 committed by Joseph Witt
parent ea2cfb102f
commit 1b37d78403
No known key found for this signature in database
GPG Key ID: 9093BF854F811A1A
65 changed files with 1543 additions and 2118 deletions

View File

@ -51,11 +51,6 @@ limitations under the License.
<artifactId>nifi-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>c2-protocol-api</artifactId>
@ -125,12 +120,6 @@ limitations under the License.
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>

View File

@ -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<String, String> 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;
}
}

View File

@ -59,11 +59,6 @@ limitations under the License.
<artifactId>nifi-framework-nar-loading-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>

View File

@ -352,11 +352,6 @@
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>

View File

@ -65,12 +65,6 @@
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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 org.mockito.Mockito;
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 class TestStandardHashiCorpVaultCommunicationService {
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 class TestStandardHashiCorpVaultCommunicationService {
// 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 class TestStandardHashiCorpVaultCommunicationService {
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();
}
}

View File

@ -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<X509Certificate> certificates = new ArrayList<>();
private final List<KeyStore.PrivateKeyEntry> 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<X509Certificate> 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<KeyStore.PrivateKeyEntry> 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);
}
}
}

View File

@ -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<String> 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<String> 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());
}
}

View File

@ -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 <em>first</em>
* 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> 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.
* <p>
*
* @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);
}
}
}

View File

@ -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<String> 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) {
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -53,7 +53,6 @@
<module>nifi-security-kerberos</module>
<module>nifi-security-ssl</module>
<module>nifi-security-utils-api</module>
<module>nifi-security-utils</module>
<module>nifi-single-user-utils</module>
<module>nifi-site-to-site-client</module>
<module>nifi-swagger-integration</module>

View File

@ -34,12 +34,6 @@ language governing permissions and limitations under the License. -->
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-ssl-context-service-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-ssl-context-service</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
@ -56,6 +50,11 @@ language governing permissions and limitations under the License. -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -96,12 +96,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>

View File

@ -51,7 +51,13 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@ -26,10 +26,10 @@ import org.apache.nifi.event.transport.configuration.ShutdownTimeout;
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.Mock;
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 @@ public class StringNettyEventSenderFactoryTest {
}
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();
}
}

View File

@ -22,12 +22,6 @@
<artifactId>nifi-flow-registry-client-services</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>

View File

@ -36,6 +36,10 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-ssl-context-service-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kerberos-user-service-api</artifactId>
@ -68,7 +72,13 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-ssl-context-service</artifactId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@ -35,10 +35,9 @@ import org.apache.nifi.kafka.service.api.producer.RecordSummary;
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.BeforeEach;
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.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.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<String> fileLocationNames = Set.of(
"KAFKA_SSL_KEYSTORE_LOCATION", "KAFKA_SSL_TRUSTSTORE_LOCATION");
private static final Set<String> 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 @@ public class Kafka3ConnectionServiceBaseIT {
}
@Test
void testVerifySuccessful() throws InitializationException {
void testVerifySuccessful() {
final Map<PropertyDescriptor, String> properties = new LinkedHashMap<>();
properties.put(Kafka3ConnectionService.BOOTSTRAP_SERVERS, kafkaContainer.getBootstrapServers());
final MockConfigurationContext configurationContext = new MockConfigurationContext(properties, null, null);
@ -311,18 +355,18 @@ public class Kafka3ConnectionServiceBaseIT {
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;

View File

@ -29,13 +29,13 @@ public class Kafka3ConnectionServiceSSLIT extends Kafka3ConnectionServiceBaseIT
protected Map<String, String> getKafkaContainerConfigProperties() {
final Map<String, String> 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 @@ public class Kafka3ConnectionServiceSSLIT extends Kafka3ConnectionServiceBaseIT
protected Map<String, String> getAdminClientConfigProperties() {
final Map<String, String> 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;
}
}

View File

@ -91,7 +91,7 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-ssl-context-service</artifactId>
<artifactId>nifi-security-utils-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@ -291,11 +291,6 @@
<artifactId>commons-codec</artifactId>
<version>${org.apache.commons.codec.version}</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert</artifactId>
@ -542,6 +537,18 @@
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>

View File

@ -81,8 +81,6 @@ import org.apache.nifi.processors.standard.util.ProxyAuthenticator;
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.HashSet;
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 class InvokeHTTP extends AbstractProcessor {
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);
}

View File

@ -36,16 +36,15 @@ import org.apache.nifi.provenance.ProvenanceEventType;
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 org.mockito.Answers;
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 class InvokeHTTPTest {
}
@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 @@ public class InvokeHTTPTest {
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 @@ public class InvokeHTTPTest {
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 @@ public class InvokeHTTPTest {
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");

View File

@ -28,9 +28,10 @@ import okio.Okio;
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.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.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.AfterEach;
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.Files;
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.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCES
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();
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[]{};
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
);
trustManager = new StandardTrustManagerBuilder().trustStore(keyStore).build();
serverKeyStoreNoTrustStoreSslContext = new StandardSslContextBuilder()
.keyStore(keyStore)
.keyPassword(protectionParameter)
.build();
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())
);
keyStoreSslContext = new StandardSslContextBuilder()
.trustStore(keyStore)
.keyStore(keyStore)
.keyPassword(protectionParameter)
.build();
trustStoreSslContext = new StandardSslContextBuilder()
.trustStore(keyStore)
.build();
}
@BeforeEach
@ -238,7 +199,7 @@ public class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
@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 class TestListenHTTP {
}
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<MockFlowFile> 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 class TestListenHTTP {
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 @@ public class TestListenHTTP {
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 @@ public class TestListenHTTP {
}
}
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);

View File

@ -24,21 +24,28 @@ import org.apache.nifi.event.transport.configuration.TransportProtocol;
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

View File

@ -27,8 +27,9 @@ import org.apache.nifi.flowfile.FlowFile;
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.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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -52,11 +52,18 @@ import org.mockito.junit.jupiter.MockitoExtension;
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 class TestPutTCP {
@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 @@ public class TestPutTCP {
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;

View File

@ -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;
}
}

View File

@ -42,7 +42,13 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@ -22,9 +22,9 @@ import org.apache.nifi.distributed.cache.client.DistributedMapCacheClientService
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.junit.jupiter.api.Test;
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 class DistributedMapCacheTlsTest {
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;
}

View File

@ -28,12 +28,18 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-utils-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -34,11 +34,11 @@ import org.apache.nifi.expression.ExpressionLanguageScope;
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.File;
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 class StandardSSLContextService extends AbstractControllerService impleme
*/
@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 class StandardSSLContextService extends AbstractControllerService impleme
@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();
}
return trustManager;
} catch (final TlsException e) {
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");
}
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);
}
}

View File

@ -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.List;
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 class StandardTlsConfiguration implements TlsConfiguration {
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;

View File

@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<ValidationResult> 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<String, String> 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();
}
}

View File

@ -18,8 +18,8 @@ package org.apache.nifi.ssl;
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.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.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 class StandardRestrictedSSLContextServiceTest {
}
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);
}
}

View File

@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<ValidationResult> 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<String, String> 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);
}
}

View File

@ -49,7 +49,13 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@ -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 @@ import static org.mockito.Mockito.when;
@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 @@ class StandardKeyManagerProviderTest {
@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<X509KeyManager> keyManager = provider.getKeyManager(sslContextService);

View File

@ -24,10 +24,11 @@ import okhttp3.mockwebserver.RecordedRequest;
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 org.mockito.junit.jupiter.MockitoExtension;
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 @@ class StandardWebClientServiceProviderTest {
@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);

View File

@ -35,12 +35,12 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-identity</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-identity</artifactId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>

View File

@ -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.InvalidLoginCredentialsException
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 @@ public class LdapProvider implements LoginIdentityProvider {
}
}
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 class LdapProvider implements LoginIdentityProvider {
// 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());

View File

@ -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;
}
}

View File

@ -34,11 +34,10 @@ import org.apache.nifi.authorization.util.IdentityMappingUtil;
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.NamingException;
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 @@ public class LdapUserGroupProvider implements UserGroupProvider {
}
do {
userList.addAll(ldapTemplate.search(userSearchBase, userFilter.encode(), userControls, new AbstractContextMapper<User>() {
userList.addAll(ldapTemplate.search(userSearchBase, userFilter.encode(), userControls, new AbstractContextMapper<>() {
@Override
protected User doMapFromContext(DirContextOperations ctx) {
// get the user
@ -567,7 +572,7 @@ public class LdapUserGroupProvider implements UserGroupProvider {
}
do {
groupList.addAll(ldapTemplate.search(groupSearchBase, groupFilter.encode(), groupControls, new AbstractContextMapper<Group>() {
groupList.addAll(ldapTemplate.search(groupSearchBase, groupFilter.encode(), groupControls, new AbstractContextMapper<>() {
@Override
protected Group doMapFromContext(DirContextOperations ctx) {
// get the group identity
@ -830,22 +835,91 @@ public class LdapUserGroupProvider implements UserGroupProvider {
}
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;
}
}

View File

@ -87,12 +87,6 @@
<version>2.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
@ -264,10 +258,6 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>

View File

@ -75,11 +75,6 @@
<artifactId>nifi-security-utils-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>

View File

@ -56,11 +56,6 @@
<artifactId>nifi-framework-core-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert</artifactId>

View File

@ -151,11 +151,6 @@
<artifactId>nifi-framework-cluster-protocol</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-site-to-site-client</artifactId>

View File

@ -127,11 +127,6 @@
<artifactId>nifi-property-encryptor</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-xml-processing</artifactId>

View File

@ -81,11 +81,6 @@
<artifactId>nifi-security-utils-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar-utils</artifactId>
@ -340,6 +335,12 @@
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>

View File

@ -53,9 +53,9 @@ import org.apache.nifi.controller.repository.claim.ContentClaim;
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.invocation.InvocationOnMock;
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.InputStream;
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 class LoadBalancedQueueIT {
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);

View File

@ -29,9 +29,9 @@ import io.netty.channel.SimpleChannelInboundHandler;
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 org.junit.jupiter.api.condition.EnabledIf;
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.net.SocketException;
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 class TestPeerChannel {
@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

View File

@ -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.SSLContext;
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

View File

@ -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<String, String> INSECURE_PROPS = new HashMap<String, String>() {{
put(ZOOKEEPER_PROPERTIES_FILE_KEY, INSECURE_ZOOKEEPER_PROPS);
}};
private static final Map<String, String> SECURE_NIFI_PROPS = new HashMap<>();
private static final Map<String, String> INSECURE_NIFI_PROPS = new HashMap<String, String>() {{
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<String, String>() {{
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<String, String>() {{
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();
}
}

View File

@ -32,11 +32,6 @@
<artifactId>nifi-security-utils-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar-utils</artifactId>

View File

@ -26,11 +26,6 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-nar-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils-api</artifactId>

View File

@ -120,8 +120,9 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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.server.SslConnectionFactory;
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 @@ class FrameworkServerConnectorFactoryTest {
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 @@ class FrameworkServerConnectorFactoryTest {
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;
}

View File

@ -443,11 +443,6 @@
<artifactId>nifi-framework-core</artifactId>
<scope>provided</scope> <!-- expected to be provided by parent classloader -->
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>

View File

@ -101,11 +101,6 @@
<artifactId>nifi-web-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-identity</artifactId>
@ -317,6 +312,18 @@
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-ssl</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-python-framework-api</artifactId>

View File

@ -20,12 +20,11 @@ import okhttp3.HttpUrl;
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 org.springframework.security.saml2.provider.service.registration.Saml2Mes
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 @@ class StandardRegistrationBuilderProviderTest {
}
@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 @@ class StandardRegistrationBuilderProviderTest {
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 @@ class StandardRegistrationBuilderProviderTest {
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 @@ class StandardRegistrationBuilderProviderTest {
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);
}

View File

@ -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 org.springframework.security.saml2.provider.service.registration.RelyingP
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 @@ class StandardRelyingPartyRegistrationRepositoryTest {
}
@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 @@ class StandardRelyingPartyRegistrationRepositoryTest {
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 @@ class StandardRelyingPartyRegistrationRepositoryTest {
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();
}
}

View File

@ -90,7 +90,7 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<artifactId>nifi-security-cert-builder</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@ -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";
static TlsConfiguration tlsConfiguration;
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;
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 @@ class ApplicationServerConnectorFactoryTest {
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;
}

View File

@ -284,11 +284,6 @@
<artifactId>nifi-bootstrap</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-xml-processing</artifactId>