Skip to content

Commit 7d477e0

Browse files
committed
feat: adds allow-list for outbound http inspection
1 parent d9e451f commit 7d477e0

9 files changed

+65
-25
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ import { AppService } from './app.service';
3838
obfuscation: { sensitiveKeys: ['cardnumber'] },
3939
},
4040
httpTrafficInspection: {
41-
ignoreRoutes: ['/v1/hidden-paths/*', '/v1/health'],
41+
ignoredInboundRoutes: ['/v1/hidden-paths/*', '/v1/health'],
42+
allowedOutboundRoutes: ['/v1/inpected-outbound-routes/*'],
4243
},
4344
}),
4445
],
@@ -178,7 +179,7 @@ Configuring specific routes to be excluded from inspection is particularly usefu
178179
- CORS
179180
- API Versioning
180181
- Route Prefixing
181-
- Traced Metadata in logs
182+
- Logged Metadata in logs
182183

183184
## License
184185

src/common-config.options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export type LoggerOptions = {
2121

2222
export type HttpTrafficInspectionOptions = {
2323
mode?: 'none' | 'all' | 'inbound' | 'outbound';
24-
ignoreRoutes?: string[];
24+
ignoredInboundRoutes?: string[];
25+
allowedOutboundRoutes?: string[];
2526
};
2627

2728
export type CORSOptions = CorsOptions | CorsOptionsDelegate<any>;

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ export * from './common-config.options';
55
export * from './logger/exception.handler';
66
export * from './logger/http-inspector-inbound.middleware';
77
export * from './logger/http-inspector-outbound.interceptor';
8+
export * from './logger/logged-metadata.decorator';
89
export * from './logger/logger.config';
910
export * from './logger/obfuscator';
10-
export * from './logger/traced-metadata.decorator';
1111

1212
export * from './utils/compression.config';
1313
export * from './utils/cors.config';

src/logger/http-inspector-inbound.middleware.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { NextFunction, Request, Response } from 'express';
88
import { MODULE_OPTIONS_TOKEN } from '../common-config.builder';
99
import { CommonConfigModuleOptions } from '../common-config.options';
10+
import { routeToRegex } from './http-inspector.utils';
1011

1112
@Injectable()
1213
class HttpInspectorInboundMiddleware implements NestMiddleware {
@@ -85,7 +86,7 @@ class HttpInspectorInboundMiddleware implements NestMiddleware {
8586

8687
export const configureHttpInspectorInbound = (app: INestApplication) => {
8788
const options = app.get<CommonConfigModuleOptions>(MODULE_OPTIONS_TOKEN);
88-
const { ignoreRoutes = [], mode = 'inbound' } =
89+
const { ignoredInboundRoutes: ignoreRoutes = [], mode = 'inbound' } =
8990
options.httpTrafficInspection ?? {};
9091
if (!['all', 'inbound'].includes(mode)) {
9192
return app;
@@ -94,15 +95,15 @@ export const configureHttpInspectorInbound = (app: INestApplication) => {
9495
if (ignoreRoutes) {
9596
Logger.log(
9697
{
97-
message: 'HTTP Inspection is set to ignore routes',
98+
message: 'Inbound HTTP Inspection is set to ignore routes',
9899
routes: ignoreRoutes,
99100
},
100101
'@gedai/common/config',
101102
);
102103
}
103104

104105
const inspector = new HttpInspectorInboundMiddleware(
105-
ignoreRoutes.map((x) => new RegExp(`^${x.replace('*', '.+')}$`, 'i')),
106+
ignoreRoutes.map(routeToRegex),
106107
);
107108
const middleware = inspector.use.bind(inspector);
108109

src/logger/http-inspector-outbound.interceptor.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import * as http from 'http';
33
import * as https from 'https';
44
import { MODULE_OPTIONS_TOKEN } from '../common-config.builder';
55
import { CommonConfigModuleOptions } from '../common-config.options';
6-
import { logRequestError, logResponse } from './http-inspector.utils';
6+
import {
7+
logRequestError,
8+
logResponse,
9+
routeToRegex,
10+
} from './http-inspector.utils';
711

812
const handleResponse =
913
(
@@ -31,11 +35,27 @@ const withTrafficInspection = (
3135
| typeof https.request
3236
| typeof http.get
3337
| typeof https.get,
38+
allowedOutboundRoutes: RegExp[],
3439
) =>
3540
function (...args: any[]) {
3641
const [urlOrOptions, callbackOrOptions, maybeCallback] = args;
3742
const requestDataChunks = [];
3843
const callback = maybeCallback || callbackOrOptions;
44+
45+
const shouldIgnoreRoute = () => {
46+
return !allowedOutboundRoutes.some((x) =>
47+
x.test(
48+
typeof urlOrOptions === 'string'
49+
? new URL(urlOrOptions).pathname.trim()
50+
: urlOrOptions.path.trim(),
51+
),
52+
);
53+
};
54+
55+
if (shouldIgnoreRoute()) {
56+
return target.apply(this, args);
57+
}
58+
3959
const wrappedCallback = () =>
4060
handleResponse(logger, requestDataChunks, callback);
4161
let request: http.ClientRequest;
@@ -60,12 +80,20 @@ const withTrafficInspection = (
6080
return request;
6181
};
6282

63-
function mountInterceptor(logger: Logger, module: typeof http | typeof https) {
83+
function mountInterceptor(
84+
logger: Logger,
85+
module: typeof http | typeof https,
86+
allowedOutboundRoutes: RegExp[],
87+
) {
6488
for (const { target, name } of [
6589
{ target: module.get, name: 'get' },
6690
{ target: module.request, name: 'request' },
6791
]) {
68-
const inspectedTarget = withTrafficInspection(logger, target);
92+
const inspectedTarget = withTrafficInspection(
93+
logger,
94+
target,
95+
allowedOutboundRoutes,
96+
);
6997
Object.defineProperty(inspectedTarget, 'name', {
7098
value: name,
7199
writable: false,
@@ -77,17 +105,23 @@ function mountInterceptor(logger: Logger, module: typeof http | typeof https) {
77105
export const configureHttpInspectorOutbound = (app: INestApplication) => {
78106
const options = app.get<CommonConfigModuleOptions>(MODULE_OPTIONS_TOKEN);
79107
// TODO: add ignore routes
80-
const { mode } = options.httpTrafficInspection ?? {};
108+
const { mode, allowedOutboundRoutes } = options.httpTrafficInspection ?? {};
81109
if (!['all', 'outbound'].includes(mode)) {
82110
return app;
83111
}
84112
const logger = new Logger('OutboundHTTPInspection');
85113
for (const module of [http, https]) {
86-
mountInterceptor(logger, module);
114+
mountInterceptor(logger, module, allowedOutboundRoutes.map(routeToRegex));
115+
}
116+
logger.log('Outbound http inspection initialized', '@gedai/common/config');
117+
if (allowedOutboundRoutes) {
118+
Logger.log(
119+
{
120+
message: 'Outbound HTTP Inspection is set to inspect routes',
121+
routes: allowedOutboundRoutes,
122+
},
123+
'@gedai/common/config',
124+
);
87125
}
88-
logger.log(
89-
'Experimental outbound http inspection initialized',
90-
'@gedai/common/config',
91-
);
92126
return app;
93127
};

src/logger/http-inspector.utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Logger } from '@nestjs/common';
22
import http from 'http';
33

4+
export const routeToRegex = (x: string) =>
5+
new RegExp(`^${x.replace('*', '.+')}$`, 'i');
6+
47
export function parseSearchString(searchString?: string) {
58
if (!searchString) {
69
return [];
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { Reflector } from '@nestjs/core';
22

3-
export const TracedMetadata =
3+
export const LoggedMetadata =
44
Reflector.createDecorator<{ name: string; value: string }[]>();

src/logger/metadata.interceptor.ts renamed to src/logger/logged-metadata.interceptor.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import {
99
Logger,
1010
NestInterceptor,
1111
} from '@nestjs/common';
12-
import { TracedMetadata } from './traced-metadata.decorator';
12+
import { LoggedMetadata } from './logged-metadata.decorator';
1313

1414
@Injectable()
15-
export class MetadataInterceptor implements NestInterceptor {
15+
export class LoggedMetadataInterceptor implements NestInterceptor {
1616
constructor(
1717
private readonly reflector: Reflector,
1818
private readonly context: ContextService,
1919
) {}
2020

2121
intercept(context: ExecutionContext, next: CallHandler<any>) {
22-
const meta = this.reflector.get(TracedMetadata, context.getHandler());
22+
const meta = this.reflector.get(LoggedMetadata, context.getHandler());
2323

2424
if (meta?.length) {
25-
this.context.set('__TracedMetadata__', meta);
25+
this.context.set('__LoggedMetadata__', meta);
2626
}
2727

2828
return next.handle();
@@ -32,9 +32,9 @@ export class MetadataInterceptor implements NestInterceptor {
3232
export const configureMetadataInterceptor = (app: INestApplication) => {
3333
const context = app.get(ContextService);
3434
const reflector = app.get(Reflector);
35-
const interceptor = new MetadataInterceptor(reflector, context);
35+
const interceptor = new LoggedMetadataInterceptor(reflector, context);
3636
app.useGlobalInterceptors(interceptor);
3737

38-
Logger.log('Traced metadata interceptor initialized', '@gedai/common/config');
38+
Logger.log('Logged metadata interceptor initialized', '@gedai/common/config');
3939
return app;
4040
};

src/logger/logger.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { config, format, transports } from 'winston';
99
import { MODULE_OPTIONS_TOKEN } from '../common-config.builder';
1010
import { CommonConfigModuleOptions } from '../common-config.options';
1111
import { configureOutboundHttpCorrelationPropagation } from './http-correlation.propagator';
12-
import { configureMetadataInterceptor } from './metadata.interceptor';
12+
import { configureMetadataInterceptor } from './logged-metadata.interceptor';
1313
import { Obfuscator, RegExpObfuscator } from './obfuscator';
1414

1515
let contextService: ContextService;
@@ -52,7 +52,7 @@ const metadata = () =>
5252
format((info) => {
5353
const meta =
5454
contextService.get<{ name: string; value: string }[]>(
55-
'__TracedMetadata__',
55+
'__LoggedMetadata__',
5656
) ?? [];
5757
const values = meta.reduce(
5858
(acc, { name, value }) => ({ ...acc, [name]: value }),

0 commit comments

Comments
 (0)