In the previous post, we looked at API Management with Kong and the Kong Ingress Controller. We did not care about security and exposed a sample toy API over a public HTTP endpoint that also required an API key. All in the clear, no firewall, no WAF, nothing… 👎👎👎
In this post, we will expose the API over TLS and configure Kong to use a CloudFlare origin certificate. An origin certificate is issued and trusted by CloudFlare to connect to the origin, which in our case is an API hosted on Kubernetes.
The API consumer will not connect directly to the Kubernetes-hosted API exposed via Kong. Instead, the consumer connects to CloudFlare over TLS and uses a certificate issued by CloudFlare that is fully trusted by browsers and other clients.
The traffic flow is as follows:
Consumer --> CloudFlare (TLS with fully trusted cert, WAF, ...) --> Kong Ingress (TLS with origin cert) --> API (HTTP)
Refer to the previous post for installation instructions. The YAML files to configure the Ingress, KongIngress, Consumer, etc… are almost the same. The Ingress resource has the following changes:
- We use a new hostname api.baeke.info
- We configure TLS for api.baeke.info by referring to a secret called baeke.info.tls which contains the CloudFlare origin certificate.
- We use an additional Kong plugin which provides whitelisting of CloudFlare addresses; only CloudFlare is allowed to connect to the Ingress
Here is the full definition:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: func namespace: default annotations: kubernetes.io/ingress.class: kong plugins.konghq.com: http-auth, whitelist spec: tls: - hosts: - api.baeke.info secretName: baeke.info.tls # cloudflare origin cert rules: - host: api.baeke.info http: paths: - path: /users backend: serviceName: func servicePort: 80
Here is the plugin definition for whitelisting with the current (June 15th, 2019) list of IP ranges used by CloudFlare. Note that you have to supply the addresses and ranges as an array. The documentation shows a comma-separated list! 🤷♂️
apiVersion: configuration.konghq.com/v1 kind: KongPlugin metadata: name: whitelist namespace: default config: whitelist: - 220.127.116.11/20 - 18.104.22.168/22 - 22.214.171.124/22 - 126.96.36.199/22 - 188.8.131.52/18 - 184.108.40.206/18 - 220.127.116.11/20 - 18.104.22.168/20 - 22.214.171.124/22 - 126.96.36.199/17 - 188.8.131.52/15 - 184.108.40.206/12 - 220.127.116.11/13 - 18.104.22.168/22 plugin: ip-restriction
I also made a change to the KongIngress resource, to only allow https to the back-end service. Only the route section is shown below:
route: methods: - GET regex_priority: 0 strip_path: true preserve_host: true protocols: - https
In the previous post, the protocols array contained the http value.
Note: for whitelisting to work, the Kong proxy service needs externalTrafficPolicy set to Local. Use kubectl edit svc kong-kong-proxy to modify that setting. You can set this value at deployment time as well. This might or might not work for you. I used AKS where this produces the desired outcome.
Get the external IP of the kong-kong-proxy service and create a DNS entry for it. I created a A record for api.baeke.info:
Make sure the orange cloud is active. In this case, this means that requests for api.baeke.info are proxied by CloudFlare. That allows us to cache, enable WAF (web application firewall), rate limiting and more!
In the Firewall section, WAF is turned on. Note that this is a paying feature!
In Crypto, Universal SSL is turned on and set to Full (strict).
Full (strict) means that CloudFlare connects to your origin over HTTPS and that it expects a valid certificate, which is checked. An origin certificate, issued by CloudFlare but not trusted by your operating system is also valid. As stated above, I use such an origin certificate at the Ingress level.
The origin certificate can be issued and/or downloaded from the Crypto section:
I created an origin certificate for *.baeke.info and baeke.info and downloaded the certificate and private key in PEM format. I then encoded the contents of the certificate and key in base64 format and used them in a secret:
apiVersion: v1 kind: Secret metadata: name: baeke.info.tls namespace: default type: kubernetes.io/tls data: tls.crt: base64-encoded-cert tls.key: base64-endoced-key
As you have seen in the Ingress definition, it referred to this secret via its name, baeke.info.tls.
When a consumer connects to the API, the fully trusted certificate issued by CloudFlare is used:
We also make sure consumers of the API need to use TLS:
With the above configuration, consumers need to securely connect to https://api.baeke.info at CloudFlare. CloudFlare connects securely to the origin, which is the external IP of the ingress. Only CloudFlare is allowed to connect to that external IP because of the whitelisting configuration.
Testing the API
Let’s try the API with the http tool:
All sorts of headers are added by CloudFlare which makes it clear that CloudFlare is proxying the requests. When we don’t add a key or specify a wrong one:
The key is now securely sent from consumer to CloudFlare to origin. Phew! 😎
In this post, we hosted an API on Kubernetes, exposed it with Kong and secured it with CloudFlare. This example can easily be extended with multiple Kong proxies for high availability and multiple APIs (/users, /orders, /products, …) that are all protected by CloudFlare with end-to-end encryption and WAF. CloudFlare lends an extra helping hand by automatically generating both the “front-end” and origin certificates.
In a follow-up post, we will look at an alternative approach via Azure Front Door Service. Stay tuned!