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,427 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import traceback
|
|
3
|
+
import click
|
|
4
|
+
from convisoappsec.flowcli import help_option
|
|
5
|
+
from convisoappsec.flowcli.common import DeployFormatter, PerformDeployException, asset_id_option
|
|
6
|
+
from convisoappsec.flowcli.deploy.create.context import pass_create_context
|
|
7
|
+
from convisoappsec.flowcli.deploy.create.with_.values import values
|
|
8
|
+
from convisoappsec.flowcli.requirements_verifier import RequirementsVerifier
|
|
9
|
+
from convisoappsec.flowcli.sast import sast
|
|
10
|
+
from convisoappsec.flowcli.sca import sca
|
|
11
|
+
from convisoappsec.flowcli.iac import iac
|
|
12
|
+
from convisoappsec.flowcli.vulnerability import vulnerability
|
|
13
|
+
from copy import deepcopy as clone
|
|
14
|
+
from convisoappsec.flow import GitAdapter
|
|
15
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
16
|
+
from convisoappsec.logger import LOGGER, log_and_notify_ast_event
|
|
17
|
+
from convisoappsec.common.cleaner import Cleaner
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_default_params_values(cmd_params):
|
|
21
|
+
""" Further information in https://click.palletsprojects.com/en/8.1.x/api/?highlight=params#click.Command.params
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
cmd_params (List[click.core.Parameter]):
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
dict: default params values dictionarie
|
|
28
|
+
"""
|
|
29
|
+
default_params = {}
|
|
30
|
+
for param in cmd_params:
|
|
31
|
+
unwanted = param.name in ['help', 'verbosity']
|
|
32
|
+
if not unwanted:
|
|
33
|
+
default_params.update({param.name: param.default})
|
|
34
|
+
return default_params
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_params(ctx_params: dict, expected_params: list):
|
|
38
|
+
""" Parse the params from the context extracting the expected params values to the context.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
ctx_params (dict): context params: Further information at https://click.palletsprojects.com/en/8.1.x/api/?highlight=context%20param#click.Context.params
|
|
42
|
+
expected_params (list): Further information at https://click.palletsprojects.com/en/8.1.x/api/?highlight=params#click.Command.params
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
dict: parsed_params: parsed params as key and value
|
|
46
|
+
"""
|
|
47
|
+
parsed_params = get_default_params_values(expected_params)
|
|
48
|
+
for param in ctx_params:
|
|
49
|
+
if param in parsed_params:
|
|
50
|
+
parsed_params.update({param: ctx_params.get(param)})
|
|
51
|
+
return parsed_params
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def perform_sast(context, control_sync_status_id) -> int:
|
|
55
|
+
"""Setup and runs the "sast run" command.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
context (<class 'click.core.Context'>): cloned context
|
|
59
|
+
control_sync_status_id (str): control sync status id
|
|
60
|
+
"""
|
|
61
|
+
sast_run = sast.commands.get('run')
|
|
62
|
+
|
|
63
|
+
specific_params = {
|
|
64
|
+
"deploy_id": context.obj.deploy['deploy_id'],
|
|
65
|
+
"start_commit": context.obj.deploy['previous_commit'],
|
|
66
|
+
"end_commit": context.obj.deploy['current_commit'],
|
|
67
|
+
"control_sync_status_id": control_sync_status_id
|
|
68
|
+
}
|
|
69
|
+
context.params.update(specific_params)
|
|
70
|
+
context.params = parse_params(context.params, sast_run.params)
|
|
71
|
+
try:
|
|
72
|
+
LOGGER.info(
|
|
73
|
+
'Running SAST on deploy ID "{deploy_id}"...'
|
|
74
|
+
.format(deploy_id=context.params["deploy_id"])
|
|
75
|
+
)
|
|
76
|
+
sast_run.invoke(context)
|
|
77
|
+
|
|
78
|
+
return context.params.get('sast_vulnerability_count', 0)
|
|
79
|
+
|
|
80
|
+
except Exception as err:
|
|
81
|
+
raise click.ClickException(str(err)) from err
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def perform_sca(context, control_sync_status_id) -> int:
|
|
85
|
+
"""Setup and runs the "sca run" command.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
context (<class 'click.core.Context'>): cloned context
|
|
89
|
+
control_sync_status_id (str): control sync status id
|
|
90
|
+
"""
|
|
91
|
+
sca_run = sca.commands.get('run')
|
|
92
|
+
context.params.update(
|
|
93
|
+
{"deploy_id": context.obj.deploy['deploy_id'],
|
|
94
|
+
"control_sync_status_id": control_sync_status_id}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
context.params = parse_params(context.params, sca_run.params)
|
|
98
|
+
try:
|
|
99
|
+
LOGGER.info(
|
|
100
|
+
'Running SCA on deploy ID "{deploy_id}"...'
|
|
101
|
+
.format(deploy_id=context.params["deploy_id"])
|
|
102
|
+
)
|
|
103
|
+
sca_run.invoke(context)
|
|
104
|
+
|
|
105
|
+
return context.params.get('sca_vulnerability_count', 0)
|
|
106
|
+
|
|
107
|
+
except Exception as err:
|
|
108
|
+
raise click.ClickException(str(err)) from err
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def perform_iac(context, control_sync_status_id) -> None:
|
|
112
|
+
"""Setup and runs the "iac run" command.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
context (<class 'click.core.Context'>): clonned context
|
|
116
|
+
control_sync_status_id (str): control sync status id
|
|
117
|
+
"""
|
|
118
|
+
iac_run = iac.commands.get('run')
|
|
119
|
+
context.params.update(
|
|
120
|
+
{"deploy_id": context.obj.deploy['deploy_id'],
|
|
121
|
+
"control_sync_status_id": control_sync_status_id}
|
|
122
|
+
)
|
|
123
|
+
context.params = parse_params(context.params, iac_run.params)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
LOGGER.info(
|
|
127
|
+
'Running IAC on deploy ID "{deploy_id}"...'
|
|
128
|
+
.format(deploy_id=context.params["deploy_id"])
|
|
129
|
+
)
|
|
130
|
+
iac_run.invoke(context)
|
|
131
|
+
except Exception as err:
|
|
132
|
+
raise click.ClickException(str(err)) from err
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def perform_vulnerabilities_service(context, company_id, control_sync_status_id) -> None:
|
|
136
|
+
auto_close_run = vulnerability.commands.get('run')
|
|
137
|
+
|
|
138
|
+
specific_params = {
|
|
139
|
+
"deploy_id": context.obj.deploy['deploy_id'],
|
|
140
|
+
"start_commit": context.obj.deploy['previous_commit'],
|
|
141
|
+
"end_commit": context.obj.deploy['current_commit'],
|
|
142
|
+
"company_id": context.params['company_id'] or company_id,
|
|
143
|
+
"control_sync_status_id": control_sync_status_id
|
|
144
|
+
}
|
|
145
|
+
context.params.update(specific_params)
|
|
146
|
+
context.params = parse_params(context.params, auto_close_run.params)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
LOGGER.info("[*] Verifying if any vulnerability was fixed...")
|
|
150
|
+
auto_close_run.invoke(context)
|
|
151
|
+
except Exception as err:
|
|
152
|
+
raise click.ClickException(str(err)) from err
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def perform_deploy(context, flow_context, prepared_context, control_sync_status_id):
|
|
156
|
+
context.obj.output_formatter = DeployFormatter(format=DeployFormatter.DEFAULT)
|
|
157
|
+
context.params = parse_params(context.params, values.params)
|
|
158
|
+
repository_dir = context.params['repository_dir']
|
|
159
|
+
asset_id = prepared_context.params.get('asset_id')
|
|
160
|
+
|
|
161
|
+
if not asset_id:
|
|
162
|
+
raise PerformDeployException("Asset ID is required")
|
|
163
|
+
|
|
164
|
+
LOGGER.info("Creating new deploy...")
|
|
165
|
+
try:
|
|
166
|
+
|
|
167
|
+
created_deploy = values.invoke(context)
|
|
168
|
+
|
|
169
|
+
if created_deploy is None:
|
|
170
|
+
conviso_api = flow_context.create_conviso_graphql_client()
|
|
171
|
+
conviso_api.control_sync_status.increase_count(
|
|
172
|
+
control_sync_status_id=control_sync_status_id,
|
|
173
|
+
asset_id=prepared_context.params['asset_id'],
|
|
174
|
+
success_count=1 # increase by one just to be success
|
|
175
|
+
)
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
conviso_api = flow_context.create_conviso_graphql_client()
|
|
179
|
+
api_key = flow_context.key
|
|
180
|
+
git_adapter = GitAdapter(repository_dir)
|
|
181
|
+
|
|
182
|
+
branch_name = get_branch_name(git_adapter, repository_dir)
|
|
183
|
+
|
|
184
|
+
LOGGER.info(f"Creating deploy: asset_id={asset_id}, "
|
|
185
|
+
f"previous={created_deploy['previous_commit']}, "
|
|
186
|
+
f"current={created_deploy['current_commit']}")
|
|
187
|
+
|
|
188
|
+
response = conviso_api.deploys.create_deploy(
|
|
189
|
+
asset_id=asset_id,
|
|
190
|
+
previous_commit=created_deploy['previous_commit'],
|
|
191
|
+
current_commit=created_deploy['current_commit'],
|
|
192
|
+
branch_name=branch_name,
|
|
193
|
+
api_key=api_key,
|
|
194
|
+
commit_history=created_deploy['commit_history']
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
response_deploy_id = response['createDeploy']['deploy']['id']
|
|
198
|
+
deploy_params = {
|
|
199
|
+
"deploy_id": response_deploy_id,
|
|
200
|
+
"current_commit": created_deploy['current_commit'],
|
|
201
|
+
"previous_commit": created_deploy['previous_commit'],
|
|
202
|
+
}
|
|
203
|
+
created_deploy.update(deploy_params)
|
|
204
|
+
|
|
205
|
+
return created_deploy
|
|
206
|
+
|
|
207
|
+
except Exception as err:
|
|
208
|
+
error_message = str(err)
|
|
209
|
+
error_text = "A deploy with the same previous and current commit already exists"
|
|
210
|
+
found = False
|
|
211
|
+
|
|
212
|
+
if error_text in error_message:
|
|
213
|
+
found = True
|
|
214
|
+
elif hasattr(err, 'errors') and isinstance(err.errors, list):
|
|
215
|
+
for nested_err in err.errors:
|
|
216
|
+
if isinstance(nested_err, dict) and error_text in nested_err.get('message', ''):
|
|
217
|
+
found = True
|
|
218
|
+
break
|
|
219
|
+
elif isinstance(nested_err, str) and error_text in nested_err:
|
|
220
|
+
found = True
|
|
221
|
+
break
|
|
222
|
+
|
|
223
|
+
elif error_text in repr(err):
|
|
224
|
+
found = True
|
|
225
|
+
|
|
226
|
+
if found:
|
|
227
|
+
LOGGER.warning("Deploy with same commits already exists")
|
|
228
|
+
return None
|
|
229
|
+
else:
|
|
230
|
+
raise PerformDeployException(f"Failed to create deploy: {error_message}") from err
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def get_branch_name(git_adapter, repository_dir):
|
|
234
|
+
"""Gets branch name"""
|
|
235
|
+
try:
|
|
236
|
+
return git_adapter.get_branch_name()
|
|
237
|
+
except Exception as e:
|
|
238
|
+
LOGGER.warning(f"HEAD is detached or error getting branch: {e}")
|
|
239
|
+
LOGGER.info("Looking for most recent branch...")
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
result = subprocess.run(
|
|
243
|
+
["git", "for-each-ref", "--sort=-creatordate",
|
|
244
|
+
"--format=%(refname:short)", "refs/heads/"],
|
|
245
|
+
cwd=repository_dir,
|
|
246
|
+
stdout=subprocess.PIPE,
|
|
247
|
+
stderr=subprocess.PIPE,
|
|
248
|
+
check=True,
|
|
249
|
+
timeout=10
|
|
250
|
+
)
|
|
251
|
+
branches = result.stdout.decode().strip().splitlines()
|
|
252
|
+
if branches:
|
|
253
|
+
branch_name = branches[0]
|
|
254
|
+
LOGGER.info(f"Using branch: {branch_name}")
|
|
255
|
+
return branch_name
|
|
256
|
+
else:
|
|
257
|
+
LOGGER.warning("No branches found")
|
|
258
|
+
return "unknown"
|
|
259
|
+
except (subprocess.SubprocessError, subprocess.TimeoutExpired) as e:
|
|
260
|
+
LOGGER.error(f"Error executing git command: {e}")
|
|
261
|
+
return "unknown"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@click.command(
|
|
265
|
+
context_settings=dict(
|
|
266
|
+
allow_extra_args=True,
|
|
267
|
+
ignore_unknown_options=True
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
@asset_id_option(required=False)
|
|
271
|
+
@click.option(
|
|
272
|
+
"--send-to-flow/--no-send-to-flow",
|
|
273
|
+
default=True,
|
|
274
|
+
show_default=True,
|
|
275
|
+
required=False,
|
|
276
|
+
help="""Enable or disable the ability of send analysis result
|
|
277
|
+
reports to flow.""",
|
|
278
|
+
hidden=True
|
|
279
|
+
)
|
|
280
|
+
@click.option(
|
|
281
|
+
'-r',
|
|
282
|
+
'--repository-dir',
|
|
283
|
+
default=".",
|
|
284
|
+
show_default=True,
|
|
285
|
+
type=click.Path(exists=True, resolve_path=True),
|
|
286
|
+
required=False,
|
|
287
|
+
help="""The source code repository directory.""",
|
|
288
|
+
)
|
|
289
|
+
@click.option(
|
|
290
|
+
"-c",
|
|
291
|
+
"--current-commit",
|
|
292
|
+
required=False,
|
|
293
|
+
help="If no value is given the HEAD commit of branch is used. [DEPLOY]",
|
|
294
|
+
)
|
|
295
|
+
@click.option(
|
|
296
|
+
"-p",
|
|
297
|
+
"--previous-commit",
|
|
298
|
+
required=False,
|
|
299
|
+
help="""If no value is given, the value is retrieved from the lastest
|
|
300
|
+
deploy at flow application. [DEPLOY]""",
|
|
301
|
+
)
|
|
302
|
+
@click.option(
|
|
303
|
+
"--company-id",
|
|
304
|
+
required=False,
|
|
305
|
+
envvar=("CONVISO_COMPANY_ID", "FLOW_COMPANY_ID"),
|
|
306
|
+
help="Company ID on Conviso Platform",
|
|
307
|
+
)
|
|
308
|
+
@click.option(
|
|
309
|
+
'--asset-name',
|
|
310
|
+
required=False,
|
|
311
|
+
envvar=("CONVISO_ASSET_NAME", "FLOW_ASSET_NAME"),
|
|
312
|
+
help="Provides a asset name.",
|
|
313
|
+
)
|
|
314
|
+
@click.option(
|
|
315
|
+
'--vulnerability-auto-close',
|
|
316
|
+
default=False,
|
|
317
|
+
is_flag=True,
|
|
318
|
+
help="Enable auto fixing vulnerabilities on cp.",
|
|
319
|
+
)
|
|
320
|
+
@click.option(
|
|
321
|
+
'--cleanup',
|
|
322
|
+
default=False,
|
|
323
|
+
is_flag=True,
|
|
324
|
+
show_default=True,
|
|
325
|
+
help="Clean up system resources, including temporary files, stopped containers, unused Docker images and volumes.",
|
|
326
|
+
)
|
|
327
|
+
@help_option
|
|
328
|
+
@pass_flow_context
|
|
329
|
+
@pass_create_context
|
|
330
|
+
@click.pass_context
|
|
331
|
+
def run(context, create_context, flow_context, **kwargs):
|
|
332
|
+
""" AST - Application Security Testing. Unifies deploy issue, SAST and SCA analyses. """
|
|
333
|
+
increase_failure_count = 1
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
prepared_context = RequirementsVerifier.prepare_context(clone(context), from_ast=True)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# After ensuring we have both asset and user permissions, AST is ready to start. We then create a control sync status
|
|
340
|
+
# on the Conviso platform, which will appear on the scans page and in the recent scans list.
|
|
341
|
+
conviso_api = flow_context.create_conviso_graphql_client()
|
|
342
|
+
control_sync_status = conviso_api.control_sync_status.create_control_sync_status(
|
|
343
|
+
asset_id=prepared_context.params['asset_id']
|
|
344
|
+
)
|
|
345
|
+
conviso_api.control_sync_status.update_control_sync_status(control_sync_status_id=control_sync_status['id'])
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
prepared_context.obj.deploy = perform_deploy(
|
|
349
|
+
clone(prepared_context), flow_context, prepared_context, control_sync_status['id']
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if prepared_context.obj.deploy is None:
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
total_sast = perform_sast(clone(prepared_context), control_sync_status_id=control_sync_status['id'])
|
|
356
|
+
total_sca = perform_sca(clone(prepared_context), control_sync_status_id=control_sync_status['id'])
|
|
357
|
+
perform_iac(clone(prepared_context), control_sync_status_id=control_sync_status['id'])
|
|
358
|
+
|
|
359
|
+
total_vulnerability_count = total_sast + total_sca
|
|
360
|
+
|
|
361
|
+
company_id = prepared_context.params['company_id']
|
|
362
|
+
|
|
363
|
+
if context.params['vulnerability_auto_close'] is True:
|
|
364
|
+
try:
|
|
365
|
+
perform_vulnerabilities_service(clone(prepared_context), company_id, control_sync_status['id'])
|
|
366
|
+
except Exception:
|
|
367
|
+
LOGGER.info("An issue occurred while attempting to fix vulnerabilities. Our technical team has been notified.")
|
|
368
|
+
full_trace = traceback.format_exc()
|
|
369
|
+
log_and_notify_ast_event(flow_context=flow_context, company_id=company_id,
|
|
370
|
+
asset_id=prepared_context.params['asset_id'], ast_log=full_trace)
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
if context.params.get('cleanup'):
|
|
374
|
+
try:
|
|
375
|
+
LOGGER.info("🧹 Cleaning up ...")
|
|
376
|
+
cleaner = Cleaner()
|
|
377
|
+
cleaner.cleanup()
|
|
378
|
+
except Exception as e:
|
|
379
|
+
LOGGER.info(f"An error occurred while cleaning up. Our technical team has been notified.")
|
|
380
|
+
full_trace = traceback.format_exc()
|
|
381
|
+
log_and_notify_ast_event(
|
|
382
|
+
flow_context=flow_context, company_id=company_id, asset_id=prepared_context.params['asset_id'],
|
|
383
|
+
ast_log=full_trace
|
|
384
|
+
)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
conviso_api.control_sync_status.update_control_sync_status(
|
|
388
|
+
control_sync_status_id=control_sync_status['id'],
|
|
389
|
+
external_vulnerability_count=total_vulnerability_count
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# increase success_count by one just to match external_vulnerability_count when ast runs successfully and if
|
|
393
|
+
# success count and external_vulnerability_count are equals, the scan status will be moved from pending to success.
|
|
394
|
+
conviso_api.control_sync_status.increase_count(
|
|
395
|
+
control_sync_status_id=control_sync_status['id'],
|
|
396
|
+
asset_id=prepared_context.params['asset_id'],
|
|
397
|
+
success_count=total_vulnerability_count
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
except Exception as err:
|
|
401
|
+
failure_details = f"{str(err)}, {traceback.format_exc()}"
|
|
402
|
+
|
|
403
|
+
conviso_api.control_sync_status.increase_count(
|
|
404
|
+
control_sync_status_id=control_sync_status['id'],
|
|
405
|
+
asset_id=prepared_context.params['asset_id'],
|
|
406
|
+
failure_count=increase_failure_count,
|
|
407
|
+
failure_reason=str(failure_details)
|
|
408
|
+
)
|
|
409
|
+
raise click.ClickException(str(err)) from err
|
|
410
|
+
|
|
411
|
+
except Exception as err:
|
|
412
|
+
error_message = str(err)
|
|
413
|
+
|
|
414
|
+
if "A deploy with the same previous and current commit already exists" in error_message:
|
|
415
|
+
LOGGER.warning("Deploy with same commits already exists")
|
|
416
|
+
return None
|
|
417
|
+
else:
|
|
418
|
+
LOGGER.error(f"AST initialization failed. Please contact support with the following error: {err}")
|
|
419
|
+
raise click.ClickException(str(err)) from err
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@click.group()
|
|
423
|
+
def ast():
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
ast.add_command(run)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import sys
|
|
3
|
+
from shlex import quote
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from convisoappsec.flow.util.ci_provider import CIProvider
|
|
9
|
+
|
|
10
|
+
class CreateDeployException(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class PerformDeployException(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
class DeployFormatter(object):
|
|
17
|
+
DEFAULT = 'default'
|
|
18
|
+
ENV_VARS = 'env_vars'
|
|
19
|
+
|
|
20
|
+
def __init__(self, format):
|
|
21
|
+
self.stategy = self._select_strategy(format)
|
|
22
|
+
|
|
23
|
+
def format(self, deploy):
|
|
24
|
+
return self.stategy(deploy)
|
|
25
|
+
|
|
26
|
+
def _select_strategy(self, format):
|
|
27
|
+
format_strategies = {
|
|
28
|
+
self.DEFAULT: self.default_format,
|
|
29
|
+
self. ENV_VARS: self.env_vars_format,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
strategy = format_strategies.get(format)
|
|
33
|
+
|
|
34
|
+
if not strategy:
|
|
35
|
+
msg_fmt = "Allowed deploy formats[{0}]. Given {1}"
|
|
36
|
+
msg = msg_fmt.format(
|
|
37
|
+
"|".join(self.FORMATS()),
|
|
38
|
+
format,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
raise ValueError(msg)
|
|
42
|
+
|
|
43
|
+
return strategy
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def FORMATS(cls):
|
|
47
|
+
return [
|
|
48
|
+
cls.DEFAULT,
|
|
49
|
+
cls.ENV_VARS,
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
def env_vars_format(self, deploy):
|
|
53
|
+
to_env_vars_translation_args = [
|
|
54
|
+
# (env_var_name, attrib_name, attrib_default_val)
|
|
55
|
+
('FLOW_DEPLOY_ID', 'id', 0),
|
|
56
|
+
('FLOW_DEPLOY_CREATED_AT', 'created_at', ''),
|
|
57
|
+
('FLOW_DEPLOY_CURRENT_VERSION_TAG', 'current_tag', ''),
|
|
58
|
+
('FLOW_DEPLOY_PREVIOUS_VERSION_TAG', 'previous_tag', ''),
|
|
59
|
+
('FLOW_DEPLOY_CURRENT_VERSION_COMMIT', 'current_commit', ''),
|
|
60
|
+
('FLOW_DEPLOY_PREVIOUS_VERSION_COMMIT', 'previous_commit', ''),
|
|
61
|
+
('CONVISO_DEPLOY_ID', 'id', 0),
|
|
62
|
+
('CONVISO_DEPLOY_CREATED_AT', 'created_at', ''),
|
|
63
|
+
('CONVISO_DEPLOY_CURRENT_VERSION_TAG', 'current_tag', ''),
|
|
64
|
+
('CONVISO_DEPLOY_PREVIOUS_VERSION_TAG', 'previous_tag', ''),
|
|
65
|
+
('CONVISO_DEPLOY_CURRENT_VERSION_COMMIT', 'current_commit', ''),
|
|
66
|
+
('CONVISO_DEPLOY_PREVIOUS_VERSION_COMMIT', 'previous_commit', ''),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
env_vars = {}
|
|
70
|
+
|
|
71
|
+
for arg in to_env_vars_translation_args:
|
|
72
|
+
env_var_name, attrib_name, attrib_default_val = arg
|
|
73
|
+
|
|
74
|
+
env_var_val = env_vars[env_var_name] = deploy.get(
|
|
75
|
+
attrib_name, attrib_default_val
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
env_vars[env_var_name] = str(env_var_val)
|
|
79
|
+
|
|
80
|
+
buffer = io.StringIO()
|
|
81
|
+
|
|
82
|
+
for var_name, var_val in env_vars.items():
|
|
83
|
+
line_fmt = "export {name}={value}"
|
|
84
|
+
line = line_fmt.format(
|
|
85
|
+
name=quote(var_name),
|
|
86
|
+
value=quote(var_val),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
print(line, file=buffer)
|
|
90
|
+
|
|
91
|
+
buffer.seek(0)
|
|
92
|
+
return buffer.read()
|
|
93
|
+
|
|
94
|
+
def default_format(self, src_deploy):
|
|
95
|
+
deploy = {
|
|
96
|
+
'id': 0,
|
|
97
|
+
'current_tag': '',
|
|
98
|
+
'previous_tag': '',
|
|
99
|
+
'current_commit': '',
|
|
100
|
+
'previous_commit': '',
|
|
101
|
+
'created_at': '',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
deploy.update(src_deploy)
|
|
105
|
+
|
|
106
|
+
default_fmt = '''
|
|
107
|
+
Deploy stats:
|
|
108
|
+
current_version.commit={current_commit}
|
|
109
|
+
current_version.tag={current_tag}
|
|
110
|
+
previous_version.commit={previous_commit}
|
|
111
|
+
previous_version.tag={previous_tag}
|
|
112
|
+
'''
|
|
113
|
+
|
|
114
|
+
return default_fmt.format(
|
|
115
|
+
**deploy
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def notify_created_deploy(deploy):
|
|
120
|
+
formatter = DeployFormatter(DeployFormatter.DEFAULT)
|
|
121
|
+
click.echo(formatter.format(deploy))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def asset_id_option(*args, **kwargs):
|
|
125
|
+
kwargs["envvar"] = kwargs.get("envvar", ("CONVISO_ASSET_ID", "FLOW_ASSET_ID"))
|
|
126
|
+
kwargs["show_envvar"] = kwargs.get("show_envvar", True)
|
|
127
|
+
kwargs["required"] = kwargs.get("required", False)
|
|
128
|
+
kwargs["hidden"] = kwargs.get("hidden", False)
|
|
129
|
+
asset_id_param = "--asset-id"
|
|
130
|
+
paramsdecl = args
|
|
131
|
+
|
|
132
|
+
if asset_id_param not in args:
|
|
133
|
+
paramsdecl = args + (asset_id_param, )
|
|
134
|
+
|
|
135
|
+
return click.option(
|
|
136
|
+
*paramsdecl,
|
|
137
|
+
type=int,
|
|
138
|
+
**kwargs
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def on_http_error(error):
|
|
142
|
+
try:
|
|
143
|
+
raise error
|
|
144
|
+
except requests.exceptions.HTTPError as e:
|
|
145
|
+
message = error.response.text
|
|
146
|
+
|
|
147
|
+
if not message:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
click.echo(
|
|
151
|
+
"HttpErrorResponse: {0}".format(message),
|
|
152
|
+
file=sys.stderr
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def __try_retrieve_ci_provider(provider_name):
|
|
157
|
+
try:
|
|
158
|
+
return CIProvider[provider_name]
|
|
159
|
+
except KeyError as e:
|
|
160
|
+
error_msg_fmt = 'Received for parameter provider_name[{}]. Expected values are: [{}]'
|
|
161
|
+
expected_values = '|'.join(CIProvider.names())
|
|
162
|
+
error_msg = error_msg_fmt.format(provider_name, expected_values)
|
|
163
|
+
|
|
164
|
+
raise ValueError(error_msg) from e
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def process_ci_provider_option(provider_name, env={}):
|
|
168
|
+
if provider_name is not None:
|
|
169
|
+
return __try_retrieve_ci_provider(provider_name)
|
|
170
|
+
|
|
171
|
+
for provider in CIProvider:
|
|
172
|
+
if provider.env_vars_exists(env):
|
|
173
|
+
return provider
|
|
174
|
+
|
|
175
|
+
return CIProvider.OTHER
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from convisoappsec.flowcli.common import on_http_error
|
|
3
|
+
from convisoappsec.common import safe_join_url
|
|
4
|
+
from convisoappsec.flow.graphql_api.v1.client import ConvisoGraphQLClient
|
|
5
|
+
|
|
6
|
+
class Companies():
|
|
7
|
+
def ls(self, flow_context, company_id=None):
|
|
8
|
+
api_key = flow_context.key
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
url = safe_join_url(flow_context.url, "/graphql")
|
|
12
|
+
conviso_api = ConvisoGraphQLClient(api_url=url,api_key=api_key)
|
|
13
|
+
|
|
14
|
+
return perform_command(conviso_api, company_id)
|
|
15
|
+
except Exception as exception:
|
|
16
|
+
on_http_error(exception)
|
|
17
|
+
raise click.ClickException(str(exception)) from exception
|
|
18
|
+
|
|
19
|
+
def perform_command(conviso_api, company_id):
|
|
20
|
+
if company_id is not None:
|
|
21
|
+
companies = conviso_api.companies.get_company_by_id(company_id)
|
|
22
|
+
else:
|
|
23
|
+
companies = conviso_api.companies.get_companies()
|
|
24
|
+
|
|
25
|
+
return companies
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from convisoappsec.flowcli import help_option
|
|
4
|
+
from .run import run
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
@help_option
|
|
9
|
+
def container():
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
container.add_command(run)
|
|
14
|
+
|
|
15
|
+
container.epilog = '''
|
|
16
|
+
Run conviso container COMMAND --help for more information on a command.
|
|
17
|
+
'''
|