Skip to content

Conversation

sedkis
Copy link
Contributor

@sedkis sedkis commented Sep 12, 2025

User description

TT-982


PR Type

Enhancement


Description

  • Add AWS Lambda middleware integration

  • Extend API spec with Lambda config

  • Compile/match Lambda paths in loader

  • Map Lambda responses to HTTP


Diagram Walkthrough

flowchart LR
  Spec["ExtendedPaths adds AWS Lambda config"]
  Loader["Loader compiles AWS Lambda URLSpecs"]
  Middleware["AWSLambdaMiddleware invokes Lambda"]
  Response["Maps Lambda output to HTTP response"]

  Spec -- "AWSLambdaMeta" --> Loader
  Loader -- "URLSpec.AWSLambda" --> Middleware
  Middleware -- "Invoke + map result" --> Response
Loading

File Walkthrough

Relevant files
Enhancement
api_definitions.go
Add AWS Lambda configuration types to API definitions       

apidef/api_definitions.go

  • Add AWSLambda to ExtendedPathsSet.
  • Define AWSLambdaMeta configuration struct.
  • Add credentials, request, response mapping structs.
  • Keep Clear() behavior unchanged.
+51/-0   
api_definition.go
Wire AWS Lambda into URL status and path compilation         

gateway/api_definition.go

  • Introduce AWSLambda URLStatus and status string.
  • Compile AWS Lambda path specs into URLSpecs.
  • Include Lambda paths in combined path set.
  • Map URLStatus to StatusAWSLambda.
+39/-14 
api_loader.go
Add AWSLambdaMiddleware to request processing chain           

gateway/api_loader.go

  • Register AWSLambdaMiddleware in middleware chain.
  • Position before VirtualEndpoint handling.
+1/-0     
model_urlspec.go
Extend URLSpec to support AWS Lambda matching                       

gateway/model_urlspec.go

  • Add AWSLambda field to URLSpec.
  • Support AWSLambda in modeSpecificSpec.
  • Match HTTP methods for AWSLambda specs.
+19/-14 
mw_aws_lambda.go
Implement AWS Lambda invocation middleware                             

gateway/mw_aws_lambda.go

  • Implement AWSLambdaMiddleware with enablement logic.
  • Build payloads (proxy or passthrough) and invoke Lambda.
  • Map Lambda responses (proxy/raw) to HTTP.
  • Handle timeouts, async invocations, and analytics.
+344/-0 

@buger
Copy link
Member

buger commented Sep 12, 2025

💔 The detected issue is not in one of the allowed statuses 💔

Detected Status Open
Allowed Statuses In Dev,In Code Review,Ready for Testing,In Test,In Progress,In Review ✔️

Please ensure your jira story is in one of the allowed statuses

Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 Security concerns

Credential handling:
The middleware loads AWS config via default chain and optional endpoint override but does not honor explicit AccessKeyID/SecretAccessKey/AssumeRole settings in AWSCredentialsConfig. If users expect constrained credentials or role assumption, the default chain might pick broader creds, causing least-privilege violations. Additionally, API spec fields contain secrets—ensure they are redacted in logs and not exposed via analytics or debug output.

⚡ Recommended focus areas for review

Possible Issue

Timeout calculation uses time.Duration(sec * float64(time.Second)) which won't compile (time.Duration expects integer types). It should use time.Duration(sec) * time.Second. This impacts default timeout when TimeoutMs <= 0.

// Prepare context with timeout
timeout := time.Duration(cfg.TimeoutMs) * time.Millisecond
if timeout <= 0 {
    // Convert seconds to duration; default to 30s if unset
    sec := m.Spec.GlobalConfig.ProxyDefaultTimeout
    if sec <= 0 {
        sec = 30
    }
    timeout = time.Duration(sec * float64(time.Second))
}
ctx, cancel := context.WithTimeout(r.Context(), timeout)
Security Concern

AWSCredentialsConfig includes access keys and secret in API spec; ensure they are not logged or exposed. Also, current AWS SDK setup ignores provided static credentials/assume role fields—only default chain/endpoint override is used. This may lead to unexpected credential sourcing and potential privilege issues.

func (m *AWSLambdaMiddleware) invokeLambda(ctx context.Context, cfg *apidef.AWSLambdaMeta, payload []byte) (*lambda.InvokeOutput, string, string, error) {
    // Load AWS config
    awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(cfg.Region))
    if err != nil {
        return nil, "", "", err
    }

    var client *lambda.Client
    if cfg.Credentials.EndpointOverride != "" {
        // For LocalStack/testing
        endpoint := cfg.Credentials.EndpointOverride
        client = lambda.NewFromConfig(awsCfg, func(o *lambda.Options) {
            o.BaseEndpoint = &endpoint
        })
    } else {
        client = lambda.NewFromConfig(awsCfg)
    }
