@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.
Files changed (88) hide show
  1. package/README.md +109 -0
  2. package/bin/contrast.js +2 -0
  3. package/dist/commands/auth/auth.js +61 -0
  4. package/dist/commands/config/config.js +24 -0
  5. package/dist/commands/scan/processScan.js +23 -0
  6. package/dist/common/HTTPClient.js +192 -0
  7. package/dist/common/errorHandling.js +60 -0
  8. package/dist/constants/constants.js +30 -0
  9. package/dist/constants/lambda.js +31 -0
  10. package/dist/constants/locales.js +259 -0
  11. package/dist/constants.js +150 -0
  12. package/dist/index.js +56 -0
  13. package/dist/lambda/__mocks__/aws.js +21 -0
  14. package/dist/lambda/__mocks__/lambdaConfig.json +42 -0
  15. package/dist/lambda/arn.js +21 -0
  16. package/dist/lambda/aws.js +169 -0
  17. package/dist/lambda/cliError.js +76 -0
  18. package/dist/lambda/constants.js +11 -0
  19. package/dist/lambda/help.js +75 -0
  20. package/dist/lambda/lambda.js +130 -0
  21. package/dist/lambda/logUtils.js +36 -0
  22. package/dist/lambda/scanDetail.js +30 -0
  23. package/dist/lambda/scanDetailCompletion.js +56 -0
  24. package/dist/lambda/scanRequest.js +93 -0
  25. package/dist/lambda/scanResults.js +16 -0
  26. package/dist/lambda/utils.js +90 -0
  27. package/dist/scan/autoDetection.js +76 -0
  28. package/dist/scan/fileFinder.js +15 -0
  29. package/dist/scan/fileUtils.js +31 -0
  30. package/dist/scan/help.js +68 -0
  31. package/dist/scan/populateProjectIdAndProjectName.js +55 -0
  32. package/dist/scan/scan.js +96 -0
  33. package/dist/scan/scanController.js +54 -0
  34. package/dist/scan/scanResults.js +85 -0
  35. package/dist/utils/commonApi.js +45 -0
  36. package/dist/utils/filterProjectPath.js +20 -0
  37. package/dist/utils/getConfig.js +30 -0
  38. package/dist/utils/oraWrapper.js +20 -0
  39. package/dist/utils/paramsUtil/commandlineParams.js +31 -0
  40. package/dist/utils/paramsUtil/configStoreParams.js +18 -0
  41. package/dist/utils/paramsUtil/envVariableParams.js +10 -0
  42. package/dist/utils/paramsUtil/paramHandler.js +28 -0
  43. package/dist/utils/paramsUtil/yamlParams.js +6 -0
  44. package/dist/utils/parsedCLIOptions.js +13 -0
  45. package/dist/utils/requestUtils.js +18 -0
  46. package/dist/utils/validationCheck.js +26 -0
  47. package/package.json +123 -0
  48. package/src/commands/auth/auth.js +73 -0
  49. package/src/commands/config/config.js +25 -0
  50. package/src/commands/scan/processScan.js +29 -0
  51. package/src/common/HTTPClient.js +269 -0
  52. package/src/common/errorHandling.ts +79 -0
  53. package/src/constants/constants.js +34 -0
  54. package/src/constants/lambda.js +41 -0
  55. package/src/constants/locales.js +381 -0
  56. package/src/constants.js +168 -0
  57. package/src/index.ts +69 -0
  58. package/src/lambda/__mocks__/aws.ts +32 -0
  59. package/src/lambda/__mocks__/lambdaConfig.json +42 -0
  60. package/src/lambda/arn.ts +32 -0
  61. package/src/lambda/aws.ts +247 -0
  62. package/src/lambda/cliError.ts +72 -0
  63. package/src/lambda/constants.ts +11 -0
  64. package/src/lambda/help.ts +76 -0
  65. package/src/lambda/lambda.ts +174 -0
  66. package/src/lambda/logUtils.ts +46 -0
  67. package/src/lambda/scanDetailCompletion.ts +78 -0
  68. package/src/lambda/scanRequest.ts +142 -0
  69. package/src/lambda/scanResults.ts +29 -0
  70. package/src/lambda/utils.ts +125 -0
  71. package/src/scan/autoDetection.js +77 -0
  72. package/src/scan/fileUtils.js +33 -0
  73. package/src/scan/help.js +74 -0
  74. package/src/scan/populateProjectIdAndProjectName.js +62 -0
  75. package/src/scan/scan.js +126 -0
  76. package/src/scan/scanController.js +69 -0
  77. package/src/scan/scanResults.js +96 -0
  78. package/src/utils/commonApi.js +54 -0
  79. package/src/utils/filterProjectPath.js +21 -0
  80. package/src/utils/getConfig.ts +42 -0
  81. package/src/utils/oraWrapper.js +24 -0
  82. package/src/utils/paramsUtil/commandlineParams.js +37 -0
  83. package/src/utils/paramsUtil/configStoreParams.js +19 -0
  84. package/src/utils/paramsUtil/envVariableParams.js +10 -0
  85. package/src/utils/paramsUtil/paramHandler.js +28 -0
  86. package/src/utils/parsedCLIOptions.js +17 -0
  87. package/src/utils/requestUtils.js +22 -0
  88. package/src/utils/validationCheck.js +34 -0
