From b3cb5d7ead12f8fc970a1c44dd54cb2176c08683 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Tue, 21 Jun 2016 15:31:56 -0700 Subject: [PATCH 01/10] NIFI-1688 Added test skeleton. --- .../standard/TestPostHTTPGroovy.groovy | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy new file mode 100644 index 000000000000..5a52856404a0 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -0,0 +1,71 @@ +/* + * 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.processors.standard + +import org.apache.nifi.util.MockFlowFile +import org.apache.nifi.util.StopWatch +import org.apache.nifi.util.TestRunners +import org.junit.After +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import java.util.concurrent.TimeUnit + +import static org.junit.Assert.assertNotNull + +@RunWith(JUnit4.class) +class TestPostHTTPGroovy extends GroovyTestCase { + private static final Logger logger = LoggerFactory.getLogger(TestPostHTTPGroovy.class) + + @BeforeClass + public static void setUpOnce() throws Exception { + logger.metaClass.methodMissing = { String name, args -> + logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") + } + } + + @Before + public void setUp() throws Exception { + + } + + @After + public void tearDown() throws Exception { + + } + + @Test + public void testShouldSupportTLSv1() { + + } + + @Test + public void testShouldSupportTLSv1_1() { + + } + + @Test + public void testShouldSupportTLSv1_2() { + + } +} \ No newline at end of file From a20c4c46696eac123f09bfdf3a5053b7bbc9e0ce Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Tue, 21 Jun 2016 15:32:54 -0700 Subject: [PATCH 02/10] NIFI-1688 Cleaned up unnecessary imports. --- .../nifi/processors/standard/TestPostHTTPGroovy.groovy | 8 -------- 1 file changed, 8 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index 5a52856404a0..3121f8bd0571 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -16,23 +16,15 @@ */ package org.apache.nifi.processors.standard -import org.apache.nifi.util.MockFlowFile -import org.apache.nifi.util.StopWatch -import org.apache.nifi.util.TestRunners import org.junit.After import org.junit.Before import org.junit.BeforeClass -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.util.concurrent.TimeUnit - -import static org.junit.Assert.assertNotNull - @RunWith(JUnit4.class) class TestPostHTTPGroovy extends GroovyTestCase { private static final Logger logger = LoggerFactory.getLogger(TestPostHTTPGroovy.class) From 0ac084142ca7946d8a7bc1d91b1820a4ad4e2789 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Wed, 22 Jun 2016 20:52:37 -0700 Subject: [PATCH 03/10] NIFI-1688 Implemented integration test that generates key pair, inserts into and persists keystore, starts embedded HTTPS Jetty server, connects, and verifies response. Currently only TLSv1.2 connections are successful because of the overlap of cipher suites. Will manually insert cipher suites into server for TLSv1 and TLSv1.1 support. --- .../standard/TestPostHTTPGroovy.groovy | 356 +++++++++++++++++- .../TestPostHTTP/ReverseHandler.groovy | 10 + 2 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index 3121f8bd0571..bbe2c3534f57 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -16,7 +16,30 @@ */ package org.apache.nifi.processors.standard +import groovy.servlet.GroovyServlet +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.ExtendedKeyUsage +import org.bouncycastle.asn1.x509.KeyPurposeId +import org.bouncycastle.asn1.x509.KeyUsage +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import org.bouncycastle.asn1.x509.X509Extension +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.X509v3CertificateBuilder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.operator.ContentSigner +import org.bouncycastle.operator.OperatorCreationException +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +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.servlet.ServletContextHandler +import org.eclipse.jetty.util.ssl.SslContextFactory import org.junit.After +import org.junit.AfterClass import org.junit.Before import org.junit.BeforeClass import org.junit.Test @@ -25,15 +48,292 @@ import org.junit.runners.JUnit4 import org.slf4j.Logger import org.slf4j.LoggerFactory +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager +import java.security.InvalidKeyException +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.NoSuchAlgorithmException +import java.security.NoSuchProviderException +import java.security.PrivateKey +import java.security.PublicKey +import java.security.Security +import java.security.SignatureException +import java.security.cert.Certificate +import java.security.cert.CertificateException +import java.security.cert.X509Certificate + @RunWith(JUnit4.class) class TestPostHTTPGroovy extends GroovyTestCase { private static final Logger logger = LoggerFactory.getLogger(TestPostHTTPGroovy.class) + private static final int KEY_SIZE = 2048; + + private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000; + private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000; + private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; + private static final String PROVIDER = "BC"; + + private static final String SUBJECT_DN = "CN=localhost,OU=Security,O=Apache,ST=CA,C=US"; + + private static final String DEFAULT_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword" + private static final String DEFAULT_KEY_PASSWORD = "thisIsABadKeyPassword" + private static final String DEFAULT_KEY_ALIAS = "jetty-host-private" + + private static final String TLSv1 = "TLSv1" + private static final String TLSv1_1 = "TLSv1.1" + private static final String TLSv1_2 = "TLSv1.2" + private static final List DEFAULT_PROTOCOLS = [TLSv1, TLSv1_1, TLSv1_2] + + private static final String DEFAULT_HOSTNAME = "localhost" + private static final int DEFAULT_TLS_PORT = 8456 + private static final String HTTPS_URL = "https://${DEFAULT_HOSTNAME}:${DEFAULT_TLS_PORT}" + + private static KeyStore keystore + private static String keystorePath + private static Server server + static private final String KEYSTORE_TYPE = "JKS" + + /** + * Generates a public/private RSA keypair using the default key size. + * + * @param keySize the key size in bits to use (defaults to 2048) + * @return the keypair + * @throws java.security.NoSuchAlgorithmException if the RSA algorithm is not available + */ + private static KeyPair generateKeyPair(int keySize = KEY_SIZE) throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(keySize); + return keyPairGenerator.generateKeyPair(); + } + + /** + * Generates a signed certificate using an on-demand keypair. + * + * @param dn the DN + * @return the certificate + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws java.security.cert.CertificateException + * @throws java.security.NoSuchProviderException + * @throws java.security.SignatureException + * @throws java.security.InvalidKeyException + * @throws org.bouncycastle.operator.OperatorCreationException + */ + private + static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { + KeyPair keyPair = generateKeyPair(); + return generateCertificate(dn, keyPair); + } + + /** + * Generates a signed certificate with a specific keypair. + * + * @param dn the DN + * @param keyPair the public key will be included in the certificate and the the private key is used to sign the certificate + * @return the certificate + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws CertificateException + * @throws NoSuchProviderException + * @throws SignatureException + * @throws InvalidKeyException + * @throws OperatorCreationException + */ + private + static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { + PrivateKey privateKey = keyPair.getPrivate(); + ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey); + SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); + Date startDate = new Date(YESTERDAY); + Date endDate = new Date(ONE_YEAR_FROM_NOW); + + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( + new X500Name(dn), + BigInteger.valueOf(System.currentTimeMillis()), + startDate, endDate, + new X500Name(dn), + subPubKeyInfo); + + // Set certificate extensions + // (1) digitalSignature extension + certBuilder.addExtension(X509Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement)); + + // (2) extendedKeyUsage extension + Vector ekUsages = new Vector<>(); + ekUsages.add(KeyPurposeId.id_kp_clientAuth); + ekUsages.add(KeyPurposeId.id_kp_serverAuth); + certBuilder.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages)); + + // Sign the certificate + X509CertificateHolder certificateHolder = certBuilder.build(sigGen); + return new JcaX509CertificateConverter().setProvider(PROVIDER) + .getCertificate(certificateHolder); + } + + /** + * Generates a certificate signed by the issuer key. + * + * @param dn the subject DN + * @param issuerDn the issuer DN + * @param issuerKey the issuer private key + * @return the certificate + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws CertificateException + * @throws NoSuchProviderException + * @throws SignatureException + * @throws InvalidKeyException + * @throws OperatorCreationException + */ + private + static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { + KeyPair keyPair = generateKeyPair(); + return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey); + } + + /** + * Generates a certificate with a specific public key signed by the issuer key. + * + * @param dn the subject DN + * @param publicKey the subject public key + * @param issuerDn the issuer DN + * @param issuerKey the issuer private key + * @return the certificate + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws CertificateException + * @throws NoSuchProviderException + * @throws SignatureException + * @throws InvalidKeyException + * @throws OperatorCreationException + */ + private + static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { + ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey); + SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); + Date startDate = new Date(YESTERDAY); + Date endDate = new Date(ONE_YEAR_FROM_NOW); + + X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder( + new X500Name(issuerDn), + BigInteger.valueOf(System.currentTimeMillis()), + startDate, endDate, + new X500Name(dn), + subPubKeyInfo); + + X509CertificateHolder certificateHolder = v3CertGen.build(sigGen); + return new JcaX509CertificateConverter().setProvider(PROVIDER) + .getCertificate(certificateHolder); + } + + public + static KeyStore prepareKeyStore(KeyPair keyPair = generateKeyPair(), String dn = SUBJECT_DN, String keystorePassword = DEFAULT_KEYSTORE_PASSWORD) { + X509Certificate certificate = generateCertificate(dn, keyPair) + keystore = KeyStore.getInstance(KEYSTORE_TYPE) + keystore.load(null, null) + keystore.setKeyEntry(DEFAULT_KEY_ALIAS, keyPair.private, DEFAULT_KEY_PASSWORD.chars, [certificate] as Certificate[]) + + keystorePath = "src/test/resources/TestPostHTTP/${System.currentTimeMillis()}.jks" + keystore.store(new FileOutputStream(keystorePath), keystorePassword.chars) + keystore + } + @BeforeClass public static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) + logger.metaClass.methodMissing = { String name, args -> logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") } + + keystore = prepareKeyStore() + server = createServer() + + // Set the default trust manager for the test (the outgoing Groovy call) to ignore certificate path verification for localhost + + def nullTrustManager = [ + checkClientTrusted: { chain, authType -> }, + checkServerTrusted: { chain, authType -> }, + getAcceptedIssuers: { null } + ] + + def nullHostnameVerifier = [ + verify: { String hostname, session -> + // Will always return true if the hostname is "localhost" + hostname.equalsIgnoreCase(DEFAULT_HOSTNAME) + } + ] + + SSLContext sc = SSLContext.getInstance("SSL") + sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null) + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()) + HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier as HostnameVerifier) + } + + private static Server createServer(List supportedProtocols = DEFAULT_PROTOCOLS) { + // Create Server + server = new Server() + + // Add some secure config + final HttpConfiguration httpsConfiguration = new HttpConfiguration() + httpsConfiguration.setSecureScheme("https") + httpsConfiguration.setSecurePort(DEFAULT_TLS_PORT) + httpsConfiguration.addCustomizer(new SecureRequestCustomizer()) + + // Build the TLS connector + final ServerConnector https = createConnector(httpsConfiguration, supportedProtocols) + + // Add this connector + server.addConnector(https) + + /** Create a simple Groovlet that responds to the incoming request by reversing the string parameter + * i.e. localhost:8456/ReverseHandler.groovy?string=Happy%20birthday -> yadhtrib yppaH + */ + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS) + context.with { + contextPath = '/' + resourceBase = 'src/test/resources/TestPostHTTP' + addServlet(GroovyServlet, '*.groovy') + } + server.setHandler(context) + server + } + + private + static ServerConnector createConnector(HttpConfiguration httpsConfiguration, List supportedProtocols = DEFAULT_PROTOCOLS) { + ServerConnector https = new ServerConnector(server, + new SslConnectionFactory(createSslContextFactory(supportedProtocols), "http/1.1"), + new HttpConnectionFactory(httpsConfiguration)) + + // set host and port + https.setHost(DEFAULT_HOSTNAME) + https.setPort(DEFAULT_TLS_PORT) + https + } + + private static SslContextFactory createSslContextFactory(List supportedProtocols = DEFAULT_PROTOCOLS) { + final SslContextFactory contextFactory = new SslContextFactory() + contextFactory.needClientAuth = false + contextFactory.wantClientAuth = false + + contextFactory.setKeyStorePath(keystorePath) + contextFactory.setKeyStoreType(KEYSTORE_TYPE) + contextFactory.setKeyStorePassword(DEFAULT_KEYSTORE_PASSWORD) + contextFactory.setKeyManagerPassword(DEFAULT_KEY_PASSWORD) + + contextFactory.setIncludeProtocols(supportedProtocols as String[]) + contextFactory + } + + @AfterClass + public static void tearDownOnce() { + new File(keystorePath).delete() } @Before @@ -43,21 +343,75 @@ class TestPostHTTPGroovy extends GroovyTestCase { @After public void tearDown() throws Exception { - + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } } @Test public void testShouldSupportTLSv1() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" + + // Configure server with TLSv1 only + server = createServer([TLSv1]) + // Start server + server.start() + + // Act + String response = new URL(url).text + logger.info("Response from ${HTTPS_URL}: ${response}") + + // Assert + assert response == MSG.reverse() } @Test public void testShouldSupportTLSv1_1() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" + + // Configure server with TLSv1.1 only + server = createServer([TLSv1_1]) + + // Start server + server.start() + // Act + String response = new URL(url).text + logger.info("Response from ${HTTPS_URL}: ${response}") + + // Assert + assert response == MSG.reverse() } @Test public void testShouldSupportTLSv1_2() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" + + // Configure server with TLSv1.2 only + server = createServer([TLSv1_2]) + + // Start server + server.start() + + // Act + String response = new URL(url).text + logger.info("Response from ${HTTPS_URL}: ${response}") + + // Assert + assert response == MSG.reverse() + } + + @Test + public void testShouldPreferTLSv1_2() { } } \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy new file mode 100644 index 000000000000..1952b3a95cc6 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy @@ -0,0 +1,10 @@ +package TestPostHTTP + +import javax.servlet.http.HttpServletResponse + +final string = request.parameterMap['string'] +if (!string || string.size() < 1){ + response.setStatus(HttpServletResponse.SC_BAD_REQUEST) + return +} +print URLDecoder.decode(string[0] as String, 'UTF-8').reverse() From 60a06e40fa1d423d628eb77922294e676fd6e1fc Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Thu, 23 Jun 2016 08:13:08 -0700 Subject: [PATCH 04/10] NIFI-1688 Added debug information for supported cipher suites. --- .../standard/TestPostHTTPGroovy.groovy | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index bbe2c3534f57..a09d080c04c1 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -48,6 +48,8 @@ import org.junit.runners.JUnit4 import org.slf4j.Logger import org.slf4j.LoggerFactory +import javax.crypto.Cipher +import javax.net.SocketFactory import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext @@ -270,9 +272,12 @@ class TestPostHTTPGroovy extends GroovyTestCase { } ] - SSLContext sc = SSLContext.getInstance("SSL") + SSLContext sc = SSLContext.getInstance(TLSv1_2) sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null) - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()) + SocketFactory socketFactory = sc.getSocketFactory() + logger.info("Max AES key length: ${Cipher.getMaxAllowedKeyLength("AES")}") + logger.info("Supported client cipher suites: ${socketFactory.supportedCipherSuites}") + HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory) HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier as HostnameVerifier) } @@ -351,7 +356,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { } @Test - public void testShouldSupportTLSv1() { + public void testDefaultShouldSupportTLSv1() { // Arrange final String MSG = "This is a test message" final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" @@ -371,7 +376,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { } @Test - public void testShouldSupportTLSv1_1() { + public void testDefaultShouldSupportTLSv1_1() { // Arrange final String MSG = "This is a test message" final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" @@ -391,7 +396,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { } @Test - public void testShouldSupportTLSv1_2() { + public void testDefaultShouldSupportTLSv1_2() { // Arrange final String MSG = "This is a test message" final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" @@ -411,7 +416,22 @@ class TestPostHTTPGroovy extends GroovyTestCase { } @Test - public void testShouldPreferTLSv1_2() { + public void testDefaultShouldPreferTLSv1_2() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/ReverseHandler.groovy?string=${URLEncoder.encode(MSG, "UTF-8")}" + + // Configure server with all TLS protocols + server = createServer() + + // Start server + server.start() + // Act + String response = new URL(url).text + logger.info("Response from ${HTTPS_URL}: ${response}") + + // Assert + assert response == MSG.reverse() } } \ No newline at end of file From 90fdce25579256223cfeda8cb13ff786d20e1714 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Thu, 23 Jun 2016 18:01:28 -0700 Subject: [PATCH 05/10] NIFI-1688 Added test Groovlet for handling POST requests. Modified construction of SSLSocketFactory to avoid hardcoding supported protocol. Added integration tests (2 of 4 pass -- TLSv1.2 is supported on my machine but TLSv1 and TLSv1.1 are not). --- .../nifi/processors/standard/PostHTTP.java | 30 ++- .../standard/TestPostHTTPGroovy.groovy | 186 +++++++++++++++++- .../resources/TestPostHTTP/PostHandler.groovy | 10 + 3 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java index 79d281512ceb..7fa85f967c27 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java @@ -353,7 +353,7 @@ public void onStopped() { configMap.clear(); final StreamThrottler throttler = throttlerRef.getAndSet(null); - if(throttler != null) { + if (throttler != null) { try { throttler.close(); } catch (IOException e) { @@ -396,7 +396,33 @@ private Config getConfig(final String url, final ProcessContext context) { throw new ProcessException(e); } - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + // String[] supportedCipherSuites = new String[] {"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + // "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_RSA_WITH_AES_256_CBC_SHA256", + // "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + // "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + // "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + // "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + // "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + // "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + // "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", + // "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + // "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + // "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + // "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + // "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + // "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + // "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + // "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + // "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + // "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + // "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + // "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + // "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256"}; + String[] supportedCipherSuites = null; + // final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory + // (sslContext, new String[]{"TLSv1"}, supportedCipherSuites, + // SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); // Also use a plain socket factory for regular http connections (especially proxies) final Registry socketFactoryRegistry = diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index a09d080c04c1..b5d2122871e8 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -17,6 +17,10 @@ package org.apache.nifi.processors.standard import groovy.servlet.GroovyServlet +import org.apache.nifi.ssl.SSLContextService +import org.apache.nifi.ssl.StandardSSLContextService +import org.apache.nifi.util.TestRunner +import org.apache.nifi.util.TestRunners import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.ExtendedKeyUsage import org.bouncycastle.asn1.x509.KeyPurposeId @@ -42,6 +46,7 @@ import org.junit.After import org.junit.AfterClass import org.junit.Before import org.junit.BeforeClass +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -85,6 +90,8 @@ class TestPostHTTPGroovy extends GroovyTestCase { private static final String DEFAULT_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword" private static final String DEFAULT_KEY_PASSWORD = "thisIsABadKeyPassword" private static final String DEFAULT_KEY_ALIAS = "jetty-host-private" + private static final String DEFAULT_CERTIFICATE_ALIAS = "jetty-host" + static private final String KEYSTORE_TYPE = "JKS" private static final String TLSv1 = "TLSv1" private static final String TLSv1_1 = "TLSv1.1" @@ -96,9 +103,12 @@ class TestPostHTTPGroovy extends GroovyTestCase { private static final String HTTPS_URL = "https://${DEFAULT_HOSTNAME}:${DEFAULT_TLS_PORT}" private static KeyStore keystore + private static KeyStore truststore private static String keystorePath + private static String truststorePath private static Server server - static private final String KEYSTORE_TYPE = "JKS" + + private static TestRunner runner /** * Generates a public/private RSA keypair using the default key size. @@ -235,7 +245,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { } public - static KeyStore prepareKeyStore(KeyPair keyPair = generateKeyPair(), String dn = SUBJECT_DN, String keystorePassword = DEFAULT_KEYSTORE_PASSWORD) { + static KeyStore prepareKeyStoreAndTrustStore(KeyPair keyPair = generateKeyPair(), String dn = SUBJECT_DN, String keystorePassword = DEFAULT_KEYSTORE_PASSWORD) { X509Certificate certificate = generateCertificate(dn, keyPair) keystore = KeyStore.getInstance(KEYSTORE_TYPE) keystore.load(null, null) @@ -243,6 +253,15 @@ class TestPostHTTPGroovy extends GroovyTestCase { keystorePath = "src/test/resources/TestPostHTTP/${System.currentTimeMillis()}.jks" keystore.store(new FileOutputStream(keystorePath), keystorePassword.chars) + + // Also prepare a truststore for the client + truststore = KeyStore.getInstance(KEYSTORE_TYPE) + truststore.load(null, null) + truststore.setCertificateEntry(DEFAULT_CERTIFICATE_ALIAS, certificate as Certificate) + + truststorePath = "src/test/resources/TestPostHTTP/${System.currentTimeMillis()}-trust.jks" + truststore.store(new FileOutputStream(truststorePath), DEFAULT_KEYSTORE_PASSWORD.chars) + keystore } @@ -254,10 +273,10 @@ class TestPostHTTPGroovy extends GroovyTestCase { logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") } - keystore = prepareKeyStore() + keystore = prepareKeyStoreAndTrustStore() server = createServer() - // Set the default trust manager for the test (the outgoing Groovy call) to ignore certificate path verification for localhost + // Set the default trust manager for the "default" tests (the outgoing Groovy call) to ignore certificate path verification for localhost def nullTrustManager = [ checkClientTrusted: { chain, authType -> }, @@ -275,10 +294,12 @@ class TestPostHTTPGroovy extends GroovyTestCase { SSLContext sc = SSLContext.getInstance(TLSv1_2) sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null) SocketFactory socketFactory = sc.getSocketFactory() - logger.info("Max AES key length: ${Cipher.getMaxAllowedKeyLength("AES")}") + logger.info("JCE unlimited strength installed: ${Cipher.getMaxAllowedKeyLength("AES") > 128}") logger.info("Supported client cipher suites: ${socketFactory.supportedCipherSuites}") HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory) HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier as HostnameVerifier) + + runner = TestRunners.newTestRunner(PostHTTP.class) } private static Server createServer(List supportedProtocols = DEFAULT_PROTOCOLS) { @@ -339,6 +360,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { @AfterClass public static void tearDownOnce() { new File(keystorePath).delete() + new File(truststorePath).delete() } @Before @@ -355,6 +377,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { } } + @Ignore @Test public void testDefaultShouldSupportTLSv1() { // Arrange @@ -375,6 +398,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { assert response == MSG.reverse() } + @Ignore @Test public void testDefaultShouldSupportTLSv1_1() { // Arrange @@ -395,6 +419,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { assert response == MSG.reverse() } + @Ignore @Test public void testDefaultShouldSupportTLSv1_2() { // Arrange @@ -415,6 +440,22 @@ class TestPostHTTPGroovy extends GroovyTestCase { assert response == MSG.reverse() } + @Ignore + @Test + public void testDefaultClient() { + // Arrange + final String MSG = "This is a test message" + final String url = "https://www.ssllabs.com/ssltest/viewMyClient.html" + + // Act + String response = new URL(url).text + logger.info("Response from ${url}: ${response}") + + // Assert + assert response + } + + @Ignore @Test public void testDefaultShouldPreferTLSv1_2() { // Arrange @@ -434,4 +475,139 @@ class TestPostHTTPGroovy extends GroovyTestCase { // Assert assert response == MSG.reverse() } + + @Test + public void testPostHTTPShouldSupportTLSv1() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/PostHandler.groovy" + + // Configure the test runner + final SSLContextService sslContextService = new StandardSSLContextService() + runner.addControllerService("ssl-context", sslContextService) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.enableControllerService(sslContextService) + + runner.setProperty(PostHTTP.URL, url) + runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") + runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") + + // Configure server with TLSv1 only + server = createServer([TLSv1]) + + // Start server + server.start() + + // Act + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + } + + @Test + public void testPostHTTPShouldSupportTLSv1_1() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/PostHandler.groovy" + + // Configure the test runner + final SSLContextService sslContextService = new StandardSSLContextService() + runner.addControllerService("ssl-context", sslContextService) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.enableControllerService(sslContextService) + + logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") + + runner.setProperty(PostHTTP.URL, url) + runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") + runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") +// runner.setProperty(PostHTTP.SEND_AS_FLOWFILE, "false") + + // Configure server with TLSv1.1 only + server = createServer([TLSv1_1]) + + // Start server + server.start() + + // Act + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + } + + @Test + public void testPostHTTPShouldSupportTLSv1_2() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/PostHandler.groovy" + + // Configure the test runner + final SSLContextService sslContextService = new StandardSSLContextService() + runner.addControllerService("ssl-context", sslContextService) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.enableControllerService(sslContextService) + + logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") + + runner.setProperty(PostHTTP.URL, url) + runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") + runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") + + // Configure server with TLSv1.2 only + server = createServer([TLSv1_2]) + + // Start server + server.start() + + // Act + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + } + + @Test + public void testPostHTTPShouldPreferTLSv1_2() { + // Arrange + final String MSG = "This is a test message" + final String url = "${HTTPS_URL}/PostHandler.groovy" + + // Configure the test runner + final SSLContextService sslContextService = new StandardSSLContextService() + runner.addControllerService("ssl-context", sslContextService) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.enableControllerService(sslContextService) + + logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") + + runner.setProperty(PostHTTP.URL, url) + runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") + runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") + + // Configure server with all TLS protocols + server = createServer() + + // Start server + server.start() + + // Act + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + } } \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy new file mode 100644 index 000000000000..6a36f4a25640 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy @@ -0,0 +1,10 @@ +package TestPostHTTP + +import javax.servlet.http.HttpServletResponse + +final contents = request.reader.text +if (!contents || contents.size() < 1){ + response.setStatus(HttpServletResponse.SC_BAD_REQUEST) + return +} +print "Thank you for submitting flowfile content ${contents}" From fc864b2459e89406c66df20ad0edce18ce161296 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Thu, 23 Jun 2016 20:43:04 -0700 Subject: [PATCH 06/10] NIFI-1688 Resolved issue in PostHTTP -- now uses SSLContextService's protocol setting. Tests pass (require cleanup). Previously, dynamically-generated keystores with only RSA certificates were not acceptable for TLSv1 or TLSv1.1 connections which required DSA/DSS cipher suites for some reason. --- .../nifi/processors/standard/PostHTTP.java | 4 +- .../standard/TestPostHTTPGroovy.groovy | 47 +++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java index 7fa85f967c27..7df50bdf84c3 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java @@ -392,6 +392,7 @@ private Config getConfig(final String url, final ProcessContext context) { final SSLContext sslContext; try { sslContext = createSSLContext(sslContextService); + getLogger().info("PostHTTP supports protocol: " + sslContext.getProtocol()); } catch (final Exception e) { throw new ProcessException(e); } @@ -423,7 +424,6 @@ private Config getConfig(final String url, final ProcessContext context) { // (sslContext, new String[]{"TLSv1"}, supportedCipherSuites, // SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); - // Also use a plain socket factory for regular http connections (especially proxies) final Registry socketFactoryRegistry = RegistryBuilder.create() @@ -463,6 +463,8 @@ private SSLContext createSSLContext(final SSLContextService service) builder = builder.loadKeyMaterial(keystore, service.getKeyStorePassword().toCharArray()); } + builder = builder.useProtocol(service.getSslAlgorithm()); + final SSLContext sslContext = builder.build(); return sslContext; } diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index b5d2122871e8..a726dfd4e9c2 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -106,6 +106,10 @@ class TestPostHTTPGroovy extends GroovyTestCase { private static KeyStore truststore private static String keystorePath private static String truststorePath + + private static String keystorePassword = DEFAULT_KEYSTORE_PASSWORD + private static String truststorePassword = DEFAULT_KEYSTORE_PASSWORD + private static Server server private static TestRunner runner @@ -350,8 +354,8 @@ class TestPostHTTPGroovy extends GroovyTestCase { contextFactory.setKeyStorePath(keystorePath) contextFactory.setKeyStoreType(KEYSTORE_TYPE) - contextFactory.setKeyStorePassword(DEFAULT_KEYSTORE_PASSWORD) - contextFactory.setKeyManagerPassword(DEFAULT_KEY_PASSWORD) + contextFactory.setKeyStorePassword(keystorePassword) +// contextFactory.setKeyManagerPassword(DEFAULT_KEY_PASSWORD) contextFactory.setIncludeProtocols(supportedProtocols as String[]) contextFactory @@ -359,8 +363,8 @@ class TestPostHTTPGroovy extends GroovyTestCase { @AfterClass public static void tearDownOnce() { - new File(keystorePath).delete() - new File(truststorePath).delete() +// new File(keystorePath).delete() +// new File(truststorePath).delete() } @Before @@ -495,6 +499,10 @@ class TestPostHTTPGroovy extends GroovyTestCase { runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") // Configure server with TLSv1 only + keystorePath = "src/test/resources/localhost-ks.jks" + keystorePassword = "localtest" + truststorePath = "src/test/resources/localhost-ts.jks" + truststorePassword = "localtest" server = createServer([TLSv1]) // Start server @@ -520,6 +528,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.1") runner.enableControllerService(sslContextService) logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") @@ -530,6 +539,10 @@ class TestPostHTTPGroovy extends GroovyTestCase { // runner.setProperty(PostHTTP.SEND_AS_FLOWFILE, "false") // Configure server with TLSv1.1 only + keystorePath = "src/test/resources/localhost-ks.jks" + keystorePassword = "localtest" + truststorePath = "src/test/resources/localhost-ts.jks" + truststorePassword = "localtest" server = createServer([TLSv1_1]) // Start server @@ -598,6 +611,10 @@ class TestPostHTTPGroovy extends GroovyTestCase { runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") // Configure server with all TLS protocols + keystorePath = "src/test/resources/localhost-ks.jks" + keystorePassword = "localtest" + truststorePath = "src/test/resources/localhost-ts.jks" + truststorePassword = "localtest" server = createServer() // Start server @@ -610,4 +627,26 @@ class TestPostHTTPGroovy extends GroovyTestCase { // Assert runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) } + + @Test + public void runServer() { + + keystorePath = "src/test/resources/localhost-ks.jks" + keystorePassword = "localtest" + truststorePath = "src/test/resources/localhost-ts.jks" + truststorePassword = "localtest" + + // Configure server with TLSv1 only + server = createServer() + + // Start server + server.start() + + boolean exit = false + + while (!exit) { + sleep(5000) + logger.info("Still running") + } + } } \ No newline at end of file From 26df6c8d9bf683a4b55665cdbcf6d4d196ed032d Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Thu, 23 Jun 2016 20:48:16 -0700 Subject: [PATCH 07/10] NIFI-1688 Tests pass (cleanup still required). --- .../processors/standard/TestPostHTTPGroovy.groovy | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index a726dfd4e9c2..79569f405d25 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -379,6 +379,9 @@ class TestPostHTTPGroovy extends GroovyTestCase { } catch (Exception e) { e.printStackTrace(); } + + runner.clearTransferState() + runner.clearProvenanceEvents() } @Ignore @@ -490,7 +493,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { final SSLContextService sslContextService = new StandardSSLContextService() runner.addControllerService("ssl-context", sslContextService) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) runner.enableControllerService(sslContextService) @@ -526,7 +529,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { final SSLContextService sslContextService = new StandardSSLContextService() runner.addControllerService("ssl-context", sslContextService) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.1") runner.enableControllerService(sslContextService) @@ -566,7 +569,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { final SSLContextService sslContextService = new StandardSSLContextService() runner.addControllerService("ssl-context", sslContextService) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) runner.enableControllerService(sslContextService) @@ -600,7 +603,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { final SSLContextService sslContextService = new StandardSSLContextService() runner.addControllerService("ssl-context", sslContextService) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) runner.enableControllerService(sslContextService) @@ -628,6 +631,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) } + @Ignore("Runs a server temporarily until stopped for testing with s_client") @Test public void runServer() { From 075a2731881ea1ddd8458b41b89ed427f224c194 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Wed, 29 Jun 2016 15:41:32 -0700 Subject: [PATCH 08/10] NIFI-1688 Tests pass (cleanup still required). --- .../standard/TestPostHTTPGroovy.groovy | 366 ++++-------------- 1 file changed, 85 insertions(+), 281 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index 79569f405d25..d30088f73ea0 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -21,19 +21,7 @@ import org.apache.nifi.ssl.SSLContextService import org.apache.nifi.ssl.StandardSSLContextService import org.apache.nifi.util.TestRunner import org.apache.nifi.util.TestRunners -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x509.ExtendedKeyUsage -import org.bouncycastle.asn1.x509.KeyPurposeId -import org.bouncycastle.asn1.x509.KeyUsage -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo -import org.bouncycastle.asn1.x509.X509Extension -import org.bouncycastle.cert.X509CertificateHolder -import org.bouncycastle.cert.X509v3CertificateBuilder -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.bouncycastle.operator.ContentSigner -import org.bouncycastle.operator.OperatorCreationException -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import org.eclipse.jetty.server.HttpConfiguration import org.eclipse.jetty.server.HttpConnectionFactory import org.eclipse.jetty.server.SecureRequestCustomizer @@ -52,45 +40,25 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.slf4j.Logger import org.slf4j.LoggerFactory +import sun.net.www.protocol.https.HttpsURLConnectionImpl import javax.crypto.Cipher import javax.net.SocketFactory import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocket import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager -import java.security.InvalidKeyException -import java.security.KeyPair -import java.security.KeyPairGenerator +import java.nio.charset.StandardCharsets import java.security.KeyStore -import java.security.NoSuchAlgorithmException -import java.security.NoSuchProviderException -import java.security.PrivateKey -import java.security.PublicKey import java.security.Security -import java.security.SignatureException -import java.security.cert.Certificate -import java.security.cert.CertificateException -import java.security.cert.X509Certificate @RunWith(JUnit4.class) class TestPostHTTPGroovy extends GroovyTestCase { private static final Logger logger = LoggerFactory.getLogger(TestPostHTTPGroovy.class) - private static final int KEY_SIZE = 2048; - private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000; - private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000; - private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; - private static final String PROVIDER = "BC"; - - private static final String SUBJECT_DN = "CN=localhost,OU=Security,O=Apache,ST=CA,C=US"; - - private static final String DEFAULT_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword" - private static final String DEFAULT_KEY_PASSWORD = "thisIsABadKeyPassword" - private static final String DEFAULT_KEY_ALIAS = "jetty-host-private" - private static final String DEFAULT_CERTIFICATE_ALIAS = "jetty-host" static private final String KEYSTORE_TYPE = "JKS" private static final String TLSv1 = "TLSv1" @@ -101,211 +69,20 @@ class TestPostHTTPGroovy extends GroovyTestCase { private static final String DEFAULT_HOSTNAME = "localhost" private static final int DEFAULT_TLS_PORT = 8456 private static final String HTTPS_URL = "https://${DEFAULT_HOSTNAME}:${DEFAULT_TLS_PORT}" + private static final String POST_URL = "${HTTPS_URL}/PostHandler.groovy" - private static KeyStore keystore - private static KeyStore truststore - private static String keystorePath - private static String truststorePath + private static String keystorePath = "src/test/resources/localhost-ks.jks" + private static String truststorePath = "src/test/resources/localhost-ts.jks" - private static String keystorePassword = DEFAULT_KEYSTORE_PASSWORD - private static String truststorePassword = DEFAULT_KEYSTORE_PASSWORD + private static String keystorePassword = "localtest" + private static String truststorePassword = "localtest" private static Server server + private static X509TrustManager nullTrustManager + private static HostnameVerifier nullHostnameVerifier private static TestRunner runner - /** - * Generates a public/private RSA keypair using the default key size. - * - * @param keySize the key size in bits to use (defaults to 2048) - * @return the keypair - * @throws java.security.NoSuchAlgorithmException if the RSA algorithm is not available - */ - private static KeyPair generateKeyPair(int keySize = KEY_SIZE) throws NoSuchAlgorithmException { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(keySize); - return keyPairGenerator.generateKeyPair(); - } - - /** - * Generates a signed certificate using an on-demand keypair. - * - * @param dn the DN - * @return the certificate - * @throws IOException - * @throws NoSuchAlgorithmException - * @throws java.security.cert.CertificateException - * @throws java.security.NoSuchProviderException - * @throws java.security.SignatureException - * @throws java.security.InvalidKeyException - * @throws org.bouncycastle.operator.OperatorCreationException - */ - private - static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { - KeyPair keyPair = generateKeyPair(); - return generateCertificate(dn, keyPair); - } - - /** - * Generates a signed certificate with a specific keypair. - * - * @param dn the DN - * @param keyPair the public key will be included in the certificate and the the private key is used to sign the certificate - * @return the certificate - * @throws IOException - * @throws NoSuchAlgorithmException - * @throws CertificateException - * @throws NoSuchProviderException - * @throws SignatureException - * @throws InvalidKeyException - * @throws OperatorCreationException - */ - private - static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { - PrivateKey privateKey = keyPair.getPrivate(); - ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey); - SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); - Date startDate = new Date(YESTERDAY); - Date endDate = new Date(ONE_YEAR_FROM_NOW); - - X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( - new X500Name(dn), - BigInteger.valueOf(System.currentTimeMillis()), - startDate, endDate, - new X500Name(dn), - subPubKeyInfo); - - // Set certificate extensions - // (1) digitalSignature extension - certBuilder.addExtension(X509Extension.keyUsage, true, - new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement)); - - // (2) extendedKeyUsage extension - Vector ekUsages = new Vector<>(); - ekUsages.add(KeyPurposeId.id_kp_clientAuth); - ekUsages.add(KeyPurposeId.id_kp_serverAuth); - certBuilder.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages)); - - // Sign the certificate - X509CertificateHolder certificateHolder = certBuilder.build(sigGen); - return new JcaX509CertificateConverter().setProvider(PROVIDER) - .getCertificate(certificateHolder); - } - - /** - * Generates a certificate signed by the issuer key. - * - * @param dn the subject DN - * @param issuerDn the issuer DN - * @param issuerKey the issuer private key - * @return the certificate - * @throws IOException - * @throws NoSuchAlgorithmException - * @throws CertificateException - * @throws NoSuchProviderException - * @throws SignatureException - * @throws InvalidKeyException - * @throws OperatorCreationException - */ - private - static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { - KeyPair keyPair = generateKeyPair(); - return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey); - } - - /** - * Generates a certificate with a specific public key signed by the issuer key. - * - * @param dn the subject DN - * @param publicKey the subject public key - * @param issuerDn the issuer DN - * @param issuerKey the issuer private key - * @return the certificate - * @throws IOException - * @throws NoSuchAlgorithmException - * @throws CertificateException - * @throws NoSuchProviderException - * @throws SignatureException - * @throws InvalidKeyException - * @throws OperatorCreationException - */ - private - static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { - ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey); - SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); - Date startDate = new Date(YESTERDAY); - Date endDate = new Date(ONE_YEAR_FROM_NOW); - - X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder( - new X500Name(issuerDn), - BigInteger.valueOf(System.currentTimeMillis()), - startDate, endDate, - new X500Name(dn), - subPubKeyInfo); - - X509CertificateHolder certificateHolder = v3CertGen.build(sigGen); - return new JcaX509CertificateConverter().setProvider(PROVIDER) - .getCertificate(certificateHolder); - } - - public - static KeyStore prepareKeyStoreAndTrustStore(KeyPair keyPair = generateKeyPair(), String dn = SUBJECT_DN, String keystorePassword = DEFAULT_KEYSTORE_PASSWORD) { - X509Certificate certificate = generateCertificate(dn, keyPair) - keystore = KeyStore.getInstance(KEYSTORE_TYPE) - keystore.load(null, null) - keystore.setKeyEntry(DEFAULT_KEY_ALIAS, keyPair.private, DEFAULT_KEY_PASSWORD.chars, [certificate] as Certificate[]) - - keystorePath = "src/test/resources/TestPostHTTP/${System.currentTimeMillis()}.jks" - keystore.store(new FileOutputStream(keystorePath), keystorePassword.chars) - - // Also prepare a truststore for the client - truststore = KeyStore.getInstance(KEYSTORE_TYPE) - truststore.load(null, null) - truststore.setCertificateEntry(DEFAULT_CERTIFICATE_ALIAS, certificate as Certificate) - - truststorePath = "src/test/resources/TestPostHTTP/${System.currentTimeMillis()}-trust.jks" - truststore.store(new FileOutputStream(truststorePath), DEFAULT_KEYSTORE_PASSWORD.chars) - - keystore - } - - @BeforeClass - public static void setUpOnce() throws Exception { - Security.addProvider(new BouncyCastleProvider()) - - logger.metaClass.methodMissing = { String name, args -> - logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") - } - - keystore = prepareKeyStoreAndTrustStore() - server = createServer() - - // Set the default trust manager for the "default" tests (the outgoing Groovy call) to ignore certificate path verification for localhost - - def nullTrustManager = [ - checkClientTrusted: { chain, authType -> }, - checkServerTrusted: { chain, authType -> }, - getAcceptedIssuers: { null } - ] - - def nullHostnameVerifier = [ - verify: { String hostname, session -> - // Will always return true if the hostname is "localhost" - hostname.equalsIgnoreCase(DEFAULT_HOSTNAME) - } - ] - - SSLContext sc = SSLContext.getInstance(TLSv1_2) - sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null) - SocketFactory socketFactory = sc.getSocketFactory() - logger.info("JCE unlimited strength installed: ${Cipher.getMaxAllowedKeyLength("AES") > 128}") - logger.info("Supported client cipher suites: ${socketFactory.supportedCipherSuites}") - HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory) - HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier as HostnameVerifier) - - runner = TestRunners.newTestRunner(PostHTTP.class) - } - private static Server createServer(List supportedProtocols = DEFAULT_PROTOCOLS) { // Create Server server = new Server() @@ -355,16 +132,61 @@ class TestPostHTTPGroovy extends GroovyTestCase { contextFactory.setKeyStorePath(keystorePath) contextFactory.setKeyStoreType(KEYSTORE_TYPE) contextFactory.setKeyStorePassword(keystorePassword) -// contextFactory.setKeyManagerPassword(DEFAULT_KEY_PASSWORD) contextFactory.setIncludeProtocols(supportedProtocols as String[]) contextFactory } + @BeforeClass + public static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) + + logger.metaClass.methodMissing = { String name, args -> + logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") + } + + server = createServer() + + // Set the default trust manager for the "default" tests (the outgoing Groovy call) to ignore certificate path verification for localhost + + nullTrustManager = [ + checkClientTrusted: { chain, authType -> }, + checkServerTrusted: { chain, authType -> }, + getAcceptedIssuers: { null } + ] as X509TrustManager + + nullHostnameVerifier = [ + verify: { String hostname, session -> + // Will always return true if the hostname is "localhost" + hostname.equalsIgnoreCase(DEFAULT_HOSTNAME) + } + ] as HostnameVerifier + + SSLContext sc = SSLContext.getInstance(TLSv1_2) + sc.init(null, [nullTrustManager] as TrustManager[], null) + SocketFactory socketFactory = sc.getSocketFactory() + logger.info("JCE unlimited strength installed: ${Cipher.getMaxAllowedKeyLength("AES") > 128}") + logger.info("Supported client cipher suites: ${socketFactory.supportedCipherSuites}") + HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory) + HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier) + + // Configure the test runner + runner = TestRunners.newTestRunner(PostHTTP.class) + final SSLContextService sslContextService = new StandardSSLContextService() + runner.addControllerService("ssl-context", sslContextService) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) + runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.enableControllerService(sslContextService) + + runner.setProperty(PostHTTP.URL, POST_URL) + runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") + runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") + } + @AfterClass public static void tearDownOnce() { -// new File(keystorePath).delete() -// new File(truststorePath).delete() + } @Before @@ -384,7 +206,6 @@ class TestPostHTTPGroovy extends GroovyTestCase { runner.clearProvenanceEvents() } - @Ignore @Test public void testDefaultShouldSupportTLSv1() { // Arrange @@ -405,7 +226,6 @@ class TestPostHTTPGroovy extends GroovyTestCase { assert response == MSG.reverse() } - @Ignore @Test public void testDefaultShouldSupportTLSv1_1() { // Arrange @@ -426,7 +246,6 @@ class TestPostHTTPGroovy extends GroovyTestCase { assert response == MSG.reverse() } - @Ignore @Test public void testDefaultShouldSupportTLSv1_2() { // Arrange @@ -447,22 +266,6 @@ class TestPostHTTPGroovy extends GroovyTestCase { assert response == MSG.reverse() } - @Ignore - @Test - public void testDefaultClient() { - // Arrange - final String MSG = "This is a test message" - final String url = "https://www.ssllabs.com/ssltest/viewMyClient.html" - - // Act - String response = new URL(url).text - logger.info("Response from ${url}: ${response}") - - // Assert - assert response - } - - @Ignore @Test public void testDefaultShouldPreferTLSv1_2() { // Arrange @@ -475,37 +278,45 @@ class TestPostHTTPGroovy extends GroovyTestCase { // Start server server.start() + // Create a connection that could use TLSv1, TLSv1.1, or TLSv1.2 + SSLContext sc = SSLContext.getInstance("TLS") + sc.init(null, [nullTrustManager] as TrustManager[], null) + SocketFactory socketFactory = sc.getSocketFactory() + + URL formedUrl = new URL(url) + SSLSocket socket = (SSLSocket) socketFactory.createSocket(formedUrl.host, formedUrl.port) + logger.info("Enabled protocols: ${socket.enabledProtocols}") + // Act - String response = new URL(url).text - logger.info("Response from ${HTTPS_URL}: ${response}") + socket.startHandshake() + String selectedProtocol = socket.getSession().protocol + logger.info("Selected protocol: ${ selectedProtocol}") // Assert - assert response == MSG.reverse() + assert selectedProtocol == TLSv1_2 } - @Test - public void testPostHTTPShouldSupportTLSv1() { - // Arrange - final String MSG = "This is a test message" - final String url = "${HTTPS_URL}/PostHandler.groovy" - - // Configure the test runner + private static void enableContextServiceProtocol(TestRunner runner, String protocol) { final SSLContextService sslContextService = new StandardSSLContextService() runner.addControllerService("ssl-context", sslContextService) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) + runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, protocol) runner.enableControllerService(sslContextService) + logger.info("PostHTTP supported protocols: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).protocol}") + logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") + } - runner.setProperty(PostHTTP.URL, url) - runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") - runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") + // Add tests where SSLContextService only supports 1, 1.1, 1.2 + // Add tests where SSLContextService supports all and server only supports 1, 1.1, 1.2 + + @Test + public void testPostHTTPShouldSupportTLSv1() { + // Arrange + final String MSG = "This is a test message" // Configure server with TLSv1 only - keystorePath = "src/test/resources/localhost-ks.jks" - keystorePassword = "localtest" - truststorePath = "src/test/resources/localhost-ts.jks" - truststorePassword = "localtest" server = createServer([TLSv1]) // Start server @@ -523,18 +334,11 @@ class TestPostHTTPGroovy extends GroovyTestCase { public void testPostHTTPShouldSupportTLSv1_1() { // Arrange final String MSG = "This is a test message" - final String url = "${HTTPS_URL}/PostHandler.groovy" // Configure the test runner - final SSLContextService sslContextService = new StandardSSLContextService() - runner.addControllerService("ssl-context", sslContextService) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) - runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.1") - runner.enableControllerService(sslContextService) + enableContextServiceProtocol(runner, TLSv1_1) + - logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") runner.setProperty(PostHTTP.URL, url) runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") From 68e6e13b61d09266bdbf9d15a174c5b266e794e1 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Fri, 8 Jul 2016 23:13:35 -0700 Subject: [PATCH 09/10] NIFI-1688 Finished integration tests for PostHTTP processor. --- .../standard/TestPostHTTPGroovy.groovy | 176 +++++++----------- 1 file changed, 65 insertions(+), 111 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy index d30088f73ea0..891cf3c826a2 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy @@ -98,6 +98,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { // Add this connector server.addConnector(https) + logger.info("Created server with supported protocols: ${supportedProtocols}") /** Create a simple Groovlet that responds to the incoming request by reversing the string parameter * i.e. localhost:8456/ReverseHandler.groovy?string=Happy%20birthday -> yadhtrib yppaH @@ -162,14 +163,6 @@ class TestPostHTTPGroovy extends GroovyTestCase { } ] as HostnameVerifier - SSLContext sc = SSLContext.getInstance(TLSv1_2) - sc.init(null, [nullTrustManager] as TrustManager[], null) - SocketFactory socketFactory = sc.getSocketFactory() - logger.info("JCE unlimited strength installed: ${Cipher.getMaxAllowedKeyLength("AES") > 128}") - logger.info("Supported client cipher suites: ${socketFactory.supportedCipherSuites}") - HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory) - HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier) - // Configure the test runner runner = TestRunners.newTestRunner(PostHTTP.class) final SSLContextService sslContextService = new StandardSSLContextService() @@ -191,7 +184,14 @@ class TestPostHTTPGroovy extends GroovyTestCase { @Before public void setUp() throws Exception { - + // This must be executed before each test, or the connections will be re-used and if a TLSv1.1 connection is re-used against a server that only supports TLSv1.2, it will fail + SSLContext sc = SSLContext.getInstance(TLSv1_2) + sc.init(null, [nullTrustManager] as TrustManager[], null) + SocketFactory socketFactory = sc.getSocketFactory() + logger.info("JCE unlimited strength installed: ${Cipher.getMaxAllowedKeyLength("AES") > 128}") + logger.info("Supported client cipher suites: ${socketFactory.supportedCipherSuites}") + HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory) + HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier) } @After @@ -290,7 +290,7 @@ class TestPostHTTPGroovy extends GroovyTestCase { // Act socket.startHandshake() String selectedProtocol = socket.getSession().protocol - logger.info("Selected protocol: ${ selectedProtocol}") + logger.info("Selected protocol: ${selectedProtocol}") // Assert assert selectedProtocol == TLSv1_2 @@ -308,11 +308,11 @@ class TestPostHTTPGroovy extends GroovyTestCase { logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") } - // Add tests where SSLContextService only supports 1, 1.1, 1.2 - // Add tests where SSLContextService supports all and server only supports 1, 1.1, 1.2 - + /** + * This test creates a server that supports TLSv1. It iterates over an {@link SSLContextService} with TLSv1, TLSv1.1, and TLSv1.2 support. All three context services should be able to communicate successfully. + */ @Test - public void testPostHTTPShouldSupportTLSv1() { + public void testPostHTTPShouldConnectToServerWithTLSv1() { // Arrange final String MSG = "This is a test message" @@ -323,65 +323,64 @@ class TestPostHTTPGroovy extends GroovyTestCase { server.start() // Act - runner.enqueue(MSG.getBytes()) - runner.run() - - // Assert - runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + [TLSv1, TLSv1_1, TLSv1_2].each { String tlsVersion -> + logger.info("Set context service protocol to ${tlsVersion}") + enableContextServiceProtocol(runner, tlsVersion) + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + runner.clearTransferState() + logger.info("Ran successfully") + } } + /** + * This test creates a server that supports TLSv1.1. It iterates over an {@link SSLContextService} with TLSv1, TLSv1.1, and TLSv1.2 support. The context service with TLSv1 should not be able to communicate with a server that does not support it, but TLSv1.1 and TLSv1.2 should be able to communicate successfully. + */ @Test - public void testPostHTTPShouldSupportTLSv1_1() { + public void testPostHTTPShouldConnectToServerWithTLSv1_1() { // Arrange final String MSG = "This is a test message" - // Configure the test runner - enableContextServiceProtocol(runner, TLSv1_1) - - - - runner.setProperty(PostHTTP.URL, url) - runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") - runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") -// runner.setProperty(PostHTTP.SEND_AS_FLOWFILE, "false") - // Configure server with TLSv1.1 only - keystorePath = "src/test/resources/localhost-ks.jks" - keystorePassword = "localtest" - truststorePath = "src/test/resources/localhost-ts.jks" - truststorePassword = "localtest" server = createServer([TLSv1_1]) // Start server server.start() // Act + logger.info("Set context service protocol to ${TLSv1}") + enableContextServiceProtocol(runner, TLSv1) runner.enqueue(MSG.getBytes()) runner.run() // Assert - runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + runner.assertAllFlowFilesTransferred(PostHTTP.REL_FAILURE, 1) + runner.clearTransferState() + logger.expected("Unable to connect") + + [TLSv1_1, TLSv1_2].each { String tlsVersion -> + logger.info("Set context service protocol to ${tlsVersion}") + enableContextServiceProtocol(runner, tlsVersion) + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) + runner.clearTransferState() + logger.info("Ran successfully") + } } + /** + * This test creates a server that supports TLSv1.2. It iterates over an {@link SSLContextService} with TLSv1, TLSv1.1, and TLSv1.2 support. The context services with TLSv1 and TLSv1.1 should not be able to communicate with a server that does not support it, but TLSv1.2 should be able to communicate successfully. + */ @Test - public void testPostHTTPShouldSupportTLSv1_2() { + public void testPostHTTPShouldConnectToServerWithTLSv1_2() { // Arrange final String MSG = "This is a test message" - final String url = "${HTTPS_URL}/PostHandler.groovy" - - // Configure the test runner - final SSLContextService sslContextService = new StandardSSLContextService() - runner.addControllerService("ssl-context", sslContextService) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) - runner.enableControllerService(sslContextService) - - logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") - - runner.setProperty(PostHTTP.URL, url) - runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") - runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") // Configure server with TLSv1.2 only server = createServer([TLSv1_2]) @@ -390,71 +389,26 @@ class TestPostHTTPGroovy extends GroovyTestCase { server.start() // Act - runner.enqueue(MSG.getBytes()) - runner.run() - - // Assert - runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) - } - - @Test - public void testPostHTTPShouldPreferTLSv1_2() { - // Arrange - final String MSG = "This is a test message" - final String url = "${HTTPS_URL}/PostHandler.groovy" - - // Configure the test runner - final SSLContextService sslContextService = new StandardSSLContextService() - runner.addControllerService("ssl-context", sslContextService) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, truststorePath) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, truststorePassword) - runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE) - runner.enableControllerService(sslContextService) - - logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}") - - runner.setProperty(PostHTTP.URL, url) - runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context") - runner.setProperty(PostHTTP.CHUNKED_ENCODING, "false") - - // Configure server with all TLS protocols - keystorePath = "src/test/resources/localhost-ks.jks" - keystorePassword = "localtest" - truststorePath = "src/test/resources/localhost-ts.jks" - truststorePassword = "localtest" - server = createServer() - - // Start server - server.start() + [TLSv1, TLSv1_1].each { String tlsVersion -> + logger.info("Set context service protocol to ${tlsVersion}") + enableContextServiceProtocol(runner, tlsVersion) + runner.enqueue(MSG.getBytes()) + runner.run() + + // Assert + runner.assertAllFlowFilesTransferred(PostHTTP.REL_FAILURE, 1) + runner.clearTransferState() + logger.expected("Unable to connect") + } - // Act + logger.info("Set context service protocol to ${TLSv1_2}") + enableContextServiceProtocol(runner, TLSv1_2) runner.enqueue(MSG.getBytes()) runner.run() // Assert runner.assertAllFlowFilesTransferred(PostHTTP.REL_SUCCESS, 1) - } - - @Ignore("Runs a server temporarily until stopped for testing with s_client") - @Test - public void runServer() { - - keystorePath = "src/test/resources/localhost-ks.jks" - keystorePassword = "localtest" - truststorePath = "src/test/resources/localhost-ts.jks" - truststorePassword = "localtest" - - // Configure server with TLSv1 only - server = createServer() - - // Start server - server.start() - - boolean exit = false - - while (!exit) { - sleep(5000) - logger.info("Still running") - } + runner.clearTransferState() + logger.info("Ran successfully") } } \ No newline at end of file From 8aef8f2313102a490f8707d70a6529e681bacad1 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Fri, 8 Jul 2016 23:29:32 -0700 Subject: [PATCH 10/10] NIFI-1688 Removed legacy comments. Added ASF license to Groovlet handlers for test. --- .../nifi/processors/standard/PostHTTP.java | 26 ------------------- .../resources/TestPostHTTP/PostHandler.groovy | 16 ++++++++++++ .../TestPostHTTP/ReverseHandler.groovy | 16 ++++++++++++ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java index 7df50bdf84c3..e78f1e577bf0 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java @@ -397,32 +397,6 @@ private Config getConfig(final String url, final ProcessContext context) { throw new ProcessException(e); } - // String[] supportedCipherSuites = new String[] {"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", - // "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_RSA_WITH_AES_256_CBC_SHA256", - // "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", - // "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", - // "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - // "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - // "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - // "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - // "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", - // "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", - // "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", - // "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - // "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - // "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - // "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - // "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - // "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", - // "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", - // "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - // "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", - // "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", - // "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256"}; - String[] supportedCipherSuites = null; - // final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory - // (sslContext, new String[]{"TLSv1"}, supportedCipherSuites, - // SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); // Also use a plain socket factory for regular http connections (especially proxies) final Registry socketFactoryRegistry = diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy index 6a36f4a25640..230848d064f4 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/PostHandler.groovy @@ -1,3 +1,19 @@ +/* + * 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 TestPostHTTP import javax.servlet.http.HttpServletResponse diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy index 1952b3a95cc6..ccfa23b0dcc0 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestPostHTTP/ReverseHandler.groovy @@ -1,3 +1,19 @@ +/* + * 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 TestPostHTTP import javax.servlet.http.HttpServletResponse