End-to-End Security using OCI WAF, OCI API Gateway and OCI Functions

February 19, 2020 | 11 minute read
Muhammad Abdel-Halim
Principal Cloud Technology Architect
Text Size 100%:

Introduction

I'm going to start this blog by making 3 bold claims:

  1. The future is Serverless!

  2. API gateways are the best way to manage and expose Serverless endpoints to the internet!

  3. Anything facing the internet should be behind a WAF!

Let's do a quick review on Oracle Cloud Infrastructure's offerings that address these claims.

  1. Serverless: Oracle offers Oracle Functions which is based on the open-source Fn Project: is an open source, container native, serverless platform that can be run anywhere. It is easy to use and supports every programming language, and is extensible and quite performant. The tooling is installed locally on your machine. Fn functions can be tested locally and then the Cli tooling can be used to deploy that function to Oracle Functions in the cloud. 

  2. API Gateway: Oracle OCI API Gateway which is a fully managed regional API gateway service that is used to provide protected RESTful API endpoints for Oracle Functions, Oracle Kubernetes Engine, and in fact any other service or endpoint running on Oracle Cloud Infrastructure.

  3. WAF: Oracle OCI offers OCI WAF which is a PCI compliant global security service that protects applications from malicious and unwanted internet traffic. It can protect any internet facing endpoint, providing consistent rule enforcement across all customer's applications.

In this blog we will discuss a simple but typical usecase that traverses these 3 technologies and illustrates how these 3 technologies/layers can work together. We will cover the 3 layers in 3 separate sections below. The idea here is to skim the surface and not go into too much detail, just to give you a feel of how WAF and API Gateway can work together to offer authentication and SQL Injection protection for backend functions exposed as RESTful endpoints.  

The Fn Layer

