Engineering
September 20, 2023
24 min read

Kong Konnect RESTful Admin APIs and AWS AppSync GraphQL Services - Part I: Query

Claudio
Claudio Acquaviva
Principal Architect, Kong

GraphQL is a query language to enable applications to fetch data from servers.

In fact, as it isn't tied to any specific database or storage engine, GraphQL can aggregate data from multiple sources to create a natural representation of your data. The representation is a graph. The following image illustrates a typical GraphQL abstraction:

A few common GraphQL use cases include:

  • Composite pattern: Apps can retrieve data from multiple, different storage APIs.
  • API aggregation: GraphQL can be used to combine multiple APIs into a single GraphQL endpoint.
  • No over-fetching or under-fetching: GraphQL brings efficiency and power to mobile applications by offering a single endpoint to query data without multiple requests to the server.
  • Data caching: GraphQL can be used to cache data on the client side to accelerate loading times.
  • Strongly typed schema: All the types supported by the API are specified in the schema in GraphQL Schema Definition Language (SDL).

You can get a bit more familiar with GraphQL by checking out www.graphql.org.

Kong Konnect Objects and APIs

Kong Konnect is an API lifecycle management platform designed from the ground up for the cloud native era and delivered as a service. The management plane (Control Plane) is hosted in the cloud by Kong, while the runtime engine (Data Plane), Kong Gateway is managed by the customers within your preferred network environment.

Kong Konnect Objects

Kong Konnect administrators work with an object model to define their desired traffic management policies. The model comprehends multiple and fundamental objects:

  • Kong Service: This is an abstraction of an existing upstream application.
  • Kong Route: These are added to services to expose and allow access to the underlying application. A Kong Service can have multiple Kong Routes defined.
  • Kong Consumer: This is an entity that makes requests for Kong to proxy. It represents either a user or an external service. A Consumer might have multiple Credentials associated.
  • Kong Plugin: These provide advanced functionality and extend the use of the Kong Gateway, which allows you to add new features to your implementation. Kong Plugins can be applied to Kong Services, Routes, Consumers, and globally. Kong Konnect provides a comprehensive list of plugins to extend the gateway by implementing specific policies
  • Kong Upstream: This represents a virtual hostname and can be used to load balance incoming requests over multiple services, called Targets. An upstream implements health checker and circuit breakers, which are able to enable and disable targets based on their ability or inability to serve requests.
  • There are also other Objects, like Kong Certificates and Keys, SNIs, Vaults, Workspaces, Admins, and Developers.

The model defines some relationships between objects. For example: a Kong Service has multiple Routes and Plugins defined. Likewise, a Kong Upstream defines multiple Targets related to it.

Kong Konnect RESTful Admin APIs and GraphQL model

Kong Konnect provides multiple ways to allow admins to deal with the Kong Objects Lifecycle, including:

  • Konnect Graphical User Interface
  • decK to manage Kong Objects in a declarative fashion
  • RESTful Admin API, a fundamental mechanism for administration purposes

Considering the RESTful Admin API and the Kong object model relationships, a GraphQL query seems a good fit to provide an easier way to manipulate them. That is, with a single GraphQL request, we can query a Kong Service with all its dependencies, including Kong Routes, Plugins, etc.

Moreover, a GraphQL mutation request can also create a new Service along with Routes and Plugins.

In this post, we’ll exercise the Konnect RESTful Admin APIs consumption with AWS AppSync GraphQL Queries. The subsequent parts of this series will focus on the other two operations: Mutations and Subscriptions.

Kong Konnect Authentication

Before starting to send RESTful Admin requests to Kong Konnect, you need to authenticate as an admin. Kong Konnect provides two types of base URLs that are used in Kong Konnect APIs:

  • Global: https://global.api.konghq.com. Used to manage region-agnostic Kong Konnect entities that live in a global database such as Identity Provider (IdP) settings.
  • Region-specific: https://{REGION_CODE}.api.konghq.com. These endpoints are used to manage Konnect entities that live in a specific Kong Konnect region, for example, runtime groups. Service listings are all region-specific entities and therefore must use the region-specific endpoint.

For this blog post, we’re going to use the US region, so the endpoints are located at https://us.api.konghq.com.

Kong Konnect API reference documentation

For specific APIs go to:

Personal access token (PAT)

The recommended method of authentication for Kong Konnect is the personal access token (PAT), which can be obtained from the personal access token page in the Kong Konnect UI. The PAT must be passed in the Authorization header of all requests, for example:

