Skip to content

Commit da5d95d

Browse files
Merge pull request #66 from GitGuardian/sbaud/iac-diff-scan
Sbaud/iac diff scan
2 parents f9effe1 + c5dbe78 commit da5d95d

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Added
2+
3+
- `iac_diff_scan` added to GGClient. This allows scanning two directories for IaC vulnerabilities and categorise incidents as new, unchanged or deleted.

pygitguardian/client.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
from requests import Response, Session, codes
1313

1414
from .config import DEFAULT_API_VERSION, DEFAULT_BASE_URI, DEFAULT_TIMEOUT
15-
from .iac_models import IaCScanParameters, IaCScanParametersSchema, IaCScanResult
15+
from .iac_models import (
16+
IaCDiffScanResult,
17+
IaCScanParameters,
18+
IaCScanParametersSchema,
19+
IaCScanResult,
20+
)
1621
from .models import (
1722
Detail,
1823
Document,
@@ -270,6 +275,7 @@ def post(
270275
**kwargs: Any,
271276
) -> Response:
272277
# Be aware that self.iac_directory_scan bypass this method and calls self.request directly.
278+
# self.iac_diff_scan also bypass this method
273279
return self.request(
274280
"post",
275281
endpoint=endpoint,
@@ -462,14 +468,23 @@ def create_honeytoken(
462468
result.status_code = resp.status_code
463469
return result
464470

465-
# For IaC Scans
466471
def iac_directory_scan(
467472
self,
468473
directory: Path,
469474
filenames: List[str],
470475
scan_parameters: IaCScanParameters,
471476
extra_headers: Optional[Dict[str, str]] = None,
472477
) -> Union[Detail, IaCScanResult]:
478+
"""
479+
iac_directory_scan handles the /iac_scan endpoint of the API.
480+
481+
:param directory: path to the directory to scan
482+
:param filenames: filenames of the directory to include in the scan
483+
:param scan_parameters: minimum severities wanted and policies to ignore
484+
example: {"ignored_policies":["GG_IAC_0003"],"minimum_severity":"HIGH"}
485+
:param extra_headers: optional extra headers to add to the request
486+
:return: ScanResult response and status code
487+
"""
473488
tar = _create_tar(directory, filenames)
474489
result: Union[Detail, IaCScanResult]
475490
try:
@@ -498,6 +513,55 @@ def iac_directory_scan(
498513

499514
return result
500515

516+
def iac_diff_scan(
517+
self,
518+
reference: bytes,
519+
current: bytes,
520+
scan_parameters: IaCScanParameters,
521+
extra_headers: Optional[Dict[str, str]] = None,
522+
) -> Union[Detail, IaCDiffScanResult]:
523+
"""
524+
iac_diff_scan handles the /iac_diff_scan endpoint of the API.
525+
526+
Scan two directories and compare their vulnerabilities.
527+
Vulnerabilities in reference but not in current are considered "new".
528+
Vulnerabilities in both reference and current are considered "unchanged".
529+
Vulnerabilities in current but not in reference are considered "deleted".
530+
531+
:param reference: tar file containing the reference directory. Usually an incoming commit
532+
:param current: tar file of the current directory. Usually HEAD
533+
:param scan_parameters: minimum severities wanted and policies to ignore
534+
example: {"ignored_policies":["GG_IAC_0003"],"minimum_severity":"HIGH"}
535+
:param extra_headers: optional extra headers to add to the request
536+
:return: ScanResult response and status code
537+
"""
538+
result: Union[Detail, IaCDiffScanResult]
539+
try:
540+
# bypass self.post because data argument is needed in self.request and self.post use it as json
541+
resp = self.request(
542+
"post",
543+
endpoint="iac_diff_scan",
544+
extra_headers=extra_headers,
545+
files={
546+
"reference": reference,
547+
"current": current,
548+
},
549+
data={
550+
"scan_parameters": IaCScanParametersSchema().dumps(scan_parameters),
551+
},
552+
)
553+
except requests.exceptions.ReadTimeout:
554+
result = Detail("The request timed out.")
555+
result.status_code = 504
556+
else:
557+
if is_ok(resp):
558+
result = IaCDiffScanResult.from_dict(resp.json())
559+
else:
560+
result = load_detail(resp)
561+
562+
result.status_code = resp.status_code
563+
return result
564+
501565
def read_metadata(self) -> Optional[Detail]:
502566
"""
503567
Fetch server preferences and store them in `self.secret_scan_preferences`.

pygitguardian/iac_models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,26 @@ class IaCScanResult(Base, FromDictMixin):
6363
Type[BaseSchema], marshmallow_dataclass.class_schema(IaCScanResult, BaseSchema)
6464
)
6565
IaCScanResult.SCHEMA = IaCScanResultSchema()
66+
67+
68+
@dataclass
69+
class IaCDiffScanEntities(Base):
70+
unchanged: List[IaCFileResult] = field(default_factory=list)
71+
new: List[IaCFileResult] = field(default_factory=list)
72+
deleted: List[IaCFileResult] = field(default_factory=list)
73+
74+
75+
@dataclass
76+
class IaCDiffScanResult(Base, FromDictMixin):
77+
id: str = ""
78+
type: str = ""
79+
iac_engine_version: str = ""
80+
entities_with_incidents: IaCDiffScanEntities = field(
81+
default_factory=IaCDiffScanEntities
82+
)
83+
84+
85+
IaCDiffScanResultSchema = cast(
86+
Type[BaseSchema], marshmallow_dataclass.class_schema(IaCDiffScanResult, BaseSchema)
87+
)
88+
IaCDiffScanResult.SCHEMA = IaCDiffScanResultSchema()

0 commit comments

Comments
 (0)