Skip to content

Commit c40631e

Browse files
Refactoring validations (#8)
moved simple validations to the main file, util now has only two utilities: 1. validateAndParseFailureCriteria 2. isFailureCriteriaSatisfied --------- Co-authored-by: Adish Agarwal <[email protected]>
1 parent 1e20d06 commit c40631e

File tree

3 files changed

+56
-139
lines changed

3 files changed

+56
-139
lines changed

src/commons/constants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export const IAC_TOOL_NAME = 'analyze-code-security-scc';
2121
// TODO: add iac tool documentation link.
2222
export const IAC_TOOL_DOCUMENTATION_LINK = '';
2323
export const SCAN_FILE_MAX_SIZE_BYTES = 1000000;
24-
export const MAX_SCAN_TIMEOUT = 900000;
25-
export const MIN_SCAN_TIMEOUT = 60000;
24+
export const MAX_SCAN_TIMEOUT = '10m';
25+
export const MIN_SCAN_TIMEOUT = '1m';
2626
export const DEFAULT_FAILURE_CRITERIA = 'Critical:1,High:1,Medium:1,Low:1,Operator:or';
2727
export const DEFAULT_FAIL_SILENTLY = false;
2828
export const DEFAULT_IGNORE_VIOLATIONS = false;
@@ -35,7 +35,7 @@ export const IAC_VERSION_CONFIG_KEY = 'iac_version';
3535
export const IGONRE_VIOLATIONS_CONFIG_KEY = 'ignore_violations';
3636
export const FAILURE_CRITERIA_CONFIG_KEY = 'failure_criteria';
3737
export const FAIL_SILENTLY_CONFIG_KEY = 'fail_silently';
38-
export const SCAN_TIMEOUT_CONFIG_KEY = 'scan_time_out';
38+
export const SCAN_TIMEOUT_CONFIG_KEY = 'scan_timeout';
3939
export const ACTION_NAME = 'google-github-actions/analyze-code-security-scc';
4040
export const ACTION_FAIL_ERROR = (reason: string) => `${ACTION_NAME}, reason: ${reason}.`;
4141
export const IAC_SCAN_RESULT_OUTPUT_KEY = 'iac_scan_result';

src/main.ts

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,28 @@ import {
2525

2626
import * as fs from 'fs/promises';
2727

28-
import { errorMessage } from '@google-github-actions/actions-utils';
28+
import { errorMessage, parseBoolean, parseDuration } from '@google-github-actions/actions-utils';
2929

30-
import { FailureCriteria, IACType, Operator } from './input_configuration';
31-
import {
32-
getFailureCriteriasViolated,
33-
getViolationCountBySeverity,
34-
validateAndParseFailSilently,
35-
validateAndParseFailureCriteria,
36-
validateAndParseIgnoreViolations,
37-
validateAndParseScanTimeOut,
38-
} from './utils';
39-
import { IACAccessor, Severity, Violation } from './accessor';
30+
import { IACType } from './input_configuration';
31+
import { isFailureCriteriaSatisfied, validateAndParseFailureCriteria } from './utils';
32+
import { IACAccessor, Violation } from './accessor';
4033
import { VALIDATE_ENDPOINT_DOMAIN } from './commons/http_config';
4134
import { SarifReportGenerator } from './reports/iac_scan_report_processor';
4235
import { IACScanReportProcessor } from './reports/iac_scan_report_processor';
4336
import {
4437
ACTION_FAIL_ERROR,
38+
DEFAULT_FAIL_SILENTLY,
39+
DEFAULT_IGNORE_VIOLATIONS,
40+
DEFAULT_SCAN_TIMEOUT,
4541
FAILURE_CRITERIA_CONFIG_KEY,
4642
FAIL_SILENTLY_CONFIG_KEY,
4743
IAC_SCAN_RESULT,
4844
IAC_SCAN_RESULT_OUTPUT_KEY,
4945
IAC_TYPE_CONFIG_KEY,
5046
IAC_VERSION_CONFIG_KEY,
5147
IGONRE_VIOLATIONS_CONFIG_KEY,
48+
MAX_SCAN_TIMEOUT,
49+
MIN_SCAN_TIMEOUT,
5250
ORGANIZATION_ID_CONFIG_KEY,
5351
SARIF_REPORT_FILE_NAME,
5452
SCAN_FILE_REF_CONFIG_KEY,
@@ -67,9 +65,19 @@ async function run(): Promise<void> {
6765
const scanFileRef = getInput(SCAN_FILE_REF_CONFIG_KEY, { required: true });
6866
const iacType = getInput(IAC_TYPE_CONFIG_KEY, { required: true });
6967
const iacVersion = getInput(IAC_VERSION_CONFIG_KEY, { required: true });
70-
const scanTimeOut = validateAndParseScanTimeOut(getInput(SCAN_TIMEOUT_CONFIG_KEY));
71-
const ignoreViolations = validateAndParseIgnoreViolations(
68+
const scanTimeoutInput = getInput(SCAN_TIMEOUT_CONFIG_KEY);
69+
const scanTimeoutMs = parseDuration(scanTimeoutInput) * 1000 || DEFAULT_SCAN_TIMEOUT;
70+
if (
71+
scanTimeoutMs > parseDuration(MAX_SCAN_TIMEOUT) * 1000 ||
72+
scanTimeoutMs < parseDuration(MIN_SCAN_TIMEOUT) * 1000
73+
) {
74+
throw new Error(
75+
`invalid input received for ${SCAN_TIMEOUT_CONFIG_KEY}: ${scanTimeoutInput} - ${SCAN_TIMEOUT_CONFIG_KEY} must be between ${MIN_SCAN_TIMEOUT} and ${MAX_SCAN_TIMEOUT}`,
76+
);
77+
}
78+
const ignoreViolations = parseBoolean(
7279
getInput(IGONRE_VIOLATIONS_CONFIG_KEY),
80+
DEFAULT_IGNORE_VIOLATIONS,
7381
);
7482
const failureCriteria = validateAndParseFailureCriteria(getInput(FAILURE_CRITERIA_CONFIG_KEY));
7583

@@ -85,7 +93,7 @@ async function run(): Promise<void> {
8593
const accessor = new IACAccessor(
8694
VALIDATE_ENDPOINT_DOMAIN,
8795
organizationId,
88-
scanTimeOut,
96+
scanTimeoutMs,
8997
scanStartTime,
9098
version,
9199
);
@@ -117,7 +125,7 @@ async function run(): Promise<void> {
117125
const msg = errorMessage(err);
118126
setOutput(IAC_SCAN_RESULT_OUTPUT_KEY, IAC_SCAN_RESULT.ERROR);
119127
// if config is not found or `fail_silently` is configured to false fail the build.
120-
const failSilently = validateAndParseFailSilently(getInput(FAIL_SILENTLY_CONFIG_KEY));
128+
const failSilently = parseBoolean(getInput(FAIL_SILENTLY_CONFIG_KEY), DEFAULT_FAIL_SILENTLY);
121129
if (!failSilently) {
122130
setFailed(ACTION_FAIL_ERROR(`failing build due to internal error: ${msg}`));
123131
} else {
@@ -128,28 +136,4 @@ async function run(): Promise<void> {
128136
}
129137
}
130138

131-
/**
132-
* isFailureCriteriaSatisfied decides if the failure criteria was satisfied.
133-
*
134-
* It decides this on the basis of configuration customer has set in their workflow and the violations
135-
* present in their plan file.
136-
*/
137-
function isFailureCriteriaSatisfied(
138-
failure_criteria: FailureCriteria,
139-
violations: Violation[],
140-
): boolean {
141-
const violationsCountBySeverity: Map<Severity, number> = getViolationCountBySeverity(violations);
142-
logDebug(`Violations count by Severity: ${[...violationsCountBySeverity.entries()]}`);
143-
const violationsThresholdBySeverity = failure_criteria.violationsThresholdBySeverity;
144-
const failureCriteriasViolated: boolean[] = getFailureCriteriasViolated(
145-
violationsCountBySeverity,
146-
violationsThresholdBySeverity,
147-
);
148-
const operator: Operator = failure_criteria.operator;
149-
150-
if (operator == Operator.AND) {
151-
return failureCriteriasViolated.reduce((acc, currentValue) => acc && currentValue, true);
152-
} else return failureCriteriasViolated.reduce((acc, currentValue) => acc || currentValue, false);
153-
}
154-
155139
run();

src/utils.ts

Lines changed: 30 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -14,59 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { errorMessage, parseDuration } from '@google-github-actions/actions-utils/dist';
17+
import { debug as logDebug } from '@actions/core';
18+
19+
import { errorMessage } from '@google-github-actions/actions-utils';
1820
import { FailureCriteria, Operator } from './input_configuration';
1921
import { Severity, Violation } from './accessor';
20-
import {
21-
DEFAULT_FAILURE_CRITERIA,
22-
DEFAULT_FAIL_SILENTLY,
23-
DEFAULT_IGNORE_VIOLATIONS,
24-
DEFAULT_SCAN_TIMEOUT,
25-
MAX_SCAN_TIMEOUT,
26-
MIN_SCAN_TIMEOUT,
27-
SCAN_TIMEOUT_CONFIG_KEY,
28-
} from './commons/constants';
29-
30-
/**
31-
* validateAndParseScanTimeOut validates whether given string is valid timeout for scan.
32-
*
33-
* If the string is empty or null, this returns the default value for scan_timeout.
34-
*/
35-
export function validateAndParseScanTimeOut(scan_timeout?: string): number {
36-
if (isEmptyString(scan_timeout)) {
37-
return DEFAULT_SCAN_TIMEOUT;
38-
}
39-
40-
try {
41-
const scanTimeOutNum = parseDuration(scan_timeout ?? '') * 1000;
42-
if (scanTimeOutNum > MAX_SCAN_TIMEOUT || scanTimeOutNum < MIN_SCAN_TIMEOUT) {
43-
throw new Error(
44-
`Expected ${SCAN_TIMEOUT_CONFIG_KEY} to be less than or equal to ${MAX_SCAN_TIMEOUT} and greater than or equal to ${MIN_SCAN_TIMEOUT}, found: ${scanTimeOutNum}`,
45-
);
46-
}
47-
return scanTimeOutNum;
48-
} catch (err) {
49-
const msg = errorMessage(err);
50-
throw new Error(`scan_timeout validation failed: ${msg}`);
51-
}
52-
}
53-
54-
/**
55-
* validateAndParseIgnoreViolations valdiates whether given string is valid boolean..
56-
*
57-
* If the string is empty or null, this returns the default value for ignore_violations.
58-
*/
59-
export function validateAndParseIgnoreViolations(ignore_violation?: string): boolean {
60-
if (isEmptyString(ignore_violation)) {
61-
return DEFAULT_IGNORE_VIOLATIONS;
62-
}
63-
try {
64-
return validateAndReturnBoolean(ignore_violation);
65-
} catch (err) {
66-
const msg = errorMessage(err);
67-
throw new Error(`ignore_violations validation failed: ${msg}`);
68-
}
69-
}
22+
import { DEFAULT_FAILURE_CRITERIA } from './commons/constants';
7023

7124
/**
7225
* validateAndParseFailureCriteria valdiates whether given string is valid representation for FailureCriteria.
@@ -80,7 +33,7 @@ export function validateAndParseIgnoreViolations(ignore_violation?: string): boo
8033
* Example of a valid failure_criteria string: "CRITICAL:2, HIGH:1, LOW:1, Operator:and".
8134
*/
8235
export function validateAndParseFailureCriteria(failure_criteria?: string): FailureCriteria {
83-
if (isEmptyString(failure_criteria)) {
36+
if (!failure_criteria || failure_criteria == '') {
8437
failure_criteria = DEFAULT_FAILURE_CRITERIA;
8538
}
8639
try {
@@ -93,19 +46,28 @@ export function validateAndParseFailureCriteria(failure_criteria?: string): Fail
9346
}
9447

9548
/**
96-
* validateAndParseFailSilently valdiates whether given string is valid boolean.
49+
* isFailureCriteriaSatisfied decides if the failure criteria was satisfied.
9750
*
98-
* If the string is empty or null, this returns the default value for fail_silently.
51+
* It decides this on the basis of configuration customer has set in their workflow and the violations
52+
* present in their plan file.
9953
*/
100-
export function validateAndParseFailSilently(fail_silently?: string): boolean {
101-
if (isEmptyString(fail_silently)) {
102-
return DEFAULT_FAIL_SILENTLY;
103-
}
104-
try {
105-
return validateAndReturnBoolean(fail_silently);
106-
} catch (err) {
107-
const msg = errorMessage(err);
108-
throw new Error(`fail_silently validation failed: ${msg}`);
54+
export function isFailureCriteriaSatisfied(
55+
failure_criteria: FailureCriteria,
56+
violations: Violation[],
57+
): boolean {
58+
const violationsCountBySeverity: Map<Severity, number> = getViolationCountBySeverity(violations);
59+
logDebug(`Violations count by Severity: ${[...violationsCountBySeverity.entries()]}`);
60+
const violationsThresholdBySeverity = failure_criteria.violationsThresholdBySeverity;
61+
const failureCriteriasViolated: boolean[] = getFailureCriteriasViolated(
62+
violationsCountBySeverity,
63+
violationsThresholdBySeverity,
64+
);
65+
const operator: Operator = failure_criteria.operator;
66+
67+
if (operator == Operator.AND) {
68+
return failureCriteriasViolated.reduce((acc, currentValue) => acc && currentValue, true);
69+
} else {
70+
return failureCriteriasViolated.reduce((acc, currentValue) => acc || currentValue, false);
10971
}
11072
}
11173

@@ -115,7 +77,7 @@ export function validateAndParseFailSilently(fail_silently?: string): boolean {
11577
* It compares the violations count found in reported violation summary with the violation severity threshold provided by the customer.
11678
* returns an array of boolean denoting whether a failure criteria was violated or not.
11779
*/
118-
export function getFailureCriteriasViolated(
80+
function getFailureCriteriasViolated(
11981
violationsCountBySeverity: Map<Severity, number>,
12082
violationsThresholdBySeverity: Map<Severity, number>,
12183
): boolean[] {
@@ -133,7 +95,7 @@ export function getFailureCriteriasViolated(
13395
/**
13496
* getViolationCountBySeverity generates a map of Severity to the Violation's count.
13597
*/
136-
export function getViolationCountBySeverity(violations: Violation[]): Map<Severity, number> {
98+
function getViolationCountBySeverity(violations: Violation[]): Map<Severity, number> {
13799
const violationsCountBySeverity: Map<Severity, number> = new Map();
138100
violations.forEach((violation) => {
139101
const severity: Severity = violation.severity ?? Severity.SeverityUnspecified;
@@ -176,14 +138,10 @@ function validateAndExtractFailureCriteriaFromMap(
176138
if (violationsThresholdBySeverity.has(severity)) {
177139
throw new Error(`multiple severities of type ${key} found.`);
178140
}
179-
let valueNum;
180-
try {
181-
valueNum = validateAndReturnNumber(value);
182-
} catch (err) {
183-
const msg = errorMessage(err);
184-
throw new Error(`invalid severity count, ${msg}`);
141+
if (isNaN(+value)) {
142+
throw new Error(`invalid severity count`);
185143
}
186-
violationsThresholdBySeverity.set(severity, valueNum);
144+
violationsThresholdBySeverity.set(severity, +value);
187145
});
188146

189147
if (!operator) {
@@ -225,28 +183,3 @@ export function extractSeverityKey(key: string, errMsg: string): Severity {
225183
}
226184
return severityKey;
227185
}
228-
229-
function validateAndReturnNumber(number?: string): number {
230-
if (!number) {
231-
throw new Error(`Number is empty`);
232-
}
233-
if (isNaN(+number)) {
234-
throw new Error(`Invalid number: ${number}`);
235-
}
236-
return +number;
237-
}
238-
239-
function validateAndReturnBoolean(str?: string): boolean {
240-
str = str?.toUpperCase();
241-
if (str == 'TRUE') {
242-
return true;
243-
}
244-
if (str == 'FALSE') {
245-
return false;
246-
}
247-
throw new Error(`Expected true or false, found: ${str}`);
248-
}
249-
250-
function isEmptyString(str?: string): boolean {
251-
return str == null || str == '';
252-
}

0 commit comments

Comments
 (0)