@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,42 @@
1
+ {
2
+ "$metadata": {
3
+ "httpStatusCode": 200,
4
+ "requestId": "c1495998-4606-46ba-b4fc-f7d0d165172d",
5
+ "attempts": 1,
6
+ "totalRetryDelay": 0
7
+ },
8
+ "Code": {
9
+ "Location": "https://awslambda-eu-cent-1-tasks.s3.eu-central-1.amazonaws.com/snapshots/123456789012/FunctionLambda-90e7680-c598cb5f-6bc9-4107-9fe7-13c126eb3b9e?versionId=ThxUComOVL.qnyEN9Bmi3xzK1JshuKA3&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGcaDGV1LWNlbnRyYWwtMSJIMEYCIQDAMKTGQ5WEMHM9H7cZQugjEX8QWvq5zRxtXg%2Fz6m9lmAIhAMst%2F5iQkOPJ%2BNfflPiwD4GQchajTfvbNvVeGlz%2BdFi%2FKokECPD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQAxoMNjgwNjg2NTU5NDM0IgwPQDJYINU9W7Csw4sq3QO52WKm5PvlVEeLrwstuDqPPKxhdK57A6z2foEF0Bp%2Bz%2Fp%2Flei2DzbpqjPDluzljZmzh3kP8QKPwq25Yk59%2BfXnxlSkHYe3GXO6W6VLBkPk81T0C9keVwdMijW1ZV94KN8qBB17kERR3sFIhBWXJRLhXSAdLREAfNLaE%2FgnP1eC1b5MZCBW46lbjmYHhHshgZJEUD2fy%2BRuOB4HijldLkpHKgZfwiD0ICXkvxF5NQT6tUhlQPNN%2BCrC2RQ0NSkmjXiaL2BXaDxaQVhZwMTGzBEyLAonA9bSisObWrVEjJcC4%2Bqz9ce25l2yYf77lEXWymEp9NvFFcUNAt6tt%2FAm2qMixcLxV0Y2NuBUBjIvPnNnTvBop%2FvAC1Rh6033AmWpmkK0tD65wEsTS4XAEtnD%2B%2Bku4r6kzatRJ88Lq9YOTbjs%2BMEccy9YIFp7Rwf2%2Bdw3EKHSqz4aB0R9KYa07NooZ%2Bym1VZWfGLfhFRJwKRLk3qKkY%2Bj4laizIE%2BBgqC7f7%2FYXcFk8RGX8vlX5y%2BMKWhBXoMPqAPG7ruoG1RbjQX8TEJvuG0G8c5x76fRjB2VRlykY78wa7%2B83a8oBqMfojq7hQWByNv%2B13KHVIIqrG87tL%2BtrmS7GAoils0vXf0Mvowqt6RkgY6pAGKzTtWpXiIhFLe5n2CNNDMuT9xCSCpQIWf2M9GcNabj%2FAUHIa81gvZwuHzB6DYpEj2cZ7wO22Ve%2FRLIrzdkBROoRYnT0GFevXUJAoO2WolWU13JS7owlBPTW7aET6o7fHJUhwAxPZPCp1cSaFMO5cDN%2BTOVm7V6P4es6m87S1yozp5SzHx3KblhJJF6krPHX6YCWC%2B%2FAu9tu7PqgiZ3RQTZ26f4A%3D%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220330T153702Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAZ47AUUDFFU2TD45R%2F20220330%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Signature=4f9187001f127561afee310d9a127acacb5064c51688f7cd65f98ccb0a7aaa10",
10
+ "RepositoryType": "S3"
11
+ },
12
+ "Configuration": {
13
+ "Architectures": ["x86_64"],
14
+ "CodeSha256": "KNNSfx0YOrZ+pG3fK0qbGBJv/b8V0/gWFFkFw581i4o=",
15
+ "CodeSize": 39577174,
16
+ "DeadLetterConfig": {
17
+ "TargetArn": "arn:aws:sqs:eu-central-1:123456789012:devlocaleuDeadLetterQ-89016d3"
18
+ },
19
+ "Description": "",
20
+ "Environment": {
21
+ "Variables": {
22
+ "EVENTS_BUCKET": "devlocaleu-cn-events-4h6eig-eu-central-1-1676820",
23
+ "AGENTS_TABLE": "devlocaleu.agent.agents"
24
+ }
25
+ },
26
+ "EphemeralStorage": { "Size": 512 },
27
+ "FunctionArn": "arn:aws:lambda:eu-central-1:123456789012:function:FunctionLambda-90e7680",
28
+ "FunctionName": "FunctionLambda-90e7680",
29
+ "Handler": "com.contrastsecurity.scan.ScanHandler::handleEventDataWithContext",
30
+ "LastModified": "2022-03-28T11:14:56.000+0000",
31
+ "LastUpdateStatus": "Successful",
32
+ "MemorySize": 1024,
33
+ "PackageType": "Zip",
34
+ "RevisionId": "d174710a-23ff-499b-a7ba-61036beabd7a",
35
+ "Role": "arn:aws:iam::123456789012:role/Function_Role",
36
+ "Runtime": "java11",
37
+ "State": "Active",
38
+ "Timeout": 900,
39
+ "TracingConfig": { "Mode": "PassThrough" },
40
+ "Version": "$LATEST"
41
+ }
42
+ }
@@ -0,0 +1,32 @@
1
+ import { CliError } from './cliError'
2
+ import { ERRORS } from './constants'
3
+
4
+ type ARN = {
5
+ partition: string
6
+ service: string
7
+ region: string
8
+ accountId: string
9
+ resource: string
10
+ resourceId?: string
11
+ }
12
+
13
+ const ARN_REGEX = /arn:(?<partition>[^:\n]*):(?<service>[^:\n]*):(?<region>[^:\n]*):(?<accountId>[^:\n]*):(?<ignore>(?<resource>[^:/\n]*)[:/])?(?<resourceId>.*)/
14
+
15
+ const parseARN = (arn: string | undefined) => {
16
+ if (!arn) {
17
+ throw new CliError(ERRORS.FAILED_TO_START_SCAN, {
18
+ errorCode: 'failedToParseArn'
19
+ })
20
+ }
21
+
22
+ const arnMatch = arn.match(ARN_REGEX)
23
+ if (!arnMatch) {
24
+ throw new CliError(ERRORS.FAILED_TO_START_SCAN, {
25
+ errorCode: 'failedToParseArn'
26
+ })
27
+ }
28
+
29
+ return arnMatch.groups as ARN
30
+ }
31
+
32
+ export { parseARN, ARN }
@@ -0,0 +1,247 @@
1
+ import {
2
+ Lambda,
3
+ GetFunctionCommand,
4
+ GetLayerVersionByArnCommand,
5
+ FunctionConfiguration,
6
+ ResourceNotFoundException,
7
+ LambdaServiceException
8
+ } from '@aws-sdk/client-lambda'
9
+ import {
10
+ GetRolePolicyCommand,
11
+ IAMClient,
12
+ paginateListRolePolicies,
13
+ paginateListAttachedRolePolicies,
14
+ GetPolicyCommand,
15
+ GetPolicyVersionCommand
16
+ } from '@aws-sdk/client-iam'
17
+ import { fromIni } from '@aws-sdk/credential-provider-ini'
18
+ import { RegionInputConfig } from '@aws-sdk/config-resolver/dist-types/regionConfig/resolveRegionConfig'
19
+ import { EndpointsInputConfig } from '@aws-sdk/config-resolver/dist-types/endpointsConfig/resolveEndpointsConfig'
20
+ import { AwsAuthInputConfig } from '@aws-sdk/middleware-signing/dist-types/configurations'
21
+ import { CliError } from './cliError'
22
+ import { LambdaOptions } from './lambda'
23
+ import { ERRORS } from './constants'
24
+
25
+ type AWSClientConfig = RegionInputConfig &
26
+ EndpointsInputConfig &
27
+ AwsAuthInputConfig
28
+
29
+ const getAwsClientOptions = ({
30
+ region,
31
+ endpointUrl,
32
+ profile
33
+ }: LambdaOptions): AWSClientConfig => {
34
+ const credentials = profile ? fromIni({ profile }) : undefined
35
+ return {
36
+ region: region || process.env.AWS_DEFAULT_REGION,
37
+ endpoint: endpointUrl,
38
+ credentials
39
+ }
40
+ }
41
+
42
+ const getLambdaClient = (lambdaOptions: LambdaOptions) => {
43
+ try {
44
+ const clientOptions = getAwsClientOptions(lambdaOptions)
45
+ return new Lambda(clientOptions)
46
+ } catch (error) {
47
+ const errorObj = error as any
48
+ if (errorObj?.code === 'ERR_INVALID_URL') {
49
+ throw new CliError(ERRORS.AWS_ERROR, { description: errorObj.message })
50
+ }
51
+
52
+ throw error
53
+ }
54
+ }
55
+
56
+ const getIAMClient = (lambdaOptions: LambdaOptions) => {
57
+ const clientOptions = getAwsClientOptions(lambdaOptions)
58
+ return new IAMClient(clientOptions)
59
+ }
60
+
61
+ const getLambdaFunctionConfiguration = async (
62
+ client: Lambda,
63
+ lambdaOptions: LambdaOptions
64
+ ) => {
65
+ const { functionName: FunctionName } = lambdaOptions
66
+ const getFunctionCommand = new GetFunctionCommand({ FunctionName })
67
+ try {
68
+ return await client.send(getFunctionCommand)
69
+ } catch (error) {
70
+ throwAwsError(error)
71
+ }
72
+ }
73
+
74
+ const getLayersLinks = async (
75
+ client: Lambda,
76
+ functionConfiguration: FunctionConfiguration
77
+ ) => {
78
+ const { Layers: layers = [] } = functionConfiguration
79
+ const resultPromises = layers.map(async layerDict => {
80
+ try {
81
+ const layerArn = layerDict.Arn
82
+ const getLayerVersionByArnCommand = new GetLayerVersionByArnCommand({
83
+ Arn: layerArn
84
+ })
85
+ const layer = await client.send(getLayerVersionByArnCommand)
86
+ return {
87
+ Arn: layerArn,
88
+ Location: layer.Content?.Location
89
+ }
90
+ } catch (e) {
91
+ if (e instanceof ResourceNotFoundException) {
92
+ e.message = `The layer ${layerDict.Arn} could not be found. We will continue the scan without it.`
93
+ throw e
94
+ }
95
+ throw e
96
+ }
97
+ })
98
+ const results = await Promise.allSettled(resultPromises)
99
+ const validLayers = results.filter(layerResult => {
100
+ if (layerResult.status === 'rejected') {
101
+ console.warn(layerResult.reason.message)
102
+ return false
103
+ }
104
+ return true
105
+ })
106
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
107
+ // @ts-ignore: value is not recognized
108
+ return validLayers.map(layer => layer.value)
109
+ }
110
+
111
+ const getLambdaPolicies = async (
112
+ functionConfiguration: FunctionConfiguration,
113
+ lambdaOptions: LambdaOptions
114
+ ) => {
115
+ const { Role: roleArn } = functionConfiguration
116
+ const roleSplitList = roleArn?.split('/')
117
+
118
+ if (roleSplitList) {
119
+ const roleName = roleSplitList[roleSplitList.length - 1]
120
+ const client = exports.getIAMClient(lambdaOptions)
121
+ const rolePolicies = await getRolePolicies(roleName, client)
122
+ const attachedPolicies = await getAttachedPolicies(roleName, client)
123
+ return [...rolePolicies, ...attachedPolicies]
124
+ }
125
+ }
126
+
127
+ const getRolePolicyNames = async (roleName: string, client: IAMClient) => {
128
+ const listRolePolicyNames = []
129
+
130
+ try {
131
+ for await (const page of paginateListRolePolicies(
132
+ { client },
133
+ { RoleName: roleName }
134
+ )) {
135
+ if (page.PolicyNames) {
136
+ listRolePolicyNames.push(...page.PolicyNames)
137
+ }
138
+ }
139
+ } catch (error) {
140
+ throwAwsError(error)
141
+ }
142
+
143
+ return listRolePolicyNames
144
+ }
145
+
146
+ const getAttachedPolicyNames = async (roleName: string, client: IAMClient) => {
147
+ const listAttachedPolicies = []
148
+ for await (const page of paginateListAttachedRolePolicies(
149
+ { client },
150
+ { RoleName: roleName }
151
+ )) {
152
+ if (page.AttachedPolicies) {
153
+ listAttachedPolicies.push(...page.AttachedPolicies)
154
+ }
155
+ }
156
+ return listAttachedPolicies
157
+ }
158
+
159
+ const getRolePolicies = async (roleName: string, client: IAMClient) => {
160
+ const listRolePolicyNames = await exports.getRolePolicyNames(roleName, client)
161
+
162
+ if (listRolePolicyNames) {
163
+ const rolePoliciesPromises = listRolePolicyNames.map(
164
+ async (policyName: any) => {
165
+ const getRolePolicyCommand = new GetRolePolicyCommand({
166
+ PolicyName: policyName,
167
+ RoleName: roleName
168
+ })
169
+
170
+ const rolePolicy = await client.send(getRolePolicyCommand)
171
+ const policyDoc = JSON.parse(
172
+ decodeURIComponent(rolePolicy?.PolicyDocument || '{}')
173
+ )
174
+ policyDoc.PolicyName = policyName
175
+ return policyDoc
176
+ }
177
+ )
178
+
179
+ const rolePolicies = await Promise.all(rolePoliciesPromises)
180
+ return rolePolicies
181
+ }
182
+
183
+ return []
184
+ }
185
+
186
+ const getAttachedPolicies = async (roleName: string, client: IAMClient) => {
187
+ const listAttachedPolicies = await exports.getAttachedPolicyNames(
188
+ roleName,
189
+ client
190
+ )
191
+ const attachedPoliciesPromises = listAttachedPolicies.map(
192
+ async (policyDict: { PolicyArn: any; PolicyName: any }) => {
193
+ const getPolicyCommand = new GetPolicyCommand({
194
+ PolicyArn: policyDict.PolicyArn
195
+ })
196
+ const policy = await client.send(getPolicyCommand)
197
+ if (policy.Policy) {
198
+ const getPolicyVersionCommand = new GetPolicyVersionCommand({
199
+ PolicyArn: policy.Policy.Arn,
200
+ VersionId: policy.Policy.DefaultVersionId
201
+ })
202
+ const policyVersion = await client.send(getPolicyVersionCommand)
203
+ const policyDoc = JSON.parse(
204
+ decodeURIComponent(policyVersion?.PolicyVersion?.Document || '{}')
205
+ )
206
+ policyDoc['PolicyName'] = policyDict.PolicyName
207
+ policyDoc['PolicyArn'] = policyDict.PolicyArn
208
+ return policyDoc
209
+ }
210
+ }
211
+ )
212
+
213
+ const attachedPolicies = await Promise.all(attachedPoliciesPromises)
214
+ return attachedPolicies
215
+ }
216
+
217
+ /**
218
+ *
219
+ * @param error any error
220
+ * @returns throw AWS error in union format
221
+ */
222
+ const throwAwsError = (error: any) => {
223
+ const serviceError = error as LambdaServiceException
224
+
225
+ if (error instanceof Error && serviceError.$metadata) {
226
+ const { httpStatusCode } = serviceError.$metadata
227
+ const { message } = error
228
+
229
+ throw new CliError(ERRORS.AWS_ERROR, {
230
+ statusCode: httpStatusCode,
231
+ description: message
232
+ })
233
+ }
234
+
235
+ throw error
236
+ }
237
+
238
+ export {
239
+ getAttachedPolicyNames,
240
+ getRolePolicyNames,
241
+ getIAMClient,
242
+ getLambdaClient,
243
+ getLambdaFunctionConfiguration,
244
+ getLayersLinks,
245
+ getLambdaPolicies,
246
+ throwAwsError
247
+ }
@@ -0,0 +1,72 @@
1
+ import i18n from 'i18n'
2
+ import * as errorHandling from '../common/errorHandling'
3
+
4
+ type ErrorDetails = {
5
+ statusCode?: number // API statusCode
6
+ errorCode?: string // internal errorCode
7
+ description?: string // free usage
8
+ data?: any //
9
+ }
10
+
11
+ class CliError extends Error {
12
+ statusCode?: number
13
+ errorCode?: string
14
+ description?: string
15
+ data?: any
16
+
17
+ statusCodeDescription?: string
18
+ errorCodeDescription?: string
19
+
20
+ constructor(headerMessage: string, details?: ErrorDetails) {
21
+ const message = i18n.__(headerMessage || '')
22
+ super(message)
23
+
24
+ const { statusCode, errorCode, data, description } = details || {}
25
+
26
+ this.statusCode = statusCode
27
+ this.errorCode = errorCode
28
+ this.data = data
29
+ this.description = description
30
+
31
+ if (errorCode) {
32
+ this.errorCodeDescription = i18n.__(errorCode || '')
33
+ }
34
+
35
+ if (statusCode) {
36
+ this.statusCodeDescription = this.getMessageByStatusCode(statusCode)
37
+ }
38
+ }
39
+
40
+ getErrorMessage() {
41
+ let finalDesc =
42
+ this.errorCodeDescription || this.statusCodeDescription || ''
43
+
44
+ if (this.description) {
45
+ finalDesc += finalDesc ? `\n${this.description}` : this.description
46
+ }
47
+ return errorHandling.getErrorMessage(this.message, finalDesc)
48
+ }
49
+
50
+ getMessageByStatusCode(statusCode: number) {
51
+ switch (statusCode) {
52
+ case 200:
53
+ return ''
54
+ case 400:
55
+ return i18n.__('badRequestErrorHeader')
56
+ case 401:
57
+ return i18n.__('unauthenticatedErrorHeader')
58
+ case 403:
59
+ return i18n.__('forbiddenRequestErrorHeader')
60
+ case 404:
61
+ return i18n.__('not_found_404')
62
+ case 423:
63
+ return i18n.__('resourceLockedErrorHeader')
64
+ case 500:
65
+ return i18n.__('internalServerErrorHeader')
66
+ default:
67
+ return i18n.__('something_went_wrong')
68
+ }
69
+ }
70
+ }
71
+
72
+ export { CliError }
@@ -0,0 +1,11 @@
1
+ // TODO: don't forget to add translation in `src/constants/lambda.js` for each value
2
+
3
+ const ERRORS = Object.freeze({
4
+ AWS_ERROR: 'awsError',
5
+ FAILED_TO_START_SCAN: 'failedToStartScan',
6
+ FAILED_TO_GET_SCAN: 'failedToGetScan',
7
+ FAILED_TO_GET_RESULTS: 'failedToGetResults',
8
+ VALIDATION_FAILED: 'validationFailed'
9
+ })
10
+
11
+ export { ERRORS }
@@ -0,0 +1,76 @@
1
+ import commandLineUsage from 'command-line-usage'
2
+ import i18n from 'i18n'
3
+
4
+ const lambdaUsageGuide = commandLineUsage([
5
+ {
6
+ header: i18n.__('lambdaHeader'),
7
+ content: [i18n.__('lambdaSummary')]
8
+ },
9
+ {
10
+ header: i18n.__('constantsPrerequisitesHeader'),
11
+ content: [i18n.__('lambdaPrerequisitesContent')]
12
+ },
13
+ {
14
+ header: i18n.__('constantsUsage'),
15
+ content: [i18n.__('lambdaUsage')]
16
+ },
17
+ {
18
+ header: i18n.__('constantsOptions'),
19
+ content: [
20
+ {
21
+ name: i18n.__('lambdaFunctionNameOption'),
22
+ summary: i18n.__('lambdaFunctionNameSummery')
23
+ },
24
+ {
25
+ name: i18n.__('lambdaEndpointOption'),
26
+ summary:
27
+ '{italic ' +
28
+ i18n.__('constantsOptional') +
29
+ '}: ' +
30
+ i18n.__('lambdaEndpointSummery')
31
+ },
32
+ {
33
+ name: i18n.__('lambdaRegionOption'),
34
+ summary:
35
+ '{italic ' +
36
+ i18n.__('constantsOptional') +
37
+ '}: ' +
38
+ i18n.__('lambdaRegionSummery')
39
+ },
40
+ {
41
+ name: i18n.__('lambdaProfileOption'),
42
+ summary:
43
+ '{italic ' +
44
+ i18n.__('constantsOptional') +
45
+ '}: ' +
46
+ i18n.__('lambdaProfileSummery')
47
+ },
48
+ {
49
+ name: i18n.__('lambdaJsonOption'),
50
+ summary:
51
+ '{italic ' +
52
+ i18n.__('constantsOptional') +
53
+ '}: ' +
54
+ i18n.__('lambdaJsonSummery')
55
+ },
56
+ {
57
+ name: i18n.__('lambdaVerboseOption'),
58
+ summary:
59
+ '{italic ' +
60
+ i18n.__('constantsOptional') +
61
+ '}: ' +
62
+ i18n.__('lambdaVerbosSummery')
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ content: [
68
+ { name: i18n.__('lambdaHelpOption'), summary: i18n.__('helpSummary') }
69
+ ]
70
+ },
71
+ {
72
+ content: '{underline https://www.contrastsecurity.com}'
73
+ }
74
+ ])
75
+
76
+ export { lambdaUsageGuide }
@@ -0,0 +1,174 @@
1
+ import commandLineArgs from 'command-line-args'
2
+ import { performance } from 'perf_hooks'
3
+ import { kebabCase } from 'lodash'
4
+ import i18n from 'i18n'
5
+ import { getAuth } from '../utils/paramsUtil/paramHandler'
6
+ import { CliError } from './cliError'
7
+ import { ERRORS } from './constants'
8
+ import { lambdaUsageGuide } from './help'
9
+ import { log } from './logUtils'
10
+ import { pollScanUntilCompletion } from './scanDetailCompletion'
11
+ import { requestScanFunctionPost } from './scanRequest'
12
+ import { getScanResults } from './scanResults'
13
+ import { prettyPrintResults } from './utils'
14
+
15
+ type LambdaOptions = {
16
+ functionName?: string
17
+ region?: string
18
+ endpointUrl?: string
19
+ profile?: string
20
+ help?: boolean
21
+ verbose?: boolean
22
+ jsonOutput?: boolean
23
+ _unknown?: string[]
24
+ }
25
+
26
+ type ApiParams = {
27
+ organizationId: string
28
+ provider: 'aws'
29
+ accountId: string
30
+ }
31
+
32
+ const failedStates = [
33
+ 'UNSUPPORTED',
34
+ 'EXCLUDED',
35
+ 'CANCELED',
36
+ 'FAILED',
37
+ 'DISMISSED'
38
+ ]
39
+
40
+ const printHelpMessage = () => {
41
+ log(lambdaUsageGuide)
42
+ }
43
+
44
+ const getLambdaOptions = (argv: string[]) => {
45
+ const lambdaDefinitions = [
46
+ { name: 'function-name', alias: 'f', type: String },
47
+ { name: 'region', alias: 'r', type: String },
48
+ { name: 'endpoint-url', alias: 'e', type: String },
49
+ { name: 'profile', alias: 'p', type: String },
50
+ { name: 'help', alias: 'h', type: Boolean },
51
+ { name: 'verbose', alias: 'v', type: Boolean },
52
+ { name: 'json-output', alias: 'j', type: Boolean }
53
+ ]
54
+
55
+ const lambdaOptions: LambdaOptions = commandLineArgs(lambdaDefinitions, {
56
+ argv,
57
+ partial: true,
58
+ camelCase: true,
59
+ caseInsensitive: true
60
+ })
61
+
62
+ return lambdaOptions
63
+ }
64
+
65
+ const processLambda = async (argv: string[]) => {
66
+ const lambdaOptions = getLambdaOptions(argv)
67
+ const { help } = lambdaOptions
68
+
69
+ if (help) {
70
+ return handleLambdaHelp()
71
+ }
72
+
73
+ try {
74
+ validateRequiredLambdaParams(lambdaOptions)
75
+
76
+ await actualProcessLambda(lambdaOptions)
77
+ } catch (error) {
78
+ if (error instanceof CliError) {
79
+ console.error(error.getErrorMessage())
80
+ } else if (error instanceof Error) {
81
+ console.error(error.message)
82
+ }
83
+ process.exit(1)
84
+ }
85
+ }
86
+
87
+ const actualProcessLambda = async (lambdaOptions: LambdaOptions) => {
88
+ const auth = getAuth()
89
+ const startTime = performance.now()
90
+ const { jsonOutput } = lambdaOptions
91
+ const { scanId, params, functionArn } = await requestScanFunctionPost(
92
+ auth,
93
+ lambdaOptions
94
+ )
95
+ const scans = await pollScanUntilCompletion(auth, 10, params, scanId)
96
+ const failedScan = scans
97
+ ?.filter((s: any) => s.scanType === 2)
98
+ .find((s: any) => failedStates.includes(s.state))
99
+
100
+ if (failedScan) {
101
+ throw new CliError(ERRORS.FAILED_TO_GET_SCAN, {
102
+ statusCode: 200,
103
+ errorCode: failedScan.state,
104
+ description: failedScan.stateReasonText
105
+ })
106
+ }
107
+
108
+ const resultsResponse = await getScanResults(
109
+ auth,
110
+ params,
111
+ scanId,
112
+ functionArn
113
+ )
114
+
115
+ if (jsonOutput) {
116
+ console.log(JSON.stringify(resultsResponse?.data?.results, null, 2))
117
+ return
118
+ }
119
+
120
+ const results = resultsResponse?.data?.results
121
+ if (!results) {
122
+ throw new CliError(ERRORS.FAILED_TO_GET_RESULTS, {
123
+ errorCode: 'missingResults'
124
+ })
125
+ }
126
+
127
+ if (!results.length) {
128
+ log('👏 No vulnerabilities found')
129
+ }
130
+
131
+ const endTime = performance.now()
132
+ const scanDurationMs = endTime - startTime
133
+
134
+ log(`----- Scan completed ${(scanDurationMs / 1000).toFixed(2)}s -----`)
135
+
136
+ if (results?.length) {
137
+ prettyPrintResults(results)
138
+ }
139
+ }
140
+
141
+ const validateRequiredLambdaParams = (options: LambdaOptions) => {
142
+ if (options._unknown?.length) {
143
+ throw new CliError(ERRORS.VALIDATION_FAILED, {
144
+ description: i18n.__('notSupportedFlags', options._unknown.join('\n'))
145
+ })
146
+ }
147
+
148
+ if (!options?.functionName) {
149
+ throw new CliError(ERRORS.VALIDATION_FAILED, {
150
+ errorCode: 'missingFunctionName'
151
+ })
152
+ }
153
+
154
+ const flagsWithoutValues = Object.entries(options)
155
+ .filter(([, value]) => !value)
156
+ .map(([key]) => key)
157
+ .map(p => `--${kebabCase(p)}`)
158
+
159
+ if (flagsWithoutValues.length) {
160
+ throw new CliError(ERRORS.VALIDATION_FAILED, {
161
+ description: i18n.__(
162
+ 'missingFlagArguments',
163
+ flagsWithoutValues.join('\n')
164
+ )
165
+ })
166
+ }
167
+ }
168
+
169
+ const handleLambdaHelp = () => {
170
+ printHelpMessage()
171
+ process.exit(0)
172
+ }
173
+
174
+ export { processLambda, LambdaOptions, ApiParams }
@@ -0,0 +1,46 @@
1
+ import chalk from 'chalk'
2
+ import util from 'util'
3
+
4
+ interface logStyles {
5
+ bold?: boolean
6
+ italic?: boolean
7
+ underline?: boolean
8
+ strikethrough?: boolean
9
+ }
10
+
11
+ const log = (message: string | number, styles?: logStyles) => {
12
+ let chalkFunction = chalk.reset
13
+
14
+ if (styles?.bold) {
15
+ chalkFunction = chalk.bold
16
+ } else if (styles?.italic) {
17
+ chalkFunction = chalk.italic
18
+ } else if (styles?.underline) {
19
+ chalkFunction = chalk.underline
20
+ } else if (styles?.strikethrough) {
21
+ chalkFunction = chalk.strikethrough
22
+ }
23
+
24
+ console.log(styles ? chalkFunction(message) : message)
25
+ }
26
+
27
+ /**
28
+ *
29
+ * @param obj any json object or string
30
+ * @param depth determines how levels it will recurse to show the json
31
+ */
32
+ const prettyPrintJson = (obj: string | any, depth: number | null = null) => {
33
+ if (!obj) {
34
+ return
35
+ }
36
+
37
+ let objToPrint = obj
38
+
39
+ if (typeof obj === 'string') {
40
+ objToPrint = JSON.parse(obj)
41
+ }
42
+
43
+ console.log(util.inspect(objToPrint, { colors: true, depth }))
44
+ }
45
+
46
+ export { log, prettyPrintJson }