Skip to content

Commit

Permalink
Add new command securityscorecard-alert-rules-list (#38464) (#38539)
Browse files Browse the repository at this point in the history
* feat: enable portfolio column in alerts list

* feat: add new rules list command

* chore: add release notes for v 1.0.11

Co-authored-by: Mariano Mendez <[email protected]>
  • Loading branch information
content-bot and hx0ar authored Feb 9, 2025
1 parent 0f93150 commit 2fcd00c
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 14 deletions.
25 changes: 24 additions & 1 deletion Packs/SecurityScorecard/Integrations/SecurityScorecard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,27 @@ Retrieve metadata for an issue type, including description and recommendation.
| SecurityScorecard.Metadata.Issues.title | string | issue title. |
| SecurityScorecard.Metadata.Issues.short_description | string | issue short description. |
| SecurityScorecard.Metadata.Issues.long_description | string | issue long description. |
| SecurityScorecard.Metadata.Issues.recommendation | string | issue recommendation. |
| SecurityScorecard.Metadata.Issues.recommendation | string | issue recommendation. |

### securityscorecard-alert-rules-list

***
List alert subscriptions for the user.

#### Base Command

`securityscorecard-alert-rules-list`

#### Input

This command does not require any arguments.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| SecurityScorecard.AlertRules.Rule.id | String | Alert Rule ID. |
| SecurityScorecard.AlertRules.Rule.name | String | Alert Rule name. |
| SecurityScorecard.AlertRules.Rule.target | String | Target of the Rule. |
| SecurityScorecard.AlertRules.Rule.updated_at | Date | Timestamp when the alert rule was last updated. |
| SecurityScorecard.AlertRules.Rule.paused_at | String | Timestamp when the alert rule was paused. |
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ def delete_alert(self, id: str) -> None:
return_empty_response=True
)

def get_subscriptions(self) -> Dict[str, Any]:

query_params: Dict[str, Any] = assign_params(
username=self.username,
)

return self.http_request_wrapper(
method="GET",
url_suffix="subscriptions",
params=query_params
)

def get_alerts_last_week(self, email: str, portfolio_id: Optional[str]) -> Dict[str, Any]:

