mirror of https://github.com/apache/nifi.git
NIFI-12200 Removed module nifi-toolkit-tls
This closes #7879 Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
parent
f6fcfb433c
commit
88bdeee219
|
@ -44,7 +44,6 @@ env:
|
|||
-pl -:nifi-kafka-connector-assembly
|
||||
-pl -:nifi-kafka-connector-tests
|
||||
-pl -:nifi-toolkit-encrypt-config
|
||||
-pl -:nifi-toolkit-tls
|
||||
-pl -:nifi-toolkit-assembly
|
||||
-pl -:nifi-registry-assembly
|
||||
-pl -:nifi-registry-toolkit-assembly
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
## How to execute minifi-c2-integration-tests
|
||||
|
||||
### Build required modules
|
||||
mvn -pl minifi/minifi-assembly -am install -T1C -DskipTests
|
||||
mvn -pl minifi/minifi-docker -am install -T1C -DskipTests -P docker
|
||||
mvn -pl minifi/minifi-c2/minifi-c2-assembly -am install -T1C -DskipTests
|
||||
mvn -pl minifi/minifi-c2/minifi-c2-docker -am install -T1C -DskipTests -P docker
|
||||
mvn -pl nifi-toolkit/nifi-toolkit-assembly -am install -T1C -DskipTests
|
||||
- mvn -pl minifi/minifi-assembly -am install -T1C -DskipTests
|
||||
- mvn -pl minifi/minifi-docker -am install -T1C -DskipTests -P docker
|
||||
- mvn -pl minifi/minifi-c2/minifi-c2-assembly -am install -T1C -DskipTests
|
||||
- mvn -pl minifi/minifi-c2/minifi-c2-docker -am install -T1C -DskipTests -P docker
|
||||
- mvn -pl nifi-toolkit/nifi-toolkit-assembly -am install -T1C -DskipTests
|
||||
|
||||
### Execute integration tests
|
||||
mvn verify -Pdocker -f minifi/minifi-c2/minifi-c2-integration-tests/pom.xml
|
||||
- mvn verify -Pdocker -f minifi/minifi-c2/minifi-c2-integration-tests/pom.xml
|
|
@ -38,14 +38,9 @@ limitations under the License.
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-tls</artifactId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.palantir.docker.compose</groupId>
|
||||
|
|
|
@ -1,212 +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.c2.integration.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.palantir.docker.compose.DockerComposeExtension;
|
||||
import com.palantir.docker.compose.connection.DockerPort;
|
||||
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.URI;
|
||||
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 javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.nifi.controller.flow.VersionedDataflow;
|
||||
import org.apache.nifi.security.util.KeyStoreUtils;
|
||||
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.toolkit.tls.standalone.TlsToolkitStandalone;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public abstract class AbstractTestSecure extends AbstractTestUnsecure {
|
||||
public static final String C2_URL = "https://c2:10443/c2/config";
|
||||
|
||||
private final DockerComposeExtension docker;
|
||||
private final Path certificatesDirectory;
|
||||
private final SSLContext trustSslContext;
|
||||
|
||||
protected AbstractTestSecure(DockerComposeExtension docker, Path certificatesDirectory, SSLContext trustSslContext) {
|
||||
this.docker = docker;
|
||||
this.certificatesDirectory = certificatesDirectory;
|
||||
this.trustSslContext = trustSslContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConfigUrl(DockerComposeExtension 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[0]));
|
||||
new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig());
|
||||
|
||||
tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine();
|
||||
tlsToolkitStandaloneCommandLine.parse("-O", "-o", certificatesDirectory.getParent().resolve("badCert").toFile().getAbsolutePath(), "-C", "CN=user3");
|
||||
new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig());
|
||||
|
||||
final KeyStore trustStore = KeyStoreUtils.getKeyStore("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);
|
||||
|
||||
TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(
|
||||
null, null, null,
|
||||
certificatesDirectory.resolve("c2").resolve("truststore.jks").toFile().getAbsolutePath(), "badTrustPass", KeystoreType.JKS);
|
||||
|
||||
|
||||
return SslContextFactory.createSslContext(tlsConfiguration);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
VersionedDataflow versionedDataflow = assertReturnCode("?class=raspi2", sslContext, 200);
|
||||
assertEquals("raspi2.v1", versionedDataflow.getRootGroup().getName());
|
||||
|
||||
assertReturnCode("?class=raspi3", sslContext, 403);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUser2() throws Exception {
|
||||
SSLContext sslContext = loadSslContext("user2");
|
||||
|
||||
assertReturnCode("", sslContext, 403);
|
||||
assertReturnCode("?class=raspi2", sslContext, 403);
|
||||
|
||||
VersionedDataflow versionedDataflow = assertReturnCode("?class=raspi3", sslContext, 200);
|
||||
assertEquals("raspi3.v2", versionedDataflow.getRootGroup().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUser3() throws Exception {
|
||||
SSLContext sslContext = loadSslContext("user3");
|
||||
|
||||
assertReturnCode("", sslContext, 400);
|
||||
|
||||
VersionedDataflow dataflowRaspi2 = assertReturnCode("?class=raspi2", sslContext, 200);
|
||||
assertEquals("raspi2.v1", dataflowRaspi2.getRootGroup().getName());
|
||||
|
||||
VersionedDataflow dataflowRaspi3 = assertReturnCode("?class=raspi3", sslContext, 200);
|
||||
assertEquals("raspi3.v2", dataflowRaspi3.getRootGroup().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUser3WrongCA() {
|
||||
assertThrows(IOException.class, () -> 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 {
|
||||
String keystorePasswd;
|
||||
try (InputStream inputStream = Files.newInputStream(directory.resolve("CN=" + username + ".password"))) {
|
||||
keystorePasswd = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
|
||||
}
|
||||
TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(
|
||||
directory.resolve("CN=" + username + ".p12").toFile().getAbsolutePath(),
|
||||
keystorePasswd,
|
||||
KeystoreType.PKCS12,
|
||||
certificatesDirectory.resolve("c2").resolve("truststore.jks").toFile().getAbsolutePath(),
|
||||
"badTrustPass",
|
||||
KeystoreType.JKS);
|
||||
|
||||
return SslContextFactory.createSslContext(tlsConfiguration);
|
||||
}
|
||||
|
||||
protected VersionedDataflow assertReturnCode(String query, SSLContext sslContext, int expectedReturnCode) throws Exception {
|
||||
HttpsURLConnection httpsURLConnection = openUrlConnection(C2_URL + query, sslContext);
|
||||
httpsURLConnection.setRequestProperty("Accept", "application/json");
|
||||
try {
|
||||
assertEquals(expectedReturnCode, httpsURLConnection.getResponseCode());
|
||||
if (expectedReturnCode == 200) {
|
||||
try (InputStream inputStream = httpsURLConnection.getInputStream()) {
|
||||
return toVersionedDataFlow(inputStream);
|
||||
}
|
||||
}
|
||||
} 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) URI.create(url).toURL().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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,79 +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.c2.integration.test;
|
||||
|
||||
import com.palantir.docker.compose.DockerComposeExtension;
|
||||
import org.apache.nifi.minifi.c2.integration.test.health.HttpsStatusCodeHealthCheck;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DelegatingConfigurationProviderSecureTest extends AbstractTestSecure {
|
||||
public static final String C2_UPSTREAM_URL = "https://c2:10443/c2/config";
|
||||
private static SSLSocketFactory healthCheckSocketFactory;
|
||||
private static Path certificatesDirectory;
|
||||
private static SSLContext trustSslContext;
|
||||
|
||||
// Not annotated as rule because we need to generate certificatesDirectory first
|
||||
public static DockerComposeExtension docker = DockerComposeExtension.builder()
|
||||
.file("target/test-classes/docker-compose-DelegatingProviderSecureTest.yml")
|
||||
.waitingForServices(Arrays.asList("squid", "c2-upstream"),
|
||||
new HttpsStatusCodeHealthCheck(container -> C2_UPSTREAM_URL, containers -> containers.get(0), containers -> containers.get(1), () -> healthCheckSocketFactory, 403))
|
||||
.waitingForServices(Arrays.asList("squid", "c2"),
|
||||
new HttpsStatusCodeHealthCheck(container -> C2_URL, containers -> containers.get(0), containers -> containers.get(1), () -> healthCheckSocketFactory, 403))
|
||||
.build();
|
||||
|
||||
public DelegatingConfigurationProviderSecureTest() {
|
||||
super(docker, certificatesDirectory, trustSslContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates certificates with the tls-toolkit and then starts up the docker compose file
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void initCertificates() throws Exception {
|
||||
certificatesDirectory = Paths.get(DelegatingConfigurationProviderSecureTest.class.getClassLoader()
|
||||
.getResource("docker-compose-DelegatingProviderSecureTest.yml").getFile()).getParent().toAbsolutePath().resolve("certificates-DelegatingConfigurationProviderSecureTest");
|
||||
trustSslContext = initCertificates(certificatesDirectory, Arrays.asList("c2", "c2-upstream"));
|
||||
healthCheckSocketFactory = trustSslContext.getSocketFactory();
|
||||
|
||||
docker.before();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
docker.after();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
super.setup(docker);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpstreamPermissionDenied() throws Exception {
|
||||
assertReturnCode("?class=raspi4", loadSslContext("user3"), 403);
|
||||
}
|
||||
}
|
|
@ -1,69 +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.c2.integration.test;
|
||||
|
||||
import com.palantir.docker.compose.DockerComposeExtension;
|
||||
import org.apache.nifi.minifi.c2.integration.test.health.HttpsStatusCodeHealthCheck;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class FileSystemCacheProviderSecureTest extends AbstractTestSecure {
|
||||
private static SSLSocketFactory healthCheckSocketFactory;
|
||||
|
||||
// Not annotated as rule because we need to generate certificatesDirectory first
|
||||
public static DockerComposeExtension docker = DockerComposeExtension.builder()
|
||||
.file("target/test-classes/docker-compose-FileSystemCacheProviderSecureTest.yml")
|
||||
.waitingForServices(Arrays.asList("squid", "c2"),
|
||||
new HttpsStatusCodeHealthCheck(container -> C2_URL, containers -> containers.get(0), containers -> containers.get(1), () -> healthCheckSocketFactory, 403))
|
||||
.build();
|
||||
private static Path certificatesDirectory;
|
||||
private static SSLContext trustSslContext;
|
||||
|
||||
public FileSystemCacheProviderSecureTest() {
|
||||
super(docker, certificatesDirectory, trustSslContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates certificates with the tls-toolkit and then starts up the docker compose file
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void initCertificates() throws Exception {
|
||||
certificatesDirectory = Paths.get(FileSystemCacheProviderSecureTest.class.getClassLoader()
|
||||
.getResource("docker-compose-FileSystemCacheProviderSecureTest.yml").getFile()).getParent().toAbsolutePath().resolve("certificates-FileSystemCacheProviderSecureTest");
|
||||
trustSslContext = initCertificates(certificatesDirectory, Arrays.asList("c2"));
|
||||
healthCheckSocketFactory = trustSslContext.getSocketFactory();
|
||||
docker.before();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
docker.after();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
super.setup(docker);
|
||||
}
|
||||
}
|
|
@ -17,10 +17,10 @@
|
|||
## How to execute minifi-integration-tests
|
||||
|
||||
### Build required modules
|
||||
mvn -pl minifi/minifi-assembly -am install -T1C -DskipTests
|
||||
mvn -pl minifi/minifi-docker -am install -T1C -DskipTests -P docker
|
||||
mvn -pl nifi-toolkit/nifi-toolkit-assembly -am install -T1C -DskipTests
|
||||
mvn -pl minifi/minifi-integration-tests -am install -T1C -DskipTests
|
||||
- mvn -pl minifi/minifi-assembly -am install -T1C -DskipTests
|
||||
- mvn -pl minifi/minifi-docker -am install -T1C -DskipTests -P docker
|
||||
- mvn -pl nifi-toolkit/nifi-toolkit-assembly -am install -T1C -DskipTests
|
||||
- mvn -pl minifi/minifi-integration-tests -am install -T1C -DskipTests
|
||||
|
||||
### Execute integration tests
|
||||
mvn verify -Pdocker -f minifi/minifi-integration-tests/pom.xml
|
||||
- mvn verify -Pdocker -f minifi/minifi-integration-tests/pom.xml
|
|
@ -50,17 +50,6 @@ limitations under the License.
|
|||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-tls</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi.minifi</groupId>
|
||||
<artifactId>minifi-toolkit-configuration</artifactId>
|
||||
|
|
|
@ -1,123 +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.integration.c2;
|
||||
|
||||
import com.palantir.docker.compose.DockerComposeExtension;
|
||||
import com.palantir.docker.compose.connection.waiting.HealthChecks;
|
||||
import org.apache.nifi.minifi.integration.util.LogUtil;
|
||||
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.toolkit.tls.standalone.TlsToolkitStandalone;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Timeout(60)
|
||||
public class C2ProtocolIntegrationTest {
|
||||
private static final String AGENT_1 = "minifi-edge1";
|
||||
private static final String AGENT_2 = "minifi-edge2";
|
||||
private static final String AGENT_3 = "minifi-edge3";
|
||||
private static final String AGENT_CLASS_1 = "raspi3";
|
||||
private static final String AGENT_CLASS_2 = "raspi4";
|
||||
private static final String SERVICE = "c2-authoritative";
|
||||
private static final String FLOW_JSON = "config.application.json.v2";
|
||||
private static Path certificatesDirectory;
|
||||
private static SSLContext trustSslContext;
|
||||
private static SSLSocketFactory healthCheckSocketFactory;
|
||||
public static DockerComposeExtension docker = DockerComposeExtension.builder()
|
||||
.file("target/test-classes/docker-compose-c2-protocol.yml")
|
||||
.waitingForService(AGENT_1, HealthChecks.toRespond2xxOverHttp(8000, dockerPort -> "http://" + dockerPort.getIp() + ":" + dockerPort.getExternalPort()))
|
||||
.waitingForService(AGENT_2, HealthChecks.toRespond2xxOverHttp(8000, dockerPort -> "http://" + dockerPort.getIp() + ":" + dockerPort.getExternalPort()))
|
||||
.waitingForService(AGENT_3, HealthChecks.toRespond2xxOverHttp(8000, dockerPort -> "http://" + dockerPort.getIp() + ":" + dockerPort.getExternalPort()))
|
||||
.build();
|
||||
|
||||
private static Path resourceDirectory;
|
||||
private static Path authoritativeFiles;
|
||||
private static Path minifiEdge1Version2;
|
||||
private static Path minifiEdge2Version2;
|
||||
private static Path minifiEdge3Version2;
|
||||
|
||||
/**
|
||||
* Generates certificates with the tls-toolkit and then starts up the docker compose file
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void init() throws Exception {
|
||||
resourceDirectory = Paths.get(C2ProtocolIntegrationTest.class.getClassLoader()
|
||||
.getResource("docker-compose-c2-protocol.yml").getFile()).getParent();
|
||||
certificatesDirectory = resourceDirectory.toAbsolutePath().resolve("certificates-c2-protocol");
|
||||
authoritativeFiles = resourceDirectory.resolve("c2").resolve("protocol").resolve(SERVICE).resolve("files");
|
||||
minifiEdge1Version2 = authoritativeFiles.resolve("edge1").resolve(AGENT_CLASS_1).resolve(FLOW_JSON);
|
||||
minifiEdge2Version2 = authoritativeFiles.resolve("edge2").resolve(AGENT_CLASS_1).resolve(FLOW_JSON);
|
||||
minifiEdge3Version2 = authoritativeFiles.resolve("edge3").resolve(AGENT_CLASS_2).resolve(FLOW_JSON);
|
||||
|
||||
if (Files.exists(minifiEdge1Version2)) {
|
||||
Files.delete(minifiEdge1Version2);
|
||||
}
|
||||
if (Files.exists(minifiEdge2Version2)) {
|
||||
Files.delete(minifiEdge2Version2);
|
||||
}
|
||||
if (Files.exists(minifiEdge3Version2)) {
|
||||
Files.delete(minifiEdge3Version2);
|
||||
}
|
||||
|
||||
List<String> toolkitCommandLine = new ArrayList<>(Arrays.asList("-O", "-o", certificatesDirectory.toFile().getAbsolutePath(), "-S", "badKeystorePass", "-P", "badTrustPass"));
|
||||
for (String serverHostname : Arrays.asList(SERVICE, AGENT_1, AGENT_2, AGENT_3)) {
|
||||
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());
|
||||
|
||||
TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(
|
||||
null,null,null,
|
||||
certificatesDirectory.resolve(SERVICE).resolve("truststore.jks").toFile().getAbsolutePath(),
|
||||
"badTrustPass",
|
||||
KeystoreType.JKS);
|
||||
trustSslContext = SslContextFactory.createSslContext(tlsConfiguration);
|
||||
healthCheckSocketFactory = trustSslContext.getSocketFactory();
|
||||
|
||||
docker.before();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopDocker() {
|
||||
docker.after();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlowPublishThroughC2Protocol() throws Exception {
|
||||
LogUtil.verifyLogEntries("c2/protocol/minifi-edge1/expected.json", docker.containers().container(AGENT_1));
|
||||
LogUtil.verifyLogEntries("c2/protocol/minifi-edge2/expected.json", docker.containers().container(AGENT_2));
|
||||
LogUtil.verifyLogEntries("c2/protocol/minifi-edge3/expected.json", docker.containers().container(AGENT_3));
|
||||
}
|
||||
}
|
|
@ -1,134 +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.integration.c2;
|
||||
|
||||
import com.palantir.docker.compose.DockerComposeExtension;
|
||||
import org.apache.nifi.minifi.c2.integration.test.health.HttpsStatusCodeHealthCheck;
|
||||
import org.apache.nifi.minifi.integration.util.LogUtil;
|
||||
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.toolkit.tls.standalone.TlsToolkitStandalone;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Timeout(180)
|
||||
public class HierarchicalC2IntegrationTest {
|
||||
private static Path certificatesDirectory;
|
||||
private static SSLContext trustSslContext;
|
||||
private static SSLSocketFactory healthCheckSocketFactory;
|
||||
|
||||
// Not annotated as rule because we need to generate certificatesDirectory first
|
||||
public static DockerComposeExtension docker = DockerComposeExtension.builder()
|
||||
.file("target/test-classes/docker-compose-c2-hierarchical.yml")
|
||||
.waitingForServices(Arrays.asList("squid-edge3", "c2"),
|
||||
new HttpsStatusCodeHealthCheck(container -> "https://c2-authoritative:10443/c2/config",
|
||||
containers -> containers.get(0), containers -> containers.get(1), () -> healthCheckSocketFactory, 403))
|
||||
.build();
|
||||
private static Path resourceDirectory;
|
||||
private static Path authoritativeFiles;
|
||||
private static Path minifiEdge1Version2;
|
||||
private static Path minifiEdge2Version2;
|
||||
private static Path minifiEdge3Version2;
|
||||
|
||||
/**
|
||||
* Generates certificates with the tls-toolkit and then starts up the docker compose file
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void initCertificates() throws Exception {
|
||||
resourceDirectory = Paths.get(HierarchicalC2IntegrationTest.class.getClassLoader()
|
||||
.getResource("docker-compose-c2-hierarchical.yml").getFile()).getParent();
|
||||
certificatesDirectory = resourceDirectory.toAbsolutePath().resolve("certificates-c2-hierarchical");
|
||||
authoritativeFiles = resourceDirectory.resolve("c2").resolve("hierarchical").resolve("c2-authoritative").resolve("files");
|
||||
minifiEdge1Version2 = authoritativeFiles.resolve("edge1").resolve("raspi3").resolve("config.application.json.v2");
|
||||
minifiEdge2Version2 = authoritativeFiles.resolve("edge2").resolve("raspi2").resolve("config.application.json.v2");
|
||||
minifiEdge3Version2 = authoritativeFiles.resolve("edge3").resolve("raspi3").resolve("config.application.json.v2");
|
||||
|
||||
if (Files.exists(minifiEdge1Version2)) {
|
||||
Files.delete(minifiEdge1Version2);
|
||||
}
|
||||
if (Files.exists(minifiEdge2Version2)) {
|
||||
Files.delete(minifiEdge2Version2);
|
||||
}
|
||||
if (Files.exists(minifiEdge3Version2)) {
|
||||
Files.delete(minifiEdge3Version2);
|
||||
}
|
||||
|
||||
List<String> toolkitCommandLine = new ArrayList<>(Arrays.asList("-O", "-o", certificatesDirectory.toFile().getAbsolutePath(), "-S", "badKeystorePass", "-P", "badTrustPass"));
|
||||
for (String serverHostname : Arrays.asList("c2-authoritative", "minifi-edge1", "c2-edge2", "minifi-edge3")) {
|
||||
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());
|
||||
|
||||
TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(
|
||||
null,null,null,
|
||||
certificatesDirectory.resolve("c2-authoritative").resolve("truststore.jks").toFile().getAbsolutePath(),
|
||||
"badTrustPass",
|
||||
KeystoreType.JKS);
|
||||
trustSslContext = SslContextFactory.createSslContext(tlsConfiguration);
|
||||
healthCheckSocketFactory = trustSslContext.getSocketFactory();
|
||||
|
||||
docker.before();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopDocker() {
|
||||
docker.after();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMiNiFiEdge1() throws Exception {
|
||||
LogUtil.verifyLogEntries("c2/hierarchical/minifi-edge1/expected.json", docker.containers().container("minifi-edge1"));
|
||||
Path csvToJsonDir = resourceDirectory.resolve("standalone").resolve("CsvToJson");
|
||||
Files.copy(csvToJsonDir.resolve("CsvToJson.json"), minifiEdge1Version2);
|
||||
LogUtil.verifyLogEntries("standalone/CsvToJson/expected.json", docker.containers().container("minifi-edge1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMiNiFiEdge2() throws Exception {
|
||||
LogUtil.verifyLogEntries("c2/hierarchical/minifi-edge2/expected.json", docker.containers().container("minifi-edge2"));
|
||||
Path csvToJsonDir = resourceDirectory.resolve("standalone").resolve("CsvToJson");
|
||||
Files.copy(csvToJsonDir.resolve("CsvToJson.json"), minifiEdge2Version2);
|
||||
LogUtil.verifyLogEntries("standalone/CsvToJson/expected.json", docker.containers().container("minifi-edge2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMiNiFiEdge3() throws Exception {
|
||||
LogUtil.verifyLogEntries("c2/hierarchical/minifi-edge3/expected.json", docker.containers().container("minifi-edge3"));
|
||||
Path csvToJsonDir = resourceDirectory.resolve("standalone").resolve("CsvToJson");
|
||||
Files.copy(csvToJsonDir.resolve("CsvToJson.json"), minifiEdge3Version2);
|
||||
LogUtil.verifyLogEntries("standalone/CsvToJson/expected.json", docker.containers().container("minifi-edge3"));
|
||||
}
|
||||
}
|
|
@ -207,11 +207,6 @@ limitations under the License.
|
|||
<artifactId>nifi-runtime</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-tls</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-jetty-configuration</artifactId>
|
||||
|
|
|
@ -30,7 +30,7 @@ NOTE: This document is provided with no warranty. All steps have been evaluated
|
|||
|
||||
== Installing Apache NiFi
|
||||
|
||||
Apache NiFi is an easy to use, powerful, and reliable system to process and distribute data. It supports powerful and scalable directed graphs of data routing, transformation, and system mediation logic. In addition to NiFi, there is the NiFi Toolkit, a collection of command-line tools which help perform administrative tasks such as interacting with remote services, generating TLS certificates, managing nodes in a cluster, and encrypting sensitive configuration values.
|
||||
Apache NiFi is an easy to use, powerful, and reliable system to process and distribute data. It supports powerful and scalable directed graphs of data routing, transformation, and system mediation logic. In addition to NiFi, there is the NiFi Toolkit, a collection of command-line tools which help perform administrative tasks such as interacting with remote services, managing nodes in a cluster, and encrypting sensitive configuration values.
|
||||
|
||||
|=======================================================================================================================
|
||||
|Description | Instructions on downloading the Apache NiFi application and Toolkit
|
||||
|
|
|
@ -72,19 +72,12 @@ WD="/tmp/test-keys-$(date +"%Y%m%d-%H%M%S")"
|
|||
mkdir "$WD"
|
||||
cd "$WD"
|
||||
|
||||
# copy existing CA key/cert pair to working directory, rename to default tls-toolkit names
|
||||
# copy existing CA key/cert pair to working directory, rename to default names
|
||||
cp /path/to/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/resources/keys/ca-key.pem ./nifi-key.key
|
||||
cp /path/to/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/resources/keys/ca-cert.pem ./nifi-cert.pem
|
||||
|
||||
# use NiFi Toolkit Docker image to generate new keys/certs
|
||||
docker run -v "$WD":/tmp -w /tmp apache/nifi-toolkit:latest tls-toolkit standalone \
|
||||
--hostnames proxy \
|
||||
--subjectAlternativeNames localhost \
|
||||
--nifiDnSuffix ", OU=nifi" \
|
||||
--keyStorePassword password \
|
||||
--trustStorePassword password \
|
||||
--days 9999 \
|
||||
-O
|
||||
# place new keystores / truststores
|
||||
# for more info, see the [walkthrough for securing NiFi with mTLS](https://nifi.apache.org/docs/nifi-docs/html/walkthroughs.html#securing-nifi-with-mtls)
|
||||
|
||||
# switch to output directory, create final output directory
|
||||
cd "$WD"
|
||||
|
@ -124,22 +117,18 @@ WD="/tmp/test-keys-$(date +"%Y%m%d-%H%M%S")"
|
|||
mkdir "$WD"
|
||||
cd "$WD"
|
||||
|
||||
# copy existing CA key/cert pair to working directory, rename to default tls-toolkit names
|
||||
# copy existing CA key/cert pair to working directory, rename to default names
|
||||
cp /path/to/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/resources/keys/ca-key.pem ./nifi-key.key
|
||||
cp /path/to/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/resources/keys/ca-cert.pem ./nifi-cert.pem
|
||||
|
||||
# use NiFi Toolkit Docker image to generate new keys/certs
|
||||
docker run -v "$WD":/tmp -w /tmp apache/nifi-toolkit:latest tls-toolkit standalone \
|
||||
--clientCertDn "CN=user2, OU=nifi" \
|
||||
--clientCertPassword password \
|
||||
--days 9999 \
|
||||
-O
|
||||
# place new keystores / truststores
|
||||
# for more info, see the [walkthrough for securing NiFi with mTLS](https://nifi.apache.org/docs/nifi-docs/html/walkthroughs.html#securing-nifi-with-mtls)
|
||||
|
||||
# switch to output directory, create final output directory
|
||||
cd "$WD"
|
||||
mkdir keys
|
||||
|
||||
# transform tls-toolkit output to final output
|
||||
# transform keystores to final output
|
||||
keytool -importkeystore \
|
||||
-srckeystore CN=user2_OU=nifi.p12 -srcstoretype PKCS12 -srcstorepass password -srcalias nifi-key \
|
||||
-destkeystore keys/user2-ks.jks -deststoretype JKS -deststorepass password -destalias user2-key
|
||||
|
@ -185,18 +174,8 @@ WD="/tmp/test-keys-$(date +"%Y%m%d-%H%M%S")"
|
|||
mkdir "$WD"
|
||||
cd "$WD"
|
||||
|
||||
# use NiFi Toolkit Docker image to generate new keys/certs
|
||||
docker run -v "$WD":/tmp -w /tmp apache/nifi-toolkit:latest tls-toolkit standalone \
|
||||
--certificateAuthorityHostname "Test CA (Do Not Trust)" \
|
||||
--hostnames registry \
|
||||
--subjectAlternativeNames localhost \
|
||||
--nifiDnSuffix ", OU=nifi" \
|
||||
--keyStorePassword password \
|
||||
--trustStorePassword password \
|
||||
--clientCertDn "CN=user1, OU=nifi" \
|
||||
--clientCertPassword password \
|
||||
--days 9999 \
|
||||
-O
|
||||
# place new keystores / truststores
|
||||
# for more info, see the [walkthrough for securing NiFi with mTLS](https://nifi.apache.org/docs/nifi-docs/html/walkthroughs.html#securing-nifi-with-mtls)
|
||||
|
||||
# switch to output directory, create final output directory
|
||||
cd "$WD"
|
||||
|
|
|
@ -48,9 +48,6 @@ RUN chmod -R +x ${NIFI_TOOLKIT_BASE_DIR}/scripts/*.sh \
|
|||
|
||||
USER nifi
|
||||
|
||||
# Default port for TLS Toolkit CA Server
|
||||
EXPOSE 9443
|
||||
|
||||
WORKDIR ${NIFI_TOOLKIT_HOME}
|
||||
|
||||
# Startup NiFi
|
||||
|
|
|
@ -53,9 +53,6 @@ RUN chmod -R +x ${NIFI_TOOLKIT_BASE_DIR}/scripts/*.sh \
|
|||
|
||||
USER nifi
|
||||
|
||||
# Default port for TLS Toolkit CA Server
|
||||
EXPOSE 9443
|
||||
|
||||
WORKDIR ${NIFI_TOOLKIT_HOME}
|
||||
|
||||
# Startup NiFi
|
||||
|
|
|
@ -15,6 +15,3 @@ test 2 -eq $? || exit 1
|
|||
|
||||
docker run --rm $IMAGE cli invalid 1>/dev/null 2>&1
|
||||
test 255 -eq $? || exit 1
|
||||
|
||||
docker run --rm $IMAGE tls-toolkit invalid 1>/dev/null 2>&1
|
||||
test 2 -eq $? || exit 1
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -exuo pipefail
|
||||
|
||||
VERSION=$1
|
||||
IMAGE=apache/nifi-toolkit:${VERSION}
|
||||
CONTAINER=nifi-toolkit-$VERSION-tls-toolkit-integration-test
|
||||
|
||||
TOKEN=D40F6B95-801F-4800-A1E1-A9FCC712E0BD
|
||||
|
||||
trap " { docker rm -f $CONTAINER ; } " EXIT
|
||||
|
||||
echo "Starting CA server using the tls-toolkit server command"
|
||||
docker run -d --name $CONTAINER $IMAGE tls-toolkit server -t $TOKEN -c $CONTAINER
|
||||
|
||||
echo "Requesting client certificate using the tls-toolkit client command"
|
||||
docker run --rm --link $CONTAINER $IMAGE tls-toolkit client -t $TOKEN -c $CONTAINER
|
|
@ -28,7 +28,6 @@ language governing permissions and limitations under the License. -->
|
|||
<excludes combine.children="append">
|
||||
<exclude>src/main/resources/conf/config-client.json</exclude>
|
||||
<exclude>src/main/resources/conf/config-server.json</exclude>
|
||||
<exclude>docker/tests/tls-toolkit.sh</exclude>
|
||||
<exclude>docker/tests/exit-codes.sh</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
|
@ -63,11 +62,6 @@ language governing permissions and limitations under the License. -->
|
|||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-tls</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-encrypt-config</artifactId>
|
||||
|
@ -199,19 +193,6 @@ language governing permissions and limitations under the License. -->
|
|||
<executable>${project.basedir}/docker/tests/exit-codes.sh</executable>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>Docker integration tests - tls-toolkit</id>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>
|
||||
<argument>${project.version}-maven</argument>
|
||||
</arguments>
|
||||
<executable>${project.basedir}/docker/tests/tls-toolkit.sh</executable>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
@ -234,10 +215,6 @@ language governing permissions and limitations under the License. -->
|
|||
<id>Docker integration tests - exit codes</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>Docker integration tests - tls-toolkit</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
@ -261,10 +238,6 @@ language governing permissions and limitations under the License. -->
|
|||
<id>Docker integration tests - exit codes</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>Docker integration tests - tls-toolkit</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
@echo off
|
||||
rem
|
||||
rem Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
rem contributor license agreements. See the NOTICE file distributed with
|
||||
rem this work for additional information regarding copyright ownership.
|
||||
rem The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
rem (the "License"); you may not use this file except in compliance with
|
||||
rem the License. You may obtain a copy of the License at
|
||||
rem
|
||||
rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
rem
|
||||
rem Unless required by applicable law or agreed to in writing, software
|
||||
rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
rem See the License for the specific language governing permissions and
|
||||
rem limitations under the License.
|
||||
rem
|
||||
|
||||
rem Use JAVA_HOME if it's set; otherwise, just use java
|
||||
|
||||
if "%JAVA_HOME%" == "" goto noJavaHome
|
||||
if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
|
||||
set JAVA_EXE=%JAVA_HOME%\bin\java.exe
|
||||
goto startConfig
|
||||
|
||||
:noJavaHome
|
||||
echo The JAVA_HOME environment variable is not defined correctly.
|
||||
echo Instead the PATH will be used to find the java executable.
|
||||
echo.
|
||||
set JAVA_EXE=java
|
||||
goto startConfig
|
||||
|
||||
:startConfig
|
||||
set LIB_DIR=%~dp0..\classpath;%~dp0..\lib
|
||||
|
||||
if "%JAVA_OPTS%" == "" set JAVA_OPTS=-Xms128m -Xmx256m
|
||||
|
||||
SET JAVA_PARAMS=-cp %LIB_DIR%\* %JAVA_OPTS% org.apache.nifi.toolkit.tls.TlsToolkitMain
|
||||
|
||||
cmd.exe /C ""%JAVA_EXE%" %JAVA_PARAMS% %* ""
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
# Script structure inspired from Apache Karaf and other Apache projects with similar startup approaches
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
NIFI_TOOLKIT_HOME=$(cd "${SCRIPT_DIR}" && cd .. && pwd)
|
||||
PROGNAME=$(basename "$0")
|
||||
|
||||
|
||||
warn() {
|
||||
echo "${PROGNAME}: $*"
|
||||
}
|
||||
|
||||
die() {
|
||||
warn "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
detectOS() {
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false;
|
||||
aix=false;
|
||||
os400=false;
|
||||
darwin=false;
|
||||
case "$(uname)" in
|
||||
CYGWIN*)
|
||||
cygwin=true
|
||||
;;
|
||||
AIX*)
|
||||
aix=true
|
||||
;;
|
||||
OS400*)
|
||||
os400=true
|
||||
;;
|
||||
Darwin)
|
||||
darwin=true
|
||||
;;
|
||||
esac
|
||||
# For AIX, set an environment variable
|
||||
if ${aix}; then
|
||||
export LDR_CNTRL=MAXDATA=0xB0000000@DSA
|
||||
echo ${LDR_CNTRL}
|
||||
fi
|
||||
}
|
||||
|
||||
locateJava() {
|
||||
# Setup the Java Virtual Machine
|
||||
if $cygwin ; then
|
||||
[ -n "${JAVA}" ] && JAVA=$(cygpath --unix "${JAVA}")
|
||||
[ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --unix "${JAVA_HOME}")
|
||||
fi
|
||||
|
||||
if [ "x${JAVA}" = "x" ] && [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=$(java-config --jre-home)
|
||||
fi
|
||||
if [ "x${JAVA}" = "x" ]; then
|
||||
if [ "x${JAVA_HOME}" != "x" ]; then
|
||||
if [ ! -d "${JAVA_HOME}" ]; then
|
||||
die "JAVA_HOME is not valid: ${JAVA_HOME}"
|
||||
fi
|
||||
JAVA="${JAVA_HOME}/bin/java"
|
||||
else
|
||||
warn "JAVA_HOME not set; results may vary"
|
||||
JAVA=$(type java)
|
||||
JAVA=$(expr "${JAVA}" : '.* \(/.*\)$')
|
||||
if [ "x${JAVA}" = "x" ]; then
|
||||
die "java command not found"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
init() {
|
||||
# Determine if there is special OS handling we must perform
|
||||
detectOS
|
||||
|
||||
# Locate the Java VM to execute
|
||||
locateJava "$1"
|
||||
}
|
||||
|
||||
run() {
|
||||
LIBS="${NIFI_TOOLKIT_HOME}/lib/*"
|
||||
|
||||
sudo_cmd_prefix=""
|
||||
if $cygwin; then
|
||||
NIFI_TOOLKIT_HOME=$(cygpath --path --windows "${NIFI_TOOLKIT_HOME}")
|
||||
CLASSPATH="$NIFI_TOOLKIT_HOME/classpath;$(cygpath --path --windows "${LIBS}")"
|
||||
else
|
||||
CLASSPATH="$NIFI_TOOLKIT_HOME/classpath:${LIBS}"
|
||||
fi
|
||||
|
||||
export JAVA_HOME="$JAVA_HOME"
|
||||
export NIFI_TOOLKIT_HOME="$NIFI_TOOLKIT_HOME"
|
||||
|
||||
umask 0077
|
||||
exec "${JAVA}" -cp "${CLASSPATH}" ${JAVA_OPTS:--Xms128m -Xmx256m} org.apache.nifi.toolkit.tls.TlsToolkitMain "$@"
|
||||
}
|
||||
|
||||
|
||||
init "$1"
|
||||
run "$@"
|
|
@ -65,17 +65,6 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-tls</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
|
|
|
@ -35,8 +35,8 @@ import org.apache.nifi.flow.encryptor.JsonFlowEncryptor
|
|||
import org.apache.nifi.properties.scheme.ProtectionScheme
|
||||
import org.apache.nifi.properties.scheme.StandardProtectionScheme
|
||||
import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode
|
||||
import org.apache.nifi.toolkit.commandline.CommandLineParseException
|
||||
import org.apache.nifi.toolkit.commandline.ExitCode
|
||||
import org.apache.nifi.util.NiFiBootstrapUtils
|
||||
import org.apache.nifi.util.NiFiProperties
|
||||
import org.apache.nifi.util.console.TextDevice
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.nifi.toolkit.tls.commandLine;
|
||||
package org.apache.nifi.toolkit.commandline;
|
||||
|
||||
/**
|
||||
* Exception for errors while parsing the command line
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.nifi.toolkit.tls.commandLine;
|
||||
package org.apache.nifi.toolkit.commandline;
|
||||
|
||||
/**
|
||||
* Possible exit codes
|
|
@ -21,7 +21,6 @@ import org.apache.commons.cli.CommandLineParser
|
|||
import org.apache.commons.cli.DefaultParser
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
|
||||
import org.apache.nifi.util.NiFiProperties
|
||||
import org.apache.nifi.util.console.TextDevice
|
||||
import org.apache.nifi.util.console.TextDevices
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-toolkit-tls</artifactId>
|
||||
<description>Tooling to make tls configuration easier</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-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-cert-builder</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-resources</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<classifier>resources</classifier>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.inject</groupId>
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-resources</artifactId>
|
||||
<type>zip</type>
|
||||
<version>${project.version}</version>
|
||||
<classifier>resources</classifier>
|
||||
<overWrite>true</overWrite>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
<includes>**/nifi.properties</includes>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.rat</groupId>
|
||||
<artifactId>apache-rat-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes combine.children="append">
|
||||
<exclude>src/test/resources/rootCert.crt</exclude>
|
||||
<exclude>src/test/resources/rootCert.key</exclude>
|
||||
<exclude>src/test/resources/rootCert-pkcs8.key</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -1,117 +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.toolkit.tls;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
|
||||
import org.apache.nifi.toolkit.tls.service.client.TlsCertificateAuthorityClientCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.service.server.TlsCertificateAuthorityServiceCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.status.TlsToolkitGetStatusCommandLine;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Command line entry point that looks through a map of possible services to delegate to
|
||||
*/
|
||||
public class TlsToolkitMain {
|
||||
public static final String DESCRIPTION = "DESCRIPTION";
|
||||
public static final String UNABLE_TO_GET_DESCRIPTION = "Unable to get description. (";
|
||||
private final Map<String, Class<?>> mainMap;
|
||||
|
||||
public TlsToolkitMain() {
|
||||
mainMap = new LinkedHashMap<>();
|
||||
mainMap.put("standalone", TlsToolkitStandaloneCommandLine.class);
|
||||
mainMap.put("server", TlsCertificateAuthorityServiceCommandLine.class);
|
||||
mainMap.put("client", TlsCertificateAuthorityClientCommandLine.class);
|
||||
mainMap.put("status", TlsToolkitGetStatusCommandLine.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callthrough to doMain
|
||||
*
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
new TlsToolkitMain().doMain(args);
|
||||
}
|
||||
|
||||
private <T> T printUsageAndExit(String message, ExitCode exitCode) {
|
||||
System.out.println(message);
|
||||
System.out.println();
|
||||
System.out.println("Usage: tls-toolkit service [-h] [args]");
|
||||
System.out.println();
|
||||
System.out.println("Services:");
|
||||
mainMap.forEach((s, aClass) -> System.out.println(" " + s + ": " + getDescription(aClass)));
|
||||
System.out.println();
|
||||
System.exit(exitCode.ordinal());
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getDescription(Class<?> clazz) {
|
||||
try {
|
||||
Field declaredField = clazz.getDeclaredField(DESCRIPTION);
|
||||
return String.valueOf(declaredField.get(null));
|
||||
} catch (Exception e) {
|
||||
return UNABLE_TO_GET_DESCRIPTION + e.getMessage() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, Class<?>> getMainMap() {
|
||||
return mainMap;
|
||||
}
|
||||
|
||||
protected Method getMain(String service) {
|
||||
Class<?> mainClass = mainMap.get(service);
|
||||
if (mainClass == null) {
|
||||
printUsageAndExit("Unknown service: " + service, ExitCode.INVALID_ARGS);
|
||||
}
|
||||
|
||||
try {
|
||||
return mainClass.getDeclaredMethod("main", String[].class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return printUsageAndExit("Service " + service + " is missing main method.", ExitCode.SERVICE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the main of the relevant service
|
||||
*
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public void doMain(String[] args) {
|
||||
if (args.length < 1) {
|
||||
printUsageAndExit("Expected at least a service argument.", ExitCode.INVALID_ARGS);
|
||||
}
|
||||
|
||||
String service = args[0].toLowerCase();
|
||||
|
||||
try {
|
||||
getMain(service).invoke(null, (Object) Arrays.copyOfRange(args, 1, args.length, String[].class));
|
||||
} catch (IllegalAccessException e) {
|
||||
printUsageAndExit("Service " + service + " has invalid main method.", ExitCode.SERVICE_ERROR);
|
||||
} catch (InvocationTargetException e) {
|
||||
printUsageAndExit("Service " + service + " error: " + e.getCause().getMessage(), ExitCode.SERVICE_ERROR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,112 +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.toolkit.tls.commandLine;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.nifi.toolkit.tls.TlsToolkitMain;
|
||||
|
||||
public abstract class BaseCommandLine {
|
||||
|
||||
public static final String HELP_ARG = "help";
|
||||
public static final String JAVA_HOME = "JAVA_HOME";
|
||||
public static final String NIFI_TOOLKIT_HOME = "NIFI_TOOLKIT_HOME";
|
||||
public static final String FOOTER = new StringBuilder(System.lineSeparator()).append("Java home: ")
|
||||
.append(System.getenv(JAVA_HOME)).append(System.lineSeparator()).append("NiFi Toolkit home: ").append(System.getenv(NIFI_TOOLKIT_HOME)).toString();
|
||||
|
||||
private final Options options;
|
||||
private final String header;
|
||||
|
||||
public BaseCommandLine(String header) {
|
||||
this.header = System.lineSeparator() + header + System.lineSeparator() + System.lineSeparator();
|
||||
this.options = new Options();
|
||||
this.options.addOption("h", HELP_ARG, false, "Print help and exit.");
|
||||
}
|
||||
|
||||
protected void addOptionWithArg(String arg, String longArg, String description) {
|
||||
addOptionWithArg(arg, longArg, description, null);
|
||||
}
|
||||
|
||||
protected void addOptionNoArg(String arg, String longArg, String description) {
|
||||
options.addOption(arg, longArg, false, description);
|
||||
}
|
||||
|
||||
protected void addOptionWithArg(String arg, String longArg, String description, Object defaultVal) {
|
||||
String fullDescription = description;
|
||||
if (defaultVal != null) {
|
||||
fullDescription += " (default: " + defaultVal + ")";
|
||||
}
|
||||
options.addOption(arg, longArg, true, fullDescription);
|
||||
}
|
||||
|
||||
public void printUsage(String errorMessage) {
|
||||
if (errorMessage != null) {
|
||||
System.out.println(errorMessage);
|
||||
System.out.println();
|
||||
}
|
||||
HelpFormatter helpFormatter = new HelpFormatter();
|
||||
helpFormatter.setWidth(160);
|
||||
helpFormatter.printHelp(TlsToolkitMain.class.getCanonicalName(), header, options, FOOTER, true);
|
||||
}
|
||||
|
||||
protected <T> T printUsageAndThrow(String errorMessage, ExitCode exitCode) throws CommandLineParseException {
|
||||
printUsage(errorMessage);
|
||||
throw new CommandLineParseException(errorMessage, exitCode);
|
||||
}
|
||||
|
||||
protected int getIntValue(CommandLine commandLine, String arg, int defaultVal) throws CommandLineParseException {
|
||||
try {
|
||||
return Integer.parseInt(commandLine.getOptionValue(arg, Integer.toString(defaultVal)));
|
||||
} catch (NumberFormatException e) {
|
||||
return printUsageAndThrow("Expected integer for " + arg + " argument. (" + e.getMessage() + ")", ExitCode.ERROR_PARSING_INT_ARG);
|
||||
}
|
||||
}
|
||||
|
||||
protected CommandLine doParse(String[] args) throws CommandLineParseException {
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine commandLine;
|
||||
try {
|
||||
commandLine = parser.parse(options, args);
|
||||
if (commandLine.hasOption(HELP_ARG)) {
|
||||
return printUsageAndThrow(null, ExitCode.HELP);
|
||||
}
|
||||
postParse(commandLine);
|
||||
} catch (ParseException e) {
|
||||
return printUsageAndThrow("Error parsing command line. (" + e.getMessage() + ")", ExitCode.ERROR_PARSING_COMMAND_LINE);
|
||||
}
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
protected void postParse(CommandLine commandLine) throws CommandLineParseException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the command line arguments
|
||||
*
|
||||
* @param args the command line arguments
|
||||
* @throws CommandLineParseException if the arguments cannot be parsed
|
||||
*/
|
||||
public void parse(String... args) throws CommandLineParseException {
|
||||
doParse(args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,157 +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.toolkit.tls.commandLine;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.nifi.security.util.KeystoreType;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class with common CLI parsing functionality as well as arguments shared by multiple entry points
|
||||
*/
|
||||
public abstract class BaseTlsToolkitCommandLine extends BaseCommandLine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BaseTlsToolkitCommandLine.class);
|
||||
|
||||
public static final String KEY_SIZE_ARG = "keySize";
|
||||
public static final String KEY_ALGORITHM_ARG = "keyAlgorithm";
|
||||
public static final String CERTIFICATE_AUTHORITY_HOSTNAME_ARG = "certificateAuthorityHostname";
|
||||
public static final String DAYS_ARG = "days";
|
||||
public static final String KEY_STORE_TYPE_ARG = "keyStoreType";
|
||||
public static final String SIGNING_ALGORITHM_ARG = "signingAlgorithm";
|
||||
public static final String DN_ARG = "dn";
|
||||
public static final String DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG = "differentKeyAndKeystorePasswords";
|
||||
|
||||
public static final String KEYSTORE = "keystore.";
|
||||
public static final String TRUSTSTORE = "truststore.";
|
||||
|
||||
private int keySize;
|
||||
private String keyAlgorithm;
|
||||
private String certificateAuthorityHostname;
|
||||
private String keyStoreType;
|
||||
private int days;
|
||||
private String signingAlgorithm;
|
||||
private boolean differentPasswordForKeyAndKeystore;
|
||||
|
||||
public BaseTlsToolkitCommandLine(String header) {
|
||||
super(header);
|
||||
if (shouldAddDaysArg()) {
|
||||
addOptionWithArg("d", DAYS_ARG, "Number of days issued certificate should be valid for.", TlsConfig.DEFAULT_DAYS);
|
||||
}
|
||||
addOptionWithArg("T", KEY_STORE_TYPE_ARG, "The type of keyStores to generate.", getKeyStoreTypeDefault());
|
||||
addOptionWithArg("c", CERTIFICATE_AUTHORITY_HOSTNAME_ARG, "Hostname of NiFi Certificate Authority", TlsConfig.DEFAULT_HOSTNAME);
|
||||
addOptionWithArg("a", KEY_ALGORITHM_ARG, "Algorithm to use for generated keys.", TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
|
||||
addOptionWithArg("k", KEY_SIZE_ARG, "Number of bits for generated keys.", TlsConfig.DEFAULT_KEY_SIZE);
|
||||
if (shouldAddSigningAlgorithmArg()) {
|
||||
addOptionWithArg("s", SIGNING_ALGORITHM_ARG, "Algorithm to use for signing certificates.", TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
}
|
||||
addOptionNoArg("g", DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG, "Use different generated password for the key and the keyStore.");
|
||||
}
|
||||
|
||||
protected String getKeyStoreTypeDefault() {
|
||||
return TlsConfig.DEFAULT_KEY_STORE_TYPE;
|
||||
}
|
||||
|
||||
protected boolean shouldAddSigningAlgorithmArg() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean shouldAddDaysArg() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bits used when generating KeyPairs
|
||||
*
|
||||
* @return the number of bits used when generating KeyPairs
|
||||
*/
|
||||
public int getKeySize() {
|
||||
return keySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the algorithm used when generating KeyPairs
|
||||
*
|
||||
* @return the algorithm used when generating KeyPairs
|
||||
*/
|
||||
public String getKeyAlgorithm() {
|
||||
return keyAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CA Hostname
|
||||
*
|
||||
* @return the CA Hostname
|
||||
*/
|
||||
public String getCertificateAuthorityHostname() {
|
||||
return certificateAuthorityHostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type to use for KeyStores
|
||||
*
|
||||
* @return the type to use for KeyStores
|
||||
*/
|
||||
public String getKeyStoreType() {
|
||||
return keyStoreType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of Certificates should be valid for
|
||||
*
|
||||
* @return the number of Certificates should be valid for
|
||||
*/
|
||||
public int getDays() {
|
||||
return days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signing algorithm to use for cryptographic operations
|
||||
*
|
||||
* @return the signing algorithm to use for cryptographic operations
|
||||
*/
|
||||
public String getSigningAlgorithm() {
|
||||
return signingAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if different passwords should be used for KeyStore and individual Key entries
|
||||
*
|
||||
* @return true if different passwords should be used for KeyStore and individual Key entries
|
||||
*/
|
||||
public boolean differentPasswordForKeyAndKeystore() {
|
||||
return differentPasswordForKeyAndKeystore;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postParse(CommandLine commandLine) throws CommandLineParseException {
|
||||
certificateAuthorityHostname = commandLine.getOptionValue(CERTIFICATE_AUTHORITY_HOSTNAME_ARG, TlsConfig.DEFAULT_HOSTNAME);
|
||||
days = getIntValue(commandLine, DAYS_ARG, TlsConfig.DEFAULT_DAYS);
|
||||
keySize = getIntValue(commandLine, KEY_SIZE_ARG, TlsConfig.DEFAULT_KEY_SIZE);
|
||||
keyAlgorithm = commandLine.getOptionValue(KEY_ALGORITHM_ARG, TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
|
||||
keyStoreType = commandLine.getOptionValue(KEY_STORE_TYPE_ARG, getKeyStoreTypeDefault());
|
||||
if (KeystoreType.PKCS12.toString().equalsIgnoreCase(keyStoreType)) {
|
||||
logger.info("Command line argument --" + KEY_STORE_TYPE_ARG + "=" + keyStoreType + " only applies to keystore, recommended truststore type of " + KeystoreType.JKS.toString() +
|
||||
" unaffected.");
|
||||
}
|
||||
signingAlgorithm = commandLine.getOptionValue(SIGNING_ALGORITHM_ARG, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
differentPasswordForKeyAndKeystore = commandLine.hasOption(DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +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.toolkit.tls.configuration;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.net.URI;
|
||||
|
||||
public class GetStatusConfig {
|
||||
|
||||
private final URI url;
|
||||
|
||||
private final SSLContext sslContext;
|
||||
|
||||
public GetStatusConfig(final URI url, final SSLContext sslContext) {
|
||||
this.url = url;
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
public URI getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public SSLContext getSslContext() {
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,118 +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.toolkit.tls.configuration;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Contains an instance identifier, a number that either corresponds to the instance identifier or to the global order and relevant passwords
|
||||
*/
|
||||
public class InstanceDefinition {
|
||||
private final InstanceIdentifier instanceIdentifier;
|
||||
private final int number;
|
||||
private final String keyStorePassword;
|
||||
private final String keyPassword;
|
||||
private final String trustStorePassword;
|
||||
|
||||
public InstanceDefinition(InstanceIdentifier instanceIdentifier, int number, String keyStorePassword, String keyPassword, String trustStorePassword) {
|
||||
this.number = number;
|
||||
this.instanceIdentifier = instanceIdentifier;
|
||||
this.keyStorePassword = keyStorePassword;
|
||||
this.keyPassword = keyPassword;
|
||||
this.trustStorePassword = trustStorePassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of instance definitions
|
||||
*
|
||||
* @param fullHostNameExpressions the host expressions defining the global order (null if not relevant)
|
||||
* @param currentHostnameExpressions the host expressions to create instance definitions for
|
||||
* @param keyStorePasswords a supplier for keyStorePasswords
|
||||
* @param keyPasswords a supplier for keyPasswords
|
||||
* @param trustStorePasswords a supplier for trustStorePasswords
|
||||
* @return a list of instance definitions
|
||||
*/
|
||||
public static List<InstanceDefinition> createDefinitions(Stream<String> fullHostNameExpressions, Stream<String> currentHostnameExpressions, Supplier<String> keyStorePasswords,
|
||||
Supplier<String> keyPasswords, Supplier<String> trustStorePasswords) {
|
||||
if (fullHostNameExpressions == null) {
|
||||
return InstanceIdentifier.createIdentifiers(currentHostnameExpressions).map(id -> createDefinition(id, id.getNumber(), keyStorePasswords, keyPasswords, trustStorePasswords))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
Map<InstanceIdentifier, Integer> orderMap = InstanceIdentifier.createOrderMap(fullHostNameExpressions);
|
||||
return InstanceIdentifier.createIdentifiers(currentHostnameExpressions).map(id -> {
|
||||
Integer number = orderMap.get(id);
|
||||
if (number == null) {
|
||||
throw new IllegalArgumentException("Unable to find " + id.getHostname() + " in specified " + TlsToolkitStandaloneCommandLine.GLOBAL_PORT_SEQUENCE_ARG + " expression(s).");
|
||||
}
|
||||
return createDefinition(id, number, keyStorePasswords, keyPasswords, trustStorePasswords);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
protected static InstanceDefinition createDefinition(InstanceIdentifier instanceIdentifier, int number, Supplier<String> keyStorePasswords, Supplier<String> keyPasswords,
|
||||
Supplier<String> trustStorePasswords) {
|
||||
String keyStorePassword = null;
|
||||
if (keyStorePasswords != null) {
|
||||
keyStorePassword = keyStorePasswords.get();
|
||||
}
|
||||
|
||||
String keyPassword;
|
||||
if (keyPasswords == null) {
|
||||
keyPassword = keyStorePassword;
|
||||
} else {
|
||||
keyPassword = keyPasswords.get();
|
||||
}
|
||||
|
||||
String trustStorePassword = null;
|
||||
if (trustStorePasswords != null) {
|
||||
trustStorePassword = trustStorePasswords.get();
|
||||
}
|
||||
|
||||
return new InstanceDefinition(instanceIdentifier, number, keyStorePassword, keyPassword, trustStorePassword);
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return instanceIdentifier.getHostname();
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public String getKeyStorePassword() {
|
||||
return keyStorePassword;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return keyPassword;
|
||||
}
|
||||
|
||||
public String getTrustStorePassword() {
|
||||
return trustStorePassword;
|
||||
}
|
||||
|
||||
public InstanceIdentifier getInstanceIdentifier() {
|
||||
return instanceIdentifier;
|
||||
}
|
||||
}
|
|
@ -1,176 +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.toolkit.tls.configuration;
|
||||
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Each instance is uniquely identified by its hostname and instance number
|
||||
*/
|
||||
public class InstanceIdentifier {
|
||||
public static final Comparator<InstanceIdentifier> HOST_IDENTIFIER_COMPARATOR = (o1, o2) -> {
|
||||
int i = o1.getHostname().compareTo(o2.getHostname());
|
||||
if (i == 0) {
|
||||
return o1.getNumber() - o2.getNumber();
|
||||
}
|
||||
return i;
|
||||
};
|
||||
private static final Pattern RANGE_PATTERN = Pattern.compile("^[0-9]+(-[0-9]+)?$");
|
||||
private final String hostname;
|
||||
private final int number;
|
||||
|
||||
public InstanceIdentifier(String hostname, int number) {
|
||||
this.hostname = hostname;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a map that can be used to deterministically assign global instance numbers
|
||||
*
|
||||
* @param hostnameExpressions the hostname expressions to expand
|
||||
* @return a map that can be used to deterministically assign global instance numbers
|
||||
*/
|
||||
public static Map<InstanceIdentifier, Integer> createOrderMap(Stream<String> hostnameExpressions) {
|
||||
List<InstanceIdentifier> instanceIdentifiers = createIdentifiers(hostnameExpressions).sorted(HOST_IDENTIFIER_COMPARATOR).collect(Collectors.toList());
|
||||
Map<InstanceIdentifier, Integer> result = new HashMap<>();
|
||||
for (int i = 0; i < instanceIdentifiers.size(); i++) {
|
||||
result.put(instanceIdentifiers.get(i), i + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stream of hostname identifiers from a stream of hostname expressions
|
||||
*
|
||||
* @param hostnameExpressions the hostname expressions
|
||||
* @return the hostname identifiers
|
||||
*/
|
||||
public static Stream<InstanceIdentifier> createIdentifiers(Stream<String> hostnameExpressions) {
|
||||
return hostnameExpressions.flatMap(hostnameExpression -> extractHostnames(hostnameExpression).flatMap(hostname -> {
|
||||
ExtractedRange extractedRange = new ExtractedRange(hostname, '(', ')');
|
||||
if (extractedRange.range == null) {
|
||||
return Stream.of(new InstanceIdentifier(hostname, 1));
|
||||
}
|
||||
if (!StringUtils.isEmpty(extractedRange.afterClose)) {
|
||||
throw new IllegalArgumentException("No characters expected after )");
|
||||
}
|
||||
return extractedRange.range.map(numString -> new InstanceIdentifier(extractedRange.beforeOpen, Integer.parseInt(numString)));
|
||||
}));
|
||||
}
|
||||
|
||||
protected static Stream<String> extractHostnames(String hostname) {
|
||||
ExtractedRange extractedRange = new ExtractedRange(hostname, '[', ']');
|
||||
if (extractedRange.range == null) {
|
||||
return Stream.of(hostname);
|
||||
}
|
||||
return extractedRange.range.map(s -> extractedRange.beforeOpen + s + extractedRange.afterClose).flatMap(InstanceIdentifier::extractHostnames);
|
||||
}
|
||||
|
||||
private static Stream<String> extractRange(String range) {
|
||||
if (!RANGE_PATTERN.matcher(range).matches()) {
|
||||
throw new IllegalArgumentException("Expected either one number or two separated by a single hyphen");
|
||||
}
|
||||
String[] split = range.split("-");
|
||||
if (split.length == 1) {
|
||||
String prefix = "1-";
|
||||
if (split[0].charAt(0) == '0') {
|
||||
prefix = String.format("%0" + split[0].length() + "d-", 1);
|
||||
}
|
||||
return extractRange(prefix + split[0]);
|
||||
} else {
|
||||
int baseLength = split[0].length();
|
||||
int low = Integer.parseInt(split[0]);
|
||||
String padding = split[0].substring(0, split[0].length() - Integer.toString(low).length());
|
||||
int high = Integer.parseInt(split[1]);
|
||||
IntStream intRange = IntStream.range(Math.min(low, high), Math.max(low, high) + 1)
|
||||
.map(i -> high < low ? high - i + low : i);
|
||||
return intRange.mapToObj(i -> {
|
||||
String s = Integer.toString(i);
|
||||
int length = s.length();
|
||||
if (length >= baseLength) {
|
||||
return s;
|
||||
} else {
|
||||
return padding.substring(0, baseLength - length) + s;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
InstanceIdentifier that = (InstanceIdentifier) o;
|
||||
|
||||
if (number != that.number) return false;
|
||||
return hostname != null ? hostname.equals(that.hostname) : that.hostname == null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = hostname != null ? hostname.hashCode() : 0;
|
||||
result = 31 * result + number;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class ExtractedRange {
|
||||
private final String beforeOpen;
|
||||
private final Stream<String> range;
|
||||
private final String afterClose;
|
||||
|
||||
public ExtractedRange(String string, char rangeOpen, char rangeClose) {
|
||||
int openBracket = string.indexOf(rangeOpen);
|
||||
if (openBracket >= 0) {
|
||||
int closeBracket = string.indexOf(rangeClose, openBracket);
|
||||
if (closeBracket < 0) {
|
||||
throw new IllegalArgumentException("Unable to find matching " + rangeClose + " for " + rangeOpen + " in " + string);
|
||||
}
|
||||
beforeOpen = string.substring(0, openBracket);
|
||||
if (closeBracket + 1 < string.length()) {
|
||||
afterClose = string.substring(closeBracket + 1);
|
||||
} else {
|
||||
afterClose = "";
|
||||
}
|
||||
range = extractRange(string.substring(openBracket + 1, closeBracket));
|
||||
} else {
|
||||
beforeOpen = string;
|
||||
range = null;
|
||||
afterClose = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +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.toolkit.tls.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
|
||||
|
||||
/**
|
||||
* Configuration object of the standalone service
|
||||
*/
|
||||
public class StandaloneConfig extends TlsConfig {
|
||||
private File baseDir;
|
||||
private NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
|
||||
private List<InstanceDefinition> instanceDefinitions;
|
||||
private List<String> clientDns;
|
||||
private List<String> clientPasswords;
|
||||
private boolean clientPasswordsGenerated;
|
||||
private boolean overwrite;
|
||||
private boolean splitKeystore;
|
||||
|
||||
// TODO: A lot of these fields are null and cause NPEs in {@link TlsToolkitStandalone} when not executed with expected input
|
||||
|
||||
public List<String> getClientDns() {
|
||||
return clientDns;
|
||||
}
|
||||
|
||||
public void setClientDns(List<String> clientDns) {
|
||||
this.clientDns = clientDns;
|
||||
}
|
||||
|
||||
public boolean isOverwrite() {
|
||||
return overwrite;
|
||||
}
|
||||
|
||||
public void setOverwrite(boolean overwrite) {
|
||||
this.overwrite = overwrite;
|
||||
}
|
||||
|
||||
public File getBaseDir() {
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
public void setBaseDir(File baseDir) {
|
||||
this.baseDir = baseDir;
|
||||
}
|
||||
|
||||
public NiFiPropertiesWriterFactory getNiFiPropertiesWriterFactory() {
|
||||
return niFiPropertiesWriterFactory;
|
||||
}
|
||||
|
||||
public void setNiFiPropertiesWriterFactory(NiFiPropertiesWriterFactory niFiPropertiesWriterFactory) {
|
||||
this.niFiPropertiesWriterFactory = niFiPropertiesWriterFactory;
|
||||
}
|
||||
|
||||
public List<String> getClientPasswords() {
|
||||
return clientPasswords;
|
||||
}
|
||||
|
||||
public void setClientPasswords(List<String> clientPasswords) {
|
||||
this.clientPasswords = clientPasswords;
|
||||
}
|
||||
|
||||
public boolean isClientPasswordsGenerated() {
|
||||
return clientPasswordsGenerated;
|
||||
}
|
||||
|
||||
public void setClientPasswordsGenerated(boolean clientPasswordsGenerated) {
|
||||
this.clientPasswordsGenerated = clientPasswordsGenerated;
|
||||
}
|
||||
|
||||
public List<InstanceDefinition> getInstanceDefinitions() {
|
||||
return instanceDefinitions;
|
||||
}
|
||||
|
||||
public void setInstanceDefinitions(List<InstanceDefinition> instanceDefinitions) {
|
||||
this.instanceDefinitions = instanceDefinitions;
|
||||
}
|
||||
|
||||
public void setSplitKeystore(boolean splitKeystore) {
|
||||
this.splitKeystore = splitKeystore;
|
||||
}
|
||||
|
||||
public boolean isSplitKeystore() {
|
||||
return this.splitKeystore;
|
||||
}
|
||||
}
|
|
@ -1,85 +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.toolkit.tls.configuration;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.service.client.TlsCertificateSigningRequestPerformer;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Configuration object for CA client
|
||||
*/
|
||||
public class TlsClientConfig extends TlsConfig {
|
||||
private String trustStore;
|
||||
private String trustStorePassword;
|
||||
private String trustStoreType = DEFAULT_KEY_STORE_TYPE;
|
||||
|
||||
public TlsClientConfig() {
|
||||
}
|
||||
|
||||
public TlsClientConfig(TlsConfig tlsConfig) {
|
||||
setToken(tlsConfig.getToken());
|
||||
setCaHostname(tlsConfig.getCaHostname());
|
||||
setPort(tlsConfig.getPort());
|
||||
setKeyStoreType(tlsConfig.getKeyStoreType());
|
||||
setKeyPairAlgorithm(tlsConfig.getKeyPairAlgorithm());
|
||||
setKeySize(tlsConfig.getKeySize());
|
||||
setSigningAlgorithm(tlsConfig.getSigningAlgorithm());
|
||||
setDnPrefix(tlsConfig.getDnPrefix());
|
||||
setDnSuffix(tlsConfig.getDnSuffix());
|
||||
setDomainAlternativeNames(tlsConfig.getDomainAlternativeNames());
|
||||
}
|
||||
|
||||
|
||||
public String getTrustStoreType() {
|
||||
return trustStoreType;
|
||||
}
|
||||
|
||||
public void setTrustStoreType(String trustStoreType) {
|
||||
this.trustStoreType = trustStoreType;
|
||||
}
|
||||
|
||||
public String getTrustStore() {
|
||||
return trustStore;
|
||||
}
|
||||
|
||||
public void setTrustStore(String trustStore) {
|
||||
this.trustStore = trustStore;
|
||||
}
|
||||
|
||||
public String getTrustStorePassword() {
|
||||
return trustStorePassword;
|
||||
}
|
||||
|
||||
public void setTrustStorePassword(String trustStorePassword) {
|
||||
this.trustStorePassword = trustStorePassword;
|
||||
}
|
||||
|
||||
public TlsCertificateSigningRequestPerformer createCertificateSigningRequestPerformer() throws NoSuchAlgorithmException {
|
||||
return new TlsCertificateSigningRequestPerformer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initDefaults() {
|
||||
super.initDefaults();
|
||||
if (StringUtils.isEmpty(trustStoreType)) {
|
||||
trustStoreType = DEFAULT_KEY_STORE_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,237 +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.toolkit.tls.configuration;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Configuration object for CA server
|
||||
*/
|
||||
public class TlsConfig {
|
||||
public static final String DEFAULT_HOSTNAME = "localhost";
|
||||
public static final String DEFAULT_KEY_STORE_TYPE = "jks";
|
||||
public static final int DEFAULT_PORT = 9443;
|
||||
public static final int DEFAULT_DAYS = 825;
|
||||
public static final int DEFAULT_KEY_SIZE = 2048;
|
||||
public static final String DEFAULT_KEY_PAIR_ALGORITHM = "RSA";
|
||||
public static final String DEFAULT_SIGNING_ALGORITHM = "SHA256WITHRSA";
|
||||
public static final String DEFAULT_DN_PREFIX = "CN=";
|
||||
public static final String DEFAULT_DN_SUFFIX = ", OU=NIFI";
|
||||
|
||||
private int days = DEFAULT_DAYS;
|
||||
private int keySize = DEFAULT_KEY_SIZE;
|
||||
private String keyPairAlgorithm = DEFAULT_KEY_PAIR_ALGORITHM;
|
||||
private String signingAlgorithm = DEFAULT_SIGNING_ALGORITHM;
|
||||
|
||||
private String dn;
|
||||
private String keyStore;
|
||||
private String keyStoreType = DEFAULT_KEY_STORE_TYPE;
|
||||
private String keyStorePassword;
|
||||
private String keyPassword;
|
||||
private String token;
|
||||
private String caHostname = DEFAULT_HOSTNAME;
|
||||
private int port = DEFAULT_PORT;
|
||||
private String dnPrefix = DEFAULT_DN_PREFIX;
|
||||
private String dnSuffix = DEFAULT_DN_SUFFIX;
|
||||
private String additionalCACertificate = "";
|
||||
private List<String> domainAlternativeNames;
|
||||
|
||||
public String calcDefaultDn(String hostname) {
|
||||
return dnPrefix + hostname + dnSuffix;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getKeyStore() {
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
public void setKeyStore(String keyStore) {
|
||||
this.keyStore = keyStore;
|
||||
}
|
||||
|
||||
public String getKeyStoreType() {
|
||||
return keyStoreType;
|
||||
}
|
||||
|
||||
public void setKeyStoreType(String keyStoreType) {
|
||||
this.keyStoreType = keyStoreType;
|
||||
}
|
||||
|
||||
public String getKeyStorePassword() {
|
||||
return keyStorePassword;
|
||||
}
|
||||
|
||||
public void setKeyStorePassword(String keyStorePassword) {
|
||||
this.keyStorePassword = keyStorePassword;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return keyPassword;
|
||||
}
|
||||
|
||||
public void setKeyPassword(String keyPassword) {
|
||||
this.keyPassword = keyPassword;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getCaHostname() {
|
||||
return caHostname;
|
||||
}
|
||||
|
||||
public void setCaHostname(String caHostname) {
|
||||
this.caHostname = caHostname;
|
||||
}
|
||||
|
||||
public String getDn() {
|
||||
return dn;
|
||||
}
|
||||
|
||||
public void setDn(String dn) {
|
||||
this.dn = dn;
|
||||
}
|
||||
|
||||
public int getDays() {
|
||||
return days;
|
||||
}
|
||||
|
||||
public void setDays(int days) {
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
public int getKeySize() {
|
||||
return keySize;
|
||||
}
|
||||
|
||||
public void setKeySize(int keySize) {
|
||||
this.keySize = keySize;
|
||||
}
|
||||
|
||||
public String getKeyPairAlgorithm() {
|
||||
return keyPairAlgorithm;
|
||||
}
|
||||
|
||||
public void setKeyPairAlgorithm(String keyPairAlgorithm) {
|
||||
this.keyPairAlgorithm = keyPairAlgorithm;
|
||||
}
|
||||
|
||||
public String getSigningAlgorithm() {
|
||||
return signingAlgorithm;
|
||||
}
|
||||
|
||||
public void setSigningAlgorithm(String signingAlgorithm) {
|
||||
this.signingAlgorithm = signingAlgorithm;
|
||||
}
|
||||
|
||||
public String getDnPrefix() {
|
||||
return dnPrefix;
|
||||
}
|
||||
|
||||
public void setDnPrefix(String dnPrefix) {
|
||||
this.dnPrefix = dnPrefix;
|
||||
}
|
||||
|
||||
public String getDnSuffix() {
|
||||
return dnSuffix;
|
||||
}
|
||||
|
||||
public void setDnSuffix(String dnSuffix) {
|
||||
this.dnSuffix = dnSuffix;
|
||||
}
|
||||
|
||||
public void initDefaults() {
|
||||
if (days == 0) {
|
||||
days = DEFAULT_DAYS;
|
||||
}
|
||||
if (keySize == 0) {
|
||||
keySize = DEFAULT_KEY_SIZE;
|
||||
}
|
||||
if (StringUtils.isEmpty(keyPairAlgorithm)) {
|
||||
keyPairAlgorithm = DEFAULT_KEY_PAIR_ALGORITHM;
|
||||
}
|
||||
if (StringUtils.isEmpty(signingAlgorithm)) {
|
||||
signingAlgorithm = DEFAULT_SIGNING_ALGORITHM;
|
||||
}
|
||||
if (port == 0) {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
if (StringUtils.isEmpty(keyStoreType)) {
|
||||
keyStoreType = DEFAULT_KEY_STORE_TYPE;
|
||||
}
|
||||
if (StringUtils.isEmpty(caHostname)) {
|
||||
caHostname = DEFAULT_HOSTNAME;
|
||||
}
|
||||
if (StringUtils.isEmpty(dn)) {
|
||||
dn = calcDefaultDn(caHostname);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getDomainAlternativeNames() {
|
||||
return domainAlternativeNames;
|
||||
}
|
||||
|
||||
public void setDomainAlternativeNames(List<String> domainAlternativeNames) {
|
||||
this.domainAlternativeNames = domainAlternativeNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to an additional CA certificate file in PEM format which has been used to sign the CA certificate the toolkit will use.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* nifi-cert.pem [existing PEM file for intermediate CA generated by Org's IT team and signed by org-ca.pem]
|
||||
* org-ca.pem [PEM file for root CA owned by Org's IT team]
|
||||
*
|
||||
* {@code getAdditionalCACertificate() == "/path/to/org-ca.pem"}
|
||||
*
|
||||
* @return the path to this file
|
||||
*/
|
||||
public String getAdditionalCACertificate() {
|
||||
return additionalCACertificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to an additional CA certificate file in PEM format which has been used to sign the CA certificate the toolkit will use.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* nifi-cert.pem [existing PEM file for intermediate CA generated by Org's IT team and signed by org-ca.pem]
|
||||
* org-ca.pem [PEM file for root CA owned by Org's IT team]
|
||||
*
|
||||
* {@code setAdditionalCACertificate("/path/to/org-ca.pem");}
|
||||
*
|
||||
* @param additionalCACertificate the path to this file
|
||||
*/
|
||||
public void setAdditionalCACertificate(String additionalCACertificate) {
|
||||
this.additionalCACertificate = additionalCACertificate;
|
||||
}
|
||||
}
|
|
@ -1,177 +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.toolkit.tls.manager;
|
||||
|
||||
import org.apache.nifi.security.util.KeyStoreUtils;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.manager.writer.ConfigurationWriter;
|
||||
import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.PasswordUtil;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for managing KeyStores and Certificates
|
||||
*/
|
||||
public class BaseTlsManager {
|
||||
private final TlsConfig tlsConfig;
|
||||
private final PasswordUtil passwordUtil;
|
||||
private final InputStreamFactory inputStreamFactory;
|
||||
private final KeyStore keyStore;
|
||||
private final List<ConfigurationWriter<TlsConfig>> configurationWriters;
|
||||
private boolean differentKeyAndKeyStorePassword = false;
|
||||
private boolean keyStorePasswordGenerated = false;
|
||||
|
||||
public BaseTlsManager(TlsConfig tlsConfig) throws GeneralSecurityException, IOException {
|
||||
this(tlsConfig, new PasswordUtil(), FileInputStream::new);
|
||||
}
|
||||
|
||||
public BaseTlsManager(TlsConfig tlsConfig, PasswordUtil passwordUtil, InputStreamFactory inputStreamFactory) throws GeneralSecurityException, IOException {
|
||||
this.tlsConfig = tlsConfig;
|
||||
this.passwordUtil = passwordUtil;
|
||||
this.inputStreamFactory = inputStreamFactory;
|
||||
this.keyStore = loadKeystore(tlsConfig.getKeyStore(), tlsConfig.getKeyStoreType(), getKeyStorePassword());
|
||||
this.configurationWriters = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the KeyStore
|
||||
*
|
||||
* @return the KeyStore
|
||||
*/
|
||||
public KeyStore getKeyStore() {
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an entry from the KeyStore with the given alias
|
||||
*
|
||||
* @param alias the alias
|
||||
* @return an entry from the KeyStore with the given alias
|
||||
* @throws GeneralSecurityException if there is a problem retrieving the entry
|
||||
*/
|
||||
public KeyStore.Entry getEntry(String alias) throws GeneralSecurityException {
|
||||
String keyPassword = getKeyPassword();
|
||||
return keyStore.getEntry(alias, new KeyStore.PasswordProtection(keyPassword == null ? null : keyPassword.toCharArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the private key of the KeyPair to the KeyStore and returns the entry
|
||||
*
|
||||
* @param keyPair the KeyPair
|
||||
* @param alias the alias
|
||||
* @param certificates the certificate chain
|
||||
* @return the entry
|
||||
* @throws GeneralSecurityException if there is a problem performing the operation
|
||||
*/
|
||||
public KeyStore.Entry addPrivateKeyToKeyStore(KeyPair keyPair, String alias, Certificate... certificates) throws GeneralSecurityException {
|
||||
String passphrase = getKeyPassword();
|
||||
keyStore.setKeyEntry(alias, keyPair.getPrivate(), passphrase == null ? null : passphrase.toCharArray(), certificates);
|
||||
return getEntry(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag indicating whether to use a different key and keystore password
|
||||
*
|
||||
* @param differentKeyAndKeyStorePassword a flag indicating whether to use a different key and keystore password
|
||||
*/
|
||||
public void setDifferentKeyAndKeyStorePassword(boolean differentKeyAndKeyStorePassword) {
|
||||
this.differentKeyAndKeyStorePassword = differentKeyAndKeyStorePassword;
|
||||
}
|
||||
|
||||
private String getKeyPassword() {
|
||||
String result = tlsConfig.getKeyPassword();
|
||||
if (StringUtils.isEmpty(result)) {
|
||||
if (differentKeyAndKeyStorePassword) {
|
||||
result = passwordUtil.generatePassword();
|
||||
} else {
|
||||
result = getKeyStorePassword();
|
||||
}
|
||||
tlsConfig.setKeyPassword(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getKeyStorePassword() {
|
||||
String result = tlsConfig.getKeyStorePassword();
|
||||
if (StringUtils.isEmpty(result)) {
|
||||
result = passwordUtil.generatePassword();
|
||||
keyStorePasswordGenerated = true;
|
||||
tlsConfig.setKeyStorePassword(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected KeyStore loadKeystore(String keyStore, String keyStoreType, String keyStorePassword) throws GeneralSecurityException, IOException {
|
||||
KeyStore result = KeyStoreUtils.getKeyStore(keyStoreType);
|
||||
File file = new File(keyStore);
|
||||
if (file.exists()) {
|
||||
try (InputStream stream = inputStreamFactory.create(file)) {
|
||||
result.load(stream, keyStorePassword.toCharArray());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result.load(null, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the KeyStore and configuration information
|
||||
*
|
||||
* @param outputStreamFactory factory interface for creating output streams
|
||||
* @throws IOException if there is an IO problem while writing
|
||||
* @throws GeneralSecurityException if there is a security problem while writing
|
||||
*/
|
||||
public void write(OutputStreamFactory outputStreamFactory) throws IOException, GeneralSecurityException {
|
||||
String keyStorePassword = getKeyStorePassword();
|
||||
tlsConfig.setKeyStorePassword(TlsHelper.writeKeyStore(keyStore, outputStreamFactory, new File(tlsConfig.getKeyStore()), keyStorePassword, keyStorePasswordGenerated));
|
||||
|
||||
for (ConfigurationWriter<TlsConfig> configurationWriter : configurationWriters) {
|
||||
configurationWriter.write(tlsConfig, outputStreamFactory);
|
||||
}
|
||||
}
|
||||
|
||||
protected PasswordUtil getPasswordUtil() {
|
||||
return passwordUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ConfigurationWriter which will have an opportunity to write configuration information
|
||||
*
|
||||
* @param configurationWriter a ConfigurationWriter which will have an opportunity to write configuration information
|
||||
*/
|
||||
public void addConfigurationWriter(ConfigurationWriter<TlsConfig> configurationWriter) {
|
||||
configurationWriters.add(configurationWriter);
|
||||
}
|
||||
|
||||
protected TlsConfig getTlsConfig() {
|
||||
return tlsConfig;
|
||||
}
|
||||
}
|
|
@ -1,65 +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.toolkit.tls.manager;
|
||||
|
||||
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* KeyStore manager capable of reading or instantiating a CA Certificate
|
||||
*/
|
||||
public class TlsCertificateAuthorityManager extends BaseTlsManager {
|
||||
public TlsCertificateAuthorityManager(TlsConfig tlsConfig) throws GeneralSecurityException, IOException {
|
||||
super(tlsConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the CA from the KeyStore, creating one and putting it into the KeyStore if not present
|
||||
*
|
||||
* @return the PrivateKeyEntry for the CA
|
||||
*
|
||||
* @throws GeneralSecurityException if there is a security problem
|
||||
* @throws IOException if there is an IO problem
|
||||
*/
|
||||
public KeyStore.PrivateKeyEntry getOrGenerateCertificateAuthority() throws GeneralSecurityException, IOException {
|
||||
KeyStore.Entry entry = getEntry(TlsToolkitStandalone.NIFI_KEY);
|
||||
if (entry == null) {
|
||||
TlsConfig tlsConfig = getTlsConfig();
|
||||
KeyPair keyPair = TlsHelper.generateKeyPair(tlsConfig.getKeyPairAlgorithm(), tlsConfig.getKeySize());
|
||||
final X500Principal issuer = new X500Principal(tlsConfig.getDn());
|
||||
final Duration validityPeriod = Duration.ofDays(tlsConfig.getDays());
|
||||
final StandardCertificateBuilder certificateBuilder = new StandardCertificateBuilder(keyPair, issuer, validityPeriod);
|
||||
X509Certificate caCert = certificateBuilder.build();
|
||||
entry = addPrivateKeyToKeyStore(keyPair, TlsToolkitStandalone.NIFI_KEY, caCert);
|
||||
} else if (!KeyStore.PrivateKeyEntry.class.isInstance(entry)) {
|
||||
throw new IOException("Expected " + TlsToolkitStandalone.NIFI_KEY + " alias to contain a private key entry");
|
||||
}
|
||||
|
||||
return (KeyStore.PrivateKeyEntry) entry;
|
||||
}
|
||||
}
|
|
@ -1,118 +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.toolkit.tls.manager;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.manager.writer.ConfigurationWriter;
|
||||
import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.PasswordUtil;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
|
||||
import org.bouncycastle.util.io.pem.PemWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class TlsClientManager extends BaseTlsManager {
|
||||
private final TlsClientConfig tlsClientConfig;
|
||||
private final KeyStore trustStore;
|
||||
private final List<ConfigurationWriter<TlsClientConfig>> configurationWriters;
|
||||
private final Set<String> certificateAliases;
|
||||
private File certificateAuthorityDirectory;
|
||||
|
||||
public TlsClientManager(TlsClientConfig tlsClientConfig) throws GeneralSecurityException, IOException {
|
||||
this(tlsClientConfig, new PasswordUtil(), FileInputStream::new);
|
||||
}
|
||||
|
||||
public TlsClientManager(TlsClientConfig tlsClientConfig, PasswordUtil passwordUtil, InputStreamFactory inputStreamFactory) throws GeneralSecurityException, IOException {
|
||||
super(tlsClientConfig, passwordUtil, inputStreamFactory);
|
||||
this.trustStore = loadKeystore(tlsClientConfig.getTrustStore(), tlsClientConfig.getTrustStoreType(), tlsClientConfig.getTrustStorePassword());
|
||||
this.tlsClientConfig = tlsClientConfig;
|
||||
this.configurationWriters = new ArrayList<>();
|
||||
this.certificateAliases = new HashSet<>();
|
||||
}
|
||||
|
||||
public void setCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
|
||||
trustStore.setCertificateEntry(alias, cert);
|
||||
certificateAliases.add(alias);
|
||||
}
|
||||
|
||||
public void setCertificateAuthorityDirectory(File certificateAuthorityDirectory) {
|
||||
this.certificateAuthorityDirectory = certificateAuthorityDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStreamFactory outputStreamFactory) throws IOException, GeneralSecurityException {
|
||||
super.write(outputStreamFactory);
|
||||
|
||||
String trustStorePassword = tlsClientConfig.getTrustStorePassword();
|
||||
boolean trustStorePasswordGenerated = false;
|
||||
if (StringUtils.isEmpty(trustStorePassword)) {
|
||||
trustStorePassword = getPasswordUtil().generatePassword();
|
||||
trustStorePasswordGenerated = true;
|
||||
}
|
||||
|
||||
trustStorePassword = TlsHelper.writeKeyStore(trustStore, outputStreamFactory, new File(tlsClientConfig.getTrustStore()), trustStorePassword, trustStorePasswordGenerated);
|
||||
tlsClientConfig.setTrustStorePassword(trustStorePassword);
|
||||
|
||||
for (ConfigurationWriter<TlsClientConfig> configurationWriter : configurationWriters) {
|
||||
configurationWriter.write(tlsClientConfig, outputStreamFactory);
|
||||
}
|
||||
|
||||
if (certificateAuthorityDirectory != null) {
|
||||
// Write out all trusted certificates from truststore
|
||||
for (String alias : Collections.list(trustStore.aliases())) {
|
||||
try {
|
||||
KeyStore.Entry trustStoreEntry = trustStore.getEntry(alias, null);
|
||||
if (trustStoreEntry instanceof KeyStore.TrustedCertificateEntry) {
|
||||
Certificate trustedCertificate = ((KeyStore.TrustedCertificateEntry) trustStoreEntry).getTrustedCertificate();
|
||||
try (OutputStream outputStream = outputStreamFactory.create(new File(certificateAuthorityDirectory, TlsHelper.escapeFilename(alias) + ".pem"));
|
||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
|
||||
PemWriter pemWriter = new PemWriter(outputStreamWriter)) {
|
||||
pemWriter.writeObject(new JcaMiscPEMGenerator(trustedCertificate));
|
||||
}
|
||||
}
|
||||
} catch (UnrecoverableEntryException e) {
|
||||
// Ignore, not a trusted cert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addClientConfigurationWriter(ConfigurationWriter<TlsClientConfig> configurationWriter) {
|
||||
configurationWriters.add(configurationWriter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,38 +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.toolkit.tls.manager.writer;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class that can write out configuration information on the given object
|
||||
*
|
||||
* @param <T> the type of Object to write information about
|
||||
*/
|
||||
public interface ConfigurationWriter<T> {
|
||||
/**
|
||||
* Writes configuration information about the given object
|
||||
*
|
||||
* @param t the object
|
||||
* @param outputStreamFactory an OutputStreamFactory
|
||||
* @throws IOException if there is an IO problem
|
||||
*/
|
||||
void write(T t, OutputStreamFactory outputStreamFactory) throws IOException;
|
||||
}
|
|
@ -1,48 +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.toolkit.tls.manager.writer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Will write the object in JSON format
|
||||
*
|
||||
* @param <T> the type of object
|
||||
*/
|
||||
public class JsonConfigurationWriter<T> implements ConfigurationWriter<T> {
|
||||
private final ObjectWriter objectWriter;
|
||||
private final File file;
|
||||
|
||||
public JsonConfigurationWriter(ObjectMapper objectMapper, File file) {
|
||||
this.objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T tlsConfig, OutputStreamFactory outputStreamFactory) throws IOException {
|
||||
try (OutputStream stream = outputStreamFactory.create(file)) {
|
||||
objectWriter.writeValue(stream, tlsConfig);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,127 +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.toolkit.tls.manager.writer;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriter;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Will write a nifi.properties file appropriate for the given client config
|
||||
*/
|
||||
public class NifiPropertiesTlsClientConfigWriter implements ConfigurationWriter<TlsClientConfig> {
|
||||
public static final String HOSTNAME_PROPERTIES = "hostname.properties";
|
||||
public static final String OVERLAY_PROPERTIES = "overlay.properties";
|
||||
public static final String INCREMENTING_PROPERTIES = "incrementing.properties";
|
||||
public static final String CONF = "./conf/";
|
||||
private final NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
|
||||
private final File outputFile;
|
||||
private final String hostname;
|
||||
private final int hostNum;
|
||||
private final Properties overlayProperties;
|
||||
private final Set<String> metaProperties;
|
||||
|
||||
public NifiPropertiesTlsClientConfigWriter(NiFiPropertiesWriterFactory niFiPropertiesWriterFactory, File outputFile, String hostname, int hostNum) throws IOException {
|
||||
this.niFiPropertiesWriterFactory = niFiPropertiesWriterFactory;
|
||||
this.outputFile = outputFile;
|
||||
this.hostname = hostname;
|
||||
this.hostNum = hostNum;
|
||||
this.overlayProperties = new Properties();
|
||||
this.overlayProperties.load(getClass().getClassLoader().getResourceAsStream(OVERLAY_PROPERTIES));
|
||||
HashSet<String> metaProperties = new HashSet<>();
|
||||
metaProperties.add(HOSTNAME_PROPERTIES);
|
||||
metaProperties.add(INCREMENTING_PROPERTIES);
|
||||
getIncrementingPropertiesStream().forEach(metaProperties::add);
|
||||
this.metaProperties = Collections.unmodifiableSet(metaProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(TlsClientConfig tlsClientConfig, OutputStreamFactory outputStreamFactory) throws IOException {
|
||||
NiFiPropertiesWriter niFiPropertiesWriter = niFiPropertiesWriterFactory.create();
|
||||
updateProperties(niFiPropertiesWriter, tlsClientConfig);
|
||||
try (OutputStream stream = outputStreamFactory.create(outputFile)) {
|
||||
niFiPropertiesWriter.writeNiFiProperties(stream);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateProperties(NiFiPropertiesWriter niFiPropertiesWriter, TlsClientConfig tlsClientConfig) throws IOException {
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEYSTORE, CONF + new File(tlsClientConfig.getKeyStore()).getName());
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsClientConfig.getKeyStoreType());
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsClientConfig.getKeyStorePassword());
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEY_PASSWD, tlsClientConfig.getKeyPassword());
|
||||
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_TRUSTSTORE, CONF + new File(tlsClientConfig.getTrustStore()).getName());
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsClientConfig.getTrustStoreType());
|
||||
niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsClientConfig.getTrustStorePassword());
|
||||
|
||||
getHostnamePropertyStream().forEach(s -> niFiPropertiesWriter.setPropertyValue(s, hostname));
|
||||
|
||||
overlayProperties.stringPropertyNames().stream().filter(s -> !metaProperties.contains(s)).forEach(s -> niFiPropertiesWriter.setPropertyValue(s, overlayProperties.getProperty(s)));
|
||||
|
||||
getIncrementingPropertyMap().entrySet().forEach(nameToIntegerEntry -> niFiPropertiesWriter.setPropertyValue(nameToIntegerEntry.getKey(), Integer.toString(nameToIntegerEntry.getValue())));
|
||||
}
|
||||
|
||||
protected Properties getOverlayProperties() {
|
||||
return overlayProperties;
|
||||
}
|
||||
|
||||
protected Map<String, Integer> getIncrementingPropertyMap() {
|
||||
return getIncrementingPropertiesStream().collect(Collectors.toMap(Function.identity(), portProperty -> {
|
||||
String portVal = overlayProperties.getProperty(portProperty);
|
||||
int startingPort;
|
||||
try {
|
||||
startingPort = Integer.parseInt(portVal);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new NumberFormatException("Expected numeric values in " + OVERLAY_PROPERTIES + " (" + portProperty + " was " + portVal + ")");
|
||||
}
|
||||
return startingPort + hostNum - 1;
|
||||
}));
|
||||
}
|
||||
|
||||
protected Stream<String> getIncrementingPropertiesStream() {
|
||||
return getCommaSeparatedPropertyStream(INCREMENTING_PROPERTIES);
|
||||
}
|
||||
|
||||
protected Stream<String> getHostnamePropertyStream() {
|
||||
return getCommaSeparatedPropertyStream(HOSTNAME_PROPERTIES);
|
||||
}
|
||||
|
||||
private Stream<String> getCommaSeparatedPropertyStream(String property) {
|
||||
String hostnamePropertyString = overlayProperties.getProperty(property);
|
||||
if (!StringUtils.isEmpty(hostnamePropertyString)) {
|
||||
return Arrays.stream(hostnamePropertyString.split(",")).map(String::trim);
|
||||
}
|
||||
return Stream.of();
|
||||
}
|
||||
}
|
|
@ -1,93 +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.toolkit.tls.properties;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Class capable of writing out updated NiFi properties. It keeps a list of original lines and a map of updates to apply.
|
||||
*
|
||||
* It first writes all the original properties (with updated values if they exist) and then adds any new properties at the end.
|
||||
*/
|
||||
public class NiFiPropertiesWriter {
|
||||
private final List<String> lines;
|
||||
private final Map<String, String> updatedValues;
|
||||
|
||||
public NiFiPropertiesWriter(List<String> lines) {
|
||||
this.lines = new ArrayList<>(lines);
|
||||
this.updatedValues = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property value
|
||||
*
|
||||
* @param key the property key
|
||||
* @param value the property value
|
||||
*/
|
||||
public void setPropertyValue(String key, String value) {
|
||||
updatedValues.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an updated nifi.properties to the given OutputStream
|
||||
*
|
||||
* @param outputStream the output stream
|
||||
* @throws IOException if there is an IO error
|
||||
*/
|
||||
public void writeNiFiProperties(OutputStream outputStream) throws IOException {
|
||||
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
|
||||
Map<String, String> remainingValues = new HashMap<>(updatedValues);
|
||||
Set<String> keysSeen = new HashSet<>();
|
||||
for (String line : lines) {
|
||||
String key = line.split("=")[0].trim();
|
||||
boolean outputLine = true;
|
||||
if (!key.isEmpty() && !key.startsWith("#")) {
|
||||
if (!keysSeen.add(key)) {
|
||||
throw new IOException("Found key more than once in nifi.properties: " + key);
|
||||
}
|
||||
String value = remainingValues.remove(key);
|
||||
if (value != null) {
|
||||
writer.write(key);
|
||||
writer.write("=");
|
||||
writer.write(value);
|
||||
outputLine = false;
|
||||
}
|
||||
}
|
||||
if (outputLine) {
|
||||
writer.write(line);
|
||||
}
|
||||
writer.newLine();
|
||||
}
|
||||
for (Map.Entry<String, String> keyValueEntry : remainingValues.entrySet()) {
|
||||
writer.write(keyValueEntry.getKey());
|
||||
writer.write("=");
|
||||
writer.write(keyValueEntry.getValue());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +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.toolkit.tls.properties;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.TlsToolkitMain;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Factory for creating NifiPropertiesWriters so that the lines only have to be read once
|
||||
*/
|
||||
public class NiFiPropertiesWriterFactory {
|
||||
private final List<String> lines;
|
||||
|
||||
public NiFiPropertiesWriterFactory() throws IOException {
|
||||
this(TlsToolkitMain.class.getClassLoader().getResourceAsStream("conf/nifi.properties"));
|
||||
}
|
||||
|
||||
public NiFiPropertiesWriterFactory(InputStream inputStream) throws IOException {
|
||||
List<String> lines = new ArrayList<>();
|
||||
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
lines.add(line.trim());
|
||||
}
|
||||
}
|
||||
this.lines = Collections.unmodifiableList(lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a NifiPropertiesWriter with based on the read nifi.properties
|
||||
*
|
||||
* @return a NifiPropertiesWriter with based on the read nifi.properties
|
||||
*/
|
||||
public NiFiPropertiesWriter create() {
|
||||
return new NiFiPropertiesWriter(lines);
|
||||
}
|
||||
}
|
|
@ -1,117 +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.toolkit.tls.service;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.BaseTlsToolkitCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Common base argument logic for the CA server and client
|
||||
*/
|
||||
public abstract class BaseCertificateAuthorityCommandLine extends BaseTlsToolkitCommandLine {
|
||||
public static final String TOKEN_ARG = "token";
|
||||
public static final String CONFIG_JSON_ARG = "configJson";
|
||||
public static final String READ_CONFIG_JSON_ARG = "configJsonIn";
|
||||
public static final String USE_CONFIG_JSON_ARG = "useConfigJson";
|
||||
|
||||
public static final String PORT_ARG = "PORT";
|
||||
public static final String DEFAULT_CONFIG_JSON = new File("config.json").getPath();
|
||||
|
||||
private String token;
|
||||
private String configJsonOut;
|
||||
private String configJsonIn;
|
||||
private int port;
|
||||
private String dn;
|
||||
|
||||
public BaseCertificateAuthorityCommandLine(String header) {
|
||||
super(header);
|
||||
addOptionWithArg("t", TOKEN_ARG, getTokenDescription());
|
||||
addOptionWithArg("f", CONFIG_JSON_ARG, "The place to write configuration info", DEFAULT_CONFIG_JSON);
|
||||
addOptionWithArg(null, READ_CONFIG_JSON_ARG, "The place to read configuration info from (defaults to the value of " + CONFIG_JSON_ARG + "), implies "
|
||||
+ USE_CONFIG_JSON_ARG + " if set.", CONFIG_JSON_ARG + " value");
|
||||
addOptionNoArg("F", USE_CONFIG_JSON_ARG, "Flag specifying that all configuration is read from " + CONFIG_JSON_ARG + " to facilitate automated use (otherwise "
|
||||
+ CONFIG_JSON_ARG + " will only be written to.");
|
||||
addOptionWithArg("p", PORT_ARG, getPortDescription(), TlsConfig.DEFAULT_PORT);
|
||||
addOptionWithArg("D", DN_ARG, getDnDescription(), new TlsConfig().calcDefaultDn(getDnHostname()));
|
||||
}
|
||||
|
||||
protected abstract String getTokenDescription();
|
||||
|
||||
protected abstract String getDnDescription();
|
||||
|
||||
protected abstract String getPortDescription();
|
||||
|
||||
protected abstract String getDnHostname();
|
||||
|
||||
@Override
|
||||
protected CommandLine doParse(String[] args) throws CommandLineParseException {
|
||||
CommandLine commandLine = super.doParse(args);
|
||||
|
||||
token = commandLine.getOptionValue(TOKEN_ARG);
|
||||
|
||||
boolean useConfigJson = commandLine.hasOption(USE_CONFIG_JSON_ARG);
|
||||
|
||||
configJsonOut = commandLine.getOptionValue(CONFIG_JSON_ARG, DEFAULT_CONFIG_JSON);
|
||||
configJsonIn = commandLine.getOptionValue(READ_CONFIG_JSON_ARG);
|
||||
if (StringUtils.isEmpty(configJsonIn) && useConfigJson) {
|
||||
configJsonIn = configJsonOut;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(token) && StringUtils.isEmpty(configJsonIn)) {
|
||||
printUsageAndThrow(TOKEN_ARG + " argument must not be empty unless " + USE_CONFIG_JSON_ARG + " or " + READ_CONFIG_JSON_ARG+ " set", ExitCode.ERROR_TOKEN_ARG_EMPTY);
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(token)) {
|
||||
byte[] tokenBytes = token.getBytes(StandardCharsets.UTF_8);
|
||||
if (tokenBytes.length < 16) {
|
||||
printUsageAndThrow(TOKEN_ARG + " does not meet minimum size of 16 bytes", ExitCode.ERROR_TOKEN_ARG_TOO_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
port = getIntValue(commandLine, PORT_ARG, TlsConfig.DEFAULT_PORT);
|
||||
dn = commandLine.getOptionValue(DN_ARG, new TlsConfig().calcDefaultDn(getDnHostname()));
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getConfigJsonOut() {
|
||||
return configJsonOut;
|
||||
}
|
||||
|
||||
public String getConfigJsonIn() {
|
||||
return configJsonIn;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getDn() {
|
||||
return dn;
|
||||
}
|
||||
}
|
|
@ -1,90 +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.toolkit.tls.service.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.manager.TlsClientManager;
|
||||
import org.apache.nifi.toolkit.tls.manager.writer.JsonConfigurationWriter;
|
||||
import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* Client that will generate a CSR and submit to a CA, writing out the results to a keystore and truststore along with a config file if successful
|
||||
*/
|
||||
public class TlsCertificateAuthorityClient {
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityClient.class);
|
||||
private final OutputStreamFactory outputStreamFactory;
|
||||
|
||||
public TlsCertificateAuthorityClient() {
|
||||
this(FileOutputStream::new);
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityClient(OutputStreamFactory outputStreamFactory) {
|
||||
this.outputStreamFactory = outputStreamFactory;
|
||||
}
|
||||
|
||||
public void generateCertificateAndGetItSigned(TlsClientConfig tlsClientConfig, String certificateDirectory, String configJson, boolean differentKeyAndKeyStorePassword) throws Exception {
|
||||
TlsClientManager tlsClientManager;
|
||||
try {
|
||||
tlsClientManager = new TlsClientManager(tlsClientConfig);
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to open existing keystore, it can be reused by specifiying both " + BaseCertificateAuthorityCommandLine.CONFIG_JSON_ARG + " and " +
|
||||
BaseCertificateAuthorityCommandLine.USE_CONFIG_JSON_ARG);
|
||||
throw e;
|
||||
}
|
||||
tlsClientManager.setDifferentKeyAndKeyStorePassword(differentKeyAndKeyStorePassword);
|
||||
|
||||
if (!StringUtils.isEmpty(certificateDirectory)) {
|
||||
tlsClientManager.setCertificateAuthorityDirectory(new File(certificateDirectory));
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(configJson)) {
|
||||
tlsClientManager.addClientConfigurationWriter(new JsonConfigurationWriter<>(new ObjectMapper(), new File(configJson)));
|
||||
}
|
||||
|
||||
if (tlsClientManager.getEntry(TlsToolkitStandalone.NIFI_KEY) == null) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Requesting new certificate from " + tlsClientConfig.getCaHostname() + ":" + tlsClientConfig.getPort());
|
||||
}
|
||||
KeyPair keyPair = TlsHelper.generateKeyPair(tlsClientConfig.getKeyPairAlgorithm(), tlsClientConfig.getKeySize());
|
||||
|
||||
X509Certificate[] certificates = tlsClientConfig.createCertificateSigningRequestPerformer().perform(keyPair);
|
||||
|
||||
tlsClientManager.addPrivateKeyToKeyStore(keyPair, TlsToolkitStandalone.NIFI_KEY, certificates);
|
||||
tlsClientManager.setCertificateEntry(TlsToolkitStandalone.NIFI_CERT, certificates[certificates.length - 1]);
|
||||
} else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Already had entry for " + TlsToolkitStandalone.NIFI_KEY + " not requesting new certificate.");
|
||||
}
|
||||
}
|
||||
|
||||
tlsClientManager.write(outputStreamFactory);
|
||||
}
|
||||
}
|
|
@ -1,173 +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.toolkit.tls.service.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
|
||||
import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command line parser for a TlsClientConfig object and a main entry point to invoke the parser and run the CA client
|
||||
*/
|
||||
public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAuthorityCommandLine {
|
||||
public static final String DESCRIPTION = "Generates a private key and gets it signed by the certificate authority.";
|
||||
public static final String CERTIFICATE_DIRECTORY = "certificateDirectory";
|
||||
public static final String SUBJECT_ALTERNATIVE_NAMES = "subjectAlternativeNames";
|
||||
public static final String DEFAULT_CERTIFICATE_DIRECTORY = ".";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityClientCommandLine.class);
|
||||
private final InputStreamFactory inputStreamFactory;
|
||||
|
||||
private String certificateDirectory;
|
||||
private List<InstanceDefinition> domainAlternativeNames;
|
||||
|
||||
public TlsCertificateAuthorityClientCommandLine() {
|
||||
this(FileInputStream::new);
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityClientCommandLine(InputStreamFactory inputStreamFactory) {
|
||||
super(DESCRIPTION);
|
||||
this.inputStreamFactory = inputStreamFactory;
|
||||
addOptionWithArg("C", CERTIFICATE_DIRECTORY, "The file to write the CA certificate to", DEFAULT_CERTIFICATE_DIRECTORY);
|
||||
addOptionWithArg(null, SUBJECT_ALTERNATIVE_NAMES, "Comma-separated list of domains to use as Subject Alternative Names in the certificate");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
TlsCertificateAuthorityClientCommandLine tlsCertificateAuthorityClientCommandLine = new TlsCertificateAuthorityClientCommandLine();
|
||||
try {
|
||||
tlsCertificateAuthorityClientCommandLine.parse(args);
|
||||
} catch (CommandLineParseException e) {
|
||||
System.exit(e.getExitCode().ordinal());
|
||||
}
|
||||
new TlsCertificateAuthorityClient().generateCertificateAndGetItSigned(tlsCertificateAuthorityClientCommandLine.createClientConfig(),
|
||||
tlsCertificateAuthorityClientCommandLine.getCertificateDirectory(), tlsCertificateAuthorityClientCommandLine.getConfigJsonOut(),
|
||||
tlsCertificateAuthorityClientCommandLine.differentPasswordForKeyAndKeystore());
|
||||
System.exit(ExitCode.SUCCESS.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAddDaysArg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAddSigningAlgorithmArg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTokenDescription() {
|
||||
return "The token to use to prevent MITM (required and must be same as one used by CA)";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDnDescription() {
|
||||
return "The dn to use for the client certificate";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPortDescription() {
|
||||
return "The port to use to communicate with the Certificate Authority";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDnHostname() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
logger.warn("Unable to determine hostname", e);
|
||||
return "localhost";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandLine doParse(String[] args) throws CommandLineParseException {
|
||||
CommandLine commandLine = super.doParse(args);
|
||||
certificateDirectory = commandLine.getOptionValue(CERTIFICATE_DIRECTORY, DEFAULT_CERTIFICATE_DIRECTORY);
|
||||
|
||||
if (commandLine.hasOption(SUBJECT_ALTERNATIVE_NAMES)) {
|
||||
domainAlternativeNames = Collections.unmodifiableList(
|
||||
InstanceDefinition.createDefinitions(
|
||||
null,
|
||||
Arrays.stream(commandLine.getOptionValues(SUBJECT_ALTERNATIVE_NAMES)).flatMap(s -> Arrays.stream(s.split(",")).map(String::trim)),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
));
|
||||
} else {
|
||||
domainAlternativeNames = Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
public String getCertificateDirectory() {
|
||||
return certificateDirectory;
|
||||
}
|
||||
|
||||
public List<String> getDomainAlternativeNames() {
|
||||
if (domainAlternativeNames == null) {
|
||||
domainAlternativeNames = Collections.EMPTY_LIST;
|
||||
}
|
||||
return domainAlternativeNames.stream().map(InstanceDefinition::getHostname).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public TlsClientConfig createClientConfig() throws IOException {
|
||||
String configJsonIn = getConfigJsonIn();
|
||||
if (!StringUtils.isEmpty(configJsonIn)) {
|
||||
try (InputStream inputStream = inputStreamFactory.create(new File(configJsonIn))) {
|
||||
TlsClientConfig tlsClientConfig = new ObjectMapper().readValue(inputStream, TlsClientConfig.class);
|
||||
tlsClientConfig.initDefaults();
|
||||
return tlsClientConfig;
|
||||
}
|
||||
} else {
|
||||
TlsClientConfig tlsClientConfig = new TlsClientConfig();
|
||||
tlsClientConfig.setCaHostname(getCertificateAuthorityHostname());
|
||||
tlsClientConfig.setDn(getDn());
|
||||
tlsClientConfig.setDomainAlternativeNames(getDomainAlternativeNames());
|
||||
tlsClientConfig.setToken(getToken());
|
||||
tlsClientConfig.setPort(getPort());
|
||||
tlsClientConfig.setKeyStore(KEYSTORE + getKeyStoreType().toLowerCase());
|
||||
tlsClientConfig.setKeyStoreType(getKeyStoreType());
|
||||
tlsClientConfig.setTrustStore(TRUSTSTORE + tlsClientConfig.getTrustStoreType().toLowerCase());
|
||||
tlsClientConfig.setKeySize(getKeySize());
|
||||
tlsClientConfig.setKeyPairAlgorithm(getKeyAlgorithm());
|
||||
tlsClientConfig.setSigningAlgorithm(getSigningAlgorithm());
|
||||
return tlsClientConfig;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +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.toolkit.tls.service.client;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Socket Factory validates that it is talking to a RootCa claiming to have the given hostname. It adds the certificate
|
||||
* to a list for later validation against the payload's hmac
|
||||
*/
|
||||
public class TlsCertificateAuthorityClientSocketFactory extends SSLConnectionSocketFactory {
|
||||
private final String caHostname;
|
||||
private final List<X509Certificate> certificates;
|
||||
|
||||
public TlsCertificateAuthorityClientSocketFactory(SSLContext sslContext, String caHostname, List<X509Certificate> certificates) {
|
||||
super(sslContext);
|
||||
this.caHostname = caHostname;
|
||||
this.certificates = certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
|
||||
InetSocketAddress localAddress, HttpContext context) throws IOException {
|
||||
Socket result = super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
|
||||
if (!SSLSocket.class.isInstance(result)) {
|
||||
throw new IOException("Expected tls socket");
|
||||
}
|
||||
SSLSocket sslSocket = (SSLSocket) result;
|
||||
java.security.cert.Certificate[] peerCertificateChain = sslSocket.getSession().getPeerCertificates();
|
||||
if (peerCertificateChain.length != 1) {
|
||||
throw new IOException("Expected root ca cert");
|
||||
}
|
||||
if (!X509Certificate.class.isInstance(peerCertificateChain[0])) {
|
||||
throw new IOException("Expected root ca cert in X509 format");
|
||||
}
|
||||
String cn;
|
||||
try {
|
||||
X509Certificate certificate = (X509Certificate) peerCertificateChain[0];
|
||||
cn = IETFUtils.valueToString(new JcaX509CertificateHolder(certificate).getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue());
|
||||
certificates.add(certificate);
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
if (!caHostname.equals(cn)) {
|
||||
throw new IOException("Expected cn of " + caHostname + " but got " + cn);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,161 +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.toolkit.tls.service.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BoundedInputStream;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.apache.nifi.security.util.TlsPlatform;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TlsCertificateSigningRequestPerformer {
|
||||
public static final String RECEIVED_RESPONSE_CODE = "Received response code ";
|
||||
public static final String EXPECTED_ONE_CERTIFICATE = "Expected one certificate";
|
||||
public static final String EXPECTED_RESPONSE_TO_CONTAIN_HMAC = "Expected response to contain hmac";
|
||||
public static final String UNEXPECTED_HMAC_RECEIVED_POSSIBLE_MAN_IN_THE_MIDDLE = "Unexpected hmac received, possible man in the middle";
|
||||
public static final String EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE = "Expected response to contain certificate";
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsCertificateSigningRequestPerformer.class);
|
||||
private final Supplier<HttpClientBuilder> httpClientBuilderSupplier;
|
||||
private final String caHostname;
|
||||
private final String dn;
|
||||
private final List<String> domainAlternativeNames;
|
||||
private final String token;
|
||||
private final int port;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String signingAlgorithm;
|
||||
|
||||
public TlsCertificateSigningRequestPerformer(TlsClientConfig tlsClientConfig) {
|
||||
this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
|
||||
tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
|
||||
}
|
||||
|
||||
protected TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) {
|
||||
this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
|
||||
tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
|
||||
}
|
||||
|
||||
private TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, String caHostname,
|
||||
String dn, List<String> domainAlternativeNames, String token, int port, String signingAlgorithm) {
|
||||
this.httpClientBuilderSupplier = httpClientBuilderSupplier;
|
||||
this.caHostname = caHostname;
|
||||
this.dn = dn;
|
||||
this.domainAlternativeNames = domainAlternativeNames;
|
||||
this.token = token;
|
||||
this.port = port;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.signingAlgorithm = signingAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a CSR to the Certificate authority, checks the resulting hmac, and returns the chain if everything succeeds
|
||||
*
|
||||
* @param keyPair the keypair to generate the csr for
|
||||
* @throws IOException if there is a problem during the process
|
||||
* @return the resulting certificate chain
|
||||
*/
|
||||
public X509Certificate[] perform(KeyPair keyPair) throws IOException {
|
||||
try {
|
||||
List<X509Certificate> certificates = new ArrayList<>();
|
||||
|
||||
HttpClientBuilder httpClientBuilder = httpClientBuilderSupplier.get();
|
||||
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
|
||||
sslContextBuilder.setProtocol(TlsPlatform.getLatestProtocol());
|
||||
|
||||
// We will be validating that we are talking to the correct host once we get the response's hmac of the token and public key of the ca
|
||||
sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
|
||||
httpClientBuilder.setSSLSocketFactory(new TlsCertificateAuthorityClientSocketFactory(sslContextBuilder.build(), caHostname, certificates));
|
||||
|
||||
String jsonResponseString;
|
||||
int responseCode;
|
||||
try (CloseableHttpClient client = httpClientBuilder.build()) {
|
||||
JcaPKCS10CertificationRequest request = TlsHelper.generateCertificationRequest(dn, domainAlternativeNames, keyPair, signingAlgorithm);
|
||||
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(TlsHelper.calculateHMac(token, request.getPublicKey()),
|
||||
TlsHelper.pemEncodeJcaObject(request));
|
||||
|
||||
HttpPost httpPost = new HttpPost();
|
||||
httpPost.setEntity(new ByteArrayEntity(objectMapper.writeValueAsBytes(tlsCertificateAuthorityRequest)));
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Requesting certificate with dn " + dn + " from " + caHostname + ":" + port);
|
||||
}
|
||||
try (CloseableHttpResponse response = client.execute(new HttpHost(caHostname, port, "https"), httpPost)) {
|
||||
jsonResponseString = IOUtils.toString(new BoundedInputStream(response.getEntity().getContent(), 1024 * 1024), StandardCharsets.UTF_8);
|
||||
responseCode = response.getStatusLine().getStatusCode();
|
||||
}
|
||||
}
|
||||
|
||||
if (responseCode != Response.SC_OK) {
|
||||
throw new IOException(RECEIVED_RESPONSE_CODE + responseCode + " with payload " + jsonResponseString);
|
||||
}
|
||||
|
||||
if (certificates.size() != 1) {
|
||||
throw new IOException(EXPECTED_ONE_CERTIFICATE);
|
||||
}
|
||||
|
||||
TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse = objectMapper.readValue(jsonResponseString, TlsCertificateAuthorityResponse.class);
|
||||
if (!tlsCertificateAuthorityResponse.hasHmac()) {
|
||||
throw new IOException(EXPECTED_RESPONSE_TO_CONTAIN_HMAC);
|
||||
}
|
||||
|
||||
X509Certificate caCertificate = certificates.get(0);
|
||||
byte[] expectedHmac = TlsHelper.calculateHMac(token, caCertificate.getPublicKey());
|
||||
|
||||
if (!MessageDigest.isEqual(expectedHmac, tlsCertificateAuthorityResponse.getHmac())) {
|
||||
throw new IOException(UNEXPECTED_HMAC_RECEIVED_POSSIBLE_MAN_IN_THE_MIDDLE);
|
||||
}
|
||||
|
||||
if (!tlsCertificateAuthorityResponse.hasCertificate()) {
|
||||
throw new IOException(EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE);
|
||||
}
|
||||
X509Certificate x509Certificate = TlsHelper.parseCertificate(new StringReader(tlsCertificateAuthorityResponse.getPemEncodedCertificate()));
|
||||
x509Certificate.verify(caCertificate.getPublicKey());
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Got certificate with dn " + x509Certificate.getSubjectX500Principal());
|
||||
}
|
||||
return new X509Certificate[]{x509Certificate, caCertificate};
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +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.toolkit.tls.service.dto;
|
||||
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
public class TlsCertificateAuthorityRequest {
|
||||
private byte[] hmac;
|
||||
private String csr;
|
||||
|
||||
public TlsCertificateAuthorityRequest() {
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityRequest(byte[] hmac, String csr) {
|
||||
this.hmac = hmac;
|
||||
this.csr = csr;
|
||||
}
|
||||
|
||||
public byte[] getHmac() {
|
||||
return hmac;
|
||||
}
|
||||
|
||||
public void setHmac(byte[] hmac) {
|
||||
this.hmac = hmac;
|
||||
}
|
||||
|
||||
public boolean hasHmac() {
|
||||
return hmac != null && hmac.length > 0;
|
||||
}
|
||||
|
||||
public String getCsr() {
|
||||
return csr;
|
||||
}
|
||||
|
||||
public void setCsr(String csr) {
|
||||
this.csr = csr;
|
||||
}
|
||||
|
||||
public boolean hasCsr() {
|
||||
return !StringUtils.isEmpty(csr);
|
||||
}
|
||||
}
|
|
@ -1,70 +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.toolkit.tls.service.dto;
|
||||
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
public class TlsCertificateAuthorityResponse {
|
||||
private byte[] hmac;
|
||||
private String pemEncodedCertificate;
|
||||
private String error;
|
||||
|
||||
public TlsCertificateAuthorityResponse() {
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityResponse(byte[] hmac, String pemEncodedCertificate) {
|
||||
this.hmac = hmac;
|
||||
this.pemEncodedCertificate = pemEncodedCertificate;
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public byte[] getHmac() {
|
||||
return hmac;
|
||||
}
|
||||
|
||||
public void setHmac(byte[] hmac) {
|
||||
this.hmac = hmac;
|
||||
}
|
||||
|
||||
public String getPemEncodedCertificate() {
|
||||
return pemEncodedCertificate;
|
||||
}
|
||||
|
||||
public void setPemEncodedCertificate(String pemEncodedCertificate) {
|
||||
this.pemEncodedCertificate = pemEncodedCertificate;
|
||||
}
|
||||
|
||||
public boolean hasCertificate() {
|
||||
return !StringUtils.isEmpty(pemEncodedCertificate);
|
||||
}
|
||||
|
||||
public boolean hasHmac() {
|
||||
return hmac != null && hmac.length > 0;
|
||||
}
|
||||
}
|
|
@ -1,129 +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.toolkit.tls.service.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.nifi.security.util.TlsPlatform;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.manager.TlsCertificateAuthorityManager;
|
||||
import org.apache.nifi.toolkit.tls.manager.writer.JsonConfigurationWriter;
|
||||
import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* Starts a Jetty server that will either load an existing CA or create one and use it to sign CSRs
|
||||
*/
|
||||
public class TlsCertificateAuthorityService {
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityService.class);
|
||||
private final OutputStreamFactory outputStreamFactory;
|
||||
private Server server;
|
||||
|
||||
private ServerConnector serverConnector;
|
||||
|
||||
public TlsCertificateAuthorityService() {
|
||||
this(FileOutputStream::new);
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityService(OutputStreamFactory outputStreamFactory) {
|
||||
this.outputStreamFactory = outputStreamFactory;
|
||||
}
|
||||
|
||||
private static ServerConnector createSSLConnector(Server server, int port, KeyStore keyStore, String keyPassword) {
|
||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
sslContextFactory.setIncludeProtocols(TlsPlatform.getLatestProtocol());
|
||||
sslContextFactory.setKeyStore(keyStore);
|
||||
sslContextFactory.setKeyManagerPassword(keyPassword);
|
||||
|
||||
HttpConfiguration httpsConfig = new HttpConfiguration();
|
||||
httpsConfig.addCustomizer(new SecureRequestCustomizer());
|
||||
|
||||
ServerConnector sslConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConfig));
|
||||
sslConnector.setPort(port);
|
||||
|
||||
return sslConnector;
|
||||
}
|
||||
|
||||
public synchronized void start(TlsConfig tlsConfig, String configJson, boolean differentPasswordsForKeyAndKeystore) throws Exception {
|
||||
if (server != null) {
|
||||
throw new IllegalStateException("Server already started");
|
||||
}
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
TlsCertificateAuthorityManager tlsManager;
|
||||
try {
|
||||
tlsManager = new TlsCertificateAuthorityManager(tlsConfig);
|
||||
tlsManager.setDifferentKeyAndKeyStorePassword(differentPasswordsForKeyAndKeystore);
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to open existing keystore, it can be reused by specifiying both " + BaseCertificateAuthorityCommandLine.CONFIG_JSON_ARG + " and " +
|
||||
BaseCertificateAuthorityCommandLine.USE_CONFIG_JSON_ARG);
|
||||
throw e;
|
||||
}
|
||||
tlsManager.addConfigurationWriter(new JsonConfigurationWriter<>(objectMapper, new File(configJson)));
|
||||
|
||||
KeyStore.PrivateKeyEntry privateKeyEntry = tlsManager.getOrGenerateCertificateAuthority();
|
||||
KeyPair keyPair = new KeyPair(privateKeyEntry.getCertificate().getPublicKey(), privateKeyEntry.getPrivateKey());
|
||||
Certificate[] certificateChain = privateKeyEntry.getCertificateChain();
|
||||
if (certificateChain.length != 1) {
|
||||
throw new IOException("Expected root ca cert to be only certificate in chain");
|
||||
}
|
||||
Certificate certificate = certificateChain[0];
|
||||
X509Certificate caCert;
|
||||
if (certificate instanceof X509Certificate) {
|
||||
caCert = (X509Certificate) certificate;
|
||||
} else {
|
||||
throw new IOException("Expected " + X509Certificate.class + " as root ca cert");
|
||||
}
|
||||
tlsManager.write(outputStreamFactory);
|
||||
String signingAlgorithm = tlsConfig.getSigningAlgorithm();
|
||||
int days = tlsConfig.getDays();
|
||||
server = new Server();
|
||||
serverConnector = createSSLConnector(server, tlsConfig.getPort(), tlsManager.getKeyStore(), tlsConfig.getKeyPassword());
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(new TlsCertificateAuthorityServiceHandler(signingAlgorithm, days, tlsConfig.getToken(), caCert, keyPair, objectMapper));
|
||||
server.start();
|
||||
}
|
||||
|
||||
public synchronized void shutdown() throws Exception {
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("Server already shutdown");
|
||||
}
|
||||
server.stop();
|
||||
server.join();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return serverConnector.getLocalPort();
|
||||
}
|
||||
}
|
|
@ -1,111 +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.toolkit.tls.service.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Command line parser for a TlsConfig object and a main entry point to invoke the parser and run the CA server
|
||||
*/
|
||||
public class TlsCertificateAuthorityServiceCommandLine extends BaseCertificateAuthorityCommandLine {
|
||||
public static final String DESCRIPTION = "Acts as a Certificate Authority that can be used by clients to get Certificates";
|
||||
public static final String NIFI_CA_KEYSTORE = "nifi-ca-" + KEYSTORE;
|
||||
|
||||
private final InputStreamFactory inputStreamFactory;
|
||||
|
||||
public TlsCertificateAuthorityServiceCommandLine() {
|
||||
this(FileInputStream::new);
|
||||
}
|
||||
|
||||
public TlsCertificateAuthorityServiceCommandLine(InputStreamFactory inputStreamFactory) {
|
||||
super(DESCRIPTION);
|
||||
this.inputStreamFactory = inputStreamFactory;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
TlsCertificateAuthorityServiceCommandLine tlsCertificateAuthorityServiceCommandLine = new TlsCertificateAuthorityServiceCommandLine();
|
||||
try {
|
||||
tlsCertificateAuthorityServiceCommandLine.parse(args);
|
||||
} catch (CommandLineParseException e) {
|
||||
System.exit(e.getExitCode().ordinal());
|
||||
}
|
||||
TlsCertificateAuthorityService tlsCertificateAuthorityService = new TlsCertificateAuthorityService();
|
||||
tlsCertificateAuthorityService.start(tlsCertificateAuthorityServiceCommandLine.createConfig(), tlsCertificateAuthorityServiceCommandLine.getConfigJsonOut(),
|
||||
tlsCertificateAuthorityServiceCommandLine.differentPasswordForKeyAndKeystore());
|
||||
System.out.println("Server Started");
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
public TlsConfig createConfig() throws IOException {
|
||||
String configJsonIn = getConfigJsonIn();
|
||||
if (!StringUtils.isEmpty(configJsonIn)) {
|
||||
try (InputStream inputStream = inputStreamFactory.create(new File(configJsonIn))) {
|
||||
TlsConfig tlsConfig = new ObjectMapper().readValue(inputStream, TlsConfig.class);
|
||||
tlsConfig.initDefaults();
|
||||
return tlsConfig;
|
||||
}
|
||||
} else {
|
||||
TlsConfig tlsConfig = new TlsConfig();
|
||||
tlsConfig.setCaHostname(getCertificateAuthorityHostname());
|
||||
tlsConfig.setDn(getDn());
|
||||
tlsConfig.setToken(getToken());
|
||||
tlsConfig.setPort(getPort());
|
||||
tlsConfig.setKeyStore(NIFI_CA_KEYSTORE + getKeyStoreType().toLowerCase());
|
||||
tlsConfig.setKeyStoreType(getKeyStoreType());
|
||||
tlsConfig.setKeySize(getKeySize());
|
||||
tlsConfig.setKeyPairAlgorithm(getKeyAlgorithm());
|
||||
tlsConfig.setSigningAlgorithm(getSigningAlgorithm());
|
||||
tlsConfig.setDays(getDays());
|
||||
return tlsConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTokenDescription() {
|
||||
return "The token to use to prevent MITM (required and must be same as one used by clients)";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDnDescription() {
|
||||
return "The dn to use for the CA certificate";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPortDescription() {
|
||||
return "The port for the Certificate Authority to listen on";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDnHostname() {
|
||||
String dnHostname = getCertificateAuthorityHostname();
|
||||
if (StringUtils.isEmpty(dnHostname)) {
|
||||
return "YOUR_CA_HOSTNAME";
|
||||
}
|
||||
return dnHostname;
|
||||
}
|
||||
}
|
|
@ -1,149 +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.toolkit.tls.service.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.io.input.BoundedReader;
|
||||
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.Extensions;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Jetty service handler that validates the hmac of a CSR and issues a certificate if it checks out
|
||||
*/
|
||||
public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
|
||||
public static final String CSR_FIELD_MUST_BE_SET = "csr field must be set";
|
||||
public static final String HMAC_FIELD_MUST_BE_SET = "hmac field must be set";
|
||||
public static final String FORBIDDEN = "forbidden";
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityServiceHandler.class);
|
||||
private final String signingAlgorithm;
|
||||
private final int days;
|
||||
private final String token;
|
||||
private final X509Certificate caCert;
|
||||
private final KeyPair keyPair;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public TlsCertificateAuthorityServiceHandler(String signingAlgorithm, int days, String token, X509Certificate caCert, KeyPair keyPair, ObjectMapper objectMapper) {
|
||||
this.signingAlgorithm = signingAlgorithm;
|
||||
this.days = days;
|
||||
this.token = token;
|
||||
this.caCert = caCert;
|
||||
this.keyPair = keyPair;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException {
|
||||
try {
|
||||
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = objectMapper.readValue(new BoundedReader(request.getReader(), 1024 * 1024), TlsCertificateAuthorityRequest.class);
|
||||
|
||||
if (!tlsCertificateAuthorityRequest.hasHmac()) {
|
||||
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(HMAC_FIELD_MUST_BE_SET), Response.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tlsCertificateAuthorityRequest.hasCsr()) {
|
||||
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(CSR_FIELD_MUST_BE_SET), Response.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.parseCsr(tlsCertificateAuthorityRequest.getCsr());
|
||||
byte[] expectedHmac = TlsHelper.calculateHMac(token, jcaPKCS10CertificationRequest.getPublicKey());
|
||||
|
||||
if (MessageDigest.isEqual(expectedHmac, tlsCertificateAuthorityRequest.getHmac())) {
|
||||
String dn = jcaPKCS10CertificationRequest.getSubject().toString();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Received CSR with DN " + dn);
|
||||
}
|
||||
|
||||
final List<String> dnsNames = getDnsNames(jcaPKCS10CertificationRequest);
|
||||
X509Certificate x509Certificate = new StandardCertificateBuilder(keyPair, caCert.getSubjectX500Principal(), Duration.ofDays(days))
|
||||
.setSubject(new X500Principal(dn))
|
||||
.setSubjectPublicKey(jcaPKCS10CertificationRequest.getPublicKey())
|
||||
.setDnsSubjectAlternativeNames(dnsNames)
|
||||
.build();
|
||||
|
||||
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, caCert.getPublicKey()),
|
||||
TlsHelper.pemEncodeJcaObject(x509Certificate)), Response.SC_OK);
|
||||
} else {
|
||||
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(FORBIDDEN), Response.SC_FORBIDDEN);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ServletException("Server error");
|
||||
} finally {
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponse(ObjectMapper objectMapper, HttpServletRequest request, HttpServletResponse response, TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse,
|
||||
int responseCode) throws IOException {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(new StringBuilder("Returning code:").append(responseCode).append(" payload ").append(objectMapper.writeValueAsString(tlsCertificateAuthorityResponse))
|
||||
.append(" to ").append(request.getRemoteHost()).toString());
|
||||
}
|
||||
if (responseCode == Response.SC_OK) {
|
||||
objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
|
||||
response.setStatus(responseCode);
|
||||
} else {
|
||||
response.setStatus(responseCode);
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getDnsNames(final JcaPKCS10CertificationRequest request) {
|
||||
final Extensions extensions = request.getRequestedExtensions();
|
||||
final GeneralNames generalNames = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
|
||||
|
||||
final List<String> dnsNames = new ArrayList<>();
|
||||
for (final GeneralName generalName : generalNames.getNames()) {
|
||||
final ASN1Encodable nameEncoded = generalName.getName();
|
||||
final String name = IETFUtils.valueToString(nameEncoded);
|
||||
dnsNames.add(name);
|
||||
}
|
||||
|
||||
return dnsNames;
|
||||
}
|
||||
}
|
|
@ -1,339 +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.toolkit.tls.standalone;
|
||||
|
||||
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
|
||||
import org.apache.nifi.security.util.KeyStoreUtils;
|
||||
import org.apache.nifi.security.util.KeystoreType;
|
||||
import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition;
|
||||
import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.manager.TlsCertificateAuthorityManager;
|
||||
import org.apache.nifi.toolkit.tls.manager.TlsClientManager;
|
||||
import org.apache.nifi.toolkit.tls.manager.writer.NifiPropertiesTlsClientConfigWriter;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
|
||||
import org.bouncycastle.util.io.pem.PemWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SignatureException;
|
||||
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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class TlsToolkitStandalone {
|
||||
public static final String NIFI_KEY = "nifi-key";
|
||||
public static final String NIFI_CERT = "nifi-cert";
|
||||
public static final String NIFI_PROPERTIES = "nifi.properties";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsToolkitStandalone.class);
|
||||
private final OutputStreamFactory outputStreamFactory;
|
||||
|
||||
public TlsToolkitStandalone() {
|
||||
this(FileOutputStream::new);
|
||||
}
|
||||
|
||||
public TlsToolkitStandalone(OutputStreamFactory outputStreamFactory) {
|
||||
this.outputStreamFactory = outputStreamFactory;
|
||||
}
|
||||
|
||||
public void splitKeystore(StandaloneConfig standaloneConfig) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(new FileInputStream(standaloneConfig.getKeyStore()), standaloneConfig.getKeyStorePassword().toCharArray());
|
||||
|
||||
if(keyStore.size() == 0) {
|
||||
throw new KeyStoreException("Provided keystore " + standaloneConfig.getKeyStore() + " was empty. No cert/key pairs to output to file.");
|
||||
}
|
||||
|
||||
if(standaloneConfig.getKeyPassword() == null || standaloneConfig.getKeyPassword().isEmpty()) {
|
||||
splitKeystore(keyStore, standaloneConfig.getKeyStorePassword().toCharArray(), standaloneConfig.getBaseDir());
|
||||
} else {
|
||||
splitKeystore(keyStore, standaloneConfig.getKeyPassword().toCharArray(), standaloneConfig.getBaseDir());
|
||||
}
|
||||
}
|
||||
|
||||
private void splitKeystore(KeyStore keyStore, char[] keyPassphrase, File outputDirectory) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
|
||||
HashMap<String, Certificate> certificates = TlsHelper.extractCerts(keyStore);
|
||||
HashMap<String, Key> keys = TlsHelper.extractKeys(keyStore, keyPassphrase);
|
||||
TlsHelper.outputCertsAsPem(certificates, outputDirectory, ".crt");
|
||||
TlsHelper.outputKeysAsPem(keys, outputDirectory, ".key");
|
||||
}
|
||||
|
||||
public void createNifiKeystoresAndTrustStores(StandaloneConfig standaloneConfig) throws GeneralSecurityException, IOException {
|
||||
// TODO: This 200 line method should be refactored, as it is difficult to test the various validations separately from the filesystem interaction and generation logic
|
||||
File baseDir = standaloneConfig.getBaseDir();
|
||||
if (!baseDir.exists() && !baseDir.mkdirs()) {
|
||||
throw new IOException(baseDir + " doesn't exist and unable to create it.");
|
||||
}
|
||||
|
||||
if (!baseDir.isDirectory()) {
|
||||
throw new IOException("Expected directory to output to");
|
||||
}
|
||||
|
||||
int days = standaloneConfig.getDays();
|
||||
String keyPairAlgorithm = standaloneConfig.getKeyPairAlgorithm();
|
||||
int keySize = standaloneConfig.getKeySize();
|
||||
|
||||
File nifiCert = new File(baseDir, NIFI_CERT + ".pem");
|
||||
File nifiKey = new File(baseDir, NIFI_KEY + ".key");
|
||||
|
||||
X509Certificate certificate;
|
||||
KeyPair caKeyPair;
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Running standalone certificate generation with output directory " + baseDir);
|
||||
}
|
||||
if (nifiCert.exists()) {
|
||||
if (!nifiKey.exists()) {
|
||||
throw new IOException(nifiCert + " exists already, but " + nifiKey + " does not, we need both certificate and key to continue with an existing CA.");
|
||||
}
|
||||
try (FileReader pemEncodedCertificate = new FileReader(nifiCert)) {
|
||||
certificate = TlsHelper.parseCertificate(pemEncodedCertificate);
|
||||
}
|
||||
try (FileReader pemEncodedKeyPair = new FileReader(nifiKey)) {
|
||||
caKeyPair = TlsHelper.parseKeyPairFromReader(pemEncodedKeyPair);
|
||||
}
|
||||
|
||||
// TODO: Do same in client/server
|
||||
// Load additional signing certificates from config
|
||||
List<X509Certificate> signingCertificates = new ArrayList<>();
|
||||
|
||||
// Read the provided additional CA certificate if it exists and extract the certificate
|
||||
if (!StringUtils.isBlank(standaloneConfig.getAdditionalCACertificate())) {
|
||||
X509Certificate signingCertificate;
|
||||
final File additionalCACertFile = new File(standaloneConfig.getAdditionalCACertificate());
|
||||
if (!additionalCACertFile.exists()) {
|
||||
throw new IOException("The additional CA certificate does not exist at " + additionalCACertFile.getAbsolutePath());
|
||||
}
|
||||
try (FileReader pemEncodedCACertificate = new FileReader(additionalCACertFile)) {
|
||||
signingCertificate = TlsHelper.parseCertificate(pemEncodedCACertificate);
|
||||
}
|
||||
signingCertificates.add(signingCertificate);
|
||||
}
|
||||
|
||||
// Support self-signed CA certificates
|
||||
signingCertificates.add(certificate);
|
||||
|
||||
boolean signatureValid = TlsHelper.verifyCertificateSignature(certificate, signingCertificates);
|
||||
|
||||
if (!signatureValid) {
|
||||
throw new SignatureException("The signing certificate was not signed by any known certificates");
|
||||
}
|
||||
|
||||
if (!caKeyPair.getPublic().equals(certificate.getPublicKey())) {
|
||||
throw new IOException("Expected " + nifiKey + " to correspond to CA certificate at " + nifiCert);
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Using existing CA certificate " + nifiCert + " and key " + nifiKey);
|
||||
}
|
||||
} else if (nifiKey.exists()) {
|
||||
throw new IOException(nifiKey + " exists already, but " + nifiCert + " does not, we need both certificate and key to continue with an existing CA.");
|
||||
} else {
|
||||
TlsCertificateAuthorityManager tlsCertificateAuthorityManager = new TlsCertificateAuthorityManager(standaloneConfig);
|
||||
KeyStore.PrivateKeyEntry privateKeyEntry = tlsCertificateAuthorityManager.getOrGenerateCertificateAuthority();
|
||||
certificate = (X509Certificate) privateKeyEntry.getCertificateChain()[0];
|
||||
caKeyPair = new KeyPair(certificate.getPublicKey(), privateKeyEntry.getPrivateKey());
|
||||
|
||||
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiCert)))) {
|
||||
pemWriter.writeObject(new JcaMiscPEMGenerator(certificate));
|
||||
}
|
||||
|
||||
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiKey)))) {
|
||||
pemWriter.writeObject(new JcaMiscPEMGenerator(caKeyPair));
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Generated new CA certificate " + nifiCert + " and key " + nifiKey);
|
||||
}
|
||||
}
|
||||
|
||||
NiFiPropertiesWriterFactory niFiPropertiesWriterFactory = standaloneConfig.getNiFiPropertiesWriterFactory();
|
||||
boolean overwrite = standaloneConfig.isOverwrite();
|
||||
|
||||
List<InstanceDefinition> instanceDefinitions = standaloneConfig.getInstanceDefinitions();
|
||||
if (instanceDefinitions.isEmpty() && logger.isInfoEnabled()) {
|
||||
logger.info("No " + TlsToolkitStandaloneCommandLine.HOSTNAMES_ARG + " specified, not generating any host certificates or configuration.");
|
||||
}
|
||||
|
||||
List<String> domainAlternativeNames = standaloneConfig.getDomainAlternativeNames();
|
||||
|
||||
for (Integer instanceIndex : IntStream.range(0, instanceDefinitions.size()).boxed().collect(Collectors.toList())) {
|
||||
InstanceDefinition instanceDefinition = instanceDefinitions.get(instanceIndex);
|
||||
String hostname = instanceDefinition.getHostname();
|
||||
File hostDir;
|
||||
int hostIdentifierNumber = instanceDefinition.getInstanceIdentifier().getNumber();
|
||||
if (hostIdentifierNumber == 1) {
|
||||
hostDir = new File(baseDir, hostname);
|
||||
} else {
|
||||
hostDir = new File(baseDir, hostname + "_" + hostIdentifierNumber);
|
||||
}
|
||||
|
||||
TlsClientConfig tlsClientConfig = new TlsClientConfig(standaloneConfig);
|
||||
File keystore = new File(hostDir, "keystore." + tlsClientConfig.getKeyStoreType().toLowerCase());
|
||||
File truststore = new File(hostDir, "truststore." + tlsClientConfig.getTrustStoreType().toLowerCase());
|
||||
|
||||
// Adjust the SANs when ranges match.
|
||||
if (domainAlternativeNames.size() == 1) {
|
||||
tlsClientConfig.setDomainAlternativeNames(Collections.singletonList(domainAlternativeNames.get(0)));
|
||||
} else if (domainAlternativeNames.size() == instanceDefinitions.size()) {
|
||||
tlsClientConfig.setDomainAlternativeNames(Collections.singletonList(domainAlternativeNames.get(instanceIndex)));
|
||||
logger.info("Using alternate name " + domainAlternativeNames.get(instanceIndex) + " with hostname " + hostname + ".");
|
||||
} else if (domainAlternativeNames.size() > 0) {
|
||||
logger.warn("Hostname count does not match given alternate name count. Verify names in resulting certificate.");
|
||||
}
|
||||
|
||||
if (hostDir.exists()) {
|
||||
if (!hostDir.isDirectory()) {
|
||||
throw new IOException(hostDir + " exists but is not a directory.");
|
||||
} else if (overwrite) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Overwriting any existing ssl configuration in " + hostDir);
|
||||
}
|
||||
keystore.delete();
|
||||
if (keystore.exists()) {
|
||||
throw new IOException("Keystore " + keystore + " already exists and couldn't be deleted.");
|
||||
}
|
||||
truststore.delete();
|
||||
if (truststore.exists()) {
|
||||
throw new IOException("Truststore " + truststore + " already exists and couldn't be deleted.");
|
||||
}
|
||||
} else {
|
||||
throw new IOException(hostDir + " exists and overwrite is not set.");
|
||||
}
|
||||
} else if (!hostDir.mkdirs()) {
|
||||
throw new IOException("Unable to make directory: " + hostDir.getAbsolutePath());
|
||||
} else if (logger.isInfoEnabled()) {
|
||||
logger.info("Writing new ssl configuration to " + hostDir);
|
||||
}
|
||||
|
||||
tlsClientConfig.setKeyStore(keystore.getAbsolutePath());
|
||||
tlsClientConfig.setKeyStorePassword(instanceDefinition.getKeyStorePassword());
|
||||
tlsClientConfig.setKeyPassword(instanceDefinition.getKeyPassword());
|
||||
tlsClientConfig.setTrustStore(truststore.getAbsolutePath());
|
||||
tlsClientConfig.setTrustStorePassword(instanceDefinition.getTrustStorePassword());
|
||||
TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig);
|
||||
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
|
||||
|
||||
final List<String> dnsNames = new ArrayList<>(tlsClientConfig.getDomainAlternativeNames());
|
||||
final String defaultDn = tlsClientConfig.calcDefaultDn(hostname);
|
||||
dnsNames.add(hostname);
|
||||
|
||||
final X509Certificate clientCertificate = new StandardCertificateBuilder(caKeyPair, certificate.getSubjectX500Principal(), Duration.ofDays(days))
|
||||
.setSubject(new X500Principal(defaultDn))
|
||||
.setSubjectPublicKey(keyPair.getPublic())
|
||||
.setDnsSubjectAlternativeNames(dnsNames)
|
||||
.build();
|
||||
|
||||
tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, clientCertificate, certificate);
|
||||
tlsClientManager.setCertificateEntry(NIFI_CERT, certificate);
|
||||
tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new File(hostDir, "nifi.properties"),
|
||||
hostname, instanceDefinition.getNumber()));
|
||||
tlsClientManager.write(outputStreamFactory);
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Successfully generated TLS configuration for " + hostname + " " + hostIdentifierNumber + " in " + hostDir);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> clientDns = standaloneConfig.getClientDns();
|
||||
if (standaloneConfig.getClientDns().isEmpty() && logger.isInfoEnabled()) {
|
||||
logger.info("No " + TlsToolkitStandaloneCommandLine.CLIENT_CERT_DN_ARG + " specified, not generating any client certificates.");
|
||||
}
|
||||
|
||||
List<String> clientPasswords = standaloneConfig.getClientPasswords();
|
||||
for (int i = 0; i < clientDns.size(); i++) {
|
||||
String clientDn = clientDns.get(i);
|
||||
String clientDnFile = TlsHelper.escapeFilename(clientDn);
|
||||
File clientCertFile = new File(baseDir, clientDnFile + ".p12");
|
||||
|
||||
if (clientCertFile.exists()) {
|
||||
if (overwrite) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Overwriting existing client cert " + clientCertFile);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(clientCertFile + " exists and overwrite is not set.");
|
||||
}
|
||||
} else if (logger.isInfoEnabled()) {
|
||||
logger.info("Generating new client certificate " + clientCertFile);
|
||||
}
|
||||
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
|
||||
X509Certificate clientCert = new StandardCertificateBuilder(caKeyPair, certificate.getIssuerX500Principal(), Duration.ofDays(days))
|
||||
.setSubject(new X500Principal(clientDn))
|
||||
.setSubjectPublicKey(keyPair.getPublic())
|
||||
.build();
|
||||
final String keyStorePassword = clientPasswords.get(i);
|
||||
final KeyStore keyStore = setClientKeyStore(keyStorePassword, keyPair.getPrivate(), clientCert, certificate);
|
||||
String password = TlsHelper.writeKeyStore(keyStore, outputStreamFactory, clientCertFile, keyStorePassword, standaloneConfig.isClientPasswordsGenerated());
|
||||
|
||||
try (FileWriter fileWriter = new FileWriter(new File(baseDir, clientDnFile + ".password"))) {
|
||||
fileWriter.write(password);
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Successfully generated client certificate " + clientCertFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("tls-toolkit standalone completed successfully");
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyStore setClientKeyStore(
|
||||
final String keyStorePassword,
|
||||
final PrivateKey privateKey,
|
||||
final X509Certificate clientCertificate,
|
||||
final X509Certificate issuerCertificate
|
||||
) throws IOException, GeneralSecurityException {
|
||||
final KeyStore keyStore = KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString());
|
||||
keyStore.load(null, null);
|
||||
final char[] keyPassword = keyStorePassword.toCharArray();
|
||||
final X509Certificate[] certificates = {clientCertificate, issuerCertificate};
|
||||
keyStore.setKeyEntry(NIFI_KEY, privateKey, keyPassword, certificates);
|
||||
return keyStore;
|
||||
}
|
||||
}
|
|
@ -1,301 +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.toolkit.tls.standalone;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.BaseTlsToolkitCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
|
||||
import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition;
|
||||
import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.PasswordUtil;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Command line parser for a StandaloneConfig object and a main entry point to invoke the parser and run the standalone generator
|
||||
*/
|
||||
public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine {
|
||||
public static final String OUTPUT_DIRECTORY_ARG = "outputDirectory";
|
||||
public static final String NIFI_PROPERTIES_FILE_ARG = "nifiPropertiesFile";
|
||||
public static final String KEY_STORE_PASSWORD_ARG = "keyStorePassword";
|
||||
public static final String TRUST_STORE_PASSWORD_ARG = "trustStorePassword";
|
||||
public static final String KEY_PASSWORD_ARG = "keyPassword";
|
||||
public static final String HOSTNAMES_ARG = "hostnames";
|
||||
public static final String OVERWRITE_ARG = "isOverwrite";
|
||||
public static final String CLIENT_CERT_DN_ARG = "clientCertDn";
|
||||
public static final String CLIENT_CERT_PASSWORD_ARG = "clientCertPassword";
|
||||
public static final String GLOBAL_PORT_SEQUENCE_ARG = "globalPortSequence";
|
||||
public static final String NIFI_DN_PREFIX_ARG = "nifiDnPrefix";
|
||||
public static final String NIFI_DN_SUFFIX_ARG = "nifiDnSuffix";
|
||||
public static final String SUBJECT_ALTERNATIVE_NAMES_ARG = "subjectAlternativeNames";
|
||||
public static final String ADDITIONAL_CA_CERTIFICATE_ARG = "additionalCACertificate";
|
||||
public static final String SPLIT_KEYSTORE_ARG = "splitKeystore";
|
||||
|
||||
public static final String DEFAULT_OUTPUT_DIRECTORY = calculateDefaultOutputDirectory(Paths.get("."));
|
||||
|
||||
protected static String calculateDefaultOutputDirectory(Path currentPath) {
|
||||
Path currentAbsolutePath = currentPath.toAbsolutePath();
|
||||
Path parent = currentAbsolutePath.getParent();
|
||||
if (currentAbsolutePath.getRoot().equals(parent)) {
|
||||
return parent.toString();
|
||||
} else {
|
||||
Path currentNormalizedPath = currentAbsolutePath.normalize();
|
||||
return "../" + currentNormalizedPath.getFileName().toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static final String DESCRIPTION = "Creates certificates and config files for nifi cluster.";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsToolkitStandaloneCommandLine.class);
|
||||
|
||||
private final PasswordUtil passwordUtil;
|
||||
private File baseDir;
|
||||
private List<InstanceDefinition> instanceDefinitions;
|
||||
private NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
|
||||
private List<String> clientDns;
|
||||
private List<String> clientPasswords;
|
||||
private boolean clientPasswordsGenerated;
|
||||
private boolean overwrite;
|
||||
private boolean splitKeystore = false;
|
||||
private String splitKeystoreFile;
|
||||
private String dnPrefix;
|
||||
private String dnSuffix;
|
||||
private List<InstanceDefinition> domainAlternativeNames;
|
||||
private String additionalCACertificatePath;
|
||||
private String keyPassword;
|
||||
private String keyStorePassword;
|
||||
|
||||
public TlsToolkitStandaloneCommandLine() {
|
||||
this(new PasswordUtil());
|
||||
}
|
||||
|
||||
protected TlsToolkitStandaloneCommandLine(PasswordUtil passwordUtil) {
|
||||
super(DESCRIPTION);
|
||||
this.passwordUtil = passwordUtil;
|
||||
addOptionWithArg("o", OUTPUT_DIRECTORY_ARG, "The directory to output keystores, truststore, config files.", DEFAULT_OUTPUT_DIRECTORY);
|
||||
addOptionWithArg("n", HOSTNAMES_ARG, "Comma separated list of hostnames.");
|
||||
addOptionWithArg("f", NIFI_PROPERTIES_FILE_ARG, "Base nifi.properties file to update. (Embedded file identical to the one in a default NiFi install will be used if not specified.)");
|
||||
addOptionWithArg("S", KEY_STORE_PASSWORD_ARG, "Keystore password to use. Must either be one value or one for each host. (autogenerate if not specified)");
|
||||
addOptionWithArg("K", KEY_PASSWORD_ARG, "Key password to use. Must either be one value or one for each host. (autogenerate if not specified)");
|
||||
addOptionWithArg("P", TRUST_STORE_PASSWORD_ARG, "Keystore password to use. Must either be one value or one for each host. (autogenerate if not specified)");
|
||||
addOptionWithArg("C", CLIENT_CERT_DN_ARG, "Generate client certificate suitable for use in browser with specified DN. (Can be specified multiple times.)");
|
||||
addOptionWithArg("B", CLIENT_CERT_PASSWORD_ARG, "Password for client certificate. Must either be one value or one for each client DN. (autogenerate if not specified)");
|
||||
addOptionWithArg("G", GLOBAL_PORT_SEQUENCE_ARG, "Use sequential ports that are calculated for all hosts according to the provided hostname expressions. " +
|
||||
"(Can be specified multiple times, MUST BE SAME FROM RUN TO RUN.)");
|
||||
addOptionWithArg(null, SUBJECT_ALTERNATIVE_NAMES_ARG, "Comma-separated list of domains to use as Subject Alternative Names in the certificate");
|
||||
addOptionWithArg(null, NIFI_DN_PREFIX_ARG, "String to prepend to hostname(s) when determining DN.", TlsConfig.DEFAULT_DN_PREFIX);
|
||||
addOptionWithArg(null, NIFI_DN_SUFFIX_ARG, "String to append to hostname(s) when determining DN.", TlsConfig.DEFAULT_DN_SUFFIX);
|
||||
addOptionNoArg("O", OVERWRITE_ARG, "Overwrite existing host output.");
|
||||
addOptionWithArg(null, ADDITIONAL_CA_CERTIFICATE_ARG, "Path to additional CA certificate (used to sign toolkit CA certificate) in PEM format if necessary");
|
||||
addOptionWithArg(null, SPLIT_KEYSTORE_ARG, "Split out a given keystore into its unencrypted key and certificates. Use -S and -K to specify the keystore and key passwords.");
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine();
|
||||
try {
|
||||
tlsToolkitStandaloneCommandLine.parse(args);
|
||||
} catch (CommandLineParseException e) {
|
||||
System.exit(e.getExitCode().ordinal());
|
||||
} catch (IllegalArgumentException e) {
|
||||
tlsToolkitStandaloneCommandLine.printUsage("Error parsing command line. (" + e.getMessage() + ")");
|
||||
System.exit(ExitCode.ERROR_PARSING_INT_ARG.ordinal());
|
||||
}
|
||||
|
||||
if(tlsToolkitStandaloneCommandLine.splitKeystore) {
|
||||
StandaloneConfig conf = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig();
|
||||
try {
|
||||
new TlsToolkitStandalone().splitKeystore(conf);
|
||||
} catch (Exception e) {
|
||||
tlsToolkitStandaloneCommandLine.printUsage("Error splitting keystore. (" + e.getMessage() + ")");
|
||||
System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig());
|
||||
} catch (Exception e) {
|
||||
tlsToolkitStandaloneCommandLine.printUsage("Error generating TLS configuration. (" + e.getMessage() + ")");
|
||||
System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(ExitCode.SUCCESS.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandLine doParse(String... args) throws CommandLineParseException {
|
||||
CommandLine commandLine = super.doParse(args);
|
||||
String outputDirectory = commandLine.getOptionValue(OUTPUT_DIRECTORY_ARG, DEFAULT_OUTPUT_DIRECTORY);
|
||||
baseDir = new File(outputDirectory);
|
||||
|
||||
dnPrefix = commandLine.getOptionValue(NIFI_DN_PREFIX_ARG, TlsConfig.DEFAULT_DN_PREFIX);
|
||||
dnSuffix = commandLine.getOptionValue(NIFI_DN_SUFFIX_ARG, TlsConfig.DEFAULT_DN_SUFFIX);
|
||||
|
||||
Stream<String> globalOrderExpressions = null;
|
||||
if (commandLine.hasOption(GLOBAL_PORT_SEQUENCE_ARG)) {
|
||||
globalOrderExpressions = Arrays.stream(commandLine.getOptionValues(GLOBAL_PORT_SEQUENCE_ARG)).flatMap(s -> Arrays.stream(s.split(","))).map(String::trim);
|
||||
}
|
||||
|
||||
if (commandLine.hasOption(HOSTNAMES_ARG)) {
|
||||
instanceDefinitions = Collections.unmodifiableList(
|
||||
InstanceDefinition.createDefinitions(globalOrderExpressions,
|
||||
Arrays.stream(commandLine.getOptionValues(HOSTNAMES_ARG)).flatMap(s -> Arrays.stream(s.split(",")).map(String::trim)),
|
||||
parsePasswordSupplier(commandLine, KEY_STORE_PASSWORD_ARG, passwordUtil.passwordSupplier()),
|
||||
parsePasswordSupplier(commandLine, KEY_PASSWORD_ARG, commandLine.hasOption(DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG) ? passwordUtil.passwordSupplier() : null),
|
||||
parsePasswordSupplier(commandLine, TRUST_STORE_PASSWORD_ARG, passwordUtil.passwordSupplier())));
|
||||
} else {
|
||||
instanceDefinitions = Collections.emptyList();
|
||||
}
|
||||
|
||||
if (commandLine.hasOption(SUBJECT_ALTERNATIVE_NAMES_ARG)) {
|
||||
domainAlternativeNames = Collections.unmodifiableList(
|
||||
InstanceDefinition.createDefinitions(globalOrderExpressions,
|
||||
Arrays.stream(commandLine.getOptionValues(SUBJECT_ALTERNATIVE_NAMES_ARG)).flatMap(s -> Arrays.stream(s.split(",")).map(String::trim)),
|
||||
parsePasswordSupplier(commandLine, KEY_STORE_PASSWORD_ARG, passwordUtil.passwordSupplier()),
|
||||
parsePasswordSupplier(commandLine, KEY_PASSWORD_ARG, commandLine.hasOption(DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG) ? passwordUtil.passwordSupplier() : null),
|
||||
parsePasswordSupplier(commandLine, TRUST_STORE_PASSWORD_ARG, passwordUtil.passwordSupplier())));
|
||||
} else {
|
||||
domainAlternativeNames = Collections.emptyList();
|
||||
}
|
||||
|
||||
String[] clientDnValues = commandLine.getOptionValues(CLIENT_CERT_DN_ARG);
|
||||
if (clientDnValues != null) {
|
||||
clientDns = Collections.unmodifiableList(Arrays.stream(clientDnValues).collect(Collectors.toList()));
|
||||
} else {
|
||||
clientDns = Collections.emptyList();
|
||||
}
|
||||
|
||||
clientPasswords = Collections.unmodifiableList(getPasswords(CLIENT_CERT_PASSWORD_ARG, commandLine, clientDns.size(), CLIENT_CERT_DN_ARG));
|
||||
clientPasswordsGenerated = commandLine.getOptionValues(CLIENT_CERT_PASSWORD_ARG) == null;
|
||||
overwrite = commandLine.hasOption(OVERWRITE_ARG);
|
||||
|
||||
if(commandLine.hasOption(SPLIT_KEYSTORE_ARG)) {
|
||||
if(commandLine.hasOption(KEY_STORE_PASSWORD_ARG)) {
|
||||
splitKeystoreFile = commandLine.getOptionValue(SPLIT_KEYSTORE_ARG);
|
||||
keyStorePassword = commandLine.getOptionValue(KEY_STORE_PASSWORD_ARG);
|
||||
keyPassword = commandLine.getOptionValue(KEY_PASSWORD_ARG);
|
||||
splitKeystore = true;
|
||||
} else {
|
||||
printUsageAndThrow("-splitKeystore specified but no keyStorePassword supplied.", ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS);
|
||||
}
|
||||
}
|
||||
|
||||
additionalCACertificatePath = commandLine.getOptionValue(ADDITIONAL_CA_CERTIFICATE_ARG);
|
||||
|
||||
String nifiPropertiesFile = commandLine.getOptionValue(NIFI_PROPERTIES_FILE_ARG, "");
|
||||
try {
|
||||
if (StringUtils.isEmpty(nifiPropertiesFile)) {
|
||||
logger.info("No " + NIFI_PROPERTIES_FILE_ARG + " specified, using embedded one.");
|
||||
niFiPropertiesWriterFactory = new NiFiPropertiesWriterFactory();
|
||||
} else {
|
||||
logger.info("Using " + nifiPropertiesFile + " as template.");
|
||||
niFiPropertiesWriterFactory = new NiFiPropertiesWriterFactory(new FileInputStream(nifiPropertiesFile));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
printUsageAndThrow("Unable to read nifi.properties from " + (StringUtils.isEmpty(nifiPropertiesFile) ? "classpath" : nifiPropertiesFile), ExitCode.ERROR_READING_NIFI_PROPERTIES);
|
||||
}
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
private List<String> getPasswords(String arg, CommandLine commandLine, int num, String numArg) throws CommandLineParseException {
|
||||
String[] optionValues = commandLine.getOptionValues(arg);
|
||||
if (optionValues == null) {
|
||||
return IntStream.range(0, num).mapToObj(operand -> passwordUtil.generatePassword()).collect(Collectors.toList());
|
||||
}
|
||||
if (optionValues.length == 1) {
|
||||
return IntStream.range(0, num).mapToObj(value -> optionValues[0]).collect(Collectors.toList());
|
||||
} else if (optionValues.length == num) {
|
||||
return Arrays.stream(optionValues).collect(Collectors.toList());
|
||||
}
|
||||
return printUsageAndThrow("Expected either 1 value or " + num + " (the number of " + numArg + ") values for " + arg, ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS);
|
||||
}
|
||||
|
||||
private Supplier<String> parsePasswordSupplier(CommandLine commandLine, String option, Supplier<String> defaultSupplier) {
|
||||
if (commandLine.hasOption(option)) {
|
||||
String[] values = commandLine.getOptionValues(option);
|
||||
if (values.length == 1) {
|
||||
return PasswordUtil.passwordSupplier(values[0]);
|
||||
} else {
|
||||
return PasswordUtil.passwordSupplier("Provided " + option + " exhausted, please don't specify " + option
|
||||
+ ", specify one value to be used for all NiFi instances, or specify one value for each NiFi instance.", values);
|
||||
}
|
||||
} else {
|
||||
return defaultSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the StandaloneConfig for use in running TlsToolkitStandalone
|
||||
*
|
||||
* @return the StandaloneConfig for use in running TlsToolkitStandalone
|
||||
*/
|
||||
public StandaloneConfig createConfig() {
|
||||
StandaloneConfig standaloneConfig = new StandaloneConfig();
|
||||
|
||||
standaloneConfig.setBaseDir(baseDir);
|
||||
standaloneConfig.setNiFiPropertiesWriterFactory(niFiPropertiesWriterFactory);
|
||||
standaloneConfig.setInstanceDefinitions(instanceDefinitions);
|
||||
standaloneConfig.setOverwrite(overwrite);
|
||||
standaloneConfig.setClientDns(clientDns);
|
||||
standaloneConfig.setClientPasswords(clientPasswords);
|
||||
standaloneConfig.setClientPasswordsGenerated(clientPasswordsGenerated);
|
||||
|
||||
standaloneConfig.setCaHostname(getCertificateAuthorityHostname());
|
||||
standaloneConfig.setKeyStore("nifi-ca-" + KEYSTORE + getKeyStoreType().toLowerCase());
|
||||
standaloneConfig.setKeyStoreType(getKeyStoreType());
|
||||
standaloneConfig.setKeySize(getKeySize());
|
||||
standaloneConfig.setKeyPairAlgorithm(getKeyAlgorithm());
|
||||
standaloneConfig.setSigningAlgorithm(getSigningAlgorithm());
|
||||
standaloneConfig.setDays(getDays());
|
||||
standaloneConfig.setDnPrefix(dnPrefix);
|
||||
standaloneConfig.setDnSuffix(dnSuffix);
|
||||
standaloneConfig.setDomainAlternativeNames(domainAlternativeNames.stream().map(InstanceDefinition::getHostname).collect(Collectors.toList()));
|
||||
standaloneConfig.setAdditionalCACertificate(additionalCACertificatePath);
|
||||
standaloneConfig.initDefaults();
|
||||
|
||||
return standaloneConfig;
|
||||
}
|
||||
|
||||
public StandaloneConfig createSplitKeystoreConfig() {
|
||||
StandaloneConfig splitKeystoreConfig = new StandaloneConfig();
|
||||
splitKeystoreConfig.setBaseDir(baseDir);
|
||||
splitKeystoreConfig.setKeyPassword(keyPassword);
|
||||
splitKeystoreConfig.setKeyStorePassword(keyStorePassword);
|
||||
splitKeystoreConfig.setKeyStore(splitKeystoreFile);
|
||||
splitKeystoreConfig.setSplitKeystore(splitKeystore);
|
||||
|
||||
return splitKeystoreConfig;
|
||||
}
|
||||
}
|
|
@ -1,50 +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.toolkit.tls.status;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.configuration.GetStatusConfig;
|
||||
import org.glassfish.jersey.client.ClientConfig;
|
||||
import org.glassfish.jersey.client.ClientProperties;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class TlsToolkitGetStatus {
|
||||
|
||||
public void get(final GetStatusConfig config) {
|
||||
final SSLContext sslContext = config.getSslContext();
|
||||
|
||||
final ClientBuilder clientBuilder = ClientBuilder.newBuilder();
|
||||
if (sslContext != null) {
|
||||
clientBuilder.sslContext(sslContext);
|
||||
}
|
||||
|
||||
final ClientConfig clientConfig = new ClientConfig();
|
||||
clientConfig.property(ClientProperties.CONNECT_TIMEOUT, 10000);
|
||||
clientConfig.property(ClientProperties.READ_TIMEOUT, 10000);
|
||||
clientBuilder.withConfig(clientConfig);
|
||||
|
||||
final Client client = clientBuilder.build();
|
||||
final WebTarget target = client.target(config.getUrl());
|
||||
final Response response = target.request().get();
|
||||
System.out.println("Response Code - " + response.getStatus());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,142 +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.toolkit.tls.status;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
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.TlsPlatform;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
|
||||
import org.apache.nifi.toolkit.tls.configuration.GetStatusConfig;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TlsToolkitGetStatusCommandLine extends BaseCommandLine {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TlsToolkitGetStatusCommandLine.class);
|
||||
|
||||
public static final String URL_ARG = "url";
|
||||
public static final String KEYSTORE_ARG = "keyStore";
|
||||
public static final String KEYSTORE_TYPE_ARG = "keyStoreType";
|
||||
public static final String KEYSTORE_PASSWORD_ARG = "keyStorePassword";
|
||||
public static final String KEY_PASSWORD_ARG = "keyPassword";
|
||||
public static final String TRUSTSTORE_ARG = "trustStore";
|
||||
public static final String TRUSTSTORE_TYPE_ARG = "trustStoreType";
|
||||
public static final String TRUSTSTORE_PASSWORD_ARG = "trustStorePassword";
|
||||
public static final String PROTOCOL_ARG = "protocol";
|
||||
|
||||
public static final String DEFAULT_PROTOCOL = TlsPlatform.getLatestProtocol();
|
||||
public static final String DEFAULT_KEYSTORE_TYPE = "JKS";
|
||||
|
||||
public static final String DESCRIPTION = "Checks the status of an HTTPS endpoint by making a GET request using a supplied keystore and truststore.";
|
||||
|
||||
|
||||
private URI url;
|
||||
private SSLContext sslContext;
|
||||
|
||||
public TlsToolkitGetStatusCommandLine() {
|
||||
super(DESCRIPTION);
|
||||
addOptionWithArg("u", URL_ARG, "The full url to connect to, for example: https://localhost:9443/v1/api");
|
||||
addOptionWithArg("ks", KEYSTORE_ARG, "The key store to use");
|
||||
addOptionWithArg("kst", KEYSTORE_TYPE_ARG, "The type of key store being used (PKCS12 or JKS)", DEFAULT_KEYSTORE_TYPE);
|
||||
addOptionWithArg("ksp", KEYSTORE_PASSWORD_ARG, "The password of the key store being used");
|
||||
addOptionWithArg("kp", KEY_PASSWORD_ARG, "The key password of the key store being used");
|
||||
addOptionWithArg("ts", TRUSTSTORE_ARG, "The trust store being used");
|
||||
addOptionWithArg("tst", TRUSTSTORE_TYPE_ARG, "The type of trust store being used (PKCS12 or JKS)", DEFAULT_KEYSTORE_TYPE);
|
||||
addOptionWithArg("tsp", TRUSTSTORE_PASSWORD_ARG, "The password of the trust store being used");
|
||||
addOptionWithArg("p", PROTOCOL_ARG, "The protocol to use", DEFAULT_PROTOCOL);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TlsToolkitGetStatusCommandLine commandLine = new TlsToolkitGetStatusCommandLine();
|
||||
try {
|
||||
commandLine.parse(args);
|
||||
} catch (CommandLineParseException e) {
|
||||
System.exit(e.getExitCode().ordinal());
|
||||
}
|
||||
|
||||
final GetStatusConfig config = commandLine.createConfig();
|
||||
try {
|
||||
final TlsToolkitGetStatus tlsToolkitGetStatus = new TlsToolkitGetStatus();
|
||||
tlsToolkitGetStatus.get(config);
|
||||
} catch (Exception e) {
|
||||
commandLine.printUsage("Error communicating with " + config.getUrl().toString() + " (" + e.getMessage() + ")");
|
||||
System.exit(ExitCode.SERVICE_ERROR.ordinal());
|
||||
}
|
||||
System.exit(ExitCode.SUCCESS.ordinal());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void postParse(CommandLine commandLine) throws CommandLineParseException {
|
||||
super.postParse(commandLine);
|
||||
|
||||
final String urlValue = commandLine.getOptionValue(URL_ARG);
|
||||
if (StringUtils.isBlank(urlValue)) {
|
||||
printUsageAndThrow("Url was missing or blank", ExitCode.INVALID_ARGS);
|
||||
}
|
||||
|
||||
try {
|
||||
this.url = new URI(urlValue);
|
||||
} catch (URISyntaxException e) {
|
||||
printUsageAndThrow("Invalid Url", ExitCode.INVALID_ARGS);
|
||||
}
|
||||
|
||||
// TODO: Refactor this whole thing
|
||||
final String keystoreFilename = commandLine.getOptionValue(KEYSTORE_ARG);
|
||||
String keystoreTypeStr = commandLine.getOptionValue(KEYSTORE_TYPE_ARG, DEFAULT_KEYSTORE_TYPE);
|
||||
final String keystorePassword = commandLine.getOptionValue(KEYSTORE_PASSWORD_ARG);
|
||||
final String keyPassword = commandLine.getOptionValue(KEY_PASSWORD_ARG);
|
||||
|
||||
final String truststoreFilename = commandLine.getOptionValue(TRUSTSTORE_ARG);
|
||||
final String truststoreTypeStr = commandLine.getOptionValue(TRUSTSTORE_TYPE_ARG, DEFAULT_KEYSTORE_TYPE);
|
||||
final String truststorePassword = commandLine.getOptionValue(TRUSTSTORE_PASSWORD_ARG);
|
||||
|
||||
final String protocol = commandLine.getOptionValue(PROTOCOL_ARG, DEFAULT_PROTOCOL);
|
||||
|
||||
// This use case specifically allows truststore configuration without keystore configuration, but attempts to default the keystore type value
|
||||
if (StringUtils.isBlank(keystoreFilename)) {
|
||||
keystoreTypeStr = null;
|
||||
}
|
||||
|
||||
try {
|
||||
TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(keystoreFilename, keystorePassword, keyPassword, keystoreTypeStr,
|
||||
truststoreFilename, truststorePassword, truststoreTypeStr, protocol);
|
||||
|
||||
if (tlsConfiguration.isAnyTruststorePopulated()) {
|
||||
this.sslContext = SslContextFactory.createSslContext(tlsConfiguration);
|
||||
} else {
|
||||
printUsageAndThrow("No truststore was provided", ExitCode.INVALID_ARGS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
printUsageAndThrow("Failed to create SSL Context: " + e.getMessage(), ExitCode.INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
public GetStatusConfig createConfig() {
|
||||
return new GetStatusConfig(url, sslContext);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +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.toolkit.tls.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface InputStreamFactory {
|
||||
InputStream create(File file) throws FileNotFoundException;
|
||||
}
|
|
@ -1,26 +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.toolkit.tls.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface OutputStreamFactory {
|
||||
OutputStream create(File file) throws FileNotFoundException;
|
||||
}
|
|
@ -1,65 +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.toolkit.tls.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class PasswordUtil {
|
||||
private final SecureRandom secureRandom;
|
||||
|
||||
public PasswordUtil() {
|
||||
this(new SecureRandom());
|
||||
}
|
||||
|
||||
public PasswordUtil(SecureRandom secureRandom) {
|
||||
this.secureRandom = secureRandom;
|
||||
}
|
||||
|
||||
public String generatePassword() {
|
||||
byte[] bytes = new byte[32];
|
||||
secureRandom.nextBytes(bytes);
|
||||
String string = Base64.getEncoder().encodeToString(bytes);
|
||||
while (string.endsWith("=")) {
|
||||
string = string.substring(0, string.length() - 1);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
public Supplier<String> passwordSupplier() {
|
||||
return () -> generatePassword();
|
||||
}
|
||||
|
||||
public static Supplier<String> passwordSupplier(String password) {
|
||||
return () -> password;
|
||||
}
|
||||
|
||||
public static Supplier<String> passwordSupplier(String exhaustedMessage, String[] passwords) {
|
||||
AtomicInteger index = new AtomicInteger(0);
|
||||
return () -> {
|
||||
int i = index.getAndIncrement();
|
||||
if (i < passwords.length) {
|
||||
return passwords[i];
|
||||
} else {
|
||||
throw new PasswordsExhaustedException(exhaustedMessage);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,24 +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.toolkit.tls.util;
|
||||
|
||||
public class PasswordsExhaustedException extends RuntimeException {
|
||||
public PasswordsExhaustedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,437 +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.toolkit.tls.util;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.DERNull;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
|
||||
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.Extensions;
|
||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.openssl.PEMException;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||
import org.bouncycastle.util.IPAddress;
|
||||
import org.bouncycastle.util.io.pem.PemWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
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.PublicKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class TlsHelper {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TlsHelper.class);
|
||||
private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
|
||||
public static final String JCE_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html";
|
||||
public static final String ILLEGAL_KEY_SIZE = "illegal key size";
|
||||
private static boolean isUnlimitedStrengthCryptographyEnabled;
|
||||
private static boolean isVerbose = true;
|
||||
|
||||
// Evaluate an unlimited strength algorithm to determine if we support the capability we have on the system
|
||||
static {
|
||||
try {
|
||||
isUnlimitedStrengthCryptographyEnabled = (Cipher.getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// if there are issues with this, we default back to the value established
|
||||
isUnlimitedStrengthCryptographyEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void logTruncationWarning(File file) {
|
||||
String fileToString = file.toString();
|
||||
String fileName = file.getName();
|
||||
logger.warn("**********************************************************************************");
|
||||
logger.warn(" WARNING!!!!");
|
||||
logger.warn("**********************************************************************************");
|
||||
logger.warn("Unlimited JCE Policy is not installed which means we cannot utilize a");
|
||||
logger.warn("PKCS12 password longer than 7 characters.");
|
||||
logger.warn("Autogenerated password has been reduced to 7 characters.");
|
||||
logger.warn("");
|
||||
logger.warn("Please strongly consider installing Unlimited JCE Policy at");
|
||||
logger.warn(JCE_URL);
|
||||
logger.warn("");
|
||||
logger.warn("Another alternative is to add a stronger password with the openssl tool to the");
|
||||
logger.warn("resulting client certificate: " + fileToString);
|
||||
logger.warn("");
|
||||
logger.warn("openssl pkcs12 -in '" + fileToString + "' -out '/tmp/" + fileName + "'");
|
||||
logger.warn("openssl pkcs12 -export -in '/tmp/" + fileName + "' -out '" + fileToString + "'");
|
||||
logger.warn("rm -f '/tmp/" + fileName + "'");
|
||||
logger.warn("");
|
||||
logger.warn("**********************************************************************************");
|
||||
|
||||
}
|
||||
|
||||
private TlsHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isUnlimitedStrengthCryptographyEnabled() {
|
||||
return isUnlimitedStrengthCryptographyEnabled;
|
||||
}
|
||||
|
||||
public static String writeKeyStore(KeyStore keyStore, OutputStreamFactory outputStreamFactory, File file, String password, boolean generatedPassword) throws IOException, GeneralSecurityException {
|
||||
try (OutputStream fileOutputStream = outputStreamFactory.create(file)) {
|
||||
keyStore.store(fileOutputStream, password.toCharArray());
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().toLowerCase().contains(ILLEGAL_KEY_SIZE) && !isUnlimitedStrengthCryptographyEnabled()) {
|
||||
if (generatedPassword) {
|
||||
file.delete();
|
||||
String truncatedPassword = password.substring(0, 7);
|
||||
try (OutputStream fileOutputStream = outputStreamFactory.create(file)) {
|
||||
keyStore.store(fileOutputStream, truncatedPassword.toCharArray());
|
||||
}
|
||||
logTruncationWarning(file);
|
||||
return truncatedPassword;
|
||||
} else {
|
||||
throw new GeneralSecurityException("Specified password for " + file + " too long to work without unlimited JCE policy installed."
|
||||
+ System.lineSeparator() + "Please see " + JCE_URL);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
public static HashMap<String, Certificate> extractCerts(KeyStore keyStore) throws KeyStoreException {
|
||||
HashMap<String, Certificate> certs = new HashMap<>();
|
||||
Enumeration<String> certAliases = keyStore.aliases();
|
||||
while(certAliases.hasMoreElements()) {
|
||||
String alias = certAliases.nextElement();
|
||||
certs.put(alias, keyStore.getCertificate(alias));
|
||||
}
|
||||
return certs;
|
||||
}
|
||||
|
||||
public static HashMap<String, Key> extractKeys(KeyStore keyStore, char[] privKeyPass) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
|
||||
HashMap<String, Key> keys = new HashMap<>();
|
||||
Enumeration<String> keyAliases = keyStore.aliases();
|
||||
while(keyAliases.hasMoreElements()) {
|
||||
String alias = keyAliases.nextElement();
|
||||
Key key = keyStore.getKey(alias, privKeyPass);
|
||||
if(key != null) {
|
||||
keys.put(alias, key);
|
||||
} else {
|
||||
logger.warn("Key does not exist: Certificate with alias '" + alias + "' had no private key.");
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
public static void outputCertsAsPem(HashMap<String, Certificate> certs, File directory, String extension) {
|
||||
certs.forEach((String alias, Certificate cert)->{
|
||||
try {
|
||||
TlsHelper.outputAsPem(cert, alias, directory, extension);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void outputKeysAsPem(HashMap<String, Key> keys, File directory, String extension) {
|
||||
keys.forEach((String alias, Key key) -> {
|
||||
try {
|
||||
TlsHelper.outputAsPem(key, alias, directory, extension);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void outputAsPem(Object pemObj, String filename, File directory, String extension) throws IOException {
|
||||
try (OutputStream outputStream = new FileOutputStream(new File(directory, TlsHelper.escapeFilename(filename) + extension))) {
|
||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(outputStreamWriter);
|
||||
JcaMiscPEMGenerator pemGen = new JcaMiscPEMGenerator(pemObj);
|
||||
pemWriter.writeObject(pemGen);
|
||||
pemWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyPairGenerator createKeyPairGenerator(String algorithm, int keySize) throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator instance = KeyPairGenerator.getInstance(algorithm);
|
||||
instance.initialize(keySize);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static byte[] calculateHMac(String token, PublicKey publicKey) throws GeneralSecurityException {
|
||||
if (token == null) {
|
||||
throw new IllegalArgumentException("Token cannot be null");
|
||||
}
|
||||
byte[] tokenBytes = token.getBytes(StandardCharsets.UTF_8);
|
||||
if (tokenBytes.length < 16) {
|
||||
throw new GeneralSecurityException("Token does not meet minimum size of 16 bytes.");
|
||||
}
|
||||
SecretKeySpec keySpec = new SecretKeySpec(tokenBytes, "RAW");
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(keySpec);
|
||||
return mac.doFinal(getKeyIdentifier(publicKey));
|
||||
}
|
||||
|
||||
public static byte[] getKeyIdentifier(PublicKey publicKey) throws NoSuchAlgorithmException {
|
||||
return new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey).getKeyIdentifier();
|
||||
}
|
||||
|
||||
public static String pemEncodeJcaObject(Object object) throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
try (PemWriter pemWriter = new PemWriter(writer)) {
|
||||
pemWriter.writeObject(new JcaMiscPEMGenerator(object));
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
public static JcaPKCS10CertificationRequest parseCsr(String pemEncodedCsr) throws IOException {
|
||||
try (PEMParser pemParser = new PEMParser(new StringReader(pemEncodedCsr))) {
|
||||
Object o = pemParser.readObject();
|
||||
if (!PKCS10CertificationRequest.class.isInstance(o)) {
|
||||
throw new IOException("Expecting instance of " + PKCS10CertificationRequest.class + " but got " + o);
|
||||
}
|
||||
return new JcaPKCS10CertificationRequest((PKCS10CertificationRequest) o);
|
||||
}
|
||||
}
|
||||
|
||||
public static X509Certificate parseCertificate(Reader pemEncodedCertificate) throws IOException, CertificateException {
|
||||
return new JcaX509CertificateConverter().getCertificate(parsePem(X509CertificateHolder.class, pemEncodedCertificate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parsed {@link KeyPair} from the provided {@link Reader}. The incoming format can be PKCS #8 or PKCS #1.
|
||||
*
|
||||
* @param pemKeyPairReader a reader with access to the serialized key pair
|
||||
* @return the key pair
|
||||
* @throws IOException if there is an error reading the key pair
|
||||
*/
|
||||
public static KeyPair parseKeyPairFromReader(Reader pemKeyPairReader) throws IOException {
|
||||
// Instantiate PEMParser from Reader
|
||||
try (PEMParser pemParser = new PEMParser(pemKeyPairReader)) {
|
||||
// Read the object (deserialize)
|
||||
Object parsedObject = pemParser.readObject();
|
||||
|
||||
// If this is an ASN.1 private key, it's in PKCS #8 format and wraps the actual RSA private key
|
||||
if (PrivateKeyInfo.class.isInstance(parsedObject)) {
|
||||
if (isVerbose()) {
|
||||
logger.info("Provided private key is in PKCS #8 format");
|
||||
}
|
||||
PEMKeyPair keyPair = convertPrivateKeyFromPKCS8ToPKCS1((PrivateKeyInfo) parsedObject);
|
||||
return getKeyPair(keyPair);
|
||||
} else if (PEMKeyPair.class.isInstance(parsedObject)) {
|
||||
// Already in PKCS #1 format
|
||||
return getKeyPair((PEMKeyPair)parsedObject);
|
||||
} else {
|
||||
logger.warn("Expected one of {} or {} but got {}", PrivateKeyInfo.class, PEMKeyPair.class, parsedObject.getClass());
|
||||
throw new IOException("Expected private key in PKCS #1 or PKCS #8 unencrypted format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link KeyPair} instance containing the {@link X509Certificate} public key and the {@link java.security.spec.PKCS8EncodedKeySpec} private key from the PEM-encoded {@link PEMKeyPair}.
|
||||
*
|
||||
* @param keyPair the key pair in PEM format
|
||||
* @return the key pair in a format which provides for direct access to the keys
|
||||
* @throws PEMException if there is an error converting the key pair
|
||||
*/
|
||||
private static KeyPair getKeyPair(PEMKeyPair keyPair) throws PEMException {
|
||||
return new JcaPEMKeyConverter().getKeyPair(keyPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link PEMKeyPair} object with direct access to the public and private keys given a PKCS #8 private key.
|
||||
*
|
||||
* @param privateKeyInfo the PKCS #8 private key info
|
||||
* @return the PKCS #1 public and private key pair
|
||||
* @throws IOException if there is an error converting the key pair
|
||||
*/
|
||||
private static PEMKeyPair convertPrivateKeyFromPKCS8ToPKCS1(PrivateKeyInfo privateKeyInfo) throws IOException {
|
||||
// Parse the key wrapping to determine the internal key structure
|
||||
ASN1Encodable asn1PrivateKey = privateKeyInfo.parsePrivateKey();
|
||||
|
||||
// Convert the parsed key to an RSA private key
|
||||
RSAPrivateKey keyStruct = RSAPrivateKey.getInstance(asn1PrivateKey);
|
||||
|
||||
// Create the RSA public key from the modulus and exponent
|
||||
RSAPublicKey pubSpec = new RSAPublicKey(
|
||||
keyStruct.getModulus(), keyStruct.getPublicExponent());
|
||||
|
||||
// Create an algorithm identifier for forming the key pair
|
||||
AlgorithmIdentifier algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
|
||||
if (isVerbose()) {
|
||||
logger.info("Converted private key from PKCS #8 to PKCS #1 RSA private key");
|
||||
}
|
||||
|
||||
// Create the key pair container
|
||||
return new PEMKeyPair(new SubjectPublicKeyInfo(algId, pubSpec), new PrivateKeyInfo(algId, keyStruct));
|
||||
}
|
||||
|
||||
public static <T> T parsePem(Class<T> clazz, Reader pemReader) throws IOException {
|
||||
try (PEMParser pemParser = new PEMParser(pemReader)) {
|
||||
Object object = pemParser.readObject();
|
||||
if (!clazz.isInstance(object)) {
|
||||
throw new IOException("Expected " + clazz + " but got " + object.getClass());
|
||||
}
|
||||
return (T) object;
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyPair generateKeyPair(String algorithm, int keySize) throws NoSuchAlgorithmException {
|
||||
return createKeyPairGenerator(algorithm, keySize).generateKeyPair();
|
||||
}
|
||||
|
||||
public static JcaPKCS10CertificationRequest generateCertificationRequest(String requestedDn, List<String> domainAlternativeNames,
|
||||
KeyPair keyPair, String signingAlgorithm) throws OperatorCreationException {
|
||||
JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(requestedDn), keyPair.getPublic());
|
||||
|
||||
// add Subject Alternative Name(s)
|
||||
try {
|
||||
jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, createDomainAlternativeNamesExtensions(domainAlternativeNames, requestedDn));
|
||||
} catch (IOException e) {
|
||||
throw new OperatorCreationException("Error while adding " + domainAlternativeNames + " as Subject Alternative Name.", e);
|
||||
}
|
||||
|
||||
JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signingAlgorithm);
|
||||
return new JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
|
||||
}
|
||||
|
||||
public static Extensions createDomainAlternativeNamesExtensions(List<String> domainAlternativeNames, String requestedDn) throws IOException {
|
||||
List<GeneralName> namesList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
final String cn = IETFUtils.valueToString(new X500Name(requestedDn).getRDNs(BCStyle.CN)[0].getFirst().getValue());
|
||||
namesList.add(new GeneralName(GeneralName.dNSName, cn));
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to extract CN from request DN: " + requestedDn, e);
|
||||
}
|
||||
|
||||
if (domainAlternativeNames != null) {
|
||||
for (String alternativeName : domainAlternativeNames) {
|
||||
namesList.add(new GeneralName(IPAddress.isValid(alternativeName) ? GeneralName.iPAddress : GeneralName.dNSName, alternativeName));
|
||||
}
|
||||
}
|
||||
|
||||
GeneralNames subjectAltNames = new GeneralNames(namesList.toArray(new GeneralName[]{}));
|
||||
ExtensionsGenerator extGen = new ExtensionsGenerator();
|
||||
extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
|
||||
return extGen.generate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes special characters (particularly forward and back slashes) from strings that become file names.
|
||||
*
|
||||
* @param filename A filename you plan to write to disk which needs to be escaped.
|
||||
* @return String with special characters converted to underscores.
|
||||
*/
|
||||
public static final String escapeFilename(String filename) {
|
||||
return filename.replaceAll("[^\\w\\.\\-\\=]+", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the {@code certificate} is signed by one of the {@code signingCertificates}. The list should
|
||||
* include the certificate itself to allow for self-signed certificates. If it does not, a self-signed certificate
|
||||
* will return {@code false}.
|
||||
*
|
||||
* @param certificate the certificate containing the signature being verified
|
||||
* @param signingCertificates a list of certificates which may have signed the certificate
|
||||
* @return true if one of the signing certificates did sign the certificate
|
||||
*/
|
||||
public static boolean verifyCertificateSignature(X509Certificate certificate, List<X509Certificate> signingCertificates) {
|
||||
String certificateDisplayInfo = getCertificateDisplayInfo(certificate);
|
||||
if (isVerbose()) {
|
||||
logger.info("Verifying the certificate signature for " + certificateDisplayInfo);
|
||||
}
|
||||
boolean signatureMatches = false;
|
||||
for (X509Certificate signingCert : signingCertificates) {
|
||||
final String signingCertDisplayInfo = getCertificateDisplayInfo(signingCert);
|
||||
try {
|
||||
if (isVerbose()) {
|
||||
logger.info("Attempting to verify certificate " + certificateDisplayInfo + " signature with " + signingCertDisplayInfo);
|
||||
}
|
||||
PublicKey pub = signingCert.getPublicKey();
|
||||
certificate.verify(pub);
|
||||
if (isVerbose()) {
|
||||
logger.info("Certificate was signed by " + signingCertDisplayInfo);
|
||||
}
|
||||
signatureMatches = true;
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
// Expected if the signature does not match
|
||||
if (isVerbose()) {
|
||||
logger.warn("Certificate " + certificateDisplayInfo + " not signed by " + signingCertDisplayInfo + " [" + e.getLocalizedMessage() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return signatureMatches;
|
||||
}
|
||||
|
||||
private static String getCertificateDisplayInfo(X509Certificate certificate) {
|
||||
return certificate.getSubjectX500Principal().getName();
|
||||
}
|
||||
|
||||
private static boolean isVerbose() {
|
||||
// TODO: When verbose mode is enabled via command-line flag, this will read the variable
|
||||
return isVerbose;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,133 +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.toolkit.tls.configuration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class InstanceDefinitionTest {
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionKeyPassword() {
|
||||
testCreateDefinition("testHostname", 4, "keyStorePassword", "keyPassword", "trustStorePassword");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionNoKeyPassword() {
|
||||
testCreateDefinition("testHostname", 5, "keyStorePassword", null, "trustStorePassword");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsSingleHostSingleName() {
|
||||
testCreateDefinitions(Collections.singletonList("hostname"), Collections.singletonList("hostname"), Collections.singletonList(1), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsSingleHostnameOneNumberInParens() {
|
||||
testCreateDefinitions(Collections.singletonList("hostname(20)"),
|
||||
IntStream.range(1, 21).mapToObj(operand -> "hostname").collect(Collectors.toList()),
|
||||
integerRange(1, 20).collect(Collectors.toList()), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsSingleHostnameTwoNumbersInParens() {
|
||||
testCreateDefinitions(Collections.singletonList("hostname(5-20)"),
|
||||
IntStream.range(5, 21).mapToObj(operand -> "hostname").collect(Collectors.toList()),
|
||||
integerRange(5, 20).collect(Collectors.toList()), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsMultipleHostnamesWithMultipleNumbers() {
|
||||
testCreateDefinitions(Collections.singletonList("host[10]name[02-5](20)"),
|
||||
integerRange(1, 10).flatMap(v -> integerRange(2, 5).flatMap(v2 -> integerRange(1, 20).map(v3 -> "host" + v + "name" + String.format("%02d", v2)))).collect(Collectors.toList()),
|
||||
integerRange(1, 10).flatMap(val -> integerRange(2, 5).flatMap(val2 -> integerRange(1, 20))).collect(Collectors.toList()), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsStream() {
|
||||
testCreateDefinitions(Arrays.asList("host", "name"), Arrays.asList("host", "name"), Arrays.asList(1, 1), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsStreamNonNullKeyPasswords() {
|
||||
testCreateDefinitions(Arrays.asList("host", "name"), Arrays.asList("host", "name"), Arrays.asList(1, 1), false);
|
||||
}
|
||||
|
||||
private Stream<Integer> integerRange(int start, int endInclusive) {
|
||||
return IntStream.range(start, endInclusive + 1).mapToObj(value -> value);
|
||||
}
|
||||
|
||||
private void testCreateDefinitions(List<String> hostnameExpressions, List<String> expectedHostnames, List<Integer> expectedNumbers, boolean nullForKeyPasswords) {
|
||||
List<String> keyStorePasswords = IntStream.range(0, expectedHostnames.size()).mapToObj(value -> "testKeyStorePassword" + value).collect(Collectors.toList());
|
||||
List<String> keyPasswords;
|
||||
if (nullForKeyPasswords) {
|
||||
keyPasswords = null;
|
||||
} else {
|
||||
keyPasswords = IntStream.range(0, expectedHostnames.size()).mapToObj(value -> "testKeyPassword" + value).collect(Collectors.toList());
|
||||
}
|
||||
List<String> trustStorePasswords = IntStream.range(0, expectedHostnames.size()).mapToObj(value -> "testTrustStorePassword" + value).collect(Collectors.toList());
|
||||
List<InstanceDefinition> instanceDefinitions = InstanceDefinition.createDefinitions(null, hostnameExpressions.stream(),
|
||||
mockSupplier(keyStorePasswords.toArray(new String[0])), keyPasswords == null ? null : mockSupplier(keyPasswords.toArray(new String[0])),
|
||||
mockSupplier(trustStorePasswords.toArray(new String[0])));
|
||||
testCreateDefinitionsOutput(instanceDefinitions, expectedHostnames, expectedNumbers, keyStorePasswords, keyPasswords, trustStorePasswords);
|
||||
}
|
||||
|
||||
private void testCreateDefinitionsOutput(List<InstanceDefinition> instanceDefinitions, List<String> expectedHostnames, List<Integer> expectedNumbers, List<String> keyStorePasswords,
|
||||
List<String> keyPasswords, List<String> trustStorePasswords) {
|
||||
assertEquals(instanceDefinitions.size(), expectedHostnames.size());
|
||||
for (int i = 0; i < instanceDefinitions.size(); i++) {
|
||||
assertDefinitionEquals(instanceDefinitions.get(i), expectedHostnames.get(i), expectedNumbers.get(i), keyStorePasswords.get(i),
|
||||
keyPasswords == null ? null : keyPasswords.get(i), trustStorePasswords.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void testCreateDefinition(String hostname, int num, String keyStorePassword, String keyPassword, String trustStorePassword) {
|
||||
InstanceDefinition definition = InstanceDefinition.createDefinition(new InstanceIdentifier(hostname, num), num, mockSupplier(keyStorePassword),
|
||||
keyPassword == null ? null : mockSupplier(keyPassword), mockSupplier(trustStorePassword));
|
||||
assertDefinitionEquals(definition, hostname, num, keyStorePassword, keyPassword, trustStorePassword);
|
||||
}
|
||||
|
||||
private void assertDefinitionEquals(InstanceDefinition definition, String hostname, int num, String keyStorePassword, String keyPassword, String trustStorePassword) {
|
||||
assertEquals(hostname, definition.getHostname());
|
||||
assertEquals(num, definition.getNumber());
|
||||
assertEquals(keyStorePassword, definition.getKeyStorePassword());
|
||||
assertEquals(keyPassword == null ? keyStorePassword : keyPassword, definition.getKeyPassword());
|
||||
assertEquals(trustStorePassword, definition.getTrustStorePassword());
|
||||
}
|
||||
|
||||
private <T> Supplier<T> mockSupplier(T... values) {
|
||||
Supplier<T> supplier = mock(Supplier.class);
|
||||
if (values.length == 1) {
|
||||
when(supplier.get()).thenReturn(values[0]);
|
||||
} else if (values.length > 1) {
|
||||
when(supplier.get()).thenReturn(values[0], Arrays.copyOfRange(values, 1, values.length));
|
||||
}
|
||||
return supplier;
|
||||
}
|
||||
}
|
|
@ -1,164 +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.toolkit.tls.configuration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class InstanceIdentifierTest {
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesSingle() {
|
||||
testExtractHostnames("test[1-3]", "test1", "test2", "test3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesPadding() {
|
||||
testExtractHostnames("test[0001-3]", "test0001", "test0002", "test0003");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesLowGreaterThanHigh() {
|
||||
testExtractHostnames("test[3-1]", "test3", "test2", "test1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesLowEqualToHigh() {
|
||||
testExtractHostnames("test[3-3]", "test3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesSingleNumber() {
|
||||
testExtractHostnames("test[2]", "test1", "test2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesSingleNumberPadding() {
|
||||
testExtractHostnames("test[002]", "test001", "test002");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesNoNumber() {
|
||||
assertThrows(IllegalArgumentException.class, () -> testExtractHostnames("test[]", "test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesMultiple() {
|
||||
testExtractHostnames("test[1-3]name[1-3]", "test1name1", "test1name2", "test1name3", "test2name1", "test2name2", "test2name3", "test3name1", "test3name2", "test3name3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesUnmatched() {
|
||||
assertThrows(IllegalArgumentException.class, () -> testExtractHostnames("test["));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesSpace() {
|
||||
assertThrows(IllegalArgumentException.class, () -> testExtractHostnames("test[ 1-2]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHostnamesMultipleHyphens() {
|
||||
assertThrows(IllegalArgumentException.class, () -> testExtractHostnames("test[1-2-3]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsSingleHostSingleName() {
|
||||
testCreateIdentifiers(Collections.singletonList("hostname"), Collections.singletonList("hostname"), Collections.singletonList(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsSingleHostnameOneNumberInParens() {
|
||||
testCreateIdentifiers(Collections.singletonList("hostname(20)"),
|
||||
IntStream.range(1, 21).mapToObj(operand -> "hostname").collect(Collectors.toList()),
|
||||
integerRange(1, 20).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsSingleHostnameTwoNumbersInParens() {
|
||||
testCreateIdentifiers(Collections.singletonList("hostname(5-20)"),
|
||||
IntStream.range(5, 21).mapToObj(operand -> "hostname").collect(Collectors.toList()),
|
||||
integerRange(5, 20).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsMultipleHostnamesWithMultipleNumbers() {
|
||||
testCreateIdentifiers(Collections.singletonList("host[10]name[02-5](20)"),
|
||||
integerRange(1, 10).flatMap(v -> integerRange(2, 5).flatMap(v2 -> integerRange(1, 20).map(v3 -> "host" + v + "name" + String.format("%02d", v2)))).collect(Collectors.toList()),
|
||||
integerRange(1, 10).flatMap(val -> integerRange(2, 5).flatMap(val2 -> integerRange(1, 20))).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDefinitionsStream() {
|
||||
testCreateIdentifiers(Arrays.asList("host", "name"), Arrays.asList("host", "name"), Arrays.asList(1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateOrderMap() {
|
||||
String abc123 = "abc[1-3]";
|
||||
String abc0123 = "abc[01-3]";
|
||||
String b = "b";
|
||||
|
||||
Map<InstanceIdentifier, Integer> orderMap = InstanceIdentifier.createOrderMap(Stream.of(abc123, abc0123 + "(2)", b));
|
||||
|
||||
AtomicInteger num = new AtomicInteger(1);
|
||||
Consumer<InstanceIdentifier> action = id -> {
|
||||
int i = num.getAndIncrement();
|
||||
assertEquals(i, orderMap.get(id).intValue());
|
||||
};
|
||||
|
||||
InstanceIdentifier.extractHostnames(abc0123).flatMap(s -> Stream.of(new InstanceIdentifier(s, 1), new InstanceIdentifier(s, 2))).forEach(action);
|
||||
InstanceIdentifier.extractHostnames(abc123).map(s -> new InstanceIdentifier(s, 1)).forEach(action);
|
||||
InstanceIdentifier.extractHostnames(b).map(s -> new InstanceIdentifier(s, 1)).forEach(action);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateIdentifiersCharactersAfterNumber() {
|
||||
assertThrows(IllegalArgumentException.class, () -> InstanceIdentifier.createIdentifiers(Stream.of("test(2)a")).count());
|
||||
}
|
||||
|
||||
private Stream<Integer> integerRange(int start, int endInclusive) {
|
||||
return IntStream.range(start, endInclusive + 1).mapToObj(value -> value);
|
||||
}
|
||||
|
||||
private void testExtractHostnames(String hostnameWithRange, String... expectedHostnames) {
|
||||
assertEquals(Stream.of(expectedHostnames).collect(Collectors.toList()), InstanceIdentifier.extractHostnames(hostnameWithRange).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void testCreateIdentifiers(List<String> hostnameExpressions, List<String> expectedHostnames, List<Integer> expectedNumbers) {
|
||||
List<InstanceIdentifier> instanceIdentifiers = InstanceIdentifier.createIdentifiers(hostnameExpressions.stream()).collect(Collectors.toList());
|
||||
assertEquals(instanceIdentifiers.size(), expectedHostnames.size());
|
||||
for (int i = 0; i < instanceIdentifiers.size(); i++) {
|
||||
InstanceIdentifier identifier = instanceIdentifiers.get(i);
|
||||
assertEquals(expectedHostnames.get(i), identifier.getHostname());
|
||||
assertEquals((int) expectedNumbers.get(i), identifier.getNumber());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,148 +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.toolkit.tls.manager.writer;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriter;
|
||||
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class NifiPropertiesTlsClientConfigWriterTest {
|
||||
@Mock
|
||||
NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
|
||||
@Mock
|
||||
OutputStreamFactory outputStreamFactory;
|
||||
private NiFiPropertiesWriter niFiPropertiesWriter;
|
||||
private int hostNum;
|
||||
private String testHostname;
|
||||
private File outputFile;
|
||||
private NifiPropertiesTlsClientConfigWriter nifiPropertiesTlsClientConfigWriter;
|
||||
private TlsClientConfig tlsClientConfig;
|
||||
private ByteArrayOutputStream outputStream;
|
||||
private String keyStore;
|
||||
private String keyStorePassword;
|
||||
private String trustStore;
|
||||
private String trustStorePassword;
|
||||
private Properties overlayProperties;
|
||||
private String keyPassword;
|
||||
private String keyStoreType;
|
||||
private String trustStoreType;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws IOException {
|
||||
testHostname = "testHostname";
|
||||
hostNum = 22;
|
||||
|
||||
keyStore = "testKeyStore.jks";
|
||||
keyStoreType = TlsConfig.DEFAULT_KEY_STORE_TYPE;
|
||||
keyStorePassword = "badKeyStorePassword";
|
||||
keyPassword = "badKeyPassword";
|
||||
|
||||
trustStore = "testTrustStore.jks";
|
||||
trustStoreType = TlsConfig.DEFAULT_KEY_STORE_TYPE;
|
||||
trustStorePassword = "badTrustStorePassword";
|
||||
|
||||
outputFile = File.createTempFile("temp", "nifi");
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
when(outputStreamFactory.create(outputFile)).thenReturn(outputStream);
|
||||
|
||||
tlsClientConfig = new TlsClientConfig();
|
||||
tlsClientConfig.setKeyStore(keyStore);
|
||||
tlsClientConfig.setKeyStoreType(keyStoreType);
|
||||
tlsClientConfig.setKeyStorePassword(keyStorePassword);
|
||||
tlsClientConfig.setKeyPassword(keyPassword);
|
||||
|
||||
tlsClientConfig.setTrustStore(trustStore);
|
||||
tlsClientConfig.setTrustStoreType(trustStoreType);
|
||||
tlsClientConfig.setTrustStorePassword(trustStorePassword);
|
||||
|
||||
niFiPropertiesWriter = new NiFiPropertiesWriter(new ArrayList<>());
|
||||
when(niFiPropertiesWriterFactory.create()).thenReturn(niFiPropertiesWriter);
|
||||
nifiPropertiesTlsClientConfigWriter = new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, outputFile, testHostname, hostNum);
|
||||
overlayProperties = nifiPropertiesTlsClientConfigWriter.getOverlayProperties();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws IOException {
|
||||
nifiPropertiesTlsClientConfigWriter.write(tlsClientConfig, outputStreamFactory);
|
||||
testHostnamesAndPorts();
|
||||
assertNotEquals(0, nifiPropertiesTlsClientConfigWriter.getIncrementingPropertyMap().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoHostnameProperties() throws IOException {
|
||||
nifiPropertiesTlsClientConfigWriter.getOverlayProperties().setProperty(NifiPropertiesTlsClientConfigWriter.HOSTNAME_PROPERTIES, "");
|
||||
nifiPropertiesTlsClientConfigWriter.write(tlsClientConfig, outputStreamFactory);
|
||||
testHostnamesAndPorts();
|
||||
Properties nifiProperties = getNifiProperties();
|
||||
nifiProperties.stringPropertyNames().forEach(s -> assertNotEquals(testHostname, nifiProperties.getProperty(s)));
|
||||
}
|
||||
|
||||
private void testHostnamesAndPorts() {
|
||||
Properties nifiProperties = getNifiProperties();
|
||||
|
||||
assertEquals(NifiPropertiesTlsClientConfigWriter.CONF + keyStore, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE));
|
||||
assertEquals(keyStoreType, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE));
|
||||
assertEquals(keyStorePassword, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD));
|
||||
assertEquals(keyPassword, nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
|
||||
|
||||
assertEquals(NifiPropertiesTlsClientConfigWriter.CONF + trustStore, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
|
||||
assertEquals(trustStoreType, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE));
|
||||
assertEquals(trustStorePassword, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
|
||||
|
||||
assertEquals("", nifiProperties.getProperty(NiFiProperties.WEB_HTTP_HOST));
|
||||
assertEquals("", nifiProperties.getProperty(NiFiProperties.WEB_HTTP_PORT));
|
||||
assertEquals(Boolean.toString(true), nifiProperties.getProperty(NiFiProperties.SITE_TO_SITE_SECURE));
|
||||
assertEquals(Boolean.toString(true), nifiProperties.getProperty(NiFiProperties.CLUSTER_PROTOCOL_IS_SECURE));
|
||||
|
||||
nifiPropertiesTlsClientConfigWriter.getHostnamePropertyStream().forEach(s -> assertEquals(testHostname, nifiProperties.getProperty(s)));
|
||||
nifiPropertiesTlsClientConfigWriter.getIncrementingPropertyMap().entrySet().forEach(propertyToPortEntry -> {
|
||||
assertEquals(Integer.toString(propertyToPortEntry.getValue()), nifiProperties.getProperty(propertyToPortEntry.getKey()));
|
||||
assertEquals(Integer.parseInt(overlayProperties.getProperty(propertyToPortEntry.getKey())) + hostNum - 1, propertyToPortEntry.getValue().intValue());
|
||||
});
|
||||
}
|
||||
|
||||
private Properties getNifiProperties() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -1,77 +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.toolkit.tls.properties;
|
||||
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class NifiPropertiesWriterTest {
|
||||
private static NiFiPropertiesWriterFactory nifiPropertiesWriterFactory;
|
||||
private NiFiPropertiesWriter niFiPropertiesWriter;
|
||||
|
||||
@BeforeAll
|
||||
public static void setFactory() throws IOException {
|
||||
nifiPropertiesWriterFactory = new NiFiPropertiesWriterFactory();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws IOException {
|
||||
niFiPropertiesWriter = nifiPropertiesWriterFactory.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanUpdateAllKeys() throws IllegalAccessException, IOException {
|
||||
String testValue = NifiPropertiesWriterTest.class.getCanonicalName();
|
||||
|
||||
// Get all property keys from NiFiProperties and set them to testValue
|
||||
Set<String> propertyKeys = new HashSet<>();
|
||||
for (Field field : NiFiProperties.class.getDeclaredFields()) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && field.getType() == String.class) {
|
||||
if (!field.getName().toLowerCase().startsWith("default")) {
|
||||
String fieldName = (String) field.get(null);
|
||||
propertyKeys.add(fieldName);
|
||||
niFiPropertiesWriter.setPropertyValue(fieldName, testValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
niFiPropertiesWriter.writeNiFiProperties(outputStream);
|
||||
|
||||
// Verify operation worked
|
||||
Properties properties = new Properties();
|
||||
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
|
||||
for (String propertyKey : propertyKeys) {
|
||||
assertEquals(testValue, properties.getProperty(propertyKey));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,282 +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.toolkit.tls.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.nifi.security.util.KeyStoreUtils;
|
||||
import org.apache.nifi.security.util.KeystoreType;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.client.TlsCertificateAuthorityClient;
|
||||
import org.apache.nifi.toolkit.tls.service.server.TlsCertificateAuthorityService;
|
||||
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
|
||||
import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
|
||||
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
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.AdditionalMatchers.or;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TlsCertificateAuthorityTest {
|
||||
private File serverConfigFile;
|
||||
private File clientConfigFile;
|
||||
private OutputStreamFactory outputStreamFactory;
|
||||
private InputStreamFactory inputStreamFactory;
|
||||
private TlsConfig serverConfig;
|
||||
private TlsClientConfig clientConfig;
|
||||
private ObjectMapper objectMapper;
|
||||
private ByteArrayOutputStream serverKeyStoreOutputStream;
|
||||
private ByteArrayOutputStream clientKeyStoreOutputStream;
|
||||
private ByteArrayOutputStream clientTrustStoreOutputStream;
|
||||
private ByteArrayOutputStream serverConfigFileOutputStream;
|
||||
private ByteArrayOutputStream clientConfigFileOutputStream;
|
||||
private String subjectAlternativeName;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws FileNotFoundException {
|
||||
objectMapper = new ObjectMapper();
|
||||
serverConfigFile = new File("fake.server.config");
|
||||
clientConfigFile = new File("fake.client.config");
|
||||
String serverKeyStore = "serverKeyStore";
|
||||
String clientKeyStore = "clientKeyStore";
|
||||
String clientTrustStore = "clientTrustStore";
|
||||
serverKeyStoreOutputStream = new ByteArrayOutputStream();
|
||||
clientKeyStoreOutputStream = new ByteArrayOutputStream();
|
||||
clientTrustStoreOutputStream = new ByteArrayOutputStream();
|
||||
serverConfigFileOutputStream = new ByteArrayOutputStream();
|
||||
clientConfigFileOutputStream = new ByteArrayOutputStream();
|
||||
subjectAlternativeName = "nifi.apache.org";
|
||||
|
||||
String myTestTokenUseSomethingStronger = "myTestTokenUseSomethingStronger";
|
||||
|
||||
serverConfig = new TlsConfig();
|
||||
serverConfig.setCaHostname("localhost");
|
||||
serverConfig.setToken(myTestTokenUseSomethingStronger);
|
||||
serverConfig.setKeyStore(serverKeyStore);
|
||||
serverConfig.setDays(5);
|
||||
serverConfig.setKeySize(2048);
|
||||
serverConfig.initDefaults();
|
||||
|
||||
// set port back to 0, so Jetty will allocate a free port
|
||||
serverConfig.setPort(0);
|
||||
|
||||
clientConfig = new TlsClientConfig();
|
||||
clientConfig.setCaHostname("localhost");
|
||||
clientConfig.setDn("OU=NIFI,CN=otherHostname");
|
||||
clientConfig.setKeyStore(clientKeyStore);
|
||||
clientConfig.setTrustStore(clientTrustStore);
|
||||
clientConfig.setToken(myTestTokenUseSomethingStronger);
|
||||
clientConfig.setDomainAlternativeNames(Collections.singletonList(subjectAlternativeName));
|
||||
clientConfig.setKeySize(2048);
|
||||
clientConfig.initDefaults();
|
||||
|
||||
outputStreamFactory = mock(OutputStreamFactory.class);
|
||||
mockReturnOutputStream(outputStreamFactory, new File(serverKeyStore), serverKeyStoreOutputStream);
|
||||
mockReturnOutputStream(outputStreamFactory, new File(clientKeyStore), clientKeyStoreOutputStream);
|
||||
mockReturnOutputStream(outputStreamFactory, new File(clientTrustStore), clientTrustStoreOutputStream);
|
||||
mockReturnOutputStream(outputStreamFactory, serverConfigFile, serverConfigFileOutputStream);
|
||||
mockReturnOutputStream(outputStreamFactory, clientConfigFile, clientConfigFileOutputStream);
|
||||
|
||||
inputStreamFactory = mock(InputStreamFactory.class);
|
||||
mockReturnProperties(inputStreamFactory, serverConfigFile, serverConfig);
|
||||
mockReturnProperties(inputStreamFactory, clientConfigFile, clientConfig);
|
||||
}
|
||||
|
||||
private void mockReturnProperties(InputStreamFactory inputStreamFactory, File file, TlsConfig tlsConfig) throws FileNotFoundException {
|
||||
when(inputStreamFactory.create(eq(file))).thenAnswer(invocation -> {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
objectMapper.writeValue(byteArrayOutputStream, tlsConfig);
|
||||
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
});
|
||||
}
|
||||
|
||||
private void mockReturnOutputStream(OutputStreamFactory outputStreamFactory, File file, OutputStream outputStream) throws FileNotFoundException {
|
||||
when(outputStreamFactory.create(or(eq(file), eq(new File(file.getAbsolutePath()))))).thenReturn(outputStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientGetCertDifferentPasswordsForKeyAndKeyStore() throws Exception {
|
||||
TlsCertificateAuthorityService tlsCertificateAuthorityService = null;
|
||||
try {
|
||||
tlsCertificateAuthorityService = new TlsCertificateAuthorityService(outputStreamFactory);
|
||||
tlsCertificateAuthorityService.start(serverConfig, serverConfigFile.getAbsolutePath(), true);
|
||||
clientConfig.setPort(tlsCertificateAuthorityService.getPort());
|
||||
TlsCertificateAuthorityClient tlsCertificateAuthorityClient = new TlsCertificateAuthorityClient(outputStreamFactory);
|
||||
tlsCertificateAuthorityClient.generateCertificateAndGetItSigned(clientConfig, null, clientConfigFile.getAbsolutePath(), true);
|
||||
validate();
|
||||
} finally {
|
||||
if (tlsCertificateAuthorityService != null) {
|
||||
tlsCertificateAuthorityService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientGetCertSamePasswordsForKeyAndKeyStore() throws Exception {
|
||||
TlsCertificateAuthorityService tlsCertificateAuthorityService = null;
|
||||
try {
|
||||
tlsCertificateAuthorityService = new TlsCertificateAuthorityService(outputStreamFactory);
|
||||
tlsCertificateAuthorityService.start(serverConfig, serverConfigFile.getAbsolutePath(), false);
|
||||
clientConfig.setPort(tlsCertificateAuthorityService.getPort());
|
||||
TlsCertificateAuthorityClient tlsCertificateAuthorityClient = new TlsCertificateAuthorityClient(outputStreamFactory);
|
||||
tlsCertificateAuthorityClient.generateCertificateAndGetItSigned(clientConfig, null, clientConfigFile.getAbsolutePath(), false);
|
||||
validate();
|
||||
} finally {
|
||||
if (tlsCertificateAuthorityService != null) {
|
||||
tlsCertificateAuthorityService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientPkcs12() throws Exception {
|
||||
serverConfig.setKeyStoreType(KeystoreType.PKCS12.toString());
|
||||
clientConfig.setKeyStoreType(KeystoreType.PKCS12.toString());
|
||||
TlsCertificateAuthorityService tlsCertificateAuthorityService = null;
|
||||
try {
|
||||
tlsCertificateAuthorityService = new TlsCertificateAuthorityService(outputStreamFactory);
|
||||
tlsCertificateAuthorityService.start(serverConfig, serverConfigFile.getAbsolutePath(), false);
|
||||
clientConfig.setPort(tlsCertificateAuthorityService.getPort());
|
||||
TlsCertificateAuthorityClient tlsCertificateAuthorityClient = new TlsCertificateAuthorityClient(outputStreamFactory);
|
||||
tlsCertificateAuthorityClient.generateCertificateAndGetItSigned(clientConfig, null, clientConfigFile.getAbsolutePath(), true);
|
||||
validate();
|
||||
} finally {
|
||||
if (tlsCertificateAuthorityService != null) {
|
||||
tlsCertificateAuthorityService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenMismatch() {
|
||||
serverConfig.setToken("a different token...");
|
||||
|
||||
assertThrows(IOException.class, () -> testClientGetCertSamePasswordsForKeyAndKeyStore());
|
||||
}
|
||||
|
||||
private void validate() throws CertificateException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, SignatureException,
|
||||
NoSuchProviderException, UnrecoverableEntryException, IOException {
|
||||
Certificate caCertificate = validateServerKeyStore();
|
||||
validateClient(caCertificate);
|
||||
}
|
||||
|
||||
private Certificate validateServerKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableEntryException,
|
||||
InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
serverConfig = objectMapper.readValue(new ByteArrayInputStream(serverConfigFileOutputStream.toByteArray()), TlsConfig.class);
|
||||
|
||||
KeyStore serverKeyStore = KeyStoreUtils.getKeyStore(serverConfig.getKeyStoreType());
|
||||
serverKeyStore.load(new ByteArrayInputStream(serverKeyStoreOutputStream.toByteArray()), serverConfig.getKeyStorePassword().toCharArray());
|
||||
String keyPassword = serverConfig.getKeyPassword();
|
||||
KeyStore.Entry serverKeyEntry = serverKeyStore.getEntry(TlsToolkitStandalone.NIFI_KEY,
|
||||
new KeyStore.PasswordProtection(keyPassword == null ? serverConfig.getKeyStorePassword().toCharArray() : keyPassword.toCharArray()));
|
||||
|
||||
assertTrue(serverKeyEntry instanceof KeyStore.PrivateKeyEntry);
|
||||
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) serverKeyEntry;
|
||||
Certificate[] certificateChain = privateKeyEntry.getCertificateChain();
|
||||
assertEquals(1, certificateChain.length);
|
||||
Certificate caCertificate = certificateChain[0];
|
||||
caCertificate.verify(caCertificate.getPublicKey());
|
||||
assertPrivateAndPublicKeyMatch(privateKeyEntry.getPrivateKey(), caCertificate.getPublicKey());
|
||||
return caCertificate;
|
||||
}
|
||||
|
||||
private void validateClient(Certificate caCertificate) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException,
|
||||
UnrecoverableEntryException, InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
clientConfig = objectMapper.readValue(new ByteArrayInputStream(clientConfigFileOutputStream.toByteArray()), TlsClientConfig.class);
|
||||
|
||||
KeyStore clientKeyStore = KeyStoreUtils.getKeyStore(clientConfig.getKeyStoreType());
|
||||
clientKeyStore.load(new ByteArrayInputStream(clientKeyStoreOutputStream.toByteArray()), clientConfig.getKeyStorePassword().toCharArray());
|
||||
String keyPassword = clientConfig.getKeyPassword();
|
||||
KeyStore.Entry clientKeyStoreEntry = clientKeyStore.getEntry(TlsToolkitStandalone.NIFI_KEY,
|
||||
new KeyStore.PasswordProtection(keyPassword == null ? clientConfig.getKeyStorePassword().toCharArray() : keyPassword.toCharArray()));
|
||||
|
||||
assertTrue(clientKeyStoreEntry instanceof KeyStore.PrivateKeyEntry);
|
||||
KeyStore.PrivateKeyEntry clientPrivateKeyEntry = (KeyStore.PrivateKeyEntry) clientKeyStoreEntry;
|
||||
Certificate[] certificateChain = clientPrivateKeyEntry.getCertificateChain();
|
||||
assertEquals(2, certificateChain.length);
|
||||
assertEquals(caCertificate, certificateChain[1]);
|
||||
certificateChain[0].verify(caCertificate.getPublicKey());
|
||||
assertPrivateAndPublicKeyMatch(clientPrivateKeyEntry.getPrivateKey(), certificateChain[0].getPublicKey());
|
||||
|
||||
// Does the certificate contain the SAN we defined in the client config?
|
||||
assert(isSANPresent(certificateChain[0]));
|
||||
|
||||
KeyStore clientTrustStore = KeyStoreUtils.getKeyStore(KeystoreType.JKS.toString());
|
||||
clientTrustStore.load(new ByteArrayInputStream(clientTrustStoreOutputStream.toByteArray()), clientConfig.getTrustStorePassword().toCharArray());
|
||||
assertEquals(caCertificate, clientTrustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT));
|
||||
}
|
||||
|
||||
public static void assertPrivateAndPublicKeyMatch(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
Signature signature = Signature.getInstance(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
signature.initSign(privateKey);
|
||||
byte[] bytes = "test string".getBytes(StandardCharsets.UTF_8);
|
||||
signature.update(bytes);
|
||||
|
||||
Signature verify = Signature.getInstance(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
verify.initVerify(publicKey);
|
||||
verify.update(bytes);
|
||||
verify.verify(signature.sign());
|
||||
}
|
||||
|
||||
private boolean isSANPresent(Certificate cert) {
|
||||
Iterator<List<?>> iterator = null;
|
||||
try {
|
||||
iterator = ((X509Certificate) cert).getSubjectAlternativeNames().iterator();
|
||||
} catch (CertificateParsingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
boolean containsSAN = false;
|
||||
while(iterator.hasNext()) {
|
||||
if(iterator.next().contains(subjectAlternativeName)) {
|
||||
containsSAN = true;
|
||||
}
|
||||
}
|
||||
return containsSAN;
|
||||
}
|
||||
}
|
|
@ -1,178 +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.toolkit.tls.service.client;
|
||||
|
||||
import org.apache.nifi.security.util.KeystoreType;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class TlsCertificateAuthorityClientCommandLineTest {
|
||||
|
||||
private TlsCertificateAuthorityClientCommandLine tlsCertificateAuthorityClientCommandLine;
|
||||
private String testToken;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
tlsCertificateAuthorityClientCommandLine = new TlsCertificateAuthorityClientCommandLine();
|
||||
testToken = "testToken16bytes";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoToken() {
|
||||
final CommandLineParseException e = assertThrows(CommandLineParseException.class, () -> tlsCertificateAuthorityClientCommandLine.parse());
|
||||
|
||||
assertEquals(ExitCode.ERROR_TOKEN_ARG_EMPTY, e.getExitCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws CommandLineParseException, IOException {
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken);
|
||||
TlsClientConfig clientConfig = tlsCertificateAuthorityClientCommandLine.createClientConfig();
|
||||
|
||||
assertEquals(TlsConfig.DEFAULT_HOSTNAME, clientConfig.getCaHostname());
|
||||
assertEquals(new TlsConfig().calcDefaultDn(InetAddress.getLocalHost().getHostName()), clientConfig.getDn());
|
||||
assertEquals(TlsCertificateAuthorityClientCommandLine.KEYSTORE + TlsConfig.DEFAULT_KEY_STORE_TYPE.toLowerCase(), clientConfig.getKeyStore());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_STORE_TYPE, clientConfig.getKeyStoreType());
|
||||
assertNull(clientConfig.getKeyStorePassword());
|
||||
assertNull(clientConfig.getKeyPassword());
|
||||
assertEquals(TlsCertificateAuthorityClientCommandLine.TRUSTSTORE + TlsConfig.DEFAULT_KEY_STORE_TYPE.toLowerCase(), clientConfig.getTrustStore());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_STORE_TYPE, clientConfig.getTrustStoreType());
|
||||
assertNull(clientConfig.getTrustStorePassword());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_SIZE, clientConfig.getKeySize());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, clientConfig.getKeyPairAlgorithm());
|
||||
assertEquals(testToken, clientConfig.getToken());
|
||||
assertEquals(TlsConfig.DEFAULT_PORT, clientConfig.getPort());
|
||||
assertEquals(TlsCertificateAuthorityClientCommandLine.DEFAULT_CONFIG_JSON, tlsCertificateAuthorityClientCommandLine.getConfigJsonOut());
|
||||
assertNull(tlsCertificateAuthorityClientCommandLine.getConfigJsonIn());
|
||||
assertEquals(TlsCertificateAuthorityClientCommandLine.DEFAULT_CERTIFICATE_DIRECTORY, tlsCertificateAuthorityClientCommandLine.getCertificateDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeySize() throws CommandLineParseException {
|
||||
int keySize = 1234;
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-k", Integer.toString(keySize));
|
||||
assertEquals(keySize, tlsCertificateAuthorityClientCommandLine.getKeySize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyPairAlgorithm() throws CommandLineParseException {
|
||||
String testAlgorithm = "testAlgorithm";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-a", testAlgorithm);
|
||||
assertEquals(testAlgorithm, tlsCertificateAuthorityClientCommandLine.getKeyAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelp() {
|
||||
final CommandLineParseException e = assertThrows(CommandLineParseException.class, () -> tlsCertificateAuthorityClientCommandLine.parse("-h"));
|
||||
|
||||
assertEquals(ExitCode.HELP, e.getExitCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaHostname() throws CommandLineParseException, IOException {
|
||||
String testCaHostname = "testCaHostname";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-c", testCaHostname);
|
||||
assertEquals(testCaHostname, tlsCertificateAuthorityClientCommandLine.createClientConfig().getCaHostname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDn() throws CommandLineParseException, IOException {
|
||||
String testDn = "testDn";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-D", testDn);
|
||||
assertEquals(testDn, tlsCertificateAuthorityClientCommandLine.createClientConfig().getDn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPort() throws CommandLineParseException, IOException {
|
||||
int testPort = 2345;
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-p", Integer.toString(testPort));
|
||||
assertEquals(testPort, tlsCertificateAuthorityClientCommandLine.createClientConfig().getPort());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyStoreType() throws CommandLineParseException, IOException {
|
||||
String testType = "testType";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-T", testType);
|
||||
|
||||
TlsClientConfig clientConfig = tlsCertificateAuthorityClientCommandLine.createClientConfig();
|
||||
assertEquals(testType, clientConfig.getKeyStoreType());
|
||||
String trustStoreType = KeystoreType.JKS.toString().toLowerCase();
|
||||
assertEquals(trustStoreType, clientConfig.getTrustStoreType());
|
||||
assertEquals(TlsCertificateAuthorityClientCommandLine.KEYSTORE + testType.toLowerCase(), clientConfig.getKeyStore());
|
||||
assertEquals(TlsCertificateAuthorityClientCommandLine.TRUSTSTORE + trustStoreType, clientConfig.getTrustStore());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonOut() throws CommandLineParseException {
|
||||
String testPath = "/1/2/3/4";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-f", testPath);
|
||||
assertEquals(testPath, tlsCertificateAuthorityClientCommandLine.getConfigJsonOut());
|
||||
assertNull(tlsCertificateAuthorityClientCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonOutAndUseForBoth() throws CommandLineParseException {
|
||||
String testPath = "/1/2/3/4";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-f", testPath, "-F");
|
||||
assertEquals(testPath, tlsCertificateAuthorityClientCommandLine.getConfigJsonOut());
|
||||
assertEquals(testPath, tlsCertificateAuthorityClientCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonIn() throws CommandLineParseException {
|
||||
String testPath = "/1/2/3/4";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "--" + BaseCertificateAuthorityCommandLine.READ_CONFIG_JSON_ARG, testPath);
|
||||
assertEquals(BaseCertificateAuthorityCommandLine.DEFAULT_CONFIG_JSON, tlsCertificateAuthorityClientCommandLine.getConfigJsonOut());
|
||||
assertEquals(testPath, tlsCertificateAuthorityClientCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonInAndOut() throws CommandLineParseException {
|
||||
String testPath = "/1/2/3/4";
|
||||
String testIn = "/2/3/4/5";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-f", testPath, "--" + BaseCertificateAuthorityCommandLine.READ_CONFIG_JSON_ARG, testIn);
|
||||
assertEquals(testPath, tlsCertificateAuthorityClientCommandLine.getConfigJsonOut());
|
||||
assertEquals(testIn, tlsCertificateAuthorityClientCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCertificateFile() throws CommandLineParseException {
|
||||
String testCertificateFile = "testCertificateFile";
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-C", testCertificateFile);
|
||||
assertEquals(testCertificateFile, tlsCertificateAuthorityClientCommandLine.getCertificateDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigClientSAN() throws CommandLineParseException {
|
||||
tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "--" + TlsCertificateAuthorityClientCommandLine.SUBJECT_ALTERNATIVE_NAMES, "nifi.apache.org,minifi.apache.org");
|
||||
assertEquals("nifi.apache.org", tlsCertificateAuthorityClientCommandLine.getDomainAlternativeNames().get(0));
|
||||
assertEquals("minifi.apache.org", tlsCertificateAuthorityClientCommandLine.getDomainAlternativeNames().get(1));
|
||||
}
|
||||
}
|
|
@ -1,218 +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.toolkit.tls.service.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TlsCertificateSigningRequestPerformerTest {
|
||||
@Mock
|
||||
Supplier<HttpClientBuilder> httpClientBuilderSupplier;
|
||||
|
||||
@Spy
|
||||
HttpClientBuilder httpClientBuilder;
|
||||
|
||||
@Mock
|
||||
CloseableHttpClient closeableHttpClient;
|
||||
|
||||
@Mock
|
||||
TlsClientConfig tlsClientConfig;
|
||||
|
||||
X509Certificate caCertificate;
|
||||
|
||||
X509Certificate signedCsr;
|
||||
|
||||
ObjectMapper objectMapper;
|
||||
KeyPair keyPair;
|
||||
TlsCertificateSigningRequestPerformer tlsCertificateSigningRequestPerformer;
|
||||
String testToken;
|
||||
String testCaHostname;
|
||||
int testPort;
|
||||
List<X509Certificate> certificates;
|
||||
|
||||
TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse;
|
||||
int statusCode;
|
||||
private byte[] testHmac;
|
||||
private String testSignedCsr;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws GeneralSecurityException, OperatorCreationException, IOException {
|
||||
objectMapper = new ObjectMapper();
|
||||
keyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
|
||||
|
||||
testToken = "testTokenTestToken";
|
||||
testCaHostname = "testCaHostname";
|
||||
testPort = 8993;
|
||||
certificates = new ArrayList<>();
|
||||
|
||||
when(tlsClientConfig.getToken()).thenReturn(testToken);
|
||||
when(tlsClientConfig.getCaHostname()).thenReturn(testCaHostname);
|
||||
when(tlsClientConfig.getDn()).thenReturn(new TlsConfig().calcDefaultDn(testCaHostname));
|
||||
when(tlsClientConfig.getPort()).thenReturn(testPort);
|
||||
when(tlsClientConfig.getSigningAlgorithm()).thenReturn(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(tlsClientConfig.getDn(), null, keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
String testCsrPem = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
|
||||
when(httpClientBuilderSupplier.get()).thenReturn(httpClientBuilder);
|
||||
when(httpClientBuilder.build()).thenAnswer(invocation -> {
|
||||
Field sslSocketFactory = HttpClientBuilder.class.getDeclaredField("sslSocketFactory");
|
||||
sslSocketFactory.setAccessible(true);
|
||||
Object o = sslSocketFactory.get(httpClientBuilder);
|
||||
if( o != null) {
|
||||
Field field = TlsCertificateAuthorityClientSocketFactory.class.getDeclaredField("certificates");
|
||||
field.setAccessible(true);
|
||||
((List<X509Certificate>) field.get(o)).addAll(certificates);
|
||||
}
|
||||
return closeableHttpClient;
|
||||
});
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
when(statusLine.getStatusCode()).thenAnswer(i -> statusCode);
|
||||
when(closeableHttpClient.execute(eq(new HttpHost(testCaHostname, testPort, "https")), any(HttpPost.class))).thenAnswer(invocation -> {
|
||||
HttpPost httpPost = (HttpPost) invocation.getArguments()[1];
|
||||
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = objectMapper.readValue(httpPost.getEntity().getContent(), TlsCertificateAuthorityRequest.class);
|
||||
assertEquals(tlsCertificateAuthorityRequest.getCsr(), testCsrPem);
|
||||
CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
|
||||
when(closeableHttpResponse.getEntity()).thenAnswer(i -> {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
objectMapper.writeValue(byteArrayOutputStream, tlsCertificateAuthorityResponse);
|
||||
return new ByteArrayEntity(byteArrayOutputStream.toByteArray());
|
||||
});
|
||||
when(closeableHttpResponse.getStatusLine()).thenReturn(statusLine);
|
||||
return closeableHttpResponse;
|
||||
});
|
||||
KeyPair caKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
|
||||
caCertificate = new StandardCertificateBuilder(caKeyPair, new X500Principal("CN=fakeCa"), Duration.ofDays(TlsConfig.DEFAULT_DAYS)).build();
|
||||
testHmac = TlsHelper.calculateHMac(testToken, caCertificate.getPublicKey());
|
||||
signedCsr = new StandardCertificateBuilder(caKeyPair, caCertificate.getIssuerX500Principal(), Duration.ofDays(TlsConfig.DEFAULT_DAYS))
|
||||
.setSubject(new X500Principal(jcaPKCS10CertificationRequest.getSubject().toString()))
|
||||
.setSubjectPublicKey(jcaPKCS10CertificationRequest.getPublicKey())
|
||||
.build();
|
||||
testSignedCsr = TlsHelper.pemEncodeJcaObject(signedCsr);
|
||||
|
||||
tlsCertificateSigningRequestPerformer = new TlsCertificateSigningRequestPerformer(httpClientBuilderSupplier, tlsClientConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOk() throws Exception {
|
||||
certificates.add(caCertificate);
|
||||
statusCode = Response.SC_OK;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse(testHmac, testSignedCsr);
|
||||
tlsCertificateSigningRequestPerformer.perform(keyPair);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadStatusCode() {
|
||||
statusCode = Response.SC_FORBIDDEN;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse();
|
||||
|
||||
final IOException e = assertThrows(IOException.class, () -> tlsCertificateSigningRequestPerformer.perform(keyPair));
|
||||
assertTrue(e.getMessage().startsWith(TlsCertificateSigningRequestPerformer.RECEIVED_RESPONSE_CODE + statusCode));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test0CertSize() {
|
||||
statusCode = Response.SC_OK;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse();
|
||||
|
||||
final IOException e = assertThrows(IOException.class, () -> tlsCertificateSigningRequestPerformer.perform(keyPair));
|
||||
assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_ONE_CERTIFICATE, e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2CertSize() {
|
||||
certificates.add(caCertificate);
|
||||
certificates.add(caCertificate);
|
||||
statusCode = Response.SC_OK;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse();
|
||||
|
||||
final IOException e = assertThrows(IOException.class, () -> tlsCertificateSigningRequestPerformer.perform(keyPair));
|
||||
assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_ONE_CERTIFICATE, e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoHmac() {
|
||||
certificates.add(caCertificate);
|
||||
statusCode = Response.SC_OK;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse(null, testSignedCsr);
|
||||
|
||||
final IOException e = assertThrows(IOException.class, () -> tlsCertificateSigningRequestPerformer.perform(keyPair));
|
||||
assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_RESPONSE_TO_CONTAIN_HMAC, e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadHmac() {
|
||||
certificates.add(caCertificate);
|
||||
statusCode = Response.SC_OK;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse("badHmac".getBytes(StandardCharsets.UTF_8), testSignedCsr);
|
||||
|
||||
final IOException e = assertThrows(IOException.class, () -> tlsCertificateSigningRequestPerformer.perform(keyPair));
|
||||
assertEquals(TlsCertificateSigningRequestPerformer.UNEXPECTED_HMAC_RECEIVED_POSSIBLE_MAN_IN_THE_MIDDLE, e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCertificate() {
|
||||
certificates.add(caCertificate);
|
||||
statusCode = Response.SC_OK;
|
||||
tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse(testHmac, null);
|
||||
|
||||
final IOException e = assertThrows(IOException.class, () -> tlsCertificateSigningRequestPerformer.perform(keyPair));
|
||||
assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE, e.getMessage());
|
||||
}
|
||||
}
|
|
@ -1,152 +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.toolkit.tls.service.server;
|
||||
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
|
||||
import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TlsCertificateAuthorityServiceCommandLineTest {
|
||||
@Mock
|
||||
InputStreamFactory inputStreamFactory;
|
||||
|
||||
TlsCertificateAuthorityServiceCommandLine tlsCertificateAuthorityServiceCommandLine;
|
||||
|
||||
String testToken;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
tlsCertificateAuthorityServiceCommandLine = new TlsCertificateAuthorityServiceCommandLine(inputStreamFactory);
|
||||
testToken = "testToken16bytes";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws CommandLineParseException, IOException {
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken);
|
||||
assertEquals(BaseCertificateAuthorityCommandLine.DEFAULT_CONFIG_JSON, tlsCertificateAuthorityServiceCommandLine.getConfigJsonOut());
|
||||
assertNull(tlsCertificateAuthorityServiceCommandLine.getConfigJsonIn());
|
||||
TlsConfig tlsConfig = tlsCertificateAuthorityServiceCommandLine.createConfig();
|
||||
assertEquals(TlsConfig.DEFAULT_HOSTNAME, tlsConfig.getCaHostname());
|
||||
assertEquals(testToken, tlsConfig.getToken());
|
||||
assertEquals(TlsConfig.DEFAULT_PORT, tlsConfig.getPort());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_STORE_TYPE, tlsConfig.getKeyStoreType());
|
||||
assertEquals(TlsCertificateAuthorityServiceCommandLine.NIFI_CA_KEYSTORE + tlsConfig.getKeyStoreType().toLowerCase(), tlsConfig.getKeyStore());
|
||||
assertNull(tlsConfig.getKeyStorePassword());
|
||||
assertNull(tlsConfig.getKeyPassword());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_SIZE, tlsConfig.getKeySize());
|
||||
assertEquals(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, tlsConfig.getKeyPairAlgorithm());
|
||||
assertEquals(TlsConfig.DEFAULT_SIGNING_ALGORITHM, tlsConfig.getSigningAlgorithm());
|
||||
assertEquals(TlsConfig.DEFAULT_DAYS, tlsConfig.getDays());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaHostname() throws CommandLineParseException, IOException {
|
||||
String testCaHostname = "testCaHostname";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-c", testCaHostname);
|
||||
assertEquals(testCaHostname, tlsCertificateAuthorityServiceCommandLine.createConfig().getCaHostname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPort() throws CommandLineParseException, IOException {
|
||||
int testPort = 4321;
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-p", Integer.toString(testPort));
|
||||
assertEquals(testPort, tlsCertificateAuthorityServiceCommandLine.createConfig().getPort());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyStoreType() throws CommandLineParseException, IOException {
|
||||
String testKeyStoreType = "testKeyStoreType";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-T", testKeyStoreType);
|
||||
TlsConfig tlsConfig = tlsCertificateAuthorityServiceCommandLine.createConfig();
|
||||
assertEquals(testKeyStoreType, tlsConfig.getKeyStoreType());
|
||||
assertEquals(TlsCertificateAuthorityServiceCommandLine.NIFI_CA_KEYSTORE + tlsConfig.getKeyStoreType().toLowerCase(), tlsConfig.getKeyStore());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeySize() throws CommandLineParseException, IOException {
|
||||
int testKeySize = 8192;
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-k", Integer.toString(testKeySize));
|
||||
assertEquals(testKeySize, tlsCertificateAuthorityServiceCommandLine.createConfig().getKeySize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyPairAlgorithm() throws CommandLineParseException, IOException {
|
||||
String testAlgorithm = "testAlgorithm";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-a", testAlgorithm);
|
||||
assertEquals(testAlgorithm, tlsCertificateAuthorityServiceCommandLine.createConfig().getKeyPairAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSigningAlgorithm() throws CommandLineParseException, IOException {
|
||||
String testSigningAlgorithm = "testSigningAlgorithm";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-s", testSigningAlgorithm);
|
||||
assertEquals(testSigningAlgorithm, tlsCertificateAuthorityServiceCommandLine.createConfig().getSigningAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDays() throws CommandLineParseException, IOException {
|
||||
int days = 1234;
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-d", Integer.toString(days));
|
||||
assertEquals(days, tlsCertificateAuthorityServiceCommandLine.createConfig().getDays());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonOut() throws CommandLineParseException {
|
||||
String out = "testJson.out";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-f", out);
|
||||
assertEquals(out, tlsCertificateAuthorityServiceCommandLine.getConfigJsonOut());
|
||||
assertNull(tlsCertificateAuthorityServiceCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonOutAndUseForBoth() throws CommandLineParseException {
|
||||
String out = "testJson.out";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-f", out, "-F");
|
||||
assertEquals(out, tlsCertificateAuthorityServiceCommandLine.getConfigJsonOut());
|
||||
assertEquals(out, tlsCertificateAuthorityServiceCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonIn() throws CommandLineParseException {
|
||||
String in = "testJson.in";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "--" + BaseCertificateAuthorityCommandLine.READ_CONFIG_JSON_ARG, in);
|
||||
assertEquals(BaseCertificateAuthorityCommandLine.DEFAULT_CONFIG_JSON, tlsCertificateAuthorityServiceCommandLine.getConfigJsonOut());
|
||||
assertEquals(in, tlsCertificateAuthorityServiceCommandLine.getConfigJsonIn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigJsonInAndOut() throws CommandLineParseException {
|
||||
String out = "testJson.out";
|
||||
String in = "testJson.in";
|
||||
tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-f", out, "--" + BaseCertificateAuthorityCommandLine.READ_CONFIG_JSON_ARG, in);
|
||||
assertEquals(out, tlsCertificateAuthorityServiceCommandLine.getConfigJsonOut());
|
||||
assertEquals(in, tlsCertificateAuthorityServiceCommandLine.getConfigJsonIn());
|
||||
}
|
||||
}
|
|
@ -1,203 +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.toolkit.tls.service.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
|
||||
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
|
||||
import org.apache.nifi.toolkit.tls.util.TlsHelper;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.Extensions;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TlsCertificateAuthorityServiceHandlerTest {
|
||||
X509Certificate caCert;
|
||||
|
||||
private final Request baseRequest = mock(Request.class);
|
||||
private final HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
|
||||
private final HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
|
||||
|
||||
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest;
|
||||
|
||||
KeyPair keyPair;
|
||||
|
||||
String testToken;
|
||||
|
||||
String testPemEncodedCsr;
|
||||
|
||||
String testPemEncodedSignedCertificate;
|
||||
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
TlsCertificateAuthorityServiceHandler tlsCertificateAuthorityServiceHandler;
|
||||
|
||||
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest;
|
||||
|
||||
int statusCode;
|
||||
|
||||
StringWriter response;
|
||||
private byte[] testCaHmac;
|
||||
private byte[] testHmac;
|
||||
private String requestedDn;
|
||||
private KeyPair certificateKeyPair;
|
||||
|
||||
private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US";
|
||||
private static final List<String> SUBJECT_ALT_NAMES = Arrays.asList("127.0.0.1", "nifi.nifi.apache.org");
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
testToken = "testTokenTestToken";
|
||||
testPemEncodedSignedCertificate = "testPemEncodedSignedCertificate";
|
||||
keyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
|
||||
objectMapper = new ObjectMapper();
|
||||
when(httpServletRequest.getReader()).thenAnswer(invocation -> {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
objectMapper.writeValue(stringWriter, tlsCertificateAuthorityRequest);
|
||||
return new BufferedReader(new StringReader(stringWriter.toString()));
|
||||
});
|
||||
doAnswer(invocation -> statusCode = (int) invocation.getArguments()[0]).when(httpServletResponse).setStatus(anyInt());
|
||||
when(httpServletResponse.getWriter()).thenAnswer(invocation -> {
|
||||
response = new StringWriter();
|
||||
return new PrintWriter(response);
|
||||
});
|
||||
caCert = new StandardCertificateBuilder(keyPair, new X500Principal("CN=fakeCa"), Duration.ofDays(TlsConfig.DEFAULT_DAYS)).build();
|
||||
requestedDn = new TlsConfig().calcDefaultDn(TlsConfig.DEFAULT_HOSTNAME);
|
||||
certificateKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
|
||||
jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(requestedDn, null, certificateKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
testPemEncodedCsr = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
|
||||
tlsCertificateAuthorityServiceHandler = new TlsCertificateAuthorityServiceHandler(TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS, testToken, caCert, keyPair, objectMapper);
|
||||
testHmac = TlsHelper.calculateHMac(testToken, jcaPKCS10CertificationRequest.getPublicKey());
|
||||
testCaHmac = TlsHelper.calculateHMac(testToken, caCert.getPublicKey());
|
||||
}
|
||||
|
||||
private TlsCertificateAuthorityResponse getResponse() throws IOException {
|
||||
return objectMapper.readValue(new StringReader(response.toString()), TlsCertificateAuthorityResponse.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess() throws IOException, ServletException, GeneralSecurityException {
|
||||
tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(testHmac, testPemEncodedCsr);
|
||||
tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
|
||||
assertEquals(Response.SC_OK, statusCode);
|
||||
assertArrayEquals(testCaHmac, getResponse().getHmac());
|
||||
X509Certificate certificate = TlsHelper.parseCertificate(new StringReader(getResponse().getPemEncodedCertificate()));
|
||||
assertEquals(certificateKeyPair.getPublic(), certificate.getPublicKey());
|
||||
assertEquals(new X500Name(requestedDn), new X500Name(certificate.getSubjectX500Principal().toString()));
|
||||
certificate.verify(caCert.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCsr() throws IOException, ServletException {
|
||||
tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(testHmac, null);
|
||||
tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
|
||||
assertEquals(Response.SC_BAD_REQUEST, statusCode);
|
||||
assertEquals(TlsCertificateAuthorityServiceHandler.CSR_FIELD_MUST_BE_SET, getResponse().getError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoHmac() throws IOException, ServletException {
|
||||
tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(null, testPemEncodedCsr);
|
||||
tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
|
||||
assertEquals(Response.SC_BAD_REQUEST, statusCode);
|
||||
assertEquals(TlsCertificateAuthorityServiceHandler.HMAC_FIELD_MUST_BE_SET, getResponse().getError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForbidden() throws IOException, ServletException {
|
||||
tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest("badHmac".getBytes(StandardCharsets.UTF_8), testPemEncodedCsr);
|
||||
tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
|
||||
assertEquals(Response.SC_FORBIDDEN, statusCode);
|
||||
assertEquals(TlsCertificateAuthorityServiceHandler.FORBIDDEN, getResponse().getError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServletException() {
|
||||
assertThrows(ServletException.class, () -> tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSANAgainUsingCertificationRequestMethod() throws GeneralSecurityException, IOException, OperatorCreationException {
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
Extensions exts = TlsHelper.createDomainAlternativeNamesExtensions(SUBJECT_ALT_NAMES, SUBJECT_DN);
|
||||
String token = "someTokenData16B";
|
||||
|
||||
JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(SUBJECT_DN), keyPair.getPublic());
|
||||
jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, exts);
|
||||
JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder("SHA256WITHRSA");
|
||||
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest(
|
||||
jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
|
||||
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(
|
||||
TlsHelper.calculateHMac(token, jcaPKCS10CertificationRequest.getPublicKey()), TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest));
|
||||
|
||||
JcaPKCS10CertificationRequest jcaPKCS10CertificationDecoded = TlsHelper.parseCsr(tlsCertificateAuthorityRequest.getCsr());
|
||||
|
||||
Extensions extensions = jcaPKCS10CertificationDecoded.getRequestedExtensions();
|
||||
// Satisfy @After requirement
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
assertNotNull(extensions, "The extensions parsed from the CSR were found to be null when they should have been present.");
|
||||
assertNotNull(extensions.getExtension(Extension.subjectAlternativeName), "The Subject Alternate Name parsed from the CSR was found to be null when it should have been present.");
|
||||
assertTrue(extensions.equivalent(exts));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void verifyHandled() {
|
||||
verify(baseRequest).setHandled(true);
|
||||
}
|
||||
}
|
|
@ -1,44 +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.toolkit.tls.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class PasswordUtilTest {
|
||||
@Test
|
||||
public void testGeneratePassword() {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
PasswordUtil passwordUtil = new PasswordUtil(secureRandom);
|
||||
String generated = passwordUtil.generatePassword();
|
||||
assertEquals(43, generated.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordExhausted() {
|
||||
Supplier<String> supplier = PasswordUtil.passwordSupplier("exhausted", new String[]{"a", "b"});
|
||||
supplier.get();
|
||||
supplier.get();
|
||||
assertThrows(PasswordsExhaustedException.class, supplier::get);
|
||||
}
|
||||
}
|
|
@ -1,376 +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.toolkit.tls.util;
|
||||
|
||||
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
|
||||
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
|
||||
import org.apache.nifi.util.file.FileUtils;
|
||||
import org.bouncycastle.asn1.pkcs.Attribute;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.Extensions;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||
import org.bouncycastle.util.IPAddress;
|
||||
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.security.auth.x500.X500Principal;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.security.GeneralSecurityException;
|
||||
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.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.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TlsHelperTest {
|
||||
private static final String PASSWORD = "changeit";
|
||||
private static X509Certificate rootCert;
|
||||
private int days;
|
||||
private int keySize;
|
||||
private String keyPairAlgorithm;
|
||||
|
||||
public static X509Certificate loadCertificate(final Reader reader) throws IOException, CertificateException {
|
||||
try (PEMParser pemParser = new PEMParser(reader)) {
|
||||
Object object = pemParser.readObject();
|
||||
assertEquals(X509CertificateHolder.class, object.getClass());
|
||||
return new JcaX509CertificateConverter().getCertificate((X509CertificateHolder) object);
|
||||
}
|
||||
}
|
||||
|
||||
public static X509Certificate loadCertificate(final File file) throws IOException, CertificateException {
|
||||
try (FileReader fileReader = new FileReader(file)) {
|
||||
return loadCertificate(fileReader);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeAll() throws Exception {
|
||||
rootCert = TlsHelper.parseCertificate(new FileReader("src/test/resources/rootCert.crt"));
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
days = 360;
|
||||
keySize = 2048;
|
||||
keyPairAlgorithm = "RSA";
|
||||
}
|
||||
|
||||
private Date inFuture(int days) {
|
||||
return new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(days));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenLengthInCalculateHmac() throws GeneralSecurityException {
|
||||
List<String> badTokens = Arrays.asList(null, "", "123");
|
||||
List<String> goodTokens = Arrays.asList("0123456789abcdefghijklm", "0123456789abcdef");
|
||||
|
||||
String dn = "CN=testDN,O=testOrg";
|
||||
X509Certificate x509Certificate = new StandardCertificateBuilder(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), new X500Principal(dn), Duration.ofDays(days)).build();
|
||||
PublicKey pubKey = x509Certificate.getPublicKey();
|
||||
|
||||
for (String token : badTokens) {
|
||||
try {
|
||||
TlsHelper.calculateHMac(token, pubKey);
|
||||
fail("HMAC was calculated with a token that was too short.");
|
||||
} catch (GeneralSecurityException e) {
|
||||
assertEquals("Token does not meet minimum size of 16 bytes.", e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Token cannot be null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
for (String token : goodTokens) {
|
||||
byte[] hmac = TlsHelper.calculateHMac(token, pubKey);
|
||||
assertTrue(hmac.length > 0, "HMAC length OK");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateSelfSignedCert() throws GeneralSecurityException {
|
||||
String dn = "CN=testDN,O=testOrg";
|
||||
|
||||
X509Certificate x509Certificate = new StandardCertificateBuilder(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), new X500Principal(dn), Duration.ofDays(days)).build();
|
||||
|
||||
Date notAfter = x509Certificate.getNotAfter();
|
||||
assertTrue(notAfter.after(inFuture(days - 1)));
|
||||
assertTrue(notAfter.before(inFuture(days + 1)));
|
||||
|
||||
Date notBefore = x509Certificate.getNotBefore();
|
||||
assertTrue(notBefore.after(inFuture(-1)));
|
||||
assertTrue(notBefore.before(inFuture(1)));
|
||||
|
||||
assertEquals(dn, x509Certificate.getIssuerX500Principal().getName());
|
||||
assertEquals("SHA256withRSA", x509Certificate.getSigAlgName());
|
||||
assertEquals(keyPairAlgorithm, x509Certificate.getPublicKey().getAlgorithm());
|
||||
|
||||
x509Certificate.checkValidity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteKeyStoreSuccess(@Mock OutputStreamFactory outputStreamFactory, @TempDir File file) throws IOException, GeneralSecurityException {
|
||||
when(outputStreamFactory.create(file)).thenReturn(new ByteArrayOutputStream());
|
||||
String testPassword = "testPassword";
|
||||
final KeyStore keyStore = setupKeystore();
|
||||
assertEquals(testPassword, TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldIncludeSANFromCSR() throws Exception {
|
||||
// Arrange
|
||||
final List<String> sanEntries = Arrays.asList("127.0.0.1", "nifi.nifi.apache.org");
|
||||
final int sanCount = sanEntries.size();
|
||||
final String dn = "CN=localhost";
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithm);
|
||||
keyPairGenerator.initialize(keySize);
|
||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
|
||||
// Act
|
||||
JcaPKCS10CertificationRequest csrWithSan = TlsHelper.generateCertificationRequest(dn, sanEntries, keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
|
||||
|
||||
// Assert
|
||||
String subjectName = csrWithSan.getSubject().toString();
|
||||
assertEquals(dn, subjectName);
|
||||
|
||||
List<String> extractedSans = extractSanFromCsr(csrWithSan);
|
||||
assertEquals(sanCount + 1, extractedSans.size());
|
||||
List<String> formattedSans = sanEntries.stream()
|
||||
.map(s -> (IPAddress.isValid(s) ? "IP Address: " + new GeneralName(GeneralName.iPAddress, s).getName() : "DNS: " + s))
|
||||
.collect(Collectors.toList());
|
||||
assertTrue(extractedSans.containsAll(formattedSans));
|
||||
|
||||
// We check that the SANs also contain the CN
|
||||
assertTrue(extractedSans.contains("DNS: localhost"));
|
||||
}
|
||||
|
||||
private List<String> extractSanFromCsr(JcaPKCS10CertificationRequest csr) {
|
||||
List<String> sans = new ArrayList<>();
|
||||
Attribute[] certAttributes = csr.getAttributes();
|
||||
for (Attribute attribute : certAttributes) {
|
||||
if (attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) {
|
||||
Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0));
|
||||
GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
|
||||
GeneralName[] names = gns.getNames();
|
||||
for (GeneralName name : names) {
|
||||
String title = "";
|
||||
if (name.getTagNo() == GeneralName.dNSName) {
|
||||
title = "DNS";
|
||||
} else if (name.getTagNo() == GeneralName.iPAddress) {
|
||||
title = "IP Address";
|
||||
// name.toASN1Primitive();
|
||||
} else if (name.getTagNo() == GeneralName.otherName) {
|
||||
title = "Other Name";
|
||||
}
|
||||
sans.add(title + ": " + name.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sans;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameWithForwardSlashes() {
|
||||
String result = TlsHelper.escapeFilename("my/silly/filename.pem");
|
||||
assertEquals("my_silly_filename.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameWithBackSlashes() {
|
||||
String result = TlsHelper.escapeFilename("my\\silly\\filename.pem");
|
||||
assertEquals("my_silly_filename.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameWithDollarSign() {
|
||||
String result = TlsHelper.escapeFilename("my$illyfilename.pem");
|
||||
assertEquals("my_illyfilename.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameTwoSymbolsInARow() {
|
||||
String result = TlsHelper.escapeFilename("my!?sillyfilename.pem");
|
||||
assertEquals("my_sillyfilename.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameKeepHyphens() {
|
||||
String result = TlsHelper.escapeFilename("my-silly-filename.pem");
|
||||
assertEquals("my-silly-filename.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameDoubleSpaces() {
|
||||
String result = TlsHelper.escapeFilename("my silly filename.pem");
|
||||
assertEquals("my_silly_filename.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeAliasFilenameSymbols() {
|
||||
String result = TlsHelper.escapeFilename("./\\!@#$%^&*()_-+=.pem");
|
||||
assertEquals(".__-_=.pem", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientDnFilenameSlashes() {
|
||||
String clientDn = "CN=testuser,OU=NiFi/Organisation";
|
||||
String escapedClientDn = TlsHelper.escapeFilename(clientDn);
|
||||
|
||||
assertEquals("CN=testuser_OU=NiFi_Organisation", escapedClientDn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientDnFilenameSpecialChars() {
|
||||
String clientDn = "CN=testuser,OU=NiFi#!Organisation";
|
||||
String escapedClientDn = TlsHelper.escapeFilename(clientDn);
|
||||
|
||||
assertEquals("CN=testuser_OU=NiFi_Organisation", escapedClientDn);
|
||||
}
|
||||
|
||||
private KeyStore setupKeystore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
try (InputStream readStream = getClass().getClassLoader().getResourceAsStream("keystore.jks")) {
|
||||
ks.load(readStream, PASSWORD.toCharArray());
|
||||
}
|
||||
return ks;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutputToFileTwoCertsAsPem(@TempDir final File folder) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
|
||||
KeyStore keyStore = setupKeystore();
|
||||
HashMap<String, Certificate> certs = TlsHelper.extractCerts(keyStore);
|
||||
TlsHelper.outputCertsAsPem(certs, folder,".crt");
|
||||
|
||||
assertEquals(2, folder.listFiles().length);
|
||||
|
||||
for (File file : folder.listFiles()) {
|
||||
X509Certificate certFromFile = loadCertificate(file);
|
||||
assertTrue(certs.containsValue(certFromFile));
|
||||
X509Certificate originalCert = (X509Certificate) certs.get(file.getName().split("\\.")[0]);
|
||||
assertEquals(originalCert, certFromFile);
|
||||
assertArrayEquals(originalCert.getSignature(), certFromFile.getSignature());
|
||||
FileUtils.deleteFile(file, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Keystore contains two certificates, but one key. This is to test the edge case where a certificate does not have a key.
|
||||
@Test
|
||||
public void testOutputToFileOneKeyAsPem(@TempDir final File folder) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
|
||||
KeyStore keyStore = setupKeystore();
|
||||
HashMap<String, Key> keys = TlsHelper.extractKeys(keyStore, PASSWORD.toCharArray());
|
||||
TlsHelper.outputKeysAsPem(keys, folder, ".key");
|
||||
|
||||
for (File file : folder.listFiles()) {
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||
PEMParser pemParser = new PEMParser(br);
|
||||
PEMKeyPair key = (PEMKeyPair) pemParser.readObject();
|
||||
assertArrayEquals(keys.get(file.getName().split("\\.")[0]).getEncoded(), key.getPrivateKeyInfo().getEncoded());
|
||||
} finally {
|
||||
FileUtils.deleteFile(file, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCerts() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
|
||||
KeyStore keyStore = setupKeystore();
|
||||
HashMap<String, Certificate> certs = TlsHelper.extractCerts(keyStore);
|
||||
assertEquals(2, certs.size());
|
||||
certs.forEach((String p, Certificate q) -> assertEquals("X.509", q.getType()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractKeys() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
|
||||
KeyStore keyStore = setupKeystore();
|
||||
HashMap<String, Key> keys = TlsHelper.extractKeys(keyStore, PASSWORD.toCharArray());
|
||||
assertEquals(1, keys.size());
|
||||
keys.forEach((String alias, Key key) -> assertEquals("PKCS#8", key.getFormat()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldVerifyCertificateSignatureWhenSelfSigned() {
|
||||
assertTrue(TlsHelper.verifyCertificateSignature(rootCert, Collections.singletonList(rootCert)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldVerifyCertificateSignatureWithMultipleSigningCerts(@Mock X509Certificate mockCertificate) {
|
||||
when(mockCertificate.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Mock Certificate"));
|
||||
assertTrue(TlsHelper.verifyCertificateSignature(rootCert, Arrays.asList(mockCertificate, rootCert)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldNotVerifyCertificateSignatureWithNoSigningCerts() {
|
||||
assertFalse(TlsHelper.verifyCertificateSignature(rootCert, new ArrayList<>()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldNotVerifyCertificateSignatureWithWrongSigningCert(@Mock X509Certificate mockCertificate) {
|
||||
when(mockCertificate.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Mock Certificate"));
|
||||
assertFalse(TlsHelper.verifyCertificateSignature(rootCert, Collections.singletonList(mockCertificate)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseKeyPairFromReaderShouldHandlePKCS8PrivateKey() throws Exception {
|
||||
final KeyPair expectedKeyPair = TlsHelper.parseKeyPairFromReader(new FileReader("src/test/resources/rootCert.key"));
|
||||
final PrivateKey expectedPrivateKey = expectedKeyPair.getPrivate();
|
||||
final KeyPair keyPair = TlsHelper.parseKeyPairFromReader(new FileReader("src/test/resources/rootCert-pkcs8.key"));
|
||||
|
||||
assertEquals(expectedPrivateKey, keyPair.getPrivate());
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -1,172 +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.
|
||||
#
|
||||
|
||||
# Core Properties #
|
||||
nifi.fake.property=fake value
|
||||
nifi.flow.configuration.file=./conf/flow.json.gz
|
||||
nifi.flow.configuration.archive.dir=./conf/archive/
|
||||
nifi.flowcontroller.autoResumeState=true
|
||||
nifi.flowcontroller.graceful.shutdown.period=10 sec
|
||||
nifi.flowservice.writedelay.interval=500 ms
|
||||
nifi.administrative.yield.duration=30 sec
|
||||
# If a component has no work to do (is "bored"), how long should we wait before checking again for work?
|
||||
nifi.bored.yield.duration=10 millis
|
||||
|
||||
nifi.authorizer.configuration.file=./conf/authorizers.xml
|
||||
nifi.login.identity.provider.configuration.file=./conf/login-identity-providers.xml
|
||||
nifi.ui.banner.text=
|
||||
nifi.ui.autorefresh.interval=30 sec
|
||||
nifi.nar.library.directory=./lib
|
||||
nifi.nar.working.directory=./work/nar/
|
||||
nifi.documentation.working.directory=./work/docs/components
|
||||
|
||||
####################
|
||||
# State Management #
|
||||
####################
|
||||
nifi.state.management.configuration.file=./conf/state-management.xml
|
||||
# The ID of the local state provider
|
||||
nifi.state.management.provider.local=local-provider
|
||||
# The ID of the cluster-wide state provider. This will be ignored if NiFi is not clustered but must be populated if running in a cluster.
|
||||
nifi.state.management.provider.cluster=zk-provider
|
||||
# Specifies whether or not this instance of NiFi should run an embedded ZooKeeper server
|
||||
nifi.state.management.embedded.zookeeper.start=false
|
||||
# Properties file that provides the ZooKeeper properties to use if <nifi.state.management.embedded.zookeeper.start> is set to true
|
||||
nifi.state.management.embedded.zookeeper.properties=./conf/zookeeper.properties
|
||||
|
||||
|
||||
# Database Settings
|
||||
nifi.database.directory=./database_repository
|
||||
|
||||
# FlowFile Repository
|
||||
nifi.flowfile.repository.implementation=org.apache.nifi.controller.repository.WriteAheadFlowFileRepository
|
||||
nifi.flowfile.repository.directory=./flowfile_repository
|
||||
nifi.flowfile.repository.partitions=256
|
||||
nifi.flowfile.repository.checkpoint.interval=2 mins
|
||||
nifi.flowfile.repository.always.sync=false
|
||||
|
||||
nifi.swap.manager.implementation=org.apache.nifi.controller.FileSystemSwapManager
|
||||
nifi.queue.swap.threshold=20000
|
||||
nifi.swap.in.period=5 sec
|
||||
nifi.swap.in.threads=1
|
||||
nifi.swap.out.period=5 sec
|
||||
nifi.swap.out.threads=4
|
||||
|
||||
# Content Repository
|
||||
nifi.content.repository.implementation=org.apache.nifi.controller.repository.FileSystemRepository
|
||||
nifi.content.claim.max.appendable.size=10 MB
|
||||
nifi.content.claim.max.flow.files=100
|
||||
nifi.content.repository.directory.default=./content_repository
|
||||
nifi.content.repository.archive.max.retention.period=12 hours
|
||||
nifi.content.repository.archive.max.usage.percentage=50%
|
||||
nifi.content.repository.archive.enabled=true
|
||||
nifi.content.repository.always.sync=false
|
||||
nifi.content.viewer.url=/nifi-content-viewer/
|
||||
|
||||
# Provenance Repository Properties
|
||||
nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
|
||||
|
||||
# Persistent Provenance Repository Properties
|
||||
nifi.provenance.repository.directory.default=./provenance_repository
|
||||
nifi.provenance.repository.max.storage.time=24 hours
|
||||
nifi.provenance.repository.max.storage.size=1 GB
|
||||
nifi.provenance.repository.rollover.time=30 secs
|
||||
nifi.provenance.repository.rollover.size=100 MB
|
||||
nifi.provenance.repository.query.threads=2
|
||||
nifi.provenance.repository.index.threads=1
|
||||
nifi.provenance.repository.compress.on.rollover=true
|
||||
nifi.provenance.repository.always.sync=false
|
||||
nifi.provenance.repository.journal.count=16
|
||||
# Comma-separated list of fields. Fields that are not indexed will not be searchable. Valid fields are:
|
||||
# EventType, FlowFileUUID, Filename, TransitURI, ProcessorID, AlternateIdentifierURI, Relationship, Details
|
||||
nifi.provenance.repository.indexed.fields=EventType, FlowFileUUID, Filename, ProcessorID, Relationship
|
||||
# FlowFile Attributes that should be indexed and made searchable. Some examples to consider are filename, uuid, mime.type
|
||||
nifi.provenance.repository.indexed.attributes=
|
||||
# Large values for the shard size will result in more Java heap usage when searching the Provenance Repository
|
||||
# but should provide better performance
|
||||
nifi.provenance.repository.index.shard.size=500 MB
|
||||
# Indicates the maximum length that a FlowFile attribute can be when retrieving a Provenance Event from
|
||||
# the repository. If the length of any attribute exceeds this value, it will be truncated when the event is retrieved.
|
||||
nifi.provenance.repository.max.attribute.length=65536
|
||||
|
||||
# Volatile Provenance Respository Properties
|
||||
nifi.provenance.repository.buffer.size=100000
|
||||
|
||||
# Component Status Repository
|
||||
nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
|
||||
nifi.components.status.repository.buffer.size=1440
|
||||
nifi.components.status.snapshot.frequency=1 min
|
||||
|
||||
# Site to Site properties
|
||||
nifi.remote.input.host=
|
||||
nifi.remote.input.secure=false
|
||||
nifi.remote.input.socket.port=
|
||||
nifi.remote.input.http.enabled=true
|
||||
nifi.remote.input.http.transaction.ttl=30 sec
|
||||
|
||||
# web properties #
|
||||
nifi.web.war.directory=./lib
|
||||
nifi.web.http.host=
|
||||
nifi.web.http.port=8080
|
||||
nifi.web.https.host=
|
||||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./work/jetty
|
||||
nifi.web.jetty.threads=200
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
|
||||
nifi.security.keystore=./conf/localhost.jks
|
||||
nifi.security.keystoreType=jks
|
||||
nifi.security.keystorePasswd=vidmcgplvih3dn2fqjnckns77g
|
||||
nifi.security.keyPasswd=qgs57rmnot6p8gm97pfjutnu5g
|
||||
nifi.security.truststore=./conf/truststore.jks
|
||||
nifi.security.truststoreType=jks
|
||||
nifi.security.truststorePasswd=t7rmn1fg8np2ck1sduqdd85opv
|
||||
nifi.security.user.authorizer=file-provider
|
||||
nifi.security.user.login.identity.provider=
|
||||
nifi.security.ocsp.responder.url=
|
||||
nifi.security.ocsp.responder.pemEncodedCertificate=
|
||||
|
||||
# cluster common properties (all nodes must have same values) #
|
||||
nifi.cluster.protocol.heartbeat.interval=5 sec
|
||||
nifi.cluster.protocol.is.secure=false
|
||||
|
||||
# cluster node properties (only configure for cluster nodes) #
|
||||
nifi.cluster.is.node=false
|
||||
nifi.cluster.node.address=
|
||||
nifi.cluster.node.protocol.port=
|
||||
nifi.cluster.node.protocol.threads=10
|
||||
nifi.cluster.node.event.history.size=25
|
||||
nifi.cluster.node.connection.timeout=5 sec
|
||||
nifi.cluster.node.read.timeout=5 sec
|
||||
nifi.cluster.firewall.file=
|
||||
|
||||
# zookeeper properties, used for cluster management #
|
||||
nifi.zookeeper.connect.string=
|
||||
nifi.zookeeper.connect.timeout=3 secs
|
||||
nifi.zookeeper.session.timeout=3 secs
|
||||
nifi.zookeeper.root.node=/nifi
|
||||
|
||||
# kerberos #
|
||||
nifi.kerberos.krb5.file=
|
||||
nifi.kerberos.service.principal=
|
||||
nifi.kerberos.service.keytab.location=
|
||||
|
||||
nifi.kerberos.spnego.principal=
|
||||
nifi.kerberos.spnego.keytab.location=
|
||||
nifi.kerberos.spnego.authentication.expiration=12 hours
|
Binary file not shown.
|
@ -1,41 +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.
|
||||
#
|
||||
|
||||
# This properties file specifies how to update the provided nifi.properties
|
||||
|
||||
# Comma separated list of properties to put the hostname into
|
||||
hostname.properties= \
|
||||
nifi.remote.input.host, \
|
||||
nifi.web.https.host, \
|
||||
nifi.cluster.node.address
|
||||
|
||||
# Comma separated list of properties to increment (must also be defined in this file)
|
||||
incrementing.properties= \
|
||||
nifi.web.https.port, \
|
||||
nifi.remote.input.socket.port, \
|
||||
nifi.cluster.node.protocol.port
|
||||
|
||||
nifi.web.https.port=8443
|
||||
nifi.remote.input.socket.port=10443
|
||||
nifi.cluster.node.protocol.port=11443
|
||||
|
||||
# Properties to set verbatim
|
||||
nifi.remote.input.secure=true
|
||||
nifi.cluster.protocol.is.secure=true
|
||||
|
||||
nifi.web.http.host=
|
||||
nifi.web.http.port=
|
|
@ -1,28 +0,0 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6+OcU6ijl9hqr
|
||||
q8yI/ZyahKPenOkuO9U+xAY/g4fu/LV/cLoilaStePL+Sy2w/XXpYLg92csnQUXi
|
||||
iBH/hvlel4W4teuawaTd1ZBpzfO6dnUA68kp+VCoKI58gbz24yScl7R6cerh+GPL
|
||||
ipmvA6YyjMZrJZrmJGMZ9juZLjiW5hC+f13P0bggigZWnEmuW0cOuXR8xxWpjop/
|
||||
Fv//qO+jviZbQTzmGO7jkkex4RQwcyxlokBh8qDZ1FCvITVQhbkZqLYijXseNYip
|
||||
ZSeG3n8K8kbcwqj/dedW7DJWdue3lt7L4341VWwwpOKDebs8YrZHp/Jwq6kgwxVn
|
||||
rQVuwujvAgMBAAECggEADOpKlBBEuPXSC8+3NjNGkQneg+8U0GPDrC1APTzps+Fy
|
||||
7BWuVds+X9k998DbrCEl9vP+Zg9YUCLbH/XEQIFjUlxnGUY/uxXrPIOXESfv0Q6D
|
||||
sIeZArQ9FRCQHxubIPa5vbNg/SBHWEqfIh011ngLD+zXe+lCFOmois+OeFtP/2RQ
|
||||
wzF1as3fr+j9MvXxVkwPYTdYYzNf6Vso0uxC98AZSlt9s+2ZQ8V7NsOgvk+qSPgp
|
||||
nj/mXVF+pCJqY2KPDHGb+gZsOHwC7LG+ehHLENz05YSh6kRvvZB15zgsmdpg1dS0
|
||||
jauDVABtxfO5Eu2/yjQnSClV2qbZE7J/e0Voj4WTqQKBgQDeNkzlW1rsEcFPUeZg
|
||||
ZlA65Adb5YPsJCAt5cIIHg7jGr/uXuIbaEzem3rlMZp7vwoe0nYtyJaspG41Ou4G
|
||||
gprHowXDXpl9JBIWLQqef4RtzFyY9DaL9U7Gl05UA/0vIQooZOAfYVMTtGSIa3+g
|
||||
rR4mtgekcykCzSNHIIhgY+a3XQKBgQDXZuB+4wCGLLeS/8hi2Xdbt2gYVXO7YLn1
|
||||
VbGaSn9a8ZDKtVQJRGnk82OVvQsgzgNmcpH/MLGg/RJ/VO5WY5E7Vn5nQ0omijBj
|
||||
K95JxOef5GorDuyzH0qAZFdce5aUNwG/mUgw/m9iz/knjtqtl1uMjc1uKu+/zLiQ
|
||||
e3UuSs9YuwKBgHSpX2+eqbp8i9e/8Mo1jPOOGgr2EW+de8N894RZe4lh1tgnul+X
|
||||
P5wzVq8Tfr5vCrop1l+tCuXyoeWSXbrPQMGE5havCLg5gsFfvk5+NiGLBCZNOIH8
|
||||
NPJwJ3BWc8sVdobEhyISb5JNx+YTQfsySD0cniCJUUOmPVovS0oHyO4FAoGAEQPu
|
||||
bfeOngrAQZt0/+H/3L3jOjDd4IpmhivLyM1jvBJjBrBGQCkoWE6lqjlxvJipihk4
|
||||
0TjOf1IeePKDlU1uNorBl3SoUd0Or3bSq28jgOzxOg+GwSuSngvPHt4gafriZ3k7
|
||||
S6t9rweQvgA55AaV08eL180KfVM1rSwjeJGuSWsCgYEAuOeoNAUG5PeKjSWhxc8f
|
||||
DpRsgBsPo+bxnX4eAr+GyTJf/1uacmfejLOPHOImGnL3pGgu7SzEHGjl+McZznhC
|
||||
foJyK+7igz7iuS18AuEu4VR+J7y2vqdmWeabFHI2onEPwvlO1vrpOIL2yd/3wxgC
|
||||
qwoPTd89hQB2k+Uhuf7FLjg=
|
||||
-----END PRIVATE KEY-----
|
|
@ -1,20 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWTCCAkGgAwIBAgIGAVXgcI3oMA0GCSqGSIb3DQEBCwUAMC0xFTATBgNVBAMM
|
||||
DG5pZmkucm9vdC5jYTEUMBIGA1UECwwLYXBhY2hlLm5pZmkwHhcNMTYwNzEyMTg0
|
||||
ODQwWhcNMTcwNzEyMTg0ODQwWjAtMRUwEwYDVQQDDAxuaWZpLnJvb3QuY2ExFDAS
|
||||
BgNVBAsMC2FwYWNoZS5uaWZpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
|
||||
AQEAuvjnFOoo5fYaq6vMiP2cmoSj3pzpLjvVPsQGP4OH7vy1f3C6IpWkrXjy/kst
|
||||
sP116WC4PdnLJ0FF4ogR/4b5XpeFuLXrmsGk3dWQac3zunZ1AOvJKflQqCiOfIG8
|
||||
9uMknJe0enHq4fhjy4qZrwOmMozGayWa5iRjGfY7mS44luYQvn9dz9G4IIoGVpxJ
|
||||
rltHDrl0fMcVqY6Kfxb//6jvo74mW0E85hju45JHseEUMHMsZaJAYfKg2dRQryE1
|
||||
UIW5Gai2Io17HjWIqWUnht5/CvJG3MKo/3XnVuwyVnbnt5bey+N+NVVsMKTig3m7
|
||||
PGK2R6fycKupIMMVZ60FbsLo7wIDAQABo38wfTAOBgNVHQ8BAf8EBAMCAf4wDAYD
|
||||
VR0TBAUwAwEB/zAdBgNVHQ4EFgQUxPKyo9LTvN7jTJ1x2jVUe4nNz1swHwYDVR0j
|
||||
BBgwFoAUxPKyo9LTvN7jTJ1x2jVUe4nNz1swHQYDVR0lBBYwFAYIKwYBBQUHAwIG
|
||||
CCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBcMRAlnWDco5WbjTV0uSxmS7Dh
|
||||
RxYgt7YguqA/tdfcn6hhJZ2ZZm5By6nwP4aGFY45tOv2NsjHgchgfJ6Osl3ZxGmF
|
||||
+JrXW7mveOwZIfZzM2yFCusgkzrGOAWNL2G+lbXpRCnsTuJL6jUbRE1cFwU+iUYo
|
||||
E8Xfo6XZVNlzeab4WNerAPGssftV9C+0ya9+5+hFmBhzpGkpn5EVicxLAX6fI4K6
|
||||
N4ZCBU8DYQilYkE0xgxySSz7Ia1vo8D7Tr30CxoXGsqRLXanW0Jw1wVsLVheTLZw
|
||||
W3gp2XKOaP/BMFbIFw2iB7REeCao14u9pRAKEodpH+fpUosbFlpd8ysRvFJP
|
||||
-----END CERTIFICATE-----
|
|
@ -1,27 +0,0 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAuvjnFOoo5fYaq6vMiP2cmoSj3pzpLjvVPsQGP4OH7vy1f3C6
|
||||
IpWkrXjy/kstsP116WC4PdnLJ0FF4ogR/4b5XpeFuLXrmsGk3dWQac3zunZ1AOvJ
|
||||
KflQqCiOfIG89uMknJe0enHq4fhjy4qZrwOmMozGayWa5iRjGfY7mS44luYQvn9d
|
||||
z9G4IIoGVpxJrltHDrl0fMcVqY6Kfxb//6jvo74mW0E85hju45JHseEUMHMsZaJA
|
||||
YfKg2dRQryE1UIW5Gai2Io17HjWIqWUnht5/CvJG3MKo/3XnVuwyVnbnt5bey+N+
|
||||
NVVsMKTig3m7PGK2R6fycKupIMMVZ60FbsLo7wIDAQABAoIBAAzqSpQQRLj10gvP
|
||||
tzYzRpEJ3oPvFNBjw6wtQD086bPhcuwVrlXbPl/ZPffA26whJfbz/mYPWFAi2x/1
|
||||
xECBY1JcZxlGP7sV6zyDlxEn79EOg7CHmQK0PRUQkB8bmyD2ub2zYP0gR1hKnyId
|
||||
NdZ4Cw/s13vpQhTpqIrPjnhbT/9kUMMxdWrN36/o/TL18VZMD2E3WGMzX+lbKNLs
|
||||
QvfAGUpbfbPtmUPFezbDoL5Pqkj4KZ4/5l1RfqQiamNijwxxm/oGbDh8AuyxvnoR
|
||||
yxDc9OWEoepEb72Qdec4LJnaYNXUtI2rg1QAbcXzuRLtv8o0J0gpVdqm2ROyf3tF
|
||||
aI+Fk6kCgYEA3jZM5Vta7BHBT1HmYGZQOuQHW+WD7CQgLeXCCB4O4xq/7l7iG2hM
|
||||
3pt65TGae78KHtJ2LciWrKRuNTruBoKax6MFw16ZfSQSFi0Knn+EbcxcmPQ2i/VO
|
||||
xpdOVAP9LyEKKGTgH2FTE7RkiGt/oK0eJrYHpHMpAs0jRyCIYGPmt10CgYEA12bg
|
||||
fuMAhiy3kv/IYtl3W7doGFVzu2C59VWxmkp/WvGQyrVUCURp5PNjlb0LIM4DZnKR
|
||||
/zCxoP0Sf1TuVmORO1Z+Z0NKJoowYyveScTnn+RqKw7ssx9KgGRXXHuWlDcBv5lI
|
||||
MP5vYs/5J47arZdbjI3Nbirvv8y4kHt1LkrPWLsCgYB0qV9vnqm6fIvXv/DKNYzz
|
||||
jhoK9hFvnXvDfPeEWXuJYdbYJ7pflz+cM1avE36+bwq6KdZfrQrl8qHlkl26z0DB
|
||||
hOYWrwi4OYLBX75OfjYhiwQmTTiB/DTycCdwVnPLFXaGxIciEm+STcfmE0H7Mkg9
|
||||
HJ4giVFDpj1aL0tKB8juBQKBgBED7m33jp4KwEGbdP/h/9y94zow3eCKZoYry8jN
|
||||
Y7wSYwawRkApKFhOpao5cbyYqYoZONE4zn9SHnjyg5VNbjaKwZd0qFHdDq920qtv
|
||||
I4Ds8ToPhsErkp4Lzx7eIGn64md5O0urfa8HkL4AOeQGldPHi9fNCn1TNa0sI3iR
|
||||
rklrAoGBALjnqDQFBuT3io0locXPHw6UbIAbD6Pm8Z1+HgK/hskyX/9bmnJn3oyz
|
||||
jxziJhpy96RoLu0sxBxo5fjHGc54Qn6Ccivu4oM+4rktfALhLuFUfie8tr6nZlnm
|
||||
mxRyNqJxD8L5Ttb66TiC9snf98MYAqsKD03fPYUAdpPlIbn+xS44
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -23,7 +23,6 @@
|
|||
<artifactId>nifi-toolkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>nifi-toolkit-tls</module>
|
||||
<module>nifi-toolkit-encrypt-config</module>
|
||||
<module>nifi-toolkit-flowfile-repo</module>
|
||||
<module>nifi-toolkit-assembly</module>
|
||||
|
|
Loading…
Reference in New Issue