conviso-ast 3.0.0.dev1__py3-none-any.whl → 3.0.1__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.dev1.dist-info → conviso_ast-3.0.1.dist-info}/METADATA +4 -4
- {conviso_ast-3.0.0.dev1.dist-info → conviso_ast-3.0.1.dist-info}/RECORD +23 -24
- convisoappsec/common/retry_handler.py +2 -2
- convisoappsec/flow/util/__init__.py +1 -3
- convisoappsec/flowcli/ast/dry_run.py +99 -0
- convisoappsec/flowcli/ast/entrypoint.py +2 -1
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/time_.py +0 -2
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/versioning_style.py +0 -2
- convisoappsec/flowcli/iac/dry_run.py +94 -0
- convisoappsec/flowcli/iac/entrypoint.py +2 -0
- convisoappsec/flowcli/iac/run.py +4 -1
- convisoappsec/flowcli/sast/dry_run.py +159 -0
- convisoappsec/flowcli/sast/entrypoint.py +2 -0
- convisoappsec/flowcli/sbom/generate.py +1 -1
- convisoappsec/flowcli/sca/dry_run.py +108 -0
- convisoappsec/flowcli/sca/entrypoint.py +2 -0
- convisoappsec/version.py +1 -1
- convisoappsec/flow/source_code_scanner/__init__.py +0 -9
- convisoappsec/flow/source_code_scanner/exceptions.py +0 -2
- convisoappsec/flow/source_code_scanner/scc.py +0 -68
- convisoappsec/flow/source_code_scanner/source_code_scanner.py +0 -177
- convisoappsec/flow/util/metrics.py +0 -16
- {conviso_ast-3.0.0.dev1.data → conviso_ast-3.0.1.data}/scripts/flow_bash_completer.sh +0 -0
- {conviso_ast-3.0.0.dev1.data → conviso_ast-3.0.1.data}/scripts/flow_fish_completer.fish +0 -0
- {conviso_ast-3.0.0.dev1.data → conviso_ast-3.0.1.data}/scripts/flow_zsh_completer.sh +0 -0
- {conviso_ast-3.0.0.dev1.dist-info → conviso_ast-3.0.1.dist-info}/WHEEL +0 -0
- {conviso_ast-3.0.0.dev1.dist-info → conviso_ast-3.0.1.dist-info}/entry_points.txt +0 -0
- {conviso_ast-3.0.0.dev1.dist-info → conviso_ast-3.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: conviso-ast
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Maintainer: Conviso
|
|
5
5
|
Maintainer-email: development@convisoappsec.com
|
|
6
6
|
Project-URL: Source, https://github.com/convisoappsec/convisocli/
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: GitPython==3.1.
|
|
9
|
+
Requires-Dist: GitPython==3.1.46
|
|
10
10
|
Requires-Dist: click==8.1.8
|
|
11
11
|
Requires-Dist: requests==2.32.5
|
|
12
|
-
Requires-Dist: urllib3==2.
|
|
12
|
+
Requires-Dist: urllib3==2.6.3
|
|
13
13
|
Requires-Dist: semantic-version==2.10.0
|
|
14
14
|
Requires-Dist: docker==7.1.0
|
|
15
15
|
Requires-Dist: PyYAML==6.0.3
|
|
16
16
|
Requires-Dist: click-log==0.4.0
|
|
17
17
|
Requires-Dist: transitions==0.9.2
|
|
18
18
|
Requires-Dist: jsonschema==4.25.1
|
|
19
|
-
Requires-Dist: giturlparse<=0.
|
|
19
|
+
Requires-Dist: giturlparse<=0.14.0
|
|
20
20
|
Requires-Dist: jmespath==1.0.1
|
|
21
21
|
Requires-Dist: setuptools==78.1.0
|
|
22
22
|
Dynamic: description
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
conviso_ast-3.0.
|
|
2
|
-
conviso_ast-3.0.
|
|
3
|
-
conviso_ast-3.0.
|
|
1
|
+
conviso_ast-3.0.1.data/scripts/flow_bash_completer.sh,sha256=9q3HPuXq_FCUUV3IFGcOefsOLhPWatUkLY7txiBM7Uo,624
|
|
2
|
+
conviso_ast-3.0.1.data/scripts/flow_fish_completer.fish,sha256=-wiuarawDJkms5N-rh99brIOzhy-ktsM1mi1ohQ3Mtg,147
|
|
3
|
+
conviso_ast-3.0.1.data/scripts/flow_zsh_completer.sh,sha256=cAtTDGUs5sY4NAA7AjscmLWj0dbNZ9iZhLP6BTz6dEQ,844
|
|
4
4
|
convisoappsec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
convisoappsec/logger.py,sha256=aTNebqOau9nEadBySMTXtnbGkOkJ_q2kyFlX1mzizeg,1132
|
|
6
|
-
convisoappsec/version.py,sha256=
|
|
6
|
+
convisoappsec/version.py,sha256=Ys8n4as2Wcnr8Aq6OV-e59RKmKVeyh9Oh2nzJ2FZPFk,22
|
|
7
7
|
convisoappsec/common/__init__.py,sha256=QN7tV2C_jhTiWUrJHv2jbeq6ae3MssgLUWpQZwe8O2s,105
|
|
8
8
|
convisoappsec/common/box.py,sha256=WTtPF3YWxkcdblPmFTzrzQlPPPUwVsDt2zoi6xFMy1U,7561
|
|
9
9
|
convisoappsec/common/cleaner.py,sha256=Iy8BWCXj_v51oovcYzI_uhaJzLL-fCUyDxrbBglfwEs,2680
|
|
10
10
|
convisoappsec/common/docker.py,sha256=SYkZCgKS_kuREyJS_Zb4d61MWb58HoiFbxkSRzLJr0g,12529
|
|
11
11
|
convisoappsec/common/exceptions.py,sha256=Das7j6_nzB75-TY9xoVK12eswvxSntxI19nmXdjumzI,230
|
|
12
12
|
convisoappsec/common/git_data_parser.py,sha256=-Ou1YC8D4bpmHUcibxLiJf2rxKi8-SuTuto-6nB-ML8,2167
|
|
13
|
-
convisoappsec/common/retry_handler.py,sha256=
|
|
13
|
+
convisoappsec/common/retry_handler.py,sha256=NwBNRQrqwk3Ohz9vcls2vTqD6wqxDWjm4MWI5WIfyj8,1533
|
|
14
14
|
convisoappsec/common/strings.py,sha256=JqBqW1redAfWTWtUFya2MvrW5T26Y07Vn0pbee-BpMo,185
|
|
15
15
|
convisoappsec/common/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
convisoappsec/common/graphql/error_handlers.py,sha256=1KUBa1zYiN_Gs2uwPsjl4LovoRFboWc16U7f680FfNE,2058
|
|
@@ -44,13 +44,8 @@ convisoappsec/flow/graphql_api/v1/models/project.py,sha256=CDZlufsT-_iQIOgpOPAtd
|
|
|
44
44
|
convisoappsec/flow/graphql_api/v1/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
45
|
convisoappsec/flow/graphql_api/v1/schemas/mutations/__init__.py,sha256=R9LgniQyj694h1MD7cn1-HJRDjI4PLiOrCmsqjp16ho,3251
|
|
46
46
|
convisoappsec/flow/graphql_api/v1/schemas/resolvers/__init__.py,sha256=2jAgzA-8te6HyyV8RVAx0BnLDeZhFulZynbxm5JR8Sc,2713
|
|
47
|
-
convisoappsec/flow/
|
|
48
|
-
convisoappsec/flow/source_code_scanner/exceptions.py,sha256=6ehUyZaFfMIRMzKdh0MND8gfyTNyHY_wsprmxkxSJD0,57
|
|
49
|
-
convisoappsec/flow/source_code_scanner/scc.py,sha256=O_kdWsw2x0czuKcLORD3gP5wbXsizj4Bb2Sbsk5n_Sw,1645
|
|
50
|
-
convisoappsec/flow/source_code_scanner/source_code_scanner.py,sha256=L9LVZnUO-o_JvT8dHUxBXcbxQlxSqRP8CVUBgTUEV7s,4726
|
|
51
|
-
convisoappsec/flow/util/__init__.py,sha256=rpu69qnn68rqrK3rhPcgrVYWCIW9WVSrQ5FyCeHxng4,160
|
|
47
|
+
convisoappsec/flow/util/__init__.py,sha256=32E3AbPrBf0stKOzCIvz7BeWtBjAScIt7FNyERjN-p4,99
|
|
52
48
|
convisoappsec/flow/util/ci_provider.py,sha256=VYDESwNFbtrRcWDtTEb8tYDN5qbnLksmQI_ntsciJ4I,2093
|
|
53
|
-
convisoappsec/flow/util/metrics.py,sha256=4qGBMMR02OZN4ezFiB9iqtCuUYO8bXYqeUNDFRDpQaw,480
|
|
54
49
|
convisoappsec/flow/util/source_code_compressor.py,sha256=b2iA8Exf8wVbxR1mnvwTbruDjdpYyVloUmSlyYLSJQU,508
|
|
55
50
|
convisoappsec/flow/version_searchers/__init__.py,sha256=rJkVGlmWiiDHegvQl7d900RvnglzICXpTxuUiLlHSz0,294
|
|
56
51
|
convisoappsec/flow/version_searchers/sorted_by_versioning_style.py,sha256=xLjaHy-WDOgY5jrASnaK-uM0UL-9sL3BALwzmM5PgSA,2657
|
|
@@ -71,7 +66,8 @@ convisoappsec/flowcli/assets/create.py,sha256=JDmxDH1WbyaQYLTxoi6dS_WosRm7kjvpJV
|
|
|
71
66
|
convisoappsec/flowcli/assets/entrypoint.py,sha256=CEoev3d1ogclPNP3l31PBhGkoiZZ400kxsnnF02uxFk,310
|
|
72
67
|
convisoappsec/flowcli/assets/ls.py,sha256=IYfKIca218BvZ5ecrtDgZ_T8xVg0XO3Eq1kK16BMBsc,1484
|
|
73
68
|
convisoappsec/flowcli/ast/__init__.py,sha256=9SO9inH22PPm4jLlljVTJEeJKZujFKfwqTBbBo-TwFM,47
|
|
74
|
-
convisoappsec/flowcli/ast/
|
|
69
|
+
convisoappsec/flowcli/ast/dry_run.py,sha256=pb0eM-jXE83UpWTBcw3ulVSgY0WhWvWbcb-58j2YjC4,3588
|
|
70
|
+
convisoappsec/flowcli/ast/entrypoint.py,sha256=HI02I6r8v2cBYwCeOfQZLtFjGhTl3dF_83Tu2FXCM8Y,15999
|
|
75
71
|
convisoappsec/flowcli/companies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
72
|
convisoappsec/flowcli/companies/ls.py,sha256=0287VTXOkKnuXTaJh392crYjx0qank0pBvmESq3P_Bw,892
|
|
77
73
|
convisoappsec/flowcli/container/__init__.py,sha256=Z666kQa4qVfbcuyLaW_hvORdAPtZ9dTQFcQb2H2Nkfg,59
|
|
@@ -89,8 +85,8 @@ convisoappsec/flowcli/deploy/create/with_/tag_tracker/context.py,sha256=IZjsKSze
|
|
|
89
85
|
convisoappsec/flowcli/deploy/create/with_/tag_tracker/entrypoint.py,sha256=MUrx_fqAFQeJ2oUaAxRDPfk_qlgyNHKFMNZqmiDFWrU,675
|
|
90
86
|
convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/__init__.py,sha256=c1sggCq3RGdgyq0tqdxx_yGW1iOeBti2aoNsoiMxKnc,56
|
|
91
87
|
convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/entrypoint.py,sha256=J5dSGo0P__fKlPpeaVcAmyFnnAtZPi0E2y4Hv_U-ZQM,382
|
|
92
|
-
convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/time_.py,sha256=
|
|
93
|
-
convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/versioning_style.py,sha256=-
|
|
88
|
+
convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/time_.py,sha256=Up4tgYC5UFoDCjLYUo-6dPkmhVlJqx_eg6zioNegjI8,2608
|
|
89
|
+
convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/versioning_style.py,sha256=-BRqdRK-LFNjA6XiOrfoMKDo-ACetdsQaGEPQBTRcDo,3303
|
|
94
90
|
convisoappsec/flowcli/findings/__init__.py,sha256=0wiNMaThYuum1QA9s9-LGtcmWFxUY-mWiYMtTSuaDqY,58
|
|
95
91
|
convisoappsec/flowcli/findings/entrypoint.py,sha256=foc6wgD2BsoFZkJQiIuis260Zt-3DkTLsxej7cHv9Ko,344
|
|
96
92
|
convisoappsec/flowcli/findings/create/__init__.py,sha256=l7eIySVkTKr3auZ8Fwe7G6AG6-pM06vnl3aZcEclVP4,54
|
|
@@ -101,16 +97,19 @@ convisoappsec/flowcli/findings/create/with_/version_tracker.py,sha256=NrnYx6OMQW
|
|
|
101
97
|
convisoappsec/flowcli/findings/import_sarif/__init__.py,sha256=3iR7f9V9NdoIQTrD25EVu6OAYPPJ4RZEaM4P34BS0js,66
|
|
102
98
|
convisoappsec/flowcli/findings/import_sarif/entrypoint.py,sha256=MIHi9ZWU2Jn2o7o5mDLp81xfb14gsa33aGnqh1YR7n8,13740
|
|
103
99
|
convisoappsec/flowcli/iac/__init__.py,sha256=a3IZzSKpm987fMEliTECDeXO_Eduk7eg-aQzmaWvUXQ,47
|
|
104
|
-
convisoappsec/flowcli/iac/
|
|
105
|
-
convisoappsec/flowcli/iac/
|
|
100
|
+
convisoappsec/flowcli/iac/dry_run.py,sha256=xvC0Wb2Sxl37yEjFaFMGcRvLJ4Q8evrNQoUXuahHtCg,2990
|
|
101
|
+
convisoappsec/flowcli/iac/entrypoint.py,sha256=WMlSwHd7cLxhUfy643OquQhUHEy4yNf7s0K5bkrXYno,295
|
|
102
|
+
convisoappsec/flowcli/iac/run.py,sha256=Y86MYTM8kTcF3TdvZuGgGLmNOEx7EPVaidKnStSbVpE,11203
|
|
106
103
|
convisoappsec/flowcli/sast/__init__.py,sha256=S4O78eZGhgpT2lZY3GSUIUTQJB5a62uAVirEqbf4EQY,49
|
|
107
|
-
convisoappsec/flowcli/sast/
|
|
104
|
+
convisoappsec/flowcli/sast/dry_run.py,sha256=lUi9LCfBlBjcAYm4v_Etl3raPZcTHVr5fVxGnB1eVSM,5787
|
|
105
|
+
convisoappsec/flowcli/sast/entrypoint.py,sha256=XMu8WpZNwSujWOwbHUThk3JK_WtRWHau9kYD2ttjULY,300
|
|
108
106
|
convisoappsec/flowcli/sast/run.py,sha256=7uX6K1VLASU8RnoCKNYIIqXSsNkF4JQ8OKhAG2CKjuI,16353
|
|
109
107
|
convisoappsec/flowcli/sbom/__init__.py,sha256=qzwiPWniK41Y41XvJJhxtRZguHrSSenb57lhy64KnSc,49
|
|
110
108
|
convisoappsec/flowcli/sbom/entrypoint.py,sha256=ax2WL5BtCu24NeZYe6zzCVZ2ujlviXkvtY7PZihlz3s,263
|
|
111
|
-
convisoappsec/flowcli/sbom/generate.py,sha256=
|
|
109
|
+
convisoappsec/flowcli/sbom/generate.py,sha256=YEhl4fVRXRscSApTMkTynyka3ygsVgJD7vhUHipUp18,7190
|
|
112
110
|
convisoappsec/flowcli/sca/__init__.py,sha256=xnVoxwbpe4LrEemmWJ6svr3zdpo9S4kEIvM4HVRsLX8,47
|
|
113
|
-
convisoappsec/flowcli/sca/
|
|
111
|
+
convisoappsec/flowcli/sca/dry_run.py,sha256=fZ_tgc1k8Mn1JMznFWyi_CExR-gpLHLHwQ2erNldnHY,3438
|
|
112
|
+
convisoappsec/flowcli/sca/entrypoint.py,sha256=yEAANSG2fcD3HctUWebhx_win3CW-1Q2CO1i-ZIBUCk,295
|
|
114
113
|
convisoappsec/flowcli/sca/run.py,sha256=FWnTHgMgnIRvJSpMIkFcb6YMtvWEjEpKJyXEkjAZpA4,16725
|
|
115
114
|
convisoappsec/flowcli/vulnerability/__init__.py,sha256=c18E0J1KfZBBqpv8XGxF5dv7dxCDquFkjGkdnvFDSYI,67
|
|
116
115
|
convisoappsec/flowcli/vulnerability/assert_security_rules.py,sha256=j7VcondMeZSRxWz118aWS9qUSGdvsq4Mg-_9UXp8Dyw,6075
|
|
@@ -121,8 +120,8 @@ convisoappsec/flowcli/vulnerability/run.py,sha256=6flmvr55cKxj-duX3w4PAFwJgC5AJ0
|
|
|
121
120
|
convisoappsec/sast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
122
121
|
convisoappsec/sast/decision.py,sha256=d7dcNr9yZMzyccpFS_peAmDo0ZtfsE1qXDdYrvCux2U,1025
|
|
123
122
|
convisoappsec/sast/sastbox.py,sha256=hXZLiYh_F3f6yd1ydPYVOMKg-tNOQOZiBvKmWyedagI,11031
|
|
124
|
-
conviso_ast-3.0.
|
|
125
|
-
conviso_ast-3.0.
|
|
126
|
-
conviso_ast-3.0.
|
|
127
|
-
conviso_ast-3.0.
|
|
128
|
-
conviso_ast-3.0.
|
|
123
|
+
conviso_ast-3.0.1.dist-info/METADATA,sha256=BdZ7Ie84rkDmpp_x8q0H_ROeadkVM8QLidi9zadyDzU,1075
|
|
124
|
+
conviso_ast-3.0.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
125
|
+
conviso_ast-3.0.1.dist-info/entry_points.txt,sha256=0IvamweR_V0uG4O5Fo9NpVHTHfpZRwUE9kn7KEVZ668,109
|
|
126
|
+
conviso_ast-3.0.1.dist-info/top_level.txt,sha256=ju5r0RSCF1HA7m9JOG10jrQS4SnqQEJzl6-YMCxbSl4,14
|
|
127
|
+
conviso_ast-3.0.1.dist-info/RECORD,,
|
|
@@ -20,7 +20,7 @@ class RetryHandler:
|
|
|
20
20
|
while retries < self.max_retries:
|
|
21
21
|
try:
|
|
22
22
|
return func(*args, **kwargs)
|
|
23
|
-
except Exception:
|
|
23
|
+
except Exception as log_message:
|
|
24
24
|
retries += 1
|
|
25
25
|
time.sleep(delay)
|
|
26
26
|
delay *= self.backoff_factor
|
|
@@ -28,7 +28,7 @@ class RetryHandler:
|
|
|
28
28
|
if retries == self.max_retries:
|
|
29
29
|
full_trace = traceback.format_exc()
|
|
30
30
|
LOGGER.warning(
|
|
31
|
-
"⚠️ Maximum retries reached. Our technical team has been notified."
|
|
31
|
+
f"⚠️ Maximum retries reached. Our technical team has been notified. Error: {log_message}"
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
try:
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import json
|
|
3
|
+
import traceback
|
|
4
|
+
import sys
|
|
5
|
+
from convisoappsec.flowcli import help_option
|
|
6
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
7
|
+
from convisoappsec.logger import LOGGER
|
|
8
|
+
from convisoappsec.flowcli.common import on_http_error
|
|
9
|
+
from convisoappsec.common.cleaner import Cleaner
|
|
10
|
+
from convisoappsec.sast.sastbox import SASTBox
|
|
11
|
+
from convisoappsec.flowcli.sast.dry_run import execute_dry_run as execute_sast_dry_run
|
|
12
|
+
from convisoappsec.flowcli.sca.dry_run import execute_dry_run as execute_sca_dry_run
|
|
13
|
+
from convisoappsec.flowcli.iac.dry_run import execute_dry_run as execute_iac_dry_run
|
|
14
|
+
|
|
15
|
+
@click.command(name='dry-run')
|
|
16
|
+
@click.option(
|
|
17
|
+
"-s", "--start-commit", required=False,
|
|
18
|
+
help="If no value is set so the empty tree hash commit is used."
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"-e", "--end-commit", required=False,
|
|
22
|
+
help="If no value is set so the HEAD commit from the current branch is used"
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"-r", "--repository-dir", default=".", show_default=True,
|
|
26
|
+
type=click.Path(exists=True, resolve_path=True), required=False,
|
|
27
|
+
help="The source code repository directory."
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--sastbox-registry", default="", required=False, hidden=True,
|
|
31
|
+
envvar=("CONVISO_SASTBOX_REGISTRY", "FLOW_SASTBOX_REGISTRY"),
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--sastbox-repository-name", default="", required=False, hidden=True,
|
|
35
|
+
envvar=("CONVISO_SASTBOX_REPOSITORY_NAME", "FLOW_SASTBOX_REPOSITORY_NAME"),
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--sastbox-tag", default=SASTBox.DEFAULT_TAG, required=False, hidden=True,
|
|
39
|
+
envvar=("CONVISO_SASTBOX_TAG", "FLOW_SASTBOX_TAG"),
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--sastbox-skip-login/--sastbox-no-skip-login", default=False, required=False, hidden=True,
|
|
43
|
+
envvar=("CONVISO_SASTBOX_SKIP_LOGIN", "FLOW_SASTBOX_SKIP_LOGIN"),
|
|
44
|
+
)
|
|
45
|
+
@click.option(
|
|
46
|
+
"--custom-sca-tags", hidden=True, required=False, multiple=True, type=(str, str),
|
|
47
|
+
help="It should be passed as <repository_name> <image_tag>."
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--scanner-timeout", hidden=True, required=False, default=7200, type=int,
|
|
51
|
+
help="Set timeout for each scanner"
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
'--cleanup', default=False, is_flag=True, show_default=True,
|
|
55
|
+
help="Clean up system resources."
|
|
56
|
+
)
|
|
57
|
+
@help_option
|
|
58
|
+
@pass_flow_context
|
|
59
|
+
def dry_run(flow_context, end_commit, start_commit, repository_dir,
|
|
60
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login,
|
|
61
|
+
custom_sca_tags, scanner_timeout, cleanup):
|
|
62
|
+
"""
|
|
63
|
+
Perform a dry-run AST analysis (SAST, SCA, IaC).
|
|
64
|
+
Checks API Key, runs the scans, and outputs the results in JSON format to stdout.
|
|
65
|
+
Does NOT create assets or deploys on Conviso Platform.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
results = {}
|
|
69
|
+
|
|
70
|
+
# Run SAST
|
|
71
|
+
sast_results = execute_sast_dry_run(
|
|
72
|
+
flow_context, end_commit, start_commit, repository_dir,
|
|
73
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login
|
|
74
|
+
)
|
|
75
|
+
results['sast'] = sast_results
|
|
76
|
+
|
|
77
|
+
# Run SCA
|
|
78
|
+
sca_results = execute_sca_dry_run(
|
|
79
|
+
flow_context, repository_dir, custom_sca_tags, scanner_timeout
|
|
80
|
+
)
|
|
81
|
+
results['sca'] = sca_results
|
|
82
|
+
|
|
83
|
+
# Run IaC
|
|
84
|
+
iac_results = execute_iac_dry_run(
|
|
85
|
+
flow_context, repository_dir, scanner_timeout
|
|
86
|
+
)
|
|
87
|
+
results['iac'] = iac_results
|
|
88
|
+
|
|
89
|
+
print(json.dumps(results, indent=2))
|
|
90
|
+
|
|
91
|
+
if cleanup:
|
|
92
|
+
LOGGER.info("🧹 Cleaning up ...")
|
|
93
|
+
cleaner = Cleaner()
|
|
94
|
+
cleaner.cleanup()
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
traceback.print_exc(file=sys.stderr)
|
|
98
|
+
on_http_error(e)
|
|
99
|
+
sys.exit(1)
|
|
@@ -15,7 +15,7 @@ from convisoappsec.flow import GitAdapter
|
|
|
15
15
|
from convisoappsec.flowcli.context import pass_flow_context
|
|
16
16
|
from convisoappsec.logger import LOGGER, log_and_notify_ast_event
|
|
17
17
|
from convisoappsec.common.cleaner import Cleaner
|
|
18
|
-
|
|
18
|
+
from .dry_run import dry_run
|
|
19
19
|
|
|
20
20
|
def get_default_params_values(cmd_params):
|
|
21
21
|
""" Further information in https://click.palletsprojects.com/en/8.1.x/api/?highlight=params#click.Command.params
|
|
@@ -425,3 +425,4 @@ def ast():
|
|
|
425
425
|
|
|
426
426
|
|
|
427
427
|
ast.add_command(run)
|
|
428
|
+
ast.add_command(dry_run)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import click
|
|
2
2
|
# TODO: refactoring. all deploy create share some behavior
|
|
3
|
-
from convisoappsec.flow.util import project_metrics
|
|
4
3
|
from convisoappsec.flow.version_searchers import TimeBasedVersionSearcher
|
|
5
4
|
from convisoappsec.flow.version_control_system_adapter import GitAdapter
|
|
6
5
|
from convisoappsec.flowcli.context import pass_flow_context
|
|
@@ -72,7 +71,6 @@ def time_(flow_context, create_context, tag_tracker_context, attach_diff):
|
|
|
72
71
|
previous_version=previous_version,
|
|
73
72
|
diff_content=diff_content,
|
|
74
73
|
metrics=deploy_metrics,
|
|
75
|
-
project_metrics=project_metrics(repository_dir),
|
|
76
74
|
commit_authors=authors_data
|
|
77
75
|
)
|
|
78
76
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import click
|
|
2
2
|
# TODO: refactoring. all deploy create share some behavior
|
|
3
|
-
from convisoappsec.flow.util import project_metrics
|
|
4
3
|
from convisoappsec.flowcli.context import pass_flow_context
|
|
5
4
|
from convisoappsec.flow.version_searchers import SortedByVersioningStyle
|
|
6
5
|
from convisoappsec.flow.version_control_system_adapter import GitAdapter
|
|
@@ -103,7 +102,6 @@ def versioning_style(
|
|
|
103
102
|
previous_version=previous_version,
|
|
104
103
|
diff_content=diff_content,
|
|
105
104
|
metrics=deploy_metrics,
|
|
106
|
-
project_metrics=project_metrics(repository_dir),
|
|
107
105
|
commit_authors=authors_data
|
|
108
106
|
)
|
|
109
107
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import click_log
|
|
3
|
+
import json
|
|
4
|
+
import traceback
|
|
5
|
+
import sys
|
|
6
|
+
from convisoappsec.common.box import ContainerWrapper
|
|
7
|
+
from convisoappsec.flowcli import help_option
|
|
8
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
9
|
+
from convisoappsec.logger import LOGGER
|
|
10
|
+
from convisoappsec.flowcli.common import on_http_error
|
|
11
|
+
from convisoappsec.common.cleaner import Cleaner
|
|
12
|
+
|
|
13
|
+
def execute_dry_run(flow_context, repository_dir, scanner_timeout):
|
|
14
|
+
REQUIRED_CODEBASE_PATH = '/code'
|
|
15
|
+
IAC_IMAGE_NAME = 'iac_scanner_checkov'
|
|
16
|
+
IAC_SCAN_FILENAME = '/{}.json'.format(IAC_IMAGE_NAME)
|
|
17
|
+
containers_map = {
|
|
18
|
+
IAC_IMAGE_NAME: {
|
|
19
|
+
'repository_dir': repository_dir,
|
|
20
|
+
'repository_name': IAC_IMAGE_NAME,
|
|
21
|
+
'tag': 'unstable',
|
|
22
|
+
'command': [
|
|
23
|
+
'-c', REQUIRED_CODEBASE_PATH,
|
|
24
|
+
'-o', IAC_SCAN_FILENAME,
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
conviso_rest_api = flow_context.create_conviso_rest_api_client()
|
|
30
|
+
token = conviso_rest_api.docker_registry.get_sast_token()
|
|
31
|
+
|
|
32
|
+
LOGGER.info('💬 Preparing Environment...')
|
|
33
|
+
scanners_wrapper = ContainerWrapper(
|
|
34
|
+
token=token,
|
|
35
|
+
containers_map=containers_map,
|
|
36
|
+
logger=LOGGER,
|
|
37
|
+
timeout=scanner_timeout
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
LOGGER.info('💬 Starting IaC...')
|
|
41
|
+
scanners_wrapper.run()
|
|
42
|
+
|
|
43
|
+
results_list = []
|
|
44
|
+
for r in scanners_wrapper.scanners:
|
|
45
|
+
report_filepath = r.results
|
|
46
|
+
if report_filepath:
|
|
47
|
+
try:
|
|
48
|
+
with open(report_filepath, 'r') as f:
|
|
49
|
+
results_list.append(json.load(f))
|
|
50
|
+
except Exception as e:
|
|
51
|
+
click.echo(f"Error reading result file {report_filepath}: {e}", file=sys.stderr)
|
|
52
|
+
|
|
53
|
+
if len(results_list) == 1:
|
|
54
|
+
return results_list[0]
|
|
55
|
+
return results_list
|
|
56
|
+
|
|
57
|
+
@click.command(name='dry-run')
|
|
58
|
+
@click.option(
|
|
59
|
+
'-r', '--repository-dir', default=".", show_default=True,
|
|
60
|
+
type=click.Path(exists=True, resolve_path=True), required=False,
|
|
61
|
+
help="The source code repository directory."
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--scanner-timeout", hidden=True, required=False, default=7200, type=int,
|
|
65
|
+
help="Set timeout for each scanner"
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
'--cleanup', default=False, is_flag=True, show_default=True,
|
|
69
|
+
help="Clean up system resources."
|
|
70
|
+
)
|
|
71
|
+
@help_option
|
|
72
|
+
@pass_flow_context
|
|
73
|
+
def dry_run(flow_context, repository_dir, scanner_timeout, cleanup):
|
|
74
|
+
"""
|
|
75
|
+
Perform a dry-run IAC analysis.
|
|
76
|
+
Checks API Key, runs the scan, and outputs the results in JSON format to stdout.
|
|
77
|
+
Does NOT create assets or deploys on Conviso Platform.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
results = execute_dry_run(flow_context, repository_dir, scanner_timeout)
|
|
81
|
+
|
|
82
|
+
if results:
|
|
83
|
+
print(json.dumps(results, indent=2))
|
|
84
|
+
else:
|
|
85
|
+
print(json.dumps({}, indent=2))
|
|
86
|
+
|
|
87
|
+
if cleanup:
|
|
88
|
+
LOGGER.info("🧹 Cleaning up ...")
|
|
89
|
+
cleaner = Cleaner()
|
|
90
|
+
cleaner.cleanup()
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
on_http_error(e)
|
|
94
|
+
sys.exit(1)
|
|
@@ -2,6 +2,7 @@ import click
|
|
|
2
2
|
|
|
3
3
|
from convisoappsec.flowcli import help_option
|
|
4
4
|
from .run import run
|
|
5
|
+
from .dry_run import dry_run
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@click.group()
|
|
@@ -11,6 +12,7 @@ def iac():
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
iac.add_command(run)
|
|
15
|
+
iac.add_command(dry_run)
|
|
14
16
|
|
|
15
17
|
iac.epilog = '''
|
|
16
18
|
Run flow iac COMMAND --help for more information on a command.
|
convisoappsec/flowcli/iac/run.py
CHANGED
|
@@ -142,6 +142,7 @@ def run(context, flow_context, asset_id, company_id, repository_dir, send_to_flo
|
|
|
142
142
|
def deploy_results_to_conviso(
|
|
143
143
|
conviso_api, results_filepaths, asset_id, company_id, flow_context, deploy_id, commit_ref=None, control_sync_status_id=None
|
|
144
144
|
):
|
|
145
|
+
|
|
145
146
|
results_context = click.progressbar(results_filepaths, label="Sending results to the Conviso Platform...")
|
|
146
147
|
|
|
147
148
|
with results_context as reports:
|
|
@@ -150,7 +151,7 @@ def deploy_results_to_conviso(
|
|
|
150
151
|
with open(report_path) as report_file:
|
|
151
152
|
data = parse_data(json.load(report_file))
|
|
152
153
|
except Exception:
|
|
153
|
-
LOGGER.
|
|
154
|
+
LOGGER.warning(f"⚠️ Error processing report file. Our technical team has been notified.")
|
|
154
155
|
full_trace = traceback.format_exc()
|
|
155
156
|
log_and_notify_ast_event(
|
|
156
157
|
flow_context=flow_context, company_id=company_id, asset_id=asset_id,
|
|
@@ -181,6 +182,8 @@ def deploy_results_to_conviso(
|
|
|
181
182
|
except ResponseError as error:
|
|
182
183
|
if error.code == 'RECORD_NOT_UNIQUE':
|
|
183
184
|
continue
|
|
185
|
+
elif error.code == "Record not found" or "Record not found" in str(error):
|
|
186
|
+
continue
|
|
184
187
|
else:
|
|
185
188
|
retry_handler = RetryHandler(
|
|
186
189
|
flow_context=flow_context, company_id=company_id, asset_id=asset_id
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
import traceback
|
|
4
|
+
import json
|
|
5
|
+
from convisoappsec.sast.sastbox import SASTBox
|
|
6
|
+
from docker.errors import APIError
|
|
7
|
+
import time
|
|
8
|
+
from convisoappsec.flow import GitAdapter
|
|
9
|
+
from convisoappsec.flowcli import help_option
|
|
10
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
11
|
+
from convisoappsec.logger import LOGGER
|
|
12
|
+
from convisoappsec.common.cleaner import Cleaner
|
|
13
|
+
from convisoappsec.flowcli.common import on_http_error
|
|
14
|
+
|
|
15
|
+
class DryRunSASTBox(SASTBox):
|
|
16
|
+
def recovery_technologies_file(self):
|
|
17
|
+
# Skip technology recovery and update for dry-run
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def perform_dry_run_sastbox_scan(
|
|
21
|
+
conviso_rest_api, sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login, repository_dir, end_commit, start_commit, logger
|
|
22
|
+
):
|
|
23
|
+
max_retries = 5
|
|
24
|
+
retries = 0
|
|
25
|
+
sastbox = DryRunSASTBox(registry=sastbox_registry, repository_name=sastbox_repository_name, tag=sastbox_tag)
|
|
26
|
+
pull_progress_bar = click.progressbar(length=sastbox.size, label="Performing SAST download...")
|
|
27
|
+
|
|
28
|
+
while retries < max_retries:
|
|
29
|
+
try:
|
|
30
|
+
if not sastbox_skip_login:
|
|
31
|
+
logger("Checking SASTBox authorization...")
|
|
32
|
+
token = conviso_rest_api.docker_registry.get_sast_token()
|
|
33
|
+
sastbox.login(token)
|
|
34
|
+
|
|
35
|
+
with pull_progress_bar as progressbar:
|
|
36
|
+
for downloaded_chunk in sastbox.pull():
|
|
37
|
+
progressbar.update(downloaded_chunk)
|
|
38
|
+
break
|
|
39
|
+
except APIError as e:
|
|
40
|
+
retries += 1
|
|
41
|
+
logger(f"Retrying {retries}/{max_retries}...")
|
|
42
|
+
time.sleep(1)
|
|
43
|
+
|
|
44
|
+
if retries == max_retries:
|
|
45
|
+
logger("Max retries reached. Failed to perform SAST download.")
|
|
46
|
+
raise Exception(f"Max retries reached. Could not complete the SAST download. Error: {str(e)}")
|
|
47
|
+
|
|
48
|
+
logger("Starting SAST scan diff...")
|
|
49
|
+
|
|
50
|
+
reports = sastbox.run_scan_diff(repository_dir, end_commit, start_commit, log=logger)
|
|
51
|
+
|
|
52
|
+
logger("SAST scan diff done.")
|
|
53
|
+
|
|
54
|
+
results_filepaths = []
|
|
55
|
+
for r in reports:
|
|
56
|
+
try:
|
|
57
|
+
file_path = str(r)
|
|
58
|
+
results_filepaths.append(file_path)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
click.echo(f"Error decoding file path: {r} with error {e}.", file=sys.stderr)
|
|
61
|
+
|
|
62
|
+
return results_filepaths
|
|
63
|
+
|
|
64
|
+
def log_func(msg, new_line=True):
|
|
65
|
+
click.echo(msg, nl=new_line, err=True)
|
|
66
|
+
|
|
67
|
+
def execute_dry_run(flow_context, end_commit, start_commit, repository_dir,
|
|
68
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login):
|
|
69
|
+
git_adapter = GitAdapter(repository_dir)
|
|
70
|
+
end_commit = end_commit or git_adapter.head_commit
|
|
71
|
+
start_commit = start_commit or git_adapter.empty_repository_tree_commit
|
|
72
|
+
|
|
73
|
+
if start_commit == end_commit:
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
conviso_rest_api = flow_context.create_conviso_rest_api_client()
|
|
77
|
+
|
|
78
|
+
results_filepaths = perform_dry_run_sastbox_scan(
|
|
79
|
+
conviso_rest_api, sastbox_registry, sastbox_repository_name, sastbox_tag,
|
|
80
|
+
sastbox_skip_login, repository_dir, end_commit, start_commit, log_func
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
results_list = []
|
|
84
|
+
for path in results_filepaths:
|
|
85
|
+
try:
|
|
86
|
+
with open(path, 'r') as f:
|
|
87
|
+
results_list.append(json.load(f))
|
|
88
|
+
except Exception as e:
|
|
89
|
+
click.echo(f"Error reading result file {path}: {e}", file=sys.stderr)
|
|
90
|
+
|
|
91
|
+
if len(results_list) == 1:
|
|
92
|
+
return results_list[0]
|
|
93
|
+
return results_list
|
|
94
|
+
|
|
95
|
+
@click.command(name='dry-run')
|
|
96
|
+
@click.option(
|
|
97
|
+
"-s", "--start-commit", required=False,
|
|
98
|
+
help="If no value is set so the empty tree hash commit is used."
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"-e", "--end-commit", required=False,
|
|
102
|
+
help="If no value is set so the HEAD commit from the current branch is used"
|
|
103
|
+
)
|
|
104
|
+
@click.option(
|
|
105
|
+
"-r", "--repository-dir", default=".", show_default=True,
|
|
106
|
+
type=click.Path(exists=True, resolve_path=True), required=False,
|
|
107
|
+
help="The source code repository directory."
|
|
108
|
+
)
|
|
109
|
+
@click.option(
|
|
110
|
+
"--sastbox-registry", default="", required=False, hidden=True,
|
|
111
|
+
envvar=("CONVISO_SASTBOX_REGISTRY", "FLOW_SASTBOX_REGISTRY"),
|
|
112
|
+
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"--sastbox-repository-name", default="", required=False, hidden=True,
|
|
115
|
+
envvar=("CONVISO_SASTBOX_REPOSITORY_NAME", "FLOW_SASTBOX_REPOSITORY_NAME"),
|
|
116
|
+
)
|
|
117
|
+
@click.option(
|
|
118
|
+
"--sastbox-tag", default=SASTBox.DEFAULT_TAG, required=False, hidden=True,
|
|
119
|
+
envvar=("CONVISO_SASTBOX_TAG", "FLOW_SASTBOX_TAG"),
|
|
120
|
+
)
|
|
121
|
+
@click.option(
|
|
122
|
+
"--sastbox-skip-login/--sastbox-no-skip-login", default=False, required=False, hidden=True,
|
|
123
|
+
envvar=("CONVISO_SASTBOX_SKIP_LOGIN", "FLOW_SASTBOX_SKIP_LOGIN"),
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
'--cleanup', default=False, is_flag=True, show_default=True,
|
|
127
|
+
help="Clean up system resources."
|
|
128
|
+
)
|
|
129
|
+
@click.option(
|
|
130
|
+
"-o", "--output", required=False, help="Output the results to a JSON file."
|
|
131
|
+
)
|
|
132
|
+
@help_option
|
|
133
|
+
@pass_flow_context
|
|
134
|
+
def dry_run(flow_context, end_commit, start_commit, repository_dir,
|
|
135
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login, cleanup, output):
|
|
136
|
+
try:
|
|
137
|
+
results = execute_dry_run(
|
|
138
|
+
flow_context, end_commit, start_commit, repository_dir,
|
|
139
|
+
sastbox_registry, sastbox_repository_name, sastbox_tag, sastbox_skip_login
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if output:
|
|
143
|
+
with open(output, "w") as f:
|
|
144
|
+
json.dump(results if results else {}, f, indent=2)
|
|
145
|
+
LOGGER.info(f"Results saved to {output}")
|
|
146
|
+
elif results:
|
|
147
|
+
print(json.dumps(results, indent=2))
|
|
148
|
+
else:
|
|
149
|
+
print(json.dumps({}, indent=2))
|
|
150
|
+
|
|
151
|
+
if cleanup:
|
|
152
|
+
LOGGER.info("🧹 Cleaning up ...")
|
|
153
|
+
cleaner = Cleaner()
|
|
154
|
+
cleaner.cleanup()
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
traceback.print_exc(file=sys.stderr)
|
|
158
|
+
on_http_error(e)
|
|
159
|
+
sys.exit(1)
|
|
@@ -2,6 +2,7 @@ import click
|
|
|
2
2
|
|
|
3
3
|
from convisoappsec.flowcli import help_option
|
|
4
4
|
from .run import run
|
|
5
|
+
from .dry_run import dry_run
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@click.group()
|
|
@@ -11,6 +12,7 @@ def sast():
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
sast.add_command(run)
|
|
15
|
+
sast.add_command(dry_run)
|
|
14
16
|
|
|
15
17
|
sast.epilog = '''
|
|
16
18
|
Run flow sast COMMAND --help for more information on a command.
|
|
@@ -182,7 +182,7 @@ def generate(context, flow_context, asset_id, company_id, repository_dir, send_t
|
|
|
182
182
|
stderr=subprocess.DEVNULL
|
|
183
183
|
)
|
|
184
184
|
command = [f"./conviso/syft scan {repository_dir} -o cyclonedx-json={file_name} "
|
|
185
|
-
f"--select-catalogers '{','.join(catalogers)}'"]
|
|
185
|
+
f"--select-catalogers '{','.join(catalogers)}' --exclude ./conviso"]
|
|
186
186
|
|
|
187
187
|
subprocess.run(command, shell=True, check=True, capture_output=True)
|
|
188
188
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import click_log
|
|
3
|
+
import traceback
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from convisoappsec.common.box import ContainerWrapper
|
|
7
|
+
from convisoappsec.flowcli import help_option
|
|
8
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
9
|
+
from convisoappsec.logger import LOGGER
|
|
10
|
+
from convisoappsec.flowcli.common import on_http_error
|
|
11
|
+
from convisoappsec.common.cleaner import Cleaner
|
|
12
|
+
|
|
13
|
+
def log_func(msg, new_line=True):
|
|
14
|
+
click.echo(msg, nl=new_line, err=True)
|
|
15
|
+
|
|
16
|
+
def execute_dry_run(flow_context, repository_dir, custom_sca_tags, scanner_timeout):
|
|
17
|
+
REQUIRED_CODEBASE_PATH = '/code'
|
|
18
|
+
OSV_SCANNER_IMAGE_NAME = 'osv_scanner'
|
|
19
|
+
|
|
20
|
+
scanners = {
|
|
21
|
+
OSV_SCANNER_IMAGE_NAME: {
|
|
22
|
+
'repository_name': OSV_SCANNER_IMAGE_NAME,
|
|
23
|
+
'tag': 'latest',
|
|
24
|
+
'command': [
|
|
25
|
+
'-c', REQUIRED_CODEBASE_PATH,
|
|
26
|
+
'-f', 'json',
|
|
27
|
+
'-o', '/{}.json'.format(OSV_SCANNER_IMAGE_NAME)
|
|
28
|
+
],
|
|
29
|
+
'repository_dir': repository_dir
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if custom_sca_tags:
|
|
34
|
+
for custom_tag in custom_sca_tags:
|
|
35
|
+
scan_name, tag = custom_tag
|
|
36
|
+
if scan_name in scanners.keys():
|
|
37
|
+
scanners[scan_name]['tag'] = tag
|
|
38
|
+
|
|
39
|
+
conviso_rest_api = flow_context.create_conviso_rest_api_client()
|
|
40
|
+
token = conviso_rest_api.docker_registry.get_sast_token()
|
|
41
|
+
|
|
42
|
+
LOGGER.info('💬 Preparing Environment...')
|
|
43
|
+
scabox = ContainerWrapper(
|
|
44
|
+
token=token,
|
|
45
|
+
containers_map=scanners,
|
|
46
|
+
logger=LOGGER,
|
|
47
|
+
timeout=scanner_timeout
|
|
48
|
+
)
|
|
49
|
+
LOGGER.info('💬 Starting SCA...')
|
|
50
|
+
scabox.run()
|
|
51
|
+
|
|
52
|
+
results_list = []
|
|
53
|
+
for unit in scabox.scanners:
|
|
54
|
+
file_path = unit.results
|
|
55
|
+
if file_path:
|
|
56
|
+
try:
|
|
57
|
+
with open(file_path, 'r') as f:
|
|
58
|
+
results_list.append(json.load(f))
|
|
59
|
+
except Exception as e:
|
|
60
|
+
click.echo(f"Error reading result file {file_path}: {e}", file=sys.stderr)
|
|
61
|
+
|
|
62
|
+
if len(results_list) == 1:
|
|
63
|
+
return results_list[0]
|
|
64
|
+
return results_list
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.command(name='dry-run')
|
|
68
|
+
@click.option(
|
|
69
|
+
'-r', '--repository-dir', default=".", show_default=True,
|
|
70
|
+
type=click.Path(exists=True, resolve_path=True), required=False,
|
|
71
|
+
help="The source code repository directory."
|
|
72
|
+
)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--custom-sca-tags", hidden=True, required=False, multiple=True, type=(str, str),
|
|
75
|
+
help="It should be passed as <repository_name> <image_tag>."
|
|
76
|
+
)
|
|
77
|
+
@click.option(
|
|
78
|
+
"--scanner-timeout", hidden=True, required=False, default=7200, type=int,
|
|
79
|
+
help="Set timeout for each scanner"
|
|
80
|
+
)
|
|
81
|
+
@click.option(
|
|
82
|
+
'--cleanup', default=False, is_flag=True, show_default=True,
|
|
83
|
+
help="Clean up system resources."
|
|
84
|
+
)
|
|
85
|
+
@help_option
|
|
86
|
+
@pass_flow_context
|
|
87
|
+
def dry_run(flow_context, repository_dir, custom_sca_tags, scanner_timeout, cleanup):
|
|
88
|
+
"""
|
|
89
|
+
Perform a dry-run SCA analysis.
|
|
90
|
+
Checks API Key, runs the scan, and outputs the results in JSON format to stdout.
|
|
91
|
+
Does NOT create assets or deploys on Conviso Platform.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
results = execute_dry_run(flow_context, repository_dir, custom_sca_tags, scanner_timeout)
|
|
95
|
+
|
|
96
|
+
if results:
|
|
97
|
+
print(json.dumps(results, indent=2))
|
|
98
|
+
else:
|
|
99
|
+
print(json.dumps({}, indent=2))
|
|
100
|
+
|
|
101
|
+
if cleanup:
|
|
102
|
+
LOGGER.info("🧹 Cleaning up ...")
|
|
103
|
+
cleaner = Cleaner()
|
|
104
|
+
cleaner.cleanup()
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
on_http_error(e)
|
|
108
|
+
sys.exit(1)
|
|
@@ -2,6 +2,7 @@ import click
|
|
|
2
2
|
|
|
3
3
|
from convisoappsec.flowcli import help_option
|
|
4
4
|
from .run import run
|
|
5
|
+
from .dry_run import dry_run
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@click.group()
|
|
@@ -11,6 +12,7 @@ def sca():
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
sca.add_command(run)
|
|
15
|
+
sca.add_command(dry_run)
|
|
14
16
|
|
|
15
17
|
sca.epilog = '''
|
|
16
18
|
Run flow sca COMMAND --help for more information on a command.
|
convisoappsec/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '3.0.
|
|
1
|
+
__version__ = '3.0.1'
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import yaml
|
|
2
|
-
import tempfile
|
|
3
|
-
|
|
4
|
-
from .source_code_scanner import SourceCodeScanner
|
|
5
|
-
from .exceptions import SourceCodeScannerException
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class SCC(SourceCodeScanner):
|
|
9
|
-
|
|
10
|
-
def __init__(
|
|
11
|
-
self,
|
|
12
|
-
source_code_dir,
|
|
13
|
-
container_source_dir = '/code',
|
|
14
|
-
create_source_code_volume = True
|
|
15
|
-
):
|
|
16
|
-
super().__init__(
|
|
17
|
-
source_code_dir,
|
|
18
|
-
create_source_code_volume=create_source_code_volume
|
|
19
|
-
)
|
|
20
|
-
self.__scan_result = {}
|
|
21
|
-
self.__container_source_dir = container_source_dir
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def repository(self):
|
|
25
|
-
return 'convisoappsec/scc'
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def tag(self):
|
|
29
|
-
return 'latest'
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def container_source_dir(self):
|
|
33
|
-
return self.__container_source_dir
|
|
34
|
-
|
|
35
|
-
def _read_scan_stdout(self, stdout_generator):
|
|
36
|
-
with tempfile.TemporaryFile() as yaml_output:
|
|
37
|
-
for chunk in stdout_generator:
|
|
38
|
-
yaml_output.write(chunk)
|
|
39
|
-
|
|
40
|
-
yaml_output.seek(0)
|
|
41
|
-
|
|
42
|
-
self.__scan_result = yaml.load(
|
|
43
|
-
yaml_output,
|
|
44
|
-
Loader=yaml.FullLoader
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def summary(self):
|
|
49
|
-
summary = self.__scan_result.get('SUM')
|
|
50
|
-
if not summary:
|
|
51
|
-
raise SourceCodeScannerException(
|
|
52
|
-
'Unexpected error retrienving source code summary metrics'
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
return summary
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def total_source_code_lines(self):
|
|
59
|
-
return self.summary.get('code')
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def command(self):
|
|
63
|
-
return [
|
|
64
|
-
'--no-cocomo',
|
|
65
|
-
'--no-complexity',
|
|
66
|
-
'--format',
|
|
67
|
-
'cloc-yaml'
|
|
68
|
-
]
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import docker
|
|
2
|
-
from contextlib import suppress
|
|
3
|
-
import tempfile
|
|
4
|
-
from uuid import uuid4
|
|
5
|
-
|
|
6
|
-
from convisoappsec.flow.util import SourceCodeCompressor
|
|
7
|
-
from .exceptions import SourceCodeScannerException
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SourceCodeScanner(object):
|
|
11
|
-
SUCCESS_EXIT_CODE = 0
|
|
12
|
-
'''
|
|
13
|
-
hooks:
|
|
14
|
-
def _pre_pull(self):
|
|
15
|
-
:return: void
|
|
16
|
-
|
|
17
|
-
def _capture_stdout(self, stdout_bytes)
|
|
18
|
-
:param stdout_bytes: chunks generated by stdout
|
|
19
|
-
:paramtype stdout_bytes: bytes
|
|
20
|
-
:return: void
|
|
21
|
-
_pre_scan
|
|
22
|
-
_scan_stdout
|
|
23
|
-
_post_scan
|
|
24
|
-
'''
|
|
25
|
-
|
|
26
|
-
def __init__(self, source_code_dir, create_source_code_volume = True):
|
|
27
|
-
uuid = str(uuid4())
|
|
28
|
-
self.docker = docker.from_env(version="auto")
|
|
29
|
-
self.__container_name = "source_code_scanner_{0}".format(
|
|
30
|
-
uuid
|
|
31
|
-
)
|
|
32
|
-
self.__source_code_dir = source_code_dir
|
|
33
|
-
self.__create_source_code_volume = create_source_code_volume
|
|
34
|
-
|
|
35
|
-
if self.__create_source_code_volume:
|
|
36
|
-
self.__source_code_volume_name = "source_code_scanner_src_{0}".format(
|
|
37
|
-
uuid
|
|
38
|
-
)
|
|
39
|
-
else:
|
|
40
|
-
self.__source_code_volume_name = None
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def repository(self):
|
|
44
|
-
raise Exception('Not implemented yet!')
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def tag(self):
|
|
48
|
-
raise Exception('Not implemented yet!')
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def command(self):
|
|
52
|
-
raise Exception('Not implemented yet!')
|
|
53
|
-
|
|
54
|
-
@property
|
|
55
|
-
def container_source_dir(self):
|
|
56
|
-
raise Exception('Not implemented yet!')
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def image(self):
|
|
60
|
-
return "{repository}:{tag}".format(
|
|
61
|
-
repository=self.repository,
|
|
62
|
-
tag=self.tag,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
@property
|
|
66
|
-
def volumes(self):
|
|
67
|
-
if not self.__create_source_code_volume:
|
|
68
|
-
return {}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
self.__source_code_volume_name: {
|
|
72
|
-
'bind': self.container_source_dir,
|
|
73
|
-
'mode': 'rw',
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
def __get_container(self):
|
|
78
|
-
return self.docker.containers.get(
|
|
79
|
-
self.__container_name
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
def __get_source_code_volume(self):
|
|
83
|
-
return self.docker.volumes.get(
|
|
84
|
-
self.__source_code_volume_name
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
@property
|
|
88
|
-
def __container(self):
|
|
89
|
-
try:
|
|
90
|
-
return self.__get_container()
|
|
91
|
-
except docker.errors.NotFound:
|
|
92
|
-
return self.__create_container()
|
|
93
|
-
|
|
94
|
-
def __create_container(self):
|
|
95
|
-
return self.docker.containers.create(
|
|
96
|
-
self.image,
|
|
97
|
-
name=self.__container_name,
|
|
98
|
-
volumes=self.volumes,
|
|
99
|
-
detach=True,
|
|
100
|
-
command=self.command,
|
|
101
|
-
working_dir=self.container_source_dir if self.container_source_dir != '/code' else '/code'
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
def __pull_image(self):
|
|
105
|
-
if self.has_pre_pull:
|
|
106
|
-
self._pre_pull()
|
|
107
|
-
|
|
108
|
-
self.docker.images.pull(self.repository, self.tag)
|
|
109
|
-
|
|
110
|
-
def __load_source_code(self):
|
|
111
|
-
container = self.__container
|
|
112
|
-
|
|
113
|
-
with tempfile.TemporaryFile() as fileobj:
|
|
114
|
-
compressor = SourceCodeCompressor(
|
|
115
|
-
self.__source_code_dir
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
compressor.write_to(fileobj)
|
|
119
|
-
fileobj.seek(0)
|
|
120
|
-
|
|
121
|
-
container.put_archive(
|
|
122
|
-
self.container_source_dir,
|
|
123
|
-
fileobj
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def scan(self):
|
|
127
|
-
self.__pull_image()
|
|
128
|
-
self.__load_source_code()
|
|
129
|
-
container = self.__container
|
|
130
|
-
|
|
131
|
-
container.start()
|
|
132
|
-
|
|
133
|
-
if self.has_read_scan_stderr:
|
|
134
|
-
self._read_scan_stderr(
|
|
135
|
-
container.logs(
|
|
136
|
-
stream=True, stdout=False, stderr=True
|
|
137
|
-
)
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
if self.has_read_scan_stdout:
|
|
141
|
-
self._read_scan_stdout(
|
|
142
|
-
container.logs(
|
|
143
|
-
stream=True, stdout=True, stderr=False
|
|
144
|
-
)
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
wait_result = container.wait()
|
|
148
|
-
status_code = wait_result.get('StatusCode')
|
|
149
|
-
|
|
150
|
-
if not status_code == self.SUCCESS_EXIT_CODE:
|
|
151
|
-
raise SourceCodeScannerException(
|
|
152
|
-
'Source code scanning fail'
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
def __has_method(self, method_name):
|
|
156
|
-
return hasattr(self, method_name)
|
|
157
|
-
|
|
158
|
-
@property
|
|
159
|
-
def has_read_scan_stdout(self):
|
|
160
|
-
return self.__has_method('_read_scan_stdout')
|
|
161
|
-
|
|
162
|
-
@property
|
|
163
|
-
def has_read_scan_stderr(self):
|
|
164
|
-
return self.__has_method('_read_scan_stderr')
|
|
165
|
-
|
|
166
|
-
@property
|
|
167
|
-
def has_pre_pull(self):
|
|
168
|
-
return self.__has_method('_pre_pull')
|
|
169
|
-
|
|
170
|
-
def __del__(self):
|
|
171
|
-
with suppress(Exception):
|
|
172
|
-
container = self.__get_container()
|
|
173
|
-
container.remove()
|
|
174
|
-
|
|
175
|
-
with suppress(Exception):
|
|
176
|
-
source_code_volume = self.__get_source_code_volume()
|
|
177
|
-
source_code_volume.remove()
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from convisoappsec.flow.source_code_scanner import SCC
|
|
2
|
-
from convisoappsec.logger import LOGGER
|
|
3
|
-
import docker
|
|
4
|
-
|
|
5
|
-
def project_metrics(source_code_dir):
|
|
6
|
-
try:
|
|
7
|
-
scanner = SCC(source_code_dir, create_source_code_volume=False)
|
|
8
|
-
scanner.scan()
|
|
9
|
-
return {
|
|
10
|
-
'total_lines': scanner.total_source_code_lines
|
|
11
|
-
}
|
|
12
|
-
except docker.errors.APIError as e:
|
|
13
|
-
LOGGER.error('Error on fetch project metrics')
|
|
14
|
-
LOGGER.exception(e)
|
|
15
|
-
return {}
|
|
16
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|