Skip to content

Scoring backends

A backend connects KIMO to your scoring platform. It does two jobs:

  1. Authenticate REST API callers — KIMO has no user database; the backend decides who a request belongs to.
  2. Receive lifecycle events — every instance phase change is pushed to it, so the scoreboard can show players their instance URL, warn them before expiry, and so on.

The active backend is chosen at deploy time (integration.backend in Helm, or the KIMO_BACKEND env var) and configured with an opaque JSON blob only that backend understands.

Generic (HMAC webhooks)

The default. Works with any platform that can send a bearer token and receive webhooks.

integration:
  backend: generic
  config:
    apiKey: "choose-a-strong-key"

Auth — requests must carry Authorization: Bearer <apiKey>.

Events — POSTed as JSON to every registered webhook, signed with X-KIMO-Signature: <hex HMAC-SHA256 of the body> so the receiver can verify authenticity. Register webhooks at runtime:

curl -X POST localhost:8080/api/v1/webhooks/configure \
  -H "Authorization: Bearer $KIMO_API_KEY" \
  -d '{"url": "https://scoreboard.example.com/kimo-events", "secret": "webhook-secret"}'

CTFd

integration:
  backend: ctfd
  config:
    baseUrl: "https://ctf.example.com"      # your CTFd instance
    webhookUrl: "https://ctf.example.com/plugins/kimo/events"  # optional
    apiKey: "<ctfd admin access token>"

Auth — the caller's token is validated against CTFd's /api/v1/users/me; the resolved user and team become the request's principal.

Events — POSTed to webhookUrl (if set) as:

{
  "type": "instance.running",
  "challenge_id": "web-sqli-101",
  "team_id": "team-alpha",
  "instance_url": "web-sqli-101-team-alpha.default.svc:8080"
}

The typical player flow: a "Launch instance" button on the CTFd challenge page calls KIMO's create endpoint with the player's CTFd token, and the page shows the instance URL from the instance.running event (or by polling the get-instance endpoint).

Writing your own backend

Implement the Backend interface and register a factory:

package myplatform

import (
    "context"
    "encoding/json"
    "net/http"

    "github.com/hermannchristopher/kimo/internal/integrations"
)

type backend struct{ /* your config */ }

func init() {
    integrations.Register("myplatform", func(cfg json.RawMessage) (integrations.Backend, error) {
        b := &backend{}
        if err := json.Unmarshal(cfg, b); err != nil {
            return nil, err
        }
        return b, nil
    })
}

func (b *backend) Name() string { return "myplatform" }

func (b *backend) Notify(ctx context.Context, e integrations.Event) error {
    // push e to your platform
    return nil
}

func (b *backend) Authenticate(r *http.Request) (integrations.Principal, error) {
    // resolve the caller from r (token header, session, ...)
    return integrations.Principal{Subject: "alice", Team: "team-alpha"}, nil
}

Import the package for side effects in cmd/manager/main.go, rebuild, and select it with KIMO_BACKEND=myplatform. A backend can optionally also implement WebhookRegistrar to support the /api/v1/webhooks/configure endpoint.

Event reference

Field Meaning
Type One of instance.creating, instance.running, instance.unhealthy, instance.expiring, instance.expired, instance.failed, instance.deleted.
Instance Instance name.
Challenge Template name.
Team / Player Who the instance belongs to.
Endpoint In-cluster address of the instance's service.
Reason Human-readable detail for the transition.