Per-key node pins, k7d-fc pause/resume/exec, and HA-soak fixes. Playbook pins k7d 0.7.0. GitHub .deb, Launchpad PPA, and PyPI k7-sdk are 0.4.0.
15 KiB
Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.4.0] — 2026-09-19
Per-key node pins, k7d-fc pause/resume/exec, and HA-soak fixes. Playbook
pins k7d 0.7.0. GitHub .deb, Launchpad PPA, and PyPI k7-sdk are
0.4.0.
Added
- Optional per-key node placement for tenant isolation on the
shared per-node k7d daemon.
k7 generate-api-key --node <node>(repeatable) stores"nodes"on the key, analogous to-nfor namespaces. Placement uses the Node'skubernetes.io/hostnamelabel, notspec.nodeName.k7 nodes dedicate NODE --tenant IDwritesk7.katakate.org/tenantas a label and a NoSchedule taint so other sandboxes cannot share that daemon.k7 nodes listshows the K3s Node names (Linux hostname /kubernetes.io/hostname) to pass to--node; inventory.ini does not write that label —k7_backendsonly stamps backend labels at install. A single-node key auto-pins creates/restores; a multi-node key requires an explicitnode_name/k7 create --node. Forks must already sit on an allowed node.GET /api/v1/nodes/storageis 403 for a node-scoped key. Unscoped keys keep unrestricted placement. Root on[k7_servers]is still cluster-admin; pins do not contain a host compromise — tenant sandboxes belong on[k7_agents].k7-agenthas no kube SA token and noremote-nodeingress, so a dedicated worker breakout is that tenant, not cluster-admin. - k7d-fc resume → exec: pin Firecracker v1.16.2; pause/resume/exec
integration row and filled
PERFORMANCE.mdlifecycle table.
Fixed
k7 api status/k7 api endpointfrom a laptop no longer crash withFileNotFoundError: kubectl. Withk7 config set api.urlthey print the configured URL and probeGET /health.k7 api enable/disablestill need kubectl on a cluster node and now say so instead of tracebacking.- Kata memory below 256Mi is rejected at create time instead of waiting
300s for
FailedCreatePodSandBox(docsk7.yamlused128Mi). - Create success text after
--expose-portpoints atk7 list, not a nonexistentk7 list --name. - Kata kql
--dockerlive fork / snapshot nowfsfreezes/var/lib/dockerin the docker-vehicle around VolumeSnapshot create (thaw infinally). alpine dind has nofsfreeze; it is staged from the sandbox image via the shared/tmp.syncalone left overlay2 crash-inconsistent on Longhorn r=3. k7 deletedoes not wait for k7d VM teardown. The docker-graph leftover assertion polls up to 60s instead of a 3s sleep.- Loud kfd fork reject, cluster CA snippet in the SDK/docs path, and
K7Coreaiohttp session close on teardown.
Changed
k7 install --backendis required. There is no default runtime list (kfd needs a spare disk; installing all backends grows attack surface). Inventory hosts must setk7_backends(group vars are fine).noneis the only empty set — scheduling-only masters use[k7_servers:vars] k7_backends=none. Empty/omitted is an error, not kfd.- Playbook default
k7d_versionis 0.7.0 (was 0.6.0). README, Hetzner tutorial, and inventory examples target k7 0.4.0 on GitHub, PPA, and PyPI.
[0.3.1] — 2026-09-14
HA install and API-path --docker fixes for the 0.3.0 line. README
product name is k7 (Katakate is the org).
Fixed
k7 installcopies Firecracker pins from the repo root, so a 3-node HA all-backends install no longer looks next to the tempfile playbook and fails to stagek7d-fc/install-firecracker.sh(#59).- API-path
--dockertrusts a playbook-recorded k7d version>= 0.6.0instead of a host payload path the API container cannot see, so--dockerworks for CLI/API users (#59). k7 exectakes onesh -cstring. Nestedsh -cwas joining wrong and writing a blank file, which made the README fork demo look like memory CoW was broken (#59).- HA inventory example adds
ansible_ssh_common_argsso the first master can SSH peers on a first install (#59).
Changed
- README and the Hetzner tutorial target 0.3.1 (GitHub
.deb; Launchpad PPA is still 0.2.2 until this upload publishes). Product sentences say k7; Katakate stays on org URLs, email, and the deprecated PyPI shim. The PyPI badge links tok7-sdk.
[0.3.0] — 2026-09-12
HTTPS-by-default for k7-api, cluster-wide Cilium isolation, first-class
--docker on Kata and k7d, and RuntimeClass k7-fc. The playbook now
pins k7d 0.6.0.
Changed
k7 installservesk7-apiover HTTPS by default./etc/k7/api_endpointis nowhttps://<first-master-ip>:31007. A Caddy sidecar terminates TLS; the API container and its HTTP probes stay on:8000. The default cert is a playbook-minted cluster CA (/etc/k7/tls/ca.crt) — Let's Encrypt cannot issue for a bare IP. Breaking for existing laptop clients:K7_API_URL=http://...must switch tohttps://and, on the default track, install the CA (k7 config set api.ca ./ca.crt, or--api-ca/K7_API_CA).verify=Falseon anhttps://URL is an error. Optional--api-hostname(Let's Encrypt via Caddy, not Certbot) and--api-tls-cert/--api-tls-key.--api-insecure-httprestores today's HTTP NodePort. Keys no longer travel in cleartext; there is still no rate limiting.
Added
- Cluster-wide sandbox platform isolation. A deny-only
CiliumClusterwideNetworkPolicy(k7-sandbox-platform-deny) stops every sandbox — in every egress mode, including--egress-open— from reaching the node it runs on, the other nodes, the Kubernetes API server, cloud metadata / link-local, and thekube-system+longhorn-systempods (CoreDNS excepted). Previously an--egress-opensandbox could dial the node's SSH/kubelet/k3s ports,10.43.0.1:443,k7-apiand Longhorn. The policy setsenableDefaultDeny: false; on Cilium 1.19 anegressDenysection alone would otherwise switch the selected sandboxes to default-deny and break open egress. Cilium-only:--cni flannelclusters do not get this isolation (documented inSECURITY.md). - Opt-in sandbox ingress —
--ingress-portopens TCP ports on a sandbox and--ingress-from(sandbox:<name>/namespace:<ns>/cidr:<cidr>) scopes who may connect, as stock v1NetworkPolicyrules (so it works on Flannel too). Default is unchanged: deny all ingress. An opened port with no source is reachable only from sandboxes in the same namespace; the internet needscidr:0.0.0.0/0spelled out.k7 createwarns that acidr:source does not scope in-cluster peers on Cilium (anipBlockpeer is not evaluated for pod-to-pod traffic) — usesandbox:/namespace:for that. --expose-port— publishes a sandbox port outside the cluster through aNodePortService withexternalTrafficPolicy: Local, so the pod sees the real client IP instead of a SNAT'd one. Requires a matching--ingress-port, prints the resolvedhttp://<node-ip>:<nodeport>, reports NodePorts ink7 list, and is removed byk7 delete.k7 install --api-allow-cidr(repeatable) — restricts thek7-apiNodePort to the operator's source CIDRs via aCiliumNetworkPolicyon the API pod (k7-api-ingress) plusexternalTrafficPolicy: Localso the pod sees the real client IP. Off by default: without the flag no policy is created and the Service is untouched. Cilium-only (--cni flannelfails at the CLI — the reservedhost/remote-node/health/kube-apiserverpeers the kubelet probes need are not expressible in a v1NetworkPolicy); invalid CIDRs fail before Ansible runs, and0.0.0.0/0warns. This is defence in depth for operators who know their client CIDRs, not a secure API: the NodePort is HTTPS by default (see Changed above) and there is still no rate limiting. The policy selects a pod endpoint, never the host, so a wrong CIDR never affects SSH or the k3s API port.k7 install --hubble— opt-in Cilium Hubble flow observability (relay + pinnedhubbleCLI). Off by default; not a security control. Combined with--cni flannelit fails loudly. Hubble UI is not installed (no auth, public node IP).--docker— first-class Docker as a guest service. Onk7dandk7d-fcthe guest agent supervises a pinneddockerdwith overlay2 on a per-sandbox virtio-blk disk; forks stay overlay2. On Kata (kfd/kql) the same flag injects a privileged docker-vehicle with overlay2 on a block disk (kqlpersists/forks the graph;kfdis ephemeral).--sidecar dockerremains a deprecated alias.- RuntimeClass
k7-fc(--backend k7d-fc) — Firecracker under the jailer next to the k7d daemon, CRI exec, and overlay2 on forked--dockergraphs. - Kata guest seccomp is on (
disable_guest_seccomp = false). - Playbook default
k7d_versionis 0.6.0 (was 0.5.0).--dockerandk7-fcneed that tarball; an older k7d fails loudly.
Fixed
-
API sandbox delete returned 400 on every call because
k7 deletealways removes the{name}-exposeService and thek7-apiClusterRole had noservicesverbs. Kubernetes answers 403 (not 404) when the verb is missing, so even sandboxes that never used--expose-portfailed to delete through the API. -
k7 snapshot gcleft orphan VolumeSnapshotContents behind, so snapshot-lifecycle tests (and operator deletes) pinned namespaces in Terminating onvolumesnapshotcontent-bound-protection/pvc-as-source-protection. GC now reaps contents whose VolumeSnapshot is already gone or deleting, then drops the PVC source-protection finalizer (merge-patch — strategic-merge leaves those CRD/PVC finalizers in place). Integration teardown calls that same sweep instead of a kubectl jsonpath that silently missed items. -
k7 installtreated a busy apiserver as "Cilium is missing". The DaemonSet check usedfailed_when: falseandrc != 0as absent, so a briefunable to handle the requeststartedcilium installon a cluster that already had Cilium. Presence is now present / absent / unknown: unknown retries, then fails loud, and never installs. The Hubble relay check got the same split (cilium hubble enableHelm-upgrades the agent — do not run it because kubectl blipped). A reinstall no longer failscilium status --waiton warnings when the DaemonSet is Ready. -
GET /api/v1/nodes/storageskipped namespace authorization (CWE-862 / CWE-285). A leftover from the 0.2.1 scoping fix: this cluster-scoped route was guarded only byverify_api_key, so a namespace-scoped tenant key could read cluster-wide per-node storage topology (node names, pool utilization, per-agent error strings). The route now callsauthorize_namespace(..., all_namespaces=True); scoped keys get 403, unscoped keys are unchanged. Reported privately by Ahmed Ibrahim (@skeletonsec), who held disclosure — thank you. -
k7 install --cni flannelfailed at "Apply K7 API manifests" because the directory contained aCiliumNetworkPolicyand nocilium.ioCRD exists on Flannel. Cilium-only manifests now live inmanifests/k7-api/cilium/and are applied by a separate task gated on the CNI. -
k7 forkproduced a sandbox with no network policy at all — neither the deny-ingressNetworkPolicynor the egress policy was created, so a fork of a locked-down sandbox came up with unrestricted egress. Forks now inherit the source's egress configuration and are rolled back if their policy cannot be created.
[0.2.2] — 2026-08-18
Docs and install-path release for a two-node apt install that ships
k7d 0.2.1, plus TWO_DISK documentation.
Changed
- Two-node inventory / install docs and playbook extras so a Show HN
reader can
apt installk7 and bring up k7d 0.2.1 without guessing disk layout (TWO_DISK).
[0.2.1] — 2026-08-15
Security release. Everyone running the k7-api control plane on 0.2.0 or
earlier should upgrade. Both issues were reported privately by
Jirayu Thongchotchaung (@JirayuThongchotchaung),
who held disclosure until this release was available — thank you.
Fixed
- Server-side request forgery via the sandbox
imageregistry host (CWE-918). A sandbox creation request could name a registry host that resolves to a loopback, link-local, or private address and make the control plane issue the OCI fetch on the caller's behalf. Registry hosts are now resolved and checked against public/allowlisted ranges before any fetch, thelocalhost→ plain-HTTP downgrade is gone, and redirects are disabled so an allowlisted host cannot bounce the request inward. Resolution runs off the event loop, so the check cannot stall the API.
Added
- Optional per-key namespace authorization (CWE-862 / CWE-285).
API keys can be scoped to one or more namespaces with
k7 generate-api-key -n <namespace>, enforced on every namespace-bearing endpoint; a scoped key cannot read or mutate another namespace and cannot perform all-namespaces operations. Keys without a scope keep their previous unrestricted behaviour, so this is backward compatible — scope your keys to benefit from it.
Changed
SECURITY.mdstates the supported release line accurately.- Debian packaging targets
amd64explicitly and no longer runs the test suite inside build chroots, which is what the Launchpad PPA needs.
[0.2.0] — 2026-08-11
First public release. Ships the CLI/API deb and PyPI k7-sdk.
The API image is built on the node by the install playbook; a prebuilt GHCR
image and the apt/PPA story are fast follow-ups.
Added
- Multiple sandbox backends on one install / cluster — pick per sandbox
or specialize nodes:
kata-firecracker-devmapper(kfd) — Firecracker + jailer + LVM thin-poolkata-qemu-longhorn(kql) — QEMU via Kata + Longhorn PVC root (named snapshots, restore, disk-only fork)k7d— Katakate Rust VMM /runtimeClassName: k7(warm CoW fork; install via artifact URL /--k7d-artifactuntil the publicKatakate/k7drelease is live)
- Multi-node / HA install (Ansible inventory, Longhorn topology)
- Cilium CNI with FQDN egress (
CiliumNetworkPolicy) - API + SDK parity for pause / resume / fork
- Snapshot lifecycle + GC CronJob; restore from VolumeSnapshot (
kql) - CLI talks to the API by default (
k7 api,k7 dev api rebuild) - Docker-in-VM sidecar + performance bench harness
- Firecracker jailer integration
- Python SDK published as
k7-sdk(k7_sdk;katakatedeprecated)
Changed
- Node-local ops removed from
K7Core(API/agent split)
[0.0.3]
- Debian package packaging fixes (GHCR image name casing)
[0.0.1]
- Initial tagged release