# Cross-cloud applications using shared IP addresses Best practices for setting up cross-cloud applications using shared IP addresses to ensure your traffic remains trusted. ## Connectivity challenges with shared IP addresses If you deploy services on serverless platforms like AWS Lambda or Google Cloud Functions, your outbound traffic typically uses a shared pool of public IP addresses. Because thousands of tenants share these ranges, a single user's activity can negatively affect the IP reputation. If a security provider blocks a shared range, your connection to the API might fail. In addition, cloud providers may assess the geo-location based on the user's origin. In these cases, IP addresses from embargoed countries may be blocked by the cloud provider beyond the control of commercetools. To ensure consistent connectivity, use one of the following methods: - [Static outbound IP egress control](/api/shared-ip-addresses.md#static-outbound-ip-egress-control): Assign a dedicated IP to your functions. - [Private connectivity](/api/shared-ip-addresses.md#private-connectivity): Bypass the public internet entirely with a private connection to commercetools. - [Client-side retry and rotation](/api/shared-ip-addresses.md#client-side-retry-and-rotation): Implement logic to detect blocked IPs and trigger retries with new IPs. In addition to the above, you must [sanitize outbound request headers](/api/shared-ip-addresses.md#sanitize-outbound-request-headers) to protect your IP reputation. ## Static outbound IP egress control The most robust way to prevent blocking is to ensure your serverless functions always use a dedicated, static elastic IP (AWS) or reserved external IP (GCP) that you own and control. ### AWS implementation (Lambda) By default, Lambda runs in an AWS-managed VPC with public IPs. To fix this: - **VPC integration:** Move your Lambda into a private subnet within your own VPC. - **NAT gateway:** Route the private subnet's outbound traffic (0.0.0.0/0) through a NAT gateway located in a public subnet. - **Elastic IP:** Associate a dedicated elastic IP with that NAT gateway. **Result:** Every request to commercetools will originate from that single, static IP. ### GCP implementation (Cloud Functions and Cloud Run) - **Serverless VPC access:** Create a VPC Connector to link your serverless service to your VPC. - **Cloud NAT:** Create a Cloud Router and a Cloud NAT gateway. - **Static IP assignment:** Configure the Cloud NAT to use a manual IP allocation and assign a reserved static external IP. - **Egress setting:** Set the function egress setting to all-traffic to ensure all external calls, not only internal VPC calls, go through the NAT. ## Private connectivity For commercetools specifically, you can bypass the public internet entirely. If you are hosted on GCP, commercetools offers [Private Service Connect (PSC)](/api/private-connectivity.md). This allows your VPC to connect to commercetools via a private IP address that never traverses the public internet, effectively making IP reputation a non-issue. If you are on AWS, check with your Customer Success Manager regarding the availability of PrivateLink for your specific region, which functions similarly to PSC for AWS environments. ## Client-side retry and rotation If you cannot change the infrastructure, you can implement these client-side patterns: - Declare a global variable (for example, `IS_IP_VERIFIED`) to track the status. - If you detect a `403 Forbidden` or a connection timeout that indicates an IP block: exit immediately. Since serverless containers are reused, you may want to force a cold start by changing the global variable programmatically, which often triggers the allocation of a new underlying host or IP address. - If the application returns `500` (GCP) or `502 Bad Gateway` (AWS), let the client retry automatically. Implement exponential backoff with jitter on the client. Since the Lambda instance was terminated by your code (for example, by exiting the process), the cloud provider will be forced to provision a new instance for the retry, which will ideally land on an unblocked IP address. ```javascript import { exit } from 'process'; // --- CONFIGURATION --- const TARGET_URL = 'https://api.europe-west1.gcp.commercetools.com/'; // Global variable acts as a "State Cache" to ensure we only test once per worker let isIpVerified = false; // Helper to get current IP (for logging only) async function getCurrentIp() { try { const res = await fetch('https://checkip.amazonaws.com', { signal: AbortSignal.timeout(2000) }); return (await res.text()).trim(); } catch (error) { return "Unknown"; } } async function runCanaryTest() { /** * Sends an OPTIONS request to the target. * Returns: * - true: If connection succeeds or error is NOT 403. * - false: ONLY if a 403 Forbidden is received. */ try { const response = await fetch(TARGET_URL, { method: 'OPTIONS', headers: { 'User-Agent': 'AWS-Lambda-Canary-Check' }, signal: AbortSignal.timeout(3000) // 3-second timeout }); // CHECK: Only fail specifically on 403 if (response.status === 403) { return false; } // Treat 200, 404, 500, etc., as "Safe" to avoid killing workers on server glitches return true; } catch (error) { // If connection completely fails (timeout/DNS), assume safe or fail open return true; } } export const handler = async (event) => { // 1. Warm Start Optimization (Skip check if already verified) if (isIpVerified) { return businessLogic(event); } // 2. Perform Canary Test (Cold Start Only) const isSafe = await runCanaryTest(); // 3. Handle Failure (Only log if Bad IP is detected) if (!isSafe) { const currentIp = await getCurrentIp(); console.log(`CRITICAL: Bad IP Detected (${currentIp}). Endpoint returned 403. Terminating Worker.`); // Kill the worker immediately to force a new IP on the client's retry exit(1); } // 4. Success Path (Silent) isIpVerified = true; return businessLogic(event); }; function businessLogic(event) { return { statusCode: 200, body: JSON.stringify('Request processed successfully') }; } ``` ```python import sys import json import urllib.request import urllib.error # --- CONFIGURATION --- TARGET_URL = 'https://api.europe-west1.gcp.commercetools.com/' # Global variable acts as a "State Cache" to ensure we only test once per worker VERIFIED_GLOBAL_STATE = False def get_current_ip(): try: return urllib.request.urlopen('https://checkip.amazonaws.com', timeout=2).read().decode('utf-8').strip() except: return "Unknown" def run_canary_test(): """ Sends an OPTIONS request to the target. Returns False ONLY if a 403 Forbidden is received. """ try: req = urllib.request.Request( TARGET_URL, method='OPTIONS', headers={'User-Agent': 'AWS-Lambda-Canary-Check'} ) # Timeout set to 3s to avoid hanging with urllib.request.urlopen(req, timeout=3) as response: return True except urllib.error.HTTPError as e: if e.code == 403: return False # Treat other errors (500s, 404s) as safe to avoid killing workers on server glitches return True except Exception: # If connection completely fails, assume safe or fail open return True def lambda_handler(event, context): global VERIFIED_GLOBAL_STATE # 1. Warm Start Optimization (Skip check if already verified) if VERIFIED_GLOBAL_STATE: return business_logic(event) # 2. Perform Canary Test (Cold Start Only) is_safe = run_canary_test() # 3. Handle Failure (Only log if Bad IP is detected) if not is_safe: current_ip = get_current_ip() print(f"CRITICAL: Bad IP Detected ({current_ip}). Endpoint returned 403. Terminating Worker.") # Kill the worker immediately to force a new IP on the client's retry sys.exit(1) # 4. Success Path (Silent) VERIFIED_GLOBAL_STATE = True return business_logic(event) def business_logic(event): return { 'statusCode': 200, 'body': json.dumps('Request processed successfully') } ``` ### Limitations and considerations While currently very few IP addresses used by major cloud providers are blocked by cloud providers, you should log and monitor errors of this type to detect trends. Should the frequency of blocked IPs increase, consider moving to a [static outbound IP](/api/shared-ip-addresses.md#static-outbound-ip-egress-control) solution. ### Impact and summary This approach will effectively filter out a small number of blocked IP addresses at negligible cost and with no performance impact on 99.9 percent of your traffic. Once the frequency begins to impede operations, raise a [support ticket](https://support.commercetools.com/) and share the blocked IP addresses so we can escalate them to our cloud provider. While the canary test approach is free with no NAT gateway hourly charges, the [static outbound IP](/api/shared-ip-addresses.md#static-outbound-ip-egress-control) approach is superior for enterprise applications because: - **Deterministic success:** You avoid unpredictable IP address changes. - **No latency penalty:** No extra HTTP calls during cold starts. ## Sanitize outbound request headers When your backend forwards requests to the commercetools API, remove any headers that reveal the end user's origin or identity. If your backend sends end-user headers like `X-Forwarded-For` or cookies to the commercetools API, cloud providers may factor this data into geo-location, IP reputation, or sanctions checks. This can affect whether your backend IP address is treated as trusted. Strip the following headers from all outbound requests to the commercetools API: - `X-Forwarded-For` - `True-Client-IP` - `Forwarded` - `Cookie` - `Referer` - `Via` - Any other client-identifying headers Instead of trying to remove bad headers, explicitly define which headers are allowed (such as `Authorization`, `Content-Type`, `X-Correlation-ID`) and drop everything else by default. For the most robust solution, use a [static egress IP](/api/shared-ip-addresses.md#static-outbound-ip-egress-control) so your traffic is always recognized as trusted regardless of user origin. ## Comparison | Approach | Complexity | Reliability | | --- | :---: | --- | | [Private connectivity](/api/shared-ip-addresses.md#private-connectivity) | Higher | Highest (no public internet) | | [Static outbound IP](/api/shared-ip-addresses.md#static-outbound-ip-egress-control) | Medium | High (standard best practice) | | [Client-side Retry/Rotation](/api/shared-ip-addresses.md#client-side-retry-and-rotation) | Low | Low (reactive, not proactive) | ## Related pages - [Area overview page with navigation](/api.md) - [Previous page: Private connectivity](/api/private-connectivity.md) - [Search documentation and API specs](/search.md)