Skip to content

Commit add7874

Browse files
whummerclaude
andcommitted
add proxy support and tests for Lambda service (#128)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 650780f commit add7874

File tree

5 files changed

+363
-3
lines changed

5 files changed

+363
-3
lines changed

aws-proxy/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ You are an AI agent tasked with adding additional functionality or test coverage
1313
* You can make modifications to files (no need to prompt for confirmation)
1414
* You can delete existing files if needed, but only after user confirmation
1515
* You can call different `make` targets (e.g., `make test`) in this repo (no need to prompt for confirmation)
16-
* For each new file created or existing file modified, add a header comment to the file, something like `# Note/disclosure: This file has been (partially or fully) generated by an AI agent.`
16+
* For each new file created or existing file modified, add a header comment to the file, something like `# Note: This file has been (partially or fully) generated by an AI agent.`
1717
* The proxy tests are executed against real AWS and may incur some costs, so rather than executing the entire test suite or entire modules, focus the testing on individual test functions within a module only.
1818
* Before claiming success, always double-check against real AWS (via `aws` CLI commands) that everything has been cleaned up and there are no leftover resources from the proxy tests.
1919
* Never add any `print(..)` statements to the code - use a logger to report any status to the user, if required.

aws-proxy/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ services:
8585
- 'Put.*'
8686
# optionally, specify that only read requests should be allowed (Get*/List*/Describe*, etc)
8787
read_only: false
88+
# optionally, allow invoke/execute operations (e.g., Lambda invocations) alongside read_only mode.
89+
# execute operations have side-effects and are deliberately excluded from read_only by default.
90+
execute: false
8891
```
8992

9093
Store the configuration above to a file named `proxy_config.yml`, then we can start up the proxy via:

aws-proxy/aws_proxy/server/aws_request_forwarder.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,12 @@ def select_proxy(self, context: RequestContext) -> Optional[ProxyInstance]:
8888

8989
# check if only read requests should be forwarded
9090
read_only = service_config.get("read_only")
91-
if read_only and not self._is_read_request(context):
92-
return
91+
if read_only:
92+
allow_execute = service_config.get("execute")
93+
if not self._is_read_request(context) and not (
94+
allow_execute and self._is_execute_request(context)
95+
):
96+
return
9397

9498
# check if any operation name pattern matches
9599
operation_names = ensure_list(service_config.get("operations", []))
@@ -151,6 +155,17 @@ def _request_matches_resource(
151155
return False
152156
# For metric operations without alarm names, check if pattern is generic
153157
return bool(re.match(resource_name_pattern, ".*"))
158+
if service_name == "lambda":
159+
# Lambda function ARN format: arn:aws:lambda:{region}:{account}:function:{name}
160+
function_name = context.service_request.get("FunctionName") or ""
161+
if function_name:
162+
if ":function:" not in function_name:
163+
function_arn = f"arn:aws:lambda:{context.region}:{context.account_id}:function:{function_name}"
164+
else:
165+
function_arn = function_name
166+
return bool(re.match(resource_name_pattern, function_arn))
167+
# For operations without FunctionName (e.g., ListFunctions), check if pattern is generic
168+
return bool(re.match(resource_name_pattern, ".*"))
154169
if service_name == "logs":
155170
# CloudWatch Logs ARN format: arn:aws:logs:{region}:{account}:log-group:{name}:*
156171
log_group_name = context.service_request.get("logGroupName") or ""
@@ -290,6 +305,21 @@ def _is_read_request(self, context: RequestContext) -> bool:
290305
# TODO: add more rules
291306
return False
292307

308+
def _is_execute_request(self, context: RequestContext) -> bool:
309+
"""
310+
Function to determine whether a request is an invoke/execute request.
311+
Invoke operations have side-effects and are not considered read operations.
312+
They can be explicitly allowed alongside read_only mode via the 'execute' config flag.
313+
"""
314+
operation_name = context.service_operation.operation
315+
if context.service.service_name == "lambda" and operation_name in {
316+
"Invoke",
317+
"InvokeAsync",
318+
"InvokeWithResponseStream",
319+
}:
320+
return True
321+
return False
322+
293323
def _extract_region_from_domain(self, context: RequestContext):
294324
"""
295325
If the request domain name contains a valid region name (e.g., "us-east-2.cognito.localhost.localstack.cloud"),

aws-proxy/aws_proxy/shared/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class ProxyServiceConfig(TypedDict, total=False):
1111
operations: List[str]
1212
# whether only read requests should be forwarded
1313
read_only: bool
14+
# whether invoke/execute operations (e.g., Lambda invocations) should be forwarded
15+
# (only relevant when read_only is True, since execute has side-effects and is not a read operation)
16+
execute: bool
1417

1518

1619
class ProxyConfig(TypedDict, total=False):

0 commit comments

Comments
 (0)