Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix issue 16 by working around Java's lack of SNI support

GetSentry.com uses Server Name Indication and Java versions before 7 do
not support this. Through the naiveSsl configuration option users are
able to work around this problem without having to add the certificate
to their keystore. This should only be used for testing purposes,
but... yeah.
  • Loading branch information...
commit 31a0b38f1841f4d3ba0bc8df9e499a754e78db7c 1 parent b7f1a12
@roam roam authored
View
10 README.rst
@@ -77,6 +77,16 @@ If you want to make sure log events always reach sentry, you can turn blocking o
WARNING: By setting blocking true, you will effectively lock up the thread doing the logging! Use with care.
+Naive SSL
+^^^^^^^^^
+If you're using Java 6 or earlier, you might encounter errors when connecting to a Sentry instance using a TLS (https)
+connection since Server Name Indication (SNI) support has only been available in Java since Java 7. You can either add
+the corresponding certificate to your keystore (recommended!) or enable ``naiveSsl`` on the Sentry appender. This will
+make the Raven client use a custom hostname verifier that *will* allow the JVM to connect with the host - in fact it
+will let the Raven client connect to any host even if the certificate is invalid. Use at your own risk::
+
+ log4j.appender.sentry.naiveSsl=true
+
SENTRY_DSN Environment Variable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
201 src/main/java/net/kencochrane/sentry/RavenClient.java
@@ -3,10 +3,14 @@
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
import java.net.URL;
import static org.apache.commons.codec.binary.Base64.encodeBase64String;
@@ -23,23 +27,24 @@
private RavenConfig config;
private String sentryDSN;
private String lastID;
+ private MessageSender messageSender;
public RavenClient() {
this.sentryDSN = System.getenv("SENTRY_DSN");
if (this.sentryDSN == null || this.sentryDSN.length() == 0) {
throw new RuntimeException("You must provide a DSN to RavenClient");
}
- this.config = new RavenConfig(this.sentryDSN);
+ setConfig(new RavenConfig(this.sentryDSN));
}
public RavenClient(String sentryDSN) {
this.sentryDSN = sentryDSN;
- this.config = new RavenConfig(sentryDSN);
+ setConfig(new RavenConfig(sentryDSN));
}
- public RavenClient(String sentryDSN, String proxy) {
+ public RavenClient(String sentryDSN, String proxy, boolean naiveSsl) {
this.sentryDSN = sentryDSN;
- this.config = new RavenConfig(sentryDSN, proxy);
+ setConfig(new RavenConfig(sentryDSN, proxy, naiveSsl));
}
public RavenConfig getConfig() {
@@ -48,6 +53,16 @@ public RavenConfig getConfig() {
public void setConfig(RavenConfig config) {
this.config = config;
+ try {
+ URL endpoint = new URL(config.getSentryURL());
+ if (config.isNaiveSsl() && "https".equals(endpoint.getProtocol())) {
+ messageSender = new NaiveHttpsMessageSender(config, endpoint);
+ } else {
+ messageSender = new MessageSender(config, endpoint);
+ }
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Sentry URL is malformed", e);
+ }
}
public String getSentryDSN() {
@@ -192,39 +207,6 @@ private String buildMessage(String message, String timestamp, String loggerClass
return buildMessageBody(jsonMessage);
}
-
- /**
- * build up the sentry auth header in the following format
- * <p/>
- * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, and an
- * arbitrary client version string. The client version should be something distinct to your client,
- * and is simply for reporting purposes.
- * <p/>
- * X-Sentry-Auth: Sentry sentry_version=2.0,
- * sentry_signature=<hmac signature>,
- * sentry_timestamp=<signature timestamp>[,
- * sentry_key=<public api key>,[
- * sentry_client=<client version, arbitrary>]]
- *
- * @param hmacSignature SHA1-signed HMAC
- * @param timestamp is the timestamp of which this message was generated
- * @param publicKey is either the public_key or the shared global key between client and server.
- * @return String version of the sentry auth header
- */
- private String buildAuthHeader(String hmacSignature, long timestamp, String publicKey) {
- StringBuilder header = new StringBuilder();
- header.append("Sentry sentry_version=2.0,sentry_signature=");
- header.append(hmacSignature);
- header.append(",sentry_timestamp=");
- header.append(timestamp);
- header.append(",sentry_key=");
- header.append(publicKey);
- header.append(",sentry_client=");
- header.append(RAVEN_JAVA_VERSION);
-
- return header.toString();
- }
-
/**
* Send the message to the sentry server.
*
@@ -232,27 +214,8 @@ private String buildAuthHeader(String hmacSignature, long timestamp, String publ
* @param timestamp the timestamp of the message
*/
private void sendMessage(String messageBody, long timestamp) {
- HttpURLConnection connection = null;
try {
-
- // get the hmac Signature for the header
- String hmacSignature = RavenUtils.getSignature(messageBody, timestamp, config.getSecretKey());
-
- // get the auth header
- String authHeader = buildAuthHeader(hmacSignature, timestamp, getConfig().getPublicKey());
-
- URL endpoint = new URL(getConfig().getSentryURL());
- connection = (HttpURLConnection) endpoint.openConnection(getConfig().getProxy());
- connection.setRequestMethod("POST");
- connection.setDoOutput(true);
- connection.setReadTimeout(10000);
- connection.setRequestProperty("X-Sentry-Auth", authHeader);
- OutputStream output = connection.getOutputStream();
- output.write(messageBody.getBytes());
- output.close();
- connection.connect();
- InputStream input = connection.getInputStream();
- input.close();
+ messageSender.send(messageBody, timestamp);
} catch (IOException e) {
// Eat the errors, we don't want to cause problems if there are major issues.
e.printStackTrace();
@@ -261,16 +224,16 @@ private void sendMessage(String messageBody, long timestamp) {
/**
* Send the log message to the sentry server.
- *
+ * <p/>
* This method is deprecated. You should use captureMessage or captureException instead.
*
- * @deprecated
* @param theLogMessage The log message
* @param timestamp unix timestamp
* @param loggerClass The class associated with the log message
* @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.)
* @param culprit Who we think caused the problem.
* @param exception exception that occurred
+ * @deprecated
*/
public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) {
String message = buildMessage(theLogMessage, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, exception);
@@ -281,11 +244,11 @@ public void logMessage(String theLogMessage, long timestamp, String loggerClass,
/**
* Send the log message to the sentry server.
*
- * @param message The log message
- * @param timestamp unix timestamp
- * @param loggerClass The class associated with the log message
- * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.)
- * @param culprit Who we think caused the problem.
+ * @param message The log message
+ * @param timestamp unix timestamp
+ * @param loggerClass The class associated with the log message
+ * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.)
+ * @param culprit Who we think caused the problem.
* @return lastID The ID for the last message.
*/
public String captureMessage(String message, long timestamp, String loggerClass, int logLevel, String culprit) {
@@ -297,7 +260,7 @@ public String captureMessage(String message, long timestamp, String loggerClass,
/**
* Send the log message to the sentry server.
*
- * @param message The log message
+ * @param message The log message
* @return lastID The ID for the last message.
*/
public String captureMessage(String message) {
@@ -307,12 +270,12 @@ public String captureMessage(String message) {
/**
* Send the exception to the sentry server.
*
- * @param message The log message
- * @param timestamp unix timestamp
- * @param loggerClass The class associated with the log message
- * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.)
- * @param culprit Who we think caused the problem.
- * @param exception exception that occurred
+ * @param message The log message
+ * @param timestamp unix timestamp
+ * @param loggerClass The class associated with the log message
+ * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.)
+ * @param culprit Who we think caused the problem.
+ * @param exception exception that occurred
* @return lastID The ID for the last message.
*/
public String captureException(String message, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) {
@@ -324,10 +287,102 @@ public String captureException(String message, long timestamp, String loggerClas
/**
* Send an exception to the sentry server.
*
- * @param exception exception that occurred
- * @return lastID The ID for the last message.
+ * @param exception exception that occurred
+ * @return lastID The ID for the last message.
*/
public String captureException(Throwable exception) {
return captureException(exception.getMessage(), RavenUtils.getTimestampLong(), "root", 50, null, exception);
}
+
+ public static class MessageSender {
+
+ public final RavenConfig config;
+ public final URL endpoint;
+
+ public MessageSender(RavenConfig config, URL endpoint) {
+ this.config = config;
+ this.endpoint = endpoint;
+ }
+
+ public void send(String messageBody, long timestamp) throws IOException {
+ // get the hmac Signature for the header
+ String hmacSignature = RavenUtils.getSignature(messageBody, timestamp, config.getSecretKey());
+
+ // get the auth header
+ String authHeader = buildAuthHeader(hmacSignature, timestamp, config.getPublicKey());
+
+ HttpURLConnection connection = getConnection();
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(true);
+ connection.setReadTimeout(10000);
+ connection.setRequestProperty("X-Sentry-Auth", authHeader);
+ OutputStream output = connection.getOutputStream();
+ output.write(messageBody.getBytes());
+ output.close();
+ connection.connect();
+ InputStream input = connection.getInputStream();
+ input.close();
+ }
+
+ /**
+ * Build up the sentry auth header in the following format.
+ * <p/>
+ * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, and an
+ * arbitrary client version string. The client version should be something distinct to your client,
+ * and is simply for reporting purposes.
+ * <p/>
+ * X-Sentry-Auth: Sentry sentry_version=2.0,
+ * sentry_signature=<hmac signature>,
+ * sentry_timestamp=<signature timestamp>[,
+ * sentry_key=<public api key>,[
+ * sentry_client=<client version, arbitrary>]]
+ *
+ * @param hmacSignature SHA1-signed HMAC
+ * @param timestamp is the timestamp of which this message was generated
+ * @param publicKey is either the public_key or the shared global key between client and server.
+ * @return String version of the sentry auth header
+ */
+ protected String buildAuthHeader(String hmacSignature, long timestamp, String publicKey) {
+ StringBuilder header = new StringBuilder();
+ header.append("Sentry sentry_version=2.0,sentry_signature=");
+ header.append(hmacSignature);
+ header.append(",sentry_timestamp=");
+ header.append(timestamp);
+ header.append(",sentry_key=");
+ header.append(publicKey);
+ header.append(",sentry_client=");
+ header.append(RAVEN_JAVA_VERSION);
+
+ return header.toString();
+ }
+
+ protected HttpURLConnection getConnection() throws IOException {
+ return (HttpURLConnection) endpoint.openConnection(config.getProxy());
+ }
+ }
+
+ public static class NaiveHttpsMessageSender extends MessageSender {
+
+ public final HostnameVerifier hostnameVerifier;
+
+ public NaiveHttpsMessageSender(RavenConfig config, URL endpoint) {
+ super(config, endpoint);
+ this.hostnameVerifier = new AcceptAllHostnameVerifier();
+ }
+
+ @Override
+ protected HttpURLConnection getConnection() throws IOException {
+ HttpsURLConnection connection = (HttpsURLConnection) endpoint.openConnection(config.getProxy());
+ connection.setHostnameVerifier(hostnameVerifier);
+ return connection;
+ }
+ }
+
+ public static class AcceptAllHostnameVerifier implements HostnameVerifier {
+ @Override
+ public boolean verify(String hostname, SSLSession sslSession) {
+ return true;
+ }
+ }
+
}
View
17 src/main/java/net/kencochrane/sentry/RavenConfig.java
@@ -13,6 +13,7 @@
private int port;
private String proxyType, proxyHost;
private int proxyPort;
+ private boolean naiveSsl;
/**
* Takes in a sentryDSN and builds up the configuration
@@ -20,7 +21,7 @@
* @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}/{PROJECT_ID}'
*/
public RavenConfig(String sentryDSN) {
- this(sentryDSN, null);
+ this(sentryDSN, null, false);
}
/**
@@ -28,9 +29,10 @@ public RavenConfig(String sentryDSN) {
*
* @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}/{PROJECT_ID}'
* @param proxy proxy to use for the HTTP connections; blank or null when no proxy is to be used
+ * @param naiveSsl use a hostname verifier for SSL connections that allows all connections
*/
- public RavenConfig(String sentryDSN, String proxy) {
-
+ public RavenConfig(String sentryDSN, String proxy, boolean naiveSsl) {
+ this.naiveSsl = naiveSsl;
try {
URL url = new URL(sentryDSN);
this.host = url.getHost();
@@ -181,6 +183,14 @@ public void setPort(int port) {
this.port = port;
}
+ public boolean isNaiveSsl() {
+ return naiveSsl;
+ }
+
+ public void setNaiveSsl(boolean naiveSsl) {
+ this.naiveSsl = naiveSsl;
+ }
+
@Override
public String toString() {
return "RavenConfig{" +
@@ -190,6 +200,7 @@ public String toString() {
", secretKey='" + secretKey + '\'' +
", path='" + path + '\'' +
", projectId='" + projectId + '\'' +
+ ", naiveSsl='" + naiveSsl + '\'' +
", SentryUrl='" + getSentryURL() + '\'' +
'}';
}
View
11 src/main/java/net/kencochrane/sentry/SentryAppender.java
@@ -15,6 +15,7 @@
private String proxy;
private int queue_size;
private boolean blocking;
+ private boolean naiveSsl;
public SentryAppender()
{
@@ -54,6 +55,14 @@ public void setBlocking(boolean blocking) {
this.blocking = blocking;
}
+ public boolean isNaiveSsl() {
+ return naiveSsl;
+ }
+
+ public void setNaiveSsl(boolean naiveSsl) {
+ this.naiveSsl = naiveSsl;
+ }
+
/**
* Look for the ENV variable first, and if it isn't there, then look in the log4j properties
*
@@ -79,7 +88,7 @@ protected void append(LoggingEvent loggingEvent) {
{
if(!SentryQueue.getInstance().isSetup())
{
- SentryQueue.getInstance().setup(sentryDSN, getProxy(), queue_size, blocking);
+ SentryQueue.getInstance().setup(sentryDSN, getProxy(), queue_size, blocking, naiveSsl);
}
}
View
4 src/main/java/net/kencochrane/sentry/SentryQueue.java
@@ -41,12 +41,12 @@ public synchronized boolean isSetup()
return (queue != null);
}
- public synchronized void setup(String sentryDSN, String proxy, int queueSize, boolean blocking)
+ public synchronized void setup(String sentryDSN, String proxy, int queueSize, boolean blocking, boolean naiveSsl)
{
queue = new LinkedBlockingQueue<LoggingEvent>(queueSize);
this.blocking = blocking;
- worker = new SentryWorker(queue, sentryDSN, proxy);
+ worker = new SentryWorker(queue, sentryDSN, proxy, naiveSsl);
worker.start();
}
View
4 src/main/java/net/kencochrane/sentry/SentryWorker.java
@@ -17,11 +17,11 @@
private BlockingQueue<LoggingEvent> queue;
- public SentryWorker(BlockingQueue<LoggingEvent> queue, String sentryDSN, String proxy)
+ public SentryWorker(BlockingQueue<LoggingEvent> queue, String sentryDSN, String proxy, boolean naiveSsl)
{
this.shouldShutdown = false;
this.queue = queue;
- this.client = new RavenClient(sentryDSN, proxy);
+ this.client = new RavenClient(sentryDSN, proxy, naiveSsl);
}
@Override
View
7 src/test/resources/log4j_configuration.txt
@@ -20,4 +20,9 @@ log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
log4j.appender.sentry=net.kencochrane.sentry.SentryAppender
log4j.appender.sentry.sentry_dsn=http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1
-
+# If you're using Java 6 or earlier, you might encounter errors when connecting to a Sentry instance using a TLS (https)
+# connection since Server Name Indication support has only been available in Java since Java 7. You can either add
+# the corresponding certificate to your keystore (recommended!) or enable naiveSsl on the Sentry appender. This will
+# make the Raven client use a custom hostname verifier that *will* allow the JVM to connect with the host - in fact it
+# will let the Raven client connect to any host even if the certificate is invalid. Use at your own risk.
+# log4j.appender.sentry.naiveSsl=true
Please sign in to comment.
Something went wrong with that request. Please try again.