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,485 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
import traceback
|
|
4
|
+
import time
|
|
5
|
+
import json
|
|
6
|
+
from copy import deepcopy as clone
|
|
7
|
+
from base64 import b64decode
|
|
8
|
+
from re import search as regex_search
|
|
9
|
+
from convisoappsec.flow import GitAdapter
|
|
10
|
+
from convisoappsec.flowcli import help_option
|
|
11
|
+
from convisoappsec.flowcli.common import (asset_id_option, on_http_error)
|
|
12
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
13
|
+
from convisoappsec.sast.decision import Decision, Severity
|
|
14
|
+
from convisoappsec.sast.sastbox import SASTBox
|
|
15
|
+
from convisoappsec.flowcli.requirements_verifier import RequirementsVerifier
|
|
16
|
+
from docker.errors import APIError
|
|
17
|
+
from convisoappsec.logger import LOGGER
|
|
18
|
+
from convisoappsec.common.cleaner import Cleaner
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def log_func(msg, new_line=True):
|
|
22
|
+
click.echo(msg, nl=new_line, err=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def find_blocked_issues(
|
|
26
|
+
results_filepaths, overall_threshold, severity_threshold, severity
|
|
27
|
+
):
|
|
28
|
+
blocked_issues = []
|
|
29
|
+
|
|
30
|
+
for result_path in results_filepaths:
|
|
31
|
+
report_decision = Decision(result_path)
|
|
32
|
+
|
|
33
|
+
overall_policy = report_decision.block_from_findings(overall_threshold)
|
|
34
|
+
severity_policy = report_decision.block_from_severity(
|
|
35
|
+
severity, severity_threshold
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if overall_policy or severity_policy:
|
|
39
|
+
blocked_issues.append(report_decision)
|
|
40
|
+
click.echo(
|
|
41
|
+
"Failing execution due one or more blocking flags", file=sys.stderr
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
click.echo(result_path)
|
|
45
|
+
|
|
46
|
+
return blocked_issues
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def print_blocked_issues(blocked_issues):
|
|
50
|
+
for issue_decision in blocked_issues:
|
|
51
|
+
issues = issue_decision.filtered_issues()
|
|
52
|
+
|
|
53
|
+
for issue in issues:
|
|
54
|
+
click.echo(
|
|
55
|
+
"{issue_name}\n{filepath} at line {line_index}\n".format(
|
|
56
|
+
issue_name=issue["title"],
|
|
57
|
+
filepath=issue["filename"],
|
|
58
|
+
line_index=issue["line"],
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def perform_sastbox_scan(
|
|
65
|
+
conviso_rest_api, sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login, repository_dir, end_commit, start_commit, logger
|
|
66
|
+
):
|
|
67
|
+
|
|
68
|
+
max_retries = 5
|
|
69
|
+
retries = 0
|
|
70
|
+
sastbox = SASTBox(registry=sastbox_registry, repository_name=sastbox_repository_name, tag=sastbox_tag)
|
|
71
|
+
pull_progress_bar = click.progressbar(length=sastbox.size, label="Performing SAST download...")
|
|
72
|
+
|
|
73
|
+
while retries < max_retries:
|
|
74
|
+
try:
|
|
75
|
+
if not sastbox_skip_login:
|
|
76
|
+
logger("Checking SASTBox authorization...")
|
|
77
|
+
token = conviso_rest_api.docker_registry.get_sast_token()
|
|
78
|
+
sastbox.login(token)
|
|
79
|
+
|
|
80
|
+
with pull_progress_bar as progressbar:
|
|
81
|
+
for downloaded_chunk in sastbox.pull():
|
|
82
|
+
progressbar.update(downloaded_chunk)
|
|
83
|
+
break
|
|
84
|
+
except APIError as e:
|
|
85
|
+
retries += 1
|
|
86
|
+
logger(f"Retrying {retries}/{max_retries}...")
|
|
87
|
+
time.sleep(1)
|
|
88
|
+
|
|
89
|
+
if retries == max_retries:
|
|
90
|
+
logger("Max retries reached. Failed to perform SAST download.")
|
|
91
|
+
raise Exception(f"Max retries reached. Could not complete the SAST download. Error: {str(e)}")
|
|
92
|
+
|
|
93
|
+
logger("Starting SAST scan diff...")
|
|
94
|
+
|
|
95
|
+
reports = sastbox.run_scan_diff(repository_dir, end_commit, start_commit, log=logger)
|
|
96
|
+
|
|
97
|
+
logger("SAST scan diff done.")
|
|
98
|
+
|
|
99
|
+
results_filepaths = []
|
|
100
|
+
for r in reports:
|
|
101
|
+
try:
|
|
102
|
+
file_path = str(r)
|
|
103
|
+
results_filepaths.append(file_path)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
click.echo(f"Error decoding file path: {r} with error {e}.", file=sys.stderr)
|
|
106
|
+
|
|
107
|
+
return results_filepaths
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_conviso_references(references=[]):
|
|
111
|
+
divider = "\n"
|
|
112
|
+
|
|
113
|
+
references_to_join = []
|
|
114
|
+
|
|
115
|
+
for reference in references:
|
|
116
|
+
if reference:
|
|
117
|
+
references_to_join.append(reference)
|
|
118
|
+
|
|
119
|
+
return divider.join(references_to_join)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def parse_code_snippet(encoded_base64):
|
|
123
|
+
try:
|
|
124
|
+
decoded_text = b64decode(encoded_base64).decode("utf-8")
|
|
125
|
+
except UnicodeDecodeError:
|
|
126
|
+
try:
|
|
127
|
+
decoded_text = b64decode(encoded_base64, validate=False).decode("latin-1")
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print("Error handling decoding error:", e, file=sys.stderr)
|
|
130
|
+
decoded_text = ''
|
|
131
|
+
|
|
132
|
+
lines = decoded_text.split("\n")
|
|
133
|
+
|
|
134
|
+
cleaned_lines = []
|
|
135
|
+
for line in lines:
|
|
136
|
+
cleaned_line = line.split(": ", 1)[-1]
|
|
137
|
+
cleaned_lines.append(cleaned_line)
|
|
138
|
+
|
|
139
|
+
code_snippet = "\n".join(cleaned_lines)
|
|
140
|
+
|
|
141
|
+
return code_snippet
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def parse_first_line_number(encoded_base64):
|
|
145
|
+
try:
|
|
146
|
+
decoded_text = b64decode(encoded_base64).decode("utf-8")
|
|
147
|
+
except UnicodeDecodeError:
|
|
148
|
+
try:
|
|
149
|
+
decoded_text = b64decode(encoded_base64, validate=False).decode("latin-1")
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print("Error handling decoding error:", e, file=sys.stderr)
|
|
152
|
+
decoded_text = ''
|
|
153
|
+
|
|
154
|
+
regex = r"^(\d+):"
|
|
155
|
+
|
|
156
|
+
result = regex_search(regex, decoded_text)
|
|
157
|
+
|
|
158
|
+
if result and result.group(1):
|
|
159
|
+
return result.group(1)
|
|
160
|
+
|
|
161
|
+
line_number_when_not_found = 1
|
|
162
|
+
return line_number_when_not_found
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def deploy_results_to_conviso_beta(
|
|
166
|
+
flow_context, results_filepaths, asset_id, company_id, commit_ref=None, deploy_id=None, control_sync_status_id=None
|
|
167
|
+
):
|
|
168
|
+
"""Send SAST results to the Conviso platform."""
|
|
169
|
+
|
|
170
|
+
results_context = click.progressbar(
|
|
171
|
+
results_filepaths, label="🔧 Sending SAST report to the Conviso Platform ..."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
with results_context as reports:
|
|
175
|
+
for report_path in reports:
|
|
176
|
+
api_key = flow_context.key
|
|
177
|
+
max_retries = 5
|
|
178
|
+
conviso_api = flow_context.create_conviso_graphql_client()
|
|
179
|
+
|
|
180
|
+
for attempt in range(1, max_retries + 1):
|
|
181
|
+
try:
|
|
182
|
+
conviso_api.issues.send_issues_file(
|
|
183
|
+
company_id=company_id, asset_id=asset_id, file_path=report_path, api_key=api_key,
|
|
184
|
+
vulnerability_type='SAST_FINDING',
|
|
185
|
+
deploy_id=deploy_id, commit_ref=commit_ref, control_sync_status_id=control_sync_status_id
|
|
186
|
+
)
|
|
187
|
+
break
|
|
188
|
+
except Exception as e:
|
|
189
|
+
if attempt == max_retries:
|
|
190
|
+
print(f"Failed after {max_retries} attempts: {e}")
|
|
191
|
+
else:
|
|
192
|
+
wait_time = 2 ** attempt # Exponential backoff (2s, 4s, 8s, 16s)
|
|
193
|
+
print(f"Retry {attempt}/{max_retries} failed. Retrying in {wait_time} seconds...")
|
|
194
|
+
time.sleep(wait_time)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@click.command()
|
|
198
|
+
@asset_id_option(required=False)
|
|
199
|
+
@click.option(
|
|
200
|
+
"-s",
|
|
201
|
+
"--start-commit",
|
|
202
|
+
required=False,
|
|
203
|
+
help="If no value is set so the empty tree hash commit is used.",
|
|
204
|
+
)
|
|
205
|
+
@click.option(
|
|
206
|
+
"-e",
|
|
207
|
+
"--end-commit",
|
|
208
|
+
required=False,
|
|
209
|
+
help="""If no value is set so the HEAD commit
|
|
210
|
+
from the current branch is used""",
|
|
211
|
+
)
|
|
212
|
+
@click.option(
|
|
213
|
+
"-r",
|
|
214
|
+
"--repository-dir",
|
|
215
|
+
default=".",
|
|
216
|
+
show_default=True,
|
|
217
|
+
type=click.Path(
|
|
218
|
+
exists=True,
|
|
219
|
+
resolve_path=True,
|
|
220
|
+
),
|
|
221
|
+
required=False,
|
|
222
|
+
help="The source code repository directory.",
|
|
223
|
+
)
|
|
224
|
+
@click.option(
|
|
225
|
+
"--fail-on-severity-threshold",
|
|
226
|
+
required=False,
|
|
227
|
+
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 \
|
|
228
|
+
The severity levels are: UNDEFINED, INFO, LOW, MEDIUM, HIGH, CRITICAL.",
|
|
229
|
+
type=click.Tuple([str, int]),
|
|
230
|
+
default=(None, None),
|
|
231
|
+
)
|
|
232
|
+
@click.option(
|
|
233
|
+
"--fail-on-threshold",
|
|
234
|
+
required=False,
|
|
235
|
+
help="If the threshold has reach then the command will fail after send the result to AppSec Flow",
|
|
236
|
+
type=int,
|
|
237
|
+
default=False,
|
|
238
|
+
)
|
|
239
|
+
@click.option(
|
|
240
|
+
"--send-to-flow/--no-send-to-flow",
|
|
241
|
+
default=True,
|
|
242
|
+
show_default=True,
|
|
243
|
+
required=False,
|
|
244
|
+
hidden=True,
|
|
245
|
+
help="""Enable or disable the ability of send analysis result
|
|
246
|
+
reports to flow.""",
|
|
247
|
+
)
|
|
248
|
+
@click.option(
|
|
249
|
+
"--deploy-id",
|
|
250
|
+
default=None,
|
|
251
|
+
required=False,
|
|
252
|
+
hidden=True,
|
|
253
|
+
envvar=("CONVISO_DEPLOY_ID", "FLOW_DEPLOY_ID")
|
|
254
|
+
)
|
|
255
|
+
@click.option(
|
|
256
|
+
"--sastbox-registry",
|
|
257
|
+
default="",
|
|
258
|
+
required=False,
|
|
259
|
+
hidden=True,
|
|
260
|
+
envvar=("CONVISO_SASTBOX_REGISTRY", "FLOW_SASTBOX_REGISTRY"),
|
|
261
|
+
)
|
|
262
|
+
@click.option(
|
|
263
|
+
"--sastbox-repository-name",
|
|
264
|
+
default="",
|
|
265
|
+
required=False,
|
|
266
|
+
hidden=True,
|
|
267
|
+
envvar=("CONVISO_SASTBOX_REPOSITORY_NAME", "FLOW_SASTBOX_REPOSITORY_NAME"),
|
|
268
|
+
)
|
|
269
|
+
@click.option(
|
|
270
|
+
"--sastbox-tag",
|
|
271
|
+
default=SASTBox.DEFAULT_TAG,
|
|
272
|
+
required=False,
|
|
273
|
+
hidden=True,
|
|
274
|
+
envvar=("CONVISO_SASTBOX_TAG", "FLOW_SASTBOX_TAG"),
|
|
275
|
+
)
|
|
276
|
+
@click.option(
|
|
277
|
+
"--sastbox-skip-login/--sastbox-no-skip-login",
|
|
278
|
+
default=False,
|
|
279
|
+
required=False,
|
|
280
|
+
hidden=True,
|
|
281
|
+
envvar=("CONVISO_SASTBOX_SKIP_LOGIN", "FLOW_SASTBOX_SKIP_LOGIN"),
|
|
282
|
+
)
|
|
283
|
+
@click.option(
|
|
284
|
+
'--experimental',
|
|
285
|
+
default=False,
|
|
286
|
+
is_flag=True,
|
|
287
|
+
hidden=True,
|
|
288
|
+
help="Enable experimental features.",
|
|
289
|
+
)
|
|
290
|
+
@click.option(
|
|
291
|
+
"--company-id",
|
|
292
|
+
required=False,
|
|
293
|
+
envvar=("CONVISO_COMPANY_ID", "FLOW_COMPANY_ID"),
|
|
294
|
+
help="Company ID on Conviso Platform",
|
|
295
|
+
)
|
|
296
|
+
@click.option(
|
|
297
|
+
'--asset-name',
|
|
298
|
+
required=False,
|
|
299
|
+
envvar=("CONVISO_ASSET_NAME", "FLOW_ASSET_NAME"),
|
|
300
|
+
help="Provides a asset name.",
|
|
301
|
+
)
|
|
302
|
+
@click.option(
|
|
303
|
+
'--vulnerability-auto-close',
|
|
304
|
+
default=False,
|
|
305
|
+
is_flag=True,
|
|
306
|
+
hidden=True,
|
|
307
|
+
help="Enable auto fixing vulnerabilities on cp.",
|
|
308
|
+
)
|
|
309
|
+
@click.option(
|
|
310
|
+
'--from-ast',
|
|
311
|
+
default=False,
|
|
312
|
+
is_flag=True,
|
|
313
|
+
hidden=True,
|
|
314
|
+
help="Internal use only.",
|
|
315
|
+
)
|
|
316
|
+
@click.option(
|
|
317
|
+
'--cleanup',
|
|
318
|
+
default=False,
|
|
319
|
+
is_flag=True,
|
|
320
|
+
show_default=True,
|
|
321
|
+
help="Clean up system resources, including temporary files, stopped containers, unused Docker images and volumes.",
|
|
322
|
+
)
|
|
323
|
+
@click.option(
|
|
324
|
+
'--control-sync-status-id',
|
|
325
|
+
required=False,
|
|
326
|
+
hidden=True,
|
|
327
|
+
help="Control sync status id.",
|
|
328
|
+
)
|
|
329
|
+
@help_option
|
|
330
|
+
@pass_flow_context
|
|
331
|
+
@click.pass_context
|
|
332
|
+
def run(context, flow_context, asset_id, company_id, end_commit, start_commit, repository_dir, send_to_flow, deploy_id,
|
|
333
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login, fail_on_threshold, fail_on_severity_threshold,
|
|
334
|
+
experimental, asset_name, vulnerability_auto_close, from_ast, cleanup, control_sync_status_id):
|
|
335
|
+
"""
|
|
336
|
+
This command will perform SAST analysis at the source code. The analysis
|
|
337
|
+
results can be reported or not to flow application. The analysis can be
|
|
338
|
+
applied to specific commit range.
|
|
339
|
+
|
|
340
|
+
This command will write the analysis reports files paths to stdout.
|
|
341
|
+
"""
|
|
342
|
+
if not from_ast:
|
|
343
|
+
prepared_context = RequirementsVerifier.prepare_context(clone(context))
|
|
344
|
+
|
|
345
|
+
params_to_copy = [
|
|
346
|
+
'asset_id', 'company_id', 'start_commit', 'end_commit',
|
|
347
|
+
'repository_dir', 'send_to_flow', 'deploy_id', 'sastbox_registry',
|
|
348
|
+
'sastbox_repository_name', 'sastbox_tag', 'sastbox_skip_login',
|
|
349
|
+
'experimental', 'asset_name', 'vulnerability_auto_close', 'company_id',
|
|
350
|
+
'cleanup'
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
for param_name in params_to_copy:
|
|
354
|
+
context.params[param_name] = (
|
|
355
|
+
locals()[param_name] or prepared_context.params[param_name]
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
perform_command(context, flow_context, context.params['asset_id'], context.params['deploy_id'], context.params['end_commit'],
|
|
359
|
+
context.params['start_commit'], context.params['repository_dir'], context.params['send_to_flow'], context.params['sastbox_registry'],
|
|
360
|
+
context.params['sastbox_repository_name'], context.params['sastbox_tag'], context.params['sastbox_skip_login'],
|
|
361
|
+
context.params['fail_on_threshold'], context.params['fail_on_severity_threshold'], context.params['experimental'],
|
|
362
|
+
context.params['company_id'], context.params['cleanup'], from_ast, control_sync_status_id)
|
|
363
|
+
|
|
364
|
+
def perform_command(context, flow_context, asset_id, deploy_id, end_commit, start_commit, repository_dir, send_to_flow, sastbox_registry, sastbox_repository_name, sastbox_tag,
|
|
365
|
+
sastbox_skip_login, fail_on_threshold, fail_on_severity_threshold, experimental, company_id, cleanup, from_ast, control_sync_status_id):
|
|
366
|
+
|
|
367
|
+
if send_to_flow and experimental and not asset_id:
|
|
368
|
+
raise click.MissingParameter(
|
|
369
|
+
"It is required when sending reports to Conviso Platform using experimental API.",
|
|
370
|
+
param_type="option",
|
|
371
|
+
param_hint="--asset-id",
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
severity, severity_threshold = fail_on_severity_threshold
|
|
375
|
+
overall_threshold = fail_on_threshold
|
|
376
|
+
|
|
377
|
+
if severity and not Severity.has_value(severity):
|
|
378
|
+
raise click.BadParameter(
|
|
379
|
+
"{} is not a valid Severity. Use a valid Severity value:\n{}".format(
|
|
380
|
+
severity, [severity.name for severity in Severity]
|
|
381
|
+
),
|
|
382
|
+
param_hint="--fail-on-severity-threshold",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
git_adapter = GitAdapter(repository_dir)
|
|
387
|
+
|
|
388
|
+
end_commit = end_commit or git_adapter.head_commit
|
|
389
|
+
|
|
390
|
+
start_commit = start_commit or git_adapter.empty_repository_tree_commit
|
|
391
|
+
|
|
392
|
+
if start_commit == end_commit:
|
|
393
|
+
click.echo(
|
|
394
|
+
"Previous commit ({0}) and current commit ({1}) are the same; nothing to do.".format(
|
|
395
|
+
start_commit, end_commit
|
|
396
|
+
),
|
|
397
|
+
file=sys.stderr,
|
|
398
|
+
)
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
conviso_rest_api = flow_context.create_conviso_rest_api_client()
|
|
402
|
+
|
|
403
|
+
results_filepaths = perform_sastbox_scan(
|
|
404
|
+
conviso_rest_api, sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login, repository_dir, end_commit, start_commit, log_func,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# this set total sast vulnerability found in sast scan to send to control sync status
|
|
408
|
+
context.params['sast_vulnerability_count'] = total_vulnerability_count(results_filepaths[0])
|
|
409
|
+
|
|
410
|
+
if send_to_flow:
|
|
411
|
+
deploy_results_to_conviso_beta(
|
|
412
|
+
flow_context, results_filepaths, asset_id, company_id, commit_ref=end_commit, deploy_id=deploy_id, control_sync_status_id=control_sync_status_id
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
blocked_issues = find_blocked_issues(
|
|
416
|
+
results_filepaths, overall_threshold, severity_threshold, severity
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
if blocked_issues:
|
|
420
|
+
print_blocked_issues(blocked_issues)
|
|
421
|
+
sys.exit(1)
|
|
422
|
+
|
|
423
|
+
if cleanup and from_ast == False:
|
|
424
|
+
LOGGER.info("🧹 Cleaning up ...")
|
|
425
|
+
cleaner = Cleaner()
|
|
426
|
+
cleaner.cleanup()
|
|
427
|
+
|
|
428
|
+
except Exception as e:
|
|
429
|
+
traceback.print_exc()
|
|
430
|
+
on_http_error(e)
|
|
431
|
+
raise click.ClickException(str(e)) from e
|
|
432
|
+
|
|
433
|
+
def total_vulnerability_count(file_path: str) -> int:
|
|
434
|
+
"""
|
|
435
|
+
Extract the total vulnerability count from a SAST scan result file.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
file_path (str): Path to JSON result file containing vulnerability scan results.
|
|
439
|
+
The file should have a 'summary' section with
|
|
440
|
+
'issues_count.total' field.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
int: Total number of vulnerabilities found in the scan result file.
|
|
444
|
+
Returns 0 if the file doesn't exist or no vulnerabilities are found.
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
449
|
+
data = json.load(f)
|
|
450
|
+
return data['summary']['issues_count']['total']
|
|
451
|
+
except (FileNotFoundError, KeyError, json.JSONDecodeError):
|
|
452
|
+
return 0
|
|
453
|
+
|
|
454
|
+
EPILOG = """
|
|
455
|
+
Examples:
|
|
456
|
+
|
|
457
|
+
\b
|
|
458
|
+
1 - Reporting the results to flow api:
|
|
459
|
+
1.1 - Running an analysis at all commit range:
|
|
460
|
+
$ export CONVISO_API_KEY='your-api-key'
|
|
461
|
+
$ {command}
|
|
462
|
+
|
|
463
|
+
\b
|
|
464
|
+
1.2 - Running an analysis at specific commit range:
|
|
465
|
+
$ export CONVISO_API_KEY='your-api-key'
|
|
466
|
+
$ {command} --start-commit "$(git rev-parse HEAD~5)" --end-commit "$(git rev-parse HEAD)"
|
|
467
|
+
|
|
468
|
+
\b
|
|
469
|
+
2 - Using flags to break the job on findings ocurrence:
|
|
470
|
+
2.1 - Running an analysis and break the build if there is 10 findings or more:
|
|
471
|
+
$ export CONVISO_API_KEY='your-api-key'
|
|
472
|
+
$ {command} --fail-on-threshold 10
|
|
473
|
+
\b
|
|
474
|
+
2.2 - Running an analysis and break the build if there is 5 findings with HIGH severity or higher
|
|
475
|
+
$ export CONVISO_API_KEY='your-api-key'
|
|
476
|
+
$ {command} --fail-on-severity-threshold HIGH 5
|
|
477
|
+
""" # noqa: E501
|
|
478
|
+
|
|
479
|
+
SHORT_HELP = "Perform SAST analysis"
|
|
480
|
+
|
|
481
|
+
command = "conviso sast run"
|
|
482
|
+
run.short_help = SHORT_HELP
|
|
483
|
+
run.epilog = EPILOG.format(
|
|
484
|
+
command=command,
|
|
485
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from convisoappsec.flowcli import help_option
|
|
4
|
+
from .generate import generate
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
@help_option
|
|
9
|
+
def sbom():
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
sbom.add_command(generate)
|
|
14
|
+
|
|
15
|
+
sbom.epilog = '''
|
|
16
|
+
Run conviso sbom COMMAND --help for more information on a command.
|
|
17
|
+
'''
|