feat(jira): label standalone tests, include round data in submission comments
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
- Tests created without a campaign parent ticket now get a "standalone-test" label, so they're identifiable in Jira without cross-referencing Aegis. - The comment posted on every red/blue submission was a generic one-liner with no data — only a reopened round got a full data dump (via push_round_archived), so a round that was approved outright left no record in Jira of what was actually done. Now every submission comment includes the round number, procedure/tool/attack success (red) or detection/containment result (blue), and summary.
This commit is contained in:
@@ -59,6 +59,8 @@ def _make_test(**overrides):
|
||||
t.containment_time = None
|
||||
t.red_round_number = 1
|
||||
t.blue_round_number = 1
|
||||
t.procedure_text = None
|
||||
t.tool_used = None
|
||||
# _build_state_comment() appends these raw (not via an f-string) when
|
||||
# truthy, so an un-nulled MagicMock attribute breaks "\n".join(lines).
|
||||
t.red_summary = None
|
||||
@@ -230,6 +232,58 @@ def test_push_test_event_maps_every_state_to_jira_status(
|
||||
mock_jira.set_issue_status.assert_called_once_with(link.jira_issue_key, expected_status)
|
||||
|
||||
|
||||
@patch("app.services.jira_service.has_admin_jira_configured", return_value=True)
|
||||
@patch("app.services.jira_service.get_admin_jira_client")
|
||||
def test_push_test_event_red_review_comment_includes_round_data(mock_get_client, mock_configured, db):
|
||||
"""Every submission — not just ones that later get reopened — must leave
|
||||
a data-bearing comment in Jira. Previously this comment was a generic
|
||||
one-liner with no procedure/tool/result, so a round that got approved
|
||||
(never archived via reopen) left no record of what was actually done."""
|
||||
mock_jira = MagicMock()
|
||||
mock_get_client.return_value = mock_jira
|
||||
test = _make_test(
|
||||
red_round_number=2,
|
||||
procedure_text="ran mimikatz again", tool_used="mimikatz",
|
||||
attack_success=MagicMock(value="successful"), red_summary="Got a shell this time.",
|
||||
)
|
||||
link = _make_link(test.id)
|
||||
db.add(link)
|
||||
db.commit()
|
||||
|
||||
jira_service.push_test_event(db, test, MagicMock(username="alice", jira_account_id=None), "red_review")
|
||||
|
||||
comment = mock_jira.issue_add_comment.call_args[0][1]
|
||||
assert "Round 2" in comment
|
||||
assert "ran mimikatz again" in comment
|
||||
assert "mimikatz" in comment
|
||||
assert "successful" in comment
|
||||
assert "Got a shell this time." in comment
|
||||
|
||||
|
||||
@patch("app.services.jira_service.has_admin_jira_configured", return_value=True)
|
||||
@patch("app.services.jira_service.get_admin_jira_client")
|
||||
def test_push_test_event_blue_review_comment_includes_round_data(mock_get_client, mock_configured, db):
|
||||
mock_jira = MagicMock()
|
||||
mock_get_client.return_value = mock_jira
|
||||
test = _make_test(
|
||||
blue_round_number=1,
|
||||
detection_result=MagicMock(value="detected"),
|
||||
containment_result=MagicMock(value="contained"),
|
||||
blue_summary="Caught it via EDR.",
|
||||
)
|
||||
link = _make_link(test.id)
|
||||
db.add(link)
|
||||
db.commit()
|
||||
|
||||
jira_service.push_test_event(db, test, MagicMock(username="alice", jira_account_id=None), "blue_review")
|
||||
|
||||
comment = mock_jira.issue_add_comment.call_args[0][1]
|
||||
assert "Round 1" in comment
|
||||
assert "detected" in comment
|
||||
assert "contained" in comment
|
||||
assert "Caught it via EDR." in comment
|
||||
|
||||
|
||||
@patch("app.services.jira_service.has_admin_jira_configured", return_value=True)
|
||||
@patch("app.services.jira_service.get_admin_jira_client")
|
||||
def test_push_test_event_clears_assignee_on_in_review(mock_get_client, mock_configured, db):
|
||||
@@ -658,7 +712,61 @@ def test_auto_create_test_issue_adds_platform_label(
|
||||
if expected_label:
|
||||
assert expected_label in labels
|
||||
else:
|
||||
assert len(labels) == 2
|
||||
# Just "aegis", the technique ID, and "standalone-test" (no
|
||||
# parent_ticket_override passed here, same as any non-campaign test).
|
||||
assert len(labels) == 3
|
||||
|
||||
|
||||
@patch("app.services.jira_service.has_admin_jira_configured", return_value=True)
|
||||
@patch("app.services.jira_service.get_jira_project_key", return_value="SEC")
|
||||
@patch("app.services.jira_service.get_jira_parent_ticket_standalone", return_value=None)
|
||||
@patch("app.services.jira_service.get_admin_jira_client")
|
||||
def test_auto_create_test_issue_labels_standalone_tests(
|
||||
mock_get_client, mock_parent, mock_project_key, mock_configured, db,
|
||||
):
|
||||
"""A test created with no campaign parent ticket is standalone — tag it
|
||||
so standalone tests are identifiable in Jira without cross-referencing
|
||||
Aegis."""
|
||||
mock_jira = MagicMock()
|
||||
mock_jira.issue_create.return_value = {"key": "SEC-1", "id": "1"}
|
||||
mock_get_client.return_value = mock_jira
|
||||
from app.models.user import User
|
||||
test = _make_test_for_issue()
|
||||
technique = _make_technique()
|
||||
actor = User(username="gerardo-ruiz", email="gerardo.ruiz@kaseya.com", hashed_password="x")
|
||||
db.add(actor)
|
||||
db.commit()
|
||||
|
||||
jira_service.auto_create_test_issue(db, test, actor, technique=technique)
|
||||
|
||||
labels = mock_jira.issue_create.call_args.kwargs["fields"]["labels"]
|
||||
assert "standalone-test" in labels
|
||||
|
||||
|
||||
@patch("app.services.jira_service.has_admin_jira_configured", return_value=True)
|
||||
@patch("app.services.jira_service.get_jira_project_key", return_value="SEC")
|
||||
@patch("app.services.jira_service.get_admin_jira_client")
|
||||
def test_auto_create_test_issue_does_not_label_campaign_tests_as_standalone(
|
||||
mock_get_client, mock_project_key, mock_configured, db,
|
||||
):
|
||||
"""A campaign test passes parent_ticket_override — it must NOT get
|
||||
"standalone-test", since it's nested under the campaign's ticket."""
|
||||
mock_jira = MagicMock()
|
||||
mock_jira.issue_create.return_value = {"key": "SEC-1", "id": "1"}
|
||||
mock_get_client.return_value = mock_jira
|
||||
from app.models.user import User
|
||||
test = _make_test_for_issue()
|
||||
technique = _make_technique()
|
||||
actor = User(username="gerardo-ruiz", email="gerardo.ruiz@kaseya.com", hashed_password="x")
|
||||
db.add(actor)
|
||||
db.commit()
|
||||
|
||||
jira_service.auto_create_test_issue(
|
||||
db, test, actor, technique=technique, parent_ticket_override="CAMP-1",
|
||||
)
|
||||
|
||||
labels = mock_jira.issue_create.call_args.kwargs["fields"]["labels"]
|
||||
assert "standalone-test" not in labels
|
||||
|
||||
|
||||
def _make_user(**overrides):
|
||||
|
||||
Reference in New Issue
Block a user