Skip to content

Krun

krun

Krun (KVM-microVM) host-side provisioning: identity + runtime factory.

KrunHost is the host-side companion to sandbox's KrunRuntime. One instance per launch bundles the three things the orchestrator needs:

  • the vault-backed %host keypair materialised to tmpfs (cached, so the vault DB is opened once even when runtime and launch_args are both called);
  • a production KrunRuntime with the TCP-over-passt SSH transport wired in;
  • the extra podman run arguments terok must splice in (pubkey bind-mount, runtime-signal env var, --user root override, and the pasta DNS forwarder).

Living in executor (rather than terok) keeps the rule honest: anything that owns the L0 build path also owns the trust material that makes the resulting guest reachable. Selecting krun without provisioning the key is a contradiction, so the two operations belong together.

KrunHostKeypair(private_path, public_path, public_line, fingerprint, created) dataclass

Materialised view of the %host infrastructure keypair.

Returned by ensure_krun_host_keypair. Carries the tmpfs path to the OpenSSH-PEM private key (ready for ssh -i) and the matching public-key file (ready to bind-mount into the krun guest at /etc/ssh/authorized_keys.d/terok), so callers don't have to redo the DER→PEM conversion or re-derive the public line from raw blobs.

private_path instance-attribute

tmpfs path holding the OpenSSH-PEM private key (0600 perms).

public_path instance-attribute

Sibling .pub file (0644 perms) carrying the public line.

public_line instance-attribute

Single-line OpenSSH public key (ssh-ed25519 AAAA… comment).

fingerprint instance-attribute

Canonical SHA256:… fingerprint over the SSH wire-format blob.

created instance-attribute

True when this call minted the key; False when it was loaded.

KrunHost(*, cfg=None)

Host-side krun launch context — vault keypair + runtime + launch args.

One instance per launch. The first access to keypair opens the vault DB and materialises the %host private/public keypair to tmpfs; every subsequent access on the same instance reuses the cached result, so calling both runtime and launch_args pays that cost only once.

Requires the vault to be unlocked — the krun runtime is gated on experimental: true upstream and assumes the operator has the vault open for the session. A NoPassphraseError from the underlying vault open propagates unchanged so the orchestrator can render its own remediation hint.

Parameters:

Name Type Description Default
cfg SandboxConfig | None

Sandbox config used to open the credential DB. None means use the zero-arg default — appropriate for standalone executor flows; terok injects its own enriched config when calling.

None

Bind a krun launch to cfg; the keypair is loaded on first access.

Source code in src/terok_executor/krun.py
def __init__(self, *, cfg: SandboxConfig | None = None):
    """Bind a krun launch to *cfg*; the keypair is loaded on first access."""
    self._cfg = cfg
    self._keypair: KrunHostKeypair | None = None

__slots__ = ('_cfg', '_keypair') class-attribute instance-attribute

keypair property

Vault-backed %host keypair materialised to tmpfs (cached).

First access opens the vault DB and writes the OpenSSH-PEM private + public-key line to a tmpfs cache directory; subsequent accesses on the same instance return the same object without reopening the vault.

runtime()

Construct a production KrunRuntime in one call.

Wires together the three production pieces — the cached host keypair, the TCP-over-passt SSH transport, and a fresh PodmanRuntime for lifecycle. The experimental-flag gate stays on the orchestrator side (this factory is reachable only when the gate is open).

Source code in src/terok_executor/krun.py
def runtime(self) -> KrunRuntime:
    """Construct a production [`KrunRuntime`][terok_sandbox.KrunRuntime] in one call.

    Wires together the three production pieces — the cached host
    keypair, the TCP-over-passt SSH transport, and a fresh
    [`PodmanRuntime`][terok_sandbox.PodmanRuntime] for lifecycle.
    The experimental-flag gate stays on the orchestrator side (this
    factory is reachable only when the gate is open).
    """
    kp = self.keypair
    transport = TcpSSHTransport(
        identity_file=kp.private_path,
        endpoint_resolver=podman_port_resolver(),
    )
    return KrunRuntime(transport=transport, podman=PodmanRuntime())

launch_args()

Extra podman run args terok must splice in for a krun launch.

Four things that all reach across the orchestrator/runtime boundary into executor's domain — the L0 image, the host keypair, the in-guest init-ssh-and-repo.sh, and the DNS forwarder address — so they live here together rather than being open-coded in terok's _project_runtime_flags:

  • Bind-mount the live host pubkey over the L0's empty placeholder at /etc/ssh/authorized_keys.d/terok. z is the shared SELinux relabel (never Z — the host pubkey is host-wide and concurrent containers share the source).
  • Set TEROK_CONTAINER_RUNTIME=krun so the init script's krun gate fires.
  • Override the L0's USER dev directive with --user root so the in-guest sshd can start, listen on TCP 22, and drop to the authenticated user on connection. USER dev is the right default under crun (AI agents that refuse uid 0); under krun the session uid comes from which ssh user@… the operator picks.
  • --dns 169.254.1.1 — kept for shield-bypass; under shield-up the bind-mounted resolv.conf overrides this anyway.

Doesn't include --runtime krun itself or krun's microVM-sizing annotations — those are orchestrator-level decisions terok keeps.

