Scoring backends¶
A backend connects KIMO to your scoring platform. It does two jobs:
- Authenticate REST API callers — KIMO has no user database; the backend decides who a request belongs to.
- 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. |