From a0ef56f245b85942bee465f2e924b6fc6a989e54 Mon Sep 17 00:00:00 2001 From: Nacho Rivera <59198746+n4ch04@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:51:36 +0100 Subject: [PATCH] feat(): sqs service and checks (#1501) --- providers/aws/services/sqs/__init__.py | 0 providers/aws/services/sqs/check_extra727 | 61 -------- providers/aws/services/sqs/check_extra728 | 49 ------ providers/aws/services/sqs/sqs_client.py | 4 + .../__init__.py | 0 ...eues_not_publicly_accessible.metadata.json | 35 +++++ .../sqs_queues_not_publicly_accessible.py | 40 +++++ ...sqs_queues_not_publicly_accessible_test.py | 145 ++++++++++++++++++ .../__init__.py | 0 ...rver_side_encryption_enabled.metadata.json | 35 +++++ ...s_queues_server_side_encryption_enabled.py | 24 +++ ...ues_server_side_encryption_enabled_test.py | 78 ++++++++++ providers/aws/services/sqs/sqs_service.py | 75 +++++++++ .../aws/services/sqs/sqs_service_test.py | 117 ++++++++++++++ 14 files changed, 553 insertions(+), 110 deletions(-) create mode 100644 providers/aws/services/sqs/__init__.py delete mode 100644 providers/aws/services/sqs/check_extra727 delete mode 100644 providers/aws/services/sqs/check_extra728 create mode 100644 providers/aws/services/sqs/sqs_client.py create mode 100644 providers/aws/services/sqs/sqs_queues_not_publicly_accessible/__init__.py create mode 100644 providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.metadata.json create mode 100644 providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.py create mode 100644 providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py create mode 100644 providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/__init__.py create mode 100644 providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json create mode 100644 providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.py create mode 100644 providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py create mode 100644 providers/aws/services/sqs/sqs_service.py create mode 100644 providers/aws/services/sqs/sqs_service_test.py diff --git a/providers/aws/services/sqs/__init__.py b/providers/aws/services/sqs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/sqs/check_extra727 b/providers/aws/services/sqs/check_extra727 deleted file mode 100644 index 366e3720..00000000 --- a/providers/aws/services/sqs/check_extra727 +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_extra727="7.27" -CHECK_TITLE_extra727="[extra727] Check if SQS queues have policy set as Public" -CHECK_SCORED_extra727="NOT_SCORED" -CHECK_CIS_LEVEL_extra727="EXTRA" -CHECK_SEVERITY_extra727="Critical" -CHECK_ASFF_RESOURCE_TYPE_extra727="AwsSqsQueue" -CHECK_ALTERNATE_check727="extra727" -CHECK_SERVICENAME_extra727="sqs" -CHECK_RISK_extra727='Sensitive information could be disclosed.' -CHECK_REMEDIATION_extra727='Review service with overly permissive policies. Adhere to Principle of Least Privilege.' -CHECK_DOC_extra727='https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-examples-of-sqs-policies.html' -CHECK_CAF_EPIC_extra727='Infrastructure Security' - -extra727(){ - for regx in $REGIONS; do - LIST_SQS=$($AWSCLI sqs list-queues $PROFILE_OPT --region $regx --query QueueUrls --output text 2>&1|grep -v ^None ) - if [[ $(echo "$LIST_SQS" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then - textInfo "$regx: Access Denied trying to list queues" "$regx" - continue - fi - if [[ $LIST_SQS ]]; then - for queue in $LIST_SQS; do - SQS_POLICY=$($AWSCLI sqs get-queue-attributes --queue-url $queue $PROFILE_OPT --region $regx --attribute-names All --query Attributes.Policy) - if [[ "$SQS_POLICY" != "null" ]]; then - SQS_POLICY_ALLOW_ALL=$(echo $SQS_POLICY \ - | jq '. | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*")') - if [[ $SQS_POLICY_ALLOW_ALL ]]; then - SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION=$(echo $SQS_POLICY \ - | jq '. | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*") | select(has("Condition") | not)') - if [[ $SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION ]]; then - SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS=$(echo $SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION \ - | jq '"[Principal: " + (.Principal|tostring) + " Action: " + (.Action|tostring) + "]"' ) - textFail "$regx: SQS $queue queue policy with public access" "$regx" "$queue" - else - textInfo "$regx: SQS $queue queue policy with public access but has a Condition" "$regx" "$queue" - fi - else - textPass "$regx: SQS $queue queue without public access" "$regx" "$queue" - fi - else - textPass "$regx: SQS $queue queue without policy" "$regx" "$queue" - fi - done - else - textInfo "$regx: No SQS queues found" "$regx" - fi - done -} diff --git a/providers/aws/services/sqs/check_extra728 b/providers/aws/services/sqs/check_extra728 deleted file mode 100644 index dc3445bd..00000000 --- a/providers/aws/services/sqs/check_extra728 +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_extra728="7.28" -CHECK_TITLE_extra728="[extra728] Check if SQS queues have Server Side Encryption enabled" -CHECK_SCORED_extra728="NOT_SCORED" -CHECK_CIS_LEVEL_extra728="EXTRA" -CHECK_SEVERITY_extra728="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra728="AwsSqsQueue" -CHECK_ALTERNATE_check728="extra728" -CHECK_ASFF_COMPLIANCE_TYPE_extra728="ens-mp.info.3.sns.1" -CHECK_SERVICENAME_extra728="sqs" -CHECK_RISK_extra728='If not enabled sensitive information in transit is not protected.' -CHECK_REMEDIATION_extra728='Enable Encryption. Use a CMK where possible. It will provide additional management and privacy benefits.' -CHECK_DOC_extra728='https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-sse-existing-queue.html' -CHECK_CAF_EPIC_extra728='Data Protection' - -extra728(){ - for regx in $REGIONS; do - LIST_SQS=$($AWSCLI sqs list-queues $PROFILE_OPT --region $regx --query QueueUrls --output text 2>&1|grep -v ^None ) - if [[ $(echo "$LIST_SQS" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then - textInfo "$regx: Access Denied trying to list queues" "$regx" - continue - fi - if [[ $LIST_SQS ]]; then - for queue in $LIST_SQS; do - # check if the policy has KmsMasterKeyId therefore SSE enabled - SSE_ENABLED_QUEUE=$($AWSCLI sqs get-queue-attributes --queue-url $queue $PROFILE_OPT --region $regx --attribute-names All --query Attributes.KmsMasterKeyId --output text|grep -v ^None) - if [[ $SSE_ENABLED_QUEUE ]]; then - textPass "$regx: SQS queue $queue is using Server Side Encryption" "$regx" "$queue" - else - textFail "$regx: SQS queue $queue is not using Server Side Encryption" "$regx" "$queue" - fi - done - else - textInfo "$regx: No SQS queues found" "$regx" - fi - done -} diff --git a/providers/aws/services/sqs/sqs_client.py b/providers/aws/services/sqs/sqs_client.py new file mode 100644 index 00000000..eb423cd6 --- /dev/null +++ b/providers/aws/services/sqs/sqs_client.py @@ -0,0 +1,4 @@ +from providers.aws.lib.audit_info.audit_info import current_audit_info +from providers.aws.services.sqs.sqs_service import SQS + +sqs_client = SQS(current_audit_info) diff --git a/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/__init__.py b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.metadata.json b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.metadata.json new file mode 100644 index 00000000..8f0df552 --- /dev/null +++ b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "sqs_queues_not_publicly_accessible", + "CheckTitle": "Check if SQS queues have policy set as Public", + "CheckType": [], + "ServiceName": "sqs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:sqs:region:account-id:queue", + "Severity": "critical", + "ResourceType": "AwsSqsQueue", + "Description": "Check if SQS queues have policy set as Public", + "Risk": "Sensitive information could be disclosed", + "RelatedUrl": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-examples-of-sqs-policies.html", + "Remediation": { + "Code": { + "CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/SQS/sqs-queue-exposed.html", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/SQS/sqs-queue-exposed.html", + "Terraform": "https://docs.bridgecrew.io/docs/ensure-sqs-queue-policy-is-not-public-by-only-allowing-specific-services-or-principals-to-access-it#terraform" + }, + "Recommendation": { + "Text": "Review service with overly permissive policies. Adhere to Principle of Least Privilege.", + "Url": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-examples-of-sqs-policies.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.py b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.py new file mode 100644 index 00000000..b36699a2 --- /dev/null +++ b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible.py @@ -0,0 +1,40 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.sqs.sqs_client import sqs_client + + +class sqs_queues_not_publicly_accessible(Check): + def execute(self): + findings = [] + for queue in sqs_client.queues: + report = Check_Report(self.metadata) + report.region = queue.region + report.resource_id = queue.id + report.resource_arn = queue.arn + report.status = "PASS" + report.status_extended = f"SQS queue {queue.id} is not public" + if queue.policy: + for statement in queue.policy["Statement"]: + # Only check allow statements + if statement["Effect"] == "Allow": + if ( + "*" in statement["Principal"] + or ( + "AWS" in statement["Principal"] + and "*" in statement["Principal"]["AWS"] + ) + or ( + "CanonicalUser" in statement["Principal"] + and "*" in statement["Principal"]["CanonicalUser"] + ) + ): + if "Condition" not in statement: + report.status = "FAIL" + report.status_extended = ( + f"SQS queue {queue.id} policy with public access" + ) + else: + report.status = "FAIL" + report.status_extended = f"SQS queue {queue.id} policy with public access but has a Condition" + findings.append(report) + + return findings diff --git a/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py new file mode 100644 index 00000000..78eeecb3 --- /dev/null +++ b/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py @@ -0,0 +1,145 @@ +from re import search +from unittest import mock +from uuid import uuid4 + +from providers.aws.services.sqs.sqs_service import Queue + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + +queue_id = str(uuid4()) +topic_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{queue_id}" + +test_restricted_policy = { + "Version": "2012-10-17", + "Id": "Queue1_Policy_UUID", + "Statement": [ + { + "Sid": "Queue1_AnonymousAccess_ReceiveMessage", + "Effect": "Allow", + "Principal": {"AWS": {AWS_ACCOUNT_NUMBER}}, + "Action": "sqs:ReceiveMessage", + "Resource": topic_arn, + } + ], +} + +test_public_policy = { + "Version": "2012-10-17", + "Id": "Queue1_Policy_UUID", + "Statement": [ + { + "Sid": "Queue1_AnonymousAccess_ReceiveMessage", + "Effect": "Allow", + "Principal": "*", + "Action": "sqs:ReceiveMessage", + "Resource": topic_arn, + } + ], +} + +test_public_policy_with_condition = { + "Version": "2012-10-17", + "Id": "Queue1_Policy_UUID", + "Statement": [ + { + "Sid": "Queue1_AnonymousAccess_ReceiveMessage", + "Effect": "Allow", + "Principal": "*", + "Action": "sqs:ReceiveMessage", + "Resource": topic_arn, + "Condition": { + "DateGreaterThan": {"aws:CurrentTime": "2009-01-31T12:00Z"}, + "DateLessThan": {"aws:CurrentTime": "2009-01-31T15:00Z"}, + }, + } + ], +} + + +class Test_sqs_queues_not_publicly_accessible: + def test_no_queues(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( + sqs_queues_not_publicly_accessible, + ) + + check = sqs_queues_not_publicly_accessible() + result = check.execute() + assert len(result) == 0 + + def test_queues_not_public(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + sqs_client.queues.append( + Queue(id=queue_id, region=AWS_REGION, policy=test_restricted_policy) + ) + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( + sqs_queues_not_publicly_accessible, + ) + + check = sqs_queues_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert search("is not public", result[0].status_extended) + assert result[0].resource_id == queue_id + assert result[0].resource_arn == "" + + def test_queues_public(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + sqs_client.queues.append( + Queue(id=queue_id, region=AWS_REGION, policy=test_public_policy) + ) + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( + sqs_queues_not_publicly_accessible, + ) + + check = sqs_queues_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search("policy with public access", result[0].status_extended) + assert result[0].resource_id == queue_id + assert result[0].resource_arn == "" + + def test_queues_public_with_condition(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + sqs_client.queues.append( + Queue( + id=queue_id, region=AWS_REGION, policy=test_public_policy_with_condition + ) + ) + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( + sqs_queues_not_publicly_accessible, + ) + + check = sqs_queues_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "policy with public access but has a Condition", + result[0].status_extended, + ) + assert result[0].resource_id == queue_id + assert result[0].resource_arn == "" diff --git a/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/__init__.py b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json new file mode 100644 index 00000000..84a82d64 --- /dev/null +++ b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "sqs_queues_server_side_encryption_enabled", + "CheckTitle": "Check if SQS queues have Server Side Encryption enabled", + "CheckType": [], + "ServiceName": "sqs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:sqs:region:account-id:queue", + "Severity": "medium", + "ResourceType": "AwsSqsQueue", + "Description": "Check if SQS queues have Server Side Encryption enabled", + "Risk": "If not enabled sensitive information in transit is not protected.", + "RelatedUrl": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-sse-existing-queue.html", + "Remediation": { + "Code": { + "CLI": "aws sqs set-queue-attributes --queue-url --attributes KmsMasterKeyId=", + "NativeIaC": "https://docs.bridgecrew.io/docs/general_16-encrypt-sqs-queue#cloudformation", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/SQS/queue-encrypted-with-kms-customer-master-keys.html", + "Terraform": "https://docs.bridgecrew.io/docs/general_16-encrypt-sqs-queue#terraform" + }, + "Recommendation": { + "Text": "Enable Encryption. Use a CMK where possible. It will provide additional management and privacy benefits", + "Url": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-sse-existing-queue.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.py b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.py new file mode 100644 index 00000000..9bf57a74 --- /dev/null +++ b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.py @@ -0,0 +1,24 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.sqs.sqs_client import sqs_client + + +class sqs_queues_server_side_encryption_enabled(Check): + def execute(self): + findings = [] + for queue in sqs_client.queues: + report = Check_Report(self.metadata) + report.region = queue.region + report.resource_id = queue.id + report.resource_arn = queue.arn + report.status = "PASS" + report.status_extended = ( + f"SQS queue {queue.id} is using Server Side Encryption" + ) + if not queue.kms_key_id: + report.status = "FAIL" + report.status_extended = ( + f"SQS queue {queue.id} is not using Server Side Encryption" + ) + findings.append(report) + + return findings diff --git a/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py new file mode 100644 index 00000000..7225e691 --- /dev/null +++ b/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py @@ -0,0 +1,78 @@ +from re import search +from unittest import mock +from uuid import uuid4 + +from providers.aws.services.sqs.sqs_service import Queue + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + +test_kms_key_id = str(uuid4()) +queue_id = str(uuid4()) +topic_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{queue_id}" + + +class Test_sqs_queues_server_side_encryption_enabled: + def test_no_queues(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_server_side_encryption_enabled.sqs_queues_server_side_encryption_enabled import ( + sqs_queues_server_side_encryption_enabled, + ) + + check = sqs_queues_server_side_encryption_enabled() + result = check.execute() + assert len(result) == 0 + + def test_queues_with_encryption(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + sqs_client.queues.append( + Queue(id=queue_id, region=AWS_REGION, kms_key_id=test_kms_key_id) + ) + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_server_side_encryption_enabled.sqs_queues_server_side_encryption_enabled import ( + sqs_queues_server_side_encryption_enabled, + ) + + check = sqs_queues_server_side_encryption_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert search("is using Server Side Encryption", result[0].status_extended) + assert result[0].resource_id == queue_id + assert result[0].resource_arn == "" + + def test_queues_no_encryption(self): + sqs_client = mock.MagicMock + sqs_client.queues = [] + sqs_client.queues.append( + Queue( + id=queue_id, + region=AWS_REGION, + ) + ) + with mock.patch( + "providers.aws.services.sqs.sqs_service.SQS", + sqs_client, + ): + from providers.aws.services.sqs.sqs_queues_server_side_encryption_enabled.sqs_queues_server_side_encryption_enabled import ( + sqs_queues_server_side_encryption_enabled, + ) + + check = sqs_queues_server_side_encryption_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "is not using Server Side Encryption", result[0].status_extended + ) + assert result[0].resource_id == queue_id + assert result[0].resource_arn == "" diff --git a/providers/aws/services/sqs/sqs_service.py b/providers/aws/services/sqs/sqs_service.py new file mode 100644 index 00000000..5af7b40d --- /dev/null +++ b/providers/aws/services/sqs/sqs_service.py @@ -0,0 +1,75 @@ +import threading +from json import loads + +from pydantic import BaseModel + +from lib.logger import logger +from providers.aws.aws_provider import generate_regional_clients + + +################################ SQS +class SQS: + def __init__(self, audit_info): + self.service = "sqs" + self.session = audit_info.audit_session + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.queues = [] + self.__threading_call__(self.__list_queues__) + self.__get_queue_attributes__(self.regional_clients) + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __list_queues__(self, regional_client): + logger.info("Redshift - describing queues...") + try: + list_queues_paginator = regional_client.get_paginator("list_queues") + for page in list_queues_paginator.paginate(): + for queue in page["QueueUrls"]: + self.queues.append( + Queue( + id=queue, + region=regional_client.region, + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __get_queue_attributes__(self, regional_clients): + try: + for queue in self.queues: + regional_client = regional_clients[queue.region] + queue_attributes = regional_client.get_queue_attributes( + QueueUrl=queue.id + ) + if ( + "Attributes" in queue_attributes + and "Policy" in queue_attributes["Attributes"] + ): + queue.policy = loads(queue_attributes["Attributes"]["Policy"]) + if "KmsMasterKeyId" in queue_attributes["Attributes"]: + queue.kms_key_id = queue_attributes["Attributes"]["KmsMasterKeyId"] + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class Queue(BaseModel): + id: str + arn: str = "" + region: str + policy: dict = None + kms_key_id: str = None diff --git a/providers/aws/services/sqs/sqs_service_test.py b/providers/aws/services/sqs/sqs_service_test.py new file mode 100644 index 00000000..7505c997 --- /dev/null +++ b/providers/aws/services/sqs/sqs_service_test.py @@ -0,0 +1,117 @@ +from json import dumps +from unittest.mock import patch +from uuid import uuid4 + +import botocore +from boto3 import client, session +from moto import mock_sqs + +from providers.aws.lib.audit_info.models import AWS_Audit_Info +from providers.aws.services.sqs.sqs_service import SQS + +AWS_ACCOUNT_NUMBER = 123456789012 +AWS_REGION = "eu-west-1" + +test_queue = "test-queue" +test_key = str(uuid4()) +test_queue_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{test_queue}" +test_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "sqs:SendMessage", + "Resource": test_queue_arn, + } + ], +} + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "GetQueueAttributes": + return { + "Attributes": {"Policy": dumps(test_policy), "KmsMasterKeyId": test_key} + } + return make_api_call(self, operation_name, kwarg) + + +def mock_generate_regional_clients(service, audit_info): + regional_client = audit_info.audit_session.client(service, region_name=AWS_REGION) + regional_client.region = AWS_REGION + return {AWS_REGION: regional_client} + + +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +@patch( + "providers.aws.services.sqs.sqs_service.generate_regional_clients", + new=mock_generate_regional_clients, +) +class Test_SQS_Service: + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=AWS_ACCOUNT_NUMBER, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region=None, + credentials=None, + assumed_role_info=None, + audited_regions=None, + organizations_metadata=None, + ) + return audit_info + + # Test SQS Service + def test_service(self): + audit_info = self.set_mocked_audit_info() + sqs = SQS(audit_info) + assert sqs.service == "sqs" + + # Test SQS client + def test_client(self): + audit_info = self.set_mocked_audit_info() + sqs = SQS(audit_info) + for reg_client in sqs.regional_clients.values(): + assert reg_client.__class__.__name__ == "SQS" + + # Test SQS session + def test__get_session__(self): + audit_info = self.set_mocked_audit_info() + sqs = SQS(audit_info) + assert sqs.session.__class__.__name__ == "Session" + + @mock_sqs + # Test SQS list queues + def test__list_queues__(self): + sqs_client = client("sqs", region_name=AWS_REGION) + queue = sqs_client.create_queue(QueueName=test_queue) + audit_info = self.set_mocked_audit_info() + sqs = SQS(audit_info) + assert len(sqs.queues) == 1 + assert sqs.queues[0].id == queue["QueueUrl"] + assert sqs.queues[0].region == AWS_REGION + + @mock_sqs + # Test SQS list queues + def test__get_queue_attributes__(self): + sqs_client = client("sqs", region_name=AWS_REGION) + queue = sqs_client.create_queue( + QueueName=test_queue, + ) + audit_info = self.set_mocked_audit_info() + sqs = SQS(audit_info) + assert len(sqs.queues) == 1 + assert sqs.queues[0].id == queue["QueueUrl"] + assert sqs.queues[0].region == AWS_REGION + assert sqs.queues[0].policy + assert sqs.queues[0].kms_key_id == test_key