From 68fa1859c7caae4bdcfe647a159ed77f0ee5b4e8 Mon Sep 17 00:00:00 2001 From: Thomas Spring Date: Mon, 14 Sep 2020 18:01:10 -0700 Subject: [PATCH 1/2] Add newrelic ssl certificate if ca_bundle_path is not specified. Resolves issue #50 --- .../agent/transport/DataSenderFactory.java | 2 +- .../transport/apache/ApacheSSLManager.java | 37 ++++++++++++++++--- .../main/resources/META-INF/newrelic-com.pem | 35 ++++++++++++++++++ .../com/newrelic/agent/RPMServiceTest.java | 3 ++ 4 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 newrelic-agent/src/main/resources/META-INF/newrelic-com.pem diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/transport/DataSenderFactory.java b/newrelic-agent/src/main/java/com/newrelic/agent/transport/DataSenderFactory.java index 0cb43470d4..ab1394e90d 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/transport/DataSenderFactory.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/transport/DataSenderFactory.java @@ -64,7 +64,7 @@ public DataSender create(DataSenderConfig config, DataSenderListener dataSenderL } private ApacheHttpClientWrapper buildApacheHttpClientWrapper(DataSenderConfig config, Logger logger) { - SSLContext sslContext = ApacheSSLManager.createSSLContext(config.isSSL(), config.getCaBundlePath()); + SSLContext sslContext = ApacheSSLManager.createSSLContext(config.getCaBundlePath()); ApacheProxyManager proxyManager = new ApacheProxyManager( config.getProxyHost(), diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java b/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java index 909152d9e8..3942ea8d69 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java @@ -15,9 +15,11 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -26,13 +28,16 @@ import java.util.logging.Level; public class ApacheSSLManager { - public static SSLContext createSSLContext(boolean useSSL, String caBundlePath) { + private static final String NEW_RELIC_CERT = "META-INF/newrelic-com.pem"; + + public static SSLContext createSSLContext(String caBundlePath) { SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); try { - if (useSSL && (caBundlePath != null)) { + if (caBundlePath != null) { sslContextBuilder.loadTrustMaterial(getKeyStore(caBundlePath), null); + } else { + addNewRelicCertToTrustStore(sslContextBuilder); } - return sslContextBuilder.build(); } catch (Exception e) { Agent.LOG.log(Level.WARNING, e, "Unable to create SSL context"); @@ -40,7 +45,28 @@ public static SSLContext createSSLContext(boolean useSSL, String caBundlePath) { } } - private static KeyStore getKeyStore(String caBundlePath) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { + private static void addNewRelicCertToTrustStore(SSLContextBuilder sslContextBuilder) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + URL nrCertUrl = ApacheSSLManager.class.getClassLoader().getResource(NEW_RELIC_CERT); + if (nrCertUrl != null) { + try (InputStream is = nrCertUrl.openStream()) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate cert = cf.generateCertificate(is); + keystore.load(null, null); + keystore.setCertificateEntry("newrelic", cert); + Agent.LOG.log(Level.FINEST, "Installed New Relic ssl certificate at alias: newrelic"); + } catch (IOException e) { + Agent.LOG.log(Level.INFO, "Unable to add New Relic ssl certificate.", e); + } + } else { + Agent.LOG.log(Level.INFO, "Unable to add New Relic ssl certificate."); + } + sslContextBuilder.loadTrustMaterial(keystore, null); + } + + private static KeyStore getKeyStore(String caBundlePath) + throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); Agent.LOG.finer("SSL Keystore Provider: " + keystore.getProvider().getName()); @@ -54,7 +80,8 @@ private static KeyStore getKeyStore(String caBundlePath) throws KeyStoreExceptio try { caCerts.add((X509Certificate) cf.generateCertificate(is)); } catch (Throwable t) { - Agent.LOG.log(Level.SEVERE, "Unable to generate ca_bundle_path certificate. Will not process further certs.", t); + Agent.LOG.log(Level.SEVERE, + "Unable to generate ca_bundle_path certificate. Will not process further certs.", t); break; } } diff --git a/newrelic-agent/src/main/resources/META-INF/newrelic-com.pem b/newrelic-agent/src/main/resources/META-INF/newrelic-com.pem new file mode 100644 index 0000000000..909eb5394a --- /dev/null +++ b/newrelic-agent/src/main/resources/META-INF/newrelic-com.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +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 +aSfWVsATwHjpT+pVxJLT47vYMlo= +-----END CERTIFICATE----- diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/RPMServiceTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/RPMServiceTest.java index cc9328c610..5e36dd49f5 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/RPMServiceTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/RPMServiceTest.java @@ -119,6 +119,7 @@ public static Map createStagingMap(boolean https, boolean isHigh map.put("port", https ? MOCK_COLLECTOR_HTTPS_PORT : MOCK_COLLECTOR_HTTP_PORT); map.put("ssl", https); map.put("license_key", "deadbeefcafebabe8675309babecafe1beefdead"); + map.put("ca_bundle_path", "src/test/resources/server.cer"); map.put(AgentConfigImpl.APP_NAME, "MyApplication"); map.put(AgentConfigImpl.LABELS, "one:two;three:four"); if (isHighSec) { @@ -1095,6 +1096,7 @@ public void testBadLicense() throws Exception { map.put("port", MOCK_COLLECTOR_HTTPS_PORT); map.put("ssl", false); map.put("license_key", "xxxxxxxxxxxxxxxxxxxxxxxxxxx"); + map.put("ca_bundle_path", "src/test/resources/server.cer"); map.put(AgentConfigImpl.APP_NAME, "MyApplication"); createServiceManager(map); @@ -1108,6 +1110,7 @@ public void testBadLicenseWithPut() throws Exception { map.put("port", MOCK_COLLECTOR_HTTPS_PORT); map.put("ssl", false); map.put("license_key", "xxxxxxxxxxxxxxxxxxxxxxxxxxx"); + map.put("ca_bundle_path", "src/test/resources/server.cer"); map.put(AgentConfigImpl.APP_NAME, "MyApplication"); map.put(AgentConfigImpl.PUT_FOR_DATA_SEND_PROPERTY, true); createServiceManager(map); From c1c405b53c19ec354f5225a2401ec10a298c3c57 Mon Sep 17 00:00:00 2001 From: Thomas Spring Date: Fri, 18 Sep 2020 17:17:09 -0700 Subject: [PATCH 2/2] Cleanup and add some logging --- .../transport/apache/ApacheSSLManager.java | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java b/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java index 3942ea8d69..ed91f5e293 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheSSLManager.java @@ -19,11 +19,14 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; +import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; +import java.util.Calendar; import java.util.Collection; +import java.util.Date; import java.util.LinkedList; import java.util.logging.Level; @@ -52,19 +55,48 @@ private static void addNewRelicCertToTrustStore(SSLContextBuilder sslContextBuil if (nrCertUrl != null) { try (InputStream is = nrCertUrl.openStream()) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate cert = cf.generateCertificate(is); - keystore.load(null, null); - keystore.setCertificateEntry("newrelic", cert); - Agent.LOG.log(Level.FINEST, "Installed New Relic ssl certificate at alias: newrelic"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(is); + boolean sslCertIsValid = isSslCertValid(cert); + if (sslCertIsValid) { + logIfExpiringSoon(cert.getNotAfter()); + // Initialize keystore and add valid New Relic certificate + keystore.load(null, null); + keystore.setCertificateEntry("newrelic", cert); + Agent.LOG.log(Level.FINEST, "Installed New Relic ssl certificate at alias: newrelic. "); + Agent.LOG.log(Level.FINEST, "SSL Certificate expires on: {0}", cert.getNotAfter()); + } } catch (IOException e) { - Agent.LOG.log(Level.INFO, "Unable to add New Relic ssl certificate.", e); + Agent.LOG.log(Level.INFO, "Unable to add bundled New Relic ssl certificate.", e); } } else { - Agent.LOG.log(Level.INFO, "Unable to add New Relic ssl certificate."); + Agent.LOG.log(Level.INFO, "Unable to find bundled New Relic ssl certificate."); } sslContextBuilder.loadTrustMaterial(keystore, null); } + private static void logIfExpiringSoon(Date expiry) { + // log if less than 3 months left until certificate expires + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, +3); + if (cal.getTime().compareTo(expiry) > 0) { + Agent.LOG.log(Level.WARNING, "New Relic ssl certificate expire on {0}.\n" + + "Applications using a custom Trustore may need to update the agent " + + "or provide a valid certificate using the ca_bundle_path config", expiry); + } + } + + private static boolean isSslCertValid(X509Certificate cert) { + try { + cert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException e) { + Agent.LOG.log(Level.WARNING, "New Relic ssl certificate has expired.\n" + + "Applications using a custom Trustore may need to update the agent " + + "or provide a valid certificate using the ca_bundle_path config", e); + return false; + } + return true; + } + private static KeyStore getKeyStore(String caBundlePath) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());