fluidattacks_core_models 12.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.
- fluidattacks_core/models/__init__.py +0 -0
- fluidattacks_core/models/py.typed +0 -0
- fluidattacks_core/models/scanners/__init__.py +67 -0
- fluidattacks_core/models/scanners/apk_config.py +26 -0
- fluidattacks_core/models/scanners/common_config.py +27 -0
- fluidattacks_core/models/scanners/containers_sca_config.py +37 -0
- fluidattacks_core/models/scanners/cspm_config.py +45 -0
- fluidattacks_core/models/scanners/dast_config.py +47 -0
- fluidattacks_core/models/scanners/loader.py +74 -0
- fluidattacks_core/models/scanners/sast_config.py +15 -0
- fluidattacks_core/models/scanners/sbom_config.py +57 -0
- fluidattacks_core/models/scanners/sca_config.py +16 -0
- fluidattacks_core/models/scanners/scan_config.py +39 -0
- fluidattacks_core/models/scanners/sniffs_config.py +9 -0
- fluidattacks_core/models/state_machines/__init__.py +35 -0
- fluidattacks_core/models/state_machines/root_scanner.py +105 -0
- fluidattacks_core/models/state_machines/types.py +99 -0
- fluidattacks_core_models-12.0.0.dist-info/METADATA +15 -0
- fluidattacks_core_models-12.0.0.dist-info/RECORD +20 -0
- fluidattacks_core_models-12.0.0.dist-info/WHEEL +4 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from fluidattacks_core.models.scanners.apk_config import ApkConfig, ApkEnvironment
|
|
2
|
+
from fluidattacks_core.models.scanners.common_config import (
|
|
3
|
+
FalsePositivesConfig,
|
|
4
|
+
LocalesEnum,
|
|
5
|
+
OutputConfig,
|
|
6
|
+
OutputFormat,
|
|
7
|
+
)
|
|
8
|
+
from fluidattacks_core.models.scanners.containers_sca_config import (
|
|
9
|
+
ContainerImageConfig,
|
|
10
|
+
ContainerScaItem,
|
|
11
|
+
ContainersScaConfig,
|
|
12
|
+
DockerCredentialsConfig,
|
|
13
|
+
EcrCredentialsConfig,
|
|
14
|
+
)
|
|
15
|
+
from fluidattacks_core.models.scanners.cspm_config import AwsCredentials, AwsRole, CspmConfig
|
|
16
|
+
from fluidattacks_core.models.scanners.dast_config import (
|
|
17
|
+
DastApiAuth,
|
|
18
|
+
DastApiConfig,
|
|
19
|
+
DastConfig,
|
|
20
|
+
DastUrl,
|
|
21
|
+
)
|
|
22
|
+
from fluidattacks_core.models.scanners.loader import build_config, load_yaml_mapping
|
|
23
|
+
from fluidattacks_core.models.scanners.sast_config import SastConfig
|
|
24
|
+
from fluidattacks_core.models.scanners.sbom_config import (
|
|
25
|
+
SbomAwsCredentials,
|
|
26
|
+
SbomConfig,
|
|
27
|
+
SbomDockerCredentials,
|
|
28
|
+
SbomOutputConfig,
|
|
29
|
+
SbomPlatformOverrides,
|
|
30
|
+
SourceType,
|
|
31
|
+
)
|
|
32
|
+
from fluidattacks_core.models.scanners.sca_config import ScaConfig
|
|
33
|
+
from fluidattacks_core.models.scanners.scan_config import ScanConfig
|
|
34
|
+
from fluidattacks_core.models.scanners.sniffs_config import SniffsConfig
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"ApkConfig",
|
|
38
|
+
"ApkEnvironment",
|
|
39
|
+
"AwsCredentials",
|
|
40
|
+
"AwsRole",
|
|
41
|
+
"ContainerImageConfig",
|
|
42
|
+
"ContainerScaItem",
|
|
43
|
+
"ContainersScaConfig",
|
|
44
|
+
"CspmConfig",
|
|
45
|
+
"DastApiAuth",
|
|
46
|
+
"DastApiConfig",
|
|
47
|
+
"DastConfig",
|
|
48
|
+
"DastUrl",
|
|
49
|
+
"DockerCredentialsConfig",
|
|
50
|
+
"EcrCredentialsConfig",
|
|
51
|
+
"FalsePositivesConfig",
|
|
52
|
+
"LocalesEnum",
|
|
53
|
+
"OutputConfig",
|
|
54
|
+
"OutputFormat",
|
|
55
|
+
"SastConfig",
|
|
56
|
+
"SbomAwsCredentials",
|
|
57
|
+
"SbomConfig",
|
|
58
|
+
"SbomDockerCredentials",
|
|
59
|
+
"SbomOutputConfig",
|
|
60
|
+
"SbomPlatformOverrides",
|
|
61
|
+
"ScaConfig",
|
|
62
|
+
"ScanConfig",
|
|
63
|
+
"SniffsConfig",
|
|
64
|
+
"SourceType",
|
|
65
|
+
"build_config",
|
|
66
|
+
"load_yaml_mapping",
|
|
67
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ApkEnvironment(BaseModel):
|
|
5
|
+
model_config = {"extra": "forbid"}
|
|
6
|
+
apk_file_path: str
|
|
7
|
+
environment_id: str | None = None
|
|
8
|
+
|
|
9
|
+
def __eq__(self, other: object) -> bool:
|
|
10
|
+
"""Compare only on apk_file_path, ignoring environment_id."""
|
|
11
|
+
if not isinstance(other, ApkEnvironment):
|
|
12
|
+
return NotImplemented
|
|
13
|
+
return self.apk_file_path == other.apk_file_path
|
|
14
|
+
|
|
15
|
+
def __hash__(self) -> int:
|
|
16
|
+
"""Hash only on apk_file_path, ignoring environment_id."""
|
|
17
|
+
return hash(self.apk_file_path)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ApkConfig(BaseModel):
|
|
21
|
+
model_config = {"extra": "forbid"}
|
|
22
|
+
include: list[str] = []
|
|
23
|
+
exclude: list[str] = []
|
|
24
|
+
use_sast_analysis: StrictBool = True
|
|
25
|
+
environments: list[ApkEnvironment] | None = None
|
|
26
|
+
feature_preview: StrictBool | None = None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LocalesEnum(Enum):
|
|
7
|
+
EN = "EN"
|
|
8
|
+
ES = "ES"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OutputFormat(Enum):
|
|
12
|
+
CSV = "CSV"
|
|
13
|
+
SARIF = "SARIF"
|
|
14
|
+
ALL = "ALL"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OutputConfig(BaseModel):
|
|
18
|
+
model_config = {"extra": "forbid"}
|
|
19
|
+
file_path: str
|
|
20
|
+
format: OutputFormat
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FalsePositivesConfig(BaseModel):
|
|
24
|
+
model_config = {"extra": "forbid"}
|
|
25
|
+
findings: list[str] = []
|
|
26
|
+
method_names: list[str] = []
|
|
27
|
+
cve: list[str] = []
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EcrCredentialsConfig(BaseModel):
|
|
5
|
+
model_config = {"extra": "allow"}
|
|
6
|
+
access_key_id: str | None = None
|
|
7
|
+
secret_access_key: str | None = None
|
|
8
|
+
session_token: str | None = None
|
|
9
|
+
role_arn: str | None = None
|
|
10
|
+
external_id: str | None = None
|
|
11
|
+
aws_region: str | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DockerCredentialsConfig(BaseModel):
|
|
15
|
+
model_config = {"extra": "allow"}
|
|
16
|
+
docker_username: str
|
|
17
|
+
docker_password: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ContainerImageConfig(BaseModel):
|
|
21
|
+
model_config = {"extra": "allow"}
|
|
22
|
+
use_docker_daemon: StrictBool | None = None
|
|
23
|
+
os_override: str | None = None
|
|
24
|
+
arch_override: str | None = None
|
|
25
|
+
ecr_credentials: EcrCredentialsConfig | None = None
|
|
26
|
+
docker_credentials: DockerCredentialsConfig | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ContainerScaItem(BaseModel):
|
|
30
|
+
model_config = {"extra": "allow"}
|
|
31
|
+
image_uri: str
|
|
32
|
+
image_config: ContainerImageConfig | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ContainersScaConfig(BaseModel):
|
|
36
|
+
model_config = {"extra": "allow"}
|
|
37
|
+
images: list[ContainerScaItem]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, StrictBool, field_validator, model_validator
|
|
2
|
+
|
|
3
|
+
from fluidattacks_core.models.scanners.loader import LoaderValidationInfo, resolve_env_var
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AwsCredentials(BaseModel):
|
|
7
|
+
model_config = {"extra": "forbid"}
|
|
8
|
+
access_key_id: str
|
|
9
|
+
secret_access_key: str
|
|
10
|
+
session_token: str | None = None
|
|
11
|
+
environment_id: str | None = None
|
|
12
|
+
|
|
13
|
+
@field_validator("access_key_id", "secret_access_key", "session_token", mode="before")
|
|
14
|
+
@classmethod
|
|
15
|
+
def _resolve_credentials(cls, v: object, info: LoaderValidationInfo) -> object:
|
|
16
|
+
return resolve_env_var(v, info)
|
|
17
|
+
|
|
18
|
+
@model_validator(mode="after")
|
|
19
|
+
def _validate_key_session_token_pairing(self) -> "AwsCredentials":
|
|
20
|
+
is_temporary = self.access_key_id.startswith("ASIA")
|
|
21
|
+
if is_temporary and not self.session_token:
|
|
22
|
+
msg = "Temporary credentials (ASIA key) require a session_token"
|
|
23
|
+
raise ValueError(msg)
|
|
24
|
+
if not is_temporary and self.session_token:
|
|
25
|
+
msg = "Static credentials (AKIA key) must not include a session_token"
|
|
26
|
+
raise ValueError(msg)
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AwsRole(BaseModel):
|
|
31
|
+
model_config = {"extra": "forbid"}
|
|
32
|
+
role: str = Field(repr=False)
|
|
33
|
+
external_id: str | None = Field(default=None, repr=False)
|
|
34
|
+
environment_id: str | None = None
|
|
35
|
+
|
|
36
|
+
@field_validator("role", "external_id", mode="before")
|
|
37
|
+
@classmethod
|
|
38
|
+
def _resolve_credentials(cls, v: object, info: LoaderValidationInfo) -> object:
|
|
39
|
+
return resolve_env_var(v, info)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CspmConfig(BaseModel):
|
|
43
|
+
model_config = {"extra": "forbid"}
|
|
44
|
+
aws_credentials: list[AwsCredentials | AwsRole] = []
|
|
45
|
+
feature_preview: StrictBool | None = None
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool, field_validator
|
|
2
|
+
|
|
3
|
+
from fluidattacks_core.models.scanners.loader import LoaderValidationInfo, resolve_env_var
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DastUrl(BaseModel):
|
|
7
|
+
model_config = {"extra": "forbid"}
|
|
8
|
+
url: str
|
|
9
|
+
environment_id: str | None = None
|
|
10
|
+
|
|
11
|
+
def __eq__(self, other: object) -> bool:
|
|
12
|
+
"""Compare only on url, ignoring environment_id."""
|
|
13
|
+
if not isinstance(other, DastUrl):
|
|
14
|
+
return NotImplemented
|
|
15
|
+
return self.url == other.url
|
|
16
|
+
|
|
17
|
+
def __hash__(self) -> int:
|
|
18
|
+
"""Hash only on url, ignoring environment_id."""
|
|
19
|
+
return hash(self.url)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DastApiAuth(BaseModel):
|
|
23
|
+
model_config = {"extra": "forbid"}
|
|
24
|
+
type: str
|
|
25
|
+
key: str
|
|
26
|
+
value: str
|
|
27
|
+
|
|
28
|
+
@field_validator("key", "value", mode="before")
|
|
29
|
+
@classmethod
|
|
30
|
+
def _resolve_credentials(cls, v: object, info: LoaderValidationInfo) -> object:
|
|
31
|
+
return resolve_env_var(v, info)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DastApiConfig(BaseModel):
|
|
35
|
+
model_config = {"extra": "forbid"}
|
|
36
|
+
base_url: str
|
|
37
|
+
api_specs: list[str]
|
|
38
|
+
authentication: list[DastApiAuth] = []
|
|
39
|
+
environment_id: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DastConfig(BaseModel):
|
|
43
|
+
model_config = {"extra": "forbid"}
|
|
44
|
+
urls: list[DastUrl] = []
|
|
45
|
+
api: list[DastApiConfig] = []
|
|
46
|
+
feature_preview: StrictBool | None = None
|
|
47
|
+
strict_connectivity: StrictBool = False
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TypeVar, cast
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from pydantic import ValidationInfo as _PydanticValidationInfo
|
|
11
|
+
|
|
12
|
+
LoaderValidationInfo = _PydanticValidationInfo[dict[str, bool] | None]
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T", bound=BaseModel)
|
|
15
|
+
|
|
16
|
+
LOGGER = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
_ENV_PLACEHOLDER = re.compile(r"\$\{([A-Z_][A-Z0-9_]*)\}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _sub_env_var(m: re.Match[str]) -> str:
|
|
22
|
+
var_name = m.group(1)
|
|
23
|
+
value = os.environ.get(var_name)
|
|
24
|
+
if value is None:
|
|
25
|
+
LOGGER.warning("env var '%s' not set; substituting empty string", var_name)
|
|
26
|
+
return ""
|
|
27
|
+
LOGGER.info("env var '%s' set", var_name)
|
|
28
|
+
return value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def resolve_env_var(v: object, info: LoaderValidationInfo) -> object:
|
|
32
|
+
if not isinstance(v, str) or not (info.context or {}).get("resolve_env"):
|
|
33
|
+
return v
|
|
34
|
+
matches = list(_ENV_PLACEHOLDER.finditer(v))
|
|
35
|
+
if not matches:
|
|
36
|
+
return v
|
|
37
|
+
LOGGER.info("resolving %d placeholder(s) in field value", len(matches))
|
|
38
|
+
if len(matches) == 1 and matches[0].span() == (0, len(v)):
|
|
39
|
+
var_name = matches[0].group(1)
|
|
40
|
+
value = os.environ.get(var_name)
|
|
41
|
+
if value is None:
|
|
42
|
+
LOGGER.warning("env var '%s' not set; field will be None", var_name)
|
|
43
|
+
return None
|
|
44
|
+
LOGGER.info("env var '%s' set", var_name)
|
|
45
|
+
return value
|
|
46
|
+
return _ENV_PLACEHOLDER.sub(_sub_env_var, v)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_yaml_mapping(path: str) -> Mapping[str, object]:
|
|
50
|
+
file_path = Path(path)
|
|
51
|
+
with file_path.open(encoding="utf-8") as file:
|
|
52
|
+
data = cast("object", yaml.safe_load(file))
|
|
53
|
+
|
|
54
|
+
if data is None:
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
if not isinstance(data, Mapping):
|
|
58
|
+
msg = "YAML root must be a mapping"
|
|
59
|
+
raise TypeError(msg)
|
|
60
|
+
|
|
61
|
+
return data
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_config(path: str, model_class: type[T]) -> T:
|
|
65
|
+
raw_data = load_yaml_mapping(path)
|
|
66
|
+
config = model_class.model_validate(raw_data, context={"resolve_env": True})
|
|
67
|
+
extras = cast("Mapping[str, object] | None", config.model_extra)
|
|
68
|
+
if extras:
|
|
69
|
+
LOGGER.warning(
|
|
70
|
+
"Some keys were not recognized: %s. "
|
|
71
|
+
"The analysis will be performed only using the supported keys",
|
|
72
|
+
", ".join(sorted(extras)),
|
|
73
|
+
)
|
|
74
|
+
return config
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SastConfig(BaseModel):
|
|
5
|
+
model_config = {"extra": "forbid"}
|
|
6
|
+
include: list[str] | None = None
|
|
7
|
+
exclude: list[str] | None = None
|
|
8
|
+
recursion_limit: int | None = None
|
|
9
|
+
use_inspects: StrictBool | None = None
|
|
10
|
+
file_size_limit: StrictBool | None = None
|
|
11
|
+
multifile: StrictBool | None = None
|
|
12
|
+
use_report_soon: StrictBool | None = None
|
|
13
|
+
report_drafts: StrictBool | None = None
|
|
14
|
+
include_sniffs: StrictBool | None = None
|
|
15
|
+
feature_preview: str | None = None
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, StrictBool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SourceType(Enum):
|
|
7
|
+
DIRECTORY = "dir"
|
|
8
|
+
DOCKER = "docker"
|
|
9
|
+
DOCKER_DAEMON = "docker-daemon"
|
|
10
|
+
ECR = "ecr"
|
|
11
|
+
ECR_WITH_CREDENTIALS = "ecr-with-credentials"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SbomDockerCredentials(BaseModel):
|
|
15
|
+
model_config = {"extra": "forbid"}
|
|
16
|
+
username: str | None = None
|
|
17
|
+
password: str | None = None
|
|
18
|
+
use_docker_daemon: StrictBool | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SbomPlatformOverrides(BaseModel):
|
|
22
|
+
model_config = {"extra": "forbid"}
|
|
23
|
+
os_override: str | None = None
|
|
24
|
+
arch_override: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SbomAwsCredentials(BaseModel):
|
|
28
|
+
model_config = {"extra": "forbid"}
|
|
29
|
+
external_id: str | None = None
|
|
30
|
+
role: str | None = None
|
|
31
|
+
region: str | None = None
|
|
32
|
+
access_key_id: str | None = None
|
|
33
|
+
secret_access_key: str | None = None
|
|
34
|
+
session_token: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SbomOutputConfig(BaseModel):
|
|
38
|
+
model_config = {"extra": "forbid"}
|
|
39
|
+
name: str
|
|
40
|
+
format: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SbomConfig(BaseModel):
|
|
44
|
+
model_config = {"extra": "forbid"}
|
|
45
|
+
include: list[str] | None = None
|
|
46
|
+
exclude: list[str] | None = None
|
|
47
|
+
output: SbomOutputConfig | None = None
|
|
48
|
+
source: str | None = None
|
|
49
|
+
source_type: SourceType | None = None
|
|
50
|
+
image_uri: str | None = None
|
|
51
|
+
environment_id: str | None = None
|
|
52
|
+
docker_credentials: SbomDockerCredentials | None = None
|
|
53
|
+
platform_overrides: SbomPlatformOverrides | None = None
|
|
54
|
+
aws_credentials: SbomAwsCredentials | None = None
|
|
55
|
+
include_package_metadata: StrictBool | None = None
|
|
56
|
+
include_package_advisories: StrictBool | None = None
|
|
57
|
+
feature_preview: StrictBool | None = None
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ScaExclusion(BaseModel):
|
|
5
|
+
model_config = {"extra": "forbid"}
|
|
6
|
+
CVE: str
|
|
7
|
+
paths: list[str]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ScaConfig(BaseModel):
|
|
11
|
+
model_config = {"extra": "forbid"}
|
|
12
|
+
include: list[str] | None = None
|
|
13
|
+
exclude: list[str] | None = None
|
|
14
|
+
use_spots_sca: StrictBool | None = None
|
|
15
|
+
feature_preview: StrictBool | None = None
|
|
16
|
+
exclusions: list[ScaExclusion] = []
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool
|
|
2
|
+
|
|
3
|
+
from fluidattacks_core.models.scanners.apk_config import ApkConfig
|
|
4
|
+
from fluidattacks_core.models.scanners.common_config import (
|
|
5
|
+
FalsePositivesConfig,
|
|
6
|
+
LocalesEnum,
|
|
7
|
+
OutputConfig,
|
|
8
|
+
)
|
|
9
|
+
from fluidattacks_core.models.scanners.containers_sca_config import ContainersScaConfig
|
|
10
|
+
from fluidattacks_core.models.scanners.cspm_config import CspmConfig
|
|
11
|
+
from fluidattacks_core.models.scanners.dast_config import DastConfig
|
|
12
|
+
from fluidattacks_core.models.scanners.sast_config import SastConfig
|
|
13
|
+
from fluidattacks_core.models.scanners.sbom_config import SbomConfig
|
|
14
|
+
from fluidattacks_core.models.scanners.sca_config import ScaConfig
|
|
15
|
+
from fluidattacks_core.models.scanners.sniffs_config import SniffsConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ScanConfig(BaseModel):
|
|
19
|
+
model_config = {"extra": "allow"}
|
|
20
|
+
working_dir: str | None = None
|
|
21
|
+
namespace: str | None = None
|
|
22
|
+
commit: str | None = None
|
|
23
|
+
language: LocalesEnum | None = None
|
|
24
|
+
execution_id: str | None = None
|
|
25
|
+
checks: list[str] | None = None
|
|
26
|
+
strict: StrictBool | None = None
|
|
27
|
+
strictness_threshold: str | None = None
|
|
28
|
+
tracing_opt_out: StrictBool | None = None
|
|
29
|
+
debug: StrictBool | None = None
|
|
30
|
+
output: OutputConfig | None = None
|
|
31
|
+
sast: SastConfig | None = None
|
|
32
|
+
sca: ScaConfig | None = None
|
|
33
|
+
containers_sca: ContainersScaConfig | None = None
|
|
34
|
+
sbom: SbomConfig | None = None
|
|
35
|
+
dast: DastConfig | None = None
|
|
36
|
+
cspm: CspmConfig | None = None
|
|
37
|
+
apk: ApkConfig | None = None
|
|
38
|
+
false_positives_identified: FalsePositivesConfig | None = None
|
|
39
|
+
ss: SniffsConfig | None = None
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictBool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SniffsConfig(BaseModel):
|
|
5
|
+
model_config = {"extra": "allow"}
|
|
6
|
+
include: list[str] | None = None
|
|
7
|
+
exclude: list[str] | None = None
|
|
8
|
+
min_entropy: float | None = None
|
|
9
|
+
feature_preview: StrictBool | None = None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from fluidattacks_core.models.state_machines.root_scanner import (
|
|
2
|
+
ContextualizesScanInput,
|
|
3
|
+
ContextualizesScanOptions,
|
|
4
|
+
PerRootScanOptions,
|
|
5
|
+
StateMachineExecutePerRootInput,
|
|
6
|
+
TJobNameSegment,
|
|
7
|
+
TracesScanInput,
|
|
8
|
+
TracesScanOptions,
|
|
9
|
+
TStorageKey,
|
|
10
|
+
)
|
|
11
|
+
from fluidattacks_core.models.state_machines.types import (
|
|
12
|
+
StateMachine,
|
|
13
|
+
StateMachineCloningInput,
|
|
14
|
+
StateMachineExecuteScannerInput,
|
|
15
|
+
StateMachineExecuteSiftsInput,
|
|
16
|
+
StateMachineRequestSbomMailInput,
|
|
17
|
+
TCloningTrigger,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ContextualizesScanInput",
|
|
22
|
+
"ContextualizesScanOptions",
|
|
23
|
+
"PerRootScanOptions",
|
|
24
|
+
"StateMachine",
|
|
25
|
+
"StateMachineCloningInput",
|
|
26
|
+
"StateMachineExecutePerRootInput",
|
|
27
|
+
"StateMachineExecuteScannerInput",
|
|
28
|
+
"StateMachineExecuteSiftsInput",
|
|
29
|
+
"StateMachineRequestSbomMailInput",
|
|
30
|
+
"TCloningTrigger",
|
|
31
|
+
"TJobNameSegment",
|
|
32
|
+
"TStorageKey",
|
|
33
|
+
"TracesScanInput",
|
|
34
|
+
"TracesScanOptions",
|
|
35
|
+
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from hashlib import sha256
|
|
3
|
+
from typing import Annotated, Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from pydantic import (
|
|
6
|
+
AfterValidator,
|
|
7
|
+
BaseModel,
|
|
8
|
+
ConfigDict,
|
|
9
|
+
Field,
|
|
10
|
+
StringConstraints,
|
|
11
|
+
computed_field,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
MACHINE_NAME_DIGEST_LENGTH = 18
|
|
15
|
+
MAX_EXECUTION_NAME = 80
|
|
16
|
+
|
|
17
|
+
STORAGE_KEY_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*\Z")
|
|
18
|
+
UNSAFE_NAME_CHARS = re.compile(r"[^A-Za-z0-9_-]")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _ensure_storage_key(value: str) -> str:
|
|
22
|
+
if not STORAGE_KEY_PATTERN.fullmatch(value) or ".." in value:
|
|
23
|
+
msg = f"invalid storage key: {value!r}"
|
|
24
|
+
raise ValueError(msg)
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
TStorageKey = Annotated[str, AfterValidator(_ensure_storage_key)]
|
|
29
|
+
TVisibleAscii = Annotated[str, StringConstraints(pattern=r"^[!-~]+$")]
|
|
30
|
+
TJobNameSegment = Annotated[
|
|
31
|
+
str, StringConstraints(min_length=1, pattern=r"^[A-Za-z0-9][A-Za-z0-9_-]*$")
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PerRootScanOptions(BaseModel):
|
|
36
|
+
model_config = ConfigDict(extra="forbid", frozen=True, revalidate_instances="always")
|
|
37
|
+
config_id: TStorageKey
|
|
38
|
+
group_name: TStorageKey
|
|
39
|
+
root_nickname: TStorageKey
|
|
40
|
+
user_email: TVisibleAscii = Field(..., min_length=1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ContextualizesScanOptions(PerRootScanOptions):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TracesScanOptions(PerRootScanOptions):
|
|
48
|
+
root_id: TStorageKey
|
|
49
|
+
commit: Annotated[str, StringConstraints(pattern=r"^[0-9a-f]{40}$")]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
TOptions = TypeVar("TOptions", bound=PerRootScanOptions)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class StateMachineExecutePerRootInput(BaseModel, Generic[TOptions]):
|
|
56
|
+
model_config = ConfigDict(extra="forbid", frozen=True, serialize_by_alias=True)
|
|
57
|
+
job_definition: str = Field(..., min_length=1, serialization_alias="jobDefinition")
|
|
58
|
+
job_queue: str = Field(..., min_length=1, serialization_alias="jobQueue")
|
|
59
|
+
scanner_name: TJobNameSegment = Field(..., serialization_alias="scannerName")
|
|
60
|
+
options: TOptions = Field(..., exclude=True)
|
|
61
|
+
|
|
62
|
+
@computed_field(alias="configId") # type: ignore[prop-decorator]
|
|
63
|
+
@property
|
|
64
|
+
def config_id(self) -> str:
|
|
65
|
+
return self.options.config_id
|
|
66
|
+
|
|
67
|
+
@computed_field(alias="groupName") # type: ignore[prop-decorator]
|
|
68
|
+
@property
|
|
69
|
+
def group_name(self) -> str:
|
|
70
|
+
return self.options.group_name
|
|
71
|
+
|
|
72
|
+
@computed_field(alias="rootNickname") # type: ignore[prop-decorator]
|
|
73
|
+
@property
|
|
74
|
+
def root_nickname(self) -> str:
|
|
75
|
+
return self.options.root_nickname
|
|
76
|
+
|
|
77
|
+
@computed_field(alias="executionOptions") # type: ignore[prop-decorator]
|
|
78
|
+
@property
|
|
79
|
+
def execution_options(self) -> str:
|
|
80
|
+
return self.options.model_dump_json()
|
|
81
|
+
|
|
82
|
+
def to_wire(self) -> dict[str, str]:
|
|
83
|
+
return self.model_dump(mode="json", by_alias=True)
|
|
84
|
+
|
|
85
|
+
def get_machine_name(self) -> str:
|
|
86
|
+
digest = sha256(
|
|
87
|
+
(
|
|
88
|
+
f"{self.options.group_name}\x00{self.options.root_nickname}"
|
|
89
|
+
f"\x00{self.scanner_name}\x00{self.options.config_id}"
|
|
90
|
+
).encode()
|
|
91
|
+
).hexdigest()[:MACHINE_NAME_DIGEST_LENGTH]
|
|
92
|
+
readable = UNSAFE_NAME_CHARS.sub(
|
|
93
|
+
"_",
|
|
94
|
+
f"{self.options.group_name}_{self.options.root_nickname}_{self.scanner_name}",
|
|
95
|
+
)
|
|
96
|
+
budget = MAX_EXECUTION_NAME - MACHINE_NAME_DIGEST_LENGTH - 1
|
|
97
|
+
return f"{readable[:budget]}_{digest}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ContextualizesScanInput(StateMachineExecutePerRootInput[ContextualizesScanOptions]):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TracesScanInput(StateMachineExecutePerRootInput[TracesScanOptions]):
|
|
105
|
+
pass
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import Literal, NamedTuple
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, computed_field
|
|
7
|
+
|
|
8
|
+
TCloningTrigger = Literal[
|
|
9
|
+
"clone_with_warp_scheduler",
|
|
10
|
+
"clone_no_special_network",
|
|
11
|
+
"root_activation",
|
|
12
|
+
"upload_git_root_file_mutation",
|
|
13
|
+
"add_git_root_mutation",
|
|
14
|
+
"migration",
|
|
15
|
+
"update_git_root_mutation",
|
|
16
|
+
"execute_machine_flow",
|
|
17
|
+
"sync_git_root_mutation",
|
|
18
|
+
"update_group_mutation",
|
|
19
|
+
"update_group_managed_mutation",
|
|
20
|
+
"reattack",
|
|
21
|
+
"clone_roots_that_have_not_cloned",
|
|
22
|
+
"re_queue_reattacks_scheduler",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StateMachine(NamedTuple):
|
|
27
|
+
arn: str
|
|
28
|
+
name: str
|
|
29
|
+
creation_date: datetime
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StateMachineCloningInput(BaseModel):
|
|
33
|
+
model_config = {"extra": "forbid"}
|
|
34
|
+
group_name: str = Field(..., serialization_alias="groupName")
|
|
35
|
+
network_name: str | None = Field(default=None, serialization_alias="networkName")
|
|
36
|
+
roots: list[str] = Field(..., serialization_alias="roots")
|
|
37
|
+
user_email: str = Field(..., serialization_alias="userEmail")
|
|
38
|
+
trigger: TCloningTrigger = Field(serialization_alias="trigger")
|
|
39
|
+
execute_sifts: bool = Field(..., serialization_alias="executeSifts")
|
|
40
|
+
cloning_job_queue: str = Field(
|
|
41
|
+
default="integrates_clone", serialization_alias="cloningJobQueue"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@computed_field(alias="configId") # type: ignore[prop-decorator]
|
|
45
|
+
@cached_property
|
|
46
|
+
def config_id(self) -> str:
|
|
47
|
+
return str(uuid4().time)
|
|
48
|
+
|
|
49
|
+
def get_machine_name(self) -> str:
|
|
50
|
+
return f"{self.group_name}_{self.trigger}-{self.config_id}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class StateMachineExecuteScannerInput(BaseModel):
|
|
54
|
+
model_config = {"extra": "forbid"}
|
|
55
|
+
config_id: str = Field(
|
|
56
|
+
default_factory=lambda: str(uuid4().time), serialization_alias="configId"
|
|
57
|
+
)
|
|
58
|
+
group_name: str = Field(..., serialization_alias="groupName")
|
|
59
|
+
image_refs: list[str] | None = Field(default=None, serialization_alias="imageRefs")
|
|
60
|
+
job_definition: str = Field(..., serialization_alias="jobDefinition")
|
|
61
|
+
job_queue: str = Field(..., serialization_alias="jobQueue")
|
|
62
|
+
job_origin: str = Field(..., serialization_alias="jobOrigin", exclude=True)
|
|
63
|
+
network_name: str | None = Field(default=None, serialization_alias="networkName")
|
|
64
|
+
roots: list[str] = Field(..., min_length=1, serialization_alias="roots")
|
|
65
|
+
scanner_name: str = Field(..., serialization_alias="scannerName")
|
|
66
|
+
user_email: str = Field(..., serialization_alias="userEmail")
|
|
67
|
+
|
|
68
|
+
def get_machine_name(self) -> str:
|
|
69
|
+
return f"{self.group_name}_{self.job_origin}_{self.scanner_name}_{self.config_id}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class StateMachineExecuteSiftsInput(BaseModel):
|
|
73
|
+
model_config = {"extra": "forbid"}
|
|
74
|
+
config_id: str = Field(
|
|
75
|
+
default_factory=lambda: str(uuid4().time), serialization_alias="configId"
|
|
76
|
+
)
|
|
77
|
+
group_name: str = Field(..., serialization_alias="groupName")
|
|
78
|
+
root_nickname: str = Field(..., serialization_alias="rootNickname")
|
|
79
|
+
trigger: TCloningTrigger = Field(..., serialization_alias="trigger")
|
|
80
|
+
user_email: str = Field(..., serialization_alias="userEmail")
|
|
81
|
+
|
|
82
|
+
def get_machine_name(self) -> str:
|
|
83
|
+
return f"{self.group_name}_{self.trigger}_execute_sifts_{self.config_id}"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class StateMachineRequestSbomMailInput(BaseModel):
|
|
87
|
+
model_config = {"extra": "forbid"}
|
|
88
|
+
config_id: str = Field(
|
|
89
|
+
default_factory=lambda: str(uuid4().time), serialization_alias="configId"
|
|
90
|
+
)
|
|
91
|
+
group_name: str = Field(..., serialization_alias="groupName")
|
|
92
|
+
item_kind: Literal["root", "image"] = Field(..., serialization_alias="itemKind")
|
|
93
|
+
item_name: str = Field(..., serialization_alias="itemName")
|
|
94
|
+
notification_id: str = Field(..., serialization_alias="notificationId")
|
|
95
|
+
user_email: str = Field(..., serialization_alias="userEmail")
|
|
96
|
+
sbom_format: str = Field(..., serialization_alias="sbomFormat")
|
|
97
|
+
|
|
98
|
+
def get_machine_name(self) -> str:
|
|
99
|
+
return f"{self.group_name}_request_sbom_mail_{self.config_id}"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fluidattacks_core_models
|
|
3
|
+
Version: 12.0.0
|
|
4
|
+
Summary: Fluid Attacks Core Models Library
|
|
5
|
+
Author-email: Development <development@fluidattacks.com>
|
|
6
|
+
License: MPL-2.0
|
|
7
|
+
Classifier: Development Status :: 1 - Planning
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: pydantic>=2.12.3
|
|
15
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
fluidattacks_core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
fluidattacks_core/models/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
fluidattacks_core/models/scanners/__init__.py,sha256=dsVrg8xfgSoqdfui5bHysLDITmpErDfJoB6K6kdhKYI,1873
|
|
4
|
+
fluidattacks_core/models/scanners/apk_config.py,sha256=KpR5DKXSO5-P8DMAbOhMnyWwb92Uxdv_XYBGYTErRE8,839
|
|
5
|
+
fluidattacks_core/models/scanners/common_config.py,sha256=YvlaE2ClSSnVD5UFMVWeu5eiMR70GMIzyDvG2oekeRI,471
|
|
6
|
+
fluidattacks_core/models/scanners/containers_sca_config.py,sha256=vSXHwck7IzcP2jNBm4IB3TClcVriS-r9mEVCMsigVQU,1047
|
|
7
|
+
fluidattacks_core/models/scanners/cspm_config.py,sha256=CFG-LVjlbPBSdpCpXtZdYs0oEJu2FOGDHF0fXLdvdZc,1703
|
|
8
|
+
fluidattacks_core/models/scanners/dast_config.py,sha256=cGZnYcJGWEFdV0g_RNl-HKwDhPyIHYO2PlMg2eOim7I,1336
|
|
9
|
+
fluidattacks_core/models/scanners/loader.py,sha256=l9kOUbDlCv-0R3LmJbOkwoXGUC7qNE4dEWiBZPVllr8,2297
|
|
10
|
+
fluidattacks_core/models/scanners/sast_config.py,sha256=iWy9icaE3UGvfBWLF5GMWBtLxYLbc6wPvTF3Yph4vfA,529
|
|
11
|
+
fluidattacks_core/models/scanners/sbom_config.py,sha256=ZisA15KwVnT587qO9wDKTYXDmfl1elOqKoJTUDWgHes,1638
|
|
12
|
+
fluidattacks_core/models/scanners/sca_config.py,sha256=SWh5gLS803vr7_9hdO4-7Y3PDof8TitLBdPUa16Kt9g,422
|
|
13
|
+
fluidattacks_core/models/scanners/scan_config.py,sha256=OShUdsFAA31bmUh2cJcf9ODhmLMnakHQ7dliVbiyWbc,1583
|
|
14
|
+
fluidattacks_core/models/scanners/sniffs_config.py,sha256=mHebiayRToLn0EjT4xbxiwmEi-LNc6VpuhQnUzoqtE4,271
|
|
15
|
+
fluidattacks_core/models/state_machines/__init__.py,sha256=wSsH_I1Y-Md-Ele3EftdwbdA7HBl8VG1JwyC5_REQEY,938
|
|
16
|
+
fluidattacks_core/models/state_machines/root_scanner.py,sha256=FQZk7-zuE1D12GjFHLERpIW3Idd3fkLhq6pmFPDvKbo,3365
|
|
17
|
+
fluidattacks_core/models/state_machines/types.py,sha256=5wPuLTEjapYvQg9DYAgmo2BaDxZQl1ZKMtiKX_izkJo,3967
|
|
18
|
+
fluidattacks_core_models-12.0.0.dist-info/METADATA,sha256=k3KybJsmyKRQ7ZaBuDtbgLBMVMFQl4LlSTyvO6IAC0E,592
|
|
19
|
+
fluidattacks_core_models-12.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
20
|
+
fluidattacks_core_models-12.0.0.dist-info/RECORD,,
|