feat(acm): Add new check for insecure algorithms in certificates (#4551)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Mario Rodriguez Lopez
2024-08-07 14:00:24 +02:00
committed by GitHub
parent 80e13bffa2
commit 1b18aef0f0
11 changed files with 233 additions and 10 deletions
+5
View File
@@ -271,6 +271,11 @@ aws:
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# aws.acm_certificates_rsa_key_length
insecure_key_algorithms:
[
"RSA-1024",
]
# AWS EKS Configuration
# aws.eks_control_plane_logging_all_types_enabled
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "acm_certificates_with_secure_key_algorithms",
"CheckTitle": "Check if ACM Certificates use a secure key algorithm",
"CheckType": [
"Data Protection"
],
"ServiceName": "acm",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:acm:region:account-id:certificate/resource-id",
"Severity": "high",
"ResourceType": "AwsCertificateManagerCertificate",
"Description": "Check if ACM Certificates use a secure key algorithm (RSA 2048 bits or more, or ECDSA 256 bits or more). For example certificates that use RSA-1024 can be compromised because the encryption could be broken in no more than 2^80 guesses making it vulnerable to a factorization attack.",
"Risk": "Certificates with weak RSA or ECDSA keys can be compromised because the length of the key defines the security of the encryption. The number of bits in the key determines the number of guesses an attacker would have to make in order to decrypt the data. The more bits in the key, the more secure the encryption.",
"RelatedUrl": "https://docs.aws.amazon.com/acm/latest/userguide/acm-certificate.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure that all ACM certificates use a secure key algorithm. If any certificates use smaller keys, regenerate them with a secure key size and update any systems that rely on these certificates.",
"Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/acm-controls.html#acm-2"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,26 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.acm.acm_client import acm_client
class acm_certificates_with_secure_key_algorithms(Check):
def execute(self):
findings = []
for certificate in acm_client.certificates:
if certificate.in_use or acm_client.provider.scan_unused_services:
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} uses a secure key algorithm ({certificate.key_algorithm})."
if certificate.key_algorithm in acm_client.audit_config.get(
"insecure_algorithms", ["RSA-1024"]
):
report.status = "FAIL"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} does not use a secure key algorithm ({certificate.key_algorithm})."
findings.append(report)
return findings
@@ -14,17 +14,28 @@ class ACM(AWSService):
# Call AWSService's __init__
super().__init__(__class__.__name__, provider)
self.certificates = []
self.__threading_call__(self.__list_certificates__)
self.__describe_certificates__()
self.__list_tags_for_certificate__()
self.__threading_call__(self._list_certificates)
self._describe_certificates()
self._list_tags_for_certificate()
def __list_certificates__(self, regional_client):
def _list_certificates(self, regional_client):
logger.info("ACM - Listing Certificates...")
try:
includes = {
"keyTypes": [
"RSA_1024",
"RSA_2048",
"RSA_3072",
"RSA_4096",
"EC_prime256v1",
"EC_secp384r1",
"EC_secp521r1",
]
}
list_certificates_paginator = regional_client.get_paginator(
"list_certificates"
)
for page in list_certificates_paginator.paginate():
for page in list_certificates_paginator.paginate(Includes=includes):
for certificate in page["CertificateSummaryList"]:
if not self.audit_resources or (
is_resource_filtered(
@@ -49,6 +60,7 @@ class ACM(AWSService):
name=certificate["DomainName"],
id=certificate["CertificateArn"].split("/")[-1],
type=certificate["Type"],
key_algorithm=certificate["KeyAlgorithm"],
expiration_days=certificate_expiration_time,
in_use=certificate.get("InUse", False),
transparency_logging=False,
@@ -60,7 +72,7 @@ class ACM(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_certificates__(self):
def _describe_certificates(self):
logger.info("ACM - Describing Certificates...")
try:
for certificate in self.certificates:
@@ -78,7 +90,7 @@ class ACM(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __list_tags_for_certificate__(self):
def _list_tags_for_certificate(self):
logger.info("ACM - List Tags...")
try:
for certificate in self.certificates:
@@ -98,6 +110,7 @@ class Certificate(BaseModel):
name: str
id: str
type: str
key_algorithm: str
tags: Optional[list] = []
expiration_days: int
in_use: bool
+3
View File
@@ -262,6 +262,9 @@ config_aws = {
],
"check_rds_instance_replicas": False,
"days_to_expire_threshold": 7,
"insecure_key_algorithms": [
"RSA-1024",
],
"eks_required_log_types": [
"api",
"audit",
+5
View File
@@ -271,6 +271,11 @@ aws:
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# aws.acm_certificates_rsa_key_length
insecure_key_algorithms:
[
"RSA-1024",
]
# AWS EKS Configuration
# aws.eks_control_plane_logging_all_types_enabled
@@ -32,6 +32,7 @@ class Test_acm_certificates_expiration_check:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
expiration_days = 5
in_use = True
@@ -42,6 +43,7 @@ class Test_acm_certificates_expiration_check:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
@@ -80,6 +82,7 @@ class Test_acm_certificates_expiration_check:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
expiration_days = -400
in_use = True
@@ -90,6 +93,7 @@ class Test_acm_certificates_expiration_check:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
@@ -127,6 +131,7 @@ class Test_acm_certificates_expiration_check:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
expiration_days = 365
in_use = True
@@ -137,6 +142,7 @@ class Test_acm_certificates_expiration_check:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
@@ -173,6 +179,7 @@ class Test_acm_certificates_expiration_check:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
expiration_days = 365
in_use = False
@@ -183,6 +190,7 @@ class Test_acm_certificates_expiration_check:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
@@ -212,6 +220,7 @@ class Test_acm_certificates_expiration_check:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
expiration_days = -400
in_use = False
@@ -222,6 +231,7 @@ class Test_acm_certificates_expiration_check:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
@@ -31,6 +31,7 @@ class Test_acm_certificates_transparency_logs_enabled:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
acm_client = mock.MagicMock
acm_client.certificates = [
@@ -39,6 +40,7 @@ class Test_acm_certificates_transparency_logs_enabled:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=365,
transparency_logging=True,
in_use=True,
@@ -74,6 +76,7 @@ class Test_acm_certificates_transparency_logs_enabled:
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA_2048"
acm_client = mock.MagicMock
acm_client.certificates = [
@@ -82,6 +85,7 @@ class Test_acm_certificates_transparency_logs_enabled:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=365,
transparency_logging=False,
in_use=True,
@@ -116,6 +120,7 @@ class Test_acm_certificates_transparency_logs_enabled:
certificate_id = str(uuid.uuid4())
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_key_algorithm = "RSA_2048"
certificate_type = "IMPORTED"
acm_client = mock.MagicMock
@@ -125,6 +130,7 @@ class Test_acm_certificates_transparency_logs_enabled:
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=365,
transparency_logging=True,
in_use=True,
@@ -0,0 +1,121 @@
import uuid
from unittest import mock
from prowler.providers.aws.services.acm.acm_service import Certificate
AWS_REGION = "us-east-1"
AWS_ACCOUNT_NUMBER = "123456789012"
class Test_acm_certificates_with_secure_key_algorithms:
def test_no_acm_certificates(self):
acm_client = mock.MagicMock
acm_client.certificates = []
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
# Test Check
from prowler.providers.aws.services.acm.acm_certificates_with_secure_key_algorithms.acm_certificates_with_secure_key_algorithms import (
acm_certificates_with_secure_key_algorithms,
)
check = acm_certificates_with_secure_key_algorithms()
result = check.execute()
assert len(result) == 0
def test_acm_certificate_secure_algorithm(self):
certificate_id = str(uuid.uuid4())
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA-2048"
acm_client = mock.MagicMock
acm_client.certificates = [
Certificate(
arn=certificate_arn,
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=365,
transparency_logging=True,
in_use=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"insecure_algorithm": ["RSA-1024"]}
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
# Test Check
from prowler.providers.aws.services.acm.acm_certificates_with_secure_key_algorithms.acm_certificates_with_secure_key_algorithms import (
acm_certificates_with_secure_key_algorithms,
)
check = acm_certificates_with_secure_key_algorithms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"ACM Certificate {certificate_id} for {certificate_name} uses a secure key algorithm ({certificate_key_algorithm})."
)
assert result[0].resource_id == certificate_id
assert result[0].resource_arn == certificate_arn
assert result[0].region == AWS_REGION
assert result[0].resource_tags == []
def test_acm_certificate_insecure_algorithm(self):
certificate_id = str(uuid.uuid4())
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA-1024"
acm_client = mock.MagicMock
acm_client.certificates = [
Certificate(
arn=certificate_arn,
id=certificate_id,
name=certificate_name,
type=certificate_type,
key_algorithm=certificate_key_algorithm,
expiration_days=365,
transparency_logging=False,
in_use=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"insecure_algorithm": ["RSA-1024"]}
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
# Test Check
from prowler.providers.aws.services.acm.acm_certificates_with_secure_key_algorithms.acm_certificates_with_secure_key_algorithms import (
acm_certificates_with_secure_key_algorithms,
)
check = acm_certificates_with_secure_key_algorithms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"ACM Certificate {certificate_id} for {certificate_name} does not use a secure key algorithm ({certificate_key_algorithm})."
)
assert result[0].resource_id == certificate_id
assert result[0].resource_arn == certificate_arn
assert result[0].region == AWS_REGION
assert result[0].resource_tags == []
@@ -18,6 +18,7 @@ make_api_call = botocore.client.BaseClient._make_api_call
certificate_arn = f"arn:aws:acm:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:certificate/{str(uuid.uuid4())}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
certificate_key_algorithm = "RSA-4096"
def mock_make_api_call(self, operation_name, kwargs):
@@ -40,7 +41,7 @@ def mock_make_api_call(self, operation_name, kwargs):
"HasAdditionalSubjectAlternativeNames": False,
"Status": "ISSUED",
"Type": certificate_type,
"KeyAlgorithm": "RSA_4096",
"KeyAlgorithm": "RSA-4096",
"KeyUsages": ["DIGITAL_SIGNATURE"],
"ExtendedKeyUsages": ["TLS_WEB_SERVER_AUTHENTICATION"],
"InUse": True,
@@ -127,7 +128,7 @@ class Test_ACM_Service:
# Test ACM List Certificates
# @mock_acm
def test__list_and_describe_certificates__(self):
def test_list_and_describe_certificates(self):
# Generate ACM Client
# acm_client = client("acm", region_name=AWS_REGION)
# Request ACM certificate
@@ -142,13 +143,14 @@ class Test_ACM_Service:
assert acm.certificates[0].arn == certificate_arn
assert acm.certificates[0].name == certificate_name
assert acm.certificates[0].type == certificate_type
assert acm.certificates[0].key_algorithm == certificate_key_algorithm
assert acm.certificates[0].expiration_days == 365
assert acm.certificates[0].transparency_logging is False
assert acm.certificates[0].region == AWS_REGION_US_EAST_1
# Test ACM List Tags
# @mock_acm
def test__list_tags_for_certificate__(self):
def test_list_tags_for_certificate(self):
# Generate ACM Client
# acm_client = client("acm", region_name=AWS_REGION)
# Request ACM certificate