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,104 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from os import SEEK_SET
|
|
5
|
+
from urllib.parse import urljoin
|
|
6
|
+
|
|
7
|
+
import jsonschema
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
PRODUCTION_API_URL = "https://api.convisoappsec.com"
|
|
11
|
+
STAGING_API_URL = "https://api.staging.convisoappsec.com"
|
|
12
|
+
DEVELOPMENT_API_URL = "http://localhost:3000"
|
|
13
|
+
DEFAULT_API_URL = PRODUCTION_API_URL
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RequestsSession(requests.Session):
|
|
17
|
+
|
|
18
|
+
def __init__(self, base_url):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.base_url = base_url
|
|
21
|
+
|
|
22
|
+
def request(self, method, url, *args, **kwargs):
|
|
23
|
+
url = urljoin(self.base_url, url)
|
|
24
|
+
|
|
25
|
+
return super().request(
|
|
26
|
+
method, url, *args, **kwargs
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FlowAPIException(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FlowAPIAccessDeniedException(FlowAPIException):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DeployNotFoundException(FlowAPIException):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DockerRegistry(object):
|
|
43
|
+
SAST_ENDPOINT = '/auth/public_auth'
|
|
44
|
+
|
|
45
|
+
def __init__(self, client):
|
|
46
|
+
self.client = client
|
|
47
|
+
|
|
48
|
+
def get_sast_token(self):
|
|
49
|
+
session = self.client.requests_session
|
|
50
|
+
response = session.get(self.SAST_ENDPOINT)
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
return response.text
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RESTClient(object):
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
url=STAGING_API_URL,
|
|
60
|
+
key=None,
|
|
61
|
+
insecure=False,
|
|
62
|
+
user_agent=None,
|
|
63
|
+
ci_provider_name=None
|
|
64
|
+
):
|
|
65
|
+
self.url = url
|
|
66
|
+
self.insecure = insecure
|
|
67
|
+
self.key = key
|
|
68
|
+
self.user_agent = user_agent
|
|
69
|
+
self.ci_provider_name = ci_provider_name
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def requests_session(self):
|
|
73
|
+
session = RequestsSession(self.url)
|
|
74
|
+
session.verify = not self.insecure
|
|
75
|
+
|
|
76
|
+
session.headers.update({
|
|
77
|
+
'x-api-key': self.key,
|
|
78
|
+
'x-flowcli-ci-provider-name': self.ci_provider_name
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if self.user_agent:
|
|
82
|
+
user_agent_header = {}
|
|
83
|
+
name = self.user_agent.get('name')
|
|
84
|
+
version = self.user_agent.get('version')
|
|
85
|
+
|
|
86
|
+
if name and version:
|
|
87
|
+
user_agent_header_fmt = "{name}/{version}"
|
|
88
|
+
user_agent_header_content = user_agent_header_fmt.format(
|
|
89
|
+
name=name,
|
|
90
|
+
version=version,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
user_agent_header = {
|
|
94
|
+
'User-Agent': user_agent_header_content
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
session.headers.update(user_agent_header)
|
|
98
|
+
|
|
99
|
+
return session
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def docker_registry(self):
|
|
104
|
+
return DockerRegistry(self)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import shutil
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RunnerCleanup:
|
|
9
|
+
"""Class to handle CI/CD runner cleanup operations"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, temp_dirs: Optional[List[str]] = None):
|
|
12
|
+
"""
|
|
13
|
+
Initialize cleanup class
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
temp_dirs: Optional list of temporary directories to clean
|
|
17
|
+
"""
|
|
18
|
+
self.temp_dirs = temp_dirs or [
|
|
19
|
+
"/tmp",
|
|
20
|
+
"/var/tmp",
|
|
21
|
+
str(Path.home() / ".cache")
|
|
22
|
+
]
|
|
23
|
+
self.is_runner = self._is_running_in_ci()
|
|
24
|
+
self._check_requirements()
|
|
25
|
+
|
|
26
|
+
def _is_running_in_ci(self) -> bool:
|
|
27
|
+
"""Check if running in a CI/CD environment"""
|
|
28
|
+
ci_indicators = {
|
|
29
|
+
'CI': None,
|
|
30
|
+
'GITLAB_CI': None,
|
|
31
|
+
'GITHUB_ACTIONS': None,
|
|
32
|
+
'JENKINS_URL': None,
|
|
33
|
+
'TRAVIS': None,
|
|
34
|
+
'CIRCLECI': None,
|
|
35
|
+
'BUILD_ID': None,
|
|
36
|
+
'CI_JOB_ID': None,
|
|
37
|
+
'GITHUB_RUN_ID': None,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Check if any CI-specific environment variable is set
|
|
41
|
+
for var in ci_indicators:
|
|
42
|
+
if os.environ.get(var):
|
|
43
|
+
print(f"Detected CI environment: {var}")
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
# Additional check for runner-specific variables
|
|
47
|
+
if os.environ.get('RUNNER_TEMP') or os.environ.get('RUNNER_WORKSPACE'):
|
|
48
|
+
print("Detected GitHub Actions runner environment")
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
print("No CI environment detected")
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def _run_command(self, command: str) -> Optional[str]:
|
|
55
|
+
"""Execute shell command and return output"""
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
command,
|
|
60
|
+
shell=True,
|
|
61
|
+
check=True,
|
|
62
|
+
stdout=subprocess.PIPE,
|
|
63
|
+
stderr=subprocess.PIPE,
|
|
64
|
+
text=True
|
|
65
|
+
)
|
|
66
|
+
return result.stdout.strip()
|
|
67
|
+
except subprocess.CalledProcessError as e:
|
|
68
|
+
print(f"Error executing '{command}': {e.stderr}")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def _check_requirements(self) -> None:
|
|
72
|
+
"""Check system requirements"""
|
|
73
|
+
if not self._run_command("docker --version"):
|
|
74
|
+
raise RuntimeError("Docker is not installed or not accessible")
|
|
75
|
+
|
|
76
|
+
def get_disk_space(self) -> tuple[float, float, float]:
|
|
77
|
+
"""Get disk space information in GB"""
|
|
78
|
+
stat = shutil.disk_usage("/")
|
|
79
|
+
total = stat.total / (1024 ** 3)
|
|
80
|
+
used = stat.used / (1024 ** 3)
|
|
81
|
+
free = stat.free / (1024 ** 3)
|
|
82
|
+
return total, used, free
|
|
83
|
+
|
|
84
|
+
def cleanup_docker(self) -> None:
|
|
85
|
+
"""Clean up Docker resources"""
|
|
86
|
+
commands = [
|
|
87
|
+
"docker container prune -f",
|
|
88
|
+
"docker image prune -f",
|
|
89
|
+
"docker volume prune -f",
|
|
90
|
+
"docker network prune -f"
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
for cmd in commands:
|
|
94
|
+
self._run_command(cmd)
|
|
95
|
+
|
|
96
|
+
def cleanup_temp(self) -> None:
|
|
97
|
+
"""Clean up temporary directories"""
|
|
98
|
+
|
|
99
|
+
for dir_path in self.temp_dirs:
|
|
100
|
+
dir_path = Path(dir_path)
|
|
101
|
+
if not dir_path.exists():
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
for item in dir_path.iterdir():
|
|
105
|
+
try:
|
|
106
|
+
if item.is_file():
|
|
107
|
+
item.unlink()
|
|
108
|
+
elif item.is_dir():
|
|
109
|
+
shutil.rmtree(item, ignore_errors=True)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"Failed to remove {item}: {e}")
|
|
112
|
+
|
|
113
|
+
def cleanup_all(self):
|
|
114
|
+
"""Perform full cleanup and return before/after disk space stats"""
|
|
115
|
+
print("Cleaning up ...")
|
|
116
|
+
|
|
117
|
+
self.cleanup_docker()
|
|
118
|
+
self.cleanup_temp()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
from convisoappsec.common.graphql.low_client import GraphQLClient
|
|
3
|
+
from convisoappsec.flow.graphql_api.beta.resources_api import IssuesAPI
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConvisoGraphQLClientBeta():
|
|
7
|
+
DEFAULT_AUTHORIZATION_HEADER_NAME = 'x-api-key'
|
|
8
|
+
|
|
9
|
+
def __init__(self, api_url, api_key):
|
|
10
|
+
headers = {
|
|
11
|
+
self.DEFAULT_AUTHORIZATION_HEADER_NAME: api_key
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
self.__low_client = GraphQLClient(api_url, headers)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def issues(self):
|
|
18
|
+
return IssuesAPI(self.__low_client)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.normalize import Normalize
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CreateOrUpdateContainerFindingInput:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
asset_id,
|
|
8
|
+
title,
|
|
9
|
+
description,
|
|
10
|
+
severity,
|
|
11
|
+
solution,
|
|
12
|
+
reference,
|
|
13
|
+
affected_version,
|
|
14
|
+
package,
|
|
15
|
+
cve,
|
|
16
|
+
patched_version,
|
|
17
|
+
category,
|
|
18
|
+
original_issue_id_from_tool
|
|
19
|
+
):
|
|
20
|
+
self.asset_id = asset_id
|
|
21
|
+
self.title = title
|
|
22
|
+
self.description = description
|
|
23
|
+
self.severity = Normalize.normalize_severity(severity)
|
|
24
|
+
self.solution = solution
|
|
25
|
+
self.reference = reference
|
|
26
|
+
self.affected_version = affected_version
|
|
27
|
+
self.package = package
|
|
28
|
+
self.patched_version = patched_version
|
|
29
|
+
self.original_issue_id_from_tool = original_issue_id_from_tool
|
|
30
|
+
self.category = self.process_field(category)
|
|
31
|
+
self.cve = self.process_field(cve)
|
|
32
|
+
|
|
33
|
+
def to_graphql_dict(self):
|
|
34
|
+
"""
|
|
35
|
+
This function returns a dictionary containing various attributes of an
|
|
36
|
+
asset in a GraphQL format.
|
|
37
|
+
"""
|
|
38
|
+
return {
|
|
39
|
+
"assetId": int(self.asset_id),
|
|
40
|
+
"title": self.title,
|
|
41
|
+
"description": self.description,
|
|
42
|
+
"severity": self.severity,
|
|
43
|
+
"solution": self.solution,
|
|
44
|
+
"reference": self.reference,
|
|
45
|
+
"affectedVersion": self.affected_version,
|
|
46
|
+
"package": self.package,
|
|
47
|
+
"cve": self.cve,
|
|
48
|
+
"patchedVersion": self.patched_version,
|
|
49
|
+
"category": self.category,
|
|
50
|
+
"originalIssueIdFromTool": self.original_issue_id_from_tool
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def process_field(value):
|
|
55
|
+
"""
|
|
56
|
+
Processes a field to ensure it is converted into a string.
|
|
57
|
+
|
|
58
|
+
- If the value is a list, it joins the items into a comma-separated string.
|
|
59
|
+
- If the value is a string, it returns the string as is.
|
|
60
|
+
- If the value is neither a list nor a string, it returns an empty string.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
value (list | str | Any): The value to process.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
str: The processed string representation of the value.
|
|
67
|
+
"""
|
|
68
|
+
if isinstance(value, list):
|
|
69
|
+
return ' , '.join(value)
|
|
70
|
+
elif isinstance(value, str):
|
|
71
|
+
return value
|
|
72
|
+
return ''
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Normalize:
|
|
2
|
+
|
|
3
|
+
@staticmethod
|
|
4
|
+
def normalize_severity(severity):
|
|
5
|
+
"""
|
|
6
|
+
The function normalizes severity by validating and returning a standardized severity level.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
validate_severity = ["LOW", "MEDIUM", "HIGH", "CRITICAL", "NOTIFICATION"]
|
|
10
|
+
if severity.upper() in validate_severity:
|
|
11
|
+
return severity.upper()
|
|
12
|
+
else:
|
|
13
|
+
return validate_severity[0]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.normalize import Normalize
|
|
2
|
+
|
|
3
|
+
class CreateSastFindingInput:
|
|
4
|
+
def __init__(
|
|
5
|
+
self,
|
|
6
|
+
asset_id,
|
|
7
|
+
code_snippet,
|
|
8
|
+
file_name,
|
|
9
|
+
vulnerable_line,
|
|
10
|
+
first_line,
|
|
11
|
+
title,
|
|
12
|
+
description,
|
|
13
|
+
severity,
|
|
14
|
+
reference,
|
|
15
|
+
category,
|
|
16
|
+
original_issue_id_from_tool,
|
|
17
|
+
solution,
|
|
18
|
+
control_sync_status_id
|
|
19
|
+
):
|
|
20
|
+
self.asset_id = asset_id
|
|
21
|
+
self.severity = Normalize.normalize_severity(severity)
|
|
22
|
+
self.title = title
|
|
23
|
+
self.description = description
|
|
24
|
+
self.code_snippet = code_snippet
|
|
25
|
+
self.file_name = file_name
|
|
26
|
+
self.vulnerable_line = int(vulnerable_line)
|
|
27
|
+
self.first_line = int(first_line)
|
|
28
|
+
self.reference = reference
|
|
29
|
+
self.category = category
|
|
30
|
+
self.original_issue_id_from_tool = original_issue_id_from_tool
|
|
31
|
+
self.solution = solution
|
|
32
|
+
self.control_sync_status_id = control_sync_status_id
|
|
33
|
+
|
|
34
|
+
def to_graphql_dict(self):
|
|
35
|
+
"""
|
|
36
|
+
This function returns a dictionary containing various attributes of an
|
|
37
|
+
asset in a GraphQL format.
|
|
38
|
+
"""
|
|
39
|
+
return {
|
|
40
|
+
"assetId": int(self.asset_id),
|
|
41
|
+
"severity": self.severity,
|
|
42
|
+
"title": self.title,
|
|
43
|
+
"description": self.description,
|
|
44
|
+
"codeSnippet": self.code_snippet,
|
|
45
|
+
"fileName": self.file_name,
|
|
46
|
+
"vulnerableLine": int(self.vulnerable_line),
|
|
47
|
+
"firstLine": int(self.first_line),
|
|
48
|
+
"reference": self.reference,
|
|
49
|
+
"category": str(self.category),
|
|
50
|
+
"originalIssueIdFromTool": str(self.original_issue_id_from_tool),
|
|
51
|
+
"solution": str(self.solution),
|
|
52
|
+
"controlSyncStatusId": str(self.control_sync_status_id)
|
|
53
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.normalize import Normalize
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CreateScaFindingInput:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
asset_id,
|
|
8
|
+
title,
|
|
9
|
+
description,
|
|
10
|
+
severity,
|
|
11
|
+
solution,
|
|
12
|
+
reference,
|
|
13
|
+
file_name,
|
|
14
|
+
affected_version,
|
|
15
|
+
package,
|
|
16
|
+
cve,
|
|
17
|
+
patched_version,
|
|
18
|
+
category,
|
|
19
|
+
original_issue_id_from_tool,
|
|
20
|
+
control_sync_status_id
|
|
21
|
+
):
|
|
22
|
+
self.asset_id = asset_id
|
|
23
|
+
self.title = title
|
|
24
|
+
self.description = description
|
|
25
|
+
self.severity = Normalize.normalize_severity(severity)
|
|
26
|
+
self.solution = solution
|
|
27
|
+
self.reference = reference
|
|
28
|
+
self.file_name = file_name
|
|
29
|
+
self.affected_version = affected_version
|
|
30
|
+
self.package = package
|
|
31
|
+
self.patched_version = patched_version
|
|
32
|
+
self.original_issue_id_from_tool = original_issue_id_from_tool
|
|
33
|
+
self.category = self.process_field(category)
|
|
34
|
+
self.cve = self.process_field(cve)
|
|
35
|
+
self.control_sync_status_id = control_sync_status_id
|
|
36
|
+
|
|
37
|
+
def to_graphql_dict(self):
|
|
38
|
+
"""
|
|
39
|
+
This function returns a dictionary containing various attributes of an
|
|
40
|
+
asset in a GraphQL format.
|
|
41
|
+
"""
|
|
42
|
+
return {
|
|
43
|
+
"assetId": int(self.asset_id),
|
|
44
|
+
"title": self.title,
|
|
45
|
+
"description": self.description,
|
|
46
|
+
"severity": self.severity,
|
|
47
|
+
"solution": self.solution,
|
|
48
|
+
"reference": self.reference,
|
|
49
|
+
"fileName": self.file_name,
|
|
50
|
+
"affectedVersion": self.affected_version,
|
|
51
|
+
"package": self.package,
|
|
52
|
+
"cve": self.cve,
|
|
53
|
+
"patchedVersion": self.patched_version,
|
|
54
|
+
"category": self.category,
|
|
55
|
+
"originalIssueIdFromTool": self.original_issue_id_from_tool,
|
|
56
|
+
"controlSyncStatusId": self.control_sync_status_id
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def process_field(value):
|
|
61
|
+
"""
|
|
62
|
+
Processes a field to ensure it is converted into a string.
|
|
63
|
+
|
|
64
|
+
- If the value is a list, it joins the items into a comma-separated string.
|
|
65
|
+
- If the value is a string, it returns the string as is.
|
|
66
|
+
- If the value is neither a list nor a string, it returns an empty string.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
value (list | str | Any): The value to process.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
str: The processed string representation of the value.
|
|
73
|
+
"""
|
|
74
|
+
if isinstance(value, list):
|
|
75
|
+
return ' , '.join(value)
|
|
76
|
+
elif isinstance(value, str):
|
|
77
|
+
return value
|
|
78
|
+
return ''
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import jmespath
|
|
2
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.iac import CreateIacFindingInput
|
|
3
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.sast import CreateSastFindingInput
|
|
4
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.sca import CreateScaFindingInput
|
|
5
|
+
from convisoappsec.flow.graphql_api.beta.models.issues.container import CreateOrUpdateContainerFindingInput
|
|
6
|
+
from convisoappsec.flow.graphql_api.beta.schemas import mutations
|
|
7
|
+
from convisoappsec.flow.graphql_api.v1.schemas import resolvers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IssuesAPI(object):
|
|
11
|
+
""" To operations on Issues's (aka, findings and vulnerabilities)) in Conviso Platform. """
|
|
12
|
+
|
|
13
|
+
def __init__(self, conviso_graphql_client):
|
|
14
|
+
self.__conviso_graphql_client = conviso_graphql_client
|
|
15
|
+
|
|
16
|
+
def create_sast(self, sast_issue_model: CreateSastFindingInput):
|
|
17
|
+
graphql_variables = {
|
|
18
|
+
"input": sast_issue_model.to_graphql_dict()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
graphql_body_response = self.__conviso_graphql_client.execute(
|
|
22
|
+
mutations.CREATE_SAST_FINDING_INPUT,
|
|
23
|
+
graphql_variables
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
expected_path = 'createSastFinding.issue'
|
|
27
|
+
|
|
28
|
+
issue = jmespath.search(
|
|
29
|
+
expected_path,
|
|
30
|
+
graphql_body_response,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return issue
|
|
34
|
+
|
|
35
|
+
def create_sca(self, sca_issue_model: CreateScaFindingInput):
|
|
36
|
+
graphql_variables = {
|
|
37
|
+
"input": sca_issue_model.to_graphql_dict()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
graphql_body_response = self.__conviso_graphql_client.execute(
|
|
41
|
+
mutations.CREATE_SCA_FINDING_INPUT,
|
|
42
|
+
graphql_variables
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
expected_path = 'createScaFinding.issue'
|
|
46
|
+
|
|
47
|
+
issue = jmespath.search(
|
|
48
|
+
expected_path,
|
|
49
|
+
graphql_body_response,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return issue
|
|
53
|
+
|
|
54
|
+
def create_iac(self, issue_model: CreateIacFindingInput):
|
|
55
|
+
graphql_variables = {
|
|
56
|
+
"input": issue_model.to_graphql_dict()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
graphql_body_response = self.__conviso_graphql_client.execute(
|
|
60
|
+
mutations.CREATE_SAST_FINDING_INPUT,
|
|
61
|
+
graphql_variables
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expected_path = 'createSastFinding.issue'
|
|
65
|
+
|
|
66
|
+
issue = jmespath.search(
|
|
67
|
+
expected_path,
|
|
68
|
+
graphql_body_response,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return issue
|
|
72
|
+
|
|
73
|
+
def create_container(self, container_issue_model: CreateOrUpdateContainerFindingInput):
|
|
74
|
+
graphql_variables = {
|
|
75
|
+
"input": container_issue_model.to_graphql_dict()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
graphql_body_response = self.__conviso_graphql_client.execute(
|
|
79
|
+
mutations.CREATE_CONTAINER_FINDING_INPUT,
|
|
80
|
+
graphql_variables
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
expected_path = 'createOrUpdateContainerFinding.issue'
|
|
84
|
+
|
|
85
|
+
issue = jmespath.search(
|
|
86
|
+
expected_path,
|
|
87
|
+
graphql_body_response,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return issue
|
|
91
|
+
|
|
92
|
+
def auto_close_vulnerabilities(self, company_id, asset_id, statuses, page=1, vulnerability_type=None):
|
|
93
|
+
""" entry point for auto closing vulnerabilities on conviso platform """
|
|
94
|
+
if vulnerability_type is None:
|
|
95
|
+
vulnerability_type = ['SAST_FINDING', 'SCA_FINDING']
|
|
96
|
+
|
|
97
|
+
graphql_variables = {
|
|
98
|
+
'company_id': company_id,
|
|
99
|
+
'asset_id': asset_id,
|
|
100
|
+
'page': page,
|
|
101
|
+
'per_page': 100,
|
|
102
|
+
'statuses': statuses,
|
|
103
|
+
'failure_types': vulnerability_type
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
graphql_body_response = self.__conviso_graphql_client.execute(
|
|
107
|
+
resolvers.GET_ISSUES_FINGERPRINT,
|
|
108
|
+
graphql_variables
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
expected_path = 'issues'
|
|
112
|
+
|
|
113
|
+
issues = jmespath.search(
|
|
114
|
+
expected_path,
|
|
115
|
+
graphql_body_response
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return issues
|
|
119
|
+
|
|
120
|
+
def update_issue_status(self, issue_id, status, reason, control_sync_status_id):
|
|
121
|
+
""" Update issue status on conviso platform """
|
|
122
|
+
|
|
123
|
+
graphql_variables = {
|
|
124
|
+
'issueId': issue_id,
|
|
125
|
+
'status': status,
|
|
126
|
+
'reason': reason,
|
|
127
|
+
'controlSyncStatusId': control_sync_status_id
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
graphql_body_response = self.__conviso_graphql_client.execute(
|
|
131
|
+
mutations.UPDATE_ISSUE_STATUS,
|
|
132
|
+
graphql_variables
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
expected_path = 'changeIssueStatus.issue'
|
|
136
|
+
|
|
137
|
+
issue = jmespath.search(
|
|
138
|
+
expected_path,
|
|
139
|
+
graphql_body_response,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return issue
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
CREATE_SAST_FINDING_INPUT = """
|
|
2
|
+
mutation createSastFinding($input: CreateSastFindingInput!) {
|
|
3
|
+
createSastFinding(input: $input) {
|
|
4
|
+
issue {
|
|
5
|
+
id
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
CREATE_SCA_FINDING_INPUT = """
|
|
12
|
+
mutation createScaFinding($input: CreateScaFindingInput!) {
|
|
13
|
+
createScaFinding(input: $input) {
|
|
14
|
+
issue {
|
|
15
|
+
id
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
CREATE_IAC_FINDING_INPUT = """
|
|
22
|
+
mutation createOrUpdateIacFinding($input: CreateOrUpdateSastFindingInput!) {
|
|
23
|
+
createOrUpdateIacFinding(input: $input) {
|
|
24
|
+
issue {
|
|
25
|
+
id
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
CREATE_CONTAINER_FINDING_INPUT = """
|
|
32
|
+
mutation createOrUpdateContainerFinding($input: CreateOrUpdateContainerFindingInput!) {
|
|
33
|
+
createOrUpdateContainerFinding(input: $input) {
|
|
34
|
+
issue {
|
|
35
|
+
id
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
UPDATE_ISSUE_STATUS = """
|
|
42
|
+
mutation (
|
|
43
|
+
$issueId: ID!,
|
|
44
|
+
$status: IssueStatusLabel!,
|
|
45
|
+
$reason: String
|
|
46
|
+
$controlSyncStatusId: ID
|
|
47
|
+
) {
|
|
48
|
+
changeIssueStatus (
|
|
49
|
+
input: {
|
|
50
|
+
id: $issueId
|
|
51
|
+
status: $status
|
|
52
|
+
reason: $reason
|
|
53
|
+
controlSyncStatusId: $controlSyncStatusId
|
|
54
|
+
}
|
|
55
|
+
) {
|
|
56
|
+
issue {
|
|
57
|
+
id
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
File without changes
|
|
File without changes
|