Klodd
Klodd is a service that makes it easy to deploy per-team instances for CTF challenges. Though the service is great, the documentation for new CTF developers is less than ideal.
Some CTF challenges, particularly those involving prototype pollution and remote code execution, can potentially allow competitors to interfere with each other, whether out of malice or simply by solving the challenge. In these situations, it can be difficult to deploy challenges in a way that keeps CTFs fun and competitive for everyone. Klodd solves this problem, by giving each team their own deployment that is identical to everyone else’s.
You are responsible for properly configuring a wildcard DNS record to point these subdomains at your cluster. Ensure that TLS is properly configured in Traefik as well.
Authentiation
Just a side note, Klodd Auth can be configured through rCTF. View more information about this here: Klodd Auth. I would reccomend using rCTF auth, as it worked seamlessly for me.
To set up the rCTF OAuth required by Klodd, you’ll need to configure a
/auth
route that redirects incoming requests to the Klodd auth endpoint and injects the users rCTF token as described by their docs here.There’s no reason to require a Cloudflare worker though; on our custom frontend, we implemented this with a Next.js route handler with similar logic.
Installation
Install Docker, k3s and kubectl
Docker Registry
Your challenge docker images must be hosted on a docker registry. This can be done through Docker Hub or by hosting your own registry with docker run -d -p 5000:5000 --name registry registry:2.7
The exposed port can be whatever you want, just make sure to pull from that port in the future.
Build your image with docker build -t localhost:5000/image-name:tag .
Then, push it to the registry with docker push localhost:5000/image-name:tag
Traefik
Klodd depends on Traefik as a ingress controller. Though you can configure Traefik yourself, it is easier to use the default Helm chart.
To install the Traefik with Helm, run the following commands:
- Ensure you have Helm v3 > 3.9.0 installed.
- Add the Traefik Helm repository:
helm repo add traefik https://traefik.github.io/charts
- Deploy Traefik:
helm install traefik traefik/traefik
If you wish to restart Traefik, you can run helm uninstall traefik
to stop the service, and then helm install traefik traefik/traefik
.
If you wish to run Traefik with custom values you can use helm install traefik traefik/traefik -f values.yaml
.
Install the Klodd CRDs and ClusterRole
Use the following commands to install the Klodd CRDs and ClusterRole.
# Install Klodd Challenge CRD
kubectl apply -f https://raw.githubusercontent.com/TJCSec/klodd/master/manifests/klodd-crd.yaml
# Install Klodd ClusterRole
kubectl apply -f https://raw.githubusercontent.com/TJCSec/klodd/master/manifests/klodd-rbac.yaml
Starting Klodd
In yaml files listed below, you only need to worry about fields with comments.
Place the following in a file named workloads.yaml
:
apiVersion: v1
kind: Namespace
metadata:
name: klodd
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: klodd
namespace: klodd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: klodd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: klodd #
subjects:
- kind: ServiceAccount
name: klodd
namespace: klodd
---
apiVersion: v1
kind: Secret
metadata:
name: klodd
namespace: klodd
type: Opaque
data: # This is customizable. Base64 decode it or view the sample config.yaml below
config.yaml: Y2hhbGxlbmdlRG9tYWluOiBsb2NhbGhvc3QuZGlyZWN0Cmt1YmVDb25maWc6IGNsdXN0ZXIKcHVibGljVXJsOiBodHRwczovL2tsb2RkLmxvY2FsaG9zdC5kaXJlY3QKcmN0ZlVybDogaHR0cHM6Ly9iMDFsZXJzYy50Zgp0cmFlZmlrOiAKICBodHRwRW50cnlwb2ludDogd2Vic2VjdXJlCiAgdGNwRW50cnlwb2ludDogdGNwCiAgdGNwUG9ydDogMTMzNwppbmdyZXNzOiAKICBuYW1lc3BhY2VTZWxlY3RvcjoKICAgIG1hdGNoTGFiZWxzOgogICAgICBrdWJlcm5ldGVzLmlvL21ldGFkYXRhLm5hbWU6IGRlZmF1bHQgI2Vuc3VyZSB0aGlzIG1hdGNoZXMgdGhlIG5hbWVzcGFjZSB0cmFlZmlrIGlzIG9uLgogIHBvZFNlbGVjdG9yOgogICAgbWF0Y2hMYWJlbHM6CiAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHRyYWVmaWsKc2VjcmV0S2V5OiAicmFuZG9tbHkgZ2VuZXJhdGVkIHNlY3JldCBrZXkiCnJlY2FwdGNoYToKICBzaXRlS2V5OiA2TGVJeEFjVEFBQUFBSmNaVlJxeUhoNzFVTUlFR05RX01YamlaS2hJCiAgc2VjcmV0S2V5OiA2TGVJeEFjVEFBQUFBR0ctdkZJMVRuUld4TVpORnVvako0V2lmSldl
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: klodd
namespace: klodd
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: klodd
template:
metadata:
labels:
app.kubernetes.io/name: klodd
spec:
serviceAccountName: klodd #
volumes:
- name: config
secret:
secretName: klodd
containers:
- name: klodd
image: ghcr.io/tjcsec/klodd:master #
volumeMounts:
- name: config
mountPath: /app/config/
readOnly: true
ports:
- name: public
containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: klodd
namespace: klodd
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: klodd
ports:
- name: public
port: 5000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: klodd
namespace: klodd
spec:
rules:
- host: klodd.localhost.direct #change this eventually to your public domain
http:
paths:
- backend:
service:
name: klodd
port:
number: 5000
path: /
pathType: ImplementationSpecific
View the example base64 decoded config.yaml
below:
challengeDomain: localhost.direct # use localhost.direct for local testing, or your domain for production
kubeConfig: cluster
publicUrl: https://klodd.localhost.direct # eventually change this to your domain
rctfUrl: https://b01lersc.tf # Your rCTF URL here.
traefik:
httpEntrypoint: websecure
tcpEntrypoint: tcp
tcpPort: 1337
ingress:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default #ensure this matches the namespace traefik is on.
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
secretKey: "randomly generated secret key"
recaptcha: # These are test keys. Replace them with your own keys when moving to production.
siteKey: 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
secretKey: 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
To change the config.yaml
file, base64 encode it and replace the config.yaml
value in the workloads.yaml
file.
For local testing, you should only really need to chance the rctfUrl
. The challengeDomain
should be localhost.direct
and the publicUrl
should be https://klodd.localhost.direct
.
To deploy Klodd, run the following command:
kubectl apply -f workloads.yaml
Verify that Klodd is running by running the following:
kubectl get namespace
- Ensure the
klodd
namespace is present. You should see something like the following:NAME STATUS AGE default Active 31h klodd Active 5s kube-node-lease Active 31h kube-public Active 31h kube-system Active 31h
At this point, you should be able to go to https://klodd.localhost.direct/
and see the Klodd frontend.
Building a Challenge
If you haven’t already, build your image with docker build -t localhost:5000/image-name:tag .
Then, push it to the registry with docker push localhost:5000/image-name:tag
.
Replace the port with the one you exposed earlier if you decided not to use 5000.
Create a file named challenge.yaml
with the following contents:
apiVersion: "klodd.tjcsec.club/v1"
kind: Challenge
metadata:
name: test # The name of the resource is also used in the challenge URL. For example, the page for this challenge is accessible at /challenge/test.
spec:
name: Test Challenge # This is the name displayed on the frontend. It does not have to be related to metadata.name in any way.
timeout: 10000 # Each instance will be stopped after this many milliseconds.
pods:
- name: app # the name of the pod, ensure this and expose.pod match
ports:
- port: 80 # listed port inside the container, ensure this matches the exposed port
spec:
containers:
- name: main
image: localhost:5000/image-name:tag # The image to run for this pod.
resources:
requests:
memory: 100Mi
cpu: 75m
limits:
memory: 250Mi
cpu: 100m
automountServiceAccountToken: false
expose:
kind: http
pod: app # the name of the pod, ensure this and pods.name match
port: 80 # the port to expose, ensure this matches the listed port in pods.ports.port
middlewares:
- contentType:
autoDetect: false
- rateLimit:
average: 5
burst: 10
To deploy the challenge, run the following command:
kubectl create -f challenge.yaml
You should see something like
challenge.klodd.tjcsec.club/test created
Ensure your challenge is running properly with the following:
kubectl get challenge
- Ensure the challenge is present. You should see something like the following:
NAME STATUS AGE test Active 5s
You can delete your challenge with kubectl delete challenge test
Now, you should be able to go to https://klodd.localhost.direct/challenge/test
and see your challenge.
Congratulations! You have successfully deployed a challenge with Klodd!
More examples of Klodd deployments can be found here: Klodd Examples
Common Issues
Challenge Not Running
If you click the run button and your challenge stays in the “starting” state, there is probably an issue locating your docker image. Ensure that your image is built and pushed to the registry correctly, and that you are using the correct image name and tag in your challenge.yaml
file.
You can also check traefik/whoami:latest
as a test image to see if it is a problem with the image.
This is on port 80.
Unknown as the challenge status
If you see “unknown” as the status of your challenge, there is probably a port conflict or matching issue. Ensure you are using the same two ports in your challenge.yaml
file, and that it matches with your exposed port in the docker image.
Debugging
You can view the traefik container’s logs to see specific errors, or to find the namespace of the problematic pod.