Skip to content

Revert "feat: refactor Function URL permissions (#3735)" #3761

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 2 commits into from
Apr 29, 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
3 changes: 0 additions & 3 deletions .cfnlintrc.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
templates:
- tests/translator/output/**/*.json
ignore_templates:
- tests/translator/output/**/function_with_function_url_config.json
- tests/translator/output/**/function_with_function_url_config_and_autopublishalias.json
- tests/translator/output/**/function_with_function_url_config_without_cors_config.json
- tests/translator/output/**/error_*.json # Fail by design
- tests/translator/output/**/api_http_paths_with_if_condition.json
- tests/translator/output/**/api_http_paths_with_if_condition_no_value_else_case.json
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

67 changes: 0 additions & 67 deletions integration/single/test_basic_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,73 +130,6 @@ def test_basic_function_with_url_config(self, file_name, qualifier):
self.assertEqual(function_url_config["Cors"], cors_config)
self._assert_invoke(lambda_client, function_name, qualifier, 200)

@parameterized.expand(
[
("single/basic_function_with_function_url_dual_auth", None),
("single/basic_function_with_function_url_with_autopuplishalias_dual_auth", "live"),
]
)
@skipIf(current_region_does_not_support([LAMBDA_URL]), "Lambda Url is not supported in this testing region")
def test_basic_function_with_url_dual_auth(self, file_name, qualifier):
"""
Creates a basic lambda function with Function Url with authtype: None
Verifies that 2 AWS::Lambda::Permission resources are created:
- lambda:InvokeFunctionUrl
- lambda:InvokeFunction with InvokedViaFunctionUrl: True
"""
self.create_and_verify_stack(file_name)

# Get Lambda permissions
lambda_permissions = self.get_stack_resources("AWS::Lambda::Permission")

# Verify we have exactly 2 permissions
self.assertEqual(len(lambda_permissions), 2, "Expected exactly 2 Lambda permissions")

# Check for the expected permission logical IDs
invoke_function_url_permission = None
invoke_permission = None

for permission in lambda_permissions:
logical_id = permission["LogicalResourceId"]
if "MyLambdaFunctionUrlPublicPermissions" in logical_id:
invoke_function_url_permission = permission
elif "MyLambdaFunctionURLInvokeAllowPublicAccess" in logical_id:
invoke_permission = permission

# Verify both permissions exist
self.assertIsNotNone(invoke_function_url_permission, "Expected MyLambdaFunctionUrlPublicPermissions to exist")
self.assertIsNotNone(invoke_permission, "Expected MyLambdaFunctionURLInvokeAllowPublicAccess to exist")

# Get the function name and URL
function_name = self.get_physical_id_by_type("AWS::Lambda::Function")
lambda_client = self.client_provider.lambda_client

# Get the function URL configuration to verify auth type
function_url_config = (
lambda_client.get_function_url_config(FunctionName=function_name, Qualifier=qualifier)
if qualifier
else lambda_client.get_function_url_config(FunctionName=function_name)
)

# Verify the auth type is NONE
self.assertEqual(function_url_config["AuthType"], "NONE", "Expected AuthType to be NONE")

# Get the template to check for InvokedViaFunctionUrl property
cfn_client = self.client_provider.cfn_client
template = cfn_client.get_template(StackName=self.stack_name, TemplateStage="Processed")
template_body = template["TemplateBody"]

# Check if the InvokePermission has InvokedViaFunctionUrl: True
# This is a bit hacky but we don't have direct access to the resource properties
# We're checking if the string representation of the template contains this property
template_str = str(template_body)
self.assertIn("InvokedViaFunctionUrl", template_str, "Expected InvokedViaFunctionUrl property in the template")

# Get the function URL from stack outputs
function_url = self.get_stack_output("FunctionUrl")["OutputValue"]
# Invoke the function URL and verify the response
self._verify_get_request(function_url, self.FUNCTION_OUTPUT)

@skipIf(current_region_does_not_support([CODE_DEPLOY]), "CodeDeploy is not supported in this testing region")
def test_function_with_deployment_preference_alarms_intrinsic_if(self):
self.create_and_verify_stack("single/function_with_deployment_preference_alarms_intrinsic_if")
Expand Down
1 change: 0 additions & 1 deletion samtranslator/model/lambda_.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ class LambdaPermission(Resource):
"SourceArn": GeneratedProperty(),
"EventSourceToken": GeneratedProperty(),
"FunctionUrlAuthType": GeneratedProperty(),
"InvokedViaFunctionUrl": GeneratedProperty(),
}


Expand Down
54 changes: 3 additions & 51 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,8 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
lambda_url = self._construct_function_url(lambda_function, lambda_alias, self.FunctionUrlConfig)
resources.append(lambda_url)
url_permission = self._construct_url_permission(lambda_function, lambda_alias, self.FunctionUrlConfig)
invoke_dual_auth_permission = self._construct_invoke_permission(
lambda_function, lambda_alias, self.FunctionUrlConfig
)
if url_permission and invoke_dual_auth_permission:
if url_permission:
resources.append(url_permission)
resources.append(invoke_dual_auth_permission)

self._validate_deployment_preference_and_add_update_policy(
kwargs.get("deployment_preference_collection"),
Expand All @@ -336,6 +332,7 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
self.get_passthrough_resource_attributes(),
feature_toggle,
)

