localstack-core 4.7.1.dev139__py3-none-any.whl → 4.10.1.dev42__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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +560 -559
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1041 -969
- localstack/aws/api/cloudwatch/__init__.py +408 -368
- localstack/aws/api/config/__init__.py +788 -786
- localstack/aws/api/core.py +4 -0
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +9713 -8573
- 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 +646 -572
- localstack/aws/api/kinesis/__init__.py +251 -144
- localstack/aws/api/kms/__init__.py +343 -333
- localstack/aws/api/lambda_/__init__.py +585 -571
- localstack/aws/api/logs/__init__.py +682 -666
- localstack/aws/api/opensearch/__init__.py +814 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1192 -1164
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
- localstack/aws/api/route53/__init__.py +256 -254
- localstack/aws/api/route53resolver/__init__.py +396 -396
- localstack/aws/api/s3/__init__.py +1358 -1345
- localstack/aws/api/s3control/__init__.py +616 -584
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +193 -193
- 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 +1978 -1970
- localstack/aws/api/stepfunctions/__init__.py +323 -323
- localstack/aws/api/sts/__init__.py +90 -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/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +43 -10
- localstack/aws/protocol/parser.py +440 -21
- localstack/aws/protocol/serializer.py +684 -64
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/scaffold.py +15 -17
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +33 -13
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +10 -5
- localstack/cli/lpm.py +3 -4
- localstack/cli/profiles.py +1 -2
- localstack/config.py +18 -12
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +39 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +12 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/init.py +1 -1
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +85 -12
- localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
- localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +50 -6
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/entities.py +12 -1
- localstack/services/cloudformation/engine/v2/change_set_model.py +0 -3
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +14 -0
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +13 -15
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +118 -24
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +4 -1
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +5 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +6 -4
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/resource_provider.py +5 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/v2/entities.py +6 -3
- localstack/services/cloudformation/v2/provider.py +178 -33
- localstack/services/cloudformation/v2/types.py +8 -4
- localstack/services/cloudwatch/provider_v2.py +25 -28
- localstack/services/dynamodb/packages.py +2 -1
- localstack/services/dynamodb/provider.py +42 -0
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/es/provider.py +2 -2
- localstack/services/events/event_rule_engine.py +31 -13
- localstack/services/events/models.py +4 -5
- localstack/services/events/target.py +17 -9
- localstack/services/iam/provider.py +11 -116
- localstack/services/iam/resources/policy_simulator.py +133 -0
- localstack/services/kinesis/models.py +15 -2
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kinesis/provider.py +77 -0
- localstack/services/kms/models.py +34 -4
- localstack/services/kms/provider.py +107 -21
- localstack/services/lambda_/api_utils.py +3 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +1 -1
- localstack/services/lambda_/runtimes.py +8 -3
- localstack/services/logs/provider.py +36 -19
- localstack/services/moto.py +2 -1
- localstack/services/opensearch/cluster.py +15 -7
- localstack/services/opensearch/packages.py +26 -7
- localstack/services/opensearch/provider.py +6 -1
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/notifications.py +1 -1
- localstack/services/s3/presigned_url.py +27 -43
- localstack/services/s3/provider.py +68 -12
- localstack/services/s3/utils.py +42 -11
- localstack/services/ses/provider.py +16 -7
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/v2/models.py +190 -0
- localstack/services/sns/v2/provider.py +992 -2
- localstack/services/sns/v2/utils.py +138 -0
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +79 -13
- localstack/services/sqs/provider.py +8 -309
- localstack/services/sqs/query_api.py +1 -1
- localstack/services/sqs/utils.py +121 -2
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +20 -19
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +13 -4
- localstack/testing/pytest/stepfunctions/utils.py +4 -3
- localstack/testing/pytest/util.py +1 -1
- localstack/testing/pytest/validation_tracking.py +1 -2
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +1 -2
- localstack/utils/analytics/metrics/counter.py +6 -8
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/aws/arns.py +17 -9
- localstack/utils/aws/aws_responses.py +7 -7
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +7 -7
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +119 -0
- localstack/utils/catalog/common.py +58 -0
- localstack/utils/catalog/plugins.py +28 -0
- localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
- localstack/utils/collections.py +7 -8
- localstack/utils/config_listener.py +1 -1
- localstack/utils/container_networking.py +2 -3
- localstack/utils/container_utils/container_client.py +115 -131
- localstack/utils/container_utils/docker_cmd_client.py +42 -42
- localstack/utils/container_utils/docker_sdk_client.py +63 -62
- localstack/utils/crypto.py +109 -0
- localstack/utils/diagnose.py +2 -3
- localstack/utils/docker_utils.py +3 -4
- localstack/utils/files.py +31 -7
- localstack/utils/functions.py +3 -2
- localstack/utils/http.py +4 -5
- localstack/utils/json.py +19 -5
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +6 -6
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +6 -5
- localstack/utils/patch.py +2 -1
- localstack/utils/run.py +10 -9
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +2 -3
- localstack/utils/strings.py +10 -11
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +1 -4
- localstack/utils/testutil.py +5 -4
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +11 -3
- localstack/utils/urls.py +1 -3
- localstack/version.py +2 -2
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +19 -13
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +203 -199
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +4 -2
- localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
- localstack/packages/terraform.py +0 -46
- localstack/services/cloudformation/deploy.html +0 -144
- localstack/services/cloudformation/deploy_ui.py +0 -47
- localstack/services/cloudformation/plugins.py +0 -12
- localstack_core-4.7.1.dev139.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from moto.iam import iam_backends
|
|
5
|
+
from moto.iam.models import IAMBackend
|
|
6
|
+
|
|
7
|
+
from localstack.aws.api import RequestContext
|
|
8
|
+
from localstack.aws.api.iam import (
|
|
9
|
+
ActionNameType,
|
|
10
|
+
EvaluationResult,
|
|
11
|
+
PolicyEvaluationDecisionType,
|
|
12
|
+
ResourceNameType,
|
|
13
|
+
SimulatePolicyResponse,
|
|
14
|
+
SimulatePrincipalPolicyRequest,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IAMPolicySimulator(abc.ABC):
|
|
19
|
+
@abc.abstractmethod
|
|
20
|
+
def simulate_principal_policy(
|
|
21
|
+
self, context: RequestContext, request: SimulatePrincipalPolicyRequest
|
|
22
|
+
) -> SimulatePolicyResponse:
|
|
23
|
+
"""
|
|
24
|
+
Simulate principal policy
|
|
25
|
+
:param request: SimulatePrincipalPolicyRequest
|
|
26
|
+
:param context: RequestContext
|
|
27
|
+
:return: SimulatePrincipalResponse
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BasicIAMPolicySimulator(IAMPolicySimulator):
|
|
33
|
+
def simulate_principal_policy(
|
|
34
|
+
self,
|
|
35
|
+
context: RequestContext,
|
|
36
|
+
request: SimulatePrincipalPolicyRequest,
|
|
37
|
+
) -> SimulatePolicyResponse:
|
|
38
|
+
backend = self.get_iam_backend(context)
|
|
39
|
+
policies = self.get_policies_from_principal(backend, request.get("PolicySourceArn"))
|
|
40
|
+
|
|
41
|
+
def _get_statements_from_policy_list(_policies: list[str]):
|
|
42
|
+
statements = []
|
|
43
|
+
for policy_str in _policies:
|
|
44
|
+
policy_dict = json.loads(policy_str)
|
|
45
|
+
if isinstance(policy_dict["Statement"], list):
|
|
46
|
+
statements.extend(policy_dict["Statement"])
|
|
47
|
+
else:
|
|
48
|
+
statements.append(policy_dict["Statement"])
|
|
49
|
+
return statements
|
|
50
|
+
|
|
51
|
+
policy_statements = _get_statements_from_policy_list(policies)
|
|
52
|
+
|
|
53
|
+
evaluations = [
|
|
54
|
+
self.build_evaluation_result(action_name, resource_arn, policy_statements)
|
|
55
|
+
for action_name in request.get("ActionNames")
|
|
56
|
+
for resource_arn in request.get("ResourceArns")
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
response = SimulatePolicyResponse()
|
|
60
|
+
response["IsTruncated"] = False
|
|
61
|
+
response["EvaluationResults"] = evaluations
|
|
62
|
+
|
|
63
|
+
return response
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def build_evaluation_result(
|
|
67
|
+
action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: list[dict]
|
|
68
|
+
) -> EvaluationResult:
|
|
69
|
+
eval_res = EvaluationResult()
|
|
70
|
+
eval_res["EvalActionName"] = action_name
|
|
71
|
+
eval_res["EvalResourceName"] = resource_name
|
|
72
|
+
eval_res["EvalDecision"] = PolicyEvaluationDecisionType.explicitDeny
|
|
73
|
+
for statement in policy_statements:
|
|
74
|
+
# TODO Implement evaluation logic here
|
|
75
|
+
if (
|
|
76
|
+
action_name in statement["Action"]
|
|
77
|
+
and resource_name in statement["Resource"]
|
|
78
|
+
and statement["Effect"] == "Allow"
|
|
79
|
+
):
|
|
80
|
+
eval_res["EvalDecision"] = PolicyEvaluationDecisionType.allowed
|
|
81
|
+
eval_res["MatchedStatements"] = [] # TODO: add support for statement compilation.
|
|
82
|
+
return eval_res
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def get_iam_backend(context: RequestContext) -> IAMBackend:
|
|
86
|
+
return iam_backends[context.account_id][context.partition]
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def get_policies_from_principal(backend: IAMBackend, principal_arn: str) -> list[dict]:
|
|
90
|
+
policies = []
|
|
91
|
+
if ":role" in principal_arn:
|
|
92
|
+
role_name = principal_arn.split("/")[-1]
|
|
93
|
+
|
|
94
|
+
policies.append(backend.get_role(role_name=role_name).assume_role_policy_document)
|
|
95
|
+
|
|
96
|
+
policy_names = backend.list_role_policies(role_name=role_name)
|
|
97
|
+
policies.extend(
|
|
98
|
+
[
|
|
99
|
+
backend.get_role_policy(role_name=role_name, policy_name=policy_name)[1]
|
|
100
|
+
for policy_name in policy_names
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
attached_policies, _ = backend.list_attached_role_policies(role_name=role_name)
|
|
105
|
+
policies.extend([policy.document for policy in attached_policies])
|
|
106
|
+
|
|
107
|
+
if ":group" in principal_arn:
|
|
108
|
+
group_name = principal_arn.split("/")[-1]
|
|
109
|
+
policy_names = backend.list_group_policies(group_name=group_name)
|
|
110
|
+
policies.extend(
|
|
111
|
+
[
|
|
112
|
+
backend.get_group_policy(group_name=group_name, policy_name=policy_name)[1]
|
|
113
|
+
for policy_name in policy_names
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
attached_policies, _ = backend.list_attached_group_policies(group_name=group_name)
|
|
118
|
+
policies.extend([policy.document for policy in attached_policies])
|
|
119
|
+
|
|
120
|
+
if ":user" in principal_arn:
|
|
121
|
+
user_name = principal_arn.split("/")[-1]
|
|
122
|
+
policy_names = backend.list_user_policies(user_name=user_name)
|
|
123
|
+
policies.extend(
|
|
124
|
+
[
|
|
125
|
+
backend.get_user_policy(user_name=user_name, policy_name=policy_name)[1]
|
|
126
|
+
for policy_name in policy_names
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
attached_policies, _ = backend.list_attached_user_policies(user_name=user_name)
|
|
131
|
+
policies.extend([policy.document for policy in attached_policies])
|
|
132
|
+
|
|
133
|
+
return policies
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
|
|
3
|
-
from localstack.aws.api.kinesis import
|
|
4
|
-
|
|
3
|
+
from localstack.aws.api.kinesis import (
|
|
4
|
+
ConsumerDescription,
|
|
5
|
+
MetricsName,
|
|
6
|
+
Policy,
|
|
7
|
+
ResourceARN,
|
|
8
|
+
StreamName,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stores import (
|
|
11
|
+
AccountRegionBundle,
|
|
12
|
+
BaseStore,
|
|
13
|
+
CrossAccountAttribute,
|
|
14
|
+
LocalAttribute,
|
|
15
|
+
)
|
|
5
16
|
|
|
6
17
|
|
|
7
18
|
class KinesisStore(BaseStore):
|
|
@@ -13,5 +24,7 @@ class KinesisStore(BaseStore):
|
|
|
13
24
|
default=lambda: defaultdict(set)
|
|
14
25
|
)
|
|
15
26
|
|
|
27
|
+
resource_policies: dict[ResourceARN, Policy] = CrossAccountAttribute(default=dict)
|
|
28
|
+
|
|
16
29
|
|
|
17
30
|
kinesis_stores = AccountRegionBundle("kinesis", KinesisStore)
|
|
@@ -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):
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
import os
|
|
4
|
+
import re
|
|
3
5
|
import time
|
|
4
6
|
from random import random
|
|
5
7
|
|
|
@@ -8,14 +10,18 @@ from localstack.aws.api import RequestContext
|
|
|
8
10
|
from localstack.aws.api.kinesis import (
|
|
9
11
|
ConsumerARN,
|
|
10
12
|
Data,
|
|
13
|
+
GetResourcePolicyOutput,
|
|
11
14
|
HashKey,
|
|
12
15
|
KinesisApi,
|
|
13
16
|
PartitionKey,
|
|
17
|
+
Policy,
|
|
14
18
|
ProvisionedThroughputExceededException,
|
|
15
19
|
PutRecordOutput,
|
|
16
20
|
PutRecordsOutput,
|
|
17
21
|
PutRecordsRequestEntryList,
|
|
18
22
|
PutRecordsResultEntry,
|
|
23
|
+
ResourceARN,
|
|
24
|
+
ResourceNotFoundException,
|
|
19
25
|
SequenceNumber,
|
|
20
26
|
ShardId,
|
|
21
27
|
StartingPosition,
|
|
@@ -24,6 +30,7 @@ from localstack.aws.api.kinesis import (
|
|
|
24
30
|
SubscribeToShardEvent,
|
|
25
31
|
SubscribeToShardEventStream,
|
|
26
32
|
SubscribeToShardOutput,
|
|
33
|
+
ValidationException,
|
|
27
34
|
)
|
|
28
35
|
from localstack.aws.connect import connect_to
|
|
29
36
|
from localstack.constants import LOCALHOST
|
|
@@ -39,6 +46,13 @@ LOG = logging.getLogger(__name__)
|
|
|
39
46
|
MAX_SUBSCRIPTION_SECONDS = 300
|
|
40
47
|
SERVER_STARTUP_TIMEOUT = 120
|
|
41
48
|
|
|
49
|
+
DATA_STREAM_ARN_REGEX = re.compile(
|
|
50
|
+
r"^arn:aws(?:-[a-z]+)*:kinesis:[a-z0-9-]+:\d{12}:stream\/[a-zA-Z0-9_.\-]+$"
|
|
51
|
+
)
|
|
52
|
+
CONSUMER_ARN_REGEX = re.compile(
|
|
53
|
+
r"^arn:aws(?:-[a-z]+)*:kinesis:[a-z0-9-]+:\d{12}:stream\/[a-zA-Z0-9_.\-]+\/consumer\/[a-zA-Z0-9_.\-]+:\d+$"
|
|
54
|
+
)
|
|
55
|
+
|
|
42
56
|
|
|
43
57
|
def find_stream_for_consumer(consumer_arn):
|
|
44
58
|
account_id = extract_account_id_from_arn(consumer_arn)
|
|
@@ -52,6 +66,11 @@ def find_stream_for_consumer(consumer_arn):
|
|
|
52
66
|
raise Exception(f"Unable to find stream for stream consumer {consumer_arn}")
|
|
53
67
|
|
|
54
68
|
|
|
69
|
+
def is_valid_kinesis_arn(resource_arn: ResourceARN) -> bool:
|
|
70
|
+
"""Check if the provided ARN is a valid Kinesis ARN."""
|
|
71
|
+
return bool(CONSUMER_ARN_REGEX.match(resource_arn) or DATA_STREAM_ARN_REGEX.match(resource_arn))
|
|
72
|
+
|
|
73
|
+
|
|
55
74
|
class KinesisProvider(KinesisApi, ServiceLifecycleHook):
|
|
56
75
|
server_manager: KinesisServerManager
|
|
57
76
|
|
|
@@ -81,6 +100,64 @@ class KinesisProvider(KinesisApi, ServiceLifecycleHook):
|
|
|
81
100
|
def get_store(account_id: str, region_name: str) -> KinesisStore:
|
|
82
101
|
return kinesis_stores[account_id][region_name]
|
|
83
102
|
|
|
103
|
+
def put_resource_policy(
|
|
104
|
+
self,
|
|
105
|
+
context: RequestContext,
|
|
106
|
+
resource_arn: ResourceARN,
|
|
107
|
+
policy: Policy,
|
|
108
|
+
**kwargs,
|
|
109
|
+
) -> None:
|
|
110
|
+
if not is_valid_kinesis_arn(resource_arn):
|
|
111
|
+
raise ValidationException(f"invalid kinesis arn {resource_arn}")
|
|
112
|
+
|
|
113
|
+
kinesis = connect_to(
|
|
114
|
+
aws_access_key_id=context.account_id, region_name=context.region
|
|
115
|
+
).kinesis
|
|
116
|
+
try:
|
|
117
|
+
kinesis.describe_stream_summary(StreamARN=resource_arn)
|
|
118
|
+
except kinesis.exceptions.ResourceNotFoundException:
|
|
119
|
+
raise ResourceNotFoundException(f"Stream with ARN {resource_arn} not found")
|
|
120
|
+
|
|
121
|
+
store = self.get_store(context.account_id, context.region)
|
|
122
|
+
store.resource_policies[resource_arn] = policy
|
|
123
|
+
|
|
124
|
+
def get_resource_policy(
|
|
125
|
+
self,
|
|
126
|
+
context: RequestContext,
|
|
127
|
+
resource_arn: ResourceARN,
|
|
128
|
+
**kwargs,
|
|
129
|
+
) -> GetResourcePolicyOutput:
|
|
130
|
+
if not is_valid_kinesis_arn(resource_arn):
|
|
131
|
+
raise ValidationException(f"invalid kinesis arn {resource_arn}")
|
|
132
|
+
|
|
133
|
+
kinesis = connect_to(
|
|
134
|
+
aws_access_key_id=context.account_id, region_name=context.region
|
|
135
|
+
).kinesis
|
|
136
|
+
try:
|
|
137
|
+
kinesis.describe_stream_summary(StreamARN=resource_arn)
|
|
138
|
+
except kinesis.exceptions.ResourceNotFoundException:
|
|
139
|
+
raise ResourceNotFoundException(f"Stream with ARN {resource_arn} not found")
|
|
140
|
+
|
|
141
|
+
store = self.get_store(context.account_id, context.region)
|
|
142
|
+
policy = store.resource_policies.get(resource_arn, json.dumps({}))
|
|
143
|
+
return GetResourcePolicyOutput(Policy=policy)
|
|
144
|
+
|
|
145
|
+
def delete_resource_policy(
|
|
146
|
+
self,
|
|
147
|
+
context: RequestContext,
|
|
148
|
+
resource_arn: ResourceARN,
|
|
149
|
+
**kwargs,
|
|
150
|
+
) -> None:
|
|
151
|
+
if not is_valid_kinesis_arn(resource_arn):
|
|
152
|
+
raise ValidationException(f"invalid kinesis arn {resource_arn}")
|
|
153
|
+
|
|
154
|
+
store = self.get_store(context.account_id, context.region)
|
|
155
|
+
if resource_arn not in store.resource_policies:
|
|
156
|
+
raise ResourceNotFoundException(
|
|
157
|
+
f"No resource policy found for resource ARN {resource_arn}"
|
|
158
|
+
)
|
|
159
|
+
del store.resource_policies[resource_arn]
|
|
160
|
+
|
|
84
161
|
def subscribe_to_shard(
|
|
85
162
|
self,
|
|
86
163
|
context: RequestContext,
|
|
@@ -173,6 +173,7 @@ class KmsCryptoKey:
|
|
|
173
173
|
public_key: bytes | None
|
|
174
174
|
private_key: bytes | None
|
|
175
175
|
key_material: bytes
|
|
176
|
+
pending_key_material: bytes | None
|
|
176
177
|
key_spec: str
|
|
177
178
|
|
|
178
179
|
@staticmethod
|
|
@@ -217,6 +218,7 @@ class KmsCryptoKey:
|
|
|
217
218
|
def __init__(self, key_spec: str, key_material: bytes | None = None):
|
|
218
219
|
self.private_key = None
|
|
219
220
|
self.public_key = None
|
|
221
|
+
self.pending_key_material = None
|
|
220
222
|
# Technically, key_material, being a symmetric encryption key, is only relevant for
|
|
221
223
|
# key_spec == SYMMETRIC_DEFAULT.
|
|
222
224
|
# But LocalStack uses symmetric encryption with this key_material even for other specs. Asymmetric keys are
|
|
@@ -248,8 +250,9 @@ class KmsCryptoKey:
|
|
|
248
250
|
self._serialize_key(key)
|
|
249
251
|
|
|
250
252
|
def load_key_material(self, material: bytes):
|
|
251
|
-
if self.key_spec
|
|
252
|
-
|
|
253
|
+
if self.key_spec == KeySpec.SYMMETRIC_DEFAULT:
|
|
254
|
+
self.pending_key_material = material
|
|
255
|
+
elif self.key_spec in [
|
|
253
256
|
KeySpec.HMAC_224,
|
|
254
257
|
KeySpec.HMAC_256,
|
|
255
258
|
KeySpec.HMAC_384,
|
|
@@ -323,9 +326,28 @@ class KmsKey:
|
|
|
323
326
|
# remove the _custom_key_material_ tag from the tags to not readily expose the custom key material
|
|
324
327
|
del self.tags[TAG_KEY_CUSTOM_KEY_MATERIAL]
|
|
325
328
|
self.crypto_key = KmsCryptoKey(self.metadata.get("KeySpec"), custom_key_material)
|
|
329
|
+
self._internal_key_id = uuid.uuid4()
|
|
330
|
+
|
|
331
|
+
# The KMS implementation always provides a crypto key with key material which doesn't suit scenarios where a
|
|
332
|
+
# KMS Key may have no key material e.g. for external keys. Don't expose the CurrentKeyMaterialId in those cases.
|
|
333
|
+
if custom_key_material or (
|
|
334
|
+
self.metadata["Origin"] == "AWS_KMS"
|
|
335
|
+
and self.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT
|
|
336
|
+
):
|
|
337
|
+
self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id(
|
|
338
|
+
self.crypto_key.key_material
|
|
339
|
+
)
|
|
340
|
+
|
|
326
341
|
self.rotation_period_in_days = 365
|
|
327
342
|
self.next_rotation_date = None
|
|
328
343
|
|
|
344
|
+
def generate_key_material_id(self, key_material: bytes) -> str:
|
|
345
|
+
# The KeyMaterialId depends on the key material and the KeyId. Use an internal ID to prevent brute forcing
|
|
346
|
+
# the value of the key material from the public KeyId and KeyMaterialId.
|
|
347
|
+
# https://docs.aws.amazon.com/kms/latest/APIReference/API_ImportKeyMaterial.html
|
|
348
|
+
key_material_id_hex = uuid.uuid5(self._internal_key_id, key_material).hex
|
|
349
|
+
return str(key_material_id_hex) * 2
|
|
350
|
+
|
|
329
351
|
def calculate_and_set_arn(self, account_id, region):
|
|
330
352
|
self.metadata["Arn"] = kms_key_arn(self.metadata.get("KeyId"), account_id, region)
|
|
331
353
|
|
|
@@ -746,8 +768,16 @@ class KmsKey:
|
|
|
746
768
|
f"The on-demand rotations limit has been reached for the given keyId. "
|
|
747
769
|
f"No more on-demand rotations can be performed for this key: {self.metadata['Arn']}"
|
|
748
770
|
)
|
|
749
|
-
self.
|
|
750
|
-
|
|
771
|
+
current_key_material = self.crypto_key.key_material
|
|
772
|
+
pending_key_material = self.crypto_key.pending_key_material
|
|
773
|
+
|
|
774
|
+
self.previous_keys.append(current_key_material)
|
|
775
|
+
|
|
776
|
+
# If there is no pending material stored on the key, then key material will be generated.
|
|
777
|
+
self.crypto_key = KmsCryptoKey(KeySpec.SYMMETRIC_DEFAULT, pending_key_material)
|
|
778
|
+
self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id(
|
|
779
|
+
self.crypto_key.key_material
|
|
780
|
+
)
|
|
751
781
|
|
|
752
782
|
|
|
753
783
|
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 (
|
|
@@ -85,6 +88,7 @@ from localstack.aws.api.kms import (
|
|
|
85
88
|
MacAlgorithmSpec,
|
|
86
89
|
MarkerType,
|
|
87
90
|
MultiRegionKey,
|
|
91
|
+
MultiRegionKeyType,
|
|
88
92
|
NotFoundException,
|
|
89
93
|
NullableBooleanType,
|
|
90
94
|
OriginType,
|
|
@@ -137,6 +141,7 @@ from localstack.services.plugins import ServiceLifecycleHook
|
|
|
137
141
|
from localstack.utils.aws.arns import get_partition, kms_alias_arn, parse_arn
|
|
138
142
|
from localstack.utils.collections import PaginatedList
|
|
139
143
|
from localstack.utils.common import select_attributes
|
|
144
|
+
from localstack.utils.crypto import pkcs7_envelope_encrypt
|
|
140
145
|
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
141
146
|
|
|
142
147
|
LOG = logging.getLogger(__name__)
|
|
@@ -490,26 +495,39 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
490
495
|
self, context: RequestContext, request: ReplicateKeyRequest
|
|
491
496
|
) -> ReplicateKeyResponse:
|
|
492
497
|
account_id = context.account_id
|
|
493
|
-
|
|
494
|
-
key_id =
|
|
495
|
-
|
|
498
|
+
primary_key = self._get_kms_key(account_id, context.region, request.get("KeyId"))
|
|
499
|
+
key_id = primary_key.metadata.get("KeyId")
|
|
500
|
+
key_arn = primary_key.metadata.get("Arn")
|
|
501
|
+
if not primary_key.metadata.get("MultiRegion"):
|
|
496
502
|
raise UnsupportedOperationException(
|
|
497
503
|
f"Unable to replicate a non-MultiRegion key {key_id}"
|
|
498
504
|
)
|
|
499
505
|
replica_region = request.get("ReplicaRegion")
|
|
500
506
|
replicate_to_store = kms_stores[account_id][replica_region]
|
|
507
|
+
|
|
508
|
+
if (
|
|
509
|
+
primary_key.metadata.get("MultiRegionConfiguration", {}).get("MultiRegionKeyType")
|
|
510
|
+
!= MultiRegionKeyType.PRIMARY
|
|
511
|
+
):
|
|
512
|
+
raise UnsupportedOperationException(f"{key_arn} is not a multi-region primary key.")
|
|
513
|
+
|
|
501
514
|
if key_id in replicate_to_store.keys:
|
|
502
515
|
raise AlreadyExistsException(
|
|
503
516
|
f"Unable to replicate key {key_id} to region {replica_region}, as the key "
|
|
504
517
|
f"already exist there"
|
|
505
518
|
)
|
|
506
|
-
replica_key = copy.deepcopy(
|
|
519
|
+
replica_key = copy.deepcopy(primary_key)
|
|
507
520
|
replica_key.replicate_metadata(request, account_id, replica_region)
|
|
508
521
|
replicate_to_store.keys[key_id] = replica_key
|
|
509
522
|
|
|
510
|
-
self.update_primary_key_with_replica_keys(
|
|
523
|
+
self.update_primary_key_with_replica_keys(primary_key, replica_key, replica_region)
|
|
511
524
|
|
|
512
|
-
|
|
525
|
+
# CurrentKeyMaterialId is not returned in the ReplicaKeyMetadata. May be due to not being evaluated until
|
|
526
|
+
# the key has been successfully replicated as it does not show up in DescribeKey immediately either.
|
|
527
|
+
replica_key_metadata_response = copy.deepcopy(replica_key.metadata)
|
|
528
|
+
replica_key_metadata_response.pop("CurrentKeyMaterialId", None)
|
|
529
|
+
|
|
530
|
+
return ReplicateKeyResponse(ReplicaKeyMetadata=replica_key_metadata_response)
|
|
513
531
|
|
|
514
532
|
@staticmethod
|
|
515
533
|
# Adds new multi region replica key to the primary key's metadata.
|
|
@@ -1066,6 +1084,25 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1066
1084
|
self._validate_key_for_encryption_decryption(context, key)
|
|
1067
1085
|
self._validate_key_state_not_pending_import(key)
|
|
1068
1086
|
|
|
1087
|
+
# Handle the recipient field. This is used by AWS Nitro to re-encrypt the plaintext to the key specified
|
|
1088
|
+
# by the enclave. Proper support for this will take significant work to figure out how to model enforcing
|
|
1089
|
+
# the attestation measurements; for now, if recipient is specified and has an attestation doc in it including
|
|
1090
|
+
# a public key where it's expected to be, we encrypt to that public key. This at least allows users to use
|
|
1091
|
+
# localstack as a drop-in replacement for AWS when testing without having to skip the secondary decryption
|
|
1092
|
+
# when using localstack.
|
|
1093
|
+
recipient_pubkey = None
|
|
1094
|
+
if recipient:
|
|
1095
|
+
attestation_document = recipient["AttestationDocument"]
|
|
1096
|
+
# We do all of this in a try/catch and warn if it fails so that if users are currently passing a nonsense
|
|
1097
|
+
# value we don't break it for them. In the future we could do a breaking change to require a valid attestation
|
|
1098
|
+
# (or at least one that contains the public key).
|
|
1099
|
+
try:
|
|
1100
|
+
recipient_pubkey = self._extract_attestation_pubkey(attestation_document)
|
|
1101
|
+
except Exception as e:
|
|
1102
|
+
logging.warning(
|
|
1103
|
+
"Unable to extract public key from non-empty attestation document: %s", e
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1069
1106
|
try:
|
|
1070
1107
|
# TODO: Extend the implementation to handle additional encryption/decryption scenarios
|
|
1071
1108
|
# beyond the current support for offline encryption and online decryption using RSA keys if key id exists in
|
|
@@ -1079,20 +1116,27 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1079
1116
|
plaintext = key.decrypt(ciphertext, encryption_context)
|
|
1080
1117
|
except InvalidTag:
|
|
1081
1118
|
raise InvalidCiphertextException()
|
|
1119
|
+
|
|
1082
1120
|
# For compatibility, we return EncryptionAlgorithm values expected from AWS. But LocalStack currently always
|
|
1083
1121
|
# encrypts with symmetric encryption no matter the key settings.
|
|
1084
1122
|
#
|
|
1085
1123
|
# We return a key ARN instead of KeyId despite the name of the parameter, as this is what AWS does and states
|
|
1086
1124
|
# in its docs.
|
|
1087
|
-
# TODO add support for "recipient"
|
|
1088
1125
|
# https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html#API_Decrypt_RequestSyntax
|
|
1089
1126
|
# TODO add support for "dry_run"
|
|
1090
|
-
|
|
1127
|
+
response = DecryptResponse(
|
|
1091
1128
|
KeyId=key.metadata.get("Arn"),
|
|
1092
|
-
Plaintext=plaintext,
|
|
1093
1129
|
EncryptionAlgorithm=encryption_algorithm,
|
|
1094
1130
|
)
|
|
1095
1131
|
|
|
1132
|
+
# Encrypt to the recipient pubkey if specified. Otherwise, return the actual plaintext
|
|
1133
|
+
if recipient_pubkey:
|
|
1134
|
+
response["CiphertextForRecipient"] = pkcs7_envelope_encrypt(plaintext, recipient_pubkey)
|
|
1135
|
+
else:
|
|
1136
|
+
response["Plaintext"] = plaintext
|
|
1137
|
+
|
|
1138
|
+
return response
|
|
1139
|
+
|
|
1096
1140
|
def get_parameters_for_import(
|
|
1097
1141
|
self,
|
|
1098
1142
|
context: RequestContext,
|
|
@@ -1167,13 +1211,10 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1167
1211
|
# TODO check if there was already a key imported for this kms key
|
|
1168
1212
|
# if so, it has to be identical. We cannot change keys by reimporting after deletion/expiry
|
|
1169
1213
|
key_material = self._decrypt_wrapped_key_material(import_state, encrypted_key_material)
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
key_to_import_material_to.metadata["ExpirationModel"] = (
|
|
1175
|
-
ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
1176
|
-
)
|
|
1214
|
+
key_material_id = key_to_import_material_to.generate_key_material_id(key_material)
|
|
1215
|
+
key_to_import_material_to.metadata["ExpirationModel"] = (
|
|
1216
|
+
expiration_model or ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
1217
|
+
)
|
|
1177
1218
|
if (
|
|
1178
1219
|
key_to_import_material_to.metadata["ExpirationModel"]
|
|
1179
1220
|
== ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
@@ -1182,12 +1223,42 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1182
1223
|
raise ValidationException(
|
|
1183
1224
|
"A validTo date must be set if the ExpirationModel is KEY_MATERIAL_EXPIRES"
|
|
1184
1225
|
)
|
|
1226
|
+
if existing_pending_material := key_to_import_material_to.crypto_key.pending_key_material:
|
|
1227
|
+
pending_key_material_id = key_to_import_material_to.generate_key_material_id(
|
|
1228
|
+
existing_pending_material
|
|
1229
|
+
)
|
|
1230
|
+
raise KMSInvalidStateException(
|
|
1231
|
+
f"New key material (id: {key_material_id}) cannot be imported into KMS key "
|
|
1232
|
+
f"{key_to_import_material_to.metadata['Arn']}, because another key material "
|
|
1233
|
+
f"(id: {pending_key_material_id}) is pending rotation."
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1185
1236
|
# TODO actually set validTo and make the key expire
|
|
1186
1237
|
key_to_import_material_to.metadata["Enabled"] = True
|
|
1187
1238
|
key_to_import_material_to.metadata["KeyState"] = KeyState.Enabled
|
|
1188
1239
|
key_to_import_material_to.crypto_key.load_key_material(key_material)
|
|
1189
1240
|
|
|
1190
|
-
|
|
1241
|
+
# KeyMaterialId / CurrentKeyMaterialId is only exposed for symmetric encryption keys.
|
|
1242
|
+
key_material_id_response = None
|
|
1243
|
+
if key_to_import_material_to.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT:
|
|
1244
|
+
key_material_id_response = key_to_import_material_to.generate_key_material_id(
|
|
1245
|
+
key_material
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
# If there is no CurrentKeyMaterialId, instantly promote the pending key material to the current.
|
|
1249
|
+
if key_to_import_material_to.metadata.get("CurrentKeyMaterialId") is None:
|
|
1250
|
+
key_to_import_material_to.metadata["CurrentKeyMaterialId"] = (
|
|
1251
|
+
key_material_id_response
|
|
1252
|
+
)
|
|
1253
|
+
key_to_import_material_to.crypto_key.key_material = (
|
|
1254
|
+
key_to_import_material_to.crypto_key.pending_key_material
|
|
1255
|
+
)
|
|
1256
|
+
key_to_import_material_to.crypto_key.pending_key_material = None
|
|
1257
|
+
|
|
1258
|
+
return ImportKeyMaterialResponse(
|
|
1259
|
+
KeyId=key_to_import_material_to.metadata["Arn"],
|
|
1260
|
+
KeyMaterialId=key_material_id_response,
|
|
1261
|
+
)
|
|
1191
1262
|
|
|
1192
1263
|
def delete_imported_key_material(
|
|
1193
1264
|
self,
|
|
@@ -1314,7 +1385,7 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1314
1385
|
key = self._get_kms_key(account_id, region_name, key_id, any_key_state_allowed=True)
|
|
1315
1386
|
|
|
1316
1387
|
response = GetKeyRotationStatusResponse(
|
|
1317
|
-
KeyId=
|
|
1388
|
+
KeyId=key.metadata["Arn"],
|
|
1318
1389
|
KeyRotationEnabled=key.is_key_rotation_enabled,
|
|
1319
1390
|
NextRotationDate=key.next_rotation_date,
|
|
1320
1391
|
)
|
|
@@ -1406,13 +1477,13 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1406
1477
|
|
|
1407
1478
|
if key.metadata["KeySpec"] != KeySpec.SYMMETRIC_DEFAULT:
|
|
1408
1479
|
raise UnsupportedOperationException()
|
|
1409
|
-
|
|
1410
|
-
|
|
1480
|
+
self._validate_key_state_not_pending_import(key)
|
|
1481
|
+
self._validate_external_key_has_pending_material(key)
|
|
1411
1482
|
|
|
1412
1483
|
key.rotate_key_on_demand()
|
|
1413
1484
|
|
|
1414
1485
|
return RotateKeyOnDemandResponse(
|
|
1415
|
-
KeyId=
|
|
1486
|
+
KeyId=key.metadata["Arn"],
|
|
1416
1487
|
)
|
|
1417
1488
|
|
|
1418
1489
|
@handler("TagResource", expand=False)
|
|
@@ -1489,6 +1560,12 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1489
1560
|
if key.metadata["KeyState"] == KeyState.PendingImport:
|
|
1490
1561
|
raise KMSInvalidStateException(f"{key.metadata['Arn']} is pending import.")
|
|
1491
1562
|
|
|
1563
|
+
def _validate_external_key_has_pending_material(self, key: KmsKey):
|
|
1564
|
+
if key.metadata["Origin"] == "EXTERNAL" and key.crypto_key.pending_key_material is None:
|
|
1565
|
+
raise KMSInvalidStateException(
|
|
1566
|
+
f"No available key material pending rotation for the key: {key.metadata['Arn']}."
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1492
1569
|
def _validate_key_for_encryption_decryption(self, context: RequestContext, key: KmsKey):
|
|
1493
1570
|
key_usage = key.metadata["KeyUsage"]
|
|
1494
1571
|
if key_usage != "ENCRYPT_DECRYPT":
|
|
@@ -1550,6 +1627,15 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1550
1627
|
f" constraint: [Member must satisfy enum value set: {VALID_OPERATIONS}]"
|
|
1551
1628
|
)
|
|
1552
1629
|
|
|
1630
|
+
def _extract_attestation_pubkey(self, attestation_document: bytes) -> RSAPublicKey:
|
|
1631
|
+
# The attestation document comes as a COSE (CBOR Object Signing and Encryption) object: the CBOR
|
|
1632
|
+
# attestation is signed and then the attestation and signature are again CBOR-encoded. For now
|
|
1633
|
+
# we don't bother validating the signature, though in the future we could.
|
|
1634
|
+
cose_document = cbor2_loads(attestation_document)
|
|
1635
|
+
attestation = cbor2_loads(cose_document[2])
|
|
1636
|
+
public_key_bytes = attestation["public_key"]
|
|
1637
|
+
return load_der_public_key(public_key_bytes)
|
|
1638
|
+
|
|
1553
1639
|
def _decrypt_wrapped_key_material(
|
|
1554
1640
|
self,
|
|
1555
1641
|
import_state: KeyImportState,
|
|
@@ -722,7 +722,9 @@ def validate_layer_runtimes_and_architectures(
|
|
|
722
722
|
validations.append(validation_msg)
|
|
723
723
|
|
|
724
724
|
if compatible_architectures and set(compatible_architectures).difference(ARCHITECTURES):
|
|
725
|
-
constraint =
|
|
725
|
+
constraint = (
|
|
726
|
+
"[Member must satisfy enum value set: [x86_64, arm64], Member must not be null]"
|
|
727
|
+
)
|
|
726
728
|
validation_msg = f"Value '[{', '.join(list(compatible_architectures))}]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: {constraint}"
|
|
727
729
|
validations.append(validation_msg)
|
|
728
730
|
|
|
@@ -19,13 +19,9 @@ from localstack.aws.api.sqs import (
|
|
|
19
19
|
String,
|
|
20
20
|
TagMap,
|
|
21
21
|
)
|
|
22
|
-
from localstack.services.sqs.models import SqsQueue, StandardQueue
|
|
23
|
-
from localstack.services.sqs.provider import
|
|
24
|
-
|
|
25
|
-
_create_message_attribute_hash,
|
|
26
|
-
to_sqs_api_message,
|
|
27
|
-
)
|
|
28
|
-
from localstack.services.sqs.utils import generate_message_id
|
|
22
|
+
from localstack.services.sqs.models import SqsQueue, StandardQueue, to_sqs_api_message
|
|
23
|
+
from localstack.services.sqs.provider import QueueUpdateWorker
|
|
24
|
+
from localstack.services.sqs.utils import create_message_attribute_hash, generate_message_id
|
|
29
25
|
from localstack.utils.objects import singleton_factory
|
|
30
26
|
from localstack.utils.strings import md5
|
|
31
27
|
from localstack.utils.time import now
|
|
@@ -189,7 +185,7 @@ class FakeSqsClient:
|
|
|
189
185
|
MD5OfBody=md5(MessageBody),
|
|
190
186
|
Body=MessageBody,
|
|
191
187
|
Attributes=self._create_message_attributes(MessageSystemAttributes),
|
|
192
|
-
MD5OfMessageAttributes=
|
|
188
|
+
MD5OfMessageAttributes=create_message_attribute_hash(MessageAttributes),
|
|
193
189
|
MessageAttributes=MessageAttributes,
|
|
194
190
|
)
|
|
195
191
|
queue_item = queue.put(
|
|
@@ -204,7 +200,7 @@ class FakeSqsClient:
|
|
|
204
200
|
"MD5OfMessageBody": message["MD5OfBody"],
|
|
205
201
|
"MD5OfMessageAttributes": message.get("MD5OfMessageAttributes"),
|
|
206
202
|
"SequenceNumber": queue_item.sequence_number,
|
|
207
|
-
"MD5OfMessageSystemAttributes":
|
|
203
|
+
"MD5OfMessageSystemAttributes": create_message_attribute_hash(MessageSystemAttributes),
|
|
208
204
|
}
|
|
209
205
|
|
|
210
206
|
|
|
@@ -12,7 +12,7 @@ from localstack.utils.platform import get_arch
|
|
|
12
12
|
"""Customized LocalStack version of the AWS Lambda Runtime Interface Emulator (RIE).
|
|
13
13
|
https://github.com/localstack/lambda-runtime-init/blob/localstack/README-LOCALSTACK.md
|
|
14
14
|
"""
|
|
15
|
-
LAMBDA_RUNTIME_DEFAULT_VERSION = "v0.1.
|
|
15
|
+
LAMBDA_RUNTIME_DEFAULT_VERSION = "v0.1.37-pre"
|
|
16
16
|
LAMBDA_RUNTIME_VERSION = config.LAMBDA_INIT_RELEASE_VERSION or LAMBDA_RUNTIME_DEFAULT_VERSION
|
|
17
17
|
LAMBDA_RUNTIME_INIT_URL = "https://github.com/localstack/lambda-runtime-init/releases/download/{version}/aws-lambda-rie-{arch}"
|
|
18
18
|
|