Source code in src/terok_executor/krun.py
def launch_args(self) -> list[str]:
    """Extra ``podman run`` args terok must splice in for a krun launch.

    Four things that all reach across the orchestrator/runtime
    boundary into executor's domain — the L0 image, the host
    keypair, the in-guest ``init-ssh-and-repo.sh``, and the DNS
    forwarder address — so they live here together rather than
    being open-coded in terok's ``_project_runtime_flags``:

    - Bind-mount the live host pubkey over the L0's empty
      placeholder at ``/etc/ssh/authorized_keys.d/terok``.  ``z`` is
      the shared SELinux relabel (never ``Z`` — the host pubkey is
      host-wide and concurrent containers share the source).
    - Set ``TEROK_CONTAINER_RUNTIME=krun`` so the init script's krun
      gate fires.
    - Override the L0's ``USER dev`` directive with ``--user root``
      so the in-guest sshd can start, listen on TCP 22, and drop to
      the authenticated user on connection.  ``USER dev`` is the
      right default under crun (AI agents that refuse uid 0); under
      krun the session uid comes from which ``ssh user@…`` the
      operator picks.
    - ``--dns 169.254.1.1`` — kept for shield-bypass; under
      shield-up the bind-mounted resolv.conf overrides this anyway.

    Doesn't include ``--runtime krun`` itself or krun's microVM-sizing
    annotations — those are orchestrator-level decisions terok keeps.
    """
    kp = self.keypair
    return [
        "-v",
        f"{kp.public_path}:/etc/ssh/authorized_keys.d/terok:ro,z",
        "-e",
        "TEROK_CONTAINER_RUNTIME=krun",
        "--user",
        "root",
        "--dns",
        _PASTA_DNS_FORWARDER,
    ]

ensure_krun_host_keypair(*, cfg=None, runtime_dir=None)

Load (or mint, first call) the %host keypair and materialise it to tmpfs.

The vault is the system of record: the keypair lives in the sandbox credential DB under the %host infrastructure scope. This helper opens the DB, calls ensure_infra_keypair (which generates the key on first call and reloads it thereafter), and writes the OpenSSH-PEM private + the public-key line into runtime_dir (default: namespace_runtime_dir()).

The orchestrator bind-mounts public_path into the running krun guest at /etc/ssh/authorized_keys.d/terok so the guest's sshd accepts our private key. The L0 image itself ships an empty placeholder at that path; the bind-mount overlays it.

Rotation = clear the %host scope in the vault, then re-run. Typically called per task launch under krun (idempotent — loads on subsequent calls). New tasks pick up the new key; in-flight tasks keep what they had until they're stopped.

Requires the vault to be unlocked — the krun runtime is gated on experimental: true upstream and assumes the operator has the vault open for the session. A NoPassphraseError propagates unchanged so the orchestrator can render its own remediation hint.

Parameters:

Name Type Description Default
cfg SandboxConfig | None

Sandbox config used to open the credential DB. None means use the zero-arg default — appropriate for standalone executor flows; terok injects its own enriched config when calling.

None
runtime_dir Path | None

Override for the tmpfs cache directory. None uses namespace_runtime_dir, with a hard refusal to fall back to persistent disk.

None
Source code in src/terok_executor/krun.py
def ensure_krun_host_keypair(
    *,
    cfg: SandboxConfig | None = None,
    runtime_dir: Path | None = None,
) -> KrunHostKeypair:
    """Load (or mint, first call) the ``%host`` keypair and materialise it to tmpfs.

    The vault is the system of record: the keypair lives in the sandbox
    credential DB under the ``%host`` infrastructure scope.  This
    helper opens the DB, calls
    [`ensure_infra_keypair`][terok_sandbox.ensure_infra_keypair] (which
    generates the key on first call and reloads it thereafter), and
    writes the OpenSSH-PEM private + the public-key line into
    *runtime_dir* (default:
    [`namespace_runtime_dir()`][terok_util.paths.namespace_runtime_dir]).

    The orchestrator bind-mounts ``public_path`` into the running
    krun guest at ``/etc/ssh/authorized_keys.d/terok`` so the
    guest's sshd accepts our private key.  The L0 image itself ships
    an empty placeholder at that path; the bind-mount overlays it.

    Rotation = clear the ``%host`` scope in the vault, then re-run.
    Typically called per task launch under krun (idempotent — loads
    on subsequent calls).  New tasks pick up the new key; in-flight
    tasks keep what they had until they're stopped.

    Requires the vault to be unlocked — the krun runtime is gated on
    ``experimental: true`` upstream and assumes the operator has the
    vault open for the session.  A ``NoPassphraseError`` propagates
    unchanged so the orchestrator can render its own remediation hint.

    Args:
        cfg: Sandbox config used to open the credential DB.  ``None``
            means use the zero-arg default — appropriate for standalone
            executor flows; terok injects its own enriched config when
            calling.
        runtime_dir: Override for the tmpfs cache directory.  ``None``
            uses [`namespace_runtime_dir`][terok_util.paths.namespace_runtime_dir],
            with a hard refusal to fall back to persistent disk.
    """
    target_dir = _ensure_safe_runtime_dir(runtime_dir)
    private = target_dir / f"{_HOST_KEYPAIR_BASENAME}.key"
    public = target_dir / f"{_HOST_KEYPAIR_BASENAME}.key.pub"

    db = (cfg or SandboxConfig()).open_credential_db(prompt_on_tty=False)
    try:
        infra = ensure_infra_keypair("%host", db=db, comment="krun-host (terok)")
    finally:
        db.close()

    _write_atomic(private, infra.private_pem, mode=0o600)
    _write_atomic(public, (infra.public_line + "\n").encode(), mode=0o644)
    return KrunHostKeypair(
        private_path=private,
        public_path=public,
        public_line=infra.public_line,
        fingerprint=infra.fingerprint,
        created=infra.created,
    )