Skip to content

Commit 1d73587

Browse files
committed
feat: adapt CLI to pass data instead of queries
1 parent 887c514 commit 1d73587

File tree

5 files changed

+75
-6
lines changed

5 files changed

+75
-6
lines changed

src/plugins/policyPack/index.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,12 @@ export default class PolicyPackPlugin extends Plugin {
137137
}
138138

139139
private async executeRule({
140+
data,
140141
rules,
141142
policyPack,
142143
storageEngine,
143144
}: {
145+
data: any,
144146
rules: Rule[]
145147
policyPack: string
146148
storageEngine: StorageEngine
@@ -159,18 +161,17 @@ export default class PolicyPackPlugin extends Plugin {
159161

160162
findings.push(
161163
...(await this.executeRule({
164+
data,
162165
rules: subRules,
163166
policyPack,
164167
storageEngine,
165168
}))
166169
)
167170
} else {
168-
const { data } = rule.gql
169-
? await storageEngine.query(rule.gql)
170-
: { data: undefined }
171+
const ruleData = rule.gql ? data : undefined
171172
const results = (await this.policyPacksPlugins[
172173
policyPack
173-
]?.engine?.processRule(rule, data)) as RuleFinding[]
174+
]?.engine?.processRule(rule, ruleData)) as RuleFinding[]
174175

175176
findings.push(...results)
176177
}
@@ -274,13 +275,69 @@ export default class PolicyPackPlugin extends Plugin {
274275
}
275276
}
276277

278+
private getLinkedData({ providerData }: { providerData: ProviderData }): any {
279+
const linkedData = {}
280+
const allEntities = providerData?.entities || []
281+
const allConnections = providerData?.connections || {}
282+
const entitiesById: { [key: string]: any } = {}
283+
284+
for (const entity of allEntities) {
285+
// AddawsEc2Input! => queryawsEc2
286+
const mutationName = /(?<=\[)(.*?)(?=\])/
287+
.exec(entity.mutation as any)[0]
288+
.replace('Add', 'query')
289+
.replace('Input!', '')
290+
291+
linkedData[mutationName] = entity.data
292+
293+
for (const entityData of entity.data) {
294+
entitiesById[entityData.id] = entityData
295+
// eslint-disable-next-line no-underscore-dangle
296+
entityData.__typename = mutationName.replace('query', '') // or entity.name?
297+
}
298+
}
299+
300+
// connect data on a second pass
301+
for (const entityId of Object.keys(allConnections)) {
302+
const entityConnections = allConnections[entityId]
303+
const entity = entitiesById[entityId]
304+
if (!entity) {
305+
// eslint-disable-next-line no-continue
306+
continue
307+
}
308+
for (const conn of entityConnections) {
309+
const targetEntity = entitiesById[conn.id]
310+
if (!targetEntity) {
311+
// eslint-disable-next-line no-continue
312+
continue
313+
}
314+
if (conn.relation === 'child') {
315+
if (!entity[conn.field]) {
316+
entity[conn.field] = []
317+
}
318+
entity[conn.field].push(targetEntity)
319+
// inverse relation
320+
// const inverseConnField = this.schemasMap[entity.__typename] || 'account' // @TODO: account doesn't have a name
321+
// if (!targetEntity[inverseConnField]) {
322+
// targetEntity[inverseConnField] = []
323+
// }
324+
// targetEntity[inverseConnField].push(entity)
325+
} // else parent relation.. is not used atm
326+
}
327+
}
328+
329+
return linkedData
330+
}
331+
277332
async execute({
278333
storageRunning,
279334
storageEngine,
335+
providerData,
280336
processConnectionsBetweenEntities,
281337
}: {
282338
storageRunning: boolean
283339
storageEngine: StorageEngine
340+
providerData: ProviderData
284341
processConnectionsBetweenEntities: (props: {
285342
provider?: string
286343
providerData: ProviderData
@@ -314,7 +371,10 @@ export default class PolicyPackPlugin extends Plugin {
314371
mergeSchemas(currentSchema, findingsSchema),
315372
])
316373

374+
const linkedData = this.getLinkedData({ providerData })
375+
317376
const findings = await this.executeRule({
377+
data: linkedData,
318378
rules,
319379
policyPack,
320380
storageEngine,

src/rules-engine/evaluators/json-evaluator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ export default class JsonEvaluator implements RuleEvaluator<JsonRule> {
2020
}
2121

2222
async evaluateSingleResource(
23-
{ id, conditions, severity }: JsonRule,
23+
{ id, conditions, severity, exclude }: JsonRule,
2424
data: ResourceData
2525
): Promise<RuleFinding> {
26+
if (exclude && (await this.evaluateCondition(exclude, data))) {
27+
return
28+
}
2629
const result = (await this.evaluateCondition(conditions, data))
2730
? RuleResult.MATCHES
2831
: RuleResult.DOESNT_MATCH
@@ -157,6 +160,7 @@ export default class JsonEvaluator implements RuleEvaluator<JsonRule> {
157160

158161
if (firstArg && jqQuery) {
159162
firstArg = await this.runJq(firstArg, jqQuery)
163+
data.data = lodash.cloneDeep(data.data)
160164
lodash.set(data.data, data.elementPath, firstArg)
161165
}
162166

src/rules-engine/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export default class RulesProvider implements Engine {
5151
): Promise<RuleFinding> => {
5252
const finding = await evaluator.evaluateSingleResource(rule, data)
5353

54+
if (!finding) return
55+
5456
// Inject extra fields
5557
for (const field of this.dataProcessor.extraFields) {
5658
finding[field] = data.resource[field]

src/rules-engine/operators/common.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export default {
1313
return shouldBeEmpty ? hasKeys === 0 : hasKeys > 0
1414
}
1515

16-
return false
16+
return (
17+
(data === null || data === undefined || data === '') === shouldBeEmpty
18+
)
1719
},
1820
}

src/rules-engine/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export interface RuleFinding extends Finding {
6565

6666
export interface JsonRule extends Rule {
6767
conditions: Condition
68+
exclude?: Condition
6869
}
6970

7071
export interface JsRule extends Rule {

0 commit comments

Comments
 (0)