AWS-SNS-Listener

Amazon Simple Notification Service (SNS) is a managed service that provides message delivery from publishers to subscribers.

Messaging and Conferencing · AWS-SNS-Listener

Details

IDAWS-SNS-Listener
ProviderAmazon
CategoryMessaging and Conferencing
From Version6.10.0
Docker Imagedemisto/fastapi:0.125.0.10158186
Supported ModulesAgentix XSIAM

README

Amazon Simple Notification Service (SNS) is a managed service that provides message delivery from publishers to subscribers.
This integration was integrated and tested with version January 2024 of AWS-SNS-Listener.

Configure AWS-SNS-Listener in Cortex

Parameter Description Required
Long running instance Integration is long running by default  
Listen Port Runs the service on this port from within Cortex XSOAR. Requires a unique port for each long-running integration instance. Do not use the same port for multiple instances. Note: If you click the test button more than once, a failure may occur mistakenly indicating that the port is already in use. False
Username Uses basic authentication for accessing the list. If empty, no authentication is enforced. (For Cortex XSOAR 8 and Cortex XSIAM) Optional for engines, otherwise mandatory. False
Password   False
Endpoint Set the endpoint of your listener. example: /snsv2 False
Certificate (Required for HTTPS) (For Cortex XSOAR 6.x) For use with HTTPS - the certificate that the service should use. (For Cortex XSOAR 8 and Cortex XSIAM) Custom certificates are not supported. False
Private Key (Required for HTTPS) (For Cortex XSOAR 6.x) For use with HTTPS - the private key that the service should use. (For Cortex XSOAR 8 and Cortex XSIAM) When using an engine, configure a private API key. Not supported on the Cortex XSOAR​​ or Cortex XSIAM server. False
Store sample events for mapping Because this is a push-based integration, it cannot fetch sample events in the mapping wizard. After you finish mapping, it is recommended to turn off the sample events storage to reduce performance overhead. False
Use system proxy settings   False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

Configuration parameters

  • longRunning — Long running instance
  • credentials — Username
  • endpoint — Endpoint
  • certificate — Certificate (Required for HTTPS)
  • key — Private Key (Required for HTTPS)
  • store_samples — Store sample events for mapping
  • proxy — Use system proxy settings
  • longRunningPort — Listen Port (required)

Commands (0)

This integration defines no commands.

from unittest.mock import MagicMock, patch

import pytest
import requests
from AWSSNSListener import SNSCertificateManager, handle_notification, is_valid_integration_credentials
from CommonServerPython import DemistoException

VALID_PAYLOAD = {
    "Type": "Notification",
    "MessageId": "uuid",
    "TopicArn": "topicarn",
    "Subject": "NotificationSubject",
    "Message": "NotificationMessage",
    "Timestamp": "2024-02-13T18:03:27.239Z",
    "SignatureVersion": "1",
    "Signature": b"sign",
    "SigningCertURL": "https://sns.example.amazonaws.com",
}


@pytest.fixture
def mock_params(mocker):
    return mocker.patch("AWSSNSListener.PARAMS", new={"credentials": {"identifier": "foo", "password": "bar"}}, autospec=False)


def test_handle_notification_valid():
    """
    Given a valid SNS notification message
    When handle_notification is called with the message and raw json
    Then should parse to a valid incident
    """
    raw_json = {}
    expected_notification = {
        "name": "NotificationSubject",
        "labels": [],
        "rawJSON": raw_json,
        "occurred": "2024-02-13T18:03:27.239Z",
        "details": "ExternalID:uuid TopicArn:topicarn Message:NotificationMessage",
        "type": "AWS-SNS Notification",
    }

    actual_incident = handle_notification(VALID_PAYLOAD, raw_json)

    assert actual_incident == expected_notification


