OCI API Gateway sits in front of your backend services and handles authentication, request validation, rate limiting, and routing before a single request reaches your application code. Centralizing these concerns in the gateway removes them from each service and enforces consistent policies across your entire API surface. This post covers deploying a production API Gateway with Terraform, configuring JWT authentication, setting up rate limiting with usage plans, and routing to both OCI Functions and HTTP backends.
Step 1: IAM Policy and Gateway
resource "oci_identity_policy" "api_gateway_policy" {
compartment_id = var.compartment_id
name = "api-gateway-policy"
statements = [
"Allow any-user to use functions-family in compartment id COMPARTMENT_OCID where all {request.principal.type = 'ApiGateway', request.resource.compartment.id = 'COMPARTMENT_OCID'}",
"Allow any-user to read secret-bundle in compartment id COMPARTMENT_OCID where all {request.principal.type = 'ApiGateway'}"
]
}
resource "oci_apigateway_gateway" "production" {
compartment_id = var.compartment_id
display_name = "production-api-gateway"
endpoint_type = "PRIVATE"
subnet_id = var.private_subnet_id
network_security_group_ids = [var.api_gateway_nsg_id]
certificate_id = var.tls_certificate_id
defined_tags = {
"Operations.Environment" = "production"
"Operations.ManagedBy" = "terraform"
}
}
output "gateway_hostname" {
value = oci_apigateway_gateway.production.hostname
}
Step 2: Usage Plan for Rate Limiting
resource "oci_apigateway_usage_plan" "standard_tier" {
compartment_id = var.compartment_id
display_name = "standard-api-tier"
entitlements {
name = "standard-entitlement"
rate_limit {
value = 1000
unit = "MINUTE"
}
quota {
value = 100000
unit = "DAY"
reset_policy = "CALENDAR"
operation_on_breach = "REJECT"
}
}
}
resource "oci_apigateway_usage_plan" "premium_tier" {
compartment_id = var.compartment_id
display_name = "premium-api-tier"
entitlements {
name = "premium-entitlement"
rate_limit {
value = 10000
unit = "MINUTE"
}
quota {
value = 10000000
unit = "DAY"
reset_policy = "CALENDAR"
operation_on_breach = "REJECT"
}
}
}
Step 3: API Deployment with JWT Auth and Rate Limiting
resource "oci_apigateway_deployment" "orders_api" {
compartment_id = var.compartment_id
gateway_id = oci_apigateway_gateway.production.id
display_name = "orders-api-v1"
path_prefix = "/v1"
specification {
# Global request policies apply to all routes
request_policies {
authentication {
type = "JWT_AUTHENTICATION"
audiences = ["https://api.example.com"]
issuers = ["https://identity.oraclecloud.com"]
token_header = "Authorization"
token_auth_scheme = "Bearer"
public_keys {
type = "REMOTE_JWKS"
uri = "https://identity.oraclecloud.com/.well-known/jwks.json"
max_cache_duration_in_hours = 1
}
verify_claims {
key = "scope"
values = ["orders:read", "orders:write"]
is_required = false
}
}
rate_limiting {
rate_in_requests_per_second = 100
rate_key = "CLIENT_IP"
}
usage_plans {
token_locations = ["request.headers[authorization]"]
}
cors {
allowed_origins = ["https://app.example.com"]
allowed_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowed_headers = ["Authorization", "Content-Type", "X-Correlation-ID"]
max_age_in_seconds = 3600
}
}
# Routes
routes {
path = "/orders"
methods = ["GET", "POST"]
backend {
type = "HTTP_BACKEND"
url = "https://orders-api.internal.example.com/orders"
is_ssl_verify_disabled = false
connect_timeout_in_seconds = 5
read_timeout_in_seconds = 30
send_timeout_in_seconds = 30
}
request_policies {
authorization {
type = "ANY_OF"
allowed_scope = ["orders:read", "orders:write"]
}
header_transformations {
set_headers {
items {
name = "X-Customer-ID"
values = ["request.auth.claims[sub]"]
if_exists = "OVERWRITE"
}
}
}
}
}
routes {
path = "/orders/process"
methods = ["POST"]
backend {
type = "ORACLE_FUNCTIONS_BACKEND"
function_id = var.orders_processor_function_id
}
request_policies {
authorization {
type = "ANY_OF"
allowed_scope = ["orders:write"]
}
}
}
}
defined_tags = {
"Operations.Environment" = "production"
"Operations.ManagedBy" = "terraform"
}
}
output "api_endpoint" {
value = "https://${oci_apigateway_gateway.production.hostname}/v1"
}
Step 4: Request and Response Transformation
routes {
path = "/legacy/orders"
methods = ["GET"]
backend {
type = "HTTP_BACKEND"
url = "https://legacy-orders.internal.example.com/api/orders"
}
request_policies {
header_transformations {
set_headers {
# Translate modern header to legacy format
items {
name = "X-Api-Version"
values = ["2024-01-01"]
if_exists = "OVERWRITE"
}
items {
name = "X-Auth-Token"
values = ["request.auth.claims[sub]"]
if_exists = "OVERWRITE"
}
}
rename_headers {
items {
from = "X-Correlation-ID"
to = "X-Request-ID"
if_exists = "OVERWRITE"
}
}
}
}
response_policies {
header_transformations {
filter_headers {
type = "BLOCK"
items {
name = "Server"
}
items {
name = "X-Powered-By"
}
}
}
}
}
Operational Notes
JWT token validation happens at the gateway before the request reaches your backend. Set max_cache_duration_in_hours on the remote JWKS endpoint to reduce external calls for key rotation. The gateway caches the signing keys for the specified duration. When you rotate signing keys, allow for the cache duration to expire before revoking old keys, or reduce the cache duration temporarily before rotation.
Rate limiting with rate_key = CLIENT_IP applies per source IP. For APIs consumed by services rather than browsers, use TOTAL to apply a global rate limit across all callers, or implement usage plans with subscriber keys to get per-subscriber rate limiting that survives IP address changes.
Regards,
Osama
#OCI #OracleCloud #APIGateway #JWT #Terraform #IaC #CloudSecurity #TechBlog #Oracle #PlatformEngineering #APIManagement #DevOps #RateLimiting #Authentication #OracleCloudInfrastructure #Functions #CORS #UsagePlans #CloudNative #Microservices
Leave a comment