roll your own api management platform with nginx and lua

Post on 12-Aug-2015

44.080 Views

Category:

Software

6 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Roll Your Own API Management Platform with nginx and Lua Jon Moore Senior Fellow, Comcast Cable @jon_moore

access control

capacity management (“rate limiting”)

capacity management (“rate limiting”)

HTTP

Proxy

custom logic!

Lua

--- Validates the OAuth signature!-- @return Const.HTTP_UNAUTHORIZED if either the key or signature is invalid!-- this method is internal and should not be called directly!function _M.validate_signature(self)! local headers = self.req.get_oauth_params()! local key = headers[Const.OAUTH_CONSUMER_KEY]! local keyconf = self.conf.keys[key]! if keyconf == nil then! return {! code = Const.HTTP_UNAUTHORIZED! error = Const.ERROR_INVALID_CONSUMER_KEY! }! end!! local sig = get_hmac_signature(self.req, keyconf.secret)! if sig ~= headers[Const.OAUTH_SIGNATURE] then! return {! code = Const.HTTP_UNAUTHORIZED,! error = Const.ERROR_INVALID_SIGNATURE! }! end!end!

Lua < 3k LOC

testing

function TestOAuth1:test_reject_request_when_signature_invalid()! local header = Header:new()! header[Const.OAUTH_SIGNATURE] = “invalid”! local req = Req:new({oauth_params = header })! local conf = Conf:new()! local oauth = OAuth1:new(conf, req)!! local res = oauth:authorize()! assertEquals(res.code, Const.HTTP_UNAUTHORIZED)! assertEquals(res.error, Const.ERROR_INVALID_SIGNATURE)!end!!lu = LuaUnit.new()!Lu:setOutputType(“tap”)!os.exit(lu:runSuite())!

ngx.log(ngx.ERR, “oops”)!

ngx.log(ngx.ERR, “oops”)!

function get_oauth_params_from_auth_header()!

function get_oauth_params_from_auth_header(env)! env = env or ngx! local auth_hdrs = env.req.get_headers()[“Authorization”]! ...!

function get_oauth_params_from_auth_header(env)! env = env or ngx! local auth_hdrs = env.req.get_headers()[“Authorization”]! ...!

function get_oauth_params_from_auth_header(env)! env = env or ngx! local auth_hdrs = env.req.get_headers()[“Authorization”]! ...!

function TestRequest:test_retrieve_oauth_params_from_header()! local header = [[Oauth realm=“example.com”, ]]! .. [[oauth_consumer_key=“mykey”,]]! .. [[oauth_version=“1.0”]]! local ngx = StubNgx:new({ Authorization = header })! local res = get_oauth_params_from_auth_header(ngx)! assertEquals(“1.0”, res.oauth_version)! ...!

function TestRequest:test_retrieve_oauth_params_from_header()! local header = [[Oauth realm=“example.com”, ]]! .. [[oauth_consumer_key=“mykey”,]]! .. [[oauth_version=“1.0”]]! local ngx = StubNgx:new({ Authorization = header })! local res = get_oauth_params_from_auth_header(ngx)! assertEquals(“1.0”, res.oauth_version)! ...!

function TestRequest:test_retrieve_oauth_params_from_header()! local header = [[Oauth realm=“example.com”, ]]! .. [[oauth_consumer_key=“mykey”,]]! .. [[oauth_version=“1.0”]]! local ngx = StubNgx:new({ Authorization = header })! local res = get_oauth_params_from_auth_header(ngx)! assertEquals(“1.0”, res.oauth_version)! ...!

function TestRequest:test_retrieve_oauth_params_from_header()! local header = [[Oauth realm=“example.com”, ]]! .. [[oauth_consumer_key=“mykey”,]]! .. [[oauth_version=“1.0”]]! local ngx = StubNgx:new({ Authorization = header })! local res = get_oauth_params_from_auth_header(ngx)! assertEquals(“1.0”, res.oauth_version)! ...!

function TestRequest:test_retrieve_oauth_params_from_header()! local header = [[Oauth realm=“example.com”, ]]! .. [[oauth_consumer_key=“mykey”,]]! .. [[oauth_version=“1.0”]]! local ngx = StubNgx:new({ Authorization = header })! local res = get_oauth_params_from_auth_header(ngx)! assertEquals(“1.0”, res.oauth_version)! ...!

