@contrast/contrast 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -0
- package/bin/contrast.js +2 -0
- package/dist/commands/auth/auth.js +61 -0
- package/dist/commands/config/config.js +24 -0
- package/dist/commands/scan/processScan.js +23 -0
- package/dist/common/HTTPClient.js +192 -0
- package/dist/common/errorHandling.js +60 -0
- package/dist/constants/constants.js +30 -0
- package/dist/constants/lambda.js +31 -0
- package/dist/constants/locales.js +259 -0
- package/dist/constants.js +150 -0
- package/dist/index.js +56 -0
- package/dist/lambda/__mocks__/aws.js +21 -0
- package/dist/lambda/__mocks__/lambdaConfig.json +42 -0
- package/dist/lambda/arn.js +21 -0
- package/dist/lambda/aws.js +169 -0
- package/dist/lambda/cliError.js +76 -0
- package/dist/lambda/constants.js +11 -0
- package/dist/lambda/help.js +75 -0
- package/dist/lambda/lambda.js +130 -0
- package/dist/lambda/logUtils.js +36 -0
- package/dist/lambda/scanDetail.js +30 -0
- package/dist/lambda/scanDetailCompletion.js +56 -0
- package/dist/lambda/scanRequest.js +93 -0
- package/dist/lambda/scanResults.js +16 -0
- package/dist/lambda/utils.js +90 -0
- package/dist/scan/autoDetection.js +76 -0
- package/dist/scan/fileFinder.js +15 -0
- package/dist/scan/fileUtils.js +31 -0
- package/dist/scan/help.js +68 -0
- package/dist/scan/populateProjectIdAndProjectName.js +55 -0
- package/dist/scan/scan.js +96 -0
- package/dist/scan/scanController.js +54 -0
- package/dist/scan/scanResults.js +85 -0
- package/dist/utils/commonApi.js +45 -0
- package/dist/utils/filterProjectPath.js +20 -0
- package/dist/utils/getConfig.js +30 -0
- package/dist/utils/oraWrapper.js +20 -0
- package/dist/utils/paramsUtil/commandlineParams.js +31 -0
- package/dist/utils/paramsUtil/configStoreParams.js +18 -0
- package/dist/utils/paramsUtil/envVariableParams.js +10 -0
- package/dist/utils/paramsUtil/paramHandler.js +28 -0
- package/dist/utils/paramsUtil/yamlParams.js +6 -0
- package/dist/utils/parsedCLIOptions.js +13 -0
- package/dist/utils/requestUtils.js +18 -0
- package/dist/utils/validationCheck.js +26 -0
- package/package.json +123 -0
- package/src/commands/auth/auth.js +73 -0
- package/src/commands/config/config.js +25 -0
- package/src/commands/scan/processScan.js +29 -0
- package/src/common/HTTPClient.js +269 -0
- package/src/common/errorHandling.ts +79 -0
- package/src/constants/constants.js +34 -0
- package/src/constants/lambda.js +41 -0
- package/src/constants/locales.js +381 -0
- package/src/constants.js +168 -0
- package/src/index.ts +69 -0
- package/src/lambda/__mocks__/aws.ts +32 -0
- package/src/lambda/__mocks__/lambdaConfig.json +42 -0
- package/src/lambda/arn.ts +32 -0
- package/src/lambda/aws.ts +247 -0
- package/src/lambda/cliError.ts +72 -0
- package/src/lambda/constants.ts +11 -0
- package/src/lambda/help.ts +76 -0
- package/src/lambda/lambda.ts +174 -0
- package/src/lambda/logUtils.ts +46 -0
- package/src/lambda/scanDetailCompletion.ts +78 -0
- package/src/lambda/scanRequest.ts +142 -0
- package/src/lambda/scanResults.ts +29 -0
- package/src/lambda/utils.ts +125 -0
- package/src/scan/autoDetection.js +77 -0
- package/src/scan/fileUtils.js +33 -0
- package/src/scan/help.js +74 -0
- package/src/scan/populateProjectIdAndProjectName.js +62 -0
- package/src/scan/scan.js +126 -0
- package/src/scan/scanController.js +69 -0
- package/src/scan/scanResults.js +96 -0
- package/src/utils/commonApi.js +54 -0
- package/src/utils/filterProjectPath.js +21 -0
- package/src/utils/getConfig.ts +42 -0
- package/src/utils/oraWrapper.js +24 -0
- package/src/utils/paramsUtil/commandlineParams.js +37 -0
- package/src/utils/paramsUtil/configStoreParams.js +19 -0
- package/src/utils/paramsUtil/envVariableParams.js +10 -0
- package/src/utils/paramsUtil/paramHandler.js +28 -0
- package/src/utils/parsedCLIOptions.js +17 -0
- package/src/utils/requestUtils.js +22 -0
- package/src/utils/validationCheck.js +34 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { sleep } from '../utils/requestUtils'
|
|
2
|
+
import { getHttpClient } from '../utils/commonApi'
|
|
3
|
+
import { ApiParams } from './lambda'
|
|
4
|
+
import HTTPClient from '../common/HTTPClient'
|
|
5
|
+
import ora from '../utils/oraWrapper'
|
|
6
|
+
import { CliError } from './cliError'
|
|
7
|
+
import { ERRORS } from './constants'
|
|
8
|
+
import { ContrastConf } from '../utils/getConfig'
|
|
9
|
+
|
|
10
|
+
const MS_IN_MINUTE = 1000 * 60
|
|
11
|
+
|
|
12
|
+
const getScanResources = async (
|
|
13
|
+
config: ContrastConf,
|
|
14
|
+
params: ApiParams,
|
|
15
|
+
scanId: string,
|
|
16
|
+
httpClient: HTTPClient
|
|
17
|
+
) => {
|
|
18
|
+
const res = await httpClient.getScanResources(config, params, scanId)
|
|
19
|
+
const { statusCode, body } = res
|
|
20
|
+
|
|
21
|
+
if (statusCode === 200) {
|
|
22
|
+
return res
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { errorCode } = body || {}
|
|
26
|
+
throw new CliError(ERRORS.FAILED_TO_GET_SCAN, { statusCode, errorCode })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const pollScanUntilCompletion = async (
|
|
30
|
+
config: any,
|
|
31
|
+
timeoutInMinutes: number,
|
|
32
|
+
params: ApiParams,
|
|
33
|
+
scanId: string
|
|
34
|
+
) => {
|
|
35
|
+
const client = getHttpClient(config)
|
|
36
|
+
|
|
37
|
+
const activeStatuses = ['PENDING', 'SCANNING', 'QUEUED']
|
|
38
|
+
const startedText = 'Scan started'
|
|
39
|
+
const maxEndTime = new Date().getTime() + timeoutInMinutes * MS_IN_MINUTE
|
|
40
|
+
const startScanSpinner = ora.returnOra(startedText)
|
|
41
|
+
ora.startSpinner(startScanSpinner)
|
|
42
|
+
|
|
43
|
+
await sleep(5000) // wait 5 sec before first polling
|
|
44
|
+
|
|
45
|
+
let complete = false
|
|
46
|
+
while (!complete) {
|
|
47
|
+
try {
|
|
48
|
+
const result = await exports.getScanResources(
|
|
49
|
+
config,
|
|
50
|
+
params,
|
|
51
|
+
scanId,
|
|
52
|
+
client
|
|
53
|
+
)
|
|
54
|
+
const { resources: scans } = result.body.data
|
|
55
|
+
const staticScans = scans?.filter((s: any) => s.scanType === 2)
|
|
56
|
+
complete = staticScans.some((s: any) => !activeStatuses.includes(s.state))
|
|
57
|
+
|
|
58
|
+
if (complete) {
|
|
59
|
+
ora.succeedSpinner(startScanSpinner, 'Scan Finished')
|
|
60
|
+
return scans
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await sleep(2 * 1000)
|
|
64
|
+
} catch (error) {
|
|
65
|
+
ora.failSpinner(startScanSpinner, 'Scan Failed')
|
|
66
|
+
throw error
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (Date.now() >= maxEndTime) {
|
|
70
|
+
ora.failSpinner(startScanSpinner, 'Scan timed out')
|
|
71
|
+
throw new CliError(ERRORS.FAILED_TO_GET_SCAN, {
|
|
72
|
+
errorCode: 'waitingTimedOut'
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { pollScanUntilCompletion, getScanResources }
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import i18n from 'i18n'
|
|
2
|
+
import logSymbols from 'log-symbols'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { parseARN } from './arn'
|
|
5
|
+
import {
|
|
6
|
+
getLambdaClient,
|
|
7
|
+
getLambdaFunctionConfiguration,
|
|
8
|
+
getLambdaPolicies,
|
|
9
|
+
getLayersLinks
|
|
10
|
+
} from './aws'
|
|
11
|
+
import { toLowerKeys } from './utils'
|
|
12
|
+
import { getHttpClient } from '../utils/commonApi'
|
|
13
|
+
import { ApiParams, LambdaOptions } from './lambda'
|
|
14
|
+
import { log, prettyPrintJson } from './logUtils'
|
|
15
|
+
import { CliError } from './cliError'
|
|
16
|
+
import { ERRORS } from './constants'
|
|
17
|
+
|
|
18
|
+
const sendScanPostRequest = async (
|
|
19
|
+
config: any,
|
|
20
|
+
params: ApiParams,
|
|
21
|
+
functionsEvent: unknown,
|
|
22
|
+
showProgress = false
|
|
23
|
+
) => {
|
|
24
|
+
const client = getHttpClient(config)
|
|
25
|
+
|
|
26
|
+
if (showProgress) {
|
|
27
|
+
// prettier-ignore
|
|
28
|
+
log(`${logSymbols.success} Sending Lambda Function scan request to Contrast`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const res = await client.postFunctionScan(config, params, functionsEvent)
|
|
32
|
+
const { statusCode, body } = res
|
|
33
|
+
|
|
34
|
+
if (statusCode === 201) {
|
|
35
|
+
if (showProgress) {
|
|
36
|
+
log(`${logSymbols.success} Scan requested successfully`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return body?.data?.scanId
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let { errorCode } = body?.data || {}
|
|
43
|
+
const { data } = body?.data || {}
|
|
44
|
+
|
|
45
|
+
let description = ''
|
|
46
|
+
switch (errorCode) {
|
|
47
|
+
case 'not_supported_runtime':
|
|
48
|
+
description = i18n.__(
|
|
49
|
+
errorCode,
|
|
50
|
+
data?.runtime,
|
|
51
|
+
data?.supportedRuntimes.sort().join(' | ')
|
|
52
|
+
)
|
|
53
|
+
errorCode = false
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new CliError(ERRORS.FAILED_TO_START_SCAN, {
|
|
58
|
+
statusCode,
|
|
59
|
+
errorCode,
|
|
60
|
+
data,
|
|
61
|
+
description
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const createFunctionEvent = (
|
|
66
|
+
lambdaConfig: any,
|
|
67
|
+
layersLinks: any,
|
|
68
|
+
lambdaPolicies: any
|
|
69
|
+
) => {
|
|
70
|
+
delete lambdaConfig.$metadata
|
|
71
|
+
|
|
72
|
+
const functionEvent = toLowerKeys(lambdaConfig.Configuration)
|
|
73
|
+
functionEvent['code'] = lambdaConfig.Code
|
|
74
|
+
functionEvent['rolePolicies'] = lambdaPolicies
|
|
75
|
+
|
|
76
|
+
if (layersLinks) {
|
|
77
|
+
functionEvent['layers'] = layersLinks
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { function: functionEvent }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const requestScanFunctionPost = async (
|
|
84
|
+
config: any,
|
|
85
|
+
lambdaOptions: LambdaOptions
|
|
86
|
+
) => {
|
|
87
|
+
const { verbose, jsonOutput, functionName } = lambdaOptions
|
|
88
|
+
const lambdaClient = getLambdaClient(lambdaOptions)
|
|
89
|
+
|
|
90
|
+
if (!jsonOutput) {
|
|
91
|
+
// prettier-ignore
|
|
92
|
+
log(`${logSymbols.success} Fetching configuration and policies for Lambda Function ${chalk.bold(functionName)}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const lambdaConfig = await getLambdaFunctionConfiguration(
|
|
96
|
+
lambdaClient,
|
|
97
|
+
lambdaOptions
|
|
98
|
+
)
|
|
99
|
+
if (!lambdaConfig?.Configuration) {
|
|
100
|
+
throw new CliError(ERRORS.FAILED_TO_START_SCAN, {
|
|
101
|
+
errorCode: 'missingLambdaConfig'
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
const { Configuration } = lambdaConfig
|
|
105
|
+
const layersLinks = await getLayersLinks(lambdaClient, Configuration)
|
|
106
|
+
const lambdaPolicies = await getLambdaPolicies(Configuration, lambdaOptions)
|
|
107
|
+
|
|
108
|
+
const functionEvent = createFunctionEvent(
|
|
109
|
+
lambdaConfig,
|
|
110
|
+
layersLinks,
|
|
111
|
+
lambdaPolicies
|
|
112
|
+
)
|
|
113
|
+
const { FunctionArn: functionArn } = Configuration
|
|
114
|
+
if (!functionArn) {
|
|
115
|
+
throw new CliError(ERRORS.FAILED_TO_START_SCAN, {
|
|
116
|
+
errorCode: 'missingLambdaArn'
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const parsedARN = parseARN(functionArn)
|
|
121
|
+
const params: ApiParams = {
|
|
122
|
+
organizationId: config.organizationId,
|
|
123
|
+
provider: 'aws',
|
|
124
|
+
accountId: parsedARN.accountId
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (verbose) {
|
|
128
|
+
log(`${logSymbols.success} Fetched configuration from AWS:`)
|
|
129
|
+
prettyPrintJson(functionEvent)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const scanId = await sendScanPostRequest(
|
|
133
|
+
config,
|
|
134
|
+
params,
|
|
135
|
+
functionEvent,
|
|
136
|
+
!jsonOutput
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return { scanId, params, functionArn }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { sendScanPostRequest, requestScanFunctionPost, createFunctionEvent }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getHttpClient } from '../utils/commonApi'
|
|
2
|
+
import { CliError } from './cliError'
|
|
3
|
+
import { ERRORS } from './constants'
|
|
4
|
+
import { ApiParams } from './lambda'
|
|
5
|
+
|
|
6
|
+
const getScanResults = async (
|
|
7
|
+
config: any,
|
|
8
|
+
params: ApiParams,
|
|
9
|
+
scanId: string,
|
|
10
|
+
functionArn: string
|
|
11
|
+
) => {
|
|
12
|
+
const client = getHttpClient(config)
|
|
13
|
+
|
|
14
|
+
const { statusCode, body } = await client.getFunctionScanResults(
|
|
15
|
+
config,
|
|
16
|
+
params,
|
|
17
|
+
scanId,
|
|
18
|
+
functionArn
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if (statusCode === 200) {
|
|
22
|
+
return body
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { errorCode } = body || {}
|
|
26
|
+
throw new CliError(ERRORS.FAILED_TO_GET_RESULTS, { statusCode, errorCode })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { getScanResults }
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { capitalize, groupBy, minBy, sortBy } from 'lodash'
|
|
3
|
+
import { log } from './logUtils'
|
|
4
|
+
|
|
5
|
+
const groupByCVE = ({ title }: any) =>
|
|
6
|
+
title.substring(0, title.indexOf('[') - 1)
|
|
7
|
+
|
|
8
|
+
const groupByDependency = ({ title }: any) =>
|
|
9
|
+
title.substring(title.indexOf('[') + 1, title.indexOf(']'))
|
|
10
|
+
|
|
11
|
+
const prettyPrintResults = (results: any[]) => {
|
|
12
|
+
log('')
|
|
13
|
+
|
|
14
|
+
//filter out any vulnerabs which is not least privilege or dependencies- cli does not handle other vulnerabs yet
|
|
15
|
+
const vulnerabs = results.filter(r => r.category === 1 || r.category === 4)
|
|
16
|
+
const sortBySeverity = sortBy(vulnerabs, ['severity', 'title'])
|
|
17
|
+
const notDependencies = sortBySeverity.filter(r => r.category !== 1)
|
|
18
|
+
const dependencies = sortBySeverity.filter(r => r.category === 1)
|
|
19
|
+
const dependenciesByLibrary = groupBy(dependencies, groupByDependency)
|
|
20
|
+
const dependenciesCount = Object.keys(dependenciesByLibrary).length
|
|
21
|
+
|
|
22
|
+
notDependencies.forEach(printVulnerability)
|
|
23
|
+
|
|
24
|
+
const prevIndex = notDependencies.length + 1
|
|
25
|
+
Object.entries(dependenciesByLibrary).forEach(([library, group], i) => {
|
|
26
|
+
const maxSeverity = minBy(group, 'severity')
|
|
27
|
+
const allCves = group.map(groupByCVE)
|
|
28
|
+
|
|
29
|
+
log(prevIndex + i)
|
|
30
|
+
log(
|
|
31
|
+
`${chalk.bold(capitalize(maxSeverity.severityText))} | ${chalk.bold(
|
|
32
|
+
'Vulnerable dependency'
|
|
33
|
+
)} ${library} has ${group.length} known CVEs`
|
|
34
|
+
)
|
|
35
|
+
log(allCves.join(', '))
|
|
36
|
+
if (maxSeverity.remediation?.description) {
|
|
37
|
+
log(
|
|
38
|
+
`${chalk.bold('Recommendation:')} ${
|
|
39
|
+
maxSeverity.remediation.description
|
|
40
|
+
}`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
log('')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const resultCount = notDependencies.length + dependenciesCount
|
|
47
|
+
const groupByType = groupBy(notDependencies, ['categoryText'])
|
|
48
|
+
const summary = Object.values(groupByType).map(
|
|
49
|
+
group => `${group.length} ${capitalize(group[0].categoryText)}`
|
|
50
|
+
)
|
|
51
|
+
log(`Found ${resultCount} vulnerabilities`, { bold: true })
|
|
52
|
+
summary.push(`${dependenciesCount} Dependencies`)
|
|
53
|
+
|
|
54
|
+
log(chalk.bold(summary.join(' | ')))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const underlineLinks = (text: string) => {
|
|
58
|
+
if (!text) {
|
|
59
|
+
return text
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g
|
|
63
|
+
return text.replace(urlRegex, chalk.underline('$1'))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const printVulnerability = (vulnerability: any, index: number) => {
|
|
67
|
+
log(index + 1)
|
|
68
|
+
const descriptionWithLinks = underlineLinks(vulnerability.description)
|
|
69
|
+
log(
|
|
70
|
+
`${chalk.bold(capitalize(vulnerability.severityText))} | ${chalk.bold(
|
|
71
|
+
vulnerability.title
|
|
72
|
+
)} ${descriptionWithLinks}`
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const category = vulnerability?.categoryText
|
|
76
|
+
switch (category) {
|
|
77
|
+
case 'PERMISSIONS':
|
|
78
|
+
printLeastPrivilegeRemediation(vulnerability)
|
|
79
|
+
break
|
|
80
|
+
default:
|
|
81
|
+
printRemediation(vulnerability)
|
|
82
|
+
}
|
|
83
|
+
log('')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const printLeastPrivilegeRemediation = (vulnerability: any) => {
|
|
87
|
+
log(
|
|
88
|
+
`${chalk.bold(
|
|
89
|
+
'Recommendation:'
|
|
90
|
+
)} Replace the existing policies with the following`
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const violatingPolicies =
|
|
94
|
+
vulnerability?.evidence?.leastPrivilege?.violatingPolicies || []
|
|
95
|
+
|
|
96
|
+
violatingPolicies
|
|
97
|
+
.filter((vp: any) => vp?.suggestedPolicy?.suggestedPolicyCode?.length)
|
|
98
|
+
.map((vp: any) => vp?.suggestedPolicy?.suggestedPolicyCode)
|
|
99
|
+
.forEach((policies: any) => {
|
|
100
|
+
policies.forEach((policy: any) => {
|
|
101
|
+
console.log(policy.snippet)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const printRemediation = (vulnerability: any) => {
|
|
107
|
+
log(`Remediation - ${vulnerability?.remediation?.description || 'Unknown'}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toLowerKeys(obj: Record<string, unknown>) {
|
|
111
|
+
return Object.keys(obj).reduce((accumulator, key) => {
|
|
112
|
+
const new_key = `${key[0].toLowerCase()}${key.slice(1)}`
|
|
113
|
+
accumulator[new_key] = obj[key]
|
|
114
|
+
return accumulator
|
|
115
|
+
}, {} as Record<string, unknown>)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { toLowerKeys, prettyPrintResults }
|
|
119
|
+
|
|
120
|
+
export const exportedForTesting = {
|
|
121
|
+
printLeastPrivilegeRemediation,
|
|
122
|
+
printRemediation,
|
|
123
|
+
printVulnerability,
|
|
124
|
+
underlineLinks
|
|
125
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const i18n = require('i18n')
|
|
2
|
+
const { zipValidator } = require('./scan')
|
|
3
|
+
const fileFinder = require('./fileUtils')
|
|
4
|
+
const { supportedLanguages } = require('../constants/constants')
|
|
5
|
+
|
|
6
|
+
const autoDetectFileAndLanguage = async configToUse => {
|
|
7
|
+
const entries = await fileFinder.findFile()
|
|
8
|
+
|
|
9
|
+
if (entries.length === 1) {
|
|
10
|
+
console.log(i18n.__('foundScanFile', entries[0]))
|
|
11
|
+
|
|
12
|
+
if (hasWhiteSpace(entries[0])) {
|
|
13
|
+
console.log(i18n.__('fileHasWhiteSpacesError'))
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
configToUse.file = entries[0]
|
|
18
|
+
if (configToUse.name === undefined) {
|
|
19
|
+
configToUse.name = entries[0]
|
|
20
|
+
}
|
|
21
|
+
zipValidator(configToUse)
|
|
22
|
+
assignLanguage(entries, configToUse)
|
|
23
|
+
} else {
|
|
24
|
+
errorOnFileDetection(entries)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hasWhiteSpace = s => {
|
|
29
|
+
const filename = s.split('/').pop()
|
|
30
|
+
return filename.indexOf(' ') >= 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const errorOnFileDetection = entries => {
|
|
34
|
+
if (entries.length > 1) {
|
|
35
|
+
console.log(i18n.__('searchingDirectoryScan'))
|
|
36
|
+
for (let file in entries) {
|
|
37
|
+
console.log('-', entries[file])
|
|
38
|
+
}
|
|
39
|
+
console.log('')
|
|
40
|
+
console.log(i18n.__('specifyFileScanError'))
|
|
41
|
+
} else {
|
|
42
|
+
console.log(i18n.__('noFileFoundScan'))
|
|
43
|
+
console.log('')
|
|
44
|
+
console.log(i18n.__('specifyFileScanError'))
|
|
45
|
+
}
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const assignLanguage = (entries, configToUse) => {
|
|
50
|
+
let split = entries[0].split('.')
|
|
51
|
+
const fileType = split[split.length - 1]
|
|
52
|
+
if (fileType === 'war' || fileType === 'jar') {
|
|
53
|
+
console.log('Language is Java')
|
|
54
|
+
configToUse.language = 'JAVA'
|
|
55
|
+
} else if (fileType === 'dll') {
|
|
56
|
+
console.log('Language is Dotnet')
|
|
57
|
+
configToUse.language = 'DOTNET'
|
|
58
|
+
} else if (fileType === 'js') {
|
|
59
|
+
console.log('Language is Javascript')
|
|
60
|
+
configToUse.language = supportedLanguages.JAVASCRIPT
|
|
61
|
+
} else if (fileType === 'zip') {
|
|
62
|
+
if (configToUse.language !== supportedLanguages.JAVASCRIPT) {
|
|
63
|
+
console.log(i18n.__('zipErrorScan'))
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
console.log('Language is Javascript within zip file')
|
|
67
|
+
} else {
|
|
68
|
+
console.log(i18n.__('unknownFileErrorScan'))
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
autoDetectFileAndLanguage,
|
|
75
|
+
assignLanguage,
|
|
76
|
+
errorOnFileDetection
|
|
77
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const fg = require('fast-glob')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const i18n = require('i18n')
|
|
4
|
+
|
|
5
|
+
const findFile = async () => {
|
|
6
|
+
console.log(i18n.__('searchingScanFileDirectory', process.cwd()))
|
|
7
|
+
return fg(['**/*.jar', '**/*.war', '**/*.zip', '**/*.dll'], {
|
|
8
|
+
dot: false,
|
|
9
|
+
deep: 3,
|
|
10
|
+
onlyFiles: true
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const checkFilePermissions = file => {
|
|
15
|
+
let readableFile = false
|
|
16
|
+
try {
|
|
17
|
+
fs.accessSync(file, fs.constants.R_OK)
|
|
18
|
+
return (readableFile = true) // testing purposes
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.log('Invalid permissions found on ', file)
|
|
21
|
+
process.exit(0)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const fileExists = path => {
|
|
26
|
+
return fs.existsSync(path)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
findFile,
|
|
31
|
+
fileExists,
|
|
32
|
+
checkFilePermissions
|
|
33
|
+
}
|
package/src/scan/help.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const commandLineUsage = require('command-line-usage')
|
|
2
|
+
const i18n = require('i18n')
|
|
3
|
+
|
|
4
|
+
const scanUsageGuide = commandLineUsage([
|
|
5
|
+
{
|
|
6
|
+
header: i18n.__('scanHeader')
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
header: i18n.__('constantsPrerequisitesHeader'),
|
|
10
|
+
content: [
|
|
11
|
+
'{bold ' + i18n.__('constantsPrerequisitesContentScanLanguages') + '}',
|
|
12
|
+
i18n.__('constantsPrerequisitesContent'),
|
|
13
|
+
'',
|
|
14
|
+
i18n.__('constantsUsageCommandInfo'),
|
|
15
|
+
i18n.__('constantsUsageCommandInfo24Hours')
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
header: i18n.__('constantsScanOptions'),
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
name: i18n.__('scanOptionsFileName'),
|
|
23
|
+
summary:
|
|
24
|
+
'{italic ' +
|
|
25
|
+
i18n.__('constantsOptional') +
|
|
26
|
+
'}: ' +
|
|
27
|
+
i18n.__('scanOptionsFileNameSummary')
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: i18n.__('scanOptionsLanguage'),
|
|
31
|
+
summary:
|
|
32
|
+
'{italic ' +
|
|
33
|
+
i18n.__('constantsOptional') +
|
|
34
|
+
'}: ' +
|
|
35
|
+
i18n.__('scanOptionsLanguageSummaryOptional') +
|
|
36
|
+
'{italic ' +
|
|
37
|
+
i18n.__('constantsRequired') +
|
|
38
|
+
'}: ' +
|
|
39
|
+
i18n.__('scanOptionsLanguageSummaryRequired')
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: i18n.__('scanOptionsName'),
|
|
43
|
+
summary:
|
|
44
|
+
'{italic ' +
|
|
45
|
+
i18n.__('constantsOptional') +
|
|
46
|
+
'}: ' +
|
|
47
|
+
i18n.__('scanOptionsNameSummary')
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: i18n.__('scanOptionsTimeout'),
|
|
51
|
+
summary:
|
|
52
|
+
'{italic ' +
|
|
53
|
+
i18n.__('constantsOptional') +
|
|
54
|
+
'}: ' +
|
|
55
|
+
i18n.__('scanOptionsTimeoutSummary')
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: i18n.__('scanOptionsVerbose'),
|
|
59
|
+
summary:
|
|
60
|
+
'{italic ' +
|
|
61
|
+
i18n.__('constantsOptional') +
|
|
62
|
+
'}: ' +
|
|
63
|
+
i18n.__('scanOptionsVerboseSummary')
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
content: '{underline https://www.contrastsecurity.com}'
|
|
69
|
+
}
|
|
70
|
+
])
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
scanUsageGuide
|
|
74
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const commonApi = require('../utils/commonApi.js')
|
|
2
|
+
const i18n = require('i18n')
|
|
3
|
+
|
|
4
|
+
const populateProjectId = async config => {
|
|
5
|
+
const client = commonApi.getHttpClient(config)
|
|
6
|
+
let proj = await createProjectId(config, client)
|
|
7
|
+
if (proj === undefined) {
|
|
8
|
+
proj = await getExistingProjectIdByName(config, client).then(res => {
|
|
9
|
+
return res
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return proj
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const createProjectId = async (config, client) => {
|
|
17
|
+
return client
|
|
18
|
+
.createProjectId(config)
|
|
19
|
+
.then(res => {
|
|
20
|
+
if (res.statusCode === 409) {
|
|
21
|
+
console.log(i18n.__('foundExistingProjectScan'))
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (res.statusCode === 201) {
|
|
26
|
+
console.log(i18n.__('projectCreatedScan'))
|
|
27
|
+
if (config.verbose) {
|
|
28
|
+
console.log(i18n.__('populateProjectIdMessage', res.body.id))
|
|
29
|
+
}
|
|
30
|
+
return res.body.id
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
.catch(err => {
|
|
34
|
+
if (config.verbose) {
|
|
35
|
+
console.log(err)
|
|
36
|
+
}
|
|
37
|
+
console.log(i18n.__('connectionError'))
|
|
38
|
+
process.exit(0)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getExistingProjectIdByName = async (config, client) => {
|
|
43
|
+
return client
|
|
44
|
+
.getProjectIdByName(config)
|
|
45
|
+
.then(res => {
|
|
46
|
+
if (res.statusCode === 200) {
|
|
47
|
+
if (config.verbose) {
|
|
48
|
+
console.log(
|
|
49
|
+
i18n.__('populateProjectIdMessage', res.body.content[0].id)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
return res.body.content[0].id
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
.catch(err => {
|
|
56
|
+
console.log(err)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
populateProjectId: populateProjectId
|
|
62
|
+
}
|