diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f447b016ac..27c8073368 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -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) --- diff --git a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json index 4d801f8da9..157e93faca 100644 --- a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json +++ b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json @@ -24,9 +24,9 @@ ], "Remediation": { "Code": { - "CLI": "aws kms put-key-policy --key-id --policy-name default --policy '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam:::root\"},\"Action\":\"kms:*\",\"Resource\":\"*\"}]}'", + "CLI": "aws kms put-key-policy --key-id --policy-name default --policy '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam:::root\"},\"Action\":\"kms:\\*\",\"Resource\":\"\\*\"}]}'", "NativeIaC": "```yaml\n# CloudFormation: restrict KMS key policy to account root (removes any public access)\nResources:\n :\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:::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:::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:::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\" \"\" {\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": { diff --git a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py index 408034647a..011760ac03 100644 --- a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py +++ b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py @@ -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 = ( diff --git a/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py b/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py index c8abdaaf5d..1637d1344e 100644 --- a/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py +++ b/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py @@ -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