test harness

nginx-oauth1.conf!

test harness

nginx-oauth1.conf!

test harness

nginx-oauth1.conf!

test harness

nginx-oauth1.conf!

test harness

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

def spec_using_valid_oauth_credentials(self, harness)! auth = OAuth1(“mykey”, “mysecret”)! body_data = “{‘function’: ‘tick’}”! harness.reset_data()! response = requests.post(root_url, data=body_data, auth=auth,! headers={‘Content-Type’:! ‘application/json’})! assert response.status_code == 200! assert “Authorization” in harness.forwarded_headers! assert “Oauth” in harness.forwarded_headers[“Authorization”]! assert harness.forwarded_body == body_data!

capacity management

N = XR # concurrent

requests

transaction rate

response time

API Mgmt client origin

1s 2 req/s

API Mgmt client origin

1s 2 req/s

N = XR = 2 req/s × 1s = 2 req

API Mgmt client origin

1s 2 req/s

N = XR = 2 req/s × 1s = 2 req

API Mgmt client origin

1s 2 req/s

N = XR = 2 req/s × 1s = 2 req

API Mgmt client origin

1s 2 req/s

N = XR = 2 req/s × 1s = 2 req

API Mgmt client origin

1s 2 req/s

N = XR = 2 req/s × 1s = 2 req

API Mgmt client origin

1s 2 req/s

API Mgmt client origin

10s 2 req/s

N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

✗ N = XR = 2 req/s × 10s = 20 req

API Mgmt client origin

10s 2 req/s

✗ ✗ N = XR = 2 req/s × 10s = 20 req

access_by_lua ...!

log_by_lua ...!

+1

-1

deployment

Version Control

templates vault (keys)

nginx.conf!ssh!

architecture

HAProxy HAProxy

VIP

. . .

<API>

DC1 DC2 DC3

VIP VIP VIP

entry-vip-dc1. A 10.1.0.1!

<foo> <foo> <bar>

<bar>

foo-dc1. CNAME entry-vip-dc1.!

foo. CNAME foo-dc1.! (GSLB)

entry-vip-dc2. A 10.2.0.1!entry-vip-dc3. A 10.3.0.1!

DC1 DC2 DC3

VIP VIP VIP

entry-vip-dc1. A 10.1.0.1!

<foo> <foo> <bar>

<bar>

foo-dc1. CNAME entry-vip-dc1.!

foo. CNAME foo-dc1.! (GSLB)

entry-vip-dc2. A 10.2.0.1!entry-vip-dc3. A 10.3.0.1!

DC1 DC2 DC3

VIP VIP VIP

entry-vip-dc1. A 10.1.0.1!

<foo> <foo> <bar>

<bar>

foo-dc1. CNAME entry-vip-dc1.!

foo. CNAME foo-dc1.! (GSLB)

entry-vip-dc2. A 10.2.0.1!entry-vip-dc3. A 10.3.0.1!

DC1 DC2 DC3

VIP VIP VIP

entry-vip-dc1. A 10.1.0.1!

<foo> <foo> <bar>

<bar>

foo-dc1. CNAME entry-vip-dc1.!

foo. CNAME foo-dc1.! (GSLB)

entry-vip-dc2. A 10.2.0.1!entry-vip-dc3. A 10.3.0.1!

DC1 DC2 DC3

VIP VIP VIP

entry-vip-dc1. A 10.1.0.1!

<foo> <foo> <bar>

<bar>

foo-dc1. CNAME entry-vip-dc1.!

foo. CNAME foo-dc1.! (GSLB)

entry-vip-dc2. A 10.2.0.1!entry-vip-dc3. A 10.3.0.1!

DC1 DC2 DC3

VIP VIP VIP

entry-vip-dc1. A 10.1.0.1!

<foo> <foo> <bar>

<bar>

foo-dc1. CNAME entry-vip-dc1.!

foo. CNAME foo-dc1.! (GSLB)

entry-vip-dc2. A 10.2.0.1!entry-vip-dc3. A 10.3.0.1!

Roll Your Own API Management with nginx and Lua

• nginx + Lua => great for HTTP middleware with a small amount of custom logic

• Automated test and deployment pipeline with Vagrant, Python, and Ansible

• Concurrent request limiting, not rate limiting • Network architecture with operational flexibility

Presentation title (optional) 67

top related