Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Roll-your-own API Management Platform with NGINX and Lua

Sean Cribbs
September 24, 2015

Roll-your-own API Management Platform with NGINX and Lua

We recently replaced a proprietary API management solution with an in-house implementation built with NGINX and Lua that let us get to a continuous delivery practice in a handful of months. Learn about our development process and the overall architecture that allowed us to write minimal amounts of code, enjoying native code performance while permitting interactive coding, and how we leveraged other open source tools like Vagrant, Ansible, and OpenStack to build an automation-rich delivery pipeline. We will also take an in-depth look at our capacity management approach that differs from the rate limiting concept prevalent in the API community.

Sean Cribbs

September 24, 2015
Tweet

More Decks by Sean Cribbs

Other Decks in Technology

Transcript

  1. CodeBig 1 API Consumer CDN • traffic shaping • caching

    • access control • rate limiting Vendor APIM
  2. CodeBig 1 API Consumer CDN • traffic shaping • caching

    • access control • rate limiting Vendor APIM Internet Comcast LB • DNS RR • VIP
  3. CodeBig 1 API Consumer CDN • traffic shaping • caching

    • access control • rate limiting Vendor APIM • DMZ intermediary • path-host mapping iAuth Internet Comcast LB • DNS RR • VIP
  4. CodeBig 1 API Consumer CDN • traffic shaping • caching

    • access control • rate limiting Vendor APIM • DMZ intermediary • path-host mapping iAuth Internet Comcast Origin APIs LB • DNS RR • VIP
  5. Lua

  6. -­‐-­‐  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
  7. DC3 VIP DC2 VIP DC1 VIP Cross-Datacenter dc1-­‐vip.    

             A              10.1.0.1 dc2-­‐vip.              A              10.2.0.1 dc3-­‐vip.              A              10.3.0.1
  8. DC3 VIP DC2 VIP DC1 VIP Cross-Datacenter vod vod acct

    acct dc1-­‐vip.              A              10.1.0.1 dc2-­‐vip.              A              10.2.0.1 dc3-­‐vip.              A              10.3.0.1
  9. DC3 VIP DC2 VIP DC1 VIP Cross-Datacenter vod vod acct

    acct dc1-­‐vip.              A              10.1.0.1 dc2-­‐vip.              A              10.2.0.1 dc3-­‐vip.              A              10.3.0.1 vod-­‐dc1.              CNAME      dc1-­‐vip. vod-­‐dc2.              CNAME      dc2-­‐vip.
  10. DC3 VIP DC2 VIP DC1 VIP Cross-Datacenter vod vod acct

    acct dc1-­‐vip.              A              10.1.0.1 dc2-­‐vip.              A              10.2.0.1 dc3-­‐vip.              A              10.3.0.1 vod-­‐dc1.              CNAME      dc1-­‐vip. vod-­‐dc2.              CNAME      dc2-­‐vip. vod-­‐dc1-­‐fo.        CNAME      dc1-­‐vip. vod-­‐dc1-­‐fo.        CNAME      dc2-­‐vip.
  11. DC3 VIP DC2 VIP DC1 VIP Cross-Datacenter vod vod acct

    acct dc1-­‐vip.              A              10.1.0.1 dc2-­‐vip.              A              10.2.0.1 dc3-­‐vip.              A              10.3.0.1 vod-­‐dc1.              CNAME      dc1-­‐vip. vod-­‐dc2.              CNAME      dc2-­‐vip. vod-­‐dc1-­‐fo.        CNAME      dc1-­‐vip. vod-­‐dc1-­‐fo.        CNAME      dc2-­‐vip. vod.                      CNAME      vod-­‐dc1. vod.                      CNAME      vod-­‐dc2.
  12. Concurrent Request Limiting lua_shared_dict            

     counts    50M;   access_by_lua          …      +1   log_by_lua                …      -­‐1
  13. function  TestOAuth1:test_reject_request_when_timestamp_expired()          -­‐-­‐  default  timestamp  is

     01/01/2014  UTC  so  it's  definitely  stale          local  conf  =  Conf:new({validate_timestamp  =  true,  ttl  =  300})          local  header  =  Header:new()          local  req  =  Req:new({oauth_params  =  header})          local  oauth  =  OAuth1:new(conf,  req)          local  res  =  oauth:authorize()          assertEquals(res.code,  Const.HTTP_UNAUTHORIZED)          assertEquals(res.error,  Const.ERROR_EXPIRED_TIMESTAMP)   end   lu  =  LuaUnit.new()   lu:setOutputType("tap")   os.exit(lu:runSuite())
  14. function  get_oauth_params_from_auth_header(env)      env  =  env  or  ngx  

       local  auth_hdrs  =  env.req.get_headers()["Authorization"]      ...
  15. 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)      ...
  16. 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  
  17. Configs in VCS config templates API configs vault
 (keys) Playbooks

    in VCS vip.conf vhost.lua vhost.conf vhost.conf vhost.lua nginx.conf
  18. Configs in VCS config templates API configs vault
 (keys) Playbooks

    in VCS vip.conf vhost.lua vhost.conf vhost.conf vhost.lua nginx.conf
  19. Configs in VCS config templates API configs vault
 (keys) ssh

    Playbooks in VCS vip.conf vhost.lua vhost.conf vhost.conf vhost.lua nginx.conf
  20. Configs in VCS config templates API configs vault
 (keys) ssh

    Playbooks in VCS vip.conf vhost.lua vhost.conf vhost.conf vhost.lua nginx.conf
  21. Impact index=codebig  host=*.cimops.net  source="/var/log/nginx/access.log"  |   eval  d  =  request_time

     -­‐  upstream_response_time  |  
 timechart  span=1m  perc99(d)  max(d)
  22. Impact seconds index=codebig  host=*.cimops.net  source="/var/log/nginx/access.log"  |   eval  d  =

     request_time  -­‐  upstream_response_time  |  
 timechart  span=1m  perc99(d)  max(d)
  23. Impact seconds index=codebig  host=*.cimops.net  source="/var/log/nginx/access.log"  |   eval  d  =

     request_time  -­‐  upstream_response_time  |  
 timechart  span=1m  perc99(d)  max(d) 99th
  24. Impact seconds index=codebig  host=*.cimops.net  source="/var/log/nginx/access.log"  |   eval  d  =

     request_time  -­‐  upstream_response_time  |  
 timechart  span=1m  perc99(d)  max(d) 99th max
  25. Conclusion NGINX + Lua for HTTP middleware Automated test and

    deployment pipeline Concurrent request limiting
  26. Conclusion NGINX + Lua for HTTP middleware Automated test and

    deployment pipeline Concurrent request limiting Operational flexibility