blob: 82cfdd3ec91a0bae98c4380b69d48e091bb69c8d [file] [log] [blame]
/*
* 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.bootstrap.util;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.nifi.properties.NiFiPropertiesLoader;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.StringUtils;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.util.IPAddress;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TestSecureNiFiConfigUtil {
public static final String TEST_RESOURCE_DIR = "src/test/resources/";
private final Logger logger = LoggerFactory.getLogger("org.apache.nifi.bootstrap.util.TestSecureNiFiConfigUtil");
private static final String PROPERTIES_PREFIX = "nifi-properties";
private static final boolean EXPECT_STORES_TO_EXIST = true;
private Path nifiPropertiesFile;
private Path keystorePath;
private Path truststorePath;
private final Path existingKeystorePath = getTestFilePath("existing-keystore.p12");
private final Path existingTruststorePath = getTestFilePath("existing-truststore.p12");
private NiFiProperties configureSecureNiFiProperties(Path testPropertiesFile) throws IOException {
Files.copy(testPropertiesFile, nifiPropertiesFile, StandardCopyOption.REPLACE_EXISTING);
SecureNiFiConfigUtil.configureSecureNiFiProperties(nifiPropertiesFile.toString(), logger);
return new NiFiPropertiesLoader().load(nifiPropertiesFile.toFile());
}
private static Path getPathFromClasspath(String filename) {
try {
return Paths.get(TestSecureNiFiConfigUtil.class.getClassLoader().getResource(filename).toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
private static Path getTestFilePath(String filename) {
return Paths.get(TEST_RESOURCE_DIR + filename);
}
private static String getFileHash(Path filepath) throws IOException {
return DigestUtils.sha256Hex(Files.readAllBytes(filepath));
}
@Before
public void init() throws IOException {
nifiPropertiesFile = Files.createTempFile(PROPERTIES_PREFIX, ".properties");
TlsConfiguration tlsConfig = new TemporaryKeyStoreBuilder().build();
Files.move(Paths.get(tlsConfig.getKeystorePath()), existingKeystorePath, StandardCopyOption.REPLACE_EXISTING);
Files.move(Paths.get(tlsConfig.getTruststorePath()), existingTruststorePath, StandardCopyOption.REPLACE_EXISTING);
}
@After
public void cleanUp() throws IOException {
deleteIfExists(nifiPropertiesFile);
deleteIfExists(keystorePath);
deleteIfExists(truststorePath);
deleteIfExists(existingKeystorePath);
deleteIfExists(existingTruststorePath);
}
private static void deleteIfExists(Path path) throws IOException {
if (path != null && StringUtils.isNotEmpty(path.toString())) {
Files.deleteIfExists(path);
}
}
private void runTestWithExpectedSuccess(String testPropertiesFile, List<String> expectedSANs) throws IOException, GeneralSecurityException {
Path testPropertiesFilePath = getPathFromClasspath(testPropertiesFile);
NiFiProperties niFiProperties = this.configureSecureNiFiProperties(testPropertiesFilePath);
keystorePath = Paths.get(niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE));
Assert.assertTrue(keystorePath.toFile().exists());
Assert.assertFalse(niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).isEmpty());
Assert.assertEquals("PKCS12", niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE));
char[] keyPassword = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray();
KeyStore keyStore = KeyStoreUtils.loadKeyStore(keystorePath.toString(),
niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray(),
niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE));
String alias = keyStore.aliases().nextElement();
Assert.assertTrue(keyStore.isKeyEntry(alias));
Key key = keyStore.getKey(alias, keyPassword);
Assert.assertNotNull(key);
truststorePath = Paths.get(niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
Assert.assertTrue(truststorePath.toFile().exists());
Assert.assertFalse(niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).isEmpty());
Assert.assertEquals("PKCS12", niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE));
KeyStore trustStore = KeyStoreUtils.loadKeyStore(truststorePath.toString(),
niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE));
String trustAlias = trustStore.aliases().nextElement();
Assert.assertTrue(trustStore.isCertificateEntry(trustAlias));
Certificate certificate = trustStore.getCertificate(trustAlias);
certificate.verify(certificate.getPublicKey());
if (!expectedSANs.isEmpty()) {
Collection<List<?>> sans = ((X509Certificate)certificate).getSubjectAlternativeNames();
Set<String> foundSands = new HashSet<>();
for(List<?> list : sans) {
String san = (String) list.get(1);
if (IPAddress.isValid(san)) {
Assert.assertEquals(GeneralName.iPAddress, list.get(0));
} else {
Assert.assertEquals(GeneralName.dNSName, list.get(0));
}
foundSands.add((String) list.get(1));
}
for(String expectedSAN : expectedSANs) {
Assert.assertTrue(foundSands.contains(expectedSAN));
}
}
}
private void runTestWithNoExpectedUpdates(String testPropertiesFile, boolean expectBothStoresToExist) throws IOException {
this.runTestWithNoExpectedUpdates(testPropertiesFile, expectBothStoresToExist, expectBothStoresToExist);
}
private void runTestWithNoExpectedUpdates(String testPropertiesFile, boolean expectKeystoreToExist, boolean expectTruststoreToExist)
throws IOException {
Path testPropertiesFilePath = getPathFromClasspath(testPropertiesFile);
NiFiProperties niFiProperties = this.configureSecureNiFiProperties(testPropertiesFilePath);
keystorePath = Paths.get(niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE));
truststorePath = Paths.get(niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
Assert.assertEquals(expectKeystoreToExist, Paths.get(niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE)).toFile().exists());
Assert.assertEquals(expectTruststoreToExist, Paths.get(niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE)).toFile().exists());
// Show that nifi.properties was not updated
Assert.assertEquals(getFileHash(nifiPropertiesFile), getFileHash(testPropertiesFilePath));
}
@Test
public void testSuccessfulConfiguration() throws IOException, GeneralSecurityException {
runTestWithExpectedSuccess("nifi.properties.success", Collections.emptyList());
}
@Test
public void testSuccessfulDNSSANs() throws IOException, GeneralSecurityException {
runTestWithExpectedSuccess("nifi.properties.dns-sans",
Arrays.asList("test-host", "remote-host", "proxy-host", "cluster-host"));
}
@Test
public void testNoHttps() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.no-https", !EXPECT_STORES_TO_EXIST);
}
@Test
public void testNoKeystores() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.no-keystores", !EXPECT_STORES_TO_EXIST);
}
@Test
public void testTruststorePasswordSet() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.truststore-password", !EXPECT_STORES_TO_EXIST);
}
@Test
public void testKeystorePasswordSet() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.keystore-password", !EXPECT_STORES_TO_EXIST);
}
@Test
public void test_keystoreAndTruststoreAlreadyExist() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.stores-exist", EXPECT_STORES_TO_EXIST);
}
@Test
public void testNoKeystoresTypes() throws IOException, GeneralSecurityException {
runTestWithExpectedSuccess("nifi.properties.no-keystore-types", Collections.emptyList());
}
@Test
public void testFailure_onlyTruststoreExists() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.only-truststore", !EXPECT_STORES_TO_EXIST, EXPECT_STORES_TO_EXIST);
}
@Test
public void testFailure_onlyKeystoreExists() throws IOException {
runTestWithNoExpectedUpdates("nifi.properties.only-keystore", EXPECT_STORES_TO_EXIST, !EXPECT_STORES_TO_EXIST);
}
}