feat(): sqs service and checks (#1501)

This commit is contained in:
Nacho Rivera
2022-11-17 22:51:36 +01:00
committed by GitHub
parent e016fb2d6b
commit a0ef56f245
14 changed files with 553 additions and 110 deletions

View File

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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": []
}

View File

@@ -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

View File

@@ -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 == ""

View File

@@ -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 <QUEUE_URL> --attributes KmsMasterKeyId=<KEY>",
"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": []
}

View File

@@ -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

View File

@@ -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 == ""

View File

@@ -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

View File

@@ -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