2019-06-15 22:53:07 +00:00
|
|
|
import * as Ajv from 'ajv';
|
2019-06-15 22:38:05 +00:00
|
|
|
import { readFile } from 'fs';
|
2019-06-16 00:25:47 +00:00
|
|
|
import { safeLoad } from 'js-yaml';
|
2019-06-15 22:53:07 +00:00
|
|
|
import { JSONPath } from 'jsonpath-plus';
|
2019-06-16 00:25:47 +00:00
|
|
|
import { intersection, isNil } from 'lodash';
|
|
|
|
import { Logger, LogLevel } from 'noicejs';
|
2019-06-15 22:38:05 +00:00
|
|
|
import { promisify } from 'util';
|
2019-06-16 00:25:47 +00:00
|
|
|
|
2019-06-15 22:38:05 +00:00
|
|
|
import { CONFIG_SCHEMA } from './config';
|
|
|
|
|
|
|
|
const readFileSync = promisify(readFile);
|
|
|
|
|
|
|
|
export interface Rule {
|
2019-06-16 00:25:47 +00:00
|
|
|
// metadata
|
|
|
|
desc: string;
|
2019-06-15 22:38:05 +00:00
|
|
|
level: LogLevel;
|
|
|
|
name: string;
|
|
|
|
tags: Array<string>;
|
2019-06-16 00:25:47 +00:00
|
|
|
// data
|
|
|
|
check: any;
|
2019-06-16 03:46:27 +00:00
|
|
|
filter?: any;
|
2019-06-16 00:25:47 +00:00
|
|
|
select: string;
|
2019-06-15 22:38:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface RuleSelector {
|
|
|
|
excludeLevel: Array<LogLevel>;
|
|
|
|
excludeName: Array<string>;
|
|
|
|
excludeTag: Array<string>;
|
|
|
|
includeLevel: Array<LogLevel>;
|
|
|
|
includeName: Array<string>;
|
|
|
|
includeTag: Array<string>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function loadRules(paths: Array<string>): Promise<Array<Rule>> {
|
|
|
|
const rules = [];
|
|
|
|
|
|
|
|
for (const path of paths) {
|
|
|
|
const contents = await readFileSync(path, {
|
|
|
|
encoding: 'utf-8',
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = safeLoad(contents, {
|
|
|
|
schema: CONFIG_SCHEMA,
|
|
|
|
});
|
|
|
|
|
|
|
|
rules.push(...data.rules);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rules;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function resolveRules(rules: Array<Rule>, selector: RuleSelector): Promise<Array<Rule>> {
|
|
|
|
const activeRules = new Set<Rule>();
|
|
|
|
|
|
|
|
for (const r of rules) {
|
|
|
|
if (selector.excludeLevel.includes(r.level)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selector.excludeName.includes(r.name)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const excludedTags = intersection(selector.excludeTag, r.tags);
|
|
|
|
if (excludedTags.length > 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selector.includeLevel.includes(r.level)) {
|
|
|
|
activeRules.add(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selector.includeName.includes(r.name)) {
|
|
|
|
activeRules.add(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
const includedTags = intersection(selector.includeTag, r.tags);
|
|
|
|
if (includedTags.length > 0) {
|
|
|
|
activeRules.add(r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Array.from(activeRules);
|
|
|
|
}
|
|
|
|
|
2019-06-15 23:27:36 +00:00
|
|
|
export function checkRule(rule: Rule, data: any, logger: Logger): boolean {
|
2019-06-15 22:53:07 +00:00
|
|
|
const ajv = new ((Ajv as any).default)()
|
2019-06-16 00:25:47 +00:00
|
|
|
const check = ajv.compile(rule.check);
|
2019-06-16 03:46:27 +00:00
|
|
|
const filter = compileFilter(rule, ajv);
|
2019-06-15 22:53:07 +00:00
|
|
|
const scopes = JSONPath({
|
|
|
|
json: data,
|
2019-06-16 00:25:47 +00:00
|
|
|
path: rule.select,
|
2019-06-15 22:53:07 +00:00
|
|
|
});
|
2019-06-15 23:27:36 +00:00
|
|
|
|
2019-06-16 00:25:47 +00:00
|
|
|
if (isNil(scopes) || scopes.length === 0) {
|
|
|
|
logger.debug('no data selected');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const item of scopes) {
|
|
|
|
logger.debug({ item }, 'filtering item');
|
|
|
|
if (filter(item)) {
|
|
|
|
logger.debug({ item }, 'checking item')
|
|
|
|
if (!check(item)) {
|
|
|
|
logger.warn({
|
|
|
|
desc: rule.desc,
|
|
|
|
errors: check.errors,
|
|
|
|
item,
|
|
|
|
}, 'rule failed on item');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logger.debug({ errors: filter.errors, item }, 'skipping item');
|
2019-06-15 23:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-16 00:25:47 +00:00
|
|
|
|
2019-06-15 23:27:36 +00:00
|
|
|
return true;
|
2019-06-16 03:46:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function compileFilter(rule: Rule, ajv: any): any {
|
|
|
|
if (isNil(rule.filter)) {
|
|
|
|
return () => true;
|
|
|
|
} else {
|
|
|
|
return ajv.compile(rule.filter);
|
|
|
|
}
|
2019-06-15 22:38:05 +00:00
|
|
|
}
|