blob: 222512fd5696dc5d8f43cc9f6dfe66dc5ae8145b [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.minifi.c2.integration.test;
import com.palantir.docker.compose.DockerComposeRule;
import com.palantir.docker.compose.connection.DockerPort;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
import org.junit.Test;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
public abstract class AbstractTestSecure extends AbstractTestUnsecure {
public static final String C2_URL = "https://c2:10443/c2/config";
private final DockerComposeRule docker;
private final Path certificatesDirectory;
private final SSLContext trustSslContext;
protected AbstractTestSecure(DockerComposeRule docker, Path certificatesDirectory, SSLContext trustSslContext) {
this.docker = docker;
this.certificatesDirectory = certificatesDirectory;
this.trustSslContext = trustSslContext;
}
@Override
protected String getConfigUrl(DockerComposeRule docker) {
return C2_URL;
}
public static SSLContext initCertificates(Path certificatesDirectory, List<String> serverHostnames) throws Exception {
List<String> toolkitCommandLine = new ArrayList<>(Arrays.asList("-O", "-o", certificatesDirectory.toFile().getAbsolutePath(),
"-C", "CN=user1", "-C", "CN=user2", "-C", "CN=user3", "-C", "CN=user4", "-S", "badKeystorePass", "-K", "badKeyPass", "-P", "badTrustPass"));
for (String serverHostname : serverHostnames) {
toolkitCommandLine.add("-n");
toolkitCommandLine.add(serverHostname);
}
Files.createDirectories(certificatesDirectory);
TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine();
tlsToolkitStandaloneCommandLine.parse(toolkitCommandLine.toArray(new String[toolkitCommandLine.size()]));
new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig());
tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine();
tlsToolkitStandaloneCommandLine.parse(new String[]{"-O", "-o", certificatesDirectory.getParent().resolve("badCert").toFile().getAbsolutePath(), "-C", "CN=user3"});
new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig());
final KeyStore trustStore = KeyStoreUtils.getTrustStore("jks");
try (final InputStream trustStoreStream = new FileInputStream(certificatesDirectory.resolve("c2").resolve("truststore.jks").toFile().getAbsolutePath())) {
trustStore.load(trustStoreStream, "badTrustPass".toCharArray());
}
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
return SslContextFactory.createTrustSslContext(certificatesDirectory.resolve("c2").resolve("truststore.jks").toFile().getAbsolutePath(), "badTrustPass".toCharArray(), "jks", "TLS");
}
@Test
public void testNoClientCert() throws Exception {
assertReturnCode("", trustSslContext, 403);
assertReturnCode("?class=raspi2", trustSslContext, 403);
assertReturnCode("?class=raspi3", trustSslContext, 403);
}
@Test
public void testUser1() throws Exception {
SSLContext sslContext = loadSslContext("user1");
assertReturnCode("", sslContext, 403);
ConfigSchema configSchema = assertReturnCode("?class=raspi2", sslContext, 200);
assertEquals("raspi2.v1", configSchema.getFlowControllerProperties().getName());
assertReturnCode("?class=raspi3", sslContext, 403);
}
@Test
public void testUser2() throws Exception {
SSLContext sslContext = loadSslContext("user2");
assertReturnCode("", sslContext, 403);
assertReturnCode("?class=raspi2", sslContext, 403);
ConfigSchema configSchema = assertReturnCode("?class=raspi3", sslContext, 200);
assertEquals("raspi3.v2", configSchema.getFlowControllerProperties().getName());
}
@Test
public void testUser3() throws Exception {
SSLContext sslContext = loadSslContext("user3");
assertReturnCode("", sslContext, 400);
ConfigSchema configSchema = assertReturnCode("?class=raspi2", sslContext, 200);
assertEquals("raspi2.v1", configSchema.getFlowControllerProperties().getName());
configSchema = assertReturnCode("?class=raspi3", sslContext, 200);
assertEquals("raspi3.v2", configSchema.getFlowControllerProperties().getName());
}
@Test(expected = IOException.class)
public void testUser3WrongCA() throws Exception {
assertReturnCode("?class=raspi3", loadSslContext("user3", certificatesDirectory.getParent().resolve("badCert")), 403);
}
@Test
public void testUser4() throws Exception {
SSLContext sslContext = loadSslContext("user4");
assertReturnCode("", sslContext, 403);
assertReturnCode("?class=raspi2", sslContext, 403);
assertReturnCode("?class=raspi3", sslContext, 403);
}
protected SSLContext loadSslContext(String username) throws GeneralSecurityException, IOException {
return loadSslContext(username, certificatesDirectory);
}
protected SSLContext loadSslContext(String username, Path directory) throws GeneralSecurityException, IOException {
char[] keystorePasswd;
try (InputStream inputStream = Files.newInputStream(directory.resolve("CN=" + username + ".password"))) {
keystorePasswd = IOUtils.toString(inputStream, StandardCharsets.UTF_8).toCharArray();
}
return SslContextFactory.createSslContext(
directory.resolve("CN=" + username + ".p12").toFile().getAbsolutePath(),
keystorePasswd,
"PKCS12",
certificatesDirectory.resolve("c2").resolve("truststore.jks").toFile().getAbsolutePath(),
"badTrustPass".toCharArray(), "jks", SslContextFactory.ClientAuth.NONE, "TLS");
}
protected ConfigSchema assertReturnCode(String query, SSLContext sslContext, int expectedReturnCode) throws Exception {
HttpsURLConnection httpsURLConnection = openUrlConnection(C2_URL + query, sslContext);
try {
assertEquals(expectedReturnCode, httpsURLConnection.getResponseCode());
if (expectedReturnCode == 200) {
return SchemaLoader.loadConfigSchemaFromYaml(httpsURLConnection.getInputStream());
}
} finally {
httpsURLConnection.disconnect();
}
return null;
}
protected HttpsURLConnection openUrlConnection(String url, SSLContext sslContext) throws IOException {
DockerPort dockerPort = docker.containers().container("squid").port(3128);
HttpsURLConnection httpURLConnection = (HttpsURLConnection) new URL(url).openConnection(
new Proxy(Proxy.Type.HTTP, new InetSocketAddress(dockerPort.getIp(), dockerPort.getExternalPort())));
httpURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
return httpURLConnection;
}
@Override
protected HttpURLConnection openSuperUserUrlConnection(String url) throws IOException {
try {
return openUrlConnection(url, loadSslContext("user3"));
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
}
}