UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

170 lines (136 loc) 4.12 kB
'use strict' const log = require('../log') const web = require('../plugins/util/web') const { getActiveRequest } = require('./store') const { addSpecificEndpoint, specificBlockingTypes, getBlockingData, getBlockingAction, } = require('./blocking') const waf = require('./waf') const addresses = require('./addresses') const { startGraphqlResolver, graphqlMiddlewareChannel, apolloHttpServerChannel, apolloChannel, apolloServerCoreChannel, } = require('./channels') const { updateBlockFailureMetric } = require('./telemetry') const graphqlRequestData = new WeakMap() function enable () { enableApollo() enableGraphql() } function disable () { disableApollo() disableGraphql() } function onGraphqlStartResolver ({ abortController, resolverInfo }) { const req = getActiveRequest() if (!req) return if (!resolverInfo || typeof resolverInfo !== 'object') return const result = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req) const blockingAction = getBlockingAction(result?.actions) if (blockingAction) { const requestData = graphqlRequestData.get(req) if (requestData?.isInGraphqlRequest) { requestData.blocked = true requestData.wafAction = blockingAction abortController?.abort() } } } function enterInApolloMiddleware (data) { const req = data?.req || getActiveRequest() if (!req) return graphqlRequestData.set(req, { blocked: false, }) } function enterInApolloServerCoreRequest () { const req = getActiveRequest() if (!req) return graphqlRequestData.set(req, { isInGraphqlRequest: true, blocked: false, }) } function enterInApolloRequest () { const req = getActiveRequest() if (!req) return let requestData = graphqlRequestData.get(req) if (!requestData) { // executeHTTPGraphQLRequest is the GraphQL request boundary, so seed here // when no upstream hook (express4 middleware, drainHttpServer) has run. requestData = { blocked: false } graphqlRequestData.set(req, requestData) } requestData.isInGraphqlRequest = true addSpecificEndpoint(req, specificBlockingTypes.GRAPHQL) } function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) { const req = getActiveRequest() if (!req) return const requestData = graphqlRequestData.get(req) if (requestData?.blocked) { const rootSpan = web.root(req) if (!rootSpan) return try { const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, requestData.wafAction) abortData.statusCode = blockingData.statusCode abortData.headers = blockingData.headers abortData.message = blockingData.body rootSpan.setTag('appsec.blocked', 'true') abortController?.abort() } catch (err) { rootSpan.setTag('_dd.appsec.block.failed', 1) log.error('[ASM] Blocking error', err) updateBlockFailureMetric(req) } } graphqlRequestData.delete(req) } function enableApollo () { graphqlMiddlewareChannel.subscribe({ start: enterInApolloMiddleware, }) apolloServerCoreChannel.subscribe({ start: enterInApolloServerCoreRequest, asyncEnd: beforeWriteApolloGraphqlResponse, }) apolloChannel.subscribe({ start: enterInApolloRequest, asyncEnd: beforeWriteApolloGraphqlResponse, }) apolloHttpServerChannel.subscribe({ start: enterInApolloMiddleware, }) } function disableApollo () { graphqlMiddlewareChannel.unsubscribe({ start: enterInApolloMiddleware, }) apolloServerCoreChannel.unsubscribe({ start: enterInApolloServerCoreRequest, asyncEnd: beforeWriteApolloGraphqlResponse, }) apolloChannel.unsubscribe({ start: enterInApolloRequest, asyncEnd: beforeWriteApolloGraphqlResponse, }) apolloHttpServerChannel.unsubscribe({ start: enterInApolloMiddleware, }) } function enableGraphql () { startGraphqlResolver.subscribe(onGraphqlStartResolver) } function disableGraphql () { if (startGraphqlResolver.hasSubscribers) startGraphqlResolver.unsubscribe(onGraphqlStartResolver) } module.exports = { enable, disable, }