feat(apigateway): add new check apigateway_restapi_cache_encrypted (#5448)

This commit is contained in:
Daniel Barranquero
2024-10-21 16:38:55 +02:00
committed by GitHub
parent 147c3c455b
commit 1aca7a754c
6 changed files with 376 additions and 2 deletions
@@ -0,0 +1,34 @@
{
"Provider": "aws",
"CheckID": "apigateway_restapi_cache_encrypted",
"CheckTitle": "Check if API Gateway REST API cache data is encrypted at rest.",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "apigateway",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:apigateway:region:account-id:/restapis/restapi-id/stages/stage-name",
"Severity": "medium",
"ResourceType": "AwsApiGatewayStage",
"Description": "This control checks whether all methods in API Gateway REST API stages that have cache enabled are encrypted. The control fails if any method in an API Gateway REST API stage is configured to cache and the cache is not encrypted.",
"Risk": "Without encryption, cached data in API Gateway REST APIs may be vulnerable to unauthorized access, potentially exposing sensitive information to users not authenticated to AWS.",
"RelatedUrl": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html#enable-api-gateway-caching",
"Remediation": {
"Code": {
"CLI": "aws apigateway update-stage --rest-api-id <restapi-id> --stage-name <stage-name> --patch-operations op=replace,path=/<resourcePath>/<httpMethod>/caching/enabled,value=true op=replace,path=/<resourcePath>/<httpMethod>/caching/dataEncrypted,value=true",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/apigateway-controls.html#apigateway-5",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure that API Gateway REST API cache data is encrypted at rest by enabling the 'Encrypt cache data' setting in the API Gateway stage configuration.",
"Url": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html#enable-api-gateway-caching"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.apigateway.apigateway_client import (
apigateway_client,
)
class apigateway_restapi_cache_encrypted(Check):
def execute(self):
findings = []
for rest_api in apigateway_client.rest_apis:
for stage in rest_api.stages:
if stage.cache_enabled:
report = Check_Report_AWS(self.metadata())
report.region = rest_api.region
report.resource_id = rest_api.name
report.resource_arn = stage.arn
report.resource_tags = stage.tags
report.status = "PASS"
report.status_extended = f"API Gateway {rest_api.name} ID {rest_api.id} in stage {stage.name} has cache encryption enabled."
if not stage.cache_data_encrypted:
report.status = "FAIL"
report.status_extended = f"API Gateway {rest_api.name} ID {rest_api.id} in stage {stage.name} has cache enabled but cache data is not encrypted at rest."
findings.append(report)
return findings
@@ -114,11 +114,18 @@ class APIGateway(AWSService):
waf = None
logging = False
client_certificate = False
cache_enabled = False
cache_data_encrypted = False
if "webAclArn" in stage:
waf = stage["webAclArn"]
if "methodSettings" in stage:
if stage["methodSettings"]:
logging = True
for settings in stage["methodSettings"].values():
if settings.get("loggingLevel"):
logging = True
if settings.get("cachingEnabled"):
cache_enabled = True
if settings.get("cacheDataEncrypted"):
cache_data_encrypted = True
if "clientCertificateId" in stage:
client_certificate = True
arn = f"arn:{self.audited_partition}:apigateway:{regional_client.region}::/restapis/{rest_api.id}/stages/{stage['stageName']}"
@@ -130,6 +137,8 @@ class APIGateway(AWSService):
client_certificate=client_certificate,
waf=waf,
tags=[stage.get("tags")],
cache_enabled=cache_enabled,
cache_data_encrypted=cache_data_encrypted,
)
)
except ClientError as error:
@@ -213,6 +222,8 @@ class Stage(BaseModel):
client_certificate: bool
waf: Optional[str]
tags: Optional[list] = []
cache_enabled: Optional[bool]
cache_data_encrypted: Optional[bool]
class PathResourceMethods(BaseModel):
@@ -0,0 +1,292 @@
from unittest import mock
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_REGION_EU_WEST_1,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
class Test_apigateway_restapi_cache_encrypted:
@mock_aws
def test_apigateway_no_rest_apis(self):
from prowler.providers.aws.services.apigateway.apigateway_service import (
APIGateway,
)
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted.apigateway_client",
new=APIGateway(aws_provider),
):
# Test Check
from prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted import (
apigateway_restapi_cache_encrypted,
)
check = apigateway_restapi_cache_encrypted()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_apigateway_one_rest_api_with_cache_encrypted(self):
# Create APIGateway Mocked Resources
apigateway_client = client("apigateway", region_name=AWS_REGION_US_EAST_1)
rest_api = apigateway_client.create_rest_api(
name="test-rest-api",
)
# Get the rest api's root id
root_resource_id = apigateway_client.get_resources(restApiId=rest_api["id"])[
"items"
][0]["id"]
resource = apigateway_client.create_resource(
restApiId=rest_api["id"],
parentId=root_resource_id,
pathPart="test-path",
)
apigateway_client.put_method(
restApiId=rest_api["id"],
resourceId=resource["id"],
httpMethod="GET",
authorizationType="NONE",
)
apigateway_client.put_integration(
restApiId=rest_api["id"],
resourceId=resource["id"],
httpMethod="GET",
type="HTTP",
integrationHttpMethod="POST",
uri="http://test.com",
)
apigateway_client.create_deployment(
restApiId=rest_api["id"],
stageName="test",
)
apigateway_client.update_stage(
restApiId=rest_api["id"],
stageName="test",
patchOperations=[
{
"op": "replace",
"path": "/*/*/caching/enabled",
"value": "true",
},
{
"op": "replace",
"path": "/*/*/caching/dataEncrypted",
"value": "true",
},
],
)
from prowler.providers.aws.services.apigateway.apigateway_service import (
APIGateway,
)
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted.apigateway_client",
new=APIGateway(aws_provider),
):
# Test Check
from prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted import (
apigateway_restapi_cache_encrypted,
)
check = apigateway_restapi_cache_encrypted()
result = check.execute()
assert result[0].status == "PASS"
assert len(result) == 1
assert (
result[0].status_extended
== f"API Gateway test-rest-api ID {rest_api['id']} in stage test has cache encryption enabled."
)
assert result[0].resource_id == "test-rest-api"
assert (
result[0].resource_arn
== f"arn:{aws_provider.identity.partition}:apigateway:{AWS_REGION_US_EAST_1}::/restapis/{rest_api['id']}/stages/test"
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == [None]
@mock_aws
def test_apigateway_one_rest_api_without_cache_encrypted(self):
# Create APIGateway Mocked Resources
apigateway_client = client("apigateway", region_name=AWS_REGION_US_EAST_1)
# Create APIGateway Rest API
rest_api = apigateway_client.create_rest_api(
name="test-rest-api",
)
# Get the rest api's root id
root_resource_id = apigateway_client.get_resources(restApiId=rest_api["id"])[
"items"
][0]["id"]
resource = apigateway_client.create_resource(
restApiId=rest_api["id"],
parentId=root_resource_id,
pathPart="test-path",
)
apigateway_client.put_method(
restApiId=rest_api["id"],
resourceId=resource["id"],
httpMethod="GET",
authorizationType="NONE",
)
apigateway_client.put_integration(
restApiId=rest_api["id"],
resourceId=resource["id"],
httpMethod="GET",
type="HTTP",
integrationHttpMethod="POST",
uri="http://test.com",
)
apigateway_client.create_deployment(
restApiId=rest_api["id"],
stageName="test",
)
apigateway_client.update_stage(
restApiId=rest_api["id"],
stageName="test",
patchOperations=[
{
"op": "replace",
"path": "/*/*/caching/enabled",
"value": "true",
},
{
"op": "replace",
"path": "/*/*/caching/dataEncrypted",
"value": "false",
},
],
)
from prowler.providers.aws.services.apigateway.apigateway_service import (
APIGateway,
)
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted.apigateway_client",
new=APIGateway(aws_provider),
):
# Test Check
from prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted import (
apigateway_restapi_cache_encrypted,
)
check = apigateway_restapi_cache_encrypted()
result = check.execute()
assert result[0].status == "FAIL"
assert len(result) == 1
assert (
result[0].status_extended
== f"API Gateway test-rest-api ID {rest_api['id']} in stage test has cache enabled but cache data is not encrypted at rest."
)
assert result[0].resource_id == "test-rest-api"
assert (
result[0].resource_arn
== f"arn:{aws_provider.identity.partition}:apigateway:{AWS_REGION_US_EAST_1}::/restapis/{rest_api['id']}/stages/test"
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == [None]
@mock_aws
def test_apigateway_one_rest_api_without_cache_enabled(self):
# Create APIGateway Mocked Resources
apigateway_client = client("apigateway", region_name=AWS_REGION_US_EAST_1)
# Create APIGateway Rest API
rest_api = apigateway_client.create_rest_api(
name="test-rest-api",
)
# Get the rest api's root id
root_resource_id = apigateway_client.get_resources(restApiId=rest_api["id"])[
"items"
][0]["id"]
resource = apigateway_client.create_resource(
restApiId=rest_api["id"],
parentId=root_resource_id,
pathPart="test-path",
)
apigateway_client.put_method(
restApiId=rest_api["id"],
resourceId=resource["id"],
httpMethod="GET",
authorizationType="NONE",
)
apigateway_client.put_integration(
restApiId=rest_api["id"],
resourceId=resource["id"],
httpMethod="GET",
type="HTTP",
integrationHttpMethod="POST",
uri="http://test.com",
)
apigateway_client.create_deployment(
restApiId=rest_api["id"],
stageName="test",
)
apigateway_client.update_stage(
restApiId=rest_api["id"],
stageName="test",
patchOperations=[
{
"op": "replace",
"path": "/*/*/caching/enabled",
"value": "false",
},
{
"op": "replace",
"path": "/*/*/caching/cacheDataEncrypted",
"value": "false",
},
],
)
from prowler.providers.aws.services.apigateway.apigateway_service import (
APIGateway,
)
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted.apigateway_client",
new=APIGateway(aws_provider),
):
# Test Check
from prowler.providers.aws.services.apigateway.apigateway_restapi_cache_encrypted.apigateway_restapi_cache_encrypted import (
apigateway_restapi_cache_encrypted,
)
check = apigateway_restapi_cache_encrypted()
result = check.execute()
assert len(result) == 0
@@ -141,11 +141,23 @@ class Test_APIGateway_Service:
"path": "/*/*/logging/loglevel",
"value": "INFO",
},
{
"op": "replace",
"path": "/*/*/caching/enabled",
"value": "true",
},
{
"op": "replace",
"path": "/*/*/caching/dataEncrypted",
"value": "false",
},
],
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
apigateway = APIGateway(aws_provider)
assert apigateway.rest_apis[0].stages[0].logging is True
assert apigateway.rest_apis[0].stages[0].cache_enabled is True
assert apigateway.rest_apis[0].stages[0].cache_data_encrypted is False
# Test APIGateway _get_resources
@mock_aws