通过ssh.net在每个命令上进行双因素验证

3
我正在使用以下代码通过SSH.NET连接到Unix服务器:
ssh = new SshClient("myserver.univ.edu", Username, Password);
ssh.Connect();

The connection goes through and appears to generate no exception. The server is set up to require two factor authentication, but I get no prompt on my phone (using that as physical authenticator/OTP device). But the connection seems to be ok.

I then issue a command with this code:

SshCommand NewLookup = ssh.CreateCommand("newlookup " + IpNameOrAddress.Text))
LogText.Text += NewLookup.Execute().Replace("\n", Environment.NewLine);

And then I get the push to my phone (2nd factor verification request). Once I accept the verification request through the phone, then the command executes just fine. This would be all ok, except that...

Problem

I get a push to my phone for every subsequent command, so if I want to use that connection to run multiple commands, I have to sit on my phone clicking "Accept" for every command. So, how do I avoid getting a push on every command?


什么是空闲时间?指令之间有多长时间间隔? - BOB
1
@Jeremiah - 几秒钟不到5秒,我已经将连接超时时间增加到60秒。不过你的想法很好。 - user4843530
我刚刚将连接超时时间增加到15分钟,但没有任何效果。 - user4843530
1
问题在于您正在使用ssh中的命令通道发送命令,这与shell会话不同。虽然通常不建议这样做,但我认为您需要考虑改用ShellStream来发送命令。 - NetMage
@NetMage - 是的,这正是我所想的,但由于有关ShellStream不被推荐使用的所有警告,我不愿意去那里。请将其作为答案,并在不久后如果我没有得到其他答案,我会接受它。 - user4843530
1个回答

4
为了在单个会话中使用SSH.NET发送多个命令,您可能需要使用ShellStream。这应该将您的2FA批准减少到仅打开到主机的会话。这对于不支持命令通道但支持SSH远程终端(例如您可以putty到它们)的设备(如HPE交换机)以及命令更改(shell)环境并因此需要保持会话打开以完成任务的情况也很有用。否则,SSH命令通道是处理此问题的预期(且更好的)方式。
您可以在NuDoc - SSH.NET上找到更多SSH.NET文档,而SSH.Net项目的GitHub版本包括Windows帮助文件
以下是我编写的一些代码,用于包装ShellStream,并保留StreamReaderStreamWriter,处理从(HP)交换机读取输入并过滤出转义序列,以及读取下一个提示:
public static class SshClientExt {
    public static ExtShellStream CreateExtShellStream(this SshClient sc, string termName, uint rows, uint cols, uint width, uint height, int bufSize) =>
        new ExtShellStream(sc.CreateShellStream(termName, rows, cols, width, height, bufSize));
}

public class ExtShellStream : IDisposable {
    static Regex reEscVT100 = new Regex("\x1B\\[[^A-Z]+[A-Z]", RegexOptions.Compiled);
    static TimeSpan ReadTimeout = new TimeSpan(0, 0, 10);

    ShellStream ssh;
    StreamReader sr;
    StreamWriter sw;

    public ExtShellStream(ShellStream anSSH) {
        ssh = anSSH;
        sr = new StreamReader(ssh);
        sw = new StreamWriter(ssh);
    }

    public List<string> ReadLines() {
        // try to read all in
        long prev;
        do {
            prev = ssh.Length;
            Thread.Sleep(250);
        } while (ssh.Length != prev);

        "-".Repeat(40).Dump();
        var ans = new List<string>();

        while (true) {
            var line = sr.ReadLine();
            if (line != null) {
                line = line.Remove(reEscVT100).TrimEnd();
                $@"""{line}""".Dump();
                if (line.EndsWith("#")) // stop when prompt appears
                    break;
                else
                    ans.Add(line);
            }
            else
                Thread.Sleep(500);
        }

        return ans;
    }

    public void DumpLines() => ReadLines();

    public List<string> DoCommand(string cmd) {
        sw.Write(cmd);
        sw.Write("\r");
        sw.Flush();
        while (!ssh.DataAvailable)
            Thread.Sleep(500);
        return ReadLines().SkipWhile(l => l == cmd).ToList();
    }

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing) {
        if (!disposedValue) {
            if (disposing) {
                // prevent double dispose
                // don't dispose of sr or sw: only disposable resource is ssh
                ssh.Dispose();
            }

            disposedValue = true;
        }
    }

    // This code added to correctly implement the disposable pattern.
    public void Dispose() {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
    }
    #endregion

}

下面是一个使用SSH.Net代码实现从交换机中检索配置信息的示例函数:

public static void RetrieveConfigFiles(IDFStack idf) {
    using (var sshClient = new SshClient(idf.IPAddress, username, password)) {
        sshClient.Connect();

        using (var ssh = sshClient.CreateExtShellStream("dumb", 120, 80, 0, 0, 200000)) {
            ssh.DumpLines();
            ssh.DoCommand("no page");

            File.WriteAllLines(idf.ConfigPath, ssh.DoCommand("show running-config structured"));
            File.WriteAllLines(idf.StatusPath, ssh.DoCommand("show interfaces brief"));
            File.WriteAllLines(idf.LLDPPath, ssh.DoCommand("show lldp info remote-device detail"));
        }

        sshClient.Disconnect();
    }
}

2
你本可以只说“使用shellstream”,但你超越了自己,给出了一个非常完整的代码示例。考虑到ssh.net的文档和示例非常少,这是非常宝贵的。我无法感谢你。我已经设置了赏金,一旦SE允许,我会授予给你。 - user4843530
+1 - 虽然我很感激答案已经在评论中解释了,但总的来说这并不是一种通用的正确方法,只是由于主机的相当不寻常的设置而必要。 - Martin Prikryl
1
@MartinPrikryl 添加了一些使用时的注意事项/解释。 - NetMage
2FA并不罕见。但是在您的主机上实施2FA确实很不寻常。问题标题相当通用,而您的主机实际上具有非常专有的实现。此外,整个过程还受到每个命令身份验证的复杂性的影响,这也不是通常的情况。而且该实现并不完整,因为它不能用于“exec”通道。因此,对于从谷歌搜索实现常见2FA的方法而来的人们,将发现这个答案毫无用处。 - Martin Prikryl
1
该主机上的2FA实现非常基本。问题在于“每个命令身份验证”,这是SSH.NET实现其SshClient.Connect(仅是IP连接)和其CreateCommand(身份验证加执行命令)的属性。如果CreateCommand进行身份验证,则无论您的2FA如何实现,用户每次发出CreateCommand时都会通过它。 - user4843530
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接