1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-15 23:12:44 +01:00

Add user@address:port support

This patch allows users to specify the connection port
in the store URLS like so:

```
nix store info --store "ssh-ng://localhost:22" --json
```

Previously this failed with: `error: failed to start SSH connection to 'localhost:22'`,
because the code did not distinguish the port from the hostname. This
patch remedies that problem by introducing a ParsedURL::Authority type
for working with parsed authority components of URIs.

Now that the URL parsing code is less ad-hoc we can
add more long-awaited fixes for specifying SSH connection
ports in store URIs.

Builds upon the work from bd1d2d1041.

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
Co-authored-by: John Ericson <John.Ericson@Obsidian.Systems>
This commit is contained in:
Maciej Krüger 2020-04-25 16:07:41 +02:00 committed by Sergei Zimmerman
parent c98af65da6
commit 49ba06175e
No known key found for this signature in database
18 changed files with 312 additions and 101 deletions

View file

@ -18,24 +18,62 @@ static std::string parsePublicHostKey(std::string_view host, std::string_view ss
}
}
class InvalidSSHAuthority : public Error
{
public:
InvalidSSHAuthority(const ParsedURL::Authority & authority, std::string_view reason)
: Error("invalid SSH authority: '%s': %s", authority.to_string(), reason)
{
}
};
/**
* Checks if the hostname/username are valid for use with ssh.
*
* @todo Enforce this better. Probably this needs to reimplement the same logic as in
* https://github.com/openssh/openssh-portable/blob/6ebd472c391a73574abe02771712d407c48e130d/ssh.c#L648-L681
*/
static void checkValidAuthority(const ParsedURL::Authority & authority)
{
if (const auto & user = authority.user) {
if (user->empty())
throw InvalidSSHAuthority(authority, "user name must not be empty");
if (user->starts_with("-"))
throw InvalidSSHAuthority(authority, fmt("user name '%s' must not start with '-'", *user));
}
{
std::string_view host = authority.host;
if (host.empty())
throw InvalidSSHAuthority(authority, "host name must not be empty");
if (host.starts_with("-"))
throw InvalidSSHAuthority(authority, fmt("host name '%s' must not start with '-'", host));
}
}
SSHMaster::SSHMaster(
std::string_view host,
const ParsedURL::Authority & authority,
std::string_view keyFile,
std::string_view sshPublicHostKey,
bool useMaster,
bool compress,
Descriptor logFD)
: host(host)
, fakeSSH(host == "localhost")
: authority(authority)
, hostnameAndUser([authority]() {
std::ostringstream oss;
if (authority.user)
oss << *authority.user << "@";
oss << authority.host;
return std::move(oss).str();
}())
, fakeSSH(authority.host == "localhost")
, keyFile(keyFile)
, sshPublicHostKey(parsePublicHostKey(host, sshPublicHostKey))
, sshPublicHostKey(parsePublicHostKey(authority.host, sshPublicHostKey))
, useMaster(useMaster && !fakeSSH)
, compress(compress)
, logFD(logFD)
{
if (host == "" || hasPrefix(host, "-"))
throw Error("invalid SSH host name '%s'", host);
checkValidAuthority(authority);
auto state(state_.lock());
state->tmpDir = std::make_unique<AutoDelete>(createTempDir("", "nix", 0700));
}
@ -59,14 +97,15 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
args.insert(args.end(), {"-i", keyFile});
if (!sshPublicHostKey.empty()) {
std::filesystem::path fileName = state->tmpDir->path() / "host-key";
auto p = host.rfind("@");
std::string thost = p != std::string::npos ? std::string(host, p + 1) : host;
writeFile(fileName.string(), thost + " " + sshPublicHostKey + "\n");
writeFile(fileName.string(), authority.host + " " + sshPublicHostKey + "\n");
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
}
if (compress)
args.push_back("-C");
if (authority.port)
args.push_back(fmt("-p%d", *authority.port));
// We use this to make ssh signal back to us that the connection is established.
// It really does run locally; see createSSHEnv which sets up SHELL to make
// it launch more reliably. The local command runs synchronously, so presumably
@ -77,7 +116,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
bool SSHMaster::isMasterRunning()
{
Strings args = {"-O", "check", host};
Strings args = {"-O", "check", hostnameAndUser};
addCommonSSHOpts(args);
auto res = runProgram(RunOptions{.program = "ssh", .args = args, .mergeStderrToStdout = true});
@ -142,7 +181,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(Strings && comman
Strings args;
if (!fakeSSH) {
args = {"ssh", host.c_str(), "-x"};
args = {"ssh", hostnameAndUser.c_str(), "-x"};
addCommonSSHOpts(args);
if (socketPath != "")
args.insert(args.end(), {"-S", socketPath});
@ -175,7 +214,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(Strings && comman
if (reply != "started") {
printTalkative("SSH stdout first line: %s", reply);
throw Error("failed to start SSH connection to '%s'", host);
throw Error("failed to start SSH connection to '%s'", authority.host);
}
}
@ -220,7 +259,7 @@ Path SSHMaster::startMaster()
if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
throw SysError("duping over stdout");
Strings args = {"ssh", host.c_str(), "-M", "-N", "-S", state->socketPath};
Strings args = {"ssh", hostnameAndUser.c_str(), "-M", "-N", "-S", state->socketPath};
if (verbosity >= lvlChatty)
args.push_back("-v");
addCommonSSHOpts(args);
@ -241,7 +280,7 @@ Path SSHMaster::startMaster()
if (reply != "started") {
printTalkative("SSH master stdout first line: %s", reply);
throw Error("failed to start SSH master connection to '%s'", host);
throw Error("failed to start SSH master connection to '%s'", authority.host);
}
return state->socketPath;