Check the documentation to learn how to register to Konnect and generate a PAT.

Runtime Groups, Services, Routes, and Plugins

This blog post will work with the Services, Routes, and Plugins created in the default Runtime Group. Check the documentation to learn more about Runtime Manager and Runtime Groups.Once you have your PAT generated, you can start sending requests to the Kong Konnect Control Plane. For example, you can check your Runtime Groups with:

AWS AppSync

GraphQL API creation

We're going to get started creating a GraphQL Schema with fundamental types representing the three Kong Objects: Kong Service, Kong Route, and Kong Plugin. The Schema also defines three queries:- service(name): Service -> returns information about a specific Kong Service

  • services: [Service] -> returns a list of all Kong Services created in a specific Runtime Group.
  • runtimegroups: [RuntimeGroup] -> returns all existing Runtime Groups.**

GraphQL API with Empty Schema

Create your GraphQL API with an empty schema first:aws appsync create-graphql-api --name konnect-controlplane --authentication-type API_KEY --region us-west-1``

The expected output is:

You can retrieve the GraphQL API with

Schema Definition Language (SDL)

Now create a konnect.graphql file with our Schema including Types and Queries:

You have to encode the Schema in a base64 format:

Use the base64 string to create the Schema for you GraphQL API:

Check the Schema Creation process with:

You can list the Types with:

Or create a SDL file with:

AWS AppSync VTL and JavaScript Resolvers

A GraphQL Resolver is a function that converts the GraphQL request and payload to fetch information from the underlying data source with its specific communication mechanism and responds back to the consumer. In our case, the data source is the Konnect Control Plane with its RESTful Admin APIs.

AWS AppSync supports two types of Resolvers:

For both types of Resolvers, AppSync provides support for multiple data sources including DynamoDB, Lambda functions, Amazon Aurora, Amazon OpenSearch Service. Besides all these data sources, AppSync also supports generic HTTP endpoints, like the ones Kong Konnect uses to expose its RESTful Admin APIs.

Kong Konnect as an AppSync Data Source

All AppSync queries should be sending requests to the Konnect endpoint. So, the very first thing to do is to define an AppSync Data Source based on the endpoint. Note we’re creating an HTTP type Data Source.

You can check your Data Sources with:

RuntimeGroup HTTP Resolver

Now, we should be able to create our Resolvers. To get started, let's create a VTL Resolver for the runtimegroups Query field. Notice that the Resolver is expecting the Konnect PAT as a Authorization Bearer token:

The data that came from Konnect is available for the Response Mapping Template in the Resolver Context Result Body($ctx.result.body). Check the AppSync documentation to learn more about Resolver Mapping Template Context.

The code refers to the .data section located inside the Body to produce the actual GraphQL query response. As an example, here's the output for the request sent to Konnect directly:

AppSync API Key

We’re now ready to send our first GraphQL request to AppSync. However, since the default AppSync Authorization mechanism is an API Key, we need to create one.

You can get your API Key with:

Send a Request

Let's send a request to AppSync. First of all, get the public AppSync API endpoint:

Now, send a request with curl. Notice we're injecting in the request both AppSync API Key and Kong Konnect PAT. The PAT is injected as a Bearer token, as the Resolver is expecting.

The output should be like this:

The data that came from Kong Konnect is available for the Response Mapping Template in the Resolver Context Result Body($ctx.result.body). You can check them by submitting a request directly to Konnect:

Services HTTP Resolver

Let's create a second VTL Resolver, now for the services Query field. As you can see, the Resolver definition is quite similar to the previous one. The main difference is that this time, the Resolver expects the RuntimeGroup Id as a context parameter.

Send a Request

Use the same endpoint and AppSync API Key to send a new request. Note we’re passing the RuntimeGroup id we got from the previous request as a parameter to the Query:

You should get a list of all current Kong Services:

You can check the AWS AppSync console with the Schema and Resolvers defined:

Service Resolver

With the two fundamental Resolvers in place, let's work with the third one. The service Resolver takes as parameters a service name and the runtime groups the service belongs to. The output includes the two main dependencies of the service: Kong Routes and Kong Plugins.

This time, we’re going to use the APPSYNC_JS Runtime to get a better JavaScript implementation for our Resolver. The AWS AppSync JavaScript Resolvers Overview documentation page provides a nice diagram with the anatomy of a resolver.

The GraphQL Query Request Structure

To get a better understanding of the Resolver implementation, we’re going to take an evolving approach. That is, considering the full Query, we’re going to solve it breaking with three independent steps: (1) Service, (2) Routes and (3) Plugins.

Send a Request

Let's send the Request with no Resolver first:

Since we don't have any Resolver defined, the output would look like this:

Service Section

Let's solve the Service section creating a function to be used by our Resolver. Create a file named service.js with an AppSync Function to process the section. The function takes the two query parameters, runtimegroupid and name to consume the specific Konnect endpoint. The Resolver builds the result based on the Konnect response, adding the runtimegroupid to it, so the functions responsible for the Routes and Plugin Section can use it.

service.js

Create the AppSync function with:

Fetch and save your function Id with:

basic_resolver.js

The Resolver also needs basic request and response functions. Create another file named basic_resolver.js with the functions:

Create the Resolver

Now let's create the Resolver with all functions we've created so far:

Send a Request

If we send the same Request again, we should be able to see the Service Section with its attributes. As expected the Routes and Plugins Sections are still empty.

Routes Resolver

Now, let's solve the Routes Section with another Resolver. We have to consider two things this time:

  • The Routes Section is nested inside the Query structure.
  • Since it's a nested section, the Resolver is related to the Service Field, not to the Query Field as the Service Section is.

dependencies.js

To solve the Routes Section we’re going to create a function that is able to get both Services dependencies, Kong Routes, and Kong Plugins. The function takes the runtimegroupid and serviceid from its parent Service's Resolver available in ctx.source Context field. It also expects a dependency field available in the ctx.stash. Please check the AppSync documentation to learn more about the Resolver Context Object.

Create the function with:

Get and save the function id:

routes.js

Now we need to create a specific routes field specific Resolver. The main responsibility of the function is to set the ctx.stash.dependency field, expected by the dependencies function.

Create the Resolver

The Resolver is ready to be created. The Resolver pipeline includes the previously created dependency function id:

Send a Request

Send the same request we did before to get the Kong Routes defined for our Service. The output should look like this. Notice that we still need to solve the Plugins Section.

Plugins Resolver

The Plugin Resolver works exactly the same way the Routes Resolver does. In fact, it's going to add the same dependencies function into its pipeline. The only difference is that the Resolver has to set the ctx.stash.dependency with a different value, in this case, "plugins"

plugins.js

Create the Resolver

Again, the command to create the Resolver is very similar to the one we ran for the Routes Resolver.

Send a Request

Finally, our request's output should include all Plugins enabled to our Service:

Kong Konnect GraphQL Rate Limiting Advanced Plugin

With all AppSync Resolvers in place we’re able to send as many GraphQL requests as we want. In a real-world scenario, we should have some policies to control that.

In this section, we’re going to deploy a Kong Konnect Data Plane in front of the AWS AppSync in order to enforce some Rate Limiting policies to control the number of requests AppSync should process in a specific period of time.

The final topology would look like this:

In fact, this is a circular deployment where the Konnect Control Plane manages not just the Data Plane but also the Kong Objects (Services, Routes, and Plugins) responsible for controlling the AppSync exposure.

Konnect Runtime Instance

A Konnect Runtime Instance is a single self-managed instance of Kong Gateway that functions as a Data Plane. In Kong Konnect, Runtime Instances are part of Runtime Groups and expose all Kong Objects (Services, Routes, Consumers, Plugins, etc) defined in the Runtime Group it belongs to.

Konnect Runtime Instance deployment

The first step is to deploy a Konnect Runtime Instance. Konnect supports a variety of platforms to run Runtime Instances including Kubernetes, Docker, Linux-based OSes, etc. Please check the Installation Options documentation page to learn more about Runtime Instances.

For this blog post, we’re going to deploy our Runtime Instance on an Amazon EKS Cluster. Click on the Amazon EKS documentation page to learn how to create a cluster with eksctl, the official CLI for Amazon EKS.

After getting your EKS Cluster up and running, log in to Kong Konnect.

Go to "Runtime Manager" -> "default" -> "+ New Runtime Instance" -> "Kubernetes". Click on "Generate Certificate" and save the certificate and private key into corresponding files. For example:

Cluster Certificate: tls.crt

Certificate Key: tls.key

Injecting key and digital certificate

Create a Kubernetes namespace for the data plane.

Create secrets to digital certificate and key.

Konnect Data Plane

Copy and paste this text into a file called values.yaml on your local machine.

Use Helm to deploy the data plane with endpoints provided by Kong Konnect control plane:

Checking the installation

Use kubectl to check the Kubernetes deployment

You should be able to see the new runtime in Kong Konnect control plane:

Send a request to the runtime

Use the load balancer provisioned by AWS to send requests to the data plane:

Kong Gateway Service and Route

Now, let's create a new Kong Service and Route to expose the AWS AppSync endpoint with the Konnect Runtime Instance. The Kong Service will be created in the same Runtime Group where our existing Kong Objects, in our case, the default Runtime Group. You can use the Konnect GUI if you like or, again, the Konnect RESTful API:

Kong Gateway Service

The new graphqlservice Kong Gateway Service is based on the AWS AppSync endpoint. Notice that we inject the Konnect PAT in the request:

Get your new Gateway Service Id with:

Kong Route

Use the Service Id to define the Kong Route:

You can check the Gateway and Route in the Konnect GUI:

AWS Lambda as the AppSync Authorization Method

We’re going to explore another AppSync Authorization Method, replacing the existing one based on API Key to another using an AWS Lambda Function. With this, we’re able to remove the x-api-key header from the request, leaving both AppSync and Konnect Control Plane to check the Bearer token with the PAT instead.

The AWS Lambda Function

First of all, create an IAM role to be used by the Lambda function. Create a role.json file with:

Create the IAM Role with the following command:

AWS Lambda Function

Create another file, named isauthorized.py, with the actual function code written in Python. Notice the function is extremely simple, just checking if the request has a Bearer token injected. Please, bear in mind the Authorization logic can go as complex as we want.

Zip the file:

Create the konnectIsAuthorized Lambda function:

You can invoke the function to test it:

Change the GraphQL API Authorization Mode

Now we’re ready to replace the existing authorization mode with the Lambda function based one:

Send new Requests to the Konnect Runtime Instance

We should get a similar response if we send a request to the Runtime Instance. For a simple test, the curl command uses the -k option to accept the Konnect Self-Signed Digital Certificate. Moreover, we don't need to inject the x-api-key AppSync Header, since our Lambda function is responsible for the AppSync Authorization mode.

GraphQL Rate Limiting Advanced Plugin

So far, we have deployed the Konnect Runtime Instance in front of the AWS AppSync API. However, there's no policy controlling how the GraphQL API has been consumed. Now, it’s time to enable the GraphQL Rate Limiting Advanced Plugin to our Kong Service.

The GraphQL Rate Limiting Advanced plugin provides rate limiting for GraphQL queries. The GraphQL Rate Limiting plugin extends the Rate Limiting Advanced plugin.

Due to the nature of client-specified GraphQL queries, the same HTTP request to the same URL with the same method can vary greatly in cost depending on the semantics of the GraphQL operation in the body. A common pattern to protect your GraphQL API is then to analyze and assign costs to incoming GraphQL queries and rate limit the consumer’s cost for a given time window.

GraphQL query costs are evaluated by introspecting the endpoint’s GraphQL schema and applying cost decoration to parts of the schema tree. Learn more about GraphQL Queries Cost Management on the specific GraphQL Rate Limiting Advanced Plugin documentation page.

Install Redis

As a best practice for Runtime Instance deployments, we should externalize the limits to an external Redis infrastructure. In this sense, all Kubernetes replicas of the deployment will rely on the same limits. To support a configuration like this, the GraphQL Rate Limiting Advanced Plugin provides multiple strategies, including Redis.

You can have your Redis infrastructure running on a totally and independent environment, for example, leveraging the AWS ElastiCache for Redis. To have a simpler deployment, you can run Redis in the same EKS Cluster with this declaration.

Apply GraphQL Rate Limiting Advanced plugin to the Service

Finally, let's enable the plugin to our Service. Notice we’re using a simple limit and window_size to exercise the rate-limiting policy. Also, we can tell where the Redis endpoint is using the Kubernetes Service FQDN, redis.redis.svc.cluster.local.

Send new Requests to the Runtime Instance

This time we’re using the -v option for curl to see the Rate Limiting related Headers: X-Gql-Query-Cost, X-RateLimit-Limit-30 and X-RateLimit-Remaining-30.

If you keep on sending requests to Konnect Runtime Instance you'll get a 429 error code:

Conclusion

Kong Konnect simplifies API management and improves security for all services including GraphQL infrastructure. Try it for free today!

This blog post described Kong Konnect’s capabilities to:

  1. Embedded its RESTful Admin API to support GraphQL for the Konnect Control Plane administration.
  2. Expose and protect an AWS AppSync API with enterprise-wide policies implemented by the Konnect Runtime Instance.

The next blog post of this series will describe how to implement other GraphQL operations: Mutations and Subscriptions. Stay tuned!