-
Notifications
You must be signed in to change notification settings - Fork 553
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: iac experimental single k8s file
- Loading branch information
Showing
7 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import * as fs from 'fs'; | ||
import * as YAML from 'js-yaml'; | ||
import { isLocalFolder } from '../../../../lib/detect'; | ||
import { getFileType } from '../../../../lib/iac/iac-parser'; | ||
import * as util from 'util'; | ||
import { IacFileTypes } from '../../../../lib/iac/constants'; | ||
import { IacFileScanResult, IacFileMetadata, IacFileData } from './types'; | ||
import { buildPolicyEngine } from './policy-engine'; | ||
import { transformToLegacyResults } from './legacy-adapter'; | ||
|
||
const readFileContentsAsync = util.promisify(fs.readFile); | ||
|
||
export default async function legacyWrapper(pathToScan: string, options) { | ||
const results = await localProcessing(pathToScan); | ||
const legacyResults = transformToLegacyResults(results, options); | ||
const singleFileLegacyResult = legacyResults[0]; | ||
|
||
return singleFileLegacyResult as any; | ||
} | ||
|
||
async function localProcessing( | ||
pathToScan: string, | ||
): Promise<IacFileScanResult[]> { | ||
const policyEngine = await buildPolicyEngine(); | ||
const filePathsToScan = await getFilePathsToScan(pathToScan); | ||
const fileDataToScan = await parseFileContentsForPolicyEngine( | ||
filePathsToScan, | ||
); | ||
const scanResults = await policyEngine.scanFiles(fileDataToScan); | ||
return scanResults; | ||
} | ||
|
||
async function getFilePathsToScan(pathToScan): Promise<IacFileMetadata[]> { | ||
if (isLocalFolder(pathToScan)) { | ||
throw new Error( | ||
'IaC Experimental version does not support directory scan yet.', | ||
); | ||
} | ||
if (getFileType(pathToScan) === 'tf') { | ||
throw new Error( | ||
'IaC Experimental version does not support Terraform scan yet.', | ||
); | ||
} | ||
|
||
return [ | ||
{ filePath: pathToScan, fileType: getFileType(pathToScan) as IacFileTypes }, | ||
]; | ||
} | ||
|
||
const REQUIRED_K8S_FIELDS = ['apiVersion', 'kind', 'metadata']; | ||
|
||
async function parseFileContentsForPolicyEngine( | ||
filesMetadata: IacFileMetadata[], | ||
): Promise<IacFileData[]> { | ||
const parsedFileData: Array<IacFileData> = []; | ||
for (const fileMetadata of filesMetadata) { | ||
const fileContent = await readFileContentsAsync( | ||
fileMetadata.filePath, | ||
'utf-8', | ||
); | ||
const yamlDocuments = YAML.safeLoadAll(fileContent); | ||
|
||
yamlDocuments.forEach((parsedYamlDocument, docId) => { | ||
if ( | ||
REQUIRED_K8S_FIELDS.every((requiredField) => | ||
parsedYamlDocument.hasOwnProperty(requiredField), | ||
) | ||
) { | ||
parsedFileData.push({ | ||
...fileMetadata, | ||
fileContent: fileContent, | ||
jsonContent: parsedYamlDocument, | ||
docId: yamlDocuments.length > 1 ? docId : undefined, | ||
}); | ||
} else { | ||
throw new Error('Invalid K8s File!'); | ||
} | ||
}); | ||
} | ||
|
||
return await Promise.all(parsedFileData); | ||
} |
115 changes: 115 additions & 0 deletions
115
src/cli/commands/test/iac-local-execution/legacy-adapter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { IacFileScanResult, PolicyMetadata } from './types'; | ||
import { | ||
issuesToLineNumbers, | ||
CloudConfigFileTypes, | ||
} from '@snyk/cloud-config-parser'; | ||
|
||
export function transformToLegacyResults( | ||
iacLocalExecutionResults: Array<IacFileScanResult>, | ||
options: { severityThreshold?: string }, | ||
) { | ||
const iacLocalExecutionGroupedResults = groupMultiDocResults( | ||
iacLocalExecutionResults, | ||
); | ||
return iacLocalExecutionGroupedResults.map((iacScanResult) => | ||
iacLocalFileScanToLegacyResult(iacScanResult, options.severityThreshold), | ||
); | ||
} | ||
|
||
function getLegacyFileTypeForLineNumber( | ||
fileType: string, | ||
): CloudConfigFileTypes { | ||
switch (fileType) { | ||
case 'yaml': | ||
case 'yml': | ||
return CloudConfigFileTypes.YAML; | ||
case 'json': | ||
return CloudConfigFileTypes.JSON; | ||
default: | ||
return CloudConfigFileTypes.YAML; | ||
} | ||
} | ||
|
||
function iacLocalFileScanToLegacyResult( | ||
iacFileScanResult: IacFileScanResult, | ||
severityThreshold?: string, | ||
) { | ||
const legacyIssues = iacFileScanResult.violatedPolicies.map((policy) => { | ||
const cloudConfigPath = iacFileScanResult.docId | ||
? [`[DocId:${iacFileScanResult.docId}]`].concat(policy.msg.split('.')) | ||
: policy.msg.split('.'); | ||
let lineNumber = -1; | ||
try { | ||
lineNumber = issuesToLineNumbers( | ||
iacFileScanResult.fileContent, | ||
getLegacyFileTypeForLineNumber(iacFileScanResult.fileType), | ||
cloudConfigPath, | ||
); | ||
} catch (err) { | ||
// | ||
} | ||
|
||
return { | ||
...policy, | ||
id: policy.publicId, | ||
from: [], | ||
name: policy.title, | ||
cloudConfigPath, | ||
isIgnored: false, | ||
iacDescription: { | ||
issue: policy.issue, | ||
impact: policy.impact, | ||
resolve: policy.resolve, | ||
}, | ||
severity: policy.severity, | ||
lineNumber: lineNumber, | ||
}; | ||
}); | ||
return { | ||
result: { | ||
cloudConfigResults: filterPoliciesBySeverity( | ||
legacyIssues, | ||
severityThreshold, | ||
), | ||
}, | ||
packageManager: 'k8sconfig', | ||
targetFile: iacFileScanResult.filePath, | ||
}; | ||
} | ||
|
||
function groupMultiDocResults( | ||
scanResults: Array<IacFileScanResult>, | ||
): Array<IacFileScanResult> { | ||
const groupedData = scanResults.reduce((memo, result) => { | ||
if (memo[result.filePath]) { | ||
memo[result.filePath].violatedPolicies = memo[ | ||
result.filePath | ||
].violatedPolicies.concat(result.violatedPolicies); | ||
} else { | ||
memo[result.filePath] = result; | ||
} | ||
|
||
return memo; | ||
}, {} as IacFileScanResult); | ||
|
||
return Object.keys(groupedData).map((k) => groupedData[k]); | ||
} | ||
|
||
const SEVERITIES = ['low', 'medium', 'high']; | ||
|
||
function filterPoliciesBySeverity( | ||
violatedPolicies: PolicyMetadata[], | ||
severityThreshold?: string, | ||
): PolicyMetadata[] { | ||
if (!severityThreshold || severityThreshold === SEVERITIES[0]) { | ||
return violatedPolicies; | ||
} | ||
|
||
const severitiesToInclude = SEVERITIES.slice( | ||
SEVERITIES.indexOf(severityThreshold), | ||
); | ||
|
||
return violatedPolicies.filter( | ||
(policy) => severitiesToInclude.indexOf(policy.severity) > -1, | ||
); | ||
} |
58 changes: 58 additions & 0 deletions
58
src/cli/commands/test/iac-local-execution/policy-engine.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { OpaWasmInstance, IacFileData, IacFileScanResult } from './types'; | ||
import { loadPolicy } from '@open-policy-agent/opa-wasm'; | ||
import * as fs from 'fs'; | ||
|
||
const LOCAL_POLICY_ENGINE_DIR = `.iac-data`; | ||
const LOCAL_POLICY_ENGINE_WASM_PATH = `${LOCAL_POLICY_ENGINE_DIR}/policy.wasm`; | ||
const LOCAL_POLICY_ENGINE_DATA_PATH = `${LOCAL_POLICY_ENGINE_DIR}/data.json`; | ||
|
||
export async function buildPolicyEngine(): Promise<PolicyEngine> { | ||
const policyEngineCoreDataPath = `${process.cwd()}/${LOCAL_POLICY_ENGINE_WASM_PATH}`; | ||
const policyEngineMetaDataPath = `${process.cwd()}/${LOCAL_POLICY_ENGINE_DATA_PATH}`; | ||
try { | ||
const wasmFile = fs.readFileSync(policyEngineCoreDataPath); | ||
const policyMetaData = fs.readFileSync(policyEngineMetaDataPath); | ||
const policyMetadataAsJson: Record<string, any> = JSON.parse( | ||
policyMetaData.toString(), | ||
); | ||
|
||
const opaWasmInstance: OpaWasmInstance = await loadPolicy( | ||
Buffer.from(wasmFile), | ||
); | ||
opaWasmInstance.setData(policyMetadataAsJson); | ||
|
||
return new PolicyEngine(opaWasmInstance); | ||
} catch (err) { | ||
throw new Error( | ||
`Failed to build policy engine from path: ${LOCAL_POLICY_ENGINE_DIR}: \n err: ${err.message}`, | ||
); | ||
} | ||
} | ||
|
||
class PolicyEngine { | ||
opaWasmInstance: OpaWasmInstance; | ||
|
||
constructor(opaWasmInstance: OpaWasmInstance) { | ||
this.opaWasmInstance = opaWasmInstance; | ||
} | ||
|
||
private evaluate(data: Record<string, any>) { | ||
return this.opaWasmInstance.evaluate(data)[0].result; | ||
} | ||
|
||
public async scanFiles( | ||
filesToScan: IacFileData[], | ||
): Promise<IacFileScanResult[]> { | ||
try { | ||
return filesToScan.map((iacFile: IacFileData) => { | ||
const violatedPolicies = this.evaluate(iacFile.jsonContent); | ||
return { | ||
...iacFile, | ||
violatedPolicies, | ||
}; | ||
}); | ||
} catch (err) { | ||
throw new Error(`Failed to run policy engine: ${err}`); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { IacFileInDirectory } from '../../../../lib/types'; | ||
|
||
// eslint-disable-next-line | ||
export interface IacFileMetadata extends IacFileInDirectory {} | ||
export interface IacFileData extends IacFileMetadata { | ||
jsonContent: Record<string, any>; | ||
fileContent: string; | ||
docId?: number; | ||
} | ||
export interface IacFileScanResult extends IacFileData { | ||
violatedPolicies: PolicyMetadata[]; | ||
} | ||
|
||
export interface OpaWasmInstance { | ||
evaluate: (data: Record<string, any>) => { results: PolicyMetadata[] }; | ||
setData: (data: Record<string, any>) => void; | ||
} | ||
|
||
export interface PolicyMetadata { | ||
id: string; | ||
publicId: string; | ||
type: string; | ||
subType: string; | ||
title: string; | ||
description: string; | ||
severity: string; | ||
msg: string; | ||
policyEngineType: 'opa'; | ||
issue: string; | ||
impact: string; | ||
resolve: string; | ||
references: string[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters