Skip to content

Commit

Permalink
fix monitor plugins issue and log
Browse files Browse the repository at this point in the history
  • Loading branch information
Thierry DEGREMONT committed Jul 24, 2024
1 parent ba9f95b commit d8f3c06
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 52 deletions.
73 changes: 57 additions & 16 deletions packages/graphql-mesh/custom-plugins/monitor-envelop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Logger } from '../utils/logger'
import { NoSchemaIntrospectionCustomRule } from 'graphql';
import { GraphQLError } from 'graphql';
/**
* monitor plugin in order to get event contextual log and add some security rules
* useful to
* monitor plugin in order to get event contextual log and add some security rules
* useful to
* - log the graphql Query event
* - add desabled instropection alidation rule
* - remove suggestion
* - add desabled introspection alidation rule
* - remove suggestion
* - log the execute result summary with executes duration
* - remove not allowed instropection in result
* - remove not allowed introspection in result
*/

const formatter = (error: GraphQLError, mask: string): GraphQLError => {
Expand All @@ -20,11 +20,16 @@ const formatter = (error: GraphQLError, mask: string): GraphQLError => {
};
export default ({ options }): Plugin => {
// not allow by default
// do not enabled enabledIntrospection in production
const enabledIntrospection = process.env['IS_PROUCTION_ENV'] != 'true' && ( options?.enabledIntrospection || process.env['ENABLED_INTROSPECTION'] || false )
// do not enabled allowIntrospection in production
const allowIntrospection = process.env['IS_PROUCTION_ENV'] != 'true' && (options?.introspection?.allow || process.env['ENABLED_INTROSPECTION'] || false)
// low info in log by default
const resultLogInfoLevel= options?.resultLogInfoLevel ? options.resultLogInfoLevel : "low"

const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : "low"
const denyIntrospectionHeaderName = options?.introspection?.denyHeaderName || null
const denyIntrospectionHeaderValue = options?.introspection?.denyHeaderValue || null
const allowIntrospectionHeaderName = options?.introspection?.allowHeaderName || null
const allowIntrospectionHeaderValue = options?.introspection?.allowHeaderValue || null
const isMaskSuggestion = options?.maskSuggestion?.enabled || false
const maskSuggestionMessage = options?.maskSuggestion?.message || ""
return {
onParse({ params, context }) {
Logger.graphqlQuery(context['request']['headers'], context['params'])
Expand All @@ -34,32 +39,68 @@ export default ({ options }): Plugin => {
},

onValidate: ({ addValidationRule, context }) => {
if (!enabledIntrospection) {
const headers = context['request'].headers
let deny = true
/*
allowIntrospection=false : intropection deny for all
denyIntrospectionHeaderName : name of the header to check to deny introspection is deny ex plublic proxy header
allowIntrospectionHeaderName : name of the header allow if this header and value is presents
*/
// if introspection not allow
if (allowIntrospection) {
// intropection may be allow
deny = false
// is existed a header to deny introspection
if (denyIntrospectionHeaderName) {
if (headers.get(denyIntrospectionHeaderName)) {
if (headers.get(denyIntrospectionHeaderName).includes(denyIntrospectionHeaderValue)) {
Logger.denyIntrospection("onValidate", "deny by headers " + denyIntrospectionHeaderName + ": " + headers.get(denyIntrospectionHeaderName), headers)
deny = true
}
}
}
// is existed a header mandatory to allow introspection
if (allowIntrospectionHeaderName) {
deny = true
if (headers.get(allowIntrospectionHeaderName)) {
if (headers.get(allowIntrospectionHeaderName).includes(allowIntrospectionHeaderValue)) {
Logger.allowIntrospection("onValidate", "allow by headers " + allowIntrospectionHeaderName + ": " + headers.get(allowIntrospectionHeaderName).substring(0, 4) + "...", headers)
deny = false
} else {
Logger.denyIntrospection("onValidate", "deny by bad header value " + allowIntrospectionHeaderName + ": " + headers.get(allowIntrospectionHeaderName).substring(0, 4) + "...", headers)
}
} else {
Logger.denyIntrospection("onValidate", "deny by no header " + allowIntrospectionHeaderName, headers)
}
}
}
if (deny) {
addValidationRule(NoSchemaIntrospectionCustomRule)
}

return function onValidateEnd({ valid, result, setResult }) {
if (options.maskSuggestion.enabled && !valid) {
setResult(result.map((error) => formatter(error, options.maskSuggestion.message)));
if (isMaskSuggestion && !valid) {
setResult(result.map((error) => formatter(error, maskSuggestionMessage)));
}
};
},

onExecute({ args, extendContext }) {

let timestampDebut = new Date().getTime()
return {
before() {

timestampDebut = new Date().getTime()
},
onExecuteDone({ result, args }) {
const timestampDone = new Date().getTime();

// short cut to desabled introspection response in case of bad configuration rule
if (!enabledIntrospection && args.contextValue['params'].query.includes('__schema')) {
if (!allowIntrospection && args.contextValue['params'].query.includes('__schema')) {
result['data'] = {}
result['errors'] = [{ message: "Fordidden" }]
Logger.error('SECU', 'onExecute', 'Intropection query deteted not allowed', args.contextValue['params'])
Logger.error('SECU', 'onExecute', 'Introspection query deteted not allowed', args.contextValue['params'])
}
Logger.endExec(args.contextValue['request']['headers'], result, timestampDone - timestampDebut, resultLogInfoLevel)
}
Expand Down
4 changes: 3 additions & 1 deletion packages/graphql-mesh/custom-plugins/monitor-yoga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ export function useYagaMonitoring({ options }): Plugin {
const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : "low"



return {
onRequest({ request/*, fetchAPI, endResponse */ }) {
Logger.onRequest(request)

// add resuestTimestamp in headers
const timestamp = new Date().getTime();
request.headers.append("requestTimestamp", String(timestamp))

},
onRequestParse(args) {
const beforeTimestamp = new Date().getTime();
Expand All @@ -38,7 +40,7 @@ export function useYagaMonitoring({ options }): Plugin {
const timestamp = new Date().getTime();
Logger.onRequestParseDone(requestHeaders, nRequestParseDoneEventPayload.requestParserResult['query'], nRequestParseDoneEventPayload.requestParserResult['operationName'], nRequestParseDoneEventPayload.requestParserResult['variables'], timestamp - beforeTimestamp)
if (nRequestParseDoneEventPayload.requestParserResult['query'].includes('__schema')) {
Logger.info("IntrospectionQuery", "onRequestParseDone", "introspection detected", nRequestParseDoneEventPayload.requestParserResult['query'])
Logger.introspection("onRequestParseDone", requestHeaders,nRequestParseDoneEventPayload.requestParserResult['query'])
}
}
}
Expand Down
112 changes: 77 additions & 35 deletions packages/graphql-mesh/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class Logger {
/**
* Core logger with Human format or machine one line json string format
*/
private static log(level: string, typeEv: string, message: string, data: any = null, err = null) {
private static log(level: string, typeEv: string, message: string, ctx = null, data: any = null, err = null) {

const date = new Date();
const timestamp = date.getTime();
Expand All @@ -36,14 +36,19 @@ export class Logger {
date: date.toLocaleString(this.localDateCountry),
message: message
}
if (ctx) {

log['ctx'] = ctx
}
if (data) {

log['data'] = data
}
if (err) {
log['exeception'] = {}
for (const key in Object.keys(err)) {
console.log("Exception:", err)
for (const key in err) {
log['exeception'][key] = err[key]

}
}
// if define add extra env field
Expand All @@ -56,32 +61,30 @@ export class Logger {
}
}

public static error(typeEv: string, source: string, message: string, data: any = null, e = null) {
public static error(typeEv: string, source: string, message: string, ctx = null, data = null, e = null) {
if (Logger.level == 'ERROR' || Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') {
Logger.log('ERROR', typeEv, source + ":" + message, data, e)
Logger.log('ERROR', typeEv, source + ":" + message, ctx, data, e)
}
}
public static warn(typeEv: string, source: string, message: string, data: any = null, e = null) {
public static warn(typeEv: string, source: string, message: string, ctx: any = null, data: any = null, e = null) {
if (Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') {
Logger.log('INFO', typeEv, source + ":" + message, data, e)
Logger.log('INFO', typeEv, source + ":" + message, ctx, data, e)
}
}

public static info(typeEv: string, source: string, message: string, data: any = null, e = null) {
public static info(typeEv: string, source: string, message: string, ctx: any = null, data: any = null, e = null) {
if (Logger.level == 'INFO' || Logger.level == 'DEBUG') {
Logger.log('INFO', typeEv, source + ":" + message, data, e)
Logger.log('INFO', typeEv, source + ":" + message, ctx, data, e)
}
}
public static debug(typeEv: string, source: string, message: string, data: any = null, e = null) {
public static debug(typeEv: string, source: string, message: string, ctx: any = null, data: any = null, e = null) {
if (Logger.level == 'DEBUG') {
Logger.log('DEBUG', typeEv, source + ":" + message, data, e)
Logger.log('DEBUG', typeEv, source + ":" + message, ctx, data, e)
}
}
public static onParse(headers: any) {
try {



Logger.log('INFO', "ON-PARSE", "Request", headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly))
} catch (e) {
Logger.error('LOGGER_ERROR', 'endExec logger', 'error during log generation', null, e)
Expand All @@ -92,15 +95,16 @@ export class Logger {
try {

const toLog = {
headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly),
result: {
hasErrors: (result['errors'] != undefined),
hasData: (result['data'] != undefined),
resultSummayInfo: info(result, this.maxSkackLogSize, resultLogInfoLevel)
},
duration: duration,
}
Logger.log('INFO', "endExecDone", "Request", toLog)
const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly) }

Logger.log('INFO', "endExecDone", "Request", ctx, toLog)
} catch (e) {
Logger.error('LOGGER_ERROR', 'endExec logger', 'error during log generation', null, e)

Expand All @@ -112,29 +116,28 @@ export class Logger {
const headerMap = request['headers']

const toLog = {
headers: headersToLog(headerMap, this.logTrackerHeaders, this.logHeaders, this.trackerOnly),

hasErrors: (result['errors'] != undefined),
hasData: (result['data'] != undefined),
responseInfo: info(result, this.maxSkackLogSize, resultLogInfo)
}

const ctx = { headers: headersToLog(headerMap, this.logTrackerHeaders, this.logHeaders, this.trackerOnly) }

Logger.log('INFO', "onResultProcess", "Result", toLog)
Logger.log('INFO', "onResultProcess", "Result", ctx, toLog)
} catch (e) {
Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', null, e)
}
}

public static onRequestParseDone(headers: any, query: any, operation: string, variables: any, duration: number) {
const toLog = {
headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly),
operation: operation,
query: query,
variables: variables,
parsingDuration: duration
}
Logger.log('INFO', "requestParseDone", "requestParse", toLog)
const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly) }
Logger.log('INFO', "requestParseDone", "requestParse", ctx, toLog)
}

public static onResponse(request: any, response: any, logResponseLevellevel: string) {
Expand All @@ -152,7 +155,6 @@ export class Logger {

const toLog = {
request: {
headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly),
url: request.url,
method: request.method
},
Expand All @@ -166,30 +168,68 @@ export class Logger {
if (logResponseLevellevel != 'low') {
toLog.response['bodyInfo'] = extractBody(response.bodyInit, this.bodyMaxLogSize)
}
Logger.log('INFO', "onResponse", "response", toLog)
const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly) }

Logger.log('INFO', "onResponse", "response", ctx, toLog)
}
catch (e) {
Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', null, e)
Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', null, null, e)
}
}

public static introspection(event,headers,query) {
try {

const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, false) }
const toLog = {
query: query
}
Logger.warn('WARN', event, "introspection query", ctx, toLog)
}
catch (e) {
Logger.error('LOGGER_ERROR', 'introspection logger', 'error during log generation', null, null, e)
}
}

public static denyIntrospection(event,message,headers) {
try {

const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, false) }

Logger.warn('DENY_INTROSPECTION', event, message, ctx)
}
catch (e) {
Logger.error('LOGGER_ERROR', 'denyIntrospection logger', 'error during log generation', null, null, e)
}
}

public static allowIntrospection(event,message,headers) {
try {

const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, false) }

Logger.warn('ALLOW_INTROSPECTION', event, message, ctx)
}
catch (e) {
Logger.error('LOGGER_ERROR', 'denyIntrospection logger', 'error during log generation', null, null, e)
}
}
public static onRequest(request: any) {
try {
const headers = request['headers']
const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, false) }
const toLog = {
request: {
headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, false),
url: request.url,
method: request.method,
body: request.body

}
url: request.url,
method: request.method,
//body: request.body
}
Logger.log('INFO', "onRequest", "request", toLog)

Logger.log('INFO', "onRequest", "request incomming", ctx, toLog)

}
catch (e) {
Logger.error('LOGGER_ERROR', 'onRequest logger', 'error during log generation', null, e)
Logger.error('LOGGER_ERROR', 'onRequest logger', 'error during log generation', null, null, e)
}
}

Expand All @@ -198,12 +238,13 @@ export class Logger {
const headers = request['headers']

const toLog = {
requestHeaders: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly),
fetchInfo: fetchInfo,
fetch: fetchResponseInfo,
duration: duration
}
Logger.log('INFO', "onFetch", "fetch", toLog)
const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly) }

Logger.log('INFO', "onFetch", "fetch", ctx, toLog)
} catch (e) {
Logger.error('LOGGER_ERROR', 'onFetch logger', 'error during log generation', null, e)
}
Expand All @@ -213,12 +254,13 @@ export class Logger {
try {
const regex = / /gi;
const queryTolog = {
headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly),
query: params['query'].replace(regex, ""),
operationName: params['operationName'],
variables: params['variables']
}
Logger.log('INFO', "graphqlQuery", "GraphQL Query", queryTolog)
const ctx = { headers: headersToLog(headers, this.logTrackerHeaders, this.logHeaders, this.trackerOnly) }

Logger.log('INFO', "graphqlQuery", "GraphQL Query", ctx, queryTolog)
} catch (e) {
Logger.error('LOGGER_ERROR', 'graphql query logger', 'error during log generation', null, e)
}
Expand Down

0 comments on commit d8f3c06

Please sign in to comment.