query_params: Dict[str, Any] = assign_params(
Expand Down Expand Up @@ -1199,12 +1211,9 @@ def alerts_list_command(client: SecurityScorecardClient, args: Dict[str, Any]) -

for entry in entries: # type: ignore
content: Dict[str, str] = {
# "Alert ID": entry.get("id"), this is execution id, not useful
"company": entry.get("company_name"),
"domain": entry.get("domain"),
"datetime": entry.get("created_at"),
# "Score date": entry.get("platform_score_date"), #no need for this
# "portfolios": entry.get("portfolios"),
}

change_data = entry.get("change_data")
Expand All @@ -1224,6 +1233,16 @@ def alerts_list_command(client: SecurityScorecardClient, args: Dict[str, Any]) -
except (json.JSONDecodeError, TypeError, KeyError) as e:
demisto.error(f"Error processing change_data: {str(e)}")

# Old alerts system had the possibility of multiple portfolios, new rules system allows one
portfolios = entry.get("portfolios")

if portfolios:
try:
for portfolio in portfolios:
content["target portfolio"] = portfolio.get("id")
except (json.JSONDecodeError, TypeError, KeyError) as e:
demisto.error(f"Error processing change_data: {str(e)}")

alerts.append(content)

markdown = tableToMarkdown(f"Latest Alerts for user {email}", alerts)
Expand Down Expand Up @@ -1379,6 +1398,62 @@ def fetch_alerts(client: SecurityScorecardClient):
demisto.incidents([])


def alert_rules_list_command(client: SecurityScorecardClient, args: Dict[str, Any]) -> CommandResults:
"""Retrieve alert subscriptions for the user
See https://securityscorecard.readme.io/reference/subscriptions (not available right now)
Args:
client (SecurityScorecardClient): SecurityScorecard client
args (Dict[str, Any]): Dictionary of arguments specified in the command
Returns:
CommandResults: The results of the command.
"""

response = client.get_subscriptions()
demisto.debug(f"Response received: {response}")
entries = response.get("entries")

alert_rules: List[Dict[str, str]] = []

if entries:
for entry in entries:
target = entry.get("delivery", {}).get("workflow", {}).get("filters", {}).get("scorecards", {}).get("value", "N/A")
if target == "by_id":
target = "single scorecard"
elif target == "in_portfolio":
portfolio_id = entry.get("delivery", {}).get("workflow", {}).get(
"filters", {}).get("scorecards", {}).get("portfolio_id", {}).get("value", "N/A")
target = f"portfolio with id {portfolio_id}"
elif target == "followed":
target = "all followed scorecards"
elif target == "my_scorecard":
target = "my scorecard"

content: Dict[str, str] = {
"Alert Rule ID": entry.get("id"),
"Target": target,
"Name": entry.get("delivery", {}).get("workflow", {}).get("name", "N/A"),
"Updated At": entry.get("updated_at", "N/A"),
"Paused At": entry.get("paused_at", "N/A"),
}

alert_rules.append(content)

markdown = tableToMarkdown("Alert Rules", alert_rules)

results = CommandResults(
outputs_prefix="SecurityScorecard.AlertRules.Rule",
outputs_key_field="id",
readable_output=markdown,
outputs=alert_rules,
raw_response=response
)

return results


""" MAIN FUNCTION """


Expand Down Expand Up @@ -1458,6 +1533,8 @@ def main() -> None:
return_results(company_services_get_command(client=client, args=args))
elif demisto.command() == 'securityscorecard-issue-metadata':
return_results(issue_metadata_get_command(client=client, args=args))
elif demisto.command() == 'securityscorecard-alert-rules-list':
return_results(alert_rules_list_command(client=client, args=args))

# Log exceptions and return errors
except Exception as e:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@ display: SecurityScorecard
category: Vulnerability Management
description: Provides scorecards for domains.
defaultmapperin: SecurityScorecard Alert Mapper
sectionorder:
- Connect
- Collect
commonfields:
id: SecurityScorecard
version: -1
configuration:
- name: base_url
display: SecurityScorecard API Base URL
required: true
section: Connect
defaultvalue: https://api.securityscorecard.io/
type: 0
section: Connect
- name: username
display: Username/Email
additionalinfo: The SecurityScorecard username/email.
Expand Down Expand Up @@ -310,7 +307,7 @@ script:
- "my_scorecard"
- "any_followed_company"
- name: portfolio
description: A portfolio_id to use as a target for the alert. This argument is required if the `target` argument is not specified. You can get a list of portfolios by running `!securityscorecard-portfolios-list`.
description: A portfolio_id to use as a target for the alert. This argument is required if the `target` argument is not specified. You can get a list of portfolios by running `!securityscorecard-portfolios-list`.
outputs:
- contextPath: SecurityScorecard.Alerts.GradeChangeAlert.id
description: Alert ID.
Expand Down Expand Up @@ -339,7 +336,7 @@ script:
- "my_scorecard"
- "any_followed_company"
- name: portfolio
description: A portfolio_id to use as a target for the alert. This argument is required if the `target` argument is not specified. You can get a list of portfolios by running `!securityscorecard-portfolios-list`.
description: A portfolio_id to use as a target for the alert. This argument is required if the `target` argument is not specified. You can get a list of portfolios by running `!securityscorecard-portfolios-list`.
outputs:
- contextPath: SecurityScorecard.Alerts.ScoreThresholdAlert.id
description: Alert ID.
Expand Down Expand Up @@ -478,4 +475,26 @@ script:
- contextPath: SecurityScorecard.Metadata.Issues.recommendation
type: string
description: issue recommendation.
- name: securityscorecard-alert-rules-list
description: List alert subscriptions for the user.
arguments: []
outputs:
- contextPath: SecurityScorecard.AlertRules.Rule.id
description: Alert Rule ID.
type: String
- contextPath: SecurityScorecard.AlertRules.Rule.name
description: Alert Rule name.
type: String
- contextPath: SecurityScorecard.AlertRules.Rule.target
description: Target of the Rule.
type: String
- contextPath: SecurityScorecard.AlertRules.Rule.updated_at
description: Timestamp when the alert rule was last updated.
type: Date
- contextPath: SecurityScorecard.AlertRules.Rule.paused_at
description: Timestamp when the alert rule was paused.
type: Date
runonce: false
sectionorder:
- Connect
- Collect
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
company_services_get_command, \
issue_metadata_get_command, \
company_events_get_command, \
company_event_findings_get_command
company_event_findings_get_command, \
alert_rules_list_command

from unittest.mock import MagicMock

Expand Down Expand Up @@ -891,3 +892,25 @@ def test_company_event_findings_get_command_success(mocked_security_scorecard_cl

# Assert that the mocked_security_scorecard_client's get_company_event_findings method was called with the expected arguments
mocked_security_scorecard_client.get_company_event_findings.assert_called()


def test_alert_rules_list_command(mocker):
"""
Given:
- No specific arguments
When:
- Retrieving alert rules
Then:
- Ensure the alert rules are returned correctly
"""

alert_rules_mock = test_data.get("alert_rules")
mocker.patch.object(client, "http_request_wrapper", return_value=alert_rules_mock)

response_cmd_res: CommandResults = alert_rules_list_command(client=client, args={})

alert_rules = response_cmd_res.raw_response.get("entries")

assert alert_rules == alert_rules_mock.get("entries")
assert response_cmd_res.outputs_prefix == "SecurityScorecard.AlertRules.Rule"
assert response_cmd_res.outputs_key_field == "id"
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
!securityscorecard-company-history-factor-score-get domain=google.com from=2021-06-01 to=2021-06-30 timing=weekly
!securityscorecard-company-events-get domain=groupe.schmidt date_from="2024-07-31 00:00:00Z" date_to="2024-08-08 00:00:00Z"
!securityscorecard-company-findings-get date=2024-08-13 domain=groupe.schmidt issue_type=tlscert_expired
!securityscorecard-issue-metadata issue_type=spf_record_missing
!securityscorecard-issue-metadata issue_type=spf_record_missing
!securityscorecard-alert-rules-list
Original file line number Diff line number Diff line change
Expand Up @@ -1058,5 +1058,25 @@
}
}
}
]}
]},
"alert_rules": {
"entries": [
{
"id": "1",
"name": "Alert Rule 1",
"event_type": "scorecard.changed",
"created_at": "2021-07-01T00:00:00.000Z",
"updated_at": "2021-07-02T00:00:00.000Z",
"status": "active"
},
{
"id": "2",
"name": "Alert Rule 2",
"event_type": "scorecard.changed",
"created_at": "2021-07-03T00:00:00.000Z",
"updated_at": "2021-07-04T00:00:00.000Z",
"status": "inactive"
}
]
}
}
8 changes: 8 additions & 0 deletions Packs/SecurityScorecard/ReleaseNotes/1_0_11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

#### Integrations

##### SecurityScorecard

- Added the rules listing command: _!securityscorecard-alert-rules-list_.
- Added the portfolios column in alerts listing.
- Added portfolios filtering in the backend.
2 changes: 1 addition & 1 deletion Packs/SecurityScorecard/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "SecurityScorecard",
"description": "Provides security scorecards and alerts for domains.",
"support": "partner",
"currentVersion": "1.0.10",
"currentVersion": "1.0.11",
"author": "SecurityScorecard",
"url": "https://securityscorecard.com/contact-us",
"email": "[email protected]",
Expand Down

0 comments on commit 2fcd00c

Please sign in to comment.