╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────╮ │89 .ssh => struct { │ │90 io: std.Io, │ │91 allocator: std.mem.Allocator, │ │92 - sshd_process: ?std.process.Child, │ │92 serve_process: ?std.process.Child,│ │93 }, │ │94 }, │ ╰──────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │110 }, │ │111 .raw => unreachable, │ │112 .ssh => { │ │114 - // create priv host key │ │115 - const host_key_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/host_key", .{}); │ │116 - defer host_key_file.close(io); │ │117 - try host_key_file.writeStreamingAll(io, │ │118 - \\-----BEGIN OPENSSH PRIVATE KEY----- │ │119 - \\b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS │ │120 - \\1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS1ppUfk8n7yvVKEgz3tXjt4q76VGuj │ │121 - \\LcQlRwmogzovV40LLcX0aTObZlQaLWfzJMNpCa/ztMpQlr86nsarE4lEAAAAqLe43zK3uN │ │122 - \\8yAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLWmlR+TyfvK9UoS │ │123 - \\DPe1eO3irvpUa6MtxCVHCaiDOi9XjQstxfRpM5tmVBotZ/Mkw2kJr/O0ylCWvzqexqsTiU │ │124 - \\QAAAAgQ+LCk30ZNJxb2Da5JL+QOFWCMf7bgXCWcEzhEGGvFWYAAAALcmFkYXJAcm9hcmsB │ │125 - \\AgMEBQ== │ │126 - \\-----END OPENSSH PRIVATE KEY----- │ │127 - \\ │ │128 - ); │ │129 - if (.windows != builtin.os.tag) { │ │130 - try host_key_file.setPermissions(io, @enumFromInt(0o600)); │ │131 - } │ │132 - │ │133 - // create priv client key │ │134 - const priv_key_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/key", .{}); │ │135 - defer priv_key_file.close(io); │ │136 - try priv_key_file.writeStreamingAll(io, │ │137 - \\-----BEGIN OPENSSH PRIVATE KEY----- │ │138 - \\b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW │ │139 - \\QyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22JgAAAJA+7hikPu4Y │ │140 - \\pAAAAAtzc2gtZWQyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22Jg │ │141 - \\AAAEDVlopOMnKt/7by/IA8VZvQXUS/O6VLkixOqnnahUdPCKeIs8mJqigBZ5y84J4COgnA │ │142 - \\JJ5bHPKy+lM2SliMXbYmAAAAC3JhZGFyQHJvYXJrAQI= │ │143 - \\-----END OPENSSH PRIVATE KEY----- │ │144 - \\ │ │145 - ); │ │146 - if (.windows != builtin.os.tag) { │ │147 - try priv_key_file.setPermissions(io, @enumFromInt(0o600)); │ │148 - } │ │149 - │ │150 - // create pub key │ │151 - const pub_key_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/key.pub", .{}); │ │152 - defer pub_key_file.close(io); │ │153 - try pub_key_file.writeStreamingAll(io, │ │154 - \\ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark │ │155 - \\ │ │156 - ); │ │157 - if (.windows != builtin.os.tag) { │ │158 - try pub_key_file.setPermissions(io, @enumFromInt(0o600)); │ │159 - } │ │160 - │ │161 - // create authorized_keys file │ │162 - const auth_keys_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/authorized_keys", .{}); │ │163 - defer auth_keys_file.close(io); │ │164 - try auth_keys_file.writeStreamingAll(io, │ │165 - \\ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark │ │166 - \\ │ │167 - ); │ │168 - if (.windows != builtin.os.tag) { │ │169 - try auth_keys_file.setPermissions(io, @enumFromInt(0o600)); │ │170 - } │ │171 - │ │172 - // create known_hosts file │ │173 - const known_hosts_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/known_hosts", .{}); │ │174 - defer known_hosts_file.close(io); │ │175 - const port_str = std.fmt.comptimePrint("{}", .{port}); │ │176 - try known_hosts_file.writeStreamingAll(io, "[localhost]:" ++ port_str ++ " ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLWmlR+TyfvK9UoSDPe1eO3irvpUa6MtxCVHCaiDOi9XjQstxfRpM5tmVBotZ/Mkw2kJr/O0ylCWvzqexqsTiUQ=");│ │177 - if (.windows != builtin.os.tag) { │ │178 - try known_hosts_file.setPermissions(io, @enumFromInt(0o600)); │ │179 - } │ │180 - │ │181 - // create sshd_config file │ │182 - const sshd_config_str = blk: { │ │183 - // SetEnv PATH=... allows us to propagate the test process's PATH │ │184 - // to the spawned login shell (in sshd). │ │185 - // │ │186 - // without it, shells that don't auto-resource PATH on startup (e.g. nushell │ │187 - // on NixOS) through /etc/set-environment will fail to find │ │188 - // git-upload-pack / git-receive-pack. │ │189 - const base_config = │ │190 - \\AuthenticationMethods publickey │ │191 - \\PubkeyAuthentication yes │ │192 - \\PasswordAuthentication no │ │193 - \\StrictModes no │ │194 - //SetEnv PATH={s} -- if we find $PATH defined. │ │195 - ; │ │196 - var env_map = try std.process.Environ.createMap(std.testing.io_instance.environ.process_environ, allocator); │ │197 - defer env_map.deinit(); │ │198 - │ │199 - const config_str = if (env_map.get("PATH")) |path_str| │ │200 - try std.fmt.allocPrint(allocator, "{s}\n" ++ "SetEnv PATH={s}\n", .{ base_config, path_str }) │ │201 - else │ │202 - try std.fmt.allocPrint(allocator, "{s}", .{base_config}); │ │203 - │ │204 - break :blk config_str; │ │205 - }; │ │206 - defer allocator.free(sshd_config_str); │ │207 - │ │208 - const sshd_config_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/sshd_config", .{}); │ │209 - defer sshd_config_file.close(io); │ │210 - try sshd_config_file.writeStreamingAll(io, sshd_config_str); │ │211 - if (.windows != builtin.os.tag) { │ │212 - try sshd_config_file.setPermissions(io, @enumFromInt(0o600)); │ │213 - } │ │214 - │ │215 - // create sshd.sh contents │ │216 - const cwd_path = try std.process.currentPathAlloc(io, allocator); │ │217 - defer allocator.free(cwd_path); │ │218 - const host_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "host_key" }); │ │219 - defer allocator.free(host_key_path); │ │220 - const auth_keys_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "authorized_keys" }); │ │221 - defer allocator.free(auth_keys_path); │ │222 - const sshd_contents = try std.fmt.allocPrint( │ │223 - allocator, │ │224 - "#!/bin/sh\nexec $(which sshd) -p {} -f sshd_config -h \"{s}\" -D -e -o AuthorizedKeysFile=\"{s}\"", │ │225 - .{ port, host_key_path, auth_keys_path }, │ │226 - ); │ │227 - defer allocator.free(sshd_contents); │ │113 + // generate a fresh client ed25519 key. haxy's │ │114 + // in-process SSH accepts any ed25519 key whose │ │115 + // signature verifies, so there's no authorized_keys │ │116 + // file or sshd setup to do. │ │117 + const priv_key_path = try std.fs.path.join(allocator, &.{ temp_dir_name, "key" }); │ │118 + defer allocator.free(priv_key_path); │ │119 │ │229 - // if path has a space char, it fucks up sshd │ │230 - try std.testing.expect(null == std.mem.indexOfScalar(u8, auth_keys_path, ' ')); │ │120 + var keygen = try std.process.spawn(io, .{ │ │121 + .argv = &.{ "ssh-keygen", "-t", "ed25519", "-N", "", "-q", "-f", priv_key_path }, │ │122 + .stdin = .ignore, │ │123 + .stdout = .ignore, │ │124 + .stderr = .ignore, │ │125 + }); │ │126 + const term = try keygen.wait(io); │ │127 + try std.testing.expect(term == .exited and term.exited == 0); │ │128 │ │232 - // create sshd.sh │ │233 - { │ │234 - const sshd_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/sshd.sh", .{}); │ │235 - defer sshd_file.close(io); │ │236 - try sshd_file.writeStreamingAll(io, sshd_contents); │ │237 - if (.windows != builtin.os.tag) { │ │238 - try sshd_file.setPermissions(io, .executable_file); │ │239 - } │ │240 - } │ │129 return .{ │ │130 .core = .{ │ │131 .io = io, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │132 .allocator = allocator,│ │245 - .sshd_process = null, │ │133 .serve_process = null, │ │134 }, │ │135 }; │ ╰────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │160 }, │ │161 .raw => unreachable, │ │162 .ssh => { │ │276 - std.debug.assert(self.core.sshd_process == null); │ │163 std.debug.assert(self.core.serve_process == null); │ │164 │ │165 const cwd_path = try std.process.currentPathAlloc(self.core.io, self.core.allocator);│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │166 defer self.core.allocator.free(cwd_path); │ │167 const haxy_path = try std.fs.path.join(self.core.allocator, &.{ cwd_path, "zig-out/bin/haxy" }); │ │168 defer self.core.allocator.free(haxy_path); │ │283 - const ssh_listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port + 1000}); │ │284 - const http_listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port + 2000}); │ │169 + // haxy's in-process SSH listens on `port`. give the │ │170 + // (unused-by-this-test) HTTP listener a unique port │ │171 + // too, so concurrent tests don't collide on 8080. │ │172 + const ssh_listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port}); │ │173 + const http_listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port + 1000}); │ │174 │ │175 self.core.serve_process = try std.process.spawn(self.core.io, .{ │ │176 .argv = &.{ haxy_path, "serve", "--http-listen", http_listen_arg, "--ssh-listen", ssh_listen_arg, "--data-dir", temp_dir_name },│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │177 .stdin = .ignore, │ │289 - .stdout = .ignore, │ │290 - .stderr = .ignore, │ │291 - }); │ │292 - │ │293 - const serve_address = try std.Io.net.IpAddress.parseIp4("127.0.0.1", port + 1000); │ │294 - for (0..50) |_| { │ │295 - const stream = serve_address.connect(self.core.io, .{ .mode = .stream }) catch {│ │296 - try std.Io.sleep(self.core.io, .fromMilliseconds(100), .real); │ │297 - continue; │ │298 - }; │ │299 - var probe_buf = [_]u8{0} ** 16; │ │300 - var probe_writer = stream.writer(self.core.io, &probe_buf); │ │301 - try probe_writer.interface.writeAll("probe\n"); │ │302 - try probe_writer.interface.flush(); │ │303 - stream.close(self.core.io); │ │304 - break; │ │305 - } │ │306 - │ │307 - self.core.sshd_process = try std.process.spawn(self.core.io, .{ │ │308 - .argv = &.{"./sshd.sh"}, │ │309 - .cwd = .{ .path = temp_dir_name }, │ │310 - .stdin = .pipe, │ │178 .stdout = .ignore, │ │179 .stderr = .ignore, │ │180 }); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │205 }, │ │206 .raw => unreachable, │ │207 .ssh => { │ │341 - if (self.core.sshd_process) |*process| { │ │342 - _ = process.kill(self.core.io); │ │343 - self.core.sshd_process = null; │ │344 - } │ │208 if (self.core.serve_process) |*process| {│ │209 _ = process.kill(self.core.io); │ │210 self.core.serve_process = null; │ ╰──────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │214 } │ │215 } │ │216 }; │ │354 -} │ │355 - │ │356 -fn uploadPackCommand( │ │357 - comptime is_ssh: bool, │ │358 - allocator: std.mem.Allocator, │ │359 - cwd_path: []const u8, │ │360 - comptime port: u16, │ │361 -) ![]const u8 { │ │362 - return if (is_ssh) │ │363 - std.fmt.allocPrint(allocator, "{s}/zig-out/bin/haxy ssh-git --ssh-connect 127.0.0.1:{} --service upload-pack", .{ cwd_path, port + 1000 }) │ │364 - else │ │365 - allocator.dupe(u8, "git-upload-pack"); │ │366 -} │ │367 - │ │368 -fn receivePackCommand( │ │369 - comptime is_ssh: bool, │ │370 - allocator: std.mem.Allocator, │ │371 - cwd_path: []const u8, │ │372 - comptime port: u16, │ │373 -) ![]const u8 { │ │374 - return if (is_ssh) │ │375 - std.fmt.allocPrint(allocator, "{s}/zig-out/bin/haxy ssh-git --ssh-connect 127.0.0.1:{} --service receive-pack", .{ cwd_path, port + 1000 })│ │376 - else │ │377 - allocator.dupe(u8, "git-receive-pack"); │ │217 } │ │218 │ │219 fn testFetch( │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │311 .file => false, │ │312 .wire => |wire_kind| .ssh == wire_kind, │ │313 }; │ │314 + // haxy generates a fresh host key on every run, so don't try to record │ │315 + // it. accept the unknown host and skip writing to known_hosts. the key │ │316 + // identity flag picks the ed25519 key created in Server.init. │ │317 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: { │ │476 - const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });│ │477 - defer allocator.free(known_hosts_path); │ │478 - │ │318 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); │ │319 defer allocator.free(priv_key_path); │ │320 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │482 - break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path }); │ │321 + break :blk try std.fmt.allocPrint(allocator, "ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o IdentityFile=\"{s}\"", .{priv_key_path});│ │322 } else null; │ │323 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); │ │324 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