Перейти к содержимому

Доступ по SSH

В AgentBox нет отдельного метода для SSH. Если нужен интерактивный SSH-клиент, создайте многоразовый шаблон с OpenSSH и WebSocket-мостом.

Установите websocat на свой компьютер и подготовьте пару ключей Ed25519. По умолчанию пример читает ~/.ssh/id_ed25519 и ~/.ssh/id_ed25519.pub.

mjs
import { readFile as readSshPublicKey } from "node:fs/promises";
import { homedir as homeDirectory } from "node:os";
import pathForSsh from "node:path";
import { spawnSync as runSsh } from "node:child_process";

import { Sandbox, Template, waitForPort } from "@abox-dev/sdk";

const templateName = process.env.TEMPLATE_NAME ?? "ssh-ready";
const sshTemplate = Template()
  .fromTemplate("base")
  .aptInstall(["openssh-server", "curl"])
  .runCmd(
    "case $(uname -m) in " +
      "x86_64) asset=websocat.x86_64-unknown-linux-musl ;; " +
      "aarch64|arm64) asset=websocat.aarch64-unknown-linux-musl ;; " +
      "*) exit 1 ;; esac && " +
      "curl -fsSL -o /usr/local/bin/websocat " +
      '"https://github.com/vi/websocat/releases/download/v1.14.1/$asset" && ' +
      "chmod 755 /usr/local/bin/websocat",
    { user: "root" },
  )
  .setStartCmd(
    "sudo mkdir -p /run/sshd && " +
      "sudo ssh-keygen -A && " +
      "sudo /usr/sbin/sshd -E /tmp/sshd.log && " +
      "sudo /usr/local/bin/websocat -b --exit-on-eof " +
      "ws-l:0.0.0.0:8081 tcp:127.0.0.1:22",
    waitForPort(8081),
  );

await Template.build(sshTemplate, templateName);

const publicKeyPath =
  process.env.AGENTBOX_SSH_PUBLIC_KEY ??
  pathForSsh.join(homeDirectory(), ".ssh/id_ed25519.pub");
const privateKeyPath =
  process.env.AGENTBOX_SSH_PRIVATE_KEY ??
  pathForSsh.join(homeDirectory(), ".ssh/id_ed25519");
const publicKey = (await readSshPublicKey(publicKeyPath, "utf8")).trim();

const sandbox = await Sandbox.create(templateName, {
  network: { allowPublicTraffic: false },
});

try {
  await sandbox.commands.run(
    "sudo install -d -m 700 -o user -g user /home/user/.ssh && " +
      "printf '%s\\n' \"$SSH_PUBLIC_KEY\" | " +
      "sudo tee /home/user/.ssh/authorized_keys >/dev/null && " +
      "sudo chown user:user /home/user/.ssh/authorized_keys && " +
      "sudo chmod 600 /home/user/.ssh/authorized_keys",
    { envs: { SSH_PUBLIC_KEY: publicKey } },
  );

  const proxyCommand = process.env.AGENTBOX_SSH_PROXY_COMMAND?.replaceAll(
    "__SANDBOX_ID__",
    sandbox.sandboxId,
  ).replaceAll("__TRAFFIC_TOKEN__", sandbox.trafficAccessToken ?? "");
  const connection = runSsh(
    "ssh",
    [
      "-i",
      privateKeyPath,
      "-o",
      "IdentitiesOnly=yes",
      "-o",
      "StrictHostKeyChecking=accept-new",
      "-o",
      `ProxyCommand=${proxyCommand ?? `websocat --binary -B 65536 -H="Agentbox-Traffic-Access-Token: ${sandbox.trafficAccessToken}" wss://8081-${sandbox.sandboxId}.agentbox-runtime.ru`}`,
      `user@${sandbox.sandboxId}`,
      "printf 'SSH connection works'",
    ],
    {
      encoding: "utf8",
      env: process.env,
    },
  );
  if (connection.status !== 0) throw new Error(connection.stderr);
  console.log(connection.stdout);
} finally {
  await sandbox.kill();
}

Шаблон достаточно собрать один раз, а затем использовать для новых песочниц. В каждую песочницу добавляется только открытый ключ. WebSocket-порт закрыт от публичного доступа, поэтому процесс ssh передаёт токен трафика в websocat и не выводит его в терминал. Чтобы открыть интерактивную оболочку, уберите последнюю удалённую команду и подключите ввод и вывод процесса к терминалу.

Храните закрытый ключ и токен трафика как секреты. Не добавляйте их в шаблон, репозиторий, метаданные песочницы или вывод команд.