Skip to content

Commit eca35ab

Browse files
committed
tmp
1 parent 6748e0e commit eca35ab

File tree

8 files changed

+120
-13
lines changed

8 files changed

+120
-13
lines changed

localstack-core/localstack/aws/api/cloudformation/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,7 @@ class RollbackConfiguration(TypedDict, total=False):
10701070
class Parameter(TypedDict, total=False):
10711071
ParameterKey: Optional[ParameterKey]
10721072
ParameterValue: Optional[ParameterValue]
1073+
NoEcho: Optional[NoEcho]
10731074
UsePreviousValue: Optional[UsePreviousValue]
10741075
ResolvedValue: Optional[ParameterValue]
10751076

localstack-core/localstack/services/cloudformation/engine/entities.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from localstack.services.cloudformation.engine.parameters import (
66
StackParameter,
77
convert_stack_parameters_to_list,
8+
mask_noecho,
89
strip_parameter_type,
910
)
1011
from localstack.utils.aws import arns
@@ -167,7 +168,9 @@ def describe_details(self):
167168
result["Outputs"] = outputs
168169
stack_parameters = convert_stack_parameters_to_list(self.resolved_parameters)
169170
if stack_parameters:
170-
result["Parameters"] = [strip_parameter_type(sp) for sp in stack_parameters]
171+
result["Parameters"] = [
172+
mask_noecho(strip_parameter_type(sp)) for sp in stack_parameters
173+
]
171174
if not result.get("DriftInformation"):
172175
result["DriftInformation"] = {"StackDriftStatus": "NOT_CHECKED"}
173176
for attr in ["Tags", "NotificationARNs"]:

localstack-core/localstack/services/cloudformation/engine/parameters.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def extract_stack_parameter_declarations(template: dict) -> dict[str, ParameterD
4242
ParameterKey=param_key,
4343
DefaultValue=param.get("Default"),
4444
ParameterType=param.get("Type"),
45+
NoEcho=param.get("NoEcho", False),
4546
# TODO: test & implement rest here
46-
# NoEcho=?,
4747
# ParameterConstraints=?,
4848
# Description=?
4949
)
@@ -113,6 +113,7 @@ def resolve_parameters(
113113
else:
114114
resolved_param["ParameterValue"] = new_parameter["ParameterValue"]
115115

116+
resolved_param["NoEcho"] = pm.get("NoEcho", False)
116117
resolved_parameters[pm_key] = resolved_param
117118

118119
# Note that SSM parameters always need to be resolved anew here
@@ -152,6 +153,14 @@ def strip_parameter_type(in_param: StackParameter) -> Parameter:
152153
return result
153154

154155

156+
def mask_noecho(in_param: StackParameter) -> Parameter:
157+
result = in_param.copy()
158+
noecho = result.pop("NoEcho", False)
159+
if noecho:
160+
result["ParameterValue"] = "*" * len(result.get("ParameterValue", ""))
161+
return result
162+
163+
155164
def convert_stack_parameters_to_list(
156165
in_params: dict[str, StackParameter] | None,
157166
) -> list[StackParameter]:

localstack-core/localstack/services/cloudformation/provider.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
StackInstance,
9393
StackSet,
9494
)
95-
from localstack.services.cloudformation.engine.parameters import strip_parameter_type
95+
from localstack.services.cloudformation.engine.parameters import mask_noecho, strip_parameter_type
9696
from localstack.services.cloudformation.engine.resource_ordering import (
9797
NoResourceInStack,
9898
order_resources,
@@ -667,7 +667,8 @@ def create_change_set(
667667
case ChangeSetType.UPDATE:
668668
# add changeset to existing stack
669669
old_parameters = {
670-
k: strip_parameter_type(v) for k, v in stack.resolved_parameters.items()
670+
k: mask_noecho(strip_parameter_type(v))
671+
for k, v in stack.resolved_parameters.items()
671672
}
672673
case ChangeSetType.IMPORT:
673674
raise NotImplementedError() # TODO: implement importing resources
@@ -1016,7 +1017,7 @@ def validate_template(
10161017
TemplateParameter(
10171018
ParameterKey=k,
10181019
DefaultValue=v.get("Default", ""),
1019-
NoEcho=False,
1020+
NoEcho=v.get("NoEcho", False),
10201021
Description=v.get("Description", ""),
10211022
)
10221023
for k, v in valid_template.get("Parameters", {}).items()

tests/aws/services/cloudformation/api/test_templates.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ def test_get_template_summary(deploy_cfn_template, snapshot, aws_client):
3131
snapshot.match("template-summary", res)
3232

3333

34+
@markers.aws.validated
35+
def test_describe_stacks_noecho(deploy_cfn_template, aws_client, snapshot):
36+
snapshot.add_transformer(snapshot.transform.cloudformation_api())
37+
deployment = deploy_cfn_template(
38+
template_path=os.path.join(
39+
# This template has secret parameters that should be masked
40+
os.path.dirname(__file__),
41+
"../../../templates/cfn_noecho.yml",
42+
),
43+
parameters={"MySecretParameter": "SecretValue"},
44+
)
45+
res = aws_client.cloudformation.describe_stacks(StackName=deployment.stack_name)
46+
47+
snapshot.match("stacks", res)
48+
49+
3450
@markers.aws.validated
3551
@pytest.mark.parametrize("url_style", ["s3_url", "http_path", "http_host", "http_invalid"])
3652
def test_create_stack_from_s3_template_url(

tests/aws/services/cloudformation/api/test_templates.snapshot.json

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary": {
3-
"recorded-date": "24-05-2023, 15:05:00",
3+
"recorded-date": "22-11-2024, 10:56:47",
44
"recorded-content": {
55
"template-summary": {
66
"Metadata": "{'TopicName': 'sns-topic-simple'}",
@@ -25,7 +25,7 @@
2525
}
2626
},
2727
"tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": {
28-
"recorded-date": "11-10-2023, 00:03:44",
28+
"recorded-date": "22-11-2024, 10:56:49",
2929
"recorded-content": {
3030
"create-error": {
3131
"Error": {
@@ -41,7 +41,7 @@
4141
}
4242
},
4343
"tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": {
44-
"recorded-date": "11-10-2023, 00:03:53",
44+
"recorded-date": "22-11-2024, 10:56:50",
4545
"recorded-content": {
4646
"matching-topic": [
4747
{
@@ -51,7 +51,7 @@
5151
}
5252
},
5353
"tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": {
54-
"recorded-date": "11-10-2023, 00:04:02",
54+
"recorded-date": "22-11-2024, 10:56:51",
5555
"recorded-content": {
5656
"matching-topic": [
5757
{
@@ -61,7 +61,7 @@
6161
}
6262
},
6363
"tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": {
64-
"recorded-date": "11-10-2023, 00:04:04",
64+
"recorded-date": "22-11-2024, 10:56:51",
6565
"recorded-content": {
6666
"create-error": {
6767
"Error": {
@@ -77,14 +77,21 @@
7777
}
7878
},
7979
"tests/aws/services/cloudformation/api/test_templates.py::test_validate_template": {
80-
"recorded-date": "18-06-2024, 17:23:30",
80+
"recorded-date": "22-11-2024, 10:56:51",
8181
"recorded-content": {
8282
"validate-template": {
8383
"Parameters": [
8484
{
85+
"DefaultValue": "",
8586
"Description": "The EC2 Key Pair to allow SSH access to the instance",
8687
"NoEcho": false,
8788
"ParameterKey": "KeyExample"
89+
},
90+
{
91+
"DefaultValue": "",
92+
"Description": "Secret value here",
93+
"NoEcho": true,
94+
"ParameterKey": "SecretKeyExample"
8895
}
8996
],
9097
"ResponseMetadata": {
@@ -95,12 +102,12 @@
95102
}
96103
},
97104
"tests/aws/services/cloudformation/api/test_templates.py::test_validate_invalid_json_template_should_fail": {
98-
"recorded-date": "18-06-2024, 17:25:49",
105+
"recorded-date": "22-11-2024, 10:56:51",
99106
"recorded-content": {
100107
"validate-invalid-json": {
101108
"Error": {
102109
"Code": "ValidationError",
103-
"Message": "Template format error: JSON not well-formed. (line 1, column 25)",
110+
"Message": "Template Validation Error",
104111
"Type": "Sender"
105112
},
106113
"ResponseMetadata": {
@@ -109,5 +116,52 @@
109116
}
110117
}
111118
}
119+
},
120+
"tests/aws/services/cloudformation/api/test_templates.py::test_describe_stacks_noecho": {
121+
"recorded-date": "22-11-2024, 10:56:49",
122+
"recorded-content": {
123+
"stacks": {
124+
"Stacks": [
125+
{
126+
"Capabilities": [
127+
"CAPABILITY_AUTO_EXPAND",
128+
"CAPABILITY_IAM",
129+
"CAPABILITY_NAMED_IAM"
130+
],
131+
"ChangeSetId": "arn:<partition>:cloudformation:<region>:111111111111:changeSet/<resource:1>",
132+
"CreationTime": "datetime",
133+
"DisableRollback": false,
134+
"DriftInformation": {
135+
"StackDriftStatus": "NOT_CHECKED"
136+
},
137+
"EnableTerminationProtection": false,
138+
"LastUpdatedTime": "datetime",
139+
"NotificationARNs": [],
140+
"Outputs": [
141+
{
142+
"Description": "Secret value from parameter",
143+
"OutputKey": "SecretValue",
144+
"OutputValue": "SecretValue"
145+
}
146+
],
147+
"Parameters": [
148+
{
149+
"ParameterKey": "MySecretParameter",
150+
"ParameterValue": "***********"
151+
}
152+
],
153+
"RollbackConfiguration": {},
154+
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
155+
"StackName": "<stack-name:1>",
156+
"StackStatus": "CREATE_COMPLETE",
157+
"Tags": []
158+
}
159+
],
160+
"ResponseMetadata": {
161+
"HTTPHeaders": {},
162+
"HTTPStatusCode": 200
163+
}
164+
}
165+
}
112166
}
113167
}

tests/aws/templates/cfn_noecho.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
AWSTemplateFormatVersion: "2010-09-09"
2+
3+
Parameters:
4+
MySecretParameter:
5+
Type: String
6+
NoEcho: true
7+
Description: "Secret value here"
8+
9+
Resources:
10+
LocalBucket:
11+
Type: AWS::S3::Bucket
12+
Properties:
13+
BucketName: cfn-noecho-bucket
14+
15+
Outputs:
16+
SecretValue:
17+
Description: "Secret value from parameter"
18+
Value: !Ref MySecretParameter

tests/aws/templates/valid_template.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
"KeyExample": {
44
"Description": "The EC2 Key Pair to allow SSH access to the instance",
55
"Type": "AWS::EC2::KeyPair::KeyName"
6+
},
7+
"SecretKeyExample": {
8+
"Description": "Secret value here",
9+
"NoEcho": true,
10+
"Type": "String"
611
}
712
},
813
"Resources": {

0 commit comments

Comments
 (0)