Incomplete Mapping

RequestMapping header/query allow lists and flags are effectively ignored (conditions default to forwarding all regardless of booleans). Also Path stripping logic may be inverted; ForwardPath true should forward full path or stripped path consistently. Validate intended semantics.

// Path as seen by upstream
path := r.URL.Path
if mapping.ForwardPath {
    path = m.Spec.StripListenPath(path)
}

// Headers
singleHeaders := map[string]string{}
multiHeaders := map[string][]string{}
if mapping.ForwardHeaders || !mapping.ForwardHeaders { // default: forward all
    for k, v := range r.Header {
        multiHeaders[k] = v
        if len(v) > 0 {
            singleHeaders[k] = v[0]
        }
    }
}

// Query params
singleQuery := map[string]string{}
multiQuery := map[string][]string{}
if mapping.ForwardQuerystring || !mapping.ForwardQuerystring { // default true
    q := r.URL.Query()
    for k, v := range q {
        multiQuery[k] = v
        if len(v) > 0 {
            singleQuery[k] = v[0]
        }
    }
}

// Body
isB64 := false
var bodyStr string
if mapping.ForwardBody || !mapping.ForwardBody { // default true
    if mapping.Base64EncodeBody {
        bodyStr = base64.StdEncoding.EncodeToString(bodyBytes)
        isB64 = true
    } else {
        bodyStr = string(bodyBytes)
    }
}

Copy link
Contributor

API Changes

--- prev.txt	2025-09-12 14:22:46.296050409 +0000
+++ current.txt	2025-09-12 14:22:36.658903463 +0000
@@ -334,6 +334,59 @@
     SetDisabledFlags set disabled flags to true, since by default they are not
     enabled in OAS API definition.
 
