8000 Lamba: fix unhandled error when SubnetIds is invalid by bentsku · Pull Request #12293 · localstack/localstack · GitHub
[go: up one dir, main page]

Skip to content

Lamba: fix unhandled error when SubnetIds is invalid #12293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions localstack-core/localstack/services/lambda_/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
ALIAS_REGEX = re.compile(r"(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)")
# Permission statement id
STATEMENT_ID_REGEX = re.compile(r"^[a-zA-Z0-9-_]+$")
# Pattern for a valid SubnetId
SUBNET_ID_REGEX = re.compile(r"^subnet-[0-9a-z]*$")


URL_CHAR_SET = string.ascii_lowercase + string.digits
Expand Down
22 changes: 18 additions & 4 deletions localstack-core/localstack/services/lambda_/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
from localstack.services.lambda_.api_utils import (
ARCHITECTURES,
STATEMENT_ID_REGEX,
SUBNET_ID_REGEX,
function_locators_from_arn,
)
from localstack.services.lambda_.event_source_mapping.esm_config_factory import (
Expand Down Expand Up @@ -468,9 +469,16 @@ def _function_revision_id(resolved_fn: Function, resolved_qualifier: str) -> str
return resolved_fn.versions[resolved_qualifier].config.revision_id

def _resolve_vpc_id(self, account_id: str, region_name: str, subnet_id: str) -> str:
return connect_to(
aws_access_key_id=account_id, region_name=region_name
).ec2.describe_subnets(SubnetIds=[subnet_id])["Subnets"][0]["VpcId"]
ec2_client = connect_to(aws_access_key_id=account_id, region_name=region_name).ec2
try:
return ec2_client.describe_subnets(SubnetIds=[subnet_id])["Subnets"][0]["VpcId"]
except ec2_client.exceptions.ClientError as e:
code = e.response["Error"]["Code"]
message = e.response["Error"]["Message"]
raise InvalidParameterValueException(
f"Error occurred while DescribeSubnets. EC2 Error Code: {code}. EC2 Error Message: {message}",
Type="User",
)

def _build_vpc_config(
self,
Expand All @@ -485,8 +493,14 @@ def _build_vpc_config(
if subnet_ids is not None and len(subnet_ids) == 0:
return VpcConfig(vpc_id="", security_group_ids=[], subnet_ids=[])

subnet_id = subnet_ids[0]
if not bool(SUBNET_ID_REGEX.match(subnet_id)):
raise ValidationException(
f"1 validation error detected: Value '[{subnet_id}]' at 'vpcConfig.subnetIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: ^subnet-[0-9a-z]*$]"
)

return VpcConfig(
vpc_id=self._resolve_vpc_id(account_id, region_name, subnet_ids[0]),
vpc_id=self._resolve_vpc_id(account_id, region_name, subnet_id),
security_group_ids=vpc_config.get("SecurityGroupIds", []),
subnet_ids=subnet_ids,
)
Expand Down
106 changes: 106 additions & 0 deletions tests/aws/services/lambda_/test_lambda_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,112 @@ def test_vpc_config(
"delete_vpcconfig_get_function_response", delete_vpcconfig_get_function_response
)

@markers.aws.validated
def test_invalid_vpc_config_subnet(
self, create_lambda_function, lambda_su_role, snapshot, aws_client, clean 8000 ups
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: create_lambda_function fixture unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, will update 👍

Copy link
Contributor Author
@bentsku bentsku Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no I'm so sorry, I thought I had pushed the changes but I forgot! 😭and I merged thinking the pipeline was already green... there was create_lambda_function and cleanups being unused...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries at all. It was just a tiny nit ☺️

):
"""
Test invalid "VpcConfig.SubnetIds" Property on the Lambda Function
"""
non_existent_subnet_id = f"subnet-{short_uid()}"
wrong_format_subnet_id = f"bad-format-{short_uid()}"

# AWS validates the Security Group first, so we need a valid one to test SubnetsIds
security_groups = aws_client.ec2.describe_security_groups(MaxResults=5)["SecurityGroups"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this just assumes that there is at least one valid security group (probably a sensible assumption)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think I've seen other tests doing this assumption with the default VPC, so figured I could as it'd be easier than to create the full VPC. Hopefully this is "safe" to do 😅

security_group_id = security_groups[0]["GroupId"]

snapshot.add_transformer(snapshot.transform.regex(non_existent_subnet_id, "<subnet_id_1>"))
snapshot.add_transformer(snapshot.transform.regex(wrong_format_subnet_id, "<subnet_id_2>"))

zip_file_bytes = create_lambda_archive(load_file(TEST_LAMBDA_PYTHON_ECHO), get_content=True)

with pytest.raises(ClientError) as e:
aws_client.lambda_.create_function(
FunctionName=f"fn-{short_uid()}",
Handler="index.handler",
Code={"ZipFile": zip_file_bytes},
PackageType="Zip",
Role=lambda_su_role,
Runtime=Runtime.python3_12,
VpcConfig={
"SubnetIds": [non_existent_subnet_id],
"SecurityGroupIds": [security_group_id],
},
)

snapshot.match("create-response-non-existent-subnet-id", e.value.response)

with pytest.raises(ClientError) as e:
aws_client.lambda_.create_function(
FunctionName=f"fn-{short_uid()}",
Handler="index.handler",
Code={"ZipFile": zip_file_bytes},
PackageType="Zip",
Role=lambda_su_role,
Runtime=Runtime.python3_12,
VpcConfig={
"SubnetIds": [wrong_format_subnet_id],
"SecurityGroupIds": [security_group_id],
},
)

snapshot.match("create-response-invalid-format-subnet-id", e.value.response)

@markers.aws.validated
@pytest.mark.skipif(reason="Not yet implemented", condition=not is_aws_cloud())
def test_invalid_vpc_config_security_group(
self, create_lambda_function, lambda_su_role, snapshot, aws_client, cleanups
):
"""
Test invalid "VpcConfig.SecurityGroup 6D40 Ids" Property on the Lambda Function
"""
# TODO: maybe add validation of security group id, not currently validated in LocalStack
non_existent_sg_id = f"sg-{short_uid()}"
wrong_format_sg_id = f"bad-format-{short_uid()}"
# this way, we assert that SecurityGroups existence is validated before SubnetIds
subnet_id = f"subnet-{short_uid()}"

snapshot.add_transformer(
snapshot.transform.regex(non_existent_sg_id, "<security_group_id_1>")
)
snapshot.add_transformer(
snapshot.transform.regex(wrong_format_sg_id, "<security_group_id_2>")
)

zip_file_bytes = create_lambda_archive(load_file(TEST_LAMBDA_PYTHON_ECHO), get_content=True)

with pytest.raises(ClientError) as e:
aws_client.lambda_.create_function(
FunctionName=f"fn-{short_uid()}",
Handler="index.handler",
Code={"ZipFile": zip_file_bytes},
PackageType="Zip",
Role=lambda_su_role,
Runtime=Runtime.python3_12,
VpcConfig={
"SubnetIds": [subnet_id],
"SecurityGroupIds": [non_existent_sg_id],
},
)

snapshot.match("create-response-non-existent-security-group", e.value.response)

with pytest.raises(ClientError) as e:
aws_client.lambda_.create_function(
FunctionName=f"fn-{short_uid()}",
Handler="index.handler",
Code={"ZipFile": zip_file_bytes},
PackageType="Zip",
Role=lambda_su_role,
Runtime=Runtime.python3_12,
VpcConfig={
"SubnetIds": [subnet_id],
"SecurityGroupIds": [wrong_format_sg_id],
},
)

snapshot.match("create-response-invalid-format-security-group", e.value.response)

@ 9E88 markers.aws.validated
def test_invalid_invoke(self, aws_client, snapshot):
region_name = aws_client.lambda_.meta.region_name
Expand Down
54 changes: 54 additions & 0 deletions tests/aws/services/lambda_/test_lambda_api.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -22258,5 +22258,59 @@
}
}
}
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": {
"recorded-date": "20-02-2025, 17:53:33",
"recorded-content": {
"create-response-non-existent-subnet-id": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Error occurred while DescribeSubnets. EC2 Error Code: InvalidSubnetID.NotFound. EC2 Error Message: The subnet ID '<subnet_id_1>' does not exist"
},
"Type": "User",
"message": "Error occurred while DescribeSubnets. EC2 Error Code: InvalidSubnetID.NotFound. EC2 Error Message: The subnet ID '<subnet_id_1>' does not exist",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"create-response-invalid-format-subnet-id": {
"Error": {
"Code": "ValidationException",
"Message": "1 validation error detected: Value '[<subnet_id_2>]' at 'vpcConfig.subnetIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: ^subnet-[0-9a-z]*$]"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
}
}
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": {
"recorded-date": "20-02-2025, 17:57:29",
"recorded-content": {
"create-response-non-existent-security-group": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Error occurred while DescribeSecurityGroups. EC2 Error Code: InvalidGroup.NotFound. EC2 Error Message: The security group '<security_group_id_1>' does not exist"
},
"Type": "User",
"message": "Error occurred while DescribeSecurityGroups. EC2 Error Code: InvalidGroup.NotFound. EC2 Error Message: The security group '<security_group_id_1>' does not exist",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"create-response-invalid-format-security-group": {
"Error": {
"Code": "ValidationException",
"Message": "1 validation error detected: Value '[<security_group_id_2>]' at 'vpcConfig.securityGroupIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: ^sg-[0-9a-zA-Z]*$]"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
}
}
}
}
9 changes: 9 additions & 0 deletions tests/aws/services/lambda_/test_lambda_api.validation.json
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,15 @@
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": {
"last_validated_date": "2024-09-12T11:34:43+00:00"
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config": {
"last_validated_date": "2025-02-20T17:44:18+00:00"
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": {
"last_validated_date": "2025-02-20T17:57:29+00:00"
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": {
"last_validated_date": "2025-02-20T17:53:33+00:00"
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": {
"last_validated_date": "2024-09-12T11:29:56+00:00"
},
Expand Down
Loading
0