Back to Checks
aws

KMS enclave key requires kms:RecipientAttestation conditions on sensitive actions

kms_key_enclave_attestation_not_enforced
Service: kmsSeverity: high
OfficialBuilt-inVerified
Categories:encryptiontrust-boundaries
Resource Type:AwsKmsKey

Customer-managed KMS keys used with Nitro Enclaves (identified by prowler:enclave-key=true tag, enclave in description/tags/aliases, or a policy referencing kms:RecipientAttestation:*). Every Allow on sensitive actions (kms:Decrypt, DeriveSharedSecret, GenerateDataKey*, GenerateRandom, kms:*, *) must require a kms:RecipientAttestation:* condition.

Risk

Without an attestation condition, any principal with decrypt permission (including a compromised parent instance) can use the key. Attestation is the cryptographic mechanism that binds usage to the measured enclave image; its absence removes the trust boundary the enclave was designed to enforce.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks kms_key_enclave_attestation_not_enforced

Recommendation

Bind every sensitive grant on enclave-scoped KMS keys to the enclave's measured identity via kms:RecipientAttestation:* conditions. This preserves the trust boundary the enclave exists to enforce and prevents any non-enclave principal from using the key.

Remediation

CLI

aws kms put-key-policy --key-id <key-id> --policy-name default --policy file://enclave-policy.json

Terraform

data "aws_iam_policy_document" "example_resource" {
  statement {
    actions = ["kms:Decrypt"]
    resources = ["*"]
    condition {
      test = "StringEqualsIgnoreCase"
      variable = "kms:RecipientAttestation:PCR0"  # critical: bind to enclave measurement
      values = [var.expected_pcr0]
    }
  }
}

NativeIaC

Resources:
  example_resource:
    Type: AWS::KMS::Key
    Properties:
      KeyPolicy:
        Statement:
          - Effect: Allow
            Principal:
              AWS: arn:aws:iam::123456789012:role/enclave-parent
            Action: kms:Decrypt
            Resource: "*"
            Condition:
              StringEqualsIgnoreCase:
                kms:RecipientAttestation:PCR0: <expected-pcr0>  # critical: bind to enclave measurement

Other

1Enumerate the customer-managed keys used by enclave workloads.
2For every Allow statement covering a sensitive KMS action, add a Condition block that references one or more `kms:RecipientAttestation:*` condition keys tied to the enclave measurement.
3Reject or scope any statement that cannot be conditioned on attestation.

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.kms.kms_client import kms_client
from prowler.providers.aws.services.kms.lib.enclave import (
    attestation_condition_keys,
    is_enclave_key,
    statement_targets_sensitive_actions,
)


class kms_key_enclave_attestation_not_enforced(Check):
    """Ensure enclave-scoped KMS keys require attestation on sensitive actions.

    KMS keys that back Nitro Enclave workloads are identified by the explicit
    ``prowler:enclave-key=true`` tag, or by the substring ``enclave`` in the
    key description or any tag key/value. Every ``Allow`` statement in the
    key policy that grants a sensitive action (``kms:Decrypt``,
    ``kms:DeriveSharedSecret``, ``kms:GenerateDataKey``,
    ``kms:GenerateDataKeyPair``, ``kms:GenerateRandom``, ``kms:*``, ``*``)
    must carry at least one ``kms:RecipientAttestation:*`` condition.

    - PASS: Every sensitive Allow statement carries an attestation condition.
    - FAIL: At least one sensitive Allow statement is unconditioned.
    """

    def execute(self) -> list[Check_Report_AWS]:
        """Execute the KMS enclave-attestation-condition check.

        Iterates over enabled customer-managed KMS keys tagged/described as
        enclave keys and walks their policy statements looking for sensitive
        ``Allow`` statements that lack a ``kms:RecipientAttestation:*``
        condition key.

        Returns:
            list[Check_Report_AWS]: One report per selected enclave KMS key.
        """
        findings = []
        for key in kms_client.keys:
            if (
                key.manager != "CUSTOMER"
                or key.state != "Enabled"
                or not is_enclave_key(key)
            ):
                continue
            if key.policy is None:
                if getattr(key, "policy_fetch_error", None):
                    report = Check_Report_AWS(metadata=self.metadata(), resource=key)
                    report.status = "MANUAL"
                    report.status_extended = (
                        f"KMS enclave key {key.id} policy could not be "
                        f"fetched ({key.policy_fetch_error}); attestation "
                        f"enforcement cannot be verified."
                    )
                    findings.append(report)
                continue

            report = Check_Report_AWS(metadata=self.metadata(), resource=key)
            report.status = "PASS"
            report.status_extended = (
                f"KMS enclave key {key.id} enforces attestation on every sensitive "
                f"Allow statement."
            )

            statements = key.policy.get("Statement") or []
            if isinstance(statements, dict):
                statements = [statements]

            for statement in statements:
                if not isinstance(statement, dict):
                    continue
                if statement.get("Effect") != "Allow":
                    continue
                if not statement_targets_sensitive_actions(statement):
                    continue
                if not attestation_condition_keys(statement):
                    actions = statement.get("Action", [])
                    if isinstance(actions, str):
                        actions = [actions]
                    action_names = [a for a in actions if isinstance(a, str)]
                    actions_str = (
                        ", ".join(sorted(action_names)) if action_names else "<none>"
                    )
                    report.status = "FAIL"
                    report.status_extended = (
                        f"KMS enclave key {key.id} allows sensitive action(s) "
                        f"{actions_str} without any kms:RecipientAttestation:* "
                        f"condition."
                    )
                    break
            findings.append(report)
        return findings