Skip to content

Commit

Permalink
feat: add Kubernetes configs detection logic
Browse files Browse the repository at this point in the history
  • Loading branch information
orkamara committed Jun 29, 2020
1 parent 17170c0 commit 9fe44b2
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
93 changes: 93 additions & 0 deletions src/lib/cloud-config/cloud-config-parser.ts
@@ -0,0 +1,93 @@
//TODO(orka): take out into a new lib
import * as YAML from 'js-yaml';
import * as debugLib from 'debug';
import {
IllegalCloudConfigFileError,
NotSupportedCloudConfigFileError,
} from './../errors';

const debug = debugLib('snyk-detect');

const mandatoryKeysForSupportedK8sKinds = {
deployment: ['apiVersion', 'metadata', 'spec'],
pod: ['apiVersion', 'metadata', 'spec'],
service: ['apiVersion', 'metadata', 'spec'],
podsecuritypolicy: ['apiVersion', 'metadata', 'spec'],
networkpolicy: ['apiVersion', 'metadata', 'spec'],
};

function getFileType(filePath: string): string {
const filePathSplit = filePath.split('.');
return filePathSplit[filePathSplit.length - 1].toLowerCase();
}

function parseYamlOrJson(fileContent: string, filePath: string): any {
const fileType = getFileType(filePath);
switch (fileType) {
case 'yaml':
case 'yml':
try {
return YAML.safeLoadAll(fileContent);
} catch (e) {
debug('Failed to parse cloud config as a YAML');
}
break;
case 'json':
try {
const objectsArr: any[] = [];
objectsArr.push(JSON.parse(fileContent));
return objectsArr;
} catch (e) {
debug('Failed to parse cloud config as a JSON');
}
break;
default:
debug(`Unsupported cloud config file type (${fileType})`);
}
return undefined;
}

// This function validates that there is at least one valid doc with a k8s object kind.
// A valid k8s object has a kind key (.kind) from the keys of `mandatoryKeysForSupportedK8sKinds`
// and all of the keys from `mandatoryKeysForSupportedK8sKinds[kind]`.
// If there is a doc with a supported kind, but invalid, we should fail
// The function return true if the yaml is a valid k8s one, or false otherwise
export function validateK8sFile(
fileContent: string,
filePath: string,
root: string,
) {
const k8sObjects: any[] = parseYamlOrJson(fileContent, filePath);
if (!k8sObjects) {
throw IllegalCloudConfigFileError([root]);
}

let numOfSupportedKeyDocs = 0;
for (let i = 0; i < k8sObjects.length; i++) {
const k8sObject = k8sObjects[i];
if (!k8sObject || !k8sObject.kind) {
continue;
}

const kind = k8sObject.kind.toLowerCase();
if (!Object.keys(mandatoryKeysForSupportedK8sKinds).includes(kind)) {
continue;
}

numOfSupportedKeyDocs++;

for (let i = 0; i < mandatoryKeysForSupportedK8sKinds[kind].length; i++) {
const key = mandatoryKeysForSupportedK8sKinds[kind][i];
if (!k8sObject[key]) {
debug(`Missing key (${key}) from supported k8s object kind (${kind})`);
throw IllegalCloudConfigFileError([root]);
}
}
}

if (numOfSupportedKeyDocs === 0) {
throw NotSupportedCloudConfigFileError([root]);
}

debug(`k8s config found (${filePath})`);
}
25 changes: 25 additions & 0 deletions src/lib/detect.ts
Expand Up @@ -4,6 +4,7 @@ import * as debugLib from 'debug';
import * as _ from '@snyk/lodash';
import { NoSupportedManifestsFoundError } from './errors';
import { SupportedPackageManagers } from './package-managers';
import { validateK8sFile } from './cloud-config/cloud-config-parser';

const debug = debugLib('snyk-detect');

Expand Down Expand Up @@ -137,6 +138,30 @@ export function detectPackageManager(root: string, options) {
return packageManager;
}

export function isCloudConfigProject(root: string, options): string {
if (!isLocalFolder(root)) {
debug('Cloud Config - repo case ' + root);
throw "iac option doesn't support lookup as repo";
}

if (!options.file) {
debug('Cloud Config - no file specified ' + root);
throw 'iac option works only with specified files';
}

if (localFileSuppliedButNotFound(root, options.file)) {
throw new Error(
'Could not find the specified file: ' +
options.file +
'\nPlease check that it exists and try again.',
);
}
const filePath = pathLib.resolve(root, options.file);
const fileContent = fs.readFileSync(filePath, 'utf-8');
validateK8sFile(fileContent, filePath, root);
return 'k8sconfig';
}

// User supplied a "local" file, but that file doesn't exist
function localFileSuppliedButNotFound(root, file) {
return (
Expand Down
4 changes: 4 additions & 0 deletions src/lib/errors/index.ts
Expand Up @@ -19,3 +19,7 @@ export { OptionMissingErrorError } from './option-missing-error';
export { ExcludeFlagBadInputError } from './exclude-flag-bad-input';
export { UnsupportedOptionCombinationError } from './unsupported-option-combination-error';
export { FeatureNotSupportedByPackageManagerError } from './feature-not-supported-by-package-manager-error';
export {
NotSupportedCloudConfigFileError,
IllegalCloudConfigFileError,
} from './invalid-cloud-config-file';
40 changes: 40 additions & 0 deletions src/lib/errors/invalid-cloud-config-file.ts
@@ -0,0 +1,40 @@
import chalk from 'chalk';
import { CustomError } from './custom-error';

export function NotSupportedCloudConfigFileError(atLocations: string[]) {
const locationsStr = atLocations.join(', ');
const errorMsg =
'Not supported Cloud Config target files in ' +
locationsStr +
'.\nPlease see our documentation for supported languages and ' +
'target files: ' +
chalk.underline(
'https://support.snyk.io/hc/en-us/articles/360000911957-Language-support',
) +
' and make sure you are in the right directory.';

const error = new CustomError(errorMsg);
error.code = 422;
error.userMessage = errorMsg;
return error;
}

export function IllegalCloudConfigFileError(
atLocations: string[],
): CustomError {
const locationsStr = atLocations.join(', ');
const errorMsg =
'Illegal Cloud Config target file ' +
locationsStr +
'.\nPlease see our documentation for supported languages and ' +
'target files: ' +
chalk.underline(
'https://support.snyk.io/hc/en-us/articles/360000911957-Language-support',
) +
' and make sure you are in the right directory.';

const error = new CustomError(errorMsg);
error.code = 422;
error.userMessage = errorMsg;
return error;
}

0 comments on commit 9fe44b2

Please sign in to comment.