fix(kms): detect public access for any KMS action, not just kms:* (#10071)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jfagoagas <16007882+jfagoagas@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
Copilot
2026-02-16 10:12:29 +01:00
committed by GitHub
parent 21bdbacdfb
commit 90e317d39f
4 changed files with 114 additions and 3 deletions
+1
View File
@@ -48,6 +48,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
- `pip install prowler` failing on systems without C compiler due to `netifaces` transitive dependency from `openstacksdk` [(#10055)](https://github.com/prowler-cloud/prowler/pull/10055)
- `kms_key_not_publicly_accessible` false negative for specific KMS actions (e.g., `kms:DescribeKey`, `kms:Decrypt`) with unrestricted principals [(#10071)](https://github.com/prowler-cloud/prowler/pull/10071)
---
@@ -24,9 +24,9 @@
],
"Remediation": {
"Code": {
"CLI": "aws kms put-key-policy --key-id <example_resource_id> --policy-name default --policy '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::<account_id>:root\"},\"Action\":\"kms:*\",\"Resource\":\"*\"}]}'",
"CLI": "aws kms put-key-policy --key-id <example_resource_id> --policy-name default --policy '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::<account_id>:root\"},\"Action\":\"kms:\\*\",\"Resource\":\"\\*\"}]}'",
"NativeIaC": "```yaml\n# CloudFormation: restrict KMS key policy to account root (removes any public access)\nResources:\n <example_resource_name>:\n Type: AWS::KMS::Key\n Properties:\n KeyPolicy:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Principal:\n AWS: arn:aws:iam::<account_id>:root # Critical: only account root can access; prevents public \"*\" principals\n Action: kms:*\n Resource: '*'\n```",
"Other": "1. Open AWS Console > Key Management Service (KMS)\n2. Select the affected key and go to the Key policy tab\n3. Click Edit and remove any statement with Principal set to \"*\" (or AWS: \"*\")\n4. Ensure a statement exists that allows only arn:aws:iam::<account_id>:root\n5. Save changes",
"Other": "1. Open AWS Console > Key Management Service (KMS)\n2. Select the affected key and go to the Key policy tab\n3. Click Edit and remove any statement with Principal set to \"\\*\" (or AWS: \"\\*\")\n4. Ensure a statement exists that allows only arn:aws:iam::<account_id>:root\n5. Save changes",
"Terraform": "```hcl\n# Restrict KMS key policy to the account root to avoid any public (\"*\") principals\ndata \"aws_caller_identity\" \"current\" {}\n\nresource \"aws_kms_key\" \"<example_resource_name>\" {\n policy = jsonencode({\n Version = \"2012-10-17\"\n Statement = [\n {\n Effect = \"Allow\"\n Principal = { AWS = \"arn:aws:iam::${data.aws_caller_identity.current.account_id}:root\" } # Critical: limit to account root to remove public access\n Action = \"kms:*\"\n Resource = \"*\"\n }\n ]\n })\n}\n```"
},
"Recommendation": {
@@ -19,7 +19,7 @@ class kms_key_not_publicly_accessible(Check):
if is_policy_public(
key.policy,
kms_client.audited_account,
not_allowed_actions=["kms:*"],
not_allowed_actions=[],
):
report.status = "FAIL"
report.status_extended = (
@@ -129,6 +129,116 @@ class Test_kms_key_not_publicly_accessible:
assert result[0].resource_id == key["KeyId"]
assert result[0].resource_arn == key["Arn"]
@mock_aws
def test_kms_key_public_accessible_with_describe_key(self):
# Generate KMS Client
kms_client = client("kms", region_name=AWS_REGION_US_EAST_1)
# Create KMS key with public policy allowing kms:DescribeKey
key = kms_client.create_key(
MultiRegion=False,
Policy=json.dumps(
{
"Version": "2012-10-17",
"Id": "key-default-1",
"Statement": [
{
"Sid": "AllowDescribeKeyPermissionForClusterOperator",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "kms:DescribeKey",
"Resource": "*",
}
],
}
),
)["KeyMetadata"]
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_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client",
new=KMS(aws_provider),
),
):
# Test Check
from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import (
kms_key_not_publicly_accessible,
)
check = kms_key_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"KMS key {key['KeyId']} may be publicly accessible."
)
assert result[0].resource_id == key["KeyId"]
assert result[0].resource_arn == key["Arn"]
@mock_aws
def test_kms_key_public_accessible_with_decrypt(self):
# Generate KMS Client
kms_client = client("kms", region_name=AWS_REGION_US_EAST_1)
# Create KMS key with public policy allowing kms:Decrypt
key = kms_client.create_key(
MultiRegion=False,
Policy=json.dumps(
{
"Version": "2012-10-17",
"Id": "key-default-1",
"Statement": [
{
"Sid": "AllowDecryptPermissionPublicly",
"Effect": "Allow",
"Principal": "*",
"Action": "kms:Decrypt",
"Resource": "*",
}
],
}
),
)["KeyMetadata"]
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_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client",
new=KMS(aws_provider),
),
):
# Test Check
from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import (
kms_key_not_publicly_accessible,
)
check = kms_key_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"KMS key {key['KeyId']} may be publicly accessible."
)
assert result[0].resource_id == key["KeyId"]
assert result[0].resource_arn == key["Arn"]
@mock_aws
def test_kms_key_empty_principal(self):
# Generate KMS Client