Alle indlæg
Engineering4 min læsning

Sådan kører dashboard-handlinger sikkert inde i din instans

Hver knap i Suble-dashboardet kører en kommando inde i din VM. Vi leverer den gennem hypervisorens guest agent — ingen indgående port, ingen SSH-nøgle hos os, og ingen rå shell krydser nogensinde API'et.

TS

The Suble team

Engineering ·

Hver handling i Suble-dashboardet — start en container, opret en databasebruger, eksponer et domæne, hent et image — kører i sidste ende en kommando inde i din instans. Hvordan den kommando når derhen er en sikkerhedsbeslutning, og vi traf den bevidst. Det er arkitekturen bag de grønne knapper.

Problemet med de oplagte løsninger

Den naive måde at køre en kommando i en kundes VM er at forbinde via SSH ind i den fra kontrolplanet, eller at køre en agent der lytter på en netværksport efter instruktioner. Begge udvider angrebsfladen præcis det forkerte sted: en permanent, internetnær kanal ind i hver kundemaskine, plus management-credentials hos os der — hvis de nogensinde lækkede — ville låse en hel flåde op.

Vi ville have det modsatte: en management-vej med intet der lytter på netværket overhovedet.

Kommandoer går gennem hypervisoren, ikke netværket

Suble kører på Proxmox og KVM, og hver instans booter med QEMU guest agent. I stedet for at nå din VM over netværket overdrager kontrolplanet en kommando til hypervisoren, som leverer den til guest agent over den private host-til-guest-kanal der allerede findes mellem dem. Output kommer tilbage samme vej.

Konsekvenserne er hele pointen: ingen indgående management-port på din instans, ingen SSH-nøgle fra os på din maskine, og ingen offentlig kontrolflade at angribe. Luk din firewall helt ned, og dashboard-handlinger virker stadig — fordi de aldrig rørte netværket.

Hvorfor det betyder noget

Der er ingen tjeneste vi driver, der lytter inde i din VM, og ingen rute fra det offentlige internet til handlingskanalen. En angriber på internettet kan ikke nå den — den er slet ikke på netværket.

En handling er data, aldrig en kommandostreng

Dashboardet sender aldrig en shell-kommando. Det sender en typet handling med strukturerede argumenter — docker.create med et image, porte og env; db.create_user med et brugernavn — og *serveren* bygger scriptet deterministisk ud fra det. Du kan ikke smugle vilkårlig shell gennem API'et, fordi API'et ikke accepterer shell.

ts
// API'et modtager { action, args } — aldrig en shell-streng.
// Serveren bygger kommandoen deterministisk:
function buildActionScript(action, args, engine) {
  switch (action) {
    case "docker.create":  return dockerCreateScript(args);
    case "db.create_user": return dbCreateUserScript(engine, args);
    case "ingress.set":    return ingressSetScript(args);
    // …
  }
}

Valider, derefter escape — hver værdi, hver gang

Før nogen brugerleveret værdi når bash, passerer den to uafhængige porte: en streng allowlist-regex for sin type (identifier, image-reference, port-mapping, hostname, adgangskode), og POSIX single-quote-escaping. Hver port alene ville stoppe injection; vi kører begge. En dårlig værdi afvises med 400 før jobbet overhovedet køes.

ts
const IMAGE_RE = /^[a-zA-Z0-9][a-zA-Z0-9_./:@-]{0,255}$/;

// 1) allowlist værdien for sin type, derefter 2) single-quote-escape den.
function dockerImagePull(ref) {
  assert(IMAGE_RE.test(ref), "invalid image reference"); // -> 400, køes aldrig
  return `docker pull ${shq(ref)}`;                      // shq() neutraliserer resten
}

Forsvar i dybden

Vi antager at enhver enkelt kontrol kan fejle. Typede handlinger, allowlist-validering, shell-escaping, per-handling-tilladelser og least-privilege-eksekvering er lagdelt, så ingen af dem alene er bærende.

Least privilege, fra ende til anden

  • Per-handling-tilladelser — læsehandlinger kræver en læserolle, ændrende handlinger en kontrolrolle; projektmedlemmer får præcis det deres rolle tillader, intet mere.
  • Management-*læsninger* — tabel-browseren, listen over databasebrugere — kører som en dedikeret, skrivebeskyttet databasekonto, aldrig admin-brugeren.
  • App-hemmeligheder (panel-adgangskoder, tokens) krypteres at-rest med AES-GCM og skrives aldrig til logs.
  • Nye app-typer tilføjes som skema-validerede JSON-manifester, så kataloget vokser uden at åbne nye kodeveje ind i din VM.

En holdbar kø du kan følge med i

Handlinger kører gennem en database-baseret jobkø. Hver instans serialiseres — en ændrende handling ad gangen — med leases, heartbeats og en reaper, så en nedbrudt worker aldrig kan efterlade din instans midt i en operation. Og fordi vi allerede har kommandoens rigtige output, streamer vi det direkte tilbage til dig: hent et image eller eksponer et domæne, og dashboardet viser det faktiske output, linje for linje, mens det sker.

bash
$ docker pull nginx:latest
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete
Status: Downloaded newer image for nginx:latest

Den samme pipeline, overalt

Docker-styring, image-håndtering, administrerede databaser (opret databaser og brugere, vis og slet brugere, gennemse tabeller), automatisk HTTPS-ingress med Let's Encrypt, private netværk — det hele kører på den samme validerede, agent-leverede vej. Selv pre-flight-tjekkene følger filosofien: før du eksponerer et domæne, slår vi dets DNS op og advarer dig hvis det endnu ikke peger på din instans, så et certifikat aldrig fejler i stilhed.

Sikkerhed du ikke behøver tænke på er målet. Den sikreste management-kanal er den med intet der lytter på den — og det er den, dit dashboard bruger ved hvert klik.

TS

Skrevet af

The Suble team

Engineering

Klar om få minutter. Betal pr. time.

Opret en konto og deploy din første server i dag — fra 30 kr./md.