@patch("AWSSNSListener.client")
@patch("AWSSNSListener.X509")
@patch("M2Crypto.EVP.PKey")
def test_is_valid_sns_message(mock_client, mock_x509, mock_PKey):
    sNSCertificateManager = SNSCertificateManager()
    mock_resp = requests.models.Response()
    mock_resp.status_code = 200
    response_content = """-----BEGIN VALID CERTIFICATE-----
                          -----END CERTIFICATE-----"""
    mock_resp._content = str.encode(response_content)
    mock_client.get.return_value = mock_resp
    mock_PKey.verify_final.return_value = 1
    mock_x509.get_pubkey.return_value = mock_PKey
    mock_x509.load_cert_string.return_value = mock_x509
    mock_x509.get_subject.return_value = MagicMock(CN="sns.amazonaws.com")
    is_valid = sNSCertificateManager.is_valid_sns_message(VALID_PAYLOAD)
    assert is_valid


@patch("AWSSNSListener.client")
@patch("AWSSNSListener.X509")
@patch("M2Crypto.EVP.PKey")
def test_not_valid_sns_message(mock_client, mock_x509, mock_PKey, capfd):
    sNSCertificateManager = SNSCertificateManager()
    mock_resp = requests.models.Response()
    mock_resp.status_code = 200
    response_content = """-----BEGIN INVALID CERTIFICATE-----
                          -----END CERTIFICATE-----"""
    mock_resp._content = str.encode(response_content)
    mock_client.get.return_value = mock_resp
    mock_PKey.verify_final.return_value = 2
    mock_x509.get_pubkey.return_value = mock_PKey
    mock_x509.load_cert_string.return_value = mock_x509
    with capfd.disabled():
        is_valid = sNSCertificateManager.is_valid_sns_message(VALID_PAYLOAD)
        assert is_valid is False


@patch("fastapi.security.http.HTTPBasicCredentials")
def test_valid_credentials(mock_httpBasicCredentials, mock_params):
    """
    Given valid credentials, request headers and token
    When is_valid_integration_credentials is called
    Then it should return True, header_name
    """
    mock_httpBasicCredentials.username = "foo"
    mock_httpBasicCredentials.password = "bar"
    request_headers = {}
    token = "sometoken"
    result, header_name = is_valid_integration_credentials(mock_httpBasicCredentials, request_headers, token)
    assert result is True
    assert header_name is None


@patch("fastapi.security.http.HTTPBasicCredentials")
def test_invalid_credentials(mock_httpBasicCredentials, mock_params):
    """
    Given invalid credentials, request headers and token
    When is_valid_integration_credentials is called
    Then it should return True, header_name
    """
    mock_httpBasicCredentials.username = "foot"
    mock_httpBasicCredentials.password = "bark"
    request_headers = {}
    token = "sometoken"
    result, header_name = is_valid_integration_credentials(mock_httpBasicCredentials, request_headers, token)
    assert result is False


class TestValidateSnsUrl:
    """Tests for URL format validation in _validate_sns_url."""

    def test_valid_aws_sns_url_accepted(self):
        """Test that a valid AWS SNS URL passes validation."""
        from AWSSNSListener import _validate_sns_url

        _validate_sns_url("https://sns.us-east-1.amazonaws.com/cert.pem", "SigningCertURL")

    def test_valid_aws_china_url_accepted(self):
        """Test that a valid AWS China region URL passes validation."""
        from AWSSNSListener import _validate_sns_url

        _validate_sns_url("https://sns.cn-north-1.amazonaws.com.cn/cert.pem", "SigningCertURL")

    def test_non_https_url_rejected(self):
        """Test that a non-HTTPS URL is rejected."""
        from AWSSNSListener import _validate_sns_url

        with pytest.raises(DemistoException, match="must use HTTPS"):
            _validate_sns_url("http://sns.us-east-1.amazonaws.com/cert.pem", "SigningCertURL")

    def test_non_aws_host_rejected(self):
        """Test that a non-AWS host is rejected."""
        from AWSSNSListener import _validate_sns_url

        with pytest.raises(DemistoException, match="not an AWS SNS endpoint"):
            _validate_sns_url("https://attacker.example.com/cert.pem", "SigningCertURL")

    def test_aws_like_subdomain_rejected(self):
        """Test that a URL with an AWS-like subdomain on a different host is rejected."""
        from AWSSNSListener import _validate_sns_url

        with pytest.raises(DemistoException, match="not an AWS SNS endpoint"):
            _validate_sns_url("https://sns.us-east-1.amazonaws.com.evil.com/cert.pem", "SigningCertURL")