From 00cd9eaebc38e50966de0cecc8dd437f8e7790e5 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 10 Jul 2017 17:22:22 +0100 Subject: [PATCH] =?UTF-8?q?[logging]=20Downgrade=20hapi=20connection=20err?= =?UTF-8?q?ors=20when=20connecting=20with=20the=20w=E2=80=A6=20(#11209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [logging] Downgrade hapi connection errors when connecting with the wrong protocol * [logging] Check for error code instead of message for requesting https when serving http errors --- .../logging/__tests__/log_interceptor.js | 46 +++++++++++++++---- src/server/logging/log_interceptor.js | 46 ++++++++++++++++--- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/server/logging/__tests__/log_interceptor.js b/src/server/logging/__tests__/log_interceptor.js index 92f7559bd9440..edffc66a94b5a 100644 --- a/src/server/logging/__tests__/log_interceptor.js +++ b/src/server/logging/__tests__/log_interceptor.js @@ -2,10 +2,9 @@ import expect from 'expect.js'; import { LogInterceptor } from '../log_interceptor'; -function stubClientErrorEvent(errno) { +function stubClientErrorEvent(errorMeta) { const error = new Error(); - error.errno = errno; - + Object.assign(error, errorMeta); return { event: 'error', pid: 1234, @@ -15,9 +14,9 @@ function stubClientErrorEvent(errno) { }; } -const stubEconnresetEvent = () => stubClientErrorEvent('ECONNRESET'); -const stubEpipeEvent = () => stubClientErrorEvent('EPIPE'); -const stubEcanceledEvent = () => stubClientErrorEvent('ECANCELED'); +const stubEconnresetEvent = () => stubClientErrorEvent({ errno: 'ECONNRESET' }); +const stubEpipeEvent = () => stubClientErrorEvent({ errno: 'EPIPE' }); +const stubEcanceledEvent = () => stubClientErrorEvent({ errno: 'ECANCELED' }); function assertDowngraded(transformed) { expect(!!transformed).to.be(true); @@ -43,7 +42,7 @@ describe('server logging LogInterceptor', () => { it('ignores non ECONNRESET events', () => { const interceptor = new LogInterceptor(); - const event = stubClientErrorEvent('not ECONNRESET'); + const event = stubClientErrorEvent({ errno: 'not ECONNRESET' }); expect(interceptor.downgradeIfEconnreset(event)).to.be(null); }); @@ -71,7 +70,7 @@ describe('server logging LogInterceptor', () => { it('ignores non EPIPE events', () => { const interceptor = new LogInterceptor(); - const event = stubClientErrorEvent('not EPIPE'); + const event = stubClientErrorEvent({ errno: 'not EPIPE' }); expect(interceptor.downgradeIfEpipe(event)).to.be(null); }); @@ -99,7 +98,7 @@ describe('server logging LogInterceptor', () => { it('ignores non ECANCELED events', () => { const interceptor = new LogInterceptor(); - const event = stubClientErrorEvent('not ECANCELED'); + const event = stubClientErrorEvent({ errno: 'not ECANCELLED' }); expect(interceptor.downgradeIfEcanceled(event)).to.be(null); }); @@ -110,4 +109,33 @@ describe('server logging LogInterceptor', () => { expect(interceptor.downgradeIfEcanceled(event)).to.be(null); }); }); + + describe('#downgradeIfHTTPSWhenHTTP', () => { + it('transforms https requests when serving http errors', () => { + const interceptor = new LogInterceptor(); + const event = stubClientErrorEvent({ message: 'Parse Error', code: 'HPE_INVALID_METHOD' }); + assertDowngraded(interceptor.downgradeIfHTTPSWhenHTTP(event)); + }); + + it('ignores non events', () => { + const interceptor = new LogInterceptor(); + const event = stubClientErrorEvent({ message: 'Parse Error', code: 'NOT_HPE_INVALID_METHOD' }); + expect(interceptor.downgradeIfEcanceled(event)).to.be(null); + }); + }); + + describe('#downgradeIfHTTPWhenHTTPS', () => { + it('transforms http requests when serving https errors', () => { + const message = '40735139278848:error:1407609C:SSL routines:SSL23_GET_CLIENT_HELLO:http request:../deps/openssl/openssl/ssl/s23_srvr.c:394'; // eslint-disable-line max-len + const interceptor = new LogInterceptor(); + const event = stubClientErrorEvent({ message }); + assertDowngraded(interceptor.downgradeIfHTTPWhenHTTPS(event)); + }); + + it('ignores non events', () => { + const interceptor = new LogInterceptor(); + const event = stubClientErrorEvent({ message: 'Not error' }); + expect(interceptor.downgradeIfEcanceled(event)).to.be(null); + }); + }); }); diff --git a/src/server/logging/log_interceptor.js b/src/server/logging/log_interceptor.js index de5f700f3b405..4d5169263fe03 100644 --- a/src/server/logging/log_interceptor.js +++ b/src/server/logging/log_interceptor.js @@ -1,14 +1,22 @@ import Stream from 'stream'; import { get, isEqual } from 'lodash'; +const GET_CLIENT_HELLO = /GET_CLIENT_HELLO:http/; + function doTagsMatch(event, tags) { return isEqual(get(event, 'tags'), tags); } +function doesMessageMatch(errorMessage, match) { + if (!errorMessage) return false; + const isRegExp = match instanceof RegExp; + if (isRegExp) return match.test(errorMessage); + return errorMessage === match; +} // converts the given event into a debug log if it's an error of the given type -function downgradeIfErrorMatches(errorType, event) { +function downgradeIfErrorType(errorType, event, field = 'errno') { const isClientError = doTagsMatch(event, ['connection', 'client', 'error']); - const matchesErrorType = isClientError && get(event, 'data.errno') === errorType; + const matchesErrorType = isClientError && get(event, `data.${field}`) === errorType; if (!matchesErrorType) return null; @@ -23,6 +31,22 @@ function downgradeIfErrorMatches(errorType, event) { }; } +function downgradeIfErrorMessage(match, event) { + const isClientError = doTagsMatch(event, ['connection', 'client', 'error']); + const errorMessage = get(event, 'data.message'); + const matchesErrorMessage = isClientError && doesMessageMatch(errorMessage, match); + + if (!matchesErrorMessage) return null; + + return { + event: 'log', + pid: event.pid, + timestamp: event.timestamp, + tags: ['debug', 'connection'], + data: errorMessage + }; +} + export class LogInterceptor extends Stream.Transform { constructor() { super({ @@ -42,7 +66,7 @@ export class LogInterceptor extends Stream.Transform { * @param {object} - log event */ downgradeIfEconnreset(event) { - return downgradeIfErrorMatches('ECONNRESET', event); + return downgradeIfErrorType('ECONNRESET', event); } /** @@ -56,7 +80,7 @@ export class LogInterceptor extends Stream.Transform { * @param {object} - log event */ downgradeIfEpipe(event) { - return downgradeIfErrorMatches('EPIPE', event); + return downgradeIfErrorType('EPIPE', event); } /** @@ -70,13 +94,23 @@ export class LogInterceptor extends Stream.Transform { * @param {object} - log event */ downgradeIfEcanceled(event) { - return downgradeIfErrorMatches('ECANCELED', event); + return downgradeIfErrorType('ECANCELED', event); + } + + downgradeIfHTTPSWhenHTTP(event) { + return downgradeIfErrorType('HPE_INVALID_METHOD', event, 'code'); + } + + downgradeIfHTTPWhenHTTPS(event) { + return downgradeIfErrorMessage(GET_CLIENT_HELLO, event); } _transform(event, enc, next) { const downgraded = this.downgradeIfEconnreset(event) || this.downgradeIfEpipe(event) - || this.downgradeIfEcanceled(event); + || this.downgradeIfEcanceled(event) + || this.downgradeIfHTTPSWhenHTTP(event) + || this.downgradeIfHTTPWhenHTTPS(event); this.push(downgraded || event); next();