diff --git a/localstack-core/localstack/services/kms/models.py b/localstack-core/localstack/services/kms/models.py index e39f435f77660..98e04c02161e0 100644 --- a/localstack-core/localstack/services/kms/models.py +++ b/localstack-core/localstack/services/kms/models.py @@ -247,6 +247,8 @@ class KmsKey: tags: Dict[str, str] policy: str is_key_rotation_enabled: bool + rotation_period_in_days: int + next_rotation_date: datetime.datetime def __init__( self, @@ -280,6 +282,8 @@ def __init__( # remove the _custom_key_material_ tag from the tags to not readily expose the custom key material del self.tags[TAG_KEY_CUSTOM_KEY_MATERIAL] self.crypto_key = KmsCryptoKey(self.metadata.get("KeySpec"), custom_key_material) + self.rotation_period_in_days = 365 + self.next_rotation_date = None def calculate_and_set_arn(self, account_id, region): self.metadata["Arn"] = kms_key_arn(self.metadata.get("KeyId"), account_id, region) @@ -587,6 +591,12 @@ def schedule_key_deletion(self, pending_window_in_days: int) -> None: days=pending_window_in_days ) + def _update_key_rotation_date(self) -> None: + if not self.next_rotation_date or self.next_rotation_date < datetime.datetime.now(): + self.next_rotation_date = datetime.datetime.now() + datetime.timedelta( + days=self.rotation_period_in_days + ) + # An example of how the whole policy should look like: # https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-overview.html # The default statement is here: diff --git a/localstack-core/localstack/services/kms/provider.py b/localstack-core/localstack/services/kms/provider.py index 3ccd54c359c30..2d5dad7146f1c 100644 --- a/localstack-core/localstack/services/kms/provider.py +++ b/localstack-core/localstack/services/kms/provider.py @@ -32,6 +32,7 @@ DisableKeyRequest, DisableKeyRotationRequest, EnableKeyRequest, + EnableKeyRotationRequest, EncryptionAlgorithmSpec, EncryptionContextType, EncryptResponse, @@ -1253,7 +1254,12 @@ def get_key_rotation_status( # We do not model that here, though. account_id, region_name, key_id = self._parse_key_id(request["KeyId"], context) key = self._get_kms_key(account_id, region_name, key_id, any_key_state_allowed=True) - return GetKeyRotationStatusResponse(KeyRotationEnabled=key.is_key_rotation_enabled) + return GetKeyRotationStatusResponse( + KeyId=key_id, + KeyRotationEnabled=key.is_key_rotation_enabled, + NextRotationDate=key.next_rotation_date, + RotationPeriodInDays=key.rotation_period_in_days, + ) @handler("DisableKeyRotation", expand=False) def disable_key_rotation( @@ -1267,13 +1273,16 @@ def disable_key_rotation( @handler("EnableKeyRotation", expand=False) def enable_key_rotation( - self, context: RequestContext, request: DisableKeyRotationRequest + self, context: RequestContext, request: EnableKeyRotationRequest ) -> None: # https://docs.aws.amazon.com/kms/latest/developerguide/key-state.html # "If the KMS key has imported key material or is in a custom key store: UnsupportedOperationException." # We do not model that here, though. key = self._get_kms_key(context.account_id, context.region, request.get("KeyId")) key.is_key_rotation_enabled = True + if request.get("RotationPeriodInDays"): + key.rotation_period_in_days = request.get("RotationPeriodInDays") + key._update_key_rotation_date() @handler("ListKeyPolicies", expand=False) def list_key_policies( diff --git a/tests/aws/services/kms/test_kms.py b/tests/aws/services/kms/test_kms.py index 9960583a45fe6..15222fab13207 100644 --- a/tests/aws/services/kms/test_kms.py +++ b/tests/aws/services/kms/test_kms.py @@ -56,6 +56,7 @@ def _get_all_key_ids(kms_client): def _get_alias(kms_client, alias_name, key_id=None): next_token = None + # TODO potential bug on pagination on "nextToken" attribute key while True: kwargs = {"nextToken": next_token} if next_token else {} if key_id: @@ -1097,6 +1098,22 @@ def test_key_rotation_status(self, kms_key, aws_client): aws_client.kms.disable_key_rotation(KeyId=key_id) assert aws_client.kms.get_key_rotation_status(KeyId=key_id)["KeyRotationEnabled"] is False + @markers.aws.validated + @pytest.mark.parametrize("rotation_period_in_days", [90, 180]) + def test_key_enable_rotation_status( + self, + kms_key, + aws_client, + rotation_period_in_days, + snapshot, + ): + key_id = kms_key["KeyId"] + aws_client.kms.enable_key_rotation( + KeyId=key_id, RotationPeriodInDays=rotation_period_in_days + ) + result = aws_client.kms.get_key_rotation_status(KeyId=key_id) + snapshot.match("match_response", result) + @markers.aws.validated def test_create_list_delete_alias(self, kms_create_alias, aws_client): alias_name = f"alias/{short_uid()}" diff --git a/tests/aws/services/kms/test_kms.snapshot.json b/tests/aws/services/kms/test_kms.snapshot.json index 628117a5150b7..92980b33036aa 100644 --- a/tests/aws/services/kms/test_kms.snapshot.json +++ b/tests/aws/services/kms/test_kms.snapshot.json @@ -2005,5 +2005,35 @@ } } } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[90]": { + "recorded-date": "02-03-2025, 13:34:02", + "recorded-content": { + "match_response": { + "KeyId": "", + "KeyRotationEnabled": true, + "NextRotationDate": "datetime", + "RotationPeriodInDays": 90, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[180]": { + "recorded-date": "02-03-2025, 13:34:03", + "recorded-content": { + "match_response": { + "KeyId": "", + "KeyRotationEnabled": true, + "NextRotationDate": "datetime", + "RotationPeriodInDays": 180, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/kms/test_kms.validation.json b/tests/aws/services/kms/test_kms.validation.json index 136682e84716b..dcad6b4554830 100644 --- a/tests/aws/services/kms/test_kms.validation.json +++ b/tests/aws/services/kms/test_kms.validation.json @@ -167,6 +167,12 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-INVALID-some important message]": { "last_validated_date": "2024-04-11T15:54:16+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[180]": { + "last_validated_date": "2025-03-02T13:34:03+00:00" + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[90]": { + "last_validated_date": "2025-03-02T13:34:02+00:00" + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_status": { "last_validated_date": "2024-04-11T15:53:48+00:00" },