In this scenario we will develop a simple function that takes person's name as an input argument, and outputs a welcome message after querying a database for the Person's city. The function is intentionally developed to expose a security vulnerability, namely SQL Injection attacks (about which we'll discuss below further). The function is written in GO. I will not get into the details of how to setup Fn tooling locally and how to setup the compartments and IAM policy permissions in OCI for functions deployment in your tenancy. This is covered in detail in the OCI documentation (here).

So now the function is deployed in a compartment in our tenancy inside a public subnet. Since the function is deployed in a public subnet, we can access it on a public endpoint on the Oracle Functions runtime. To find this endpoint you need to run this command using your FN Cli.

fn inspect function test gofn
{
  "annotations": {
    "fnproject.io/fn/invokeEndpoint": "https://qwdgwqzgoda.us-ashburn-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.iad.aaaaaaaaadfc7vtbvcvitby3xctv3mefeee6ncxkp2n3pbfd6mz7cx7vojva/actions/invoke",
    "oracle.com/oci/compartmentId": "ocid1.compartment.oc1..aaaaaaaa2gw4xhf6itzrzl6ndozuyzlwvnffggwi65n6ot52rcqiok4o5gmq",
    "oracle.com/oci/imageDigest": "sha256:7f6cb8ffdcd29c3b9541a90306a00d511aa13d64e9961c1c3c5b4dc18e0cd928"
  },
  "app_id": "ocid1.fnapp.oc1.iad.aaaaaaaaafs4w6v4qch46xwx7xdo7crtukpdbxtpe577k6ok7qwdgwqzgoda",
  "created_at": "2020-01-12T16:33:27.323Z",
  "id": "ocid1.fnfunc.oc1.iad.aaaaaaaaadfc7vtbvcvitby3xctv3mefeee6ncxkp2n3pbfd6mz7cx7vojva",
  "idle_timeout": 30,
  "image": "iad.ocir.io/ociateam/sphinky/gofn:0.0.13",
  "memory": 128,
  "name": "gofn",
  "timeout": 30,
  "updated_at": "2020-02-18T23:46:12.535Z"
}

Let's see how we can call this function. 

  1. Through the local tooling using the Fn Cli. Note that in this approach we don't need to explicitly sign the Fn request. 

    echo -n '{"name":"Chris"}' | fn invoke test gofn --content-type application/json
    {"message": "Hello Chris from Boston"}
  2. By using the OCI Cli. The OCI Cli will send a RAW request to the --target-uri endpoint specified on the command line.

    oci raw-request --target-uri https://qwdgwqzgoda.us-ashburn-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.iad.aaaaaaaaadfc7vtbvcvitby3xctv3mefeee6ncxkp2n3pbfd6mz7cx7vojva/actions/invoke --http-method POST --request-body '{"name":"Chris"}'
    {
      "data": {
        "message": "Hello Chris from Boston"
      },
      "headers": {
        "Connection": "keep-alive",
        "Content-Length": "38",
        "Content-Type": "text/plain; charset=utf-8",
        "Date": "Fri, 07 Feb 2020 20:11:50 GMT",
        "Fn-Call-Id": "01E0GMEG521BT0T1RZJ000JD7T",
        "Fn-Fdk-Version": "fdk-go/0.0.1",
        "Opc-Request-Id": "F57E8F41E7244302997B556845ACB068/01E0GMEG2E1BT0T1RZJ000JD7R/01E0GMEG2E1BT0T1RZJ000JD7S"
      },
      "status": "200 OK"
    }
  3. Directly sending a signed RAW request to the Fn endpoint from a client.That signature will of course be based on your private OCI key. The signing string is constructed from parts of the request, and then signed using your private key via the RSA-SHA256 algorithm. OCI offers various SDKs that can help you with this. 

Notice that the 3 approaches described above have one thing in common. Basically the client/user has to be authenticated using the Signature Authentication method, which means that they all require that the client have access to an OCI private key that maps to a principal or user in OCI. There are 3 types of principals in OCI: root principals, IAM users and instance principals. In the examples you've seen above, I was using my OCI private key which maps to my IAM user principal in OCI.

Now. Let's say we have as an external client a web application that authenticates its users via SSO against IDCS (or OKTA for that matter). If the application doesn't want to do identity propagation, it can use the Signature Authentication method above and authenticate using a user principal in OCI that is specifically provisioned for this application (like a service account). The web application will then sign all requests using the corresponding private key which it will be responsible for persisting and protecting.

But what if the application wants to do identity propagation? What if it wants to authenticate with the Function using the identity of the logged in user? In that case, the web application will want to propagate a user token as a bearer token in an Authorization header in the request to the Function. But as things stand, this will not fly. Oracle Functions only accept Signed Requests with a Signature Authorization header and cannot interrogate bearer token Authorization headers. So we need to introduce a gateway layer in front of the Function that can handle this, and verify/introspect the Authorization token before passing on the request to the backend Function. 

The API Gateway Layer

The API gateway layer helps you manage and centralise access to your API ecosystem through one single point of entry for all clients. It also helps to centralise the management of API monitoring, rate limiting, security (Authentication and Authorization) and other policies. 

In this end-to-end scenario we will focus on how the API gateway layer can be used to implement security for a deployed Function endpoint. As we mentioned above, if we want to do identity propagation from a client, we need to have a layer in front of the Function that can interrogate and verify/assert the propagated authorization token.

Let's assume that we've setup the API Gateway and have a deployment already configured with a /hello route to our backend Fn. (You can check the links below to learn more about how to setup and deploy Functions and API gateways). We need now to add authentication logic to this deployment. The way we do this is through Request Policies. OCI API gateway allows you to configure an Authentication Request Policy that can reference a custom Oracle Function that encapsulates the required authentication logic. This logic is custom, and in this case we will build a Function called <idcs-assert> that verifies an inbound token through remote introspection against IDCS (in our case the JWT token issuer is IDCS; but it could be any other OAUTH provider like OKTA e.g.).

Please note that token verification can happen in two ways:

  1. In-situ verification: Authentication Function parses token and checks and verifies the token signature in situ. But this means that the Authentication Function will need to download the OAuth provider's signing keys from the provider's public /jwk endpoint.

  2. Introspection verification: Authentication Function sends token back to OAuth provider for verification. No need to download signing keys. This is actually the better approach; for two reasons. First: assuming that an OAuth token is always going to be JWT is not safe. The OAuth spec actually assumes that they are opaque. So the Resource Server will in that case not be able to verify/validate them on its own. Second: It's good practice to always introspect the token remotely against the issuing provider, as the provider may have revoked the token in the meantime. So it's always good and safe practice to always get the issuing provider itself to do the verification on the token and confirm that it's still valid.

In our case we will implement an Authentication Function that leverages the introspection approach. We will use Node.js. Before we delve into the details of setting up this Authentication Fn, let's have a quick look at the architecture so far. Below is an outline of the API gateway layer with custom Authentication policy and the backend Fn.

Steps to setup Authentication Function for an API gateway deployment:

1. Click Edit button in the Authentication section under API Request Policies.

2. Populate Authentication Policy details. You'll need to reference the Fn application that contains your custom authentication Function and select it. The 'Authentication Token' field refers to the source of the token that will be passed to the custom function for introspection. In this case its the Authorization Header.

The custom Introspection Function:

The function has been written using Node and deployed using the Fn Cli using a node runtime. Node functions are faster and more efficient that Java functions, hence the use of Node. The function uses the fdk (the Oracle Functions Development Kit). A handler function needs to be passed to the fdk.handle() method. The handler will then asynchronously capture the JWT token from the fdk input and then return a Promise that will fire an introspection request on the /oauth2/v1/intropsect endpoint of the IDCS OAuth provider and returns a JSON response that looks like this:

{"active": true,
"principal": ,
"scope": [ "scope1", "scope2"],
"clientId": <clientId>,
"expiresAt": <timestamp>,
...}

I will explain in a separate blog, in more detail, how to implement this Authentication Function and how to handle asynchronous calls from Fn using the Node.js Fn fdk .

So now we have the API gateway layer all setup, it's time to test!

If you're eagle-eyed you will have noticed that the second test has a SQL injection attack embedded in the payload that has resulted in the display of all users in the <<USERS>> table in my sample database (obviously the backend Fn itself has no security in-depth and has not been hardened to prevent injection attacks; and that's intentional for the purpose of this usecase). This leads on to the final piece of the puzzle, which is WAF. Let's see what WAF has to offer.

