Skip to content

Commit 3251aaf

Browse files
authored
feat(various): (#28)
* feat(various): - scenarios : allowList optional options - scenarios : XForwardedFor optional options - middleware : add logger option allowing to pass custom logger - middleware : bouncer return a counter to do export metrics - middleware : correctly pass commons options - middleware : re-export from crowdsec-client to don't need to install it on parent * upgrade deps * follow sonarqube comments
1 parent e90c5a9 commit 3251aaf

12 files changed

Lines changed: 181 additions & 79 deletions

File tree

dev/raw/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"author": "",
1313
"license": "ISC",
1414
"dependencies": {
15-
"@types/node": "20.5.0",
15+
"@types/node": "20.5.1",
1616
"crowdsec-client": "workspace:^",
1717
"crowdsec-client-scenarios": "workspace:^",
1818
"crowdsec-http-middleware": "workspace:^"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"devDependencies": {
1212
"@tsconfig/node-lts": "18.12.3",
1313
"@types/jest": "29.5.3",
14-
"@types/node": "20.5.0",
14+
"@types/node": "20.5.1",
1515
"@typescript-eslint/parser": "6.4.0",
1616
"eslint": "8.47.0",
1717
"eslint-config-prettier": "9.0.0",

packages/crowdsec-client-scenarios/readme.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ and then read the documentation in the [wiki](https://github.com/thib3113/node-c
4141

4242
# Usage
4343

44-
This package, is planned to host scenarios used by [crowdsec-http-middleware](../crowdsec-http-middleware) and other middleware that extend it
44+
This package, is planned to host scenarios used by [crowdsec-http-middleware](https://www.npmjs.com/package/crowdsec-http-middleware) and other middleware that extend it
4545

4646
# Scenarios
4747

4848
in this part, we will use the variables `scenarios` and `scenariosOptions`, to illustrate the use in the middlewares
4949

5050
## Defaults scenarios
51-
the defaults scenarios are ([defined here](./src/index.ts#L8)) :
51+
the defaults scenarios are ([defined here](https://github.com/thib3113/node-crowdsec/blob/main/packages/crowdsec-client-scenarios/src/index.ts#L8)) :
5252

5353
- [AllowListEnricher](#allowlist) : allow you to skip alerts on your local ips
5454
- [XForwardedForChecker](#xforwardedforchecker) : allow to extract visitor ip
55-
- [HTTPEnricher](#httpenricher) : enrich alerts with informations from the http request
55+
- [HTTPEnricher](#httpenricher) : enrich alerts with information from the http request
5656

5757
## Available scenarios
5858
The available scenarios are :

packages/crowdsec-client-scenarios/src/scenarios/AllowList/AllowListEnricher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ const debug = createDebugger('AllowListEnricher');
1717
export class AllowListEnricher extends EnricherScenario {
1818
static scenarioName = 'allow-list';
1919
private allowed: Array<AddressObject>;
20-
constructor(options: IScenarioOptions) {
20+
constructor(options?: IScenarioOptions) {
2121
debug('construct');
2222
super(options);
2323

24-
const currentOptions = options['allow-list'];
24+
const currentOptions = options?.['allow-list'] ?? {};
2525

2626
this.allowed = (currentOptions?.allowed ?? defaultAllowed).map((ip) => getIpObject(ip));
2727
}

packages/crowdsec-client-scenarios/src/scenarios/XForwardedFor/XForwardedForChecker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface IXForwardedForOptions {
1616

1717
declare module '../IScenarioOptions.js' {
1818
interface IScenarioOptions {
19-
'x-forwarded-for': IXForwardedForOptions;
19+
'x-forwarded-for'?: IXForwardedForOptions;
2020
}
2121
}
2222

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { logger, loggerOption } from './ICrowdSecHTTPMiddlewareOptions.js';
2+
import { createDebugger } from './utils.js';
3+
4+
export class CommonsMiddleware {
5+
protected logger: Required<logger>;
6+
7+
constructor(name: string, options?: loggerOption) {
8+
this.logger = this.getLogger(name, options);
9+
}
10+
11+
getLogger(name: string, options?: loggerOption, extendedName?: string): Required<logger> {
12+
const defaultExtend = (extendName: string) => this.getLogger(name, options, extendName);
13+
if (!options) {
14+
const _debug = createDebugger(name);
15+
const debug = extendedName ? _debug.extend(extendedName) : _debug;
16+
return {
17+
warn: (...args) => debug('warning : %s', ...args),
18+
debug: debug,
19+
info: debug,
20+
error: (...args) => debug('error : %s', ...args),
21+
extend: defaultExtend
22+
};
23+
}
24+
25+
if (typeof options === 'function') {
26+
return {
27+
extend: defaultExtend,
28+
...options(name)
29+
};
30+
}
31+
32+
return {
33+
extend: defaultExtend,
34+
...options
35+
};
36+
}
37+
}

packages/crowdsec-http-middleware/src/CrowdSecHTTPBouncerMiddleware.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,23 @@ import {
88
ICrowdSecClientOptions,
99
ITLSAuthentication
1010
} from 'crowdsec-client';
11-
import { AddressObject, createDebugger } from './utils.js';
11+
import { AddressObject } from './utils.js';
1212
import { IpObjectsCacher } from './IpObjectsCacher.js';
1313
import { IncomingMessage, ServerResponse } from 'http';
14+
import { CommonsMiddleware } from './CommonsMiddleware.js';
1415

1516
type decisionScope = 'ip' | 'range';
1617
type decisionType = Decision<decisionScope>;
1718

18-
const debug = createDebugger('CrowdSecHTTPBouncerMiddleware');
19-
20-
export class CrowdSecHTTPBouncerMiddleware {
19+
export class CrowdSecHTTPBouncerMiddleware extends CommonsMiddleware {
2120
private readonly clientOptions: ICrowdSecClientOptions;
2221
public readonly client: BouncerClient;
22+
23+
//TODO store this on updates
24+
public get decisionsCount(): number {
25+
return Object.keys(this.decisions || {}).reduce((previousValue, key) => previousValue + (this.decisions[key]?.length || 0), 0);
26+
}
27+
2328
private decisions: Record<string, Array<{ selector: AddressObject; decision: decisionType }>> = {};
2429
private options: ICrowdSecHTTPBouncerMiddlewareOptions;
2530
private ipObjectCache: IpObjectsCacher;
@@ -33,7 +38,9 @@ export class CrowdSecHTTPBouncerMiddleware {
3338
private _decisionStream?: DecisionsStream<decisionScope>;
3439

3540
constructor(options: ICrowdSecHTTPBouncerMiddlewareOptions, clientOptions: ICrowdSecClientOptions, cache?: IpObjectsCacher) {
36-
debug('construct');
41+
super('CrowdSecHTTPBouncerMiddleware', options.logger);
42+
this.logger.debug('construct');
43+
3744
this.options = options;
3845
this.clientOptions = clientOptions;
3946

@@ -50,7 +57,7 @@ export class CrowdSecHTTPBouncerMiddleware {
5057
}
5158

5259
private getBouncerAuthentication(bouncerOptions: ICrowdSecHTTPMiddlewareOptions['bouncer']) {
53-
debug('getBouncerAuthentication');
60+
this.logger.debug('getBouncerAuthentication');
5461
if (Validate.implementsTKeys<ITLSAuthentication>(bouncerOptions, ['key', 'ca', 'cert'])) {
5562
return {
5663
cert: bouncerOptions.cert,
@@ -69,6 +76,7 @@ export class CrowdSecHTTPBouncerMiddleware {
6976
}
7077

7178
public async start() {
79+
this.logger.info('start');
7280
await this.client.login();
7381

7482
this._decisionStream = this.client.Decisions.getStream({
@@ -77,13 +85,11 @@ export class CrowdSecHTTPBouncerMiddleware {
7785
});
7886

7987
this._decisionStream.on('error', (e) => {
80-
debug('client stream error : %o', e);
88+
this.logger.error('client stream error', e);
8189
});
8290

8391
let timeout: NodeJS.Timeout | undefined;
8492
this._decisionStream.on('added', (decision) => {
85-
const localDebug = debug.extend('decisionAdded');
86-
localDebug('start');
8793
try {
8894
// TODO this method doesn't support range including multiple top level group
8995
const ipObject = this.ipObjectCache.getIpObjectWithCache(decision.value);
@@ -97,18 +103,14 @@ export class CrowdSecHTTPBouncerMiddleware {
97103
this.decisions[decisionId].push({ decision, selector: this.ipObjectCache.getIpObjectWithCache(decision.value) });
98104
}
99105
} catch (e) {
100-
debug('fail to add decision %o : ', decision, e);
106+
this.logger.error('fail to add decision', e, decision);
101107
}
102-
localDebug('end');
103108
});
104109

105110
this._decisionStream.on('deleted', (decision) => {
106-
const localDebug = debug.extend('decisionDeleted');
107-
localDebug('start');
108111
const ipObject = this.ipObjectCache.getIpObjectWithCache(decision.value);
109112
const decisionId = ipObject.parsedAddress[0];
110113
this.decisions[decisionId] = (this.decisions[decisionId] || []).filter(({ decision: d }) => !this.isSameDecision(d, decision));
111-
localDebug('end');
112114
});
113115

114116
this._decisionStream.resume();
@@ -119,27 +121,27 @@ export class CrowdSecHTTPBouncerMiddleware {
119121
}
120122

121123
public async stop() {
122-
debug('stop');
124+
this.logger.info('stop');
123125
return this.client.stop();
124126
}
125127

126128
public middleware(ip: string, req: IncomingMessage & { decision?: Decision<any> }) {
127-
const localDebug = debug.extend('bouncerMiddleware');
128-
localDebug('start');
129+
const localDebug = this.logger.extend('bouncerMiddleware');
130+
localDebug.debug('start');
129131

130132
const currentAddress = this.ipObjectCache.getIpObjectWithCache(ip);
131133

132-
localDebug('bouncerMiddleware receive request from %s', currentAddress.addressMinusSuffix);
133-
localDebug('start decision loop');
134+
localDebug.debug('bouncerMiddleware receive request from %s', currentAddress.addressMinusSuffix);
135+
localDebug.debug('start decision loop');
134136
const decision = (this.decisions[currentAddress.parsedAddress[0]] || []).find(({ selector }) =>
135137
currentAddress.isInSubnet(selector)
136138
);
137-
localDebug('end decision loop');
139+
localDebug.debug('end decision loop');
138140

139141
if (decision) {
140142
req.decision = decision.decision;
141143
}
142-
localDebug('end');
144+
localDebug.debug('end');
143145
}
144146

145147
public getMiddleware(getIpFromRequest: getCurrentIpFn) {

packages/crowdsec-http-middleware/src/CrowdSecHTTPMiddleware.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { ICrowdSecHTTPMiddlewareOptions } from './ICrowdSecHTTPMiddlewareOptions.js';
1+
import { ICommonOptions, ICrowdSecHTTPMiddlewareOptions } from './ICrowdSecHTTPMiddlewareOptions.js';
22
import { ICrowdSecClientOptions } from 'crowdsec-client';
33
import { pkg } from './pkg.js';
4-
import { AddressObject, createDebugger } from './utils.js';
4+
import { AddressObject } from './utils.js';
55
import type { IncomingMessage, ServerResponse } from 'http';
66
import { CrowdSecHTTPBouncerMiddleware } from './CrowdSecHTTPBouncerMiddleware.js';
77
import { IpObjectsCacher } from './IpObjectsCacher.js';
88
import { CrowdSecHTTPWatcherMiddleware } from './CrowdSecHTTPWatcherMiddleware.js';
9+
import { CommonsMiddleware } from './CommonsMiddleware.js';
910

1011
const defaultClientOptions: Partial<ICrowdSecHTTPMiddlewareOptions['clientOptions']> = {
1112
userAgent: `${pkg.name}/v${pkg.version}`
1213
};
13-
const debug = createDebugger('CrowdSecHTTPMiddleware');
1414

15-
export class CrowdSecHTTPMiddleware {
15+
export class CrowdSecHTTPMiddleware extends CommonsMiddleware {
1616
private readonly clientOptions: ICrowdSecClientOptions;
1717
private options: ICrowdSecHTTPMiddlewareOptions;
1818

@@ -21,7 +21,9 @@ export class CrowdSecHTTPMiddleware {
2121
private ipObjectCache: IpObjectsCacher;
2222

2323
constructor(options: ICrowdSecHTTPMiddlewareOptions) {
24-
debug('construct');
24+
super('CrowdSecHTTPMiddleware', options.logger);
25+
this.logger.debug('construct');
26+
2527
this.options = {
2628
protectedByHeader: true,
2729
...options
@@ -34,19 +36,24 @@ export class CrowdSecHTTPMiddleware {
3436

3537
this.ipObjectCache = new IpObjectsCacher(options.maxIpCache);
3638

39+
const commonsOptions: ICommonOptions = {
40+
logger: options.logger,
41+
maxIpCache: options.maxIpCache
42+
};
43+
3744
if (options.watcher) {
38-
this.watcher = new CrowdSecHTTPWatcherMiddleware(options.watcher, this.clientOptions);
45+
this.watcher = new CrowdSecHTTPWatcherMiddleware({ ...commonsOptions, ...options.watcher }, this.clientOptions);
3946
}
4047

4148
if (options.bouncer) {
42-
this.bouncer = new CrowdSecHTTPBouncerMiddleware(options.bouncer, this.clientOptions);
49+
this.bouncer = new CrowdSecHTTPBouncerMiddleware({ ...commonsOptions, ...options.bouncer }, this.clientOptions);
4350
}
4451
}
4552

4653
public async start() {
47-
debug('start');
54+
this.logger.info('start');
4855

49-
debug('login');
56+
this.logger.debug('login');
5057
await Promise.all([
5158
this.bouncer ? this.bouncer.start() : Promise.resolve(),
5259
this.watcher ? this.watcher.start() : Promise.resolve()
@@ -100,12 +107,12 @@ export class CrowdSecHTTPMiddleware {
100107
};
101108

102109
public getMiddleware() {
103-
debug('getMiddleware');
110+
this.logger.debug('getMiddleware');
104111
return this.middlewareFunction;
105112
}
106113

107114
public async stop() {
108-
debug('stop');
115+
this.logger.info('stop');
109116
await Promise.all([this.bouncer?.stop(), this.watcher?.stop()]);
110117
}
111118
}

0 commit comments

Comments
 (0)