Skip to content

Architecture

KIMO is a single binary that runs five controllers and an HTTP API inside one manager process.

flowchart TB
    subgraph clients [Clients]
        PL[Players]
        SP[Scoring platform]
    end

    subgraph kimo [KIMO manager]
        API[REST API :8080]
        TC[Template controller]
        IC[Instance controller]
        LC[Lifecycle controller]
        SC[Set controller]
        NC[NetworkFence controller]
        BE[Backend adapter]
    end

    subgraph cluster [Kubernetes]
        CT[ChallengeTemplate]
        CI[ChallengeInstance]
        CS[ChallengeSet]
        NF[NetworkFence]
        W[Deployment / Service / Pod]
        NP[NetworkPolicy]
    end

    PL -->|create/list/delete instances| API
    SP -->|auth + events| BE
    API --> CI
    TC --> CT
    IC --> CI
    IC --> W
    IC --> NF
    NC --> NF
    NC --> NP
    LC --> CI
    SC --> CS
    IC -.->|phase events| BE
    LC -.->|expiry events| BE

The controllers

Template controller — validates each ChallengeTemplate: the flag secret must exist, the image must be set, and the restart policy must be Deployment-compatible. It publishes status.ready and a live count of instances referencing the template. Instances of a template that is not ready wait in Pending.

Instance controller — the core state machine. For each ChallengeInstance it creates a hardened Deployment, a Service for the exposed ports, and a NetworkFence, all owned by the instance so deletion cascades. It then watches the pod (via a label-based pod watch, since the pod is two ownership hops away) and drives the phase: no pod yet → Creating, pod ready → Running, readiness failing past the template's threshold → Unhealthy. Every phase change is pushed to the scoring backend.

Lifecycle controller — enforces TTLs. It requeues itself to wake exactly at the two boundaries that matter: entering the 60-second Expiring grace window (backend notified, so a scoreboard can warn the player), and expiry itself, where it notifies the backend and deletes the instance — Kubernetes garbage collection then removes the Deployment, Service, and NetworkPolicy.

Set controller — flips a ChallengeSet active/inactive based on its schedule window, requeuing at the next boundary so rounds open and close without external triggers. A set with no schedule is always active.

NetworkFence controller — translates each NetworkFence into a deny-by-default NetworkPolicy selecting the instance's pods. See Network isolation.

The REST API

The API server (:8080) runs alongside the controllers and talks to the cluster through the same client. Authentication is fully delegated to the active backend adapter — KIMO has no user database of its own. See the REST API reference.

The backend seam

Every lifecycle transition emits an Event to a single Backend interface:

type Backend interface {
    Name() string
    Notify(ctx context.Context, event Event) error
    Authenticate(r *http.Request) (Principal, error)
}

That interface is the only seam between KIMO and the outside scoring world — swap the adapter and nothing else changes. See Scoring backends.

Pod hardening

Challenge containers are deliberately locked down:

Setting Value
runAsNonRoot true
readOnlyRootFilesystem true (writable /tmp via emptyDir)
allowPrivilegeEscalation false
automountServiceAccountToken false

Challenge images must tolerate a read-only root filesystem; anything that only needs scratch space under /tmp works unmodified.