Differentiate API environments

SaaS On-premises 2.0
By the end of this tutorial, you will be able to set up 3scale to differentiate between your production and staging environments.

In order to support a formal development lifecycle for your developer community, you may want to provide separate staging and production environments to access your API. So during development and testing, the API may be used without the adverse consequences of operating in a production environment. This is different from managing the dev, test, stage, deploy cycle of the API itself.

Overview

There are several options to provide differentiated staging and production environments for your developer community. 3scale supports a lot of flexibility on how to implement this. Once you decide on which approach is right for you, you can implement this within the NGINX gateway as a custom extension within the config files.

This tutorial describes two ways of providing differentiated environments:

Option 1: Staging restricts access based on rate limits or methods

This option is simple to set up and simple to use. The main limitations are that both environments share the same backend, and in reporting views, the production and staging traffic is mixed together.

For this option, you would create one Application plan for each environment and set the rate limits and availability of methods/metrics depending on the environment (see setting rate limits). For example in the staging mode to provide lower rate limits and optionally restrict access to any methods that are not desirable for the staging, for example expensive resources, or write/delete methods.

On your integration side, you would have to implement the mapping to the corresponding 3scale methods. Remember that this is only simulating environments and not hitting two different endpoints.

For example assuming there is a developer app under the staging plan which is restricted from "write" calls, this is the auth call to 3scale representing the mapping for POST to /words.....:

curl -v -X GET "http://su1.3scale.net/transactions/authrep.xml?provider_key=PROVIDER_KEY&app_id=APP_ID&app_key=APP_KEY&usage[words-write]=1"

The response will be 409 with the body:

<status>
  <authorized>false</authorized>
  ..

Whenever desired the plan can be upgraded from staging to production without coding changes by the developer:

  1. A self-service plan change in the developer portal.
  2. Request to the API provider to make the plan change.
  3. Plan change determinded unilaterally by the API provider.

This will depend on your service settings.

Option 2: Application plan determines API call routing to staging or production environment

This Option only refers for the downloadable Nginx configuration files.

This option allows differentiation of the API backend for each environment. The operational use is just as simple as option 1. The main difference is that the implementation is slightly more complicated (requiring custom modifications to the NGINX config files). Due to the need for NGINX to parse the response bodies, there will also be a performance hit.

In this scenario, the backend provides different response logic for the two modes, but the developer should not have to make any coding changes to switch between the two environments. 3scale achieves this by using the NGINX gateway to route calls based on the authorization response from 3scale indicating whether production calls are enabled or disabled in the respective application plan. For example, when an app under the staging plan makes a call, the gateway does the auth request to 3scale not knowing if this is a call to staging or production. The call might look like this:

curl -v -X GET "http://su1.3scale.net/transactions/authrep.xml?provider_key=PROVIDER_KEY&app_id=APP_ID&app_key=APP_KEY&usage[words]=1"
and the response
<authorized>true</authorized>
  <plan>Sandbox</plan>
...
And the response is parsed for 'plan' to determine whether to route the call to the staging or production backend:

The next steps assume you have two environments called exactly "Staging" and "Production". If you want to use different names, define them appropriately in the API section of your Admin Portal and verify the returned value from the authorization call.

On the NGINX side, you'll have to apply some modifications to the configuration files generated by 3scale. First, define a new upstream.

upstream production_upstream {
  server production-environment-url-with-port max_fails=5 fail_timeout=30;
}
upstream sandbox_upstream {
  server sandbox-environment-url-with-port max_fails=5 fail_timeout=30;
}
Next, assign the server name for your services (not obligatory if you have only one server).
listen 80;
    ## CHANGE YOUR SERVER_NAME TO YOUR CUSTOM DOMAIN OR LEAVE IT BLANK IF ONLY HAVE ONE
    server_name YOUR-SERVICE-DOMAIN-FOR-NGINX;
Where YOUR-SERVICE-DOMAIN-FOR-NGINX will be the domain(s) assigned to the server where NGINX is hosted. Having this we will have to specify the .lua file path on your server in the 'location: /' part of the config file:

## CHANGE THE PATH TO POINT TO THE RIGHT FILE ON YOUR FILESYSTEM
access_by_lua_file LUA-SYSTEM-PATH;

With the .conf file customization finished, you can pass to the .lua file, where the logic responsible for conditional proxy pass resides. Find the line starting with function authrep(params, service) and inside that function definition apply the following changes:

  • Comment out lines like here:
    -- if is_known ~= 200 then
      local res = ngx.location.capture("/threescale_authrep", { share_all_vars = true })
    
      --    -- IN HERE YOU DEFINE THE ERROR IF CREDENTIALS ARE PASSED, BUT THEY ARE NOT VALID
      --    if res.status ~= 200 then
      --       -- remove the key, if it's not 200 let's go the slow route, to 3scale's backend
      --       api_keys:delete(ngx.var.cached_key)
      --       ngx.status = res.status
      --       ngx.header.content_type = "application/json"
      --       error_authorization_failed(service)
      --    else
      --              api_keys:set(ngx.var.cached_key,200)
      --    end
    
      --         ngx.var.cached_key = nil
      -- end
    Note that the second line is NOT COMMENTED.
  • Just after these commented lines, add the following code:
    local s = ngx.re.match(res.body,[=[<plan>Sandbox]=])
    local p = ngx.re.match(res.body,[=[<plan>Production]=])
    
    if p then
      ngx.var.proxy_pass = "http://production_upstream"
    elseif s then
      ngx.var.proxy_pass = "http://sandbox_upstream"
    end
This code uses a regular expression to match the response with the plan's name. If you want to experiment more with regular expressions try Rubular.

Now the calls registering usage in certain environments will automatically hit these environments without any additional changes on the developer's side. As soon as the application plan is switched from staging to production or vice versa, the API calls will automatically be re-routed to the correct backend.

Note

If any of your environments reside on a hosted server instance (e.g. Heroku, Google app, etc.) you will have to do a host name rewrite to send a proper host name in the headers. To do this, add the following lines:

  • In .conf file under the location: / part add set $host null;
  • In .lua file in the code added by you add:
    ngx.var.host = "STAGING-HOSTNAME" in the if plan == "Staging" condition (STAGING-HOSTNAME can be e.g. 'application.herokuapp.com')
    ngx.var.host = "PRODUCTION-HOSTNAME" in the if plan == "Production" condition.