-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(aws): Add new KMS check to prevent unintentional key deletion (#…
…4595) Co-authored-by: Sergio <[email protected]>
- Loading branch information
1 parent
96f893c
commit 6c029a9
Showing
4 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
34 changes: 34 additions & 0 deletions
34
...kms/kms_cmk_not_deleted_unintentionally/kms_cmk_not_deleted_unintentionally.metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"Provider": "aws", | ||
"CheckID": "kms_cmk_not_deleted_unintentionally", | ||
"CheckTitle": "AWS KMS keys should not be deleted unintentionally", | ||
"CheckType": [ | ||
"Data Deletion Protection" | ||
], | ||
"ServiceName": "kms", | ||
"SubServiceName": "", | ||
"ResourceIdTemplate": "arn:partition:kms:region:account-id:certificate/resource-id", | ||
"Severity": "critical", | ||
"ResourceType": "AwsKmsKey", | ||
"Description": "Ensure there is no customer keys scheduled for deletion.", | ||
"Risk": "KMS keys cannot be recovered once deleted, also, all the data under a KMS key is also permanently unrecoverable if the KMS key is deleted.", | ||
"RelatedUrl": "https://docs.aws.amazon.com/kms/latest/developerguide/deleting-keys-scheduling-key-deletion.html", | ||
"Remediation": { | ||
"Code": { | ||
"CLI": "aws kms cancel-key-deletion --key-id <key-id>", | ||
"NativeIaC": "", | ||
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/kms-controls.html#kms-3", | ||
"Terraform": "" | ||
}, | ||
"Recommendation": { | ||
"Text": "Cancel the deletion before the end of the period unless you really want to delete that CMK, as it will no longer be usable.", | ||
"Url": "https://docs.aws.amazon.com/kms/latest/developerguide/deleting-keys-scheduling-key-deletion.html#deleting-keys-scheduling-key-deletion-console" | ||
} | ||
}, | ||
"Categories": [ | ||
"encryption" | ||
], | ||
"DependsOn": [], | ||
"RelatedTo": [], | ||
"Notes": "" | ||
} |
24 changes: 24 additions & 0 deletions
24
...s/services/kms/kms_cmk_not_deleted_unintentionally/kms_cmk_not_deleted_unintentionally.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from prowler.lib.check.models import Check, Check_Report_AWS | ||
from prowler.providers.aws.services.kms.kms_client import kms_client | ||
|
||
|
||
class kms_cmk_not_deleted_unintentionally(Check): | ||
def execute(self): | ||
findings = [] | ||
for key in kms_client.keys: | ||
if key.manager == "CUSTOMER": | ||
if key.state != "Disabled" or kms_client.provider.scan_unused_services: | ||
report = Check_Report_AWS(self.metadata()) | ||
report.region = key.region | ||
report.resource_tags = key.tags | ||
report.status = "PASS" | ||
report.status_extended = ( | ||
f"KMS CMK {key.id} is not scheduled for deletion." | ||
) | ||
report.resource_id = key.id | ||
report.resource_arn = key.arn | ||
if key.state == "PendingDeletion": | ||
report.status = "FAIL" | ||
report.status_extended = f"KMS CMK {key.id} is scheduled for deletion, revert it if it was unintentionally." | ||
findings.append(report) | ||
return findings |
159 changes: 159 additions & 0 deletions
159
...vices/kms/kms_cmk_not_deleted_unintentionally/kms_cmk_not_deleted_unintentionally_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
from unittest import mock | ||
|
||
from boto3 import client | ||
from moto import mock_aws | ||
|
||
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider | ||
|
||
|
||
class Test_kms_cmk_not_deleted_unintentionally: | ||
@mock_aws | ||
def test_kms_no_keys(self): | ||
from prowler.providers.aws.services.kms.kms_service import KMS | ||
|
||
aws_provider = set_mocked_aws_provider([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.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally.kms_client", | ||
new=KMS(aws_provider), | ||
): | ||
from prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally import ( | ||
kms_cmk_not_deleted_unintentionally, | ||
) | ||
|
||
check = kms_cmk_not_deleted_unintentionally() | ||
result = check.execute() | ||
|
||
assert len(result) == 0 | ||
|
||
@mock_aws | ||
def test_kms_cmk_disabled_key(self): | ||
from prowler.providers.aws.services.kms.kms_service import KMS | ||
|
||
kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) | ||
key = kms_client.create_key()["KeyMetadata"] | ||
kms_client.disable_key(KeyId=key["KeyId"]) | ||
|
||
aws_provider = set_mocked_aws_provider( | ||
[AWS_REGION_US_EAST_1], scan_unused_services=False | ||
) | ||
|
||
with mock.patch( | ||
"prowler.providers.common.provider.Provider.get_global_provider", | ||
return_value=aws_provider, | ||
), mock.patch( | ||
"prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally.kms_client", | ||
new=KMS(aws_provider), | ||
): | ||
from prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally import ( | ||
kms_cmk_not_deleted_unintentionally, | ||
) | ||
|
||
check = kms_cmk_not_deleted_unintentionally() | ||
result = check.execute() | ||
|
||
assert len(result) == 0 | ||
|
||
@mock_aws | ||
def test_kms_cmk_deleted_unintentionally(self): | ||
from prowler.providers.aws.services.kms.kms_service import KMS | ||
|
||
kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) | ||
key = kms_client.create_key()["KeyMetadata"] | ||
kms_client.schedule_key_deletion(KeyId=key["KeyId"]) | ||
|
||
aws_provider = set_mocked_aws_provider([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.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally.kms_client", | ||
new=KMS(aws_provider), | ||
): | ||
from prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally import ( | ||
kms_cmk_not_deleted_unintentionally, | ||
) | ||
|
||
check = kms_cmk_not_deleted_unintentionally() | ||
result = check.execute() | ||
|
||
assert len(result) == 1 | ||
assert result[0].status == "FAIL" | ||
assert ( | ||
result[0].status_extended | ||
== f"KMS CMK {key['KeyId']} is scheduled for deletion, revert it if it was unintentionally." | ||
) | ||
assert result[0].resource_id == key["KeyId"] | ||
assert result[0].resource_arn == key["Arn"] | ||
|
||
@mock_aws | ||
def test_kms_cmk_enabled(self): | ||
from prowler.providers.aws.services.kms.kms_service import KMS | ||
|
||
kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) | ||
key = kms_client.create_key()["KeyMetadata"] | ||
kms_client.enable_key(KeyId=key["KeyId"]) | ||
|
||
aws_provider = set_mocked_aws_provider([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.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally.kms_client", | ||
new=KMS(aws_provider), | ||
): | ||
from prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally import ( | ||
kms_cmk_not_deleted_unintentionally, | ||
) | ||
|
||
check = kms_cmk_not_deleted_unintentionally() | ||
result = check.execute() | ||
|
||
assert len(result) == 1 | ||
assert result[0].status == "PASS" | ||
assert ( | ||
result[0].status_extended | ||
== f"KMS CMK {key['KeyId']} is not scheduled for deletion." | ||
) | ||
assert result[0].resource_id == key["KeyId"] | ||
assert result[0].resource_arn == key["Arn"] | ||
|
||
@mock_aws | ||
def test_kms_cmk_disabled_with_flag_unused(self): | ||
from prowler.providers.aws.services.kms.kms_service import KMS | ||
|
||
kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) | ||
key = kms_client.create_key()["KeyMetadata"] | ||
kms_client.disable_key(KeyId=key["KeyId"]) | ||
|
||
aws_provider = set_mocked_aws_provider( | ||
[AWS_REGION_US_EAST_1], scan_unused_services=True | ||
) | ||
|
||
with mock.patch( | ||
"prowler.providers.common.provider.Provider.get_global_provider", | ||
return_value=aws_provider, | ||
), mock.patch( | ||
"prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally.kms_client", | ||
new=KMS(aws_provider), | ||
): | ||
from prowler.providers.aws.services.kms.kms_cmk_not_deleted_unintentionally.kms_cmk_not_deleted_unintentionally import ( | ||
kms_cmk_not_deleted_unintentionally, | ||
) | ||
|
||
check = kms_cmk_not_deleted_unintentionally() | ||
result = check.execute() | ||
|
||
assert len(result) == 1 | ||
assert result[0].status == "PASS" | ||
assert ( | ||
result[0].status_extended | ||
== f"KMS CMK {key['KeyId']} is not scheduled for deletion." | ||
) | ||
assert result[0].resource_id == key["KeyId"] | ||
assert result[0].resource_arn == key["Arn"] |