Skip to content

Commit 9a38e5d

Browse files
committed
feat(nhi): add secret is_vaulted info in ggshield output
1 parent 78bfce1 commit 9a38e5d

File tree

8 files changed

+116
-1
lines changed

8 files changed

+116
-1
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
For top level release notes, leave all the headers commented out.
6+
-->
7+
8+
<!--
9+
### Removed
10+
11+
- A bullet item for the Removed category.
12+
13+
-->
14+
15+
### Added
16+
17+
- Added a new section in ggshield's outputs (text and json) to notify if a secret is in one of the accounts' secrets managers.
18+
19+
<!--
20+
### Changed
21+
22+
- A bullet item for the Changed category.
23+
24+
-->
25+
<!--
26+
### Deprecated
27+
28+
- A bullet item for the Deprecated category.
29+
30+
-->
31+
<!--
32+
### Fixed
33+
34+
- A bullet item for the Fixed category.
35+
36+
-->
37+
<!--
38+
### Security
39+
40+
- A bullet item for the Security category.
41+
42+
-->

ggshield/verticals/secret/output/schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class FlattenedPolicyBreak(BaseSchema):
2222
incident_details = fields.Nested(SecretIncidentSchema)
2323
known_secret = fields.Bool(required=True, dump_default=False)
2424
ignore_reason = fields.Nested(IgnoreReasonSchema, dump_default=None)
25+
secret_vaulted = fields.Bool(required=True, dump_default=False)
2526

2627

2728
class JSONResultSchema(BaseSchema):

ggshield/verticals/secret/output/secret_json_output_handler.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ def serialized_secret(
141141
secrets[0].ignore_reason
142142
)
143143

144+
if secrets[0].is_vaulted:
145+
flattened_dict["secret_vaulted"] = secrets[0].is_vaulted
144146
for secret in secrets:
145147
flattened_dict["occurrences"].extend(self.serialize_secret_matches(secret))
146148

ggshield/verticals/secret/output/secret_text_output_handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,9 @@ def secret_header(
305305
{start_line} Secret detected: {secret_type}{validity_msg}
306306
{indent}Occurrences: {number_occurrences}
307307
{indent}Known by GitGuardian dashboard: {"YES" if known_secret else "NO"}
308-
{indent}Incident URL: {secrets[0].incident_url if known_secret and secret.incident_url else "N/A"}
308+
{indent}Incident URL: {secret.incident_url if known_secret and secret.incident_url else "N/A"}
309309
{indent}Secret SHA: {ignore_sha}
310+
{indent}Secret in Secrets Manager: {secret.is_vaulted}
310311
"""
311312
if secret.documentation_url is not None:
312313
message += f"{indent}Detector documentation: {secret.documentation_url}\n"

ggshield/verticals/secret/secret_scan_collection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class Secret:
9393
matches: List[ExtendedMatch]
9494
ignore_reason: Optional[IgnoreReason]
9595
diff_kind: Optional[DiffKind]
96+
is_vaulted: bool
9697

9798
@property
9899
def policy(self) -> str:
@@ -199,6 +200,7 @@ def from_scan_result(
199200
],
200201
ignore_reason=ignore_reason,
201202
diff_kind=policy_break.diff_kind,
203+
is_vaulted=policy_break.is_vaulted,
202204
)
203205
for policy_break, ignore_reason in to_keep
204206
]

tests/factories.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class Meta:
6767
known_secret = False
6868
incident_url = None
6969
is_excluded = False
70+
is_vaulted = False
7071
exclude_reason = None
7172
diff_kind = None
7273
content = factory.Faker("text")

tests/unit/verticals/secret/output/test_json_output.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,3 +652,35 @@ def test_ignore_reason(ignore_reason, expected_output):
652652

653653
parsed_incidents = json.loads(output)["entities_with_incidents"][0]["incidents"]
654654
assert parsed_incidents[0]["ignore_reason"] == expected_output
655+
656+
657+
@pytest.mark.parametrize(
658+
"is_vaulted",
659+
(True, False),
660+
)
661+
def test_vaulted_secret(is_vaulted: bool):
662+
"""
663+
GIVEN an result
664+
WHEN it is passed to the json output handler
665+
THEN the vaulted_secret field is as expected
666+
"""
667+
668+
secret_config = SecretConfig()
669+
scannable = ScannableFactory()
670+
policy_break = PolicyBreakFactory(content=scannable.content, is_vaulted=is_vaulted)
671+
result = Result.from_scan_result(
672+
scannable, ScanResultFactory(policy_breaks=[policy_break]), secret_config
673+
)
674+
675+
output_handler = SecretJSONOutputHandler(secret_config=secret_config, verbose=False)
676+
677+
output = output_handler._process_scan_impl(
678+
SecretScanCollection(
679+
id="scan",
680+
type="scan",
681+
results=Results(results=[result], errors=[]),
682+
)
683+
)
684+
685+
parsed_incidents = json.loads(output)["entities_with_incidents"][0]["incidents"]
686+
assert parsed_incidents[0]["vaulted_secret"] == is_vaulted

tests/unit/verticals/secret/output/test_text_output.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,37 @@ def test_ignore_reason(ignore_reason):
264264
else:
265265
assert "Ignored:" in output
266266
assert ignore_reason.to_human_readable() in output
267+
268+
269+
@pytest.mark.parametrize(
270+
"is_vaulted",
271+
(True, False),
272+
)
273+
def test_vaulted_secret(is_vaulted: bool):
274+
"""
275+
GIVEN a secret
276+
WHEN it is passed to the text output handler
277+
THEN the vaulted_secret field is displayed as expected
278+
"""
279+
280+
secret_config = SecretConfig()
281+
scannable = ScannableFactory()
282+
policy_break = PolicyBreakFactory(content=scannable.content, is_vaulted=is_vaulted)
283+
result = Result.from_scan_result(
284+
scannable, ScanResultFactory(policy_breaks=[policy_break]), secret_config
285+
)
286+
287+
output_handler = SecretTextOutputHandler(secret_config=secret_config, verbose=False)
288+
289+
output = output_handler._process_scan_impl(
290+
SecretScanCollection(
291+
id="scan",
292+
type="scan",
293+
results=Results(results=[result], errors=[]),
294+
)
295+
)
296+
297+
if is_vaulted:
298+
assert "Secret in Secrets Manager: True" in output
299+
else:
300+
assert "Secret in Secrets Manager: False" in output

0 commit comments

Comments
 (0)