Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197defmodule GitSync.SshTest do use ExUnit.Case, async: true
alias GitSync.Connection alias GitSync.KnotServer alias GitSync.Ssh
setup do dir = Path.join(System.tmp_dir!(), "git-sync-ssh-#{System.unique_integer([:positive])}") File.mkdir_p!(dir) on_exit(fn -> File.rm_rf!(dir) end)
key = Path.join(dir, "id_ed25519") {_, 0} = System.cmd("ssh-keygen", ["-t", "ed25519", "-N", "", "-C", "knot", "-f", key])
%{ connection: %Connection{ kind: :tangled, base_url: "https://knot.example.com", ssh_key: File.read!(key) }, fingerprint: fingerprint(key <> ".pub") } end
@host_key "knot.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample"
describe "with_agent/3" do test "loads the key into an agent the block can reach", context do %{connection: connection, fingerprint: fingerprint} = context
{:ok, identities} = Ssh.with_agent(connection, @host_key, &ssh_add_list/1)
assert identities =~ fingerprint end
test "verifies the host against a private known_hosts file", %{connection: connection} do {:ok, command} = Ssh.with_agent(connection, @host_key, fn env -> command = env(env, "GIT_SSH_COMMAND") known_hosts = option(command, "UserKnownHostsFile")
send(self(), {:known_hosts, File.read!(known_hosts), File.stat!(known_hosts)}) {:ok, command} end)
assert command =~ "StrictHostKeyChecking=yes" assert command =~ "GlobalKnownHostsFile=/dev/null"
assert_received {:known_hosts, contents, %File.Stat{mode: mode}} assert contents == @host_key <> "\n" assert Bitwise.band(mode, 0o777) == 0o600 end
test "ignores identities the host account happens to hold", %{connection: connection} do command = connection |> captured_env() |> env("GIT_SSH_COMMAND")
assert command =~ "-F /dev/null" assert option(command, "IdentityFile") == "/dev/null" end
test "stops the agent and removes its files when the block ends", %{connection: connection} do socket = connection |> captured_env() |> env("SSH_AUTH_SOCK")
refute File.exists?(socket) refute File.exists?(Path.dirname(socket)) end
test "stops the agent when the block raises", %{connection: connection} do assert_raise RuntimeError, fn -> Ssh.with_agent(connection, @host_key, fn env -> send(self(), {:env, env}) raise "boom" end) end
assert_received {:env, env} refute File.exists?(Path.dirname(env(env, "SSH_AUTH_SOCK"))) end
test "keeps the agent's paths out of the block's output", %{connection: connection} do {:error, output} = Ssh.with_agent(connection, @host_key, fn env -> {:error, env(env, "SSH_AUTH_SOCK")} end)
refute output =~ System.tmp_dir!() end
test "runs the block untouched for a connection with no key" do connection = %Connection{kind: :forgejo, base_url: "https://codeberg.org"}
assert {:ok, "[]"} == Ssh.with_agent(connection, @host_key, fn env -> {:ok, "#{inspect(env)}"} end) end end
describe "generate_key/0" do test "returns a private key and the public key that matches it" do assert {:ok, %{private: private, public: public}} = Ssh.generate_key()
assert private =~ "BEGIN OPENSSH PRIVATE KEY" assert String.starts_with?(public, "ssh-ed25519 ")
assert derive_public(private) == public end end
describe "scan_host/2" do test "returns the keys the host offers" do %{port: port, public: public} = KnotServer.serve()
assert {:ok, scanned} = Ssh.scan_host("127.0.0.1", port)
assert scanned =~ "[127.0.0.1]:#{port} ssh-ed25519 " assert Ssh.fingerprints(scanned) == [KnotServer.fingerprint(public)] end
test "reports a host that offers nothing" do assert {:error, reason} = Ssh.scan_host("127.0.0.1", KnotServer.refuse())
assert reason =~ "no host keys" end end
describe "fingerprints/1" do test "summarises every key in a known_hosts blob" do first = KnotServer.serve() second = KnotServer.serve()
{:ok, scanned} = Ssh.scan_host("127.0.0.1", first.port) {:ok, more} = Ssh.scan_host("127.0.0.1", second.port)
assert Ssh.fingerprints(scanned <> more) == [KnotServer.fingerprint(first.public), KnotServer.fingerprint(second.public)] end
test "has nothing to say about a blank or unreadable host key" do assert Ssh.fingerprints(nil) == [] assert Ssh.fingerprints("") == [] assert Ssh.fingerprints("knot.example.com ssh-ed25519 AAAAnonsense") == [] end end
defp ssh_add_list(env) do case System.cmd("ssh-add", ["-l"], env: env, stderr_to_stdout: true) do {output, 0} -> {:ok, output} {output, _} -> {:error, output} end end
defp captured_env(connection) do {:ok, ""} = Ssh.with_agent(connection, @host_key, fn env -> send(self(), {:env, env}) {:ok, ""} end)
assert_received {:env, env} env end
defp env(env, name) do {^name, value} = List.keyfind(env, name, 0) value end
defp option(command, name) do [_, value] = Regex.run(~r/-o #{name}=(\S+)/, command) value end
defp fingerprint(pub) do {output, 0} = System.cmd("ssh-keygen", ["-lf", pub]) output |> String.split(" ") |> Enum.at(1) end
defp derive_public(private) do path = Path.join(scratch(), "id") File.write!(path, private) File.chmod!(path, 0o600)
{public, 0} = System.cmd("ssh-keygen", ["-y", "-f", path]) public end
defp scratch do dir = Path.join(System.tmp_dir!(), "git-sync-test-#{System.unique_integer([:positive])}") File.mkdir_p!(dir) on_exit(fn -> File.rm_rf!(dir) end)
dir endend