10000 APIGW: add validation for AWS ARN in PutIntegration (#12324) · localstack/localstack@c596ce0 · GitHub
[go: up one dir, main page]

Skip to content

Commit c596ce0

Browse files
authored
APIGW: add validation for AWS ARN in PutIntegration (#12324)
1 parent 0c50584 commit c596ce0

File tree

6 files changed

+168
-6
lines changed

6 files changed

+168
-6
lines changed

localstack-core/localstack/services/apigateway/legacy/provider.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
from localstack.services.edge import ROUTER
123123
from localstack.services.moto import call_moto, call_moto_with_request
124124
from localstack.services.plugins import ServiceLifecycleHook
125-
from localstack.utils.aws.arns import get_partition
125+
from localstack.utils.aws.arns import InvalidArnException, get_partition, parse_arn
126126
from localstack.utils.collections import (
127127
DelSafeDict,
128128
PaginatedList,
@@ -1937,13 +1937,32 @@ def put_integration(
19371937
f"Member must satisfy enum value set: [HTTP, MOCK, AWS_PROXY, HTTP_PROXY, AWS]",
19381938
)
19391939

1940-
elif integration_type == IntegrationType.AWS_PROXY:
1941-
integration_uri = request.get("uri") or ""
1942-
if ":lambda:" not in integration_uri and ":firehose:" not in integration_uri:
1940+
elif integration_type in (IntegrationType.AWS_PROXY, IntegrationType.AWS):
1941+
if not request.get("integrationHttpMethod"):
1942+
raise BadRequestException("Enumeration value for HttpMethod must be non-empty")
1943+
if not (integration_uri := request.get("uri") or "").startswith("arn:"):
1944+
raise BadRequestException("Invalid ARN specified in the request")
1945+
1946+
try:
1947+
parsed_arn = parse_arn(integration_uri)
1948+
except InvalidArnException:
1949+
raise BadRequestException("Invalid ARN specified in the request")
1950+
1951+
if not any(
1952+
parsed_arn["resource"].startswith(action_type) for action_type in ("path", "action")
1953+
):
1954+
raise BadRequestException("AWS ARN for integration must contain path or action")
1955+
1956+
if integration_type == IntegrationType.AWS_PROXY and (
1957+
parsed_arn["account"] != "lambda"
1958+
or not parsed_arn["resource"].startswith("path/2015-03-31/functions/")
1959+
):
1960+
# the Firehose message is misleading, this is not implemented in AWS
19431961
raise BadRequestException(
19441962
"Integrations of type 'AWS_PROXY' currently only supports "
19451963
"Lambda function and Firehose stream invocations."
19461964
)
1965+
19471966
moto_rest_api = get_moto_rest_api(context=context, rest_api_id=request.get("restApiId"))
19481967
resource = moto_rest_api.resources.get(request.get("resourceId"))
19491968
if not resource:

tests/aws/services/apigateway/test_apigateway_api.snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3509,7 +3509,7 @@
35093509
}
35103510
},
35113511
"tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": {
3512-
"recorded-date": "21-08-2024, 15:09:28",
3512+
"recorded-date": "03-03-2025, 14:27:24",
35133513
"recorded-content": {
35143514
"put-integration-wrong-method": {
35153515
"Error": {

tests/aws/services/apigateway/test_apigateway_api.validation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"last_validated_date": "2024-12-12T10:46:41+00:00"
133133
},
134134
"tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": {
135-
"last_validated_date": "2024-08-21T15:09:28+00:00"
135+
"last_validated_date": "2025-03-03T14:27:24+00:00"
136136
},
137137
"tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": {
138138
"last_validated_date": "2024-04-15T20:48:47+00:00"

tests/aws/services/apigateway/test_apigateway_lambda.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,86 @@ def invoke_api_with_multi_value_header(url):
292292
snapshot.match("invocation-hardcoded", response_hardcoded.json())
293293

294294

295+
@markers.aws.validated
296+
def test_put_integration_aws_proxy_uri(
297+
aws_client,
298+
create_rest_apigw,
299+
create_lambda_function,
300+
create_role_with_policy,
301+
snapshot,
302+
region_name,
303+
):
304+
api_id, _, root_resource_id = create_rest_apigw(
305+
name=f"test-api-{short_uid()}",
306+
description="APIGW test PutIntegration AWS_PROXY URI",
307+
)
308+
function_name = f"function-{short_uid()}"
309+
310+
# create lambda
311+
create_function_response = create_lambda_function(
312+
func_name=function_name,
313+
handler_file=TEST_LAMBDA_AWS_PROXY,
314+
handler="lambda_aws_proxy.handler",
315+
runtime=Runtime.python3_12,
316+
)
317+
# create invocation role
318+
_, role_arn = create_role_with_policy(
319+
"Allow", "lambda:InvokeFunction", json.dumps(APIGATEWAY_ASSUME_ROLE_POLICY), "*"
320+
)
321+
lambda_arn = create_function_response["CreateFunctionResponse"]["FunctionArn"]
322+
323+
aws_client.apigateway.put_method(
324+
restApiId=api_id,
325+
resourceId=root_resource_id,
326+
httpMethod="ANY",
327+
authorizationType="NONE",
328+
)
329+
330+
default_params = {
331+
"restApiId": api_id,
332+
"resourceId": root_resource_id,
333+
"httpMethod": "ANY",
334+
"type": "AWS_PROXY",
335+
"integrationHttpMethod": "POST",
336+
"credentials": role_arn,
337+
}
338+
339+
with pytest.raises(ClientError) as e:
340+
aws_client.apigateway.put_integration(
341+
**default_params,
342+
uri=lambda_arn,
343+
)
344+
snapshot.match("put-integration-lambda-uri", e.value.response)
345+
346+
with pytest.raises(ClientError) as e:
347+
aws_client.apigateway.put_integration(
348+
**default_params,
349+
uri=f"bad-arn:lambda:path/2015-03-31/functions/{lambda_arn}/invocations",
350+
)
351+
snapshot.match("put-integration-wrong-arn", e.value.response)
352+
353+
with pytest.raises(ClientError) as e:
354+
aws_client.apigateway.put_integration(
355+
**default_params,
356+
uri=f"arn:aws:apigateway:{region_name}:lambda:test/2015-03-31/functions/{lambda_arn}/invocations",
357+
)
358+
snapshot.match("put-integration-wrong-type", e.value.response)
359+
360+
with pytest.raises(ClientError) as e:
361+
aws_client.apigateway.put_integration(
362+
**default_params,
363+
uri=f"arn:aws:apigateway:{region_name}:firehose:path/2015-03-31/functions/{lambda_arn}/invocations",
364+
)
365+
snapshot.match("put-integration-wrong-firehose", e.value.response)
366+
367+
with pytest.raises(ClientError) as e:
368+
aws_client.apigateway.put_integration(
369+
**default_params,
370+
uri=f"arn:aws:apigateway:{region_name}:lambda:path/random/value/{lambda_arn}/invocations",
371+
)
372+
snapshot.match("put-integration-bad-lambda-arn", e.value.response)
373+
374< 10000 code class="diff-text syntax-highlighted-line addition">+
295375
@markers.aws.validated
296376
def test_lambda_aws_proxy_integration_non_post_method(
297377
create_rest_apigw, create_lambda_function, create_role_with_policy, snapshot, aws_client

tests/aws/services/apigateway/test_apigateway_lambda.snapshot.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,5 +1795,65 @@
17951795
"content": ""
17961796
}
17971797
}
1798+
},
1799+
"tests/aws/services/apigateway/test_apigateway_lambda.py::test_put_integration_aws_proxy_uri": {
1800+
"recorded-date": "03-03-2025, 12:58:39",
1801+
"recorded-content": {
1802+
"put-integration-lambda-uri": {
1803+
"Error": {
1804+
"Code": "BadRequestException",
1805+
"Message": "AWS ARN for integration must contain path or action"
1806+
},
1807+
"message": "AWS ARN for integration must contain path or action",
1808+
"ResponseMetadata": {
1809+
"HTTPHeaders": {},
1810+
"HTTPStatusCode": 400
1811+
}
1812+
},
1813+
"put-integration-wrong-arn": {
1814+
"Error": {
1815+
"Code": "BadRequestException",
1816+
"Message": "Invalid ARN specified in the request"
1817+
},
1818+
"message": "Invalid ARN specified in the request",
1819+
"ResponseMetadata": {
1820+
"HTTPHeaders": {},
1821+
"HTTPStatusCode": 400
1822+
}
1823+
},
1824+
"put-integration-wrong-type": {
1825+
"Error": {
1826+
"Code": "BadRequestException",
1827+
"Message": "AWS ARN for integration must contain path or action"
1828+
},
1829+
"message": "AWS ARN for integration must contain path or action",
1830+
"ResponseMetadata": {
1831+
"HTTPHeaders": {},
1832+
"HTTPStatusCode": 400
1833+
}
1834+
},
1835+
"put-integration-wrong-firehose": {
1836+
"Error": {
1837+
"Code": "BadRequestException",
1838+
"Message": "Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations."
1839+
},
1840+
"message": "Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations.",
1841+
"ResponseMetadata": {
1842+
"HTTPHeaders": {},
1843+
"HTTPStatusCode": 400
1844+
}
1845+
},
1846+
"put-integration-bad-lambda-arn": {
1847+
"Error": {
1848+
"Code": "BadRequestException",
1849+
"Message": "Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations."
1850+
},
1851+
"message": "Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations.",
1852+
"ResponseMetadata": {
1853+
"HTTPHeaders": {},
1854+
"HTTPStatusCode": 400
1855+
}
1856+
}
1857+
}
17981858
}
17991859
}

tests/aws/services/apigateway/test_apigateway_lambda.validation.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
},
3232
"tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_selection_patterns": {
3333
"last_validated_date": "2023-09-05T19:54:21+00:00"
34+
},
35+
"tests/aws/services/apigateway/test_apigateway_lambda.py::test_put_integration_aws_proxy_uri": {
36+
"last_validated_date": "2025-03-03T12:58:39+00:00"
3437
}
3538
}

0 commit comments

Comments
 (0)
0