localstack-core 4.10.1.dev7__py3-none-any.whl → 4.11.2.dev14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +604 -561
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1201 -969
- localstack/aws/api/cloudwatch/__init__.py +375 -375
- localstack/aws/api/config/__init__.py +784 -786
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +10062 -8826
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +866 -572
- localstack/aws/api/kinesis/__init__.py +235 -147
- localstack/aws/api/kms/__init__.py +341 -336
- localstack/aws/api/lambda_/__init__.py +974 -621
- localstack/aws/api/logs/__init__.py +988 -675
- localstack/aws/api/opensearch/__init__.py +903 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1257 -1166
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +103 -67
- localstack/aws/api/route53/__init__.py +296 -254
- localstack/aws/api/route53resolver/__init__.py +397 -396
- localstack/aws/api/s3/__init__.py +1412 -1349
- localstack/aws/api/s3control/__init__.py +594 -594
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +221 -216
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1977 -1971
- localstack/aws/api/stepfunctions/__init__.py +375 -333
- localstack/aws/api/sts/__init__.py +142 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +22 -3
- localstack/aws/protocol/parser.py +1 -1
- localstack/aws/protocol/serializer.py +1 -1
- localstack/aws/scaffold.py +15 -17
- localstack/cli/localstack.py +6 -1
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +38 -3
- localstack/services/acm/provider.py +4 -0
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +60 -24
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/v2/provider.py +6 -6
- localstack/services/cloudwatch/provider.py +10 -3
- localstack/services/cloudwatch/provider_v2.py +6 -3
- localstack/services/configservice/provider.py +5 -1
- localstack/services/dynamodb/provider.py +1 -0
- localstack/services/dynamodb/v2/provider.py +1 -0
- localstack/services/dynamodbstreams/provider.py +6 -0
- localstack/services/dynamodbstreams/v2/provider.py +6 -0
- localstack/services/ec2/provider.py +6 -0
- localstack/services/es/provider.py +6 -0
- localstack/services/events/provider.py +4 -0
- localstack/services/events/v1/provider.py +9 -0
- localstack/services/firehose/provider.py +5 -0
- localstack/services/iam/provider.py +4 -0
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +44 -24
- localstack/services/kms/provider.py +97 -16
- localstack/services/lambda_/api_utils.py +40 -21
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +27 -2
- localstack/services/lambda_/invocation/lambda_service.py +51 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +240 -96
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +10 -3
- localstack/services/logs/provider.py +45 -19
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/s3/provider.py +29 -16
- localstack/services/s3/utils.py +35 -14
- localstack/services/s3control/provider.py +101 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/sns/constants.py +3 -1
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +30 -1
- localstack/services/sns/v2/provider.py +794 -31
- localstack/services/sns/v2/utils.py +20 -0
- localstack/services/sqs/models.py +37 -10
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
- localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
- localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
- localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
- localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
- localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
- localstack/services/stepfunctions/asl/eval/environment.py +30 -22
- localstack/services/stepfunctions/asl/eval/states.py +1 -1
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
- localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
- localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
- localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
- localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
- localstack/services/stepfunctions/backend/execution.py +6 -6
- localstack/services/stepfunctions/backend/execution_worker.py +5 -5
- localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
- localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
- localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
- localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
- localstack/services/stepfunctions/provider.py +78 -27
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/publisher.py +37 -155
- localstack/utils/analytics/service_request_aggregator.py +6 -4
- localstack/utils/aws/arns.py +7 -0
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/batching.py +258 -0
- localstack/utils/catalog/catalog_loader.py +111 -3
- localstack/utils/collections.py +23 -11
- localstack/utils/crypto.py +109 -0
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +7 -6
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +149 -141
- localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
- localstack/services/stepfunctions/mocking/__init__.py +0 -0
- localstack/utils/batch_policy.py +0 -124
- localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
|
@@ -135,15 +135,15 @@ SSM_PARAMETER_TYPE_RE = re.compile(
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
def is_stack_arn(stack_name_or_id: str) -> bool:
|
|
138
|
-
return ARN_STACK_REGEX.match(stack_name_or_id) is not None
|
|
138
|
+
return stack_name_or_id and ARN_STACK_REGEX.match(stack_name_or_id) is not None
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def is_changeset_arn(change_set_name_or_id: str) -> bool:
|
|
142
|
-
return ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None
|
|
142
|
+
return change_set_name_or_id and ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
def is_stack_set_arn(stack_set_name_or_id: str) -> bool:
|
|
146
|
-
return ARN_STACK_SET_REGEX.match(stack_set_name_or_id) is not None
|
|
146
|
+
return stack_set_name_or_id and ARN_STACK_SET_REGEX.match(stack_set_name_or_id) is not None
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
class StackNotFoundError(ValidationError):
|
|
@@ -1349,8 +1349,8 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1349
1349
|
def describe_stack_events(
|
|
1350
1350
|
self,
|
|
1351
1351
|
context: RequestContext,
|
|
1352
|
-
stack_name: StackName
|
|
1353
|
-
next_token: NextToken = None,
|
|
1352
|
+
stack_name: StackName,
|
|
1353
|
+
next_token: NextToken | None = None,
|
|
1354
1354
|
**kwargs,
|
|
1355
1355
|
) -> DescribeStackEventsOutput:
|
|
1356
1356
|
if not stack_name:
|
|
@@ -1388,7 +1388,7 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1388
1388
|
stack_name, message_override=f"Stack with id {stack_name} does not exist"
|
|
1389
1389
|
)
|
|
1390
1390
|
else:
|
|
1391
|
-
raise
|
|
1391
|
+
raise ValidationError("StackName is required if ChangeSetName is not specified.")
|
|
1392
1392
|
|
|
1393
1393
|
if template_stage == TemplateStage.Processed and "Transform" in stack.template_body:
|
|
1394
1394
|
template_body = json.dumps(stack.processed_template)
|
|
@@ -35,6 +35,7 @@ from localstack.services import moto
|
|
|
35
35
|
from localstack.services.cloudwatch.alarm_scheduler import AlarmScheduler
|
|
36
36
|
from localstack.services.edge import ROUTER
|
|
37
37
|
from localstack.services.plugins import SERVICE_PLUGINS, ServiceLifecycleHook
|
|
38
|
+
from localstack.state import StateVisitor
|
|
38
39
|
from localstack.utils.aws import arns
|
|
39
40
|
from localstack.utils.aws.arns import extract_account_id_from_arn, lambda_function_name
|
|
40
41
|
from localstack.utils.aws.request_context import (
|
|
@@ -306,8 +307,13 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
306
307
|
self.tags = TaggingService()
|
|
307
308
|
self.alarm_scheduler = None
|
|
308
309
|
|
|
310
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
311
|
+
visitor.visit(cloudwatch_backends)
|
|
312
|
+
|
|
309
313
|
def on_after_init(self):
|
|
310
314
|
ROUTER.add(PATH_GET_RAW_METRICS, self.get_raw_metrics)
|
|
315
|
+
|
|
316
|
+
def on_before_start(self):
|
|
311
317
|
self.start_alarm_scheduler()
|
|
312
318
|
|
|
313
319
|
def on_before_state_reset(self):
|
|
@@ -337,9 +343,10 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
337
343
|
self.alarm_scheduler = AlarmScheduler()
|
|
338
344
|
|
|
339
345
|
def shutdown_alarm_scheduler(self):
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
346
|
+
if self.alarm_scheduler:
|
|
347
|
+
LOG.debug("stopping cloudwatch scheduler")
|
|
348
|
+
self.alarm_scheduler.shutdown_scheduler()
|
|
349
|
+
self.alarm_scheduler = None
|
|
343
350
|
|
|
344
351
|
def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None:
|
|
345
352
|
moto.call_moto(context)
|
|
@@ -161,6 +161,8 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
161
161
|
|
|
162
162
|
def on_after_init(self):
|
|
163
163
|
ROUTER.add(PATH_GET_RAW_METRICS, self.get_raw_metrics)
|
|
164
|
+
|
|
165
|
+
def on_before_start(self):
|
|
164
166
|
self.start_alarm_scheduler()
|
|
165
167
|
|
|
166
168
|
def on_before_state_reset(self):
|
|
@@ -192,9 +194,10 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
192
194
|
self.alarm_scheduler = AlarmScheduler()
|
|
193
195
|
|
|
194
196
|
def shutdown_alarm_scheduler(self):
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
if self.alarm_scheduler:
|
|
198
|
+
LOG.debug("stopping cloudwatch scheduler")
|
|
199
|
+
self.alarm_scheduler.shutdown_scheduler()
|
|
200
|
+
self.alarm_scheduler = None
|
|
198
201
|
|
|
199
202
|
def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None:
|
|
200
203
|
"""
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
from localstack.aws.api.config import ConfigApi
|
|
2
|
+
from localstack.state import StateVisitor
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
class ConfigProvider(ConfigApi):
|
|
5
|
-
|
|
6
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
7
|
+
from moto.config.models import config_backends
|
|
8
|
+
|
|
9
|
+
visitor.visit(config_backends)
|
|
@@ -535,6 +535,7 @@ class DynamoDBProvider(DynamodbApi, ServiceLifecycleHook):
|
|
|
535
535
|
self.server = self._new_dynamodb_server()
|
|
536
536
|
self._expired_items_worker = ExpiredItemsWorker()
|
|
537
537
|
self._router_rules = []
|
|
538
|
+
# TODO: fix _event_forwarder to have lazy instantiation of the ThreadPoolExecutor
|
|
538
539
|
self._event_forwarder = EventForwarder()
|
|
539
540
|
|
|
540
541
|
def on_before_start(self):
|
|
@@ -392,6 +392,7 @@ class DynamoDBProvider(DynamodbApi, ServiceLifecycleHook):
|
|
|
392
392
|
|
|
393
393
|
def accept_state_visitor(self, visitor: StateVisitor):
|
|
394
394
|
visitor.visit(dynamodb_stores)
|
|
395
|
+
# FIXME: DDB v2 does not depend on dynamodbstreams_stores as DynamoDB Local holds all the state
|
|
395
396
|
visitor.visit(dynamodbstreams_stores)
|
|
396
397
|
visitor.visit(AssetDirectory(self.service, os.path.join(config.dirs.data, self.service)))
|
|
397
398
|
|
|
@@ -37,6 +37,7 @@ from localstack.services.dynamodbstreams.dynamodbstreams_api import (
|
|
|
37
37
|
table_name_from_stream_arn,
|
|
38
38
|
)
|
|
39
39
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
40
|
+
from localstack.state import StateVisitor
|
|
40
41
|
from localstack.utils.collections import select_from_typed_dict
|
|
41
42
|
|
|
42
43
|
LOG = logging.getLogger(__name__)
|
|
@@ -57,6 +58,11 @@ class DynamoDBStreamsProvider(DynamodbstreamsApi, ServiceLifecycleHook):
|
|
|
57
58
|
def __init__(self):
|
|
58
59
|
self.shard_to_region = {}
|
|
59
60
|
|
|
61
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
62
|
+
from localstack.services.dynamodbstreams.models import dynamodbstreams_stores
|
|
63
|
+
|
|
64
|
+
visitor.visit(dynamodbstreams_stores)
|
|
65
|
+
|
|
60
66
|
def describe_stream(
|
|
61
67
|
self,
|
|
62
68
|
context: RequestContext,
|
|
@@ -18,6 +18,7 @@ from localstack.services.dynamodb.utils import modify_ddblocal_arns
|
|
|
18
18
|
from localstack.services.dynamodb.v2.provider import DynamoDBProvider, modify_context_region
|
|
19
19
|
from localstack.services.dynamodbstreams.dynamodbstreams_api import get_original_region
|
|
20
20
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
21
|
+
from localstack.state import StateVisitor
|
|
21
22
|
from localstack.utils.aws.arns import parse_arn
|
|
22
23
|
|
|
23
24
|
LOG = logging.getLogger(__name__)
|
|
@@ -32,6 +33,11 @@ class DynamoDBStreamsProvider(DynamodbstreamsApi, ServiceLifecycleHook):
|
|
|
32
33
|
self.server = DynamodbServer.get()
|
|
33
34
|
self.shard_to_region = {}
|
|
34
35
|
|
|
36
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
37
|
+
# DynamoDB Streams state is entirely dependent on DynamoDB Local state, and does not hold state itself
|
|
38
|
+
# the DynamoDB provider is responsible for the persistence of DDB Streams
|
|
39
|
+
pass
|
|
40
|
+
|
|
35
41
|
def on_after_init(self):
|
|
36
42
|
# add response processor specific to ddblocal
|
|
37
43
|
handlers.modify_service_response.append(self.service, modify_ddblocal_arns)
|
|
@@ -94,6 +94,7 @@ from localstack.services.ec2.models import get_ec2_backend
|
|
|
94
94
|
from localstack.services.ec2.patches import apply_patches
|
|
95
95
|
from localstack.services.moto import call_moto, call_moto_with_request
|
|
96
96
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
97
|
+
from localstack.state import StateVisitor
|
|
97
98
|
from localstack.utils.patch import patch
|
|
98
99
|
from localstack.utils.strings import first_char_to_upper, long_uid, short_uid
|
|
99
100
|
|
|
@@ -107,6 +108,11 @@ class Ec2Provider(Ec2Api, ABC, ServiceLifecycleHook):
|
|
|
107
108
|
def on_after_init(self):
|
|
108
109
|
apply_patches()
|
|
109
110
|
|
|
111
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
112
|
+
from moto.ec2.models import ec2_backends
|
|
113
|
+
|
|
114
|
+
visitor.visit(ec2_backends)
|
|
115
|
+
|
|
110
116
|
@handler("DescribeAvailabilityZones", expand=False)
|
|
111
117
|
def describe_availability_zones(
|
|
112
118
|
self,
|
|
@@ -68,6 +68,7 @@ from localstack.aws.api.opensearch import (
|
|
|
68
68
|
)
|
|
69
69
|
from localstack.aws.connect import connect_to
|
|
70
70
|
from localstack.services.opensearch.packages import ELASTICSEARCH_DEFAULT_VERSION
|
|
71
|
+
from localstack.state import StateVisitor
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
def _version_to_opensearch(
|
|
@@ -208,6 +209,11 @@ def exception_mapper():
|
|
|
208
209
|
|
|
209
210
|
|
|
210
211
|
class EsProvider(EsApi):
|
|
212
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
213
|
+
# ES state entirely depends on `opensearch`, and delegates its entire state to it
|
|
214
|
+
# we do not need to manage state in ES
|
|
215
|
+
pass
|
|
216
|
+
|
|
211
217
|
def create_elasticsearch_domain(
|
|
212
218
|
self,
|
|
213
219
|
context: RequestContext,
|
|
@@ -168,6 +168,7 @@ from localstack.services.events.utils import (
|
|
|
168
168
|
recursive_remove_none_values_from_dict,
|
|
169
169
|
)
|
|
170
170
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
171
|
+
from localstack.state import StateVisitor
|
|
171
172
|
from localstack.utils.common import truncate
|
|
172
173
|
from localstack.utils.event_matcher import matches_event
|
|
173
174
|
from localstack.utils.strings import long_uid
|
|
@@ -246,6 +247,9 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
|
|
|
246
247
|
self._connection_service_store: ConnectionServiceDict = {}
|
|
247
248
|
self._api_destination_service_store: ApiDestinationServiceDict = {}
|
|
248
249
|
|
|
250
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
251
|
+
visitor.visit(events_stores)
|
|
252
|
+
|
|
249
253
|
def on_before_start(self):
|
|
250
254
|
JobScheduler.start()
|
|
251
255
|
|
|
@@ -45,6 +45,7 @@ from localstack.services.events.scheduler import JobScheduler
|
|
|
45
45
|
from localstack.services.events.v1.models import EventsStore, events_stores
|
|
46
46
|
from localstack.services.moto import call_moto
|
|
47
47
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
48
|
+
from localstack.state import StateVisitor
|
|
48
49
|
from localstack.utils.aws.arns import event_bus_arn, parse_arn
|
|
49
50
|
from localstack.utils.aws.client_types import ServicePrincipal
|
|
50
51
|
from localstack.utils.aws.message_forwarding import send_event_to_target
|
|
@@ -83,6 +84,14 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
|
|
|
83
84
|
def on_before_stop(self):
|
|
84
85
|
JobScheduler.shutdown()
|
|
85
86
|
|
|
87
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
88
|
+
from moto.events.models import events_backends
|
|
89
|
+
|
|
90
|
+
from localstack.services.events.v1.models import events_stores
|
|
91
|
+
|
|
92
|
+
visitor.visit(events_backends)
|
|
93
|
+
visitor.visit(events_stores)
|
|
94
|
+
|
|
86
95
|
@route("/_aws/events/rules/<path:rule_arn>/trigger")
|
|
87
96
|
def trigger_scheduled_rule(self, request: Request, rule_arn: str):
|
|
88
97
|
"""Developer endpoint to trigger a scheduled rule."""
|
|
@@ -94,6 +94,7 @@ from localstack.services.firehose.mappers import (
|
|
|
94
94
|
convert_source_config_to_desc,
|
|
95
95
|
)
|
|
96
96
|
from localstack.services.firehose.models import FirehoseStore, firehose_stores
|
|
97
|
+
from localstack.state import StateVisitor
|
|
97
98
|
from localstack.utils.aws.arns import (
|
|
98
99
|
extract_account_id_from_arn,
|
|
99
100
|
extract_region_from_arn,
|
|
@@ -251,8 +252,12 @@ class FirehoseProvider(FirehoseApi):
|
|
|
251
252
|
|
|
252
253
|
def __init__(self) -> None:
|
|
253
254
|
super().__init__()
|
|
255
|
+
# TODO: stop/restart the kinesis listeners when stopping the service / reset the state / restore the state
|
|
254
256
|
self.kinesis_listeners = {}
|
|
255
257
|
|
|
258
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
259
|
+
visitor.visit(firehose_stores)
|
|
260
|
+
|
|
256
261
|
@staticmethod
|
|
257
262
|
def get_store(account_id: str, region_name: str) -> FirehoseStore:
|
|
258
263
|
return firehose_stores[account_id][region_name]
|
|
@@ -75,6 +75,7 @@ from localstack.services.iam.resources.policy_simulator import (
|
|
|
75
75
|
)
|
|
76
76
|
from localstack.services.iam.resources.service_linked_roles import SERVICE_LINKED_ROLES
|
|
77
77
|
from localstack.services.moto import call_moto
|
|
78
|
+
from localstack.state import StateVisitor
|
|
78
79
|
from localstack.utils.aws.request_context import extract_access_key_id_from_auth_header
|
|
79
80
|
|
|
80
81
|
LOG = logging.getLogger(__name__)
|
|
@@ -110,6 +111,9 @@ class IamProvider(IamApi):
|
|
|
110
111
|
apply_iam_patches()
|
|
111
112
|
self.policy_simulator = BasicIAMPolicySimulator()
|
|
112
113
|
|
|
114
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
115
|
+
visitor.visit(iam_backends)
|
|
116
|
+
|
|
113
117
|
@handler("CreateRole", expand=False)
|
|
114
118
|
def create_role(
|
|
115
119
|
self, context: RequestContext, request: CreateRoleRequest
|
|
@@ -7,7 +7,7 @@ from localstack.packages import InstallTarget, Package
|
|
|
7
7
|
from localstack.packages.core import GitHubReleaseInstaller, NodePackageInstaller
|
|
8
8
|
from localstack.packages.java import JavaInstallerMixin, java_package
|
|
9
9
|
|
|
10
|
-
_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.
|
|
10
|
+
_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.5.1"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class KinesisMockEngine(StrEnum):
|
|
@@ -20,7 +20,6 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
|
|
|
20
20
|
from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15
|
|
21
21
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
|
22
22
|
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
|
|
23
|
-
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
24
23
|
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
25
24
|
|
|
26
25
|
from localstack.aws.api.kms import (
|
|
@@ -173,6 +172,7 @@ class KmsCryptoKey:
|
|
|
173
172
|
public_key: bytes | None
|
|
174
173
|
private_key: bytes | None
|
|
175
174
|
key_material: bytes
|
|
175
|
+
pending_key_material: bytes | None
|
|
176
176
|
key_spec: str
|
|
177
177
|
|
|
178
178
|
@staticmethod
|
|
@@ -217,6 +217,7 @@ class KmsCryptoKey:
|
|
|
217
217
|
def __init__(self, key_spec: str, key_material: bytes | None = None):
|
|
218
218
|
self.private_key = None
|
|
219
219
|
self.public_key = None
|
|
220
|
+
self.pending_key_material = None
|
|
220
221
|
# Technically, key_material, being a symmetric encryption key, is only relevant for
|
|
221
222
|
# key_spec == SYMMETRIC_DEFAULT.
|
|
222
223
|
# But LocalStack uses symmetric encryption with this key_material even for other specs. Asymmetric keys are
|
|
@@ -248,8 +249,9 @@ class KmsCryptoKey:
|
|
|
248
249
|
self._serialize_key(key)
|
|
249
250
|
|
|
250
251
|
def load_key_material(self, material: bytes):
|
|
251
|
-
if self.key_spec
|
|
252
|
-
|
|
252
|
+
if self.key_spec == KeySpec.SYMMETRIC_DEFAULT:
|
|
253
|
+
self.pending_key_material = material
|
|
254
|
+
elif self.key_spec in [
|
|
253
255
|
KeySpec.HMAC_224,
|
|
254
256
|
KeySpec.HMAC_256,
|
|
255
257
|
KeySpec.HMAC_384,
|
|
@@ -323,9 +325,28 @@ class KmsKey:
|
|
|
323
325
|
# remove the _custom_key_material_ tag from the tags to not readily expose the custom key material
|
|
324
326
|
del self.tags[TAG_KEY_CUSTOM_KEY_MATERIAL]
|
|
325
327
|
self.crypto_key = KmsCryptoKey(self.metadata.get("KeySpec"), custom_key_material)
|
|
328
|
+
self._internal_key_id = uuid.uuid4()
|
|
329
|
+
|
|
330
|
+
# The KMS implementation always provides a crypto key with key material which doesn't suit scenarios where a
|
|
331
|
+
# KMS Key may have no key material e.g. for external keys. Don't expose the CurrentKeyMaterialId in those cases.
|
|
332
|
+
if custom_key_material or (
|
|
333
|
+
self.metadata["Origin"] == "AWS_KMS"
|
|
334
|
+
and self.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT
|
|
335
|
+
):
|
|
336
|
+
self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id(
|
|
337
|
+
self.crypto_key.key_material
|
|
338
|
+
)
|
|
339
|
+
|
|
326
340
|
self.rotation_period_in_days = 365
|
|
327
341
|
self.next_rotation_date = None
|
|
328
342
|
|
|
343
|
+
def generate_key_material_id(self, key_material: bytes) -> str:
|
|
344
|
+
# The KeyMaterialId depends on the key material and the KeyId. Use an internal ID to prevent brute forcing
|
|
345
|
+
# the value of the key material from the public KeyId and KeyMaterialId.
|
|
346
|
+
# https://docs.aws.amazon.com/kms/latest/APIReference/API_ImportKeyMaterial.html
|
|
347
|
+
key_material_id_hex = uuid.uuid5(self._internal_key_id, key_material).hex
|
|
348
|
+
return str(key_material_id_hex) * 2
|
|
349
|
+
|
|
329
350
|
def calculate_and_set_arn(self, account_id, region):
|
|
330
351
|
self.metadata["Arn"] = kms_key_arn(self.metadata.get("KeyId"), account_id, region)
|
|
331
352
|
|
|
@@ -420,17 +441,15 @@ class KmsKey:
|
|
|
420
441
|
|
|
421
442
|
def derive_shared_secret(self, public_key: bytes) -> bytes:
|
|
422
443
|
key_spec = self.metadata.get("KeySpec")
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
f"{self.metadata['Arn']} key usage is {self.metadata['KeyUsage']} which is not valid for DeriveSharedSecret."
|
|
433
|
-
)
|
|
444
|
+
if key_spec not in (
|
|
445
|
+
KeySpec.ECC_NIST_P256,
|
|
446
|
+
KeySpec.ECC_SECG_P256K1,
|
|
447
|
+
KeySpec.ECC_NIST_P384,
|
|
448
|
+
KeySpec.ECC_NIST_P521,
|
|
449
|
+
):
|
|
450
|
+
raise InvalidKeyUsageException(
|
|
451
|
+
f"{self.metadata['Arn']} key usage is {self.metadata['KeyUsage']} which is not valid for DeriveSharedSecret."
|
|
452
|
+
)
|
|
434
453
|
|
|
435
454
|
# Deserialize public key from DER encoded data to EllipticCurvePublicKey.
|
|
436
455
|
try:
|
|
@@ -438,14 +457,7 @@ class KmsKey:
|
|
|
438
457
|
except (UnsupportedAlgorithm, ValueError):
|
|
439
458
|
raise ValidationException("")
|
|
440
459
|
shared_secret = self.crypto_key.key.exchange(ec.ECDH(), pub_key)
|
|
441
|
-
|
|
442
|
-
return HKDF(
|
|
443
|
-
algorithm=algorithm,
|
|
444
|
-
salt=None,
|
|
445
|
-
info=b"",
|
|
446
|
-
length=algorithm.digest_size,
|
|
447
|
-
backend=default_backend(),
|
|
448
|
-
).derive(shared_secret)
|
|
460
|
+
return shared_secret
|
|
449
461
|
|
|
450
462
|
# This method gets called when a key is replicated to another region. It's meant to populate the required metadata
|
|
451
463
|
# fields in a new replica key.
|
|
@@ -746,8 +758,16 @@ class KmsKey:
|
|
|
746
758
|
f"The on-demand rotations limit has been reached for the given keyId. "
|
|
747
759
|
f"No more on-demand rotations can be performed for this key: {self.metadata['Arn']}"
|
|
748
760
|
)
|
|
749
|
-
self.
|
|
750
|
-
|
|
761
|
+
current_key_material = self.crypto_key.key_material
|
|
762
|
+
pending_key_material = self.crypto_key.pending_key_material
|
|
763
|
+
|
|
764
|
+
self.previous_keys.append(current_key_material)
|
|
765
|
+
|
|
766
|
+
# If there is no pending material stored on the key, then key material will be generated.
|
|
767
|
+
self.crypto_key = KmsCryptoKey(KeySpec.SYMMETRIC_DEFAULT, pending_key_material)
|
|
768
|
+
self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id(
|
|
769
|
+
self.crypto_key.key_material
|
|
770
|
+
)
|
|
751
771
|
|
|
752
772
|
|
|
753
773
|
class KmsGrant:
|
|
@@ -4,10 +4,13 @@ import datetime
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
|
|
7
|
+
from cbor2 import loads as cbor2_loads
|
|
7
8
|
from cryptography.exceptions import InvalidTag
|
|
8
9
|
from cryptography.hazmat.backends import default_backend
|
|
9
10
|
from cryptography.hazmat.primitives import hashes, keywrap
|
|
10
11
|
from cryptography.hazmat.primitives.asymmetric import padding
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
|
13
|
+
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
11
14
|
|
|
12
15
|
from localstack.aws.api import CommonServiceException, RequestContext, handler
|
|
13
16
|
from localstack.aws.api.kms import (
|
|
@@ -135,9 +138,11 @@ from localstack.services.kms.utils import (
|
|
|
135
138
|
validate_alias_name,
|
|
136
139
|
)
|
|
137
140
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
141
|
+
from localstack.state import StateVisitor
|
|
138
142
|
from localstack.utils.aws.arns import get_partition, kms_alias_arn, parse_arn
|
|
139
143
|
from localstack.utils.collections import PaginatedList
|
|
140
144
|
from localstack.utils.common import select_attributes
|
|
145
|
+
from localstack.utils.crypto import pkcs7_envelope_encrypt
|
|
141
146
|
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
142
147
|
|
|
143
148
|
LOG = logging.getLogger(__name__)
|
|
@@ -197,6 +202,9 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
197
202
|
- VerifyMac
|
|
198
203
|
"""
|
|
199
204
|
|
|
205
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
206
|
+
visitor.visit(kms_stores)
|
|
207
|
+
|
|
200
208
|
#
|
|
201
209
|
# Helpers
|
|
202
210
|
#
|
|
@@ -518,7 +526,12 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
518
526
|
|
|
519
527
|
self.update_primary_key_with_replica_keys(primary_key, replica_key, replica_region)
|
|
520
528
|
|
|
521
|
-
|
|
529
|
+
# CurrentKeyMaterialId is not returned in the ReplicaKeyMetadata. May be due to not being evaluated until
|
|
530
|
+
# the key has been successfully replicated as it does not show up in DescribeKey immediately either.
|
|
531
|
+
replica_key_metadata_response = copy.deepcopy(replica_key.metadata)
|
|
532
|
+
replica_key_metadata_response.pop("CurrentKeyMaterialId", None)
|
|
533
|
+
|
|
534
|
+
return ReplicateKeyResponse(ReplicaKeyMetadata=replica_key_metadata_response)
|
|
522
535
|
|
|
523
536
|
@staticmethod
|
|
524
537
|
# Adds new multi region replica key to the primary key's metadata.
|
|
@@ -1075,6 +1088,25 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1075
1088
|
self._validate_key_for_encryption_decryption(context, key)
|
|
1076
1089
|
self._validate_key_state_not_pending_import(key)
|
|
1077
1090
|
|
|
1091
|
+
# Handle the recipient field. This is used by AWS Nitro to re-encrypt the plaintext to the key specified
|
|
1092
|
+
# by the enclave. Proper support for this will take significant work to figure out how to model enforcing
|
|
1093
|
+
# the attestation measurements; for now, if recipient is specified and has an attestation doc in it including
|
|
1094
|
+
# a public key where it's expected to be, we encrypt to that public key. This at least allows users to use
|
|
1095
|
+
# localstack as a drop-in replacement for AWS when testing without having to skip the secondary decryption
|
|
1096
|
+
# when using localstack.
|
|
1097
|
+
recipient_pubkey = None
|
|
1098
|
+
if recipient:
|
|
1099
|
+
attestation_document = recipient["AttestationDocument"]
|
|
1100
|
+
# We do all of this in a try/catch and warn if it fails so that if users are currently passing a nonsense
|
|
1101
|
+
# value we don't break it for them. In the future we could do a breaking change to require a valid attestation
|
|
1102
|
+
# (or at least one that contains the public key).
|
|
1103
|
+
try:
|
|
1104
|
+
recipient_pubkey = self._extract_attestation_pubkey(attestation_document)
|
|
1105
|
+
except Exception as e:
|
|
1106
|
+
logging.warning(
|
|
1107
|
+
"Unable to extract public key from non-empty attestation document: %s", e
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1078
1110
|
try:
|
|
1079
1111
|
# TODO: Extend the implementation to handle additional encryption/decryption scenarios
|
|
1080
1112
|
# beyond the current support for offline encryption and online decryption using RSA keys if key id exists in
|
|
@@ -1088,20 +1120,27 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1088
1120
|
plaintext = key.decrypt(ciphertext, encryption_context)
|
|
1089
1121
|
except InvalidTag:
|
|
1090
1122
|
raise InvalidCiphertextException()
|
|
1123
|
+
|
|
1091
1124
|
# For compatibility, we return EncryptionAlgorithm values expected from AWS. But LocalStack currently always
|
|
1092
1125
|
# encrypts with symmetric encryption no matter the key settings.
|
|
1093
1126
|
#
|
|
1094
1127
|
# We return a key ARN instead of KeyId despite the name of the parameter, as this is what AWS does and states
|
|
1095
1128
|
# in its docs.
|
|
1096
|
-
# TODO add support for "recipient"
|
|
1097
1129
|
# https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html#API_Decrypt_RequestSyntax
|
|
1098
1130
|
# TODO add support for "dry_run"
|
|
1099
|
-
|
|
1131
|
+
response = DecryptResponse(
|
|
1100
1132
|
KeyId=key.metadata.get("Arn"),
|
|
1101
|
-
Plaintext=plaintext,
|
|
1102
1133
|
EncryptionAlgorithm=encryption_algorithm,
|
|
1103
1134
|
)
|
|
1104
1135
|
|
|
1136
|
+
# Encrypt to the recipient pubkey if specified. Otherwise, return the actual plaintext
|
|
1137
|
+
if recipient_pubkey:
|
|
1138
|
+
response["CiphertextForRecipient"] = pkcs7_envelope_encrypt(plaintext, recipient_pubkey)
|
|
1139
|
+
else:
|
|
1140
|
+
response["Plaintext"] = plaintext
|
|
1141
|
+
|
|
1142
|
+
return response
|
|
1143
|
+
|
|
1105
1144
|
def get_parameters_for_import(
|
|
1106
1145
|
self,
|
|
1107
1146
|
context: RequestContext,
|
|
@@ -1176,13 +1215,10 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1176
1215
|
# TODO check if there was already a key imported for this kms key
|
|
1177
1216
|
# if so, it has to be identical. We cannot change keys by reimporting after deletion/expiry
|
|
1178
1217
|
key_material = self._decrypt_wrapped_key_material(import_state, encrypted_key_material)
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
key_to_import_material_to.metadata["ExpirationModel"] = (
|
|
1184
|
-
ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
1185
|
-
)
|
|
1218
|
+
key_material_id = key_to_import_material_to.generate_key_material_id(key_material)
|
|
1219
|
+
key_to_import_material_to.metadata["ExpirationModel"] = (
|
|
1220
|
+
expiration_model or ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
1221
|
+
)
|
|
1186
1222
|
if (
|
|
1187
1223
|
key_to_import_material_to.metadata["ExpirationModel"]
|
|
1188
1224
|
== ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
@@ -1191,12 +1227,42 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1191
1227
|
raise ValidationException(
|
|
1192
1228
|
"A validTo date must be set if the ExpirationModel is KEY_MATERIAL_EXPIRES"
|
|
1193
1229
|
)
|
|
1230
|
+
if existing_pending_material := key_to_import_material_to.crypto_key.pending_key_material:
|
|
1231
|
+
pending_key_material_id = key_to_import_material_to.generate_key_material_id(
|
|
1232
|
+
existing_pending_material
|
|
1233
|
+
)
|
|
1234
|
+
raise KMSInvalidStateException(
|
|
1235
|
+
f"New key material (id: {key_material_id}) cannot be imported into KMS key "
|
|
1236
|
+
f"{key_to_import_material_to.metadata['Arn']}, because another key material "
|
|
1237
|
+
f"(id: {pending_key_material_id}) is pending rotation."
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1194
1240
|
# TODO actually set validTo and make the key expire
|
|
1195
1241
|
key_to_import_material_to.metadata["Enabled"] = True
|
|
1196
1242
|
key_to_import_material_to.metadata["KeyState"] = KeyState.Enabled
|
|
1197
1243
|
key_to_import_material_to.crypto_key.load_key_material(key_material)
|
|
1198
1244
|
|
|
1199
|
-
|
|
1245
|
+
# KeyMaterialId / CurrentKeyMaterialId is only exposed for symmetric encryption keys.
|
|
1246
|
+
key_material_id_response = None
|
|
1247
|
+
if key_to_import_material_to.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT:
|
|
1248
|
+
key_material_id_response = key_to_import_material_to.generate_key_material_id(
|
|
1249
|
+
key_material
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
# If there is no CurrentKeyMaterialId, instantly promote the pending key material to the current.
|
|
1253
|
+
if key_to_import_material_to.metadata.get("CurrentKeyMaterialId") is None:
|
|
1254
|
+
key_to_import_material_to.metadata["CurrentKeyMaterialId"] = (
|
|
1255
|
+
key_material_id_response
|
|
1256
|
+
)
|
|
1257
|
+
key_to_import_material_to.crypto_key.key_material = (
|
|
1258
|
+
key_to_import_material_to.crypto_key.pending_key_material
|
|
1259
|
+
)
|
|
1260
|
+
key_to_import_material_to.crypto_key.pending_key_material = None
|
|
1261
|
+
|
|
1262
|
+
return ImportKeyMaterialResponse(
|
|
1263
|
+
KeyId=key_to_import_material_to.metadata["Arn"],
|
|
1264
|
+
KeyMaterialId=key_material_id_response,
|
|
1265
|
+
)
|
|
1200
1266
|
|
|
1201
1267
|
def delete_imported_key_material(
|
|
1202
1268
|
self,
|
|
@@ -1323,7 +1389,7 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1323
1389
|
key = self._get_kms_key(account_id, region_name, key_id, any_key_state_allowed=True)
|
|
1324
1390
|
|
|
1325
1391
|
response = GetKeyRotationStatusResponse(
|
|
1326
|
-
KeyId=
|
|
1392
|
+
KeyId=key.metadata["Arn"],
|
|
1327
1393
|
KeyRotationEnabled=key.is_key_rotation_enabled,
|
|
1328
1394
|
NextRotationDate=key.next_rotation_date,
|
|
1329
1395
|
)
|
|
@@ -1415,13 +1481,13 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1415
1481
|
|
|
1416
1482
|
if key.metadata["KeySpec"] != KeySpec.SYMMETRIC_DEFAULT:
|
|
1417
1483
|
raise UnsupportedOperationException()
|
|
1418
|
-
|
|
1419
|
-
|
|
1484
|
+
self._validate_key_state_not_pending_import(key)
|
|
1485
|
+
self._validate_external_key_has_pending_material(key)
|
|
1420
1486
|
|
|
1421
1487
|
key.rotate_key_on_demand()
|
|
1422
1488
|
|
|
1423
1489
|
return RotateKeyOnDemandResponse(
|
|
1424
|
-
KeyId=
|
|
1490
|
+
KeyId=key.metadata["Arn"],
|
|
1425
1491
|
)
|
|
1426
1492
|
|
|
1427
1493
|
@handler("TagResource", expand=False)
|
|
@@ -1498,6 +1564,12 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1498
1564
|
if key.metadata["KeyState"] == KeyState.PendingImport:
|
|
1499
1565
|
raise KMSInvalidStateException(f"{key.metadata['Arn']} is pending import.")
|
|
1500
1566
|
|
|
1567
|
+
def _validate_external_key_has_pending_material(self, key: KmsKey):
|
|
1568
|
+
if key.metadata["Origin"] == "EXTERNAL" and key.crypto_key.pending_key_material is None:
|
|
1569
|
+
raise KMSInvalidStateException(
|
|
1570
|
+
f"No available key material pending rotation for the key: {key.metadata['Arn']}."
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1501
1573
|
def _validate_key_for_encryption_decryption(self, context: RequestContext, key: KmsKey):
|
|
1502
1574
|
key_usage = key.metadata["KeyUsage"]
|
|
1503
1575
|
if key_usage != "ENCRYPT_DECRYPT":
|
|
@@ -1559,6 +1631,15 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1559
1631
|
f" constraint: [Member must satisfy enum value set: {VALID_OPERATIONS}]"
|
|
1560
1632
|
)
|
|
1561
1633
|
|
|
1634
|
+
def _extract_attestation_pubkey(self, attestation_document: bytes) -> RSAPublicKey:
|
|
1635
|
+
# The attestation document comes as a COSE (CBOR Object Signing and Encryption) object: the CBOR
|
|
1636
|
+
# attestation is signed and then the attestation and signature are again CBOR-encoded. For now
|
|
1637
|
+
# we don't bother validating the signature, though in the future we could.
|
|
1638
|
+
cose_document = cbor2_loads(attestation_document)
|
|
1639
|
+
attestation = cbor2_loads(cose_document[2])
|
|
1640
|
+
public_key_bytes = attestation["public_key"]
|
|
1641
|
+
return load_der_public_key(public_key_bytes)
|
|
1642
|
+
|
|
1562
1643
|
def _decrypt_wrapped_key_material(
|
|
1563
1644
|
self,
|
|
1564
1645
|
import_state: KeyImportState,
|