From 60ec840eef44c395e83342469ab81c0ca5a46daf Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 15:55:47 -0500 Subject: [PATCH 01/16] add disable_key, enable_key, cancel_key_deletion, and schedule_key_deletion actions to KMS endpoint --- moto/kms/models.py | 30 +++++++++++++- moto/kms/responses.py | 46 ++++++++++++++++++++++ tests/test_kms/test_kms.py | 80 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 89ebf0082..30a01d366 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -12,11 +12,13 @@ class Key(BaseModel): self.id = generate_key_id() self.policy = policy self.key_usage = key_usage + self.key_state = "Enabled" self.description = description self.enabled = True self.region = region self.account_id = "0123456789012" self.key_rotation_status = False + self.deletion_date = None @property def physical_resource_id(self): @@ -27,7 +29,7 @@ class Key(BaseModel): return "arn:aws:kms:{0}:{1}:key/{2}".format(self.region, self.account_id, self.id) def to_dict(self): - return { + key_dict = { "KeyMetadata": { "AWSAccountId": self.account_id, "Arn": self.arn, @@ -36,8 +38,11 @@ class Key(BaseModel): "Enabled": self.enabled, "KeyId": self.id, "KeyUsage": self.key_usage, + "KeyState": self.key_state, } } + key_dict['KeyMetadata']['DeletionDate'] = self.deletion_date if self.key_state == 'PendingDeletion' + return key_dict def delete(self, region_name): kms_backends[region_name].delete_key(self.id) @@ -138,6 +143,29 @@ class KmsBackend(BaseBackend): def get_key_policy(self, key_id): return self.keys[self.get_key_id(key_id)].policy + def disable_key(self, key_id): + if key_id in self.keys: + self.keys[key_id].enabled = False + self.keys[key_id].key_state = 'Disabled' + + def enable_key(self, key_id): + if key_id in self.keys: + self.keys[key_id].enabled = True + self.keys[key_id].key_state = 'Enabled' + + def cancel_key_deletion(self, key_id): + if key_id in self.keys: + self.keys[key_id].key_state = 'Disabled' + self.keys[key_id].deletion_date = None + + def schedule_key_deletion(self, key_id, pending_window_in_days=30): + if key_id in self.keys: + if 7 <= pending_window_in_days <= 30: + self.keys[key_id].enabled = False + self.keys[key_id].key_state = 'PendingDeletion' + self.keys[key_id].deletion_date = datetime.now() + timedelta(days=pending_window_in_days) + return self.keys[key_id].deletion_date + kms_backends = {} for region in boto.kms.regions(): diff --git a/moto/kms/responses.py b/moto/kms/responses.py index 0f544e954..e782d862e 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -233,6 +233,52 @@ class KmsResponse(BaseResponse): value = self.parameters.get("CiphertextBlob") return json.dumps({"Plaintext": base64.b64decode(value).decode("utf-8")}) + def disable_key(self): + key_id = self.parameters.get('KeyId') + _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) + try: + self.kms_backend.disable_key(key_id) + except KeyError: + raise JSONResponseError(404, 'Not Found', body={ + 'message': "Key 'arn:aws:kms:{region}:012345678912:key/{key_id}' does not exist".format(region=self.region, key_id=key_id), + '__type': 'NotFoundException'}) + return json.dumps(None) + + def enable_key(self): + key_id = self.parameters.get('KeyId') + _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) + try: + self.kms_backend.enable_key(key_id) + except KeyError: + raise JSONResponseError(404, 'Not Found', body={ + 'message': "Key 'arn:aws:kms:{region}:012345678912:key/{key_id}' does not exist".format(region=self.region, key_id=key_id), + '__type': 'NotFoundException'}) + return json.dumps(None) + + def cancel_key_deletion(self): + key_id = self.parameters.get('KeyId') + _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) + try: + self.kms_backend.cancel_key_deletion(key_id) + except KeyError: + raise JSONResponseError(404, 'Not Found', body={ + 'message': "Key 'arn:aws:kms:{region}:012345678912:key/{key_id}' does not exist".format(region=self.region, key_id=key_id), + '__type': 'NotFoundException'}) + return json.dumps({'KeyId': key_id}) + + def schedule_key_deletion(self): + key_id = self.parameters.get('KeyId') + _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) + try: + return json.dumps({ + 'KeyId': key_id, + 'DeletionDate': self.kms_backend.schedule_key_deletion(key_id) + }) + except KeyError: + raise JSONResponseError(404, 'Not Found', body={ + 'message': "Key 'arn:aws:kms:{region}:012345678912:key/{key_id}' does not exist".format(region=self.region, key_id=key_id), + '__type': 'NotFoundException'}) + def _assert_valid_key_id(key_id): if not re.match(r'^[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}$', key_id, re.IGNORECASE): diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 96715de71..9779f02a6 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -617,3 +617,83 @@ def test_kms_encrypt_boto3(): response = client.decrypt(CiphertextBlob=response['CiphertextBlob']) response['Plaintext'].should.equal(b'bar') + + +@mock_kms +def test_disable_key(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(description='disable-key') + client.disable_key( + KeyId=key['KeyMetadata']['KeyId'] + ) + + result = client.describe_key(KeyId='disable-key') + assert result["KeyMetadata"]["Enabled"] == False + assert result["KeyMetadata"]["KeyState"] == 'Disabled' + + +@mock_kms +def test_enable_key(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(description='enable-key') + client.disable_key( + KeyId=key['KeyMetadata']['KeyId'] + ) + client.enable_key( + KeyId=key['KeyMetadata']['KeyId'] + ) + + result = client.describe_key(KeyId='enable-key') + assert result["KeyMetadata"]["Enabled"] == True + assert result["KeyMetadata"]["KeyState"] == 'Enabled' + + +@mock_kms +def test_schedule_key_deletion(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(description='schedule-key-deletion') + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + assert response['KeyId'] == 'schedule-key-deletion' + assert response['DeletionDate'] == datetime.now() + timedelta(days=30) + + result = client.describe_key(KeyId='schedule-key-deletion') + assert result["KeyMetadata"]["Enabled"] == False + assert result["KeyMetadata"]["KeyState"] == 'PendingDeletion' + assert 'DeletionDate' in result["KeyMetadata"] + + +@mock_kms +def test_schedule_key_deletion_custom(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(description='schedule-key-deletion') + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'], + PendingWindowInDays=7 + ) + assert response['KeyId'] == 'schedule-key-deletion' + assert response['DeletionDate'] == datetime.now() + timedelta(days=7) + + result = client.describe_key(KeyId='schedule-key-deletion') + assert result["KeyMetadata"]["Enabled"] == False + assert result["KeyMetadata"]["KeyState"] == 'PendingDeletion' + assert 'DeletionDate' in result["KeyMetadata"] + + +@mock_kms +def test_cancel_key_deletion(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(description='cancel-key-deletion') + client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + response = client.cancel_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + assert response['KeyId'] == 'cancel-key-deletion' + + result = client.describe_key(KeyId='cancel-key-deletion') + assert result["KeyMetadata"]["Enabled"] == False + assert result["KeyMetadata"]["KeyState"] == 'Disabled' + assert 'DeletionDate' not in result["KeyMetadata"] From 15c24e49f0633b7cbe09378dfe22dc94e9190e1f Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 16:00:20 -0500 Subject: [PATCH 02/16] fix formatting for including DeletionDate in response --- moto/kms/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 30a01d366..4ac6ee845 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -41,7 +41,8 @@ class Key(BaseModel): "KeyState": self.key_state, } } - key_dict['KeyMetadata']['DeletionDate'] = self.deletion_date if self.key_state == 'PendingDeletion' + if self.key_state == 'PendingDeletion': + key_dict['KeyMetadata']['DeletionDate'] = self.deletion_date return key_dict def delete(self, region_name): From 7e96203020ea4cd873e0a585884b156896f49265 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 16:21:16 -0500 Subject: [PATCH 03/16] add freezegun and test DeletionDate for chedule_key_deletion --- moto/kms/models.py | 1 + tests/test_kms/test_kms.py | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 4ac6ee845..113bd1733 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -4,6 +4,7 @@ import boto.kms from moto.core import BaseBackend, BaseModel from .utils import generate_key_id from collections import defaultdict +from datetime import datetime, timedelta class Key(BaseModel): diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 9779f02a6..287ef9158 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -8,6 +8,8 @@ from boto.kms.exceptions import AlreadyExistsException, NotFoundException import sure # noqa from moto import mock_kms, mock_kms_deprecated from nose.tools import assert_raises +from freezegun import freeze_time +from datetime import datetime, timedelta @mock_kms_deprecated @@ -652,11 +654,12 @@ def test_enable_key(): def test_schedule_key_deletion(): client = boto3.client('kms', region_name='us-east-1') key = client.create_key(description='schedule-key-deletion') - response = client.schedule_key_deletion( - KeyId=key['KeyMetadata']['KeyId'] - ) - assert response['KeyId'] == 'schedule-key-deletion' - assert response['DeletionDate'] == datetime.now() + timedelta(days=30) + with freeze_time("2015-01-01 12:00:00"): + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + assert response['KeyId'] == 'schedule-key-deletion' + assert response['DeletionDate'] == datetime.now() + timedelta(days=30) result = client.describe_key(KeyId='schedule-key-deletion') assert result["KeyMetadata"]["Enabled"] == False @@ -668,12 +671,13 @@ def test_schedule_key_deletion(): def test_schedule_key_deletion_custom(): client = boto3.client('kms', region_name='us-east-1') key = client.create_key(description='schedule-key-deletion') - response = client.schedule_key_deletion( - KeyId=key['KeyMetadata']['KeyId'], - PendingWindowInDays=7 - ) - assert response['KeyId'] == 'schedule-key-deletion' - assert response['DeletionDate'] == datetime.now() + timedelta(days=7) + with freeze_time("2015-01-01 12:00:00"): + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'], + PendingWindowInDays=7 + ) + assert response['KeyId'] == 'schedule-key-deletion' + assert response['DeletionDate'] == datetime.now() + timedelta(days=7) result = client.describe_key(KeyId='schedule-key-deletion') assert result["KeyMetadata"]["Enabled"] == False From 695b4349ba62865cd3e9987af65c39a761f2c35b Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 21:43:12 -0500 Subject: [PATCH 04/16] indentation fix --- moto/kms/responses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/kms/responses.py b/moto/kms/responses.py index e782d862e..dc37c8b59 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -271,8 +271,8 @@ class KmsResponse(BaseResponse): _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) try: return json.dumps({ - 'KeyId': key_id, - 'DeletionDate': self.kms_backend.schedule_key_deletion(key_id) + 'KeyId': key_id, + 'DeletionDate': self.kms_backend.schedule_key_deletion(key_id) }) except KeyError: raise JSONResponseError(404, 'Not Found', body={ From a29daf411baf056f3fc924abae7e7bcbe224f5cc Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 21:56:32 -0500 Subject: [PATCH 05/16] fix invalid variables used in kms testing --- tests/test_kms/test_kms.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 287ef9158..ab7513b4d 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -623,8 +623,8 @@ def test_kms_encrypt_boto3(): @mock_kms def test_disable_key(): - client = boto3.client('kms', region_name='us-east-1') - key = client.create_key(description='disable-key') + client = boto3.client('kms') + key = client.create_key(Description='disable-key') client.disable_key( KeyId=key['KeyMetadata']['KeyId'] ) @@ -636,8 +636,8 @@ def test_disable_key(): @mock_kms def test_enable_key(): - client = boto3.client('kms', region_name='us-east-1') - key = client.create_key(description='enable-key') + client = boto3.client('kms') + key = client.create_key(Description='enable-key') client.disable_key( KeyId=key['KeyMetadata']['KeyId'] ) @@ -652,8 +652,8 @@ def test_enable_key(): @mock_kms def test_schedule_key_deletion(): - client = boto3.client('kms', region_name='us-east-1') - key = client.create_key(description='schedule-key-deletion') + client = boto3.client('kms') + key = client.create_key(Description='schedule-key-deletion') with freeze_time("2015-01-01 12:00:00"): response = client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'] @@ -669,8 +669,8 @@ def test_schedule_key_deletion(): @mock_kms def test_schedule_key_deletion_custom(): - client = boto3.client('kms', region_name='us-east-1') - key = client.create_key(description='schedule-key-deletion') + client = boto3.client('kms') + key = client.create_key(Description='schedule-key-deletion') with freeze_time("2015-01-01 12:00:00"): response = client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'], @@ -687,8 +687,8 @@ def test_schedule_key_deletion_custom(): @mock_kms def test_cancel_key_deletion(): - client = boto3.client('kms', region_name='us-east-1') - key = client.create_key(description='cancel-key-deletion') + client = boto3.client('kms') + key = client.create_key(Description='cancel-key-deletion') client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'] ) From 786b9ca519f830424bdddbcb69e0322f40c0d9b0 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 22:17:48 -0500 Subject: [PATCH 06/16] need region for kms client --- tests/test_kms/test_kms.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index ab7513b4d..0dc93b252 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -623,7 +623,7 @@ def test_kms_encrypt_boto3(): @mock_kms def test_disable_key(): - client = boto3.client('kms') + client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='disable-key') client.disable_key( KeyId=key['KeyMetadata']['KeyId'] @@ -636,7 +636,7 @@ def test_disable_key(): @mock_kms def test_enable_key(): - client = boto3.client('kms') + client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='enable-key') client.disable_key( KeyId=key['KeyMetadata']['KeyId'] @@ -652,7 +652,7 @@ def test_enable_key(): @mock_kms def test_schedule_key_deletion(): - client = boto3.client('kms') + client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='schedule-key-deletion') with freeze_time("2015-01-01 12:00:00"): response = client.schedule_key_deletion( @@ -669,7 +669,7 @@ def test_schedule_key_deletion(): @mock_kms def test_schedule_key_deletion_custom(): - client = boto3.client('kms') + client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='schedule-key-deletion') with freeze_time("2015-01-01 12:00:00"): response = client.schedule_key_deletion( @@ -687,7 +687,7 @@ def test_schedule_key_deletion_custom(): @mock_kms def test_cancel_key_deletion(): - client = boto3.client('kms') + client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='cancel-key-deletion') client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'] From 372f749831344f979d349238e15585f68dabc5ca Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 22:46:19 -0500 Subject: [PATCH 07/16] format DeletionDate properly for JSON serialization --- moto/kms/responses.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/moto/kms/responses.py b/moto/kms/responses.py index dc37c8b59..fe7d28523 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -9,6 +9,7 @@ from boto.exception import JSONResponseError from boto.kms.exceptions import AlreadyExistsException, NotFoundException from moto.core.responses import BaseResponse +from moto.core.utils import iso_8601_datetime_without_milliseconds from .models import kms_backends reserved_aliases = [ @@ -272,7 +273,7 @@ class KmsResponse(BaseResponse): try: return json.dumps({ 'KeyId': key_id, - 'DeletionDate': self.kms_backend.schedule_key_deletion(key_id) + 'DeletionDate': iso_8601_datetime_without_milliseconds(self.kms_backend.schedule_key_deletion(key_id)) }) except KeyError: raise JSONResponseError(404, 'Not Found', body={ From f596069dabd1852f67f3a8a63e0e01029089cb43 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 23:35:34 -0500 Subject: [PATCH 08/16] use initial KeyMetadata for identifying keys in KMS tests --- tests/test_kms/test_kms.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 0dc93b252..69cd508ba 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -658,10 +658,10 @@ def test_schedule_key_deletion(): response = client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'] ) - assert response['KeyId'] == 'schedule-key-deletion' + assert response['KeyId'] == key['KeyMetadata']['KeyId'] assert response['DeletionDate'] == datetime.now() + timedelta(days=30) - result = client.describe_key(KeyId='schedule-key-deletion') + result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False assert result["KeyMetadata"]["KeyState"] == 'PendingDeletion' assert 'DeletionDate' in result["KeyMetadata"] @@ -676,10 +676,10 @@ def test_schedule_key_deletion_custom(): KeyId=key['KeyMetadata']['KeyId'], PendingWindowInDays=7 ) - assert response['KeyId'] == 'schedule-key-deletion' + assert response['KeyId'] == key['KeyMetadata']['KeyId'] assert response['DeletionDate'] == datetime.now() + timedelta(days=7) - result = client.describe_key(KeyId='schedule-key-deletion') + result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False assert result["KeyMetadata"]["KeyState"] == 'PendingDeletion' assert 'DeletionDate' in result["KeyMetadata"] @@ -695,9 +695,9 @@ def test_cancel_key_deletion(): response = client.cancel_key_deletion( KeyId=key['KeyMetadata']['KeyId'] ) - assert response['KeyId'] == 'cancel-key-deletion' + assert response['KeyId'] == key['KeyMetadata']['KeyId'] - result = client.describe_key(KeyId='cancel-key-deletion') + result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False assert result["KeyMetadata"]["KeyState"] == 'Disabled' assert 'DeletionDate' not in result["KeyMetadata"] From 6277983e3f01daa19247e0f217941fd2d3ad4b1d Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 5 Oct 2018 23:48:19 -0500 Subject: [PATCH 09/16] missed some KeyMetadata and need to transform datetime for testing --- tests/test_kms/test_kms.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 69cd508ba..a06a07324 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -7,6 +7,7 @@ from boto.exception import JSONResponseError from boto.kms.exceptions import AlreadyExistsException, NotFoundException import sure # noqa from moto import mock_kms, mock_kms_deprecated +from moto.core.utils import iso_8601_datetime_without_milliseconds from nose.tools import assert_raises from freezegun import freeze_time from datetime import datetime, timedelta @@ -629,7 +630,7 @@ def test_disable_key(): KeyId=key['KeyMetadata']['KeyId'] ) - result = client.describe_key(KeyId='disable-key') + result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False assert result["KeyMetadata"]["KeyState"] == 'Disabled' @@ -645,7 +646,7 @@ def test_enable_key(): KeyId=key['KeyMetadata']['KeyId'] ) - result = client.describe_key(KeyId='enable-key') + result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == True assert result["KeyMetadata"]["KeyState"] == 'Enabled' @@ -659,7 +660,7 @@ def test_schedule_key_deletion(): KeyId=key['KeyMetadata']['KeyId'] ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == datetime.now() + timedelta(days=30) + assert response['DeletionDate'] == iso_8601_datetime_without_milliseconds(datetime.now() + timedelta(days=30)) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False @@ -677,7 +678,7 @@ def test_schedule_key_deletion_custom(): PendingWindowInDays=7 ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == datetime.now() + timedelta(days=7) + assert response['DeletionDate'] == iso_8601_datetime_without_milliseconds(datetime.now() + timedelta(days=7)) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False From 21c8914efe65465ef90351dfa13441bf74e67427 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Sat, 6 Oct 2018 00:13:47 -0500 Subject: [PATCH 10/16] include pending days input for schedule key deletion and update tests since boto client returns DeletionDate as datetime --- moto/kms/responses.py | 3 ++- tests/test_kms/test_kms.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/moto/kms/responses.py b/moto/kms/responses.py index fe7d28523..831045e3c 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -269,11 +269,12 @@ class KmsResponse(BaseResponse): def schedule_key_deletion(self): key_id = self.parameters.get('KeyId') + pending_window_in_days = self.parameters.get('PendingWindowInDays') _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) try: return json.dumps({ 'KeyId': key_id, - 'DeletionDate': iso_8601_datetime_without_milliseconds(self.kms_backend.schedule_key_deletion(key_id)) + 'DeletionDate': iso_8601_datetime_without_milliseconds(self.kms_backend.schedule_key_deletion(key_id, pending_window_in_days)) }) except KeyError: raise JSONResponseError(404, 'Not Found', body={ diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index a06a07324..218df1d8f 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -7,7 +7,6 @@ from boto.exception import JSONResponseError from boto.kms.exceptions import AlreadyExistsException, NotFoundException import sure # noqa from moto import mock_kms, mock_kms_deprecated -from moto.core.utils import iso_8601_datetime_without_milliseconds from nose.tools import assert_raises from freezegun import freeze_time from datetime import datetime, timedelta @@ -660,7 +659,7 @@ def test_schedule_key_deletion(): KeyId=key['KeyMetadata']['KeyId'] ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == iso_8601_datetime_without_milliseconds(datetime.now() + timedelta(days=30)) + assert response['DeletionDate'] == datetime.now() + timedelta(days=30) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False @@ -678,7 +677,7 @@ def test_schedule_key_deletion_custom(): PendingWindowInDays=7 ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == iso_8601_datetime_without_milliseconds(datetime.now() + timedelta(days=7)) + assert response['DeletionDate'] == datetime.now() + timedelta(days=7) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False From 59c233f43158d453fd9f2392a8db4d451e34a46d Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Sat, 6 Oct 2018 00:33:23 -0500 Subject: [PATCH 11/16] avoid needing to import datetime and dealing with timezone vs naive datetimes in tests --- tests/test_kms/test_kms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 218df1d8f..b09459e1d 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -9,7 +9,6 @@ import sure # noqa from moto import mock_kms, mock_kms_deprecated from nose.tools import assert_raises from freezegun import freeze_time -from datetime import datetime, timedelta @mock_kms_deprecated @@ -659,7 +658,7 @@ def test_schedule_key_deletion(): KeyId=key['KeyMetadata']['KeyId'] ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == datetime.now() + timedelta(days=30) + assert response['DeletionDate'] == '2015-01-31T12:00:00.000Z' result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False @@ -677,7 +676,7 @@ def test_schedule_key_deletion_custom(): PendingWindowInDays=7 ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == datetime.now() + timedelta(days=7) + assert response['DeletionDate'] == '2015-01-08T12:00:00.000Z' result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False From 9b25d56a3585fcc5a90a8e606b63cacf170d4351 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Sat, 6 Oct 2018 01:18:26 -0500 Subject: [PATCH 12/16] need datetime for tests since thats what boto3 returns and add default for PendingWindowInDays --- moto/kms/models.py | 2 +- moto/kms/responses.py | 5 ++++- tests/test_kms/test_kms.py | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 113bd1733..01b3d9711 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -160,7 +160,7 @@ class KmsBackend(BaseBackend): self.keys[key_id].key_state = 'Disabled' self.keys[key_id].deletion_date = None - def schedule_key_deletion(self, key_id, pending_window_in_days=30): + def schedule_key_deletion(self, key_id, pending_window_in_days): if key_id in self.keys: if 7 <= pending_window_in_days <= 30: self.keys[key_id].enabled = False diff --git a/moto/kms/responses.py b/moto/kms/responses.py index 831045e3c..7caeafcaa 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -269,7 +269,10 @@ class KmsResponse(BaseResponse): def schedule_key_deletion(self): key_id = self.parameters.get('KeyId') - pending_window_in_days = self.parameters.get('PendingWindowInDays') + if self.parameters.get('PendingWindowInDays') is None: + pending_window_in_days = 30 + else: + pending_window_in_days = self.parameters.get('PendingWindowInDays') _assert_valid_key_id(self.kms_backend.get_key_id(key_id)) try: return json.dumps({ diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index b09459e1d..e16bd2cea 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -9,6 +9,7 @@ import sure # noqa from moto import mock_kms, mock_kms_deprecated from nose.tools import assert_raises from freezegun import freeze_time +from datetime import datetime, timedelta @mock_kms_deprecated @@ -658,7 +659,7 @@ def test_schedule_key_deletion(): KeyId=key['KeyMetadata']['KeyId'] ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == '2015-01-31T12:00:00.000Z' + assert response['DeletionDate'] == datetime(2015, 1, 31, 12, 0, tzinfo=tzlocal()) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False @@ -676,7 +677,7 @@ def test_schedule_key_deletion_custom(): PendingWindowInDays=7 ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == '2015-01-08T12:00:00.000Z' + assert response['DeletionDate'] == datetime(2015, 1, 8, 12, 0, tzinfo=tzlocal()) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False From 76baab74ad6a1c4ebe8d3037bf202ca473187875 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Sat, 6 Oct 2018 01:33:02 -0500 Subject: [PATCH 13/16] missing tzlocal --- tests/test_kms/test_kms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index e16bd2cea..159f45af4 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -10,6 +10,7 @@ from moto import mock_kms, mock_kms_deprecated from nose.tools import assert_raises from freezegun import freeze_time from datetime import datetime, timedelta +from dateutil.tz import tzlocal @mock_kms_deprecated From 398dcd8230d8a2e235527bb72a0766b02d3cd6fb Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Sat, 6 Oct 2018 01:47:22 -0500 Subject: [PATCH 14/16] transform DeletionDate in model instead to accomodate Key.to_dict --- moto/kms/models.py | 5 +++-- moto/kms/responses.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 01b3d9711..bb39d1b24 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import boto.kms from moto.core import BaseBackend, BaseModel +from moto.core.utils import iso_8601_datetime_without_milliseconds from .utils import generate_key_id from collections import defaultdict from datetime import datetime, timedelta @@ -43,7 +44,7 @@ class Key(BaseModel): } } if self.key_state == 'PendingDeletion': - key_dict['KeyMetadata']['DeletionDate'] = self.deletion_date + key_dict['KeyMetadata']['DeletionDate'] = iso_8601_datetime_without_milliseconds(self.deletion_date) return key_dict def delete(self, region_name): @@ -166,7 +167,7 @@ class KmsBackend(BaseBackend): self.keys[key_id].enabled = False self.keys[key_id].key_state = 'PendingDeletion' self.keys[key_id].deletion_date = datetime.now() + timedelta(days=pending_window_in_days) - return self.keys[key_id].deletion_date + return iso_8601_datetime_without_milliseconds(self.keys[key_id].deletion_date) kms_backends = {} diff --git a/moto/kms/responses.py b/moto/kms/responses.py index 7caeafcaa..5883f51ec 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -9,7 +9,6 @@ from boto.exception import JSONResponseError from boto.kms.exceptions import AlreadyExistsException, NotFoundException from moto.core.responses import BaseResponse -from moto.core.utils import iso_8601_datetime_without_milliseconds from .models import kms_backends reserved_aliases = [ @@ -277,7 +276,7 @@ class KmsResponse(BaseResponse): try: return json.dumps({ 'KeyId': key_id, - 'DeletionDate': iso_8601_datetime_without_milliseconds(self.kms_backend.schedule_key_deletion(key_id, pending_window_in_days)) + 'DeletionDate': self.kms_backend.schedule_key_deletion(key_id, pending_window_in_days) }) except KeyError: raise JSONResponseError(404, 'Not Found', body={ From c2595b2eef9c147ccc03d2ce7ce2b59b6cec4d35 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Mon, 8 Oct 2018 08:29:21 -0500 Subject: [PATCH 15/16] cant manipulate time in server mode tests --- tests/test_kms/test_kms.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 159f45af4..4f8503ceb 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -655,12 +655,19 @@ def test_enable_key(): def test_schedule_key_deletion(): client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='schedule-key-deletion') - with freeze_time("2015-01-01 12:00:00"): + if os.environ.get('TEST_SERVER_MODE', 'false').lower() == 'false': + with freeze_time("2015-01-01 12:00:00"): + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + assert response['KeyId'] == key['KeyMetadata']['KeyId'] + assert response['DeletionDate'] == datetime(2015, 1, 31, 12, 0, tzinfo=tzlocal()) + else: + # Can't manipulate time in server mode response = client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'] ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == datetime(2015, 1, 31, 12, 0, tzinfo=tzlocal()) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False @@ -672,13 +679,21 @@ def test_schedule_key_deletion(): def test_schedule_key_deletion_custom(): client = boto3.client('kms', region_name='us-east-1') key = client.create_key(Description='schedule-key-deletion') - with freeze_time("2015-01-01 12:00:00"): + if os.environ.get('TEST_SERVER_MODE', 'false').lower() == 'false': + with freeze_time("2015-01-01 12:00:00"): + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'], + PendingWindowInDays=7 + ) + assert response['KeyId'] == key['KeyMetadata']['KeyId'] + assert response['DeletionDate'] == datetime(2015, 1, 8, 12, 0, tzinfo=tzlocal()) + else: + # Can't manipulate time in server mode response = client.schedule_key_deletion( KeyId=key['KeyMetadata']['KeyId'], PendingWindowInDays=7 ) assert response['KeyId'] == key['KeyMetadata']['KeyId'] - assert response['DeletionDate'] == datetime(2015, 1, 8, 12, 0, tzinfo=tzlocal()) result = client.describe_key(KeyId=key['KeyMetadata']['KeyId']) assert result["KeyMetadata"]["Enabled"] == False From 181e9690b84db61f180d31f30ea3e92776cb9a49 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Mon, 8 Oct 2018 08:38:49 -0500 Subject: [PATCH 16/16] need os for checking server mode env variable --- tests/test_kms/test_kms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 4f8503ceb..8bccae27a 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -import re +import os, re import boto3 import boto.kms