@@ -0,0 +1,269 @@
1
+ const _ = require('lodash')
2
+ const fs = require('fs')
3
+ const requestUtils = require('./../utils/requestUtils')
4
+ const { AUTH_CALLBACK_URL } = require('../constants/constants')
5
+
6
+ function HTTPClient(config) {
7
+ const apiKey = config.apiKey
8
+ const authToken = config.authorization
9
+ // this.rejectUnauthorized = !additionalParams.ignore_cert_errors
10
+
11
+ const superApiKey = config.super_api_key
12
+ const superAuthToken = config.super_authorization
13
+
14
+ this.requestOptions = {
15
+ forever: true,
16
+ json: true,
17
+ rejectUnauthorized: this.rejectUnauthorized,
18
+ uri: config.host,
19
+ followRedirect: false,
20
+ headers: {
21
+ 'Content-Type': 'application/json; charset=utf-8',
22
+ Authorization: authToken,
23
+ 'API-Key': apiKey,
24
+ SuperAuthorization: superAuthToken,
25
+ 'Super-API-Key': superApiKey
26
+ }
27
+ }
28
+
29
+ if (config.proxy) {
30
+ this.requestOptions.proxy = config.proxy
31
+ }
32
+
33
+ this.maybeAddCertsToRequest(config)
34
+ }
35
+
36
+ HTTPClient.prototype.maybeAddCertsToRequest = function(config) {
37
+ // cacert
38
+ const caCertFilePath = config.cacert
39
+ if (caCertFilePath) {
40
+ const caFileContent = fs.readFileSync(caCertFilePath)
41
+ if (caFileContent instanceof Error) {
42
+ throw new Error(
43
+ `Unable to read CA from config option contrast.api.certificate.ca_file='${caCertFilePath}', msg: ${caFileContent.message}`
44
+ )
45
+ }
46
+ this.requestOptions.ca = caFileContent
47
+ }
48
+
49
+ // cert
50
+ const certPath = config.cert
51
+ if (certPath) {
52
+ const certFile = fs.readFileSync(certPath)
53
+ if (certFile instanceof Error) {
54
+ throw new Error(
55
+ `Unable to read Certificate PEM file from config option contrast.api.certificate.cert_file='${certPath}', msg: ${certFile.message}`
56
+ )
57
+ }
58
+ this.requestOptions.cert = certFile
59
+ }
60
+
61
+ // key
62
+ const keyPath = config.key
63
+ if (keyPath) {
64
+ const keyFile = fs.readFileSync(keyPath)
65
+ if (keyFile instanceof Error) {
66
+ throw new Error(
67
+ `Unable to read Key PEM file from config option contrast.api.certificate.key_file='${keyPath}', msg: ${keyFile.message}`
68
+ )
69
+ }
70
+ this.requestOptions.key = keyFile
71
+ }
72
+ }
73
+
74
+ HTTPClient.prototype.getScanResultsInstances = function getScanResultsInstances(
75
+ config,
76
+ scanId
77
+ ) {
78
+ const options = _.cloneDeep(this.requestOptions)
79
+ let url = createScanResultsInstancesURL(config, scanId)
80
+ options.url = url
81
+ return requestUtils.sendRequest({ method: 'get', options })
82
+ }
83
+
84
+ HTTPClient.prototype.getSpecificScanResult = function getSpecificScanResult(
85
+ config,
86
+ scanId
87
+ ) {
88
+ const options = _.cloneDeep(this.requestOptions)
89
+ let url = createSpecificScanResultURL(config, scanId)
90
+ options.url = url
91
+ return requestUtils.sendRequest({ method: 'get', options })
92
+ }
93
+
94
+ HTTPClient.prototype.getScanId = function getScanId(config, codeArtifactId) {
95
+ const options = _.cloneDeep(this.requestOptions)
96
+ let url = createGetScanIdURL(config)
97
+ options.url = url
98
+ options.body = {
99
+ codeArtifactId: codeArtifactId,
100
+ label: `Started by CLI tool at ${new Date().toString()}`
101
+ }
102
+ return requestUtils.sendRequest({ method: 'post', options })
103
+ }
104
+
105
+ HTTPClient.prototype.sendArtifact = async function sendArtifact(config) {
106
+ const options = _.cloneDeep(this.requestOptions)
107
+
108
+ let formData = {
109
+ filename: fs.createReadStream(config.file)
110
+ }
111
+ options.formData = formData
112
+ options.headers['Content-Type'] = 'multipart/form-data'
113
+ options.url = createHarmonyUrl(config)
114
+ return requestUtils.sendRequest({ method: 'post', options })
115
+ }
116
+
117
+ HTTPClient.prototype.createProjectId = function createProjectId(config) {
118
+ const options = _.cloneDeep(this.requestOptions)
119
+
120
+ options.body = {
121
+ name: config.name,
122
+ archived: 'false',
123
+ language: config.language
124
+ }
125
+ options.url = createHarmonyProjectsUrl(config)
126
+ return requestUtils.sendRequest({ method: 'post', options })
127
+ }
128
+
129
+ HTTPClient.prototype.getProjectIdByName = function getProjectIdByName(config) {
130
+ const options = _.cloneDeep(this.requestOptions)
131
+
132
+ options.url = createHarmonyProjectsUrl(config) + '?name=' + config.name
133
+ return requestUtils.sendRequest({ method: 'get', options })
134
+ }
135
+
136
+ HTTPClient.prototype.getScanProjectById = function getScanProjectById(config) {
137
+ const options = _.cloneDeep(this.requestOptions)
138
+
139
+ options.url = createScanProjectUrl(config)
140
+ return requestUtils.sendRequest({ method: 'get', options })
141
+ }
142
+
143
+ HTTPClient.prototype.getGlobalProperties = function getGlobalProperties() {
144
+ const options = _.cloneDeep(this.requestOptions)
145
+ let url = createGlobalPropertiesUrl(options.uri)
146
+ options.url = url
147
+ return requestUtils.sendRequest({ method: 'get', options })
148
+ }
149
+
150
+ HTTPClient.prototype.pollForAuth = function pollForAuth(token) {
151
+ const options = _.cloneDeep(this.requestOptions)
152
+ let url = pollForAuthUrl()
153
+ options.url = url
154
+
155
+ let requestBody = {}
156
+ requestBody.token = token
157
+ options.body = requestBody
158
+
159
+ return requestUtils.sendRequest({ method: 'post', options })
160
+ }
161
+
162
+ // serverless - lambda
163
+ function getServerlessHost(config = {}) {
164
+ const originalHost = config?.host || config?.get('host')
165
+ const host = originalHost?.endsWith('/')
166
+ ? originalHost.slice(0, -1)
167
+ : originalHost
168
+
169
+ return `${host}/Contrast/api/serverless`
170
+ }
171
+
172
+ function createScanFunctionPostUrl(config, params) {
173
+ const url = getServerlessHost(config)
174
+ const { provider, accountId, organizationId } = params
175
+
176
+ return `${url}/organizations/${organizationId}/providers/${provider}/accounts/${accountId}/function-scan`
177
+ }
178
+
179
+ function createScanResourcesGetUrl(config, params, scanId) {
180
+ const url = getServerlessHost(config)
181
+ const { provider, accountId, organizationId } = params
182
+ const encodedScanId = encodeURIComponent(scanId)
183
+
184
+ return `${url}/organizations/${organizationId}/providers/${provider}/accounts/${accountId}/scans/${encodedScanId}/resources`
185
+ }
186
+
187
+ function createScanResultsGetUrl(config, params, scanId, functionArn) {
188
+ const url = getServerlessHost(config)
189
+ const encodedScanId = encodeURIComponent(scanId)
190
+ const encodedFunctionArn = encodeURIComponent(functionArn)
191
+ const { provider, accountId, organizationId } = params
192
+
193
+ return `${url}/organizations/${organizationId}/providers/${provider}/accounts/${accountId}/scans/${encodedScanId}/resources/${encodedFunctionArn}/results`
194
+ }
195
+
196
+ HTTPClient.prototype.postFunctionScan = async function postFunctionScan(
197
+ config,
198
+ parameters,
199
+ body
200
+ ) {
201
+ const url = createScanFunctionPostUrl(config, parameters)
202
+ const options = { ...this.requestOptions, body, url }
203
+
204
+ return requestUtils.sendRequest({ method: 'post', options })
205
+ }
206
+
207
+ HTTPClient.prototype.getScanResources = async function getScanResources(
208
+ config,
209
+ parameters,
210
+ scanId
211
+ ) {
212
+ const url = createScanResourcesGetUrl(config, parameters, scanId)
213
+ const options = { ...this.requestOptions, url }
214
+
215
+ return requestUtils.sendRequest({ method: 'get', options })
216
+ }
217
+
218
+ HTTPClient.prototype.getFunctionScanResults = async function getFunctionScanResults(
219
+ config,
220
+ parameters,
221
+ scanId,
222
+ functionArn
223
+ ) {
224
+ const url = createScanResultsGetUrl(config, parameters, scanId, functionArn)
225
+ const options = { ...this.requestOptions, url }
226
+
227
+ return requestUtils.sendRequest({ method: 'get', options })
228
+ }
229
+
230
+ // scan
231
+ const createGetScanIdURL = config => {
232
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}/scans/`
233
+ }
234
+
235
+ const createScanResultsInstancesURL = (config, scanId) => {
236
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}/scans/${scanId}/result-instances?sort=severity,asc`
237
+ }
238
+
239
+ const createRawOutputURL = (config, codeArtifactId) => {
240
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}/scans/${codeArtifactId}/raw-output`
241
+ }
242
+
243
+ const createSpecificScanResultURL = (config, scanId) => {
244
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}/scans/${scanId}`
245
+ }
246
+
247
+ function createHarmonyUrl(config) {
248
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}/code-artifacts`
249
+ }
250
+
251
+ function createHarmonyProjectsUrl(config) {
252
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects`
253
+ }
254
+
255
+ function createScanProjectUrl(config) {
256
+ return `${config.host}/Contrast/api/sast/v1/organizations/${config.organizationId}/projects/${config.projectId}`
257
+ }
258
+
259
+ const createGlobalPropertiesUrl = protocol => {
260
+ return `${protocol}/Contrast/api/ng/global/properties`
261
+ }
262
+
263
+ const pollForAuthUrl = () => {
264
+ return `${AUTH_CALLBACK_URL}/auth/credentials`
265
+ }
266
+
267
+ module.exports = HTTPClient
268
+ module.exports.pollForAuthUrl = pollForAuthUrl
269
+ module.exports.getServerlessHost = getServerlessHost
@@ -0,0 +1,79 @@
1
+ import i18n from 'i18n'
2
+
3
+ const genericError = (missingCliOption: string) => {
4
+ // prettier-ignore
5
+ console.log(`*************************** ${i18n.__('yamlMissingParametersHeader')} ***************************\n${missingCliOption}`)
6
+ console.error(i18n.__('yamlMissingParametersMessage'))
7
+ process.exit(1)
8
+ }
9
+
10
+ const unauthenticatedError = () => {
11
+ generalError('unauthenticatedErrorHeader', 'unauthenticatedErrorMessage')
12
+ }
13
+
14
+ const badRequestError = (catalogue: boolean) => {
15
+ catalogue === true
16
+ ? generalError('badRequestErrorHeader', 'badRequestCatalogueErrorMessage')
17
+ : generalError('badRequestErrorHeader', 'badRequestErrorMessage')
18
+ }
19
+
20
+ const forbiddenError = () => {
21
+ generalError('forbiddenRequestErrorHeader', 'forbiddenRequestErrorMessage')
22
+ }
23
+
24
+ const proxyError = () => {
25
+ generalError('proxyErrorHeader', 'proxyErrorMessage')
26
+ }
27
+
28
+ const hostWarningError = () => {
29
+ console.log(i18n.__('snapshotHostMessage'))
30
+ }
31
+
32
+ const failOptionError = () => {
33
+ console.log(
34
+ '\n ******************************** ' +
35
+ i18n.__('snapshotFailureHeader') +
36
+ ' ********************************\n' +
37
+ i18n.__('failOptionErrorMessage')
38
+ )
39
+ }
40
+
41
+ /**
42
+ * You don't have to pass `i18n` translation.
43
+ * String that didn't exists on translations will pass as regular string
44
+ * @param header title for the error
45
+ * @param message message for the error
46
+ * @returns error in general format
47
+ */
48
+ const getErrorMessage = (header: string, message?: string) => {
49
+ // prettier-ignore
50
+ const title = `******************************** ${i18n.__(header)} ********************************`
51
+ const multiLine = message?.includes('\n')
52
+ let finalMessage = ''
53
+
54
+ // i18n split the line if it includes "\n"
55
+ if (multiLine) {
56
+ finalMessage = `\n${message}`
57
+ } else if (message) {
58
+ finalMessage = `\n${i18n.__(message)}`
59
+ }
60
+
61
+ return `${title}${finalMessage}`
62
+ }
63
+
64
+ const generalError = (header: string, message?: string) => {
65
+ const finalMessage = getErrorMessage(header, message)
66
+ console.log(finalMessage)
67
+ }
68
+
69
+ export {
70
+ genericError,
71
+ unauthenticatedError,
72
+ badRequestError,
73
+ forbiddenError,
74
+ proxyError,
75
+ failOptionError,
76
+ hostWarningError,
77
+ generalError,
78
+ getErrorMessage
79
+ }
@@ -0,0 +1,34 @@
1
+ // Language identifiers
2
+ const NODE = 'NODE'
3
+ const DOTNET = 'DOTNET'
4
+ const JAVA = 'JAVA'
5
+ const RUBY = 'RUBY'
6
+ const PYTHON = 'PYTHON'
7
+ const GO = 'GO'
8
+ // we set the langauge as Node instead of PHP since we're using the Node engine in TS
9
+ const PHP = 'PHP'
10
+ const JAVASCRIPT = 'JAVASCRIPT'
11
+
12
+ const LOW = 'LOW'
13
+ const MEDIUM = 'MEDIUM'
14
+ const HIGH = 'HIGH'
15
+ const CRITICAL = 'CRITICAL'
16
+
17
+ const APP_NAME = 'contrast'
18
+ const APP_VERSION = '1.0.0'
19
+ const TIMEOUT = 120000
20
+ const AUTH_UI_URL = 'https://cli-auth.contrastsecurity.com'
21
+ const AUTH_CALLBACK_URL = 'https://cli-auth-api.contrastsecurity.com'
22
+
23
+ module.exports = {
24
+ supportedLanguages: { NODE, DOTNET, JAVA, RUBY, PYTHON, GO, PHP, JAVASCRIPT },
25
+ LOW,
26
+ MEDIUM,
27
+ HIGH,
28
+ CRITICAL,
29
+ APP_VERSION,
30
+ APP_NAME,
31
+ TIMEOUT,
32
+ AUTH_UI_URL,
33
+ AUTH_CALLBACK_URL
34
+ }
@@ -0,0 +1,41 @@
1
+ const lambda = {
2
+ failedToStartScan: 'Failed to start scan',
3
+ failedToParseArn: 'Failed to parse ARN',
4
+ failedToGetScan: 'Failed to get scan',
5
+ missingLambdaConfig: 'Missing Lambda Configuration',
6
+ missingLambdaArn: 'Missing Lambda ARN',
7
+ validationFailed: 'Request validation failed',
8
+ missingFunctionName:
9
+ 'Required parameter --function-name is missing.\nRun command with --help to see usage',
10
+ failedToGetResults: 'Failed to get results',
11
+ missingResults: 'Missing vulnerabilities',
12
+ missingParameter: 'Required function parameter is missing', // should use it again
13
+ awsError: 'AWS error',
14
+ missingFlagArguments: 'The following flags are missing an arguments:\n%s',
15
+ notSupportedFlags:
16
+ 'The following flags are not supported:\n%s\nRun command with --help to see usage',
17
+
18
+ // ====== errorCodes ===== //
19
+ something_went_wrong: 'Something went wrong',
20
+ not_found_404: '404 error - Not found',
21
+ internal_error: 'Internal error',
22
+ inactive_account:
23
+ 'Scanning a function of an inactive account is not supported',
24
+ not_supported_runtime:
25
+ 'Scanning resource of runtime "%s" is not supported.\nSupported runtimes: %s',
26
+ not_supported_onboard_account:
27
+ 'Scanning a function of onboard account is not supported',
28
+ scan_lock:
29
+ 'Other scan is still running. Please wait until the previous scan finishes',
30
+
31
+ // ====== statuses ===== //
32
+ unsupported: 'unsupported',
33
+ excluded: 'excluded',
34
+ canceled: 'canceled',
35
+ failed: 'failed',
36
+ dismissed: 'dismissed'
37
+ }
38
+
39
+ module.exports = {
40
+ lambda
41
+ }