conviso-ast 3.0.0__py3-none-any.whl
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.
- conviso_ast-3.0.0.data/scripts/flow_bash_completer.sh +21 -0
- conviso_ast-3.0.0.data/scripts/flow_fish_completer.fish +1 -0
- conviso_ast-3.0.0.data/scripts/flow_zsh_completer.sh +32 -0
- conviso_ast-3.0.0.dist-info/METADATA +37 -0
- conviso_ast-3.0.0.dist-info/RECORD +128 -0
- conviso_ast-3.0.0.dist-info/WHEEL +5 -0
- conviso_ast-3.0.0.dist-info/entry_points.txt +3 -0
- conviso_ast-3.0.0.dist-info/top_level.txt +1 -0
- convisoappsec/__init__.py +0 -0
- convisoappsec/common/__init__.py +5 -0
- convisoappsec/common/box.py +251 -0
- convisoappsec/common/cleaner.py +78 -0
- convisoappsec/common/docker.py +399 -0
- convisoappsec/common/exceptions.py +8 -0
- convisoappsec/common/git_data_parser.py +76 -0
- convisoappsec/common/graphql/__init__.py +0 -0
- convisoappsec/common/graphql/error_handlers.py +75 -0
- convisoappsec/common/graphql/errors.py +16 -0
- convisoappsec/common/graphql/low_client.py +51 -0
- convisoappsec/common/retry_handler.py +40 -0
- convisoappsec/common/strings.py +8 -0
- convisoappsec/flow/__init__.py +3 -0
- convisoappsec/flow/api.py +104 -0
- convisoappsec/flow/cleaner.py +118 -0
- convisoappsec/flow/graphql_api/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/client.py +18 -0
- convisoappsec/flow/graphql_api/beta/models/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/models/issues/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/models/issues/container.py +72 -0
- convisoappsec/flow/graphql_api/beta/models/issues/iac.py +6 -0
- convisoappsec/flow/graphql_api/beta/models/issues/normalize.py +13 -0
- convisoappsec/flow/graphql_api/beta/models/issues/sast.py +53 -0
- convisoappsec/flow/graphql_api/beta/models/issues/sca.py +78 -0
- convisoappsec/flow/graphql_api/beta/resources_api.py +142 -0
- convisoappsec/flow/graphql_api/beta/schemas/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/schemas/mutations/__init__.py +61 -0
- convisoappsec/flow/graphql_api/beta/schemas/resolvers/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/client.py +46 -0
- convisoappsec/flow/graphql_api/v1/models/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/models/asset.py +14 -0
- convisoappsec/flow/graphql_api/v1/models/issues.py +16 -0
- convisoappsec/flow/graphql_api/v1/models/project.py +35 -0
- convisoappsec/flow/graphql_api/v1/resources_api.py +489 -0
- convisoappsec/flow/graphql_api/v1/schemas/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/schemas/mutations/__init__.py +212 -0
- convisoappsec/flow/graphql_api/v1/schemas/resolvers/__init__.py +180 -0
- convisoappsec/flow/source_code_scanner/__init__.py +9 -0
- convisoappsec/flow/source_code_scanner/exceptions.py +2 -0
- convisoappsec/flow/source_code_scanner/scc.py +68 -0
- convisoappsec/flow/source_code_scanner/source_code_scanner.py +177 -0
- convisoappsec/flow/util/__init__.py +7 -0
- convisoappsec/flow/util/ci_provider.py +99 -0
- convisoappsec/flow/util/metrics.py +16 -0
- convisoappsec/flow/util/source_code_compressor.py +22 -0
- convisoappsec/flow/version_control_system_adapter.py +528 -0
- convisoappsec/flow/version_searchers/__init__.py +9 -0
- convisoappsec/flow/version_searchers/sorted_by_versioning_style.py +85 -0
- convisoappsec/flow/version_searchers/timebased_version_seacher.py +39 -0
- convisoappsec/flow/version_searchers/version_searcher_result.py +33 -0
- convisoappsec/flow/versioning_style/__init__.py +0 -0
- convisoappsec/flow/versioning_style/semantic_versioning.py +44 -0
- convisoappsec/flowcli/__init__.py +3 -0
- convisoappsec/flowcli/__main__.py +4 -0
- convisoappsec/flowcli/assets/__init__.py +4 -0
- convisoappsec/flowcli/assets/create.py +88 -0
- convisoappsec/flowcli/assets/entrypoint.py +20 -0
- convisoappsec/flowcli/assets/ls.py +63 -0
- convisoappsec/flowcli/ast/__init__.py +3 -0
- convisoappsec/flowcli/ast/entrypoint.py +427 -0
- convisoappsec/flowcli/common.py +175 -0
- convisoappsec/flowcli/companies/__init__.py +0 -0
- convisoappsec/flowcli/companies/ls.py +25 -0
- convisoappsec/flowcli/container/__init__.py +3 -0
- convisoappsec/flowcli/container/entrypoint.py +17 -0
- convisoappsec/flowcli/container/run.py +306 -0
- convisoappsec/flowcli/context.py +49 -0
- convisoappsec/flowcli/deploy/__init__.py +0 -0
- convisoappsec/flowcli/deploy/create/__init__.py +4 -0
- convisoappsec/flowcli/deploy/create/context.py +12 -0
- convisoappsec/flowcli/deploy/create/entrypoint.py +31 -0
- convisoappsec/flowcli/deploy/create/with_/__init__.py +3 -0
- convisoappsec/flowcli/deploy/create/with_/entrypoint.py +20 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/__init__.py +4 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/context.py +11 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/entrypoint.py +30 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/__init__.py +4 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/entrypoint.py +21 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/time_.py +84 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/versioning_style.py +115 -0
- convisoappsec/flowcli/deploy/create/with_/values.py +133 -0
- convisoappsec/flowcli/entrypoint.py +103 -0
- convisoappsec/flowcli/environment_checker.py +45 -0
- convisoappsec/flowcli/findings/__init__.py +4 -0
- convisoappsec/flowcli/findings/create/__init__.py +4 -0
- convisoappsec/flowcli/findings/create/entrypoint.py +18 -0
- convisoappsec/flowcli/findings/create/with_/__init__.py +3 -0
- convisoappsec/flowcli/findings/create/with_/entrypoint.py +19 -0
- convisoappsec/flowcli/findings/create/with_/version_tracker.py +93 -0
- convisoappsec/flowcli/findings/entrypoint.py +19 -0
- convisoappsec/flowcli/findings/import_sarif/__init__.py +4 -0
- convisoappsec/flowcli/findings/import_sarif/entrypoint.py +430 -0
- convisoappsec/flowcli/help_option.py +18 -0
- convisoappsec/flowcli/iac/__init__.py +3 -0
- convisoappsec/flowcli/iac/entrypoint.py +17 -0
- convisoappsec/flowcli/iac/run.py +328 -0
- convisoappsec/flowcli/requirements_verifier.py +132 -0
- convisoappsec/flowcli/sast/__init__.py +3 -0
- convisoappsec/flowcli/sast/entrypoint.py +17 -0
- convisoappsec/flowcli/sast/run.py +485 -0
- convisoappsec/flowcli/sbom/__init__.py +3 -0
- convisoappsec/flowcli/sbom/entrypoint.py +17 -0
- convisoappsec/flowcli/sbom/generate.py +235 -0
- convisoappsec/flowcli/sca/__init__.py +3 -0
- convisoappsec/flowcli/sca/entrypoint.py +17 -0
- convisoappsec/flowcli/sca/run.py +479 -0
- convisoappsec/flowcli/vulnerability/__init__.py +3 -0
- convisoappsec/flowcli/vulnerability/assert_security_rules.py +201 -0
- convisoappsec/flowcli/vulnerability/container_vulnerability_manager.py +175 -0
- convisoappsec/flowcli/vulnerability/entrypoint.py +18 -0
- convisoappsec/flowcli/vulnerability/rules_schema.json +53 -0
- convisoappsec/flowcli/vulnerability/run.py +487 -0
- convisoappsec/logger.py +29 -0
- convisoappsec/sast/__init__.py +0 -0
- convisoappsec/sast/decision.py +45 -0
- convisoappsec/sast/sastbox.py +296 -0
- convisoappsec/version.py +1 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import json
|
|
3
|
+
from convisoappsec.flowcli import help_option
|
|
4
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
5
|
+
from convisoappsec.sast.sastbox import SASTBox
|
|
6
|
+
from convisoappsec.flowcli.common import (asset_id_option, on_http_error)
|
|
7
|
+
from convisoappsec.flowcli.requirements_verifier import RequirementsVerifier
|
|
8
|
+
from convisoappsec.flow import GitAdapter
|
|
9
|
+
from convisoappsec.common.box import ContainerWrapper
|
|
10
|
+
from convisoappsec.common.graphql.errors import ResponseError
|
|
11
|
+
from convisoappsec.flow.cleaner import RunnerCleanup
|
|
12
|
+
from convisoappsec.logger import LOGGER
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@asset_id_option(required=False)
|
|
17
|
+
@click.option(
|
|
18
|
+
"-s",
|
|
19
|
+
"--start-commit",
|
|
20
|
+
required=False,
|
|
21
|
+
help="If no value is set so the empty tree hash commit is used.",
|
|
22
|
+
)
|
|
23
|
+
@click.option(
|
|
24
|
+
"-e",
|
|
25
|
+
"--end-commit",
|
|
26
|
+
required=False,
|
|
27
|
+
help="""If no value is set so the HEAD commit
|
|
28
|
+
from the current branch is used""",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"-r",
|
|
32
|
+
"--repository-dir",
|
|
33
|
+
default=".",
|
|
34
|
+
show_default=True,
|
|
35
|
+
type=click.Path(
|
|
36
|
+
exists=True,
|
|
37
|
+
resolve_path=True,
|
|
38
|
+
),
|
|
39
|
+
required=False,
|
|
40
|
+
help="The source code repository directory.",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--fail-on-severity-threshold",
|
|
44
|
+
required=False,
|
|
45
|
+
help="If the threshold of the informed severity and higher has reach, then the command will fail after send the results to AppSec Flow.\n \
|
|
46
|
+
The severity levels are: UNDEFINED, INFO, LOW, MEDIUM, HIGH, CRITICAL.",
|
|
47
|
+
type=click.Tuple([str, int]),
|
|
48
|
+
default=(None, None),
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--fail-on-threshold",
|
|
52
|
+
required=False,
|
|
53
|
+
help="If the threshold has reach then the command will fail after send the result to AppSec Flow",
|
|
54
|
+
type=int,
|
|
55
|
+
default=False,
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--send-to-flow/--no-send-to-flow",
|
|
59
|
+
default=True,
|
|
60
|
+
show_default=True,
|
|
61
|
+
required=False,
|
|
62
|
+
hidden=True,
|
|
63
|
+
help="""Enable or disable the ability of send analysis result
|
|
64
|
+
reports to flow.""",
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"--deploy-id",
|
|
68
|
+
default=None,
|
|
69
|
+
required=False,
|
|
70
|
+
hidden=True,
|
|
71
|
+
envvar=("CONVISO_DEPLOY_ID", "FLOW_DEPLOY_ID")
|
|
72
|
+
)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--sastbox-registry",
|
|
75
|
+
default="",
|
|
76
|
+
required=False,
|
|
77
|
+
hidden=True,
|
|
78
|
+
envvar=("CONVISO_SASTBOX_REGISTRY", "FLOW_SASTBOX_REGISTRY"),
|
|
79
|
+
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--sastbox-repository-name",
|
|
82
|
+
default="",
|
|
83
|
+
required=False,
|
|
84
|
+
hidden=True,
|
|
85
|
+
envvar=("CONVISO_SASTBOX_REPOSITORY_NAME", "FLOW_SASTBOX_REPOSITORY_NAME"),
|
|
86
|
+
)
|
|
87
|
+
@click.option(
|
|
88
|
+
"--sastbox-tag",
|
|
89
|
+
default=SASTBox.DEFAULT_TAG,
|
|
90
|
+
required=False,
|
|
91
|
+
hidden=True,
|
|
92
|
+
envvar=("CONVISO_SASTBOX_TAG", "FLOW_SASTBOX_TAG"),
|
|
93
|
+
)
|
|
94
|
+
@click.option(
|
|
95
|
+
"--sastbox-skip-login/--sastbox-no-skip-login",
|
|
96
|
+
default=False,
|
|
97
|
+
required=False,
|
|
98
|
+
hidden=True,
|
|
99
|
+
envvar=("CONVISO_SASTBOX_SKIP_LOGIN", "FLOW_SASTBOX_SKIP_LOGIN"),
|
|
100
|
+
)
|
|
101
|
+
@click.option(
|
|
102
|
+
'--experimental',
|
|
103
|
+
default=False,
|
|
104
|
+
is_flag=True,
|
|
105
|
+
hidden=True,
|
|
106
|
+
help="Enable experimental features.",
|
|
107
|
+
)
|
|
108
|
+
@click.option(
|
|
109
|
+
"--company-id",
|
|
110
|
+
required=False,
|
|
111
|
+
envvar=("CONVISO_COMPANY_ID", "FLOW_COMPANY_ID"),
|
|
112
|
+
help="Company ID on Conviso Platform",
|
|
113
|
+
)
|
|
114
|
+
@click.option(
|
|
115
|
+
'--asset-name',
|
|
116
|
+
required=False,
|
|
117
|
+
envvar=("CONVISO_ASSET_NAME", "FLOW_ASSET_NAME"),
|
|
118
|
+
help="Provides a asset name.",
|
|
119
|
+
)
|
|
120
|
+
@click.option(
|
|
121
|
+
'--vulnerability-auto-close',
|
|
122
|
+
default=False,
|
|
123
|
+
is_flag=True,
|
|
124
|
+
hidden=True,
|
|
125
|
+
help="Enable auto fixing vulnerabilities on cp.",
|
|
126
|
+
)
|
|
127
|
+
@click.option(
|
|
128
|
+
'--from-ast',
|
|
129
|
+
default=False,
|
|
130
|
+
is_flag=True,
|
|
131
|
+
hidden=True,
|
|
132
|
+
help="Internal use only.",
|
|
133
|
+
)
|
|
134
|
+
@click.option(
|
|
135
|
+
'--cleanup',
|
|
136
|
+
default=False,
|
|
137
|
+
is_flag=True,
|
|
138
|
+
show_default=True,
|
|
139
|
+
help="Clean up system resources, including temporary files, stopped containers, unused Docker images and volumes.",
|
|
140
|
+
)
|
|
141
|
+
@click.option(
|
|
142
|
+
'--control-sync-status-id',
|
|
143
|
+
required=False,
|
|
144
|
+
hidden=True,
|
|
145
|
+
help="Control sync status id.",
|
|
146
|
+
)
|
|
147
|
+
@help_option
|
|
148
|
+
@pass_flow_context
|
|
149
|
+
@click.pass_context
|
|
150
|
+
def run(context, flow_context, asset_id, company_id, end_commit, start_commit, repository_dir, send_to_flow, deploy_id,
|
|
151
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login, fail_on_threshold,
|
|
152
|
+
fail_on_severity_threshold, experimental, asset_name, vulnerability_auto_close, from_ast, cleanup,
|
|
153
|
+
control_sync_status_id):
|
|
154
|
+
|
|
155
|
+
if context.params.get('cleanup'):
|
|
156
|
+
try:
|
|
157
|
+
log_func("Cleaning before runs auto close ...")
|
|
158
|
+
cleaner = RunnerCleanup()
|
|
159
|
+
cleaner.cleanup_all()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
log_func(f"Failed to clean, trying any way ... Error: {str(e)}")
|
|
162
|
+
|
|
163
|
+
if not from_ast:
|
|
164
|
+
prepared_context = RequirementsVerifier.prepare_context(context)
|
|
165
|
+
|
|
166
|
+
params_to_copy = [
|
|
167
|
+
'asset_id', 'company_id', 'start_commit', 'end_commit',
|
|
168
|
+
'repository_dir', 'send_to_flow', 'deploy_id', 'sastbox_registry',
|
|
169
|
+
'sastbox_repository_name', 'sastbox_tag', 'sastbox_skip_login',
|
|
170
|
+
'experimental', 'asset_name', 'vulnerability_auto_close', 'company_id'
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
for param_name in params_to_copy:
|
|
174
|
+
context.params[param_name] = (
|
|
175
|
+
locals()[param_name] or prepared_context.params[param_name]
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
conviso_api = flow_context.create_conviso_api_client_beta()
|
|
179
|
+
company_id = company_id or context.params['company_id']
|
|
180
|
+
asset_id = context.params['asset_id']
|
|
181
|
+
statuses = ['CREATED', 'IDENTIFIED', 'IN_PROGRESS', 'AWAITING_VALIDATION', 'FIX_ACCEPTED']
|
|
182
|
+
|
|
183
|
+
page = 1
|
|
184
|
+
merged_issues_sast = []
|
|
185
|
+
merged_issues_sca = []
|
|
186
|
+
|
|
187
|
+
log_func("Running automatic closure of vulnerabilities...")
|
|
188
|
+
|
|
189
|
+
while True:
|
|
190
|
+
try:
|
|
191
|
+
issues_from_cp = conviso_api.issues.auto_close_vulnerabilities(
|
|
192
|
+
company_id, asset_id, statuses, page
|
|
193
|
+
)
|
|
194
|
+
except ResponseError as error:
|
|
195
|
+
if 'Variable $company_id' in str(error):
|
|
196
|
+
log_func(f"Invalid company_id passed: {company_id}", fg='red')
|
|
197
|
+
else:
|
|
198
|
+
log_func(f"error: {error}", fg='red')
|
|
199
|
+
|
|
200
|
+
log_func("⚠️ Auto-close will not be performed at this time. Please set it using --company-id and try again.", fg='red')
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
total_pages = issues_from_cp['metadata']['totalPages']
|
|
204
|
+
issues_collection = issues_from_cp['collection']
|
|
205
|
+
issues_collection = [item for item in issues_collection if item['scanSource'] == 'conviso_ast']
|
|
206
|
+
sast_issues = [item for item in issues_collection if item['type'] == 'SAST_FINDING']
|
|
207
|
+
sca_issues = [item for item in issues_collection if item['type'] == 'SCA_FINDING']
|
|
208
|
+
|
|
209
|
+
# could not be if and else because on the response could contain both.
|
|
210
|
+
if sast_issues:
|
|
211
|
+
merged_issues_sast.extend(sast_issues)
|
|
212
|
+
|
|
213
|
+
if sca_issues:
|
|
214
|
+
merged_issues_sca.extend(sca_issues)
|
|
215
|
+
|
|
216
|
+
if total_pages == 0 or total_pages == page:
|
|
217
|
+
break
|
|
218
|
+
else:
|
|
219
|
+
page += 1
|
|
220
|
+
|
|
221
|
+
# sast issues filter
|
|
222
|
+
sast_issues_with_fix_accepted = [item for item in merged_issues_sast if item['status'] == 'FIX_ACCEPTED']
|
|
223
|
+
sast_issues_without_fix_accepted = [item for item in merged_issues_sast if item['status'] != 'FIX_ACCEPTED']
|
|
224
|
+
|
|
225
|
+
# sca issues filter
|
|
226
|
+
sca_issues_with_fix_accepted = [item for item in merged_issues_sca if item['status'] == 'FIX_ACCEPTED']
|
|
227
|
+
sca_issues_without_fix_accepted = [item for item in merged_issues_sca if item['status'] != 'FIX_ACCEPTED']
|
|
228
|
+
|
|
229
|
+
if len(issues_from_cp) == 0:
|
|
230
|
+
log_func("No vulnerabilities were found on the Conviso Platform!")
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Starting executing the ast again
|
|
234
|
+
sast_hash_issues = perform_sastbox_scan(sastbox_registry, sastbox_repository_name, sastbox_tag, repository_dir)
|
|
235
|
+
|
|
236
|
+
sca_hash_issues = perform_sca_scan(repository_dir=repository_dir)
|
|
237
|
+
iac_hash_issues = perform_iac_scan(repository_dir=repository_dir)
|
|
238
|
+
|
|
239
|
+
# we need to append the two lists because at the moment this was made, iac and sast has sast as type on cp.
|
|
240
|
+
sast_hash_issues = sast_hash_issues + iac_hash_issues
|
|
241
|
+
# end ast execution
|
|
242
|
+
|
|
243
|
+
set_of_sast_hash_issues = set(sast_hash_issues)
|
|
244
|
+
set_of_sca_hash_issues = set(sca_hash_issues)
|
|
245
|
+
|
|
246
|
+
close_sast_issues(conviso_api, sast_issues_without_fix_accepted, set_of_sast_hash_issues, control_sync_status_id)
|
|
247
|
+
close_sca_issues(conviso_api, sca_issues_without_fix_accepted, set_of_sca_hash_issues, control_sync_status_id)
|
|
248
|
+
|
|
249
|
+
sast_issues_to_reopen = [
|
|
250
|
+
{'id': item['id'], 'originalIssueIdFromTool': item['originalIssueIdFromTool']}
|
|
251
|
+
for item in sast_issues_with_fix_accepted if item['originalIssueIdFromTool'] in sast_hash_issues
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
sca_issues_to_reopen = [
|
|
255
|
+
{'id': item['id'], 'originalIssueIdFromTool': item['originalIssueIdFromTool']}
|
|
256
|
+
for item in sca_issues_with_fix_accepted if item['originalIssueIdFromTool'] in sca_hash_issues
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
if sast_issues_to_reopen:
|
|
260
|
+
log_func("SAST: reopening {issues} vulnerability/vulnerabilities on conviso platform ...".format(
|
|
261
|
+
issues=len(sast_issues_to_reopen))
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
reopen_issues(conviso_api, sast_issues_to_reopen, control_sync_status_id)
|
|
265
|
+
|
|
266
|
+
if sca_issues_to_reopen:
|
|
267
|
+
log_func("SCA: reopening {issues} vulnerability/vulnerabilities on conviso platform ...".format(
|
|
268
|
+
issues=len(sca_issues_to_reopen))
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
reopen_issues(conviso_api, sca_issues_to_reopen, control_sync_status_id)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def close_sast_issues(conviso_api, issues_from_cp, issues_from_current_scan, control_sync_status_id):
|
|
275
|
+
"""
|
|
276
|
+
method to close sast issues on conviso platform
|
|
277
|
+
|
|
278
|
+
# issues_from_cp are issues already on conviso platform.
|
|
279
|
+
# issues_from_current_scan are issues identify on each time the ast command runs, these are always from a full code
|
|
280
|
+
base scan
|
|
281
|
+
"""
|
|
282
|
+
log_func("SAST: Verifying if any vulnerability was solved...")
|
|
283
|
+
|
|
284
|
+
differences = [
|
|
285
|
+
{'id': item['id'], 'originalIssueIdFromTool': item['originalIssueIdFromTool']}
|
|
286
|
+
for item in issues_from_cp if item['originalIssueIdFromTool'] not in issues_from_current_scan
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
if len(differences) == 0:
|
|
290
|
+
log_func("No vulnerabilities have been fixed yet...")
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
log_func("SAST: Fixing {issues} vulnerabilities on the Conviso Platform...".format(issues=len(differences)))
|
|
294
|
+
|
|
295
|
+
for issue in differences:
|
|
296
|
+
issue_id = issue['id']
|
|
297
|
+
status = 'FIX_ACCEPTED'
|
|
298
|
+
reason = ("The vulnerability is no longer found in the specified file/path. Its status has been updated by "
|
|
299
|
+
"Conviso AST")
|
|
300
|
+
|
|
301
|
+
conviso_api.issues.update_issue_status(
|
|
302
|
+
issue_id=issue_id, status=status, reason=reason, control_sync_status_id=control_sync_status_id
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def close_sca_issues(conviso_api, issues_from_cp, issues_from_current_scan, control_sync_status_id):
|
|
307
|
+
""" method to close sca issues on conviso platform """
|
|
308
|
+
|
|
309
|
+
log_func("SCA: Verifying if any vulnerability was solved...")
|
|
310
|
+
differences = [
|
|
311
|
+
{'id': item['id'], 'originalIssueIdFromTool': item['originalIssueIdFromTool']}
|
|
312
|
+
for item in issues_from_cp if item['originalIssueIdFromTool'] not in issues_from_current_scan
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
if len(differences) == 0:
|
|
316
|
+
log_func("No vulnerabilities have been fixed yet...")
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
log_func("SCA: Fixing {issues} vulnerabilities on the Conviso Platform...".format(issues=len(differences)))
|
|
320
|
+
|
|
321
|
+
for issue in differences:
|
|
322
|
+
issue_id = issue['id']
|
|
323
|
+
status = 'FIX_ACCEPTED'
|
|
324
|
+
reason = ("The vulnerability is no longer found in the specified file/path. Its status has been updated by "
|
|
325
|
+
"Conviso AST")
|
|
326
|
+
|
|
327
|
+
conviso_api.issues.update_issue_status(
|
|
328
|
+
issue_id=issue_id, status=status, reason=reason, control_sync_status_id=control_sync_status_id
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def reopen_issues(conviso_api, issues, control_sync_status_id):
|
|
333
|
+
""" Method to reopen issues when was detected again after a full scan is performed """
|
|
334
|
+
|
|
335
|
+
for issue in issues:
|
|
336
|
+
issue_id = issue['id']
|
|
337
|
+
status = 'IDENTIFIED'
|
|
338
|
+
reason = 'Status has been updated from Fixed to Identified by Conviso AST'
|
|
339
|
+
|
|
340
|
+
conviso_api.issues.update_issue_status(
|
|
341
|
+
issue_id=issue_id, status=status, reason=reason, control_sync_status_id=control_sync_status_id
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def perform_sastbox_scan(sastbox_registry, sastbox_repository_name, sastbox_tag, repository_dir):
|
|
345
|
+
LOGGER.info(' 🔍 [SAST] Running scan...')
|
|
346
|
+
|
|
347
|
+
sastbox = SASTBox(registry=sastbox_registry, repository_name=sastbox_repository_name, tag=sastbox_tag)
|
|
348
|
+
git_adapter = GitAdapter(repository_dir, unshallow_repository=False)
|
|
349
|
+
|
|
350
|
+
reports = sastbox.run_scan_diff(
|
|
351
|
+
repository_dir,
|
|
352
|
+
git_adapter.head_commit,
|
|
353
|
+
git_adapter.empty_repository_tree_commit
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
first_report = next(reports)
|
|
358
|
+
with open(str(first_report), 'r') as f:
|
|
359
|
+
issues = json.load(f).get("issues", [])
|
|
360
|
+
|
|
361
|
+
return [
|
|
362
|
+
issue.get("hash_issue") or issue.get("hash_issue_v2")
|
|
363
|
+
for issue in issues
|
|
364
|
+
if issue.get("hash_issue") or issue.get("hash_issue_v2")
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
except (StopIteration, Exception) as e:
|
|
368
|
+
if isinstance(e, StopIteration):
|
|
369
|
+
return []
|
|
370
|
+
return []
|
|
371
|
+
|
|
372
|
+
@pass_flow_context
|
|
373
|
+
def perform_sca_scan(flow_context, repository_dir):
|
|
374
|
+
LOGGER.info(' 🔍 [SCA] Running scan...')
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
REQUIRED_CODEBASE_PATH = '/code'
|
|
378
|
+
OSV_SCANNER_IMAGE_NAME = 'osv_scanner'
|
|
379
|
+
|
|
380
|
+
scanners = {
|
|
381
|
+
OSV_SCANNER_IMAGE_NAME: {
|
|
382
|
+
'repository_name': OSV_SCANNER_IMAGE_NAME,
|
|
383
|
+
'tag': 'latest',
|
|
384
|
+
'command': [
|
|
385
|
+
'-c', REQUIRED_CODEBASE_PATH,
|
|
386
|
+
'-f', 'json',
|
|
387
|
+
'-o', '/{}.json'.format(OSV_SCANNER_IMAGE_NAME)
|
|
388
|
+
],
|
|
389
|
+
'repository_dir': repository_dir
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
conviso_rest_api = flow_context.create_conviso_rest_api_client()
|
|
394
|
+
token = conviso_rest_api.docker_registry.get_sast_token()
|
|
395
|
+
scabox = ContainerWrapper(
|
|
396
|
+
token=token,
|
|
397
|
+
containers_map=scanners,
|
|
398
|
+
logger=None,
|
|
399
|
+
timeout=7200
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
scabox.run()
|
|
403
|
+
|
|
404
|
+
results_filepaths = []
|
|
405
|
+
for unit in scabox.scanners:
|
|
406
|
+
file_path = unit.results
|
|
407
|
+
if file_path:
|
|
408
|
+
results_filepaths.append(file_path)
|
|
409
|
+
|
|
410
|
+
hash_issues = []
|
|
411
|
+
|
|
412
|
+
for report_path in results_filepaths:
|
|
413
|
+
try:
|
|
414
|
+
with open(report_path, 'r') as report_file:
|
|
415
|
+
report_content = json.load(report_file)
|
|
416
|
+
issues = report_content.get("issues", [])
|
|
417
|
+
hash_issues.extend(issue.get("hash_issue") for issue in issues)
|
|
418
|
+
|
|
419
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
420
|
+
print(f"Error processing {report_path}: {e}")
|
|
421
|
+
|
|
422
|
+
return hash_issues
|
|
423
|
+
|
|
424
|
+
except Exception as e:
|
|
425
|
+
on_http_error(e)
|
|
426
|
+
raise click.ClickException(str(e)) from e
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@pass_flow_context
|
|
430
|
+
def perform_iac_scan(flow_context, repository_dir):
|
|
431
|
+
""" Perform an iac scan """
|
|
432
|
+
LOGGER.info(' 🔍 [IaC] Running scan...')
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
REQUIRED_CODEBASE_PATH = '/code'
|
|
436
|
+
IAC_IMAGE_NAME = 'iac_scanner_checkov'
|
|
437
|
+
IAC_SCAN_FILENAME = '/{}.json'.format(IAC_IMAGE_NAME)
|
|
438
|
+
containers_map = {
|
|
439
|
+
IAC_IMAGE_NAME: {
|
|
440
|
+
'repository_dir': repository_dir,
|
|
441
|
+
'repository_name': IAC_IMAGE_NAME,
|
|
442
|
+
'tag': 'unstable',
|
|
443
|
+
'command': [
|
|
444
|
+
'-c', REQUIRED_CODEBASE_PATH,
|
|
445
|
+
'-o', IAC_SCAN_FILENAME,
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
conviso_rest_api = flow_context.create_conviso_rest_api_client()
|
|
451
|
+
token = conviso_rest_api.docker_registry.get_sast_token()
|
|
452
|
+
scanners_wrapper = ContainerWrapper(
|
|
453
|
+
token=token,
|
|
454
|
+
containers_map=containers_map,
|
|
455
|
+
logger=None,
|
|
456
|
+
timeout=7200
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
scanners_wrapper.run()
|
|
460
|
+
|
|
461
|
+
results_filepaths = [] # [str(r.results) for r in scanners_wrapper.scanners]
|
|
462
|
+
hash_issues = []
|
|
463
|
+
for r in scanners_wrapper.scanners:
|
|
464
|
+
report_filepath = r.results
|
|
465
|
+
if report_filepath:
|
|
466
|
+
results_filepaths.append(report_filepath)
|
|
467
|
+
|
|
468
|
+
for report_path in results_filepaths:
|
|
469
|
+
try:
|
|
470
|
+
with open(report_path) as report_file:
|
|
471
|
+
report_content = json.load(report_file)
|
|
472
|
+
results = report_content.get("runs", [])[0].get("results", [])
|
|
473
|
+
for result in results:
|
|
474
|
+
partial_fingerprints = result.get("partialFingerprints", {})
|
|
475
|
+
hash_issue = partial_fingerprints.get("hashIssueV2")
|
|
476
|
+
if hash_issue:
|
|
477
|
+
hash_issues.append(hash_issue)
|
|
478
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
479
|
+
print(f"Error processing {report_path}: {e}")
|
|
480
|
+
|
|
481
|
+
return hash_issues
|
|
482
|
+
except Exception as e:
|
|
483
|
+
on_http_error(e)
|
|
484
|
+
raise click.ClickException(str(e)) from e
|
|
485
|
+
|
|
486
|
+
def log_func(msg, new_line=True, fg='blue'):
|
|
487
|
+
click.echo(click.style(msg, bold=True, fg=fg), nl=new_line, err=True)
|
convisoappsec/logger.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logging.basicConfig(
|
|
4
|
+
filename='output.log',
|
|
5
|
+
filemode='w',
|
|
6
|
+
level=logging.DEBUG,
|
|
7
|
+
format='%(levelname)s: %(filename)s: %(threadName)s: %(name)s: %(message)s'
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
LOGGER = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def log_and_notify_ast_event(flow_context, company_id, asset_id, ast_log):
|
|
14
|
+
"""
|
|
15
|
+
Logs an AST (Application Security Test) event for a specific company and asset,
|
|
16
|
+
and sends notifications.
|
|
17
|
+
|
|
18
|
+
This method performs the following actions:
|
|
19
|
+
1. Sends the provided AST log to a monitoring platform like Datadog for logging and tracking.
|
|
20
|
+
2. Sends a Slack notification with relevant details about the AST event.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
flow_context: The context of the AST execution.
|
|
24
|
+
company_id (int): The ID of the company for which the AST event is being logged.
|
|
25
|
+
asset_id (int): The ID of the asset associated with the AST event.
|
|
26
|
+
ast_log (string): A string with the log details of the AST event.
|
|
27
|
+
"""
|
|
28
|
+
conviso_api = flow_context.create_conviso_graphql_client()
|
|
29
|
+
conviso_api.ast_errors.send_ast_error(company_id = company_id, asset_id = asset_id, log = ast_log)
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
class Severity(IntEnum):
|
|
5
|
+
# Sastbox [:undefined, :info, :low, :medium, :high, :critical]
|
|
6
|
+
UNDEFINED = 0
|
|
7
|
+
INFO = 1
|
|
8
|
+
LOW = 2
|
|
9
|
+
MEDIUM = 3
|
|
10
|
+
HIGH = 4
|
|
11
|
+
CRITICAL = 5
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def has_value(cls, value):
|
|
15
|
+
return value in cls._member_names_
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Decision:
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def __init__(self, report):
|
|
23
|
+
self.report = open(report)
|
|
24
|
+
self.json = json.load(self.report)
|
|
25
|
+
self.issues = self.json.get('issues')
|
|
26
|
+
self.sev = self.issues
|
|
27
|
+
|
|
28
|
+
def block_from_severity(self, severity, threshold=1):
|
|
29
|
+
if not severity:
|
|
30
|
+
return False
|
|
31
|
+
severity = Severity[severity]
|
|
32
|
+
self.sev = [issue for issue in self.issues if Severity[issue.get('severity').upper()] >= severity]
|
|
33
|
+
return len(self.sev) >= threshold
|
|
34
|
+
|
|
35
|
+
def block_from_findings(self, threshold):
|
|
36
|
+
if not threshold:
|
|
37
|
+
return False
|
|
38
|
+
return len(self.issues) >= threshold
|
|
39
|
+
|
|
40
|
+
def filtered_issues(self):
|
|
41
|
+
return self.sev
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|