event_invoke_policies: List[Dict[str, Any]] = []
if self.EventInvokeConfig:
function_name = lambda_function.logical_id
Expand Down Expand Up @@ -1228,13 +1225,9 @@ def _construct_url_permission(
lambda_function : LambdaUrl
Lambda Function resource

lambda_alias : LambdaAlias
llambda_alias : LambdaAlias
Lambda Alias resource


function_url_config: Dict
Function url config used to create FURL

Returns
-------
LambdaPermission
Expand All @@ -1256,47 +1249,6 @@ def _construct_url_permission(
lambda_permission.FunctionUrlAuthType = auth_type
return lambda_permission

def _construct_invoke_permission(
self, lambda_function: LambdaFunction, lambda_alias: Optional[LambdaAlias], function_url_config: Dict[str, Any]
) -> Optional[LambdaPermission]:
"""
Construct the lambda permission associated with the function invoke resource in a case
for public access when AuthType is NONE

Parameters
----------
lambda_function : LambdaUrl
Lambda Function resource

lambda_alias : LambdaAlias
Lambda Alias resource

function_url_config: Dict
Function url config used to create FURL

Returns
-------
LambdaPermission
The lambda permission appended to a function that allow function invoke only from Function URL
"""
# create lambda:InvokeFunction with InvokedViaFunctionUrl=True
auth_type = function_url_config.get("AuthType")

if auth_type not in ["NONE"] or is_intrinsic(function_url_config):
return None

logical_id = f"{lambda_function.logical_id}URLInvokeAllowPublicAccess"
lambda_permission_attributes = self.get_passthrough_resource_attributes()
lambda_invoke_permission = LambdaPermission(logical_id=logical_id, attributes=lambda_permission_attributes)
lambda_invoke_permission.Action = "lambda:InvokeFunction"
lambda_invoke_permission.Principal = "*"
lambda_invoke_permission.FunctionName = (
lambda_alias.get_runtime_attr("arn") if lambda_alias else lambda_function.get_runtime_attr("name")
)
lambda_invoke_permission.InvokedViaFunctionUrl = True

return lambda_invoke_permission


class SamApi(SamResourceMacro):
"""SAM rest API macro."""
Expand Down
28 changes: 5 additions & 23 deletions tests/model/test_sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,29 +583,11 @@ def test_with_valid_function_url_config_with_lambda_permission(self):

cfnResources = function.to_cloudformation(**self.kwargs)
generatedUrlList = [x for x in cfnResources if isinstance(x, LambdaPermission)]
self.assertEqual(generatedUrlList.__len__(), 2)
for permission in generatedUrlList:
self.assertEqual(permission.FunctionName, {"Ref": "foo"})
self.assertEqual(permission.Principal, "*")
self.assertTrue(permission.Action in ["lambda:InvokeFunctionUrl", "lambda:InvokeFunction"])
if permission.Action == "lambda:InvokeFunctionUrl":
self.assertEqual(permission.FunctionUrlAuthType, "NONE")
if permission.Action == "lambda:InvokeFunction":
self.assertEqual(permission.InvokedViaFunctionUrl, True)

@patch("boto3.session.Session.region_name", "ap-southeast-1")
def test_with_aws_iam_function_url_config_with_lambda_permission(self):
function = SamFunction("foo")
function.CodeUri = "s3://foobar/foo.zip"
function.Runtime = "foo"
function.Handler = "bar"
# When create FURL with AWS_IAM
function.FunctionUrlConfig = {"AuthType": "AWS_IAM"}

cfnResources = function.to_cloudformation(**self.kwargs)
generatedUrlList = [x for x in cfnResources if isinstance(x, LambdaPermission)]
# Then no permisssion should be auto created
self.assertEqual(generatedUrlList.__len__(), 0)
self.assertEqual(generatedUrlList.__len__(), 1)
self.assertEqual(generatedUrlList[0].Action, "lambda:InvokeFunctionUrl")
self.assertEqual(generatedUrlList[0].FunctionName, {"Ref": "foo"})
self.assertEqual(generatedUrlList[0].Principal, "*")
self.assertEqual(generatedUrlList[0].FunctionUrlAuthType, "NONE")

@patch("boto3.session.Session.region_name", "ap-southeast-1")
def test_with_invalid_function_url_config_with_authorization_type_value_as_None(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,6 @@
},
"Type": "AWS::IAM::Role"
},
"MyFunctionURLInvokeAllowPublicAccess": {
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Ref": "MyFunction"
},
"InvokedViaFunctionUrl": true,
"Principal": "*"
},
"Type": "AWS::Lambda::Permission"
},
"MyFunctionUrl": {
"Properties": {
"AuthType": "NONE",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,6 @@
},
"Type": "AWS::IAM::Role"
},
"MyFunctionURLInvokeAllowPublicAccess": {
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Ref": "MyFunctionAliaslive"
},
"InvokedViaFunctionUrl": true,
"Principal": "*"
},
"Type": "AWS::Lambda::Permission"
},
"MyFunctionUrl": {
"Properties": {
"AuthType": "NONE",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,6 @@
},
"Type": "AWS::IAM::Role"
},
"MyFunctionURLInvokeAllowPublicAccess": {
"Condition": "MyCondition",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Ref": "MyFunction"
},
"InvokedViaFunctionUrl": true,
"Principal": "*"
},
"Type": "AWS::Lambda::Permission"
},
"MyFunctionUrl": {
"Condition": "MyCondition",
"Properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,6 @@
},
"Type": "AWS::IAM::Role"
},
"MyFunctionURLInvokeAllowPublicAccess": {
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Ref": "MyFunction"
},
"InvokedViaFunctionUrl": true,
"Principal": "*"
},
"Type": "AWS::Lambda::Permission"
},
"MyFunctionUrl": {
"Properties": {
"AuthType": "NONE",
Expand Down
Loading