+type AWSCredentialsConfig struct {
+	UseSDKDefaultChain bool   `bson:"use_sdk_default_chain" json:"use_sdk_default_chain"`
+	AccessKeyID        string `bson:"access_key_id" json:"access_key_id,omitempty"`
+	SecretAccessKey    string `bson:"secret_access_key" json:"secret_access_key,omitempty"`
+	SessionToken       string `bson:"session_token" json:"session_token,omitempty"`
+	AssumeRoleARN      string `bson:"assume_role_arn" json:"assume_role_arn,omitempty"`
+	ExternalID         string `bson:"external_id" json:"external_id,omitempty"`
+	EndpointOverride   string `bson:"endpoint_override" json:"endpoint_override,omitempty"`
+}
+    AWSCredentialsConfig configures how credentials are resolved for Lambda
+    invocation.
+
+type AWSLambdaMeta struct {
+	Disabled       bool   `bson:"disabled" json:"disabled"`
+	Path           string `bson:"path" json:"path"`
+	Method         string `bson:"method" json:"method"`
+	FunctionName   string `bson:"function_name" json:"function_name"`
+	Qualifier      string `bson:"qualifier" json:"qualifier,omitempty"`
+	Region         string `bson:"region" json:"region"`
+	InvocationType string `bson:"invocation_type" json:"invocation_type"`
+	TimeoutMs      int    `bson:"timeout_ms" json:"timeout_ms"`
+
+	Credentials     AWSCredentialsConfig     `bson:"credentials" json:"credentials"`
+	RequestMapping  AWSLambdaRequestMapping  `bson:"request_mapping" json:"request_mapping"`
+	ResponseMapping AWSLambdaResponseMapping `bson:"response_mapping" json:"response_mapping"`
+}
+    AWSLambdaMeta defines per-path AWS Lambda invocation configuration.
+
+type AWSLambdaRequestMapping struct {
+	Mode               string   `bson:"mode" json:"mode"`
+	ForwardBody        bool     `bson:"forward_body" json:"forward_body"`
+	ForwardHeaders     bool     `bson:"forward_headers" json:"forward_headers"`
+	ForwardQuerystring bool     `bson:"forward_querystrings" json:"forward_querystrings"`
+	ForwardPath        bool     `bson:"forward_path" json:"forward_path"`
+	PayloadVersion     string   `bson:"payload_version" json:"payload_version"`
+	Base64EncodeBody   bool     `bson:"base64_encode_body" json:"base64_encode_body"`
+	HeaderAllowList    []string `bson:"header_allow_list" json:"header_allow_list,omitempty"`
+	QueryAllowList     []string `bson:"query_allow_list" json:"query_allow_list,omitempty"`
+}
+    AWSLambdaRequestMapping controls how HTTP requests are converted into Lambda
+    payloads.
+
+type AWSLambdaResponseMapping struct {
+	Mode              string `bson:"mode" json:"mode"`
+	DefaultStatus     int    `bson:"default_status" json:"default_status"`
+	ErrorStatus       int    `bson:"error_status" json:"error_status"`
+	DecodeBase64Body  bool   `bson:"decode_base64_body" json:"decode_base64_body"`
+	UnhandledStatus   int    `bson:"unhandled_status" json:"unhandled_status"`
+	HeaderPassthrough bool   `bson:"header_passthrough" json:"header_passthrough"`
+}
+    AWSLambdaResponseMapping controls how Lambda responses are mapped to HTTP
+    responses.
+
 type AnalyticsPluginConfig struct {
 	// Enabled activates the custom plugin
 	Enabled bool `bson:"enable" json:"enable,omitempty"`
@@ -513,6 +566,7 @@
 	CircuitBreaker          []CircuitBreakerMeta  `bson:"circuit_breakers" json:"circuit_breakers,omitempty"`
 	URLRewrite              []URLRewriteMeta      `bson:"url_rewrites" json:"url_rewrites,omitempty"`
 	Virtual                 []VirtualMeta         `bson:"virtual" json:"virtual,omitempty"`
+	AWSLambda               []AWSLambdaMeta       `bson:"aws_lambda" json:"aws_lambda,omitempty"`
 	SizeLimit               []RequestSizeMeta     `bson:"size_limits" json:"size_limits,omitempty"`
 	MethodTransforms        []MethodTransformMeta `bson:"method_transforms" json:"method_transforms,omitempty"`
 	TrackEndpoints          []TrackEndpointMeta   `bson:"track_endpoints" json:"track_endpoints,omitempty"`
@@ -9172,6 +9226,21 @@
     Version attempts to extract the version data from a request, depending on
     where it is stored in the request (currently only "header" is supported)
 
+type AWSLambdaMiddleware struct {
+	*BaseMiddleware
+
+	// Has unexported fields.
+}
+    AWSLambdaMiddleware invokes AWS Lambda functions for matching endpoints.
+
+func (m *AWSLambdaMiddleware) EnabledForSpec() bool
+
+func (m *AWSLambdaMiddleware) Init()
+
+func (m *AWSLambdaMiddleware) Name() string
+
+func (m *AWSLambdaMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+
 type AccessRightsCheck struct {
 	*BaseMiddleware
 }
@@ -11401,6 +11470,7 @@
 	StatusGoPlugin                 RequestStatus = "Go plugin"
 	StatusPersistGraphQL           RequestStatus = "Persist GraphQL"
 	StatusRateLimit                RequestStatus = "Rate Limited"
+	StatusAWSLambda                RequestStatus = "AWS Lambda"
 )
     Statuses of the request, all are false-y except StatusOk and
     StatusOkAndIgnore
@@ -12012,6 +12082,7 @@
 	GoPluginMeta              GoPluginMiddleware
 	PersistGraphQL            apidef.PersistGraphQLMeta
 	RateLimit                 apidef.RateLimitMeta
+	AWSLambda                 apidef.AWSLambdaMeta
 
 	IgnoreCase bool
 	// Has unexported fields.
@@ -12048,6 +12119,7 @@
 	GoPlugin
 	PersistGraphQL
 	RateLimit
+	AWSLambda
 )
     Enums representing the various statuses for a VersionInfo Path match during
     a proxy request

Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix duration calculation

The fallback timeout calculation uses floating-point conversion on time.Second,
which is unnecessary and risks precision issues. Use integer-based duration math to
avoid subtle bugs. This also improves readability.

gateway/mw_aws_lambda.go [60-69]

 // Prepare context with timeout
 timeout := time.Duration(cfg.TimeoutMs) * time.Millisecond
 if timeout <= 0 {
     // Convert seconds to duration; default to 30s if unset
     sec := m.Spec.GlobalConfig.ProxyDefaultTimeout
     if sec <= 0 {
         sec = 30
     }
-    timeout = time.Duration(sec * float64(time.Second))
+    timeout = time.Duration(sec) * time.Second
 }
Suggestion importance[1-10]: 7

__

Why: Correctly uses integer duration math (time.Duration(sec) * time.Second) instead of multiplying by a float, avoiding precision issues and improving readability; aligns with Go best practices.