The WAF Layer

It's always advisable to hide all publicly accessible endpoints behind a WAF layer. WAF serves as an extra layer of security on top of the underlying layers. It's not an excuse for neglecting security at depth; but it provides that extra layer of protection from malicious and unwanted internet traffic, and does that consistently and across your whole API surface area protecting against different types attacks, for example those that might not have been necessarily accounted for in our publicly accessible services.

Take as an example the injection attack we saw above when we were testing our API endpoint directly against the API gateway. SQL injection attacks can be very damaging (whether exfiltration attacks where the hacker attempts to manipulate input strings to inject a SQL statement(s) that exfiltrate valuable data from an underlying database, OR destructive attacks where the attacker injects destructive SQL statements that destroy or alter data). In our example above, the 'attacker' launched an exfiltration attack on our underlying database and extracted all the users' data. And that's a serious breach!

Let's see now how we can configure WAF to intercept these types of attacks and block them. I'm not going to go into the details of how to setup a WAAS Policy in OCI along with the required DNS setup etc (this has already been covered in a previous blog; here). Instead I'll just focus on how to setup the Protection Rules, explain a little bit about the OWASP rules and their relevance to API surface area protection; and how to monitor WAF logs in near real-time.

Below is our solution architecture after the introduction of the WAF layer.

OWASP rules and API security:

OWASP (Open Web Application Security Project) is a nonprofit organisation that works to improve security of software through community led open source projects. One such project is the OWASP API security project. This project provides a high level classification of security vulnerabilities and risks for application APIs. For each vulnerability classification, OWASP recommends a set of  mitigation strategies and solutions (here).

For example, one of the OWASP classifications is (API8:2019 Injection). Under this classification, OWASP recommends several tactics including: "Validate incoming data using sufficient filters to only allow valid values for each input parameter". To this effect WAF provides us with several Protection Rules covering different types of Injection attack, including Protection Rule (950007) for Blind SQL Inject Attacks. This is the Rule we'll be using in this example.

How to switch on Protection Rules:

Let's see now how we can switch on an OWASP Protection Rule in WAF.

In the screenshot below we select the Blind SQL Injection Rule and configure it to BLOCK traffic rather than just only detect.

After the Rule is selected we will need to publish the changes.

Once the WAF policy is updated and active we can then test. You'll see below that the user is trying to append a SQL statement to the input payload. You'll notice that the response code is now 403; and that WAF is blocking our traffic. Viola! You'll also see in the logs below that the inbound SQL Injection attack was detected and source ip logged. Alarms and alerts can be configured. We'll be discussing more about this in a later blog.

Conclusion

In this brief blog we've showcased how WAF and API gateway can work together to help you expose your backend Oracle Functions securely; and protect your business-critical functions against cyber attacks and malicious actors. 

The API gateway layer helps you centralise the management and monitoring of your Functions and microservices, and also allows you to configure custom authentication/verification logic; e.g. integrating into your OAuth Security Providers for token introspection.

And we've seen how the WAF layer, with the simple activation of an OWASP Protection Rule, can detect and block injection attacks. This, in addition to security at depth, provides the extra security required to ensure full protection.

So, in conclusion, this usecase is a very simple example of a typical stack that embodies the 3 maxims that I alluded to at the start of my blog, and a clear illustration of how one can leverage these maxims in order to bring about a layered approach to implementing security against cyber attacks. 

Stay tuned for more OCI and Cloud-Native security-related content.

Useful Links

  1. Creating, deploying, and invoking a HelloWorld Function on OCI (here).

  2. Configuring security policies for OCI API gateways (here).

  3. Configuring security policies for OCI Functions (here).

  4. Configuring WAF policies in OCI (here).

Muhammad Abdel-Halim

Principal Cloud Technology Architect


Previous Post

End-to-End Monitoring of applications running on Oracle Cloud Infrastructure Part 2

Johannes Murmann | 3 min read

Next Post


Extending Oracle Blockchain Events with OCI - Part 1 (Introduction)

Tamer Qumhieh | 5 min read