Skip to content

Instantly share code, notes, and snippets.

@nginx-gists
Forked from lcrilly/api_backends.conf
Last active September 20, 2024 06:58
Show Gist options
  • Save nginx-gists/37ce65292a06219ff8d35d293c05e0b5 to your computer and use it in GitHub Desktop.
Save nginx-gists/37ce65292a06219ff8d35d293c05e0b5 to your computer and use it in GitHub Desktop.
Deploying NGINX Plus as an API Gateway, Part 1
upstream warehouse_inventory {
zone inventory_service 64k;
server 10.0.0.1:80;
server 10.0.0.2:80;
server 10.0.0.3:80;
}
upstream warehouse_pricing {
zone pricing_service 64k;
server 10.0.0.7:80;
server 10.0.0.8:80;
server 10.0.0.9:80;
}
# vim: syntax=nginx
include api_backends.conf;
include api_keys.conf;
server {
access_log /var/log/nginx/api_access.log main; # Each API may also log to a
# separate file
listen 443 ssl;
server_name api.example.com;
# TLS config
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1.2 TLSv1.3;
# API definitions, one per file
include api_conf.d/*.conf;
# Error responses
error_page 404 = @400; # Treat invalid paths as bad requests
proxy_intercept_errors on; # Do not send backend errors to client
include api_json_errors.conf; # API client-friendly JSON errors
default_type application/json; # If no content-type, assume JSON
}
# vim: syntax=nginx
include api_backends.conf;
include api_keys.conf;
server {
access_log /var/log/nginx/api_access.log main; # Each API may also log to a
# separate file
listen 443 ssl;
server_name api.example.com;
# TLS config
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1.2 TLSv1.3;
# API definitions, one per file
include api_conf.d/*.conf;
# Error responses
error_page 404 = @400; # Invalid paths are treated as bad requests
proxy_intercept_errors on; # Do not send backend errors to the client
include api_json_errors.conf; # API client friendly JSON error responses
default_type application/json; # If no content-type then assume JSON
# API key validation
location = /_validate_apikey {
internal;
if ($http_apikey = "") {
return 401; # Unauthorized
}
if ($api_client_name = "") {
return 403; # Forbidden
}
return 204; # OK (no content)
}
}
# vim: syntax=nginx
error_page 400 = @400;
location @400 { return 400 '{"status":400,"message":"Bad request"}\n'; }
error_page 401 = @401;
location @401 { return 401 '{"status":401,"message":"Unauthorized"}\n'; }
error_page 403 = @403;
location @403 { return 403 '{"status":403,"message":"Forbidden"}\n'; }
error_page 404 = @404;
location @404 { return 404 '{"status":404,"message":"Resource not found"}\n'; }
error_page 405 = @405;
location @405 { return 405 '{"status":405,"message":"Method not allowed"}\n'; }
error_page 408 = @408;
location @408 { return 408 '{"status":408,"message":"Request timeout"}\n'; }
error_page 413 = @413;
location @413 { return 413 '{"status":413,"message":"Payload too large"}\n'; }
error_page 414 = @414;
location @414 { return 414 '{"status":414,"message":"Request URI too large"}\n'; }
error_page 415 = @415;
location @415 { return 415 '{"status":415,"message":"Unsupported media type"}\n'; }
error_page 426 = @426;
location @426 { return 426 '{"status":426,"message":"HTTP request was sent to HTTPS port"}\n'; }
error_page 429 = @429;
location @429 { return 429 '{"status":429,"message":"API rate limit exceeded"}\n'; }
error_page 495 = @495;
location @495 { return 495 '{"status":495,"message":"Client certificate authentication error"}\n'; }
error_page 496 = @496;
location @496 { return 496 '{"status":496,"message":"Client certificate not presented"}\n'; }
error_page 497 = @497;
location @497 { return 497 '{"status":497,"message":"HTTP request was sent to mutual TLS port"}\n'; }
error_page 500 = @500;
location @500 { return 500 '{"status":500,"message":"Server error"}\n'; }
error_page 501 = @501;
location @501 { return 501 '{"status":501,"message":"Not implemented"}\n'; }
error_page 502 = @502;
location @502 { return 502 '{"status":502,"message":"Bad gateway"}\n'; }
# vim: syntax=nginx
map $http_apikey $api_client_name {
default "";
"7B5zIqmRGXmrJTFmKa99vcit" "client_one";
"QzVV6y1EmQFbbxOfRCwyJs35" "client_two";
"mGcjH8Fv6U9y3BVF9H3Ypb9T" "client_six";
}
# vim: syntax=nginx
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
load_module /etc/nginx/modules/ngx_http_js_module.so;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
include /etc/nginx/api_gateway.conf; # All API gateway configuration
include /etc/nginx/conf.d/*.conf; # Regular web traffic
}
#!/usr/bin/env bash
#
# oas2nginx.sh (c) NGINX, Inc. [v0.5 13-Jan-2020] Liam Crilly <[email protected]>
#
# Converts OpenAPI/Swagger spec into nginx.conf snippet (server context) as per
# https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-1/
# Requires shyaml for YAML processing: https://github.com/0k/shyaml
# Defaults
#
BASEPATH=""
PREFIX_PATH=""
UPSTREAM="my_backend"
if [ $# -lt 1 ]; then
echo "USAGE: ${0##*/} [options] oas_spec.yaml"
echo " Converts OpenAPI/Swagger spec into nginx.conf snippet"
echo " Options:"
echo " -b | --basepath <basePath> # Override OAS basePath / servers path"
echo " -p | --prefix <prefix path> # Apply further prefix to basePath"
echo " -u | --upstream <upstream name> # Specify upstream group (default: $UPSTREAM)"
exit 1
fi
which shyaml > /dev/null
if [ $? -ne 0 ]; then
echo "${0##*/} ERROR: shyaml not found, see https://github.com/0k/shyaml"
exit 1
fi
while [ $# -gt 1 ]; do
case "$1" in
"-b" | "--basepath")
BASEPATH=$2
shift; shift
;;
"-p" | "--prefix")
PREFIX_PATH=$2
shift; shift
;;
"-u" | "--upstream")
UPSTREAM=$2
shift; shift
;;
*)
echo "${0##*/} ERROR: Invalid command line option ($1)"
exit 1
;;
esac
done
if [ ! -f $1 ]; then
echo "${0##*/} ERROR: Cannot open $1"
exit 1
fi
if [ "$BASEPATH" == "" ]; then
OAS_VERSION=`shyaml -q get-value openapi < $1`
if [ $? -eq 0 ]; then
echo "${0##*/} INFO: OpenAPI $OAS_VERSION" > /dev/stderr
BASEPATH=`shyaml get-value servers < $1 2> /dev/null | grep url: | cut -f2- -d: | tail -1 | tr -d '[:blank:]'`
else
echo "${0##*/} INFO: OAS/Swagger v2" > /dev/stderr
BASEPATH=`shyaml -q get-value basePath < $1`
fi
if [ "$BASEPATH" == "" ]; then
echo "${0##*/}: WARNING: No basePath found in OAS" > /dev/stderr
BASEPATH=/
fi
fi
if [ "`echo $BASEPATH | grep -c http`" == "1" ]; then
echo "${0##*/}: INFO: Stripping scheme and hostname from basepath URL" > /dev/stderr
BASEPATH=/`echo $BASEPATH | cut -f4- -d/`
fi
echo "${0##*/}: INFO: Using basePath $BASEPATH"
if [ "$PREFIX_PATH" != "" ]; then
echo "# Strip prefix"
echo "rewrite ^$PREFIX_PATH/\(.*\)$ \1 last;"
echo ""
fi
echo "location $BASEPATH/ {" | sed -e 's_//_/_g'
echo " # Policy section here"
echo " #"
echo " error_page 403 = @405;"
echo ""
for SWAGGER_PATH in `shyaml keys paths < $1`; do
# Convert path templates to regular expressions
URI=`echo $SWAGGER_PATH | sed -e "s/\({.*}\)/\[\^\/\]\+/g"`
if [ "$SWAGGER_PATH" == "$URI" ]; then
# Exact match when no path templates
echo " location = $BASEPATH$URI {" | sed -e 's_//_/_g'
else
# Regex match
echo " location ~ ^$BASEPATH$URI\$ {" | sed -e 's_//_/_g'
fi
ESCAPED_PATH=`echo $SWAGGER_PATH | sed -e 's/\./\\\./g'`
METHODS=`shyaml keys paths.$ESCAPED_PATH < $1 | grep -v parameters | tr '\n' ' '`
if [ "$METHODS" != "" ]; then
echo " limit_except $METHODS{ deny all; }"
fi
echo " proxy_pass http://$UPSTREAM;"
echo " }"
done
echo ""
echo " return 404;"
echo "}"
# Warehouse API
#
location /api/warehouse/ {
# Policy configuration here (authentication, rate limiting, logging...)
#
access_log /var/log/nginx/warehouse_api.log main;
auth_request /_validate_apikey;
# URI routing
#
location /api/warehouse/inventory {
proxy_pass http://warehouse_inventory;
}
location /api/warehouse/pricing {
proxy_pass http://warehouse_pricing;
}
return 404; # Catch-all
}
# vim: syntax=nginx
# Warehouse API (precise definition)
#
location /api/warehouse/ {
# Policy configuration here (authentication, rate limiting, logging...)
#
access_log /var/log/nginx/warehouse_api.log main;
# URI routing
#
location = /api/warehouse/inventory { # Complete inventory
proxy_pass http://warehouse_inventory;
}
location ~ ^/api/warehouse/inventory/shelf/[^/]+$ { # Shelf inventory
proxy_pass http://warehouse_inventory;
}
location ~ ^/api/warehouse/inventory/shelf/[^/]+/box/[^/]+$ { # Box on shelf
proxy_pass http://warehouse_inventory;
}
location ~ ^/api/warehouse/pricing/[^/]+$ { # Price for specific item
proxy_pass http://warehouse_pricing;
}
return 404; # Catch-all
}
# vim: syntax=nginx
# Rewrite rules
#
rewrite ^/api/warehouse/inventory/item/price/(.*) /api/warehouse/pricing/$1;
# Warehouse API
#
location /api/warehouse/ {
# Policy configuration here (authentication, rate limiting, logging...)
#
access_log /var/log/nginx/warehouse_api.log main;
# URI routing
#
location /api/warehouse/inventory {
proxy_pass http://warehouse_inventory;
}
location /api/warehouse/pricing {
proxy_pass http://warehouse_pricing;
}
return 404; # Catch-all
}
# vim: syntax=nginx
# Warehouse API
#
location /api/warehouse/ {
# Policy configuration here (authentication, rate limiting, logging...)
#
access_log /var/log/nginx/warehouse_api.log main;
# URI routing
#
location /api/warehouse/inventory {
proxy_pass http://warehouse_inventory;
}
location /api/warehouse/pricing {
proxy_pass http://warehouse_pricing;
}
return 404; # Catch-all
}
# vim: syntax=nginx
@nginx-gists
Copy link
Author

For a discussion of these files, see Deploying NGINX Plus as an API Gateway, Part 1

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