Medium
General
Propagate AWS request ID

The variable reqID is always empty because it's never extracted from the Lambda
response. Capture X-Amzn-RequestId from InvokeOutput metadata to aid debugging and
propagate it in responses. This improves observability and parity with AWS
semantics.

gateway/mw_aws_lambda.go [81-87]

 out, reqID, fnErr, invokeErr := m.invokeLambda(ctx, cfg, payload)
+if out != nil && reqID == "" {
+    if out.ResponseMetadata.RequestID != "" {
+        reqID = out.ResponseMetadata.RequestID
+    }
+}
 ms := DurationToMillisecond(time.Since(start))
 if invokeErr != nil {
-    m.Logger().WithError(invokeErr).Error("Lambda invoke failed")
+    m.Logger().WithError(invokeErr).WithField("aws_request_id", reqID).Error("Lambda invoke failed")
     return errors.New("upstream Lambda invocation error"), http.StatusBadGateway
 }
Suggestion importance[1-10]: 6

__

Why: Identifies missing propagation of AWS Request ID and proposes safe extraction to improve observability; moderate impact and accurate to the SDK response shape.

Low
Avoid unnecessary body restoration

Unconditionally resetting r.Body to the raw bytes can break downstream handlers
expecting EOF on consumed bodies and increases memory use. Preserve original body
only when it will be forwarded, otherwise close it to free resources.

gateway/mw_aws_lambda.go [113-125]

 // Read body
 var bodyBytes []byte
 if r.Body != nil {
-    b, err := ioutil.ReadAll(r.Body)
+    b, err := io.ReadAll(r.Body)
     if err != nil {
         return nil, err
     }
     bodyBytes = b
-    // Reset so later middlewares (if any) could read, though we respond here
-    r.Body = ioutil.NopCloser(bytes.NewReader(b))
+    // Restore body only if it will be forwarded further; otherwise close to free resources
+    if cfg.RequestMapping.ForwardBody {
+        r.Body = io.NopCloser(bytes.NewReader(b))
+    } else {
+        r.Body.Close()
+    }
 }
Suggestion importance[1-10]: 5

__

Why: Reduces unnecessary body resets and memory use by restoring only when forwarding; reasonable improvement though current code still works since response terminates the chain.

Low

Copy link
Contributor

🚀 Performance Snapshot

Effort Perf Risk Hot Paths Benchmarks TL;DR
Medium 🟡 ⚠️ New AWS Lambda middleware creates per-request AWS clients and makes network calls that could impact latency and resource usage
## Performance Impact Analysis

The new AWS Lambda middleware introduces external service dependencies that could impact gateway performance. Each request to a Lambda endpoint creates a new AWS SDK client, makes network calls to AWS Lambda, and performs JSON marshaling/unmarshaling operations. These operations add latency and resource overhead to the request processing path.

The middleware is positioned before VirtualEndpoint in the chain, so it will be executed for all requests matching Lambda paths, potentially adding overhead even when Lambda functions aren't invoked.

## Critical Areas
  1. AWS SDK Client Creation: A new AWS SDK client is created for each request in invokeLambda(), which is inefficient and could lead to resource exhaustion under high load.

  2. Timeout Calculation: Line ~60-70 contains a type conversion issue: timeout = time.Duration(sec * float64(time.Second)) won't compile as time.Duration expects integer types. This affects default timeout when TimeoutMs <= 0.

  3. Memory Usage: The entire request body is read into memory before processing, which could cause memory pressure with large payloads.

  4. Request/Response Transformation: JSON marshaling/unmarshaling operations for proxy integration mode add CPU overhead.

## Optimization Recommendations
  1. Client Pooling: Implement AWS SDK client pooling or caching per API definition to avoid creating new clients for each request.

  2. Fix Timeout Calculation: Change timeout = time.Duration(sec * float64(time.Second)) to timeout = time.Duration(sec) * time.Second.

  3. Streaming Support: Consider supporting streaming for large request/response bodies instead of loading everything into memory.

  4. Connection Reuse: Ensure AWS SDK HTTP client is configured to reuse connections to reduce connection establishment overhead.

  5. Metrics & Monitoring: Add metrics for Lambda invocation latency to identify performance bottlenecks.

## Summary
  • The AWS Lambda middleware adds significant external dependencies to the request path that could impact performance.
  • Per-request AWS SDK client creation is inefficient and should be optimized with client pooling.
  • The timeout calculation has a type conversion issue that needs to be fixed.
  • Memory usage could be optimized for large payloads with streaming support.
  • Additional metrics would help monitor the performance impact in production.

Tip: Mention me again using /performance <request>.
Powered by Probe AI
Performance Impact Reviewer Prompt

Copy link
Contributor

📦 Impact Review Snapshot

Effort Downstream Updates Compatibility Docs TL;DR
Medium ⚠️ 🟡 ⚠️ AWS Lambda integration requires updates to tyk-operator and portal
## Impact Assessment

This PR adds AWS Lambda integration to the Tyk Gateway, introducing new API definition fields and middleware. The changes primarily impact tyk-operator which needs to understand the new API definition schema, and portal which would need UI components to configure Lambda integrations. The changes are additive and shouldn't break existing functionality, but downstream repositories need updates to fully support this feature.

## Required Updates

tyk-operator:

  • CRD definitions need updating to include AWS Lambda configuration
  • Validation logic for AWS Lambda fields
  • Controller logic to handle Lambda path specs

portal:

  • UI components for AWS Lambda configuration
  • Form validation for Lambda settings
  • Documentation on Lambda integration options

tyk-charts:

  • No critical updates required, but documentation should mention Lambda support
  • May need to add AWS SDK dependencies to Docker images

tyk-sink:

  • API definition handling code may need updates if it processes ExtendedPathsSet
  • RPC protocol should handle the new AWSLambda fields in API definitions
## Compatibility Concerns
  • The API definition schema changes are additive and shouldn't break backward compatibility
  • Older versions of tyk-operator will ignore the new fields but won't be able to manage Lambda integrations
  • Portal will need updates to display and configure Lambda settings, otherwise these will be inaccessible via UI
  • AWS credentials in API definitions need proper handling to avoid security issues (redaction in logs, etc.)
  • The implementation has potential security issues with credential handling - it loads AWS config via default chain but doesn't honor explicit credentials in AWSCredentialsConfig
## Summary & Recommendations
  • Coordinate releases with tyk-operator to ensure CRDs are updated to support AWS Lambda configuration
  • Update portal with UI components for Lambda configuration
  • Add documentation for AWS Lambda integration in all affected repositories
  • Ensure AWS credentials are properly handled and not exposed in logs or analytics
  • Fix the credential handling in the middleware to properly use the provided credentials rather than just the default chain
  • Consider adding tests for the Lambda integration to ensure it works correctly with different credential sources

Tip: Mention me again using /dependency <request>.
Powered by Probe AI
Tyk Gateway Dependency Impact Reviewer

Copy link
Contributor

🚦 Connectivity Review Snapshot

Effort Tests Security Perf TL;DR
Low ⚠️ 🔒 none 🟡 AWS Lambda middleware adds new external connectivity with minimal impact on existing Redis/RPC systems
## Connectivity Assessment
  • Redis Connections: The AWS Lambda middleware doesn't interact with Redis directly. It operates independently of the Redis connection pool and doesn't add any new Redis operations.
  • RPC Connections: No RPC connectivity is used by the Lambda middleware. It doesn't affect MDCB mode operations or RPC-based storage.
  • Synchronization Mechanisms: The middleware doesn't participate in cluster synchronization via Redis pub/sub channels. It's a stateless middleware that processes requests in isolation.
## Test Coverage Validation
  • Redis Tests: Not applicable as the middleware doesn't interact with Redis.
  • RPC Tests: Not applicable as the middleware doesn't use RPC connections.
  • Failure Scenario Tests: ⚠️ The implementation handles AWS Lambda connection failures and timeouts, but there are no explicit tests for these scenarios. Consider adding tests for Lambda connection failures, timeouts, and error responses.
## Security & Performance Impact
  • Authentication Changes: The middleware introduces AWS credentials handling, but these are passed directly to the AWS SDK without affecting Tyk's authentication systems.
  • Performance Considerations: 🟡 Each Lambda invocation creates a new external HTTP connection which could impact latency. The middleware properly implements timeout handling, but high-volume APIs might experience increased latency.
  • Error Handling: The middleware has comprehensive error handling for AWS Lambda invocations, including timeouts, connection failures, and function errors. Errors are properly mapped to HTTP responses.
## Summary & Recommendations
  • The AWS Lambda middleware is well-isolated from Tyk's core connectivity systems (Redis/RPC) and doesn't introduce risks to existing connection management.
  • Consider adding explicit tests for Lambda connection failures and timeout scenarios.
  • No suggestions to provide – change LGTM.

Tip: Mention me again using /connectivity <request>.
Powered by Probe AI
Connectivity Issues Reviewer Prompt for Tyk Gateway

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants