How to Use KongCustomEntity CRD for JWT Signer Plugin With KIC

In my previous post, I demonstrated how to persist private keys for the JWT Signer plugin when Kong is deployed in DB-less mode by manually writing the Kong declarative config. But what if you’re using the Kong Ingress Controller (KIC) to generate Kong configurations?

There are two options:

  • You can host the keys in a separate deployment within the same cluster, as I previously demonstrated.
  • Alternatively, you can leverage the new KongCustomEntity CRD to mthe keys more seamlessly.

In today’s post, I’ll show you how to generate keys and use the KongCustomEntity CRD to create the jwt_signer_jwks entity, which will then be utilized by the JWT Signer plugin.

Let’s get started.

Prerequisites:

  • A Kubernetes cluster (I use KinD)
  • CLIs
    • kubectl
    • helm
    • yq
    • envsubst
  • Python
  • (optional) nix

Install Kong

Add Helm Repository

1
2
helm repo add kong https://charts.konghq.com
helm repo update

Deploy Kong

We will use the kong/ingress chart to deploy Kong in DB-less mode with the Kong Ingress Controller.

1
2
3
4
5
6
7
8
helm upgrade -i kong kong/ingress \
--namespace kong \
--set gateway.image.repository=kong/kong-gateway \
--set gateway.image.tag=3.8 \
--set gateway.env.router_flavor=expressions \
--set gateway.manager.enabled=false \
--set controller.ingressController.tag=3.3 \
--create-namespace

Apply the Kong License

If you have an enterprise license for Kong, you can apply it using the KongLicense CRD. Assuming your license is stored in a license.json file, you can use the following command to set it in an environment variable:

1
export KONG_LICENSE_DATA=$(cat license.json)

Next, apply the license by running the following command:

1
2
3
4
5
6
7
8
envsubst << EOF | kubectl apply -f -
apiVersion: configuration.konghq.com/v1alpha1
kind: KongLicense
metadata:
name: kong-license
namespace: kong
rawLicenseString: '$KONG_LICENSE_DATA'
EOF

With Kong set up, let’s proceed to generate the JWKs.

Generate keys

Here is a simple Python script to generate a YAML file (private.yaml) containing both a RS256 and an ES256 private key.

You’ll need the following Python libraries:

  • joserfc
  • pyyaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from joserfc.jwk import RSAKey, ECKey
import json
import os
import yaml

def quoted_presenter(dumper, data):
if data in ['y', 'n']:
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"')
return dumper.represent_scalar('tag:yaml.org,2002:str', data)

yaml.add_representer(str, quoted_presenter)

def generate_rsa_key(key_size, algorithm):
rsa_key = RSAKey.generate_key(key_size, algorithm)
rsa_key.ensure_kid()
return rsa_key

def generate_ecc_key(curve, algorithm):
ecc_key = ECKey.generate_key(curve, algorithm)
ecc_key.ensure_kid()
return ecc_key

def export_public_key(key):
return key.as_dict(private=False)

def export_private_key(key):
return key.as_dict(private=True)

def save_private_keys_to_yaml(private_keys, directory):
jwk_private_keys = {"keys": private_keys}
yaml_private_key_path = os.path.join(directory, "private.yaml")

with open(yaml_private_key_path, "w") as f:
yaml.dump(jwk_private_keys, f, default_flow_style=False)

def main():
current_dir = os.path.dirname(os.path.abspath(__file__))

rsa_key = generate_rsa_key(2048, {"alg": "RS256"})
ecc_key = generate_ecc_key("P-256", {"alg": "ES256"})

rsa_public_key = export_public_key(rsa_key)
rsa_private_key = export_private_key(rsa_key)
ecc_public_key = export_public_key(ecc_key)
ecc_private_key = export_private_key(ecc_key)

public_keys = [rsa_public_key, ecc_public_key]
private_keys = [rsa_private_key, ecc_private_key]

save_private_keys_to_yaml(private_keys, current_dir)

if __name__ == "__main__":
main()

Create demo app

For this demo, I will create a deployment with go-httpbin image and expose it with an Ingress.

1
2
3
4
kubectl create deployment httpbin --image=mccutchen/go-httpbin
kubectl expose deployment httpbin --name httpbin-svc --port 8080
kubectl create ingress httpbin-route --class kong --rule '/test*=httpbin-svc:8080'
kubectl annotate ingress httpbin-route konghq.com/strip-path=true

Apply the Kong config

Create KongCustomEntity

We’ll store the keys in a KongCustomEntity. Let’s start by creating a template YAML file (template.yaml), and we’ll use yq to merge the keys we generated in the previous step.

1
2
3
4
5
6
7
8
9
10
11
apiVersion: configuration.konghq.com/v1alpha1
kind: KongCustomEntity
metadata:
namespace: default
name: jwt-signer-demo-keyset
spec:
type: jwt_signer_jwks
controllerName: kong
fields:
name: demo
keys: []

Run the following command to populate the keys field with the content from private.yaml:

1
yq eval '.spec.fields.keys = (load("private.yaml") | .keys)' template.yaml | kaf -

In this example, we are creating a KongCustomEntity named jwt-signer-demo-keyset in the default namespace. The type is jwt_signer_jwks, and the keyset name is demo.

Create the plugin

Now, let’s create a KongPlugin resource that will use the keyset we just created. In this case, I’m specifying ES256 as the signing algorithm.

1
2
3
4
5
6
7
8
9
10
11
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: jwt-signer-demo
namespace: default
plugin: jwt-signer
config:
access_token_jwks_uri: https://<keycloak>/auth/realms/demo/protocol/openid-connect/certs
access_token_keyset: demo
channel_token_optional: true
access_token_signing_algorithm: ES256

Apply the plugin

Finally, apply the plugin to the route we created earlier:

1
kubectl annotate ingress httpbin-route konghq.com/plugins=jwt-signer-demo

Test the plugin

Now that the plugin is applied, when you send a request to /test/anything, you should receive a 401 response. After making a request with a valid token from Keycloak, the upstream httpbin service should return a response that includes a token signed by the JWT Signer plugin.

You can verify the token header kid to confirm it was signed with the key we generated earlier.

That’s all I want to show you today, see you in the next one.