Azure Static Proxy Configuration
Configure Microsoft Azure services through static SSL proxy IPs for security compliance and IP whitelisting requirements
Table of Contents
Overview
Microsoft Azure services often require static IP addresses for security compliance, IP whitelisting, and network security requirements. This guide demonstrates how to configure various Azure services to work through your OutboundGateway static SSL proxy IPs.
Key Benefits: Fixed IP addresses for compliance, simplified Network Security Group rules, enhanced security for outbound traffic, and better control over Azure service access.
Prerequisites
Required Accounts & Tools
- Active OutboundGateway subscription with static IP credentials
- Azure subscription with appropriate permissions
- Azure CLI installed and configured
- Python 3.8+ with Azure SDK for Python
- Node.js 14+ with Azure SDK for JavaScript (optional)
Install Azure CLI
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Login to Azure
az login
# Set subscription
az account set --subscription "Your-Subscription-ID"
Azure Functions with Static Proxy
Python Azure Function Example
# __init__.py
import logging
import azure.functions as func
import requests
import os
import json
# Proxy configuration from application settings
PROXY_HOST = os.environ.get('PROXY_HOST', '192.168.1.100')
PROXY_PORT = os.environ.get('PROXY_PORT', '8080')
PROXY_USER = os.environ.get('PROXY_USER', 'your_proxy_username')
PROXY_PASS = os.environ.get('PROXY_PASS', 'your_proxy_password')
def create_proxy_session():
"""Create requests session with SSL proxy configuration"""
session = requests.Session()
proxy_url = f'https://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}'
session.proxies = {
'https': proxy_url
}
return session
app = func.FunctionApp()
@app.route(route="api-proxy", auth_level=func.AuthLevel.ANONYMOUS)
def api_proxy_handler(req: func.HttpRequest) -> func.HttpResponse:
"""HTTP trigger that makes API calls through static proxy"""
logging.info('Python HTTP trigger function processed a request.')
# Set CORS headers
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
}
# Handle preflight requests
if req.method == 'OPTIONS':
return func.HttpResponse("", status_code=204, headers=headers)
try:
session = create_proxy_session()
# Get request parameters
api_url = req.params.get('url', 'https://api.example.com/data')
method = req.method.upper()
# Make API call through proxy
if method == 'GET':
response = session.get(api_url, timeout=30)
elif method == 'POST':
request_body = req.get_json()
response = session.post(api_url, json=request_body, timeout=30)
else:
response = session.request(method, api_url, timeout=30)
response_data = {
'success': True,
'api_url': api_url,
'method': method,
'status_code': response.status_code,
'proxy_used': f'{PROXY_HOST}:{PROXY_PORT}',
'data': response.json() if response.headers.get('content-type', '').startswith('application/json') else response.text
}
return func.HttpResponse(
json.dumps(response_data),
status_code=200,
headers={**headers, 'Content-Type': 'application/json'}
)
except Exception as e:
logging.error(f"Error processing request: {str(e)}")
error_response = {
'success': False,
'error': str(e),
'proxy_used': f'{PROXY_HOST}:{PROXY_PORT}'
}
return func.HttpResponse(
json.dumps(error_response),
status_code=500,
headers={**headers, 'Content-Type': 'application/json'}
)
@app.queue_trigger(arg_name="msg", queue_name="proxy-queue", connection="AzureWebJobsStorage")
def queue_trigger_handler(msg: func.Out[str]) -> None:
"""Queue trigger that processes messages through proxy"""
try:
session = create_proxy_session()
# Process the queue message
message_data = json.loads(msg.get_body())
# Example: Make webhook call through proxy
webhook_url = 'https://webhook.example.com/processing'
response = session.post(
webhook_url,
json=message_data,
headers={'Content-Type': 'application/json'},
timeout=30
)
logging.info(f"Webhook sent successfully through proxy. Status: {response.status_code}")
# Send response to output queue
output_message = json.dumps({
'status': 'success',
'message': 'Processed successfully through proxy',
'proxy_used': f'{PROXY_HOST}:{PROXY_PORT}',
'original_message': message_data
})
msg.set(output_message)
except Exception as e:
logging.error(f"Error processing queue message: {str(e)}")
error_message = json.dumps({
'status': 'error',
'error': str(e),
'proxy_used': f'{PROXY_HOST}:{PROXY_PORT}'
})
msg.set(error_message)
requirements.txt
azure-functions
requests==2.31.0
host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"functionTimeout": "00:05:00"
}
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "python",
"PROXY_HOST": "192.168.1.100",
"PROXY_PORT": "8080",
"PROXY_USER": "your_proxy_username",
"PROXY_PASS": "your_proxy_password"
}
}
Deploy Azure Function
# Create resource group
az group create --name OutboundGatewayRG --location eastus
# Create storage account
az storage account create \
--name proxyflowstorage \
--location eastus \
--resource-group OutboundGatewayRG \
--sku Standard_LRS
# Create function app
az functionapp create \
--resource-group OutboundGatewayRG \
--consumption-plan-location eastus \
--runtime python \
--runtime-version 3.9 \
--functions-version 4 \
--name proxyflow-function-app \
--storage-account proxyflowstorage
# Configure application settings
az functionapp config appsettings set \
--name proxyflow-function-app \
--resource-group OutboundGatewayRG \
--settings PROXY_HOST=192.168.1.100 PROXY_PORT=8080 PROXY_USER=your_proxy_username PROXY_PASS=your_proxy_password
# Deploy function
func azure functionapp publish proxyflow-function-app
Azure Storage with Static Proxy
Python Example with Azure Storage SDK
from azure.storage.blob import BlobServiceClient
from azure.storage.queue import QueueServiceClient
from azure.identity import DefaultAzureCredential
import os
# Proxy configuration
PROXY_HOST = "192.168.1.100" # Your static IP
PROXY_PORT = 8080
PROXY_USER = "your_proxy_username"
PROXY_PASS = "your_proxy_password"
class ProxyAzureStorageClient:
def __init__(self, account_name, account_key=None, connection_string=None):
"""Initialize Azure Storage client with proxy configuration"""
# Set SSL proxy environment variables
os.environ['HTTPS_PROXY'] = f'https://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}'
if connection_string:
self.blob_client = BlobServiceClient.from_connection_string(connection_string)
self.queue_client = QueueServiceClient.from_connection_string(connection_string)
elif account_name and account_key:
account_url = f"https://{account_name}.blob.core.windows.net"
self.blob_client = BlobServiceClient(
account_url=account_url,
credential=account_key
)
queue_url = f"https://{account_name}.queue.core.windows.net"
self.queue_client = QueueServiceClient(
account_url=queue_url,
credential=account_key
)
else:
# Use DefaultAzureCredential (Managed Identity, Service Principal, etc.)
account_url = f"https://{account_name}.blob.core.windows.net"
self.blob_client = BlobServiceClient(
account_url=account_url,
credential=DefaultAzureCredential()
)
queue_url = f"https://{account_name}.queue.core.windows.net"
self.queue_client = QueueServiceClient(
account_url=queue_url,
credential=DefaultAzureCredential()
)
def upload_blob(self, container_name, blob_name, file_path):
"""Upload blob to Azure Storage through proxy"""
try:
blob_client = self.blob_client.get_blob_client(container=container_name, blob=blob_name)
with open(file_path, 'rb') as data:
blob_client.upload_blob(data, overwrite=True)
print(f"File {file_path} uploaded to {container_name}/{blob_name}")
return True
except Exception as e:
print(f"Upload failed: {e}")
return False
def download_blob(self, container_name, blob_name, download_path):
"""Download blob from Azure Storage through proxy"""
try:
blob_client = self.blob_client.get_blob_client(container=container_name, blob=blob_name)
with open(download_path, 'wb') as download_file:
download_file.write(blob_client.download_blob().readall())
print(f"Blob {container_name}/{blob_name} downloaded to {download_path}")
return True
except Exception as e:
print(f"Download failed: {e}")
return False
def list_blobs(self, container_name):
"""List blobs in container through proxy"""
try:
container_client = self.blob_client.get_container_client(container=container_name)
blob_list = []
for blob in container_client.list_blobs():
blob_list.append({
'name': blob.name,
'size': blob.size,
'last_modified': blob.last_modified,
'content_type': blob.content_settings.content_type if blob.content_settings else None
})
return blob_list
except Exception as e:
print(f"List blobs failed: {e}")
return []
def send_queue_message(self, queue_name, message):
"""Send message to Azure Queue through proxy"""
try:
queue_client = self.queue_client.get_queue_client(queue=queue_name)
queue_client.send_message(message)
print(f"Message sent to queue {queue_name}")
return True
except Exception as e:
print(f"Send message failed: {e}")
return False
def receive_queue_messages(self, queue_name, num_messages=1):
"""Receive messages from Azure Queue through proxy"""
try:
queue_client = self.queue_client.get_queue_client(queue=queue_name)
messages = queue_client.receive_messages(messages_per_page=num_messages)
message_list = []
for msg in messages:
message_list.append({
'id': msg.id,
'content': msg.content,
'insertion_time': msg.insertion_time,
'expiration_time': msg.expiration_time
})
return message_list
except Exception as e:
print(f"Receive messages failed: {e}")
return []
# Usage example
def main():
# Initialize with connection string
storage_client = ProxyAzureStorageClient(
connection_string="DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=youraccountkey;EndpointSuffix=core.windows.net"
)
# Upload a file
success = storage_client.upload_blob(
container_name="your-container",
blob_name="test-file.txt",
file_path="local-file.txt"
)
if success:
# List blobs
blobs = storage_client.list_blobs("your-container")
print(f"Found {len(blobs)} blobs in container")
for blob in blobs:
print(f" - {blob['name']} ({blob['size']} bytes)")
# Send queue message
storage_client.send_queue_message(
queue_name="your-queue",
message="Hello from Azure Storage through proxy!"
)
if __name__ == '__main__':
main()
Node.js Example with @azure/storage-blob
const { BlobServiceClient } = require('@azure/storage-blob');
const { QueueServiceClient } = require('@azure/storage-queue');
const { HttpsProxyAgent } = require('https-proxy-agent');
// SSL Proxy configuration
const proxyUrl = 'https://your_proxy_username:your_proxy_password@192.168.1.100:8080';
const proxyAgent = new HttpsProxyAgent(proxyUrl);
class ProxyAzureStorageClient {
constructor(connectionString) {
this.connectionString = connectionString;
// Create service clients with proxy configuration
this.blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
this.queueServiceClient = QueueServiceClient.fromConnectionString(connectionString);
// Configure proxy for all HTTP requests
this.configureProxy();
}
configureProxy() {
// Override the default HTTP agent to use proxy
const originalRequest = this.blobServiceClient.pipeline.sendRequest;
this.blobServiceClient.pipeline.sendRequest = async (request) => {
request.agent = proxyAgent;
return originalRequest.call(this.blobServiceClient.pipeline, request);
};
// Same for queue service
const originalQueueRequest = this.queueServiceClient.pipeline.sendRequest;
this.queueServiceClient.pipeline.sendRequest = async (request) => {
request.agent = proxyAgent;
return originalQueueRequest.call(this.queueServiceClient.pipeline, request);
};
}
async uploadBlob(containerName, blobName, filePath) {
try {
const containerClient = this.blobServiceClient.getContainerClient(containerName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
await blockBlobClient.uploadFile(filePath, {
blobHTTPHeaders: { blobContentType: 'text/plain' }
});
console.log(`File ${filePath} uploaded to ${containerName}/${blobName}`);
return true;
} catch (error) {
console.error('Upload failed:', error);
return false;
}
}
async downloadBlob(containerName, blobName, downloadPath) {
try {
const containerClient = this.blobServiceClient.getContainerClient(containerName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
await blockBlobClient.downloadToFile(downloadPath);
console.log(`Blob ${containerName}/${blobName} downloaded to ${downloadPath}`);
return true;
} catch (error) {
console.error('Download failed:', error);
return false;
}
}
async listBlobs(containerName) {
try {
const containerClient = this.blobServiceClient.getContainerClient(containerName);
const blobList = [];
for await (const blob of containerClient.listBlobsFlat()) {
blobList.push({
name: blob.name,
size: blob.properties.contentLength,
lastModified: blob.properties.lastModified,
contentType: blob.properties.contentType
});
}
return blobList;
} catch (error) {
console.error('List blobs failed:', error);
return [];
}
}
async sendMessage(queueName, message) {
try {
const queueClient = this.queueServiceClient.getQueueClient(queueName);
await queueClient.sendMessage(message);
console.log(`Message sent to queue ${queueName}`);
return true;
} catch (error) {
console.error('Send message failed:', error);
return false;
}
}
async receiveMessages(queueName, numMessages = 1) {
try {
const queueClient = this.queueServiceClient.getQueueClient(queueName);
const receivedMessages = await queueClient.receiveMessages({ numMessages });
const messageList = receivedMessages.map(msg => ({
id: msg.messageId,
content: msg.messageText,
insertionTime: msg.insertionTime,
expirationTime: msg.expirationTime,
popReceipt: msg.popReceipt
}));
return messageList;
} catch (error) {
console.error('Receive messages failed:', error);
return [];
}
}
}
// Usage example
async function main() {
const connectionString = "DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=youraccountkey;EndpointSuffix=core.windows.net";
const storageClient = new ProxyAzureStorageClient(connectionString);
// Upload a file
const uploadSuccess = await storageClient.uploadBlob(
'your-container',
'test-file.txt',
'local-file.txt'
);
if (uploadSuccess) {
// List blobs
const blobs = await storageClient.listBlobs('your-container');
console.log(`Found ${blobs.length} blobs in container`);
blobs.forEach(blob => {
console.log(` - ${blob.name} (${blob.size} bytes)`);
});
// Send queue message
await storageClient.sendMessage(
'your-queue',
'Hello from Azure Storage through proxy!'
);
}
}
main().catch(console.error);
Azure SQL Database with Static Proxy
Python Example with pyodbc
import pyodbc
import ssl
# Proxy and database configuration
PROXY_HOST = "192.168.1.100" # Your static IP
PROXY_PORT = 8080
PROXY_USER = "your_proxy_username"
PROXY_PASS = "your_proxy_password"
# Azure SQL Database configuration
SQL_SERVER = "your-server.database.windows.net"
SQL_DATABASE = "your-database"
SQL_USER = "your-sql-user"
SQL_PASS = "your-sql-password"
class ProxyAzureSQLClient:
def __init__(self):
"""Initialize Azure SQL client with proxy configuration"""
self.connection_string = self._build_connection_string()
def _build_connection_string(self):
"""Build connection string with proxy support"""
return (
f"DRIVER={ODBC Driver 18 for SQL Server};"
f"SERVER={PROXY_HOST},{PROXY_PORT};"
f"DATABASE={SQL_DATABASE};"
f"UID={SQL_USER};"
f"PWD={SQL_PASS};"
f"Encrypt=yes;"
f"TrustServerCertificate=no;"
f"Connection Timeout=30;"
f"Server={SQL_SERVER};" # Add actual server as part of connection string
)
def test_connection(self):
"""Test database connection through proxy"""
try:
# Configure SSL context
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
# Test connection
connection = pyodbc.connect(
self.connection_string,
attrs_before={pyodbc.SQL_ATTR_SSL_CTX: ssl_context}
)
cursor = connection.cursor()
cursor.execute("SELECT @@VERSION")
version = cursor.fetchone()[0]
cursor.close()
connection.close()
print(f"Successfully connected to Azure SQL through proxy!")
print(f"SQL Server version: {version}")
return True
except Exception as e:
print(f"Connection failed: {e}")
return False
def execute_query(self, query, params=None):
"""Execute SQL query through proxy"""
try:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
connection = pyodbc.connect(
self.connection_string,
attrs_before={pyodbc.SQL_ATTR_SSL_CTX: ssl_context}
)
cursor = connection.cursor()
if params:
cursor.execute(query, params)
else:
cursor.execute(query)
# Check if query returns results
if cursor.description:
# Get column names
columns = [column[0] for column in cursor.description]
results = []
# Fetch all rows
rows = cursor.fetchall()
for row in rows:
results.append(dict(zip(columns, row)))
cursor.close()
connection.close()
return results
else:
# For INSERT, UPDATE, DELETE queries
rows_affected = cursor.rowcount
connection.commit()
cursor.close()
connection.close()
return {'rows_affected': rows_affected}
except Exception as e:
print(f"Query execution failed: {e}")
return None
def create_sample_table(self):
"""Create a sample table for testing"""
create_table_query = """
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='proxy_test_table' AND xtype='U')
CREATE TABLE proxy_test_table (
id INT IDENTITY(1,1) PRIMARY KEY,
message NVARCHAR(255),
created_at DATETIME DEFAULT GETDATE(),
proxy_used NVARCHAR(100)
)
"""
result = self.execute_query(create_table_query)
if result:
print("Sample table created successfully")
return True
return False
def insert_test_data(self, message):
"""Insert test data through proxy"""
insert_query = """
INSERT INTO proxy_test_table (message, proxy_used)
VALUES (?, ?)
"""
params = (message, f"{PROXY_HOST}:{PROXY_PORT}")
result = self.execute_query(insert_query, params)
if result and result.get('rows_affected', 0) > 0:
print(f"Test data inserted successfully")
return True
return False
def select_test_data(self):
"""Select test data through proxy"""
select_query = """
SELECT id, message, created_at, proxy_used
FROM proxy_test_table
ORDER BY created_at DESC
"""
results = self.execute_query(select_query)
if results:
print(f"Found {len(results)} records:")
for record in results:
print(f" ID: {record['id']}, Message: {record['message']}, Proxy: {record['proxy_used']}")
return results
# Usage example
def main():
sql_client = ProxyAzureSQLClient()
# Test connection
if sql_client.test_connection():
# Create sample table
sql_client.create_sample_table()
# Insert test data
sql_client.insert_test_data("Hello from Azure SQL through static proxy!")
# Select and display data
sql_client.select_test_data()
if __name__ == '__main__':
main()
Network Security Groups Configuration
Create NSG Rules for Static IPs
# Create Network Security Group
az network nsg create \
--resource-group OutboundGatewayRG \
--name proxyflow-nsg \
--location eastus
# Create NSG rule to allow traffic from your static IP
az network nsg rule create \
--resource-group OutboundGatewayRG \
--nsg-name proxyflow-nsg \
--name allow-proxyflow-static-ips \
--protocol Tcp \
--direction Inbound \
--priority 1000 \
--source-address-prefixes 192.168.1.100/32 192.168.1.101/32 \
--source-port-ranges '*' \
--destination-address-prefixes '*' \
--destination-port-ranges 8080 443 80 \
--access Allow \
--description "Allow traffic from OutboundGateway static IPs"
# Create rule to allow all traffic from static IPs (less restrictive)
az network nsg rule create \
--resource-group OutboundGatewayRG \
--nsg-name proxyflow-nsg \
--name allow-proxyflow-all \
--protocol '*' \
--direction Inbound \
--priority 1001 \
--source-address-prefixes 192.168.1.100/32 192.168.1.101/32 \
--source-port-ranges '*' \
--destination-address-prefixes '*' \
--destination-port-ranges '*' \
--access Allow \
--description "Allow all traffic from OutboundGateway static IPs"
# List NSG rules
az network nsg rule list \
--resource-group OutboundGatewayRG \
--nsg-name proxyflow-nsg \
--output table
# Associate NSG with a subnet
az network vnet subnet update \
--resource-group OutboundGatewayRG \
--vnet-name your-vnet \
--name your-subnet \
--network-security-group proxyflow-nsg
Configure Virtual Network and Subnets
# Create Virtual Network
az network vnet create \
--resource-group OutboundGatewayRG \
--name proxyflow-vnet \
--address-prefix 10.0.0.0/16 \
--subnet-name default \
--subnet-prefix 10.0.0.0/24 \
--location eastus
# Create additional subnet for Azure Functions
az network vnet subnet create \
--resource-group OutboundGatewayRG \
--vnet-name proxyflow-vnet \
--name functions-subnet \
--address-prefix 10.0.1.0/24 \
--delegations Microsoft.Web/serverfarms
# Associate NSG with subnets
az network vnet subnet update \
--resource-group OutboundGatewayRG \
--vnet-name proxyflow-vnet \
--name default \
--network-security-group proxyflow-nsg
az network vnet subnet update \
--resource-group OutboundGatewayRG \
--vnet-name proxyflow-vnet \
--name functions-subnet \
--network-security-group proxyflow-nsg
# List VNET and subnet details
az network vnet show \
--resource-group OutboundGatewayRG \
--name proxyflow-vnet \
--output table
Managed Identity Configuration
Create and Configure Managed Identity
# Create User-Assigned Managed Identity
az identity create \
--resource-group OutboundGatewayRG \
--name proxyflow-identity
# Get identity details
IDENTITY_ID=$(az identity show \
--resource-group OutboundGatewayRG \
--name proxyflow-identity \
--query id -o tsv)
IDENTITY_CLIENT_ID=$(az identity show \
--resource-group OutboundGatewayRG \
--name proxyflow-identity \
--query clientId -o tsv)
echo "Identity ID: $IDENTITY_ID"
echo "Identity Client ID: $IDENTITY_CLIENT_ID"
# Assign Storage Blob Data Contributor role to identity
az role assignment create \
--assignee $IDENTITY_ID \
--role "Storage Blob Data Contributor" \
--scope /subscriptions/your-subscription-id/resourceGroups/OutboundGatewayRG
# Assign Storage Queue Data Contributor role to identity
az role assignment create \
--assignee $IDENTITY_ID \
--role "Storage Queue Data Contributor" \
--scope /subscriptions/your-subscription-id/resourceGroups/OutboundGatewayRG
# Assign SQL DB Contributor role to identity
az role assignment create \
--assignee $IDENTITY_ID \
--role "SQL DB Contributor" \
--scope /subscriptions/your-subscription-id/resourceGroups/OutboundGatewayRG/providers/Microsoft.Sql/servers/your-server/databases/your-database
# Configure Azure Function with managed identity
az functionapp identity assign \
--resource-group OutboundGatewayRG \
--name proxyflow-function-app \
--identities $IDENTITY_ID
# Configure application settings for managed identity
az functionapp config appsettings set \
--resource-group OutboundGatewayRG \
--name proxyflow-function-app \
--settings AZURE_CLIENT_ID=$IDENTITY_CLIENT_ID
Troubleshooting
Proxy Connection Issues
If you experience proxy connection problems:
- Verify static IP addresses are correctly configured in NSG rules
- Check proxy credentials are active and properly formatted
- Ensure proxy server is accessible from Azure resources
- Test connectivity using Azure Function logs
Authentication and Authorization
For authentication issues:
- Verify Azure AD permissions and role assignments
- Check managed identity configuration and client ID
- Ensure service principal has correct API permissions
- Validate connection strings and credential storage
Network Configuration
For network-related issues:
- Check VNET integration settings for Azure Functions
- Verify NSG rule priorities and configurations
- Ensure proper DNS resolution for proxy endpoints
- Monitor Azure service health and availability
Performance Optimization
To improve performance:
- Use connection pooling for database operations
- Implement retry logic with exponential backoff
- Monitor proxy bandwidth usage and latency
- Consider Azure Front Door for global distribution
Ready to Configure Azure with Static IPs?
Get your static SSL proxy IPs today and ensure your Microsoft Azure services maintain consistent IP addresses for compliance and security requirements.
Get Started with OutboundGateway