diff --git a/samcli/local/apigw/authorizers/lambda_authorizer.py b/samcli/local/apigw/authorizers/lambda_authorizer.py index 21450ec2a4a..dc09522e46f 100644 --- a/samcli/local/apigw/authorizers/lambda_authorizer.py +++ b/samcli/local/apigw/authorizers/lambda_authorizer.py @@ -387,8 +387,10 @@ def _is_resource_authorized(self, response: dict, method_arn: str) -> bool: resource_list = resource if isinstance(resource, list) else [resource] for resource_arn in resource_list: - # form a regular expression from the possible wildcard resource ARN - regex_method_arn = resource_arn.replace("*", ".*").replace("?", ".") + # form a regular expression from the possible wildcard resource ARN; + # escape it first so that regex syntax in the ARN itself (such as the + # "$" in the HTTP API "$default" stage) is matched literally + regex_method_arn = re.escape(resource_arn).replace(r"\*", ".*").replace(r"\?", ".") regex_method_arn += "$" if re.match(regex_method_arn, method_arn): diff --git a/tests/unit/local/apigw/test_lambda_authorizer.py b/tests/unit/local/apigw/test_lambda_authorizer.py index d38934ea215..f5e9af08de6 100644 --- a/tests/unit/local/apigw/test_lambda_authorizer.py +++ b/tests/unit/local/apigw/test_lambda_authorizer.py @@ -455,6 +455,66 @@ def test_validate_is_resource_authorized( self.assertEqual(result, expected_result) + @parameterized.expand( + [ + ( # HTTP API "$default" stage, wildcard resource + "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/*", + "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/GET/hello", + True, + ), + ( # authorizer echoes the method ARN it was given back verbatim + "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/GET/hello", + "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/GET/hello", + True, + ), + ( # a "[" in the path must not be read as a character set + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/a[b", + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/a[b", + True, + ), + ( # a "(" in the path must not be read as a group + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/x(y", + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/x(y", + True, + ), + ( # "." is a literal, not a wildcard + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/a.c", + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/abc", + False, + ), + ( # a different stage is still denied + "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/*", + "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/hello", + False, + ), + ] + ) + def test_is_resource_authorized_treats_arn_as_literal(self, resource_arn, method_arn, expected_result): + auth = LambdaAuthorizer( + "my auth", + Mock(), + Mock(), + [], + Mock(), + Mock(), + Mock(), + ) + response = { + "policyDocument": { + "Statement": [ + { + "Action": ["execute-api:Invoke"], + "Effect": "Allow", + "Resource": resource_arn, + } + ] + }, + } + + result = auth._is_resource_authorized(response, method_arn) + + self.assertEqual(result, expected_result) + class TestLambdaAuthorizerIamPolicyValidator(TestCase